Salome HOME
PAL10125 - by double click on reference original object becomes selected
[modules/gui.git] / src / SUIT / SUIT_FileDlg.cxx
1 //*********************************************************************************
2 // SUIT_FileDlg class is the extension of the Qt's Open/Save file dialog box.
3 // To get the file/directory name(s) call static methods:
4 //
5 // to invoke "Open file" or "Save file" dialog box
6 //    static QString getFileName(QWidget* parent, const QString& initial, const QStringList& filters, 
7 //                               const QString& caption, const bool open, const bool showQuickDir = true,
8 //                               SUIT_FileValidator* validator = 0);
9 //
10 // to invoke "Open files" dialog box (to get the multiple file selection)
11 //    static QStringList getOpenFileNames(QWidget* parent, const QString& initial, const QStringList& filters, 
12 //                                        const QString& caption, bool showQuickDir = true, 
13 //                                        SUIT_FileValidator* validator = 0);
14 //
15 // to invoke "Select directory" dialog box
16 //    static QString getExistingDirectory(QWidget* parent, const QString& initial,
17 //                                        const QString& caption, const bool showQuickDir = true);
18 //
19 // The parameters:
20 // - parent        parent widget (if 0, the current desktop is used)
21 // - initial       starting directory or file name (if null, last visited directory is used)
22 // - filters       file filters list; patterns inside the filter can be separated by ';','|' or ' ' 
23 //                 symbols
24 // - caption       dialog box's caption: if null, the default one is used
25 // - open          open flag - true for "Open File" and false for "Save File" dialog box
26 // - showQuickDir  this flag enables/disables "Quick directory list" controls
27 // - validator     you can provide custom file validator with this parameter
28 //
29 // Examples:
30 //   ...
31 //   QStringList flist;
32 //   flist.append( "Image files (*.bmp *.gif *.jpg )" );
33 //   flist.append( "All files (*.*)" );
34 //   QMyFileValidator* v = new QMyFileValidator( 0 );
35 //   QString fileName =  SUIT_FileDlg::getFileName( 0, QString::null, flist, "Dump view", false, true, v );
36 //   if ( !fileName.isEmpty() ) {
37 //      ... writing image to the file 
38 //   }
39 //   ...
40 //   QStringList flist;
41 //   flist.append( "*.cpp | *.cxx | *.c++" );
42 //   flist.append( "*.h | *.hpp | *.hxx" );
43 //   QString fileName =  SUIT_FileDlg::getFileName( desktop(), QString::null, flist, QString::null, true, true );
44 //
45 //*********************************************************************************
46
47 #include "SUIT_FileDlg.h"
48
49 #include "SUIT_Tools.h"   
50 #include "SUIT_Session.h"
51 #include "SUIT_Desktop.h"
52 #include "SUIT_MessageBox.h"
53 #include "SUIT_ResourceMgr.h"
54 #include "SUIT_FileValidator.h"
55
56 #include <qdir.h>
57 #include <qlabel.h>
58 #include <qregexp.h>
59 #include <qpalette.h>
60 #include <qobjectlist.h>
61 #include <qcombobox.h>
62 #include <qpushbutton.h>
63 #include <qapplication.h>
64
65 #define MIN_COMBO_SIZE 100
66
67 /*! If the selected file name has extension which does not match the selected filter
68  * this extension is ignored (and new one will be added). See below for details.
69  */
70 const bool IGNORE_NON_MATCHING_EXTENSION = true;
71
72 QString SUIT_FileDlg::myLastVisitedPath;
73
74 /*! Constructor */
75 SUIT_FileDlg::SUIT_FileDlg( QWidget* parent, bool open, bool showQuickDir, bool modal ) :
76 QFileDialog( parent, 0, modal ),
77 myValidator( 0 ),
78 myQuickCombo( 0 ), myQuickButton( 0 ), myQuickLab( 0 ),
79 myOpen( open ),
80 myAccepted( false )
81 {    
82   if ( parent->icon() )
83     setIcon( *parent->icon() );       
84   setSizeGripEnabled( true );
85   
86   if ( showQuickDir ) {
87     // inserting quick dir combo box
88     myQuickLab  = new QLabel(tr("LAB_QUICK_PATH"), this);
89     myQuickCombo = new QComboBox(false, this);
90     myQuickCombo->setSizePolicy(QSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed));
91     myQuickCombo->setMinimumSize(MIN_COMBO_SIZE, 0);
92     
93     myQuickButton = new QPushButton(tr("BUT_ADD_PATH"), this);
94
95     connect(myQuickCombo,  SIGNAL(activated(const QString&)), this, SLOT(quickDir(const QString&)));
96     connect(myQuickButton, SIGNAL(clicked()),                 this, SLOT(addQuickDir()));
97     addWidgets(myQuickLab, myQuickCombo, myQuickButton);
98
99     // getting dir list from settings
100     QString dirs;
101     SUIT_ResourceMgr* resMgr = SUIT_Session::session()->resourceMgr();
102     if ( resMgr )
103       dirs = resMgr->stringValue( "FileDlg", QString( "QuickDirList" ) );
104
105     QStringList dirList = QStringList::split(';', dirs, false);
106     if (dirList.count() > 0) {
107       for (unsigned i = 0; i < dirList.count(); i++)
108         myQuickCombo->insertItem(dirList[i]);
109     }
110     else {
111       myQuickCombo->insertItem(QDir::homeDirPath());
112     }
113   }
114   setMode( myOpen ? ExistingFile : AnyFile );     
115   setCaption( myOpen ? tr( "INF_DESK_DOC_OPEN" ) : tr( "INF_DESK_DOC_SAVE" ) );
116
117   // If last visited path doesn't exist -> switch to the first preferred path
118   if ( !myLastVisitedPath.isEmpty() ) {
119     if ( !processPath( myLastVisitedPath ) && showQuickDir )
120       processPath( myQuickCombo->text( 0 ) );
121   }
122   else {
123     if ( showQuickDir )
124       processPath(myQuickCombo->text( 0 ) );
125   } 
126
127   // set default file validator
128   myValidator = new SUIT_FileValidator(this);
129 }
130
131 /*! Destructor*/
132 SUIT_FileDlg::~SUIT_FileDlg() 
133 {
134   setValidator( 0 );
135 }
136
137 /*! Redefined from QFileDialog.*/
138 void SUIT_FileDlg::polish()
139 {
140   QFileDialog::polish();
141   if ( myQuickButton && myQuickLab ) {
142     // the following is a workaround for proper layouting of custom widgets
143     QValueList<QPushButton*> buttonList;
144     QValueList<QLabel*> labelList;
145     const QObjectList *list = children();
146     QObjectListIt it(*list);
147     int maxButWidth = myQuickLab->sizeHint().width();
148     int maxLabWidth = myQuickButton->sizeHint().width();
149     
150     for (; it.current() ; ++it) {
151       if ( it.current()->isA( "QLabel" ) ) {
152         int tempW = ((QLabel*)it.current())->minimumWidth();
153         if ( maxLabWidth < tempW ) maxLabWidth = tempW;
154         labelList.append( (QLabel*)it.current() );
155       }
156       else if( it.current()->isA("QPushButton") ) {
157         int tempW = ((QPushButton*)it.current())->minimumWidth();
158         if ( maxButWidth < tempW ) maxButWidth = tempW;
159         buttonList.append( (QPushButton*)it.current() );
160       }
161     }
162     if (maxButWidth > 0) {
163       QValueList<QPushButton*>::Iterator bListIt;
164       for ( bListIt = buttonList.begin(); bListIt != buttonList.end(); ++bListIt )
165         (*bListIt)->setFixedWidth( maxButWidth );
166     }
167     if (maxLabWidth > 0) {
168       QValueList<QLabel*>::Iterator lListIt;
169       for ( lListIt = labelList.begin(); lListIt != labelList.end(); ++lListIt )
170         (*lListIt)->setFixedWidth( maxLabWidth );
171     }
172   }
173 }
174
175 /*! Sets validator for file names to open/save
176  * Deletes previous validator if the dialog owns it.
177  */
178 void SUIT_FileDlg::setValidator( SUIT_FileValidator* v )
179 {
180   if ( myValidator && myValidator->parent() == this )
181     delete myValidator;
182   myValidator = v;
183 }
184
185 /*! Returns the selected file */
186 QString SUIT_FileDlg::selectedFile() const
187 {
188   return mySelectedFile;
189 }
190
191 /*! Returns 'true' if this is 'Open File' dialog 
192  *  and 'false' if 'Save File' dialog
193  */
194 bool SUIT_FileDlg::isOpenDlg() const
195 {
196   return myOpen;
197 }
198
199 /*! Closes this dialog and sets the return code to 'Accepted'
200  * if the selected name is valid ( see 'acceptData()' )
201  */
202 void SUIT_FileDlg::accept()
203 {
204   /* myAccepted 
205    * flag is used to warkaround the Qt 2.2.2 BUG: 
206    * accept() method is called twice if user presses 'Enter' key 
207    * in file name editor while file name is not acceptable by acceptData()
208    * (e.g. permission denied)
209    */
210   if ( !myAccepted ) {
211     if ( mode() != ExistingFiles ) {
212       mySelectedFile = QFileDialog::selectedFile();
213       addExtension();
214     }
215
216     if ( acceptData() ) {
217       myLastVisitedPath = dirPath();
218       QFileDialog::accept();        
219       myAccepted = true;
220     }
221   }
222   myAccepted = !myAccepted;
223 }
224
225 /*! Closes this dialog and sets the return code to 'Rejected' */
226 void SUIT_FileDlg::reject()
227 {
228   mySelectedFile = QString::null;
229   QFileDialog::reject();        
230 }
231
232 /*! Returns 'true' if selected file is valid.
233  * The validity is checked by a file validator, 
234  * if there is no validator the file is always
235  * considered as valid    
236  */
237 bool SUIT_FileDlg::acceptData()
238 {    
239   if ( myValidator )
240   {
241     if ( isOpenDlg() )
242       if ( mode() == ExistingFiles ) {
243         QStringList fileNames = selectedFiles();
244         for ( int i = 0; i < fileNames.count(); i++ ) {
245           if ( !myValidator->canOpen( fileNames[i] ) )
246             return false;
247         }
248         return true;
249       }
250       else {
251         return myValidator->canOpen( selectedFile() );
252       }
253     else 
254       return myValidator->canSave( selectedFile() );
255   }
256   return true;
257 }
258
259 /*! Adds an extension to the selected file name
260  * if the file has not it.
261  * The extension is extracted from the active filter.
262  */
263 void SUIT_FileDlg::addExtension()
264 {
265   // check if file name entered is empty
266   if ( mySelectedFile.stripWhiteSpace().isEmpty() )
267     return;
268
269   // current file extension
270   QString anExt = "." + SUIT_Tools::extension( mySelectedFile.stripWhiteSpace() ).stripWhiteSpace();
271
272   // If the file already has extension and it does not match the filter there are two choices:
273   // - to leave it 'as is'
274   // - to ignore it
275   // The behavior is defined by IGNORE_NON_MATCHING_EXTENSION constant
276   if ( anExt != "." && !IGNORE_NON_MATCHING_EXTENSION )
277     return;
278
279   // get selected file filter
280 #if QT_VERSION < 0x030000
281   QRegExp r( QString::fromLatin1("(?[a-zA-Z0-9.*? +;#|]*)?$") );
282   int len, index = r.match( selectedFilter().stripWhiteSpace(), 0, &len );
283 #else
284   QRegExp r( QString::fromLatin1("\\(?[a-zA-Z0-9.*? +;#|]*\\)?$") );
285   int index = r.search( selectedFilter().stripWhiteSpace() );
286 #endif
287
288   if ( index >= 0 ) {            
289     // Create wildcard regular expression basing on selected filter 
290     // in order to validate a file extension.
291     // Due to transformations from the filter list (*.txt *.*xx *.c++ SUIT*.* ) we 
292     // will have the pattern (\.txt|\..*xx|\.c\+\+|\..*) (as we validate extension only, 
293     // we remove everything except extension mask from the pattern
294 #if QT_VERSION < 0x030000
295     QString wildcard = selectedFilter().mid( index, len ).stripWhiteSpace();
296 #else
297     QString wildcard = selectedFilter().mid( index, r.matchedLength() ).stripWhiteSpace();
298 #endif
299     // replace '|' and ';' separators by space symbol and also brackets if there are some
300     wildcard.replace( QRegExp( "[\\|;|(|)]" )," " ); 
301
302     QString aPattern = wildcard.replace( QRegExp( "(^| )(\\s*)[0-9a-zA-Z*_?]*\\."), " \\." ).stripWhiteSpace().
303                                          replace( QRegExp( "\\s+" ), "|" ).replace( QRegExp( "[?]" ),".?" ).
304                                          replace( QRegExp( "[*]" ),".*" ).replace( QRegExp( "[+]" ),"\\+" );
305
306     // now we get the list of all extension masks and remove all which does not contain wildcard symbols
307     QStringList extList = QStringList::split( "|",aPattern );
308     for( int i = extList.count() - 1; i >= 0; i-- ) {
309       if ( !extList[i].contains( "." ) )
310         extList.remove( extList.at( i ) );
311     }
312     aPattern = extList.join( "|" );
313
314     // finalize pattern
315     QRegExp anExtRExp( "^("+ aPattern + ")$" );
316
317     // Check if the current file extension matches the pattern
318     if ( anExtRExp.match( anExt ) < 0 ) { 
319       // find first appropriate extension in the selected filter 
320       // (it should be without wildcard symbols)
321       for ( int i = 0; i < extList.count(); i++ ) {
322         QString newExt = extList[i].replace( QRegExp( "[\\\\][+]" ),"+" );
323         int res = newExt.findRev( '.' );
324         if ( res >= 0 ) 
325           newExt = newExt.mid( res + 1 );
326         if ( newExt.find( QRegExp("[*|?]" ) ) < 0 ) {
327           mySelectedFile.stripWhiteSpace();
328           mySelectedFile += mySelectedFile.endsWith(".") ? newExt : QString(".") + newExt;
329           break;
330         }
331       }
332     }
333   }
334 }
335
336 /*! Processes selection : tries to set given path or filename as selection */
337 bool SUIT_FileDlg::processPath( const QString& path )
338 {
339   if ( !path.isNull() ) {
340     QFileInfo fi( path );
341     if ( fi.exists() ) {
342       if ( fi.isFile() )
343         setSelection( path );
344       else if ( fi.isDir() )
345         setDir( path );
346       return true;
347     }
348     else {
349       if ( QFileInfo( fi.dirPath() ).exists() ) {
350         setDir( fi.dirPath() );
351         setSelection( path );
352         return true;
353       }
354     }
355   }
356   return false;
357 }
358 /*! Called when user selects item from "Quick Dir" combo box */
359 void SUIT_FileDlg::quickDir(const QString& dirPath)
360 {
361   QString aPath = dirPath;
362   if ( !QDir(aPath).exists() ) {
363     aPath = QDir::homeDirPath();
364     /*    SUIT_MessageBox::error1(this, 
365                            tr("ERR_ERROR"),
366                            tr("ERR_DIR_NOT_EXIST").arg(dirPath), 
367                            tr("BUT_OK"));*/
368     
369   }
370   //  else {
371   processPath(aPath);
372     //  }
373 }
374 /*!
375   Called when user presses "Add" button - adds current directory to quick directory
376   list and to the preferences
377 */
378 void SUIT_FileDlg::addQuickDir()
379 {
380   QString dp = dirPath();
381   if ( !dp.isEmpty() ) {
382     QDir dir( dp );
383     // getting dir list from settings
384     QString dirs;
385     SUIT_ResourceMgr* resMgr = SUIT_Session::session()->resourceMgr();
386     if ( resMgr )
387       dirs = resMgr->stringValue( "FileDlg", QString( "QuickDirList" ) );
388     QStringList dirList = QStringList::split(';', dirs, false);
389     bool found = false;
390     bool emptyAndHome = false;
391     if ( dirList.count() > 0 ) {
392       for ( unsigned i = 0; i < dirList.count(); i++ ) {
393         QDir aDir( dirList[i] );
394         if ( aDir.canonicalPath().isNull() && dirList[i] == dir.absPath() ||
395             !aDir.canonicalPath().isNull() && aDir.exists() && aDir.canonicalPath() == dir.canonicalPath() ) {
396           found = true;
397           break;
398         }
399       }
400     }
401     else {
402       emptyAndHome = dir.canonicalPath() == QDir(QDir::homeDirPath()).canonicalPath();
403     }
404     if ( !found ) {
405       dirList.append( dp );
406       resMgr->setValue( "FileDlg", QString( "QuickDirList" ), dirList.join(";") );
407       if ( !emptyAndHome )
408         myQuickCombo->insertItem( dp );
409     }
410   }
411 }
412 /*!
413   Returns the file name for Open/Save [ static ]
414 */
415 QString SUIT_FileDlg::getFileName( QWidget*            parent, 
416                                    const QString&      initial, 
417                                    const QStringList&  filters, 
418                                    const QString&      caption,
419                                    bool                open,
420                                    bool                showQuickDir, 
421                                    SUIT_FileValidator* validator )
422 {            
423   SUIT_FileDlg* fd = new SUIT_FileDlg( parent, open, showQuickDir, true );    
424   if ( !caption.isEmpty() )
425     fd->setCaption( caption );
426   if ( !initial.isEmpty() ) { 
427     fd->processPath( initial ); // VSR 24/03/03 check for existing of directory has been added to avoid QFileDialog's bug
428   }
429   fd->setFilters( filters );        
430   if ( validator )
431     fd->setValidator( validator );
432   fd->exec();
433   QString filename = fd->selectedFile();
434   delete fd;
435   qApp->processEvents();
436   return filename;
437 }
438
439
440 /*!
441   Returns the list of files to be opened [ static ]
442 */
443 QStringList SUIT_FileDlg::getOpenFileNames( QWidget*            parent, 
444                                             const QString&      initial, 
445                                             const QStringList&  filters, 
446                                             const QString&      caption,
447                                             bool                showQuickDir, 
448                                             SUIT_FileValidator* validator )
449 {            
450   SUIT_FileDlg* fd = new SUIT_FileDlg( parent, true, showQuickDir, true );    
451   fd->setMode( ExistingFiles );     
452   if ( !caption.isEmpty() )
453     fd->setCaption( caption );
454   if ( !initial.isEmpty() ) { 
455     fd->processPath( initial ); // VSR 24/03/03 check for existing of directory has been added to avoid QFileDialog's bug
456   }
457   fd->setFilters( filters );        
458   if ( validator )
459     fd->setValidator( validator );
460   fd->exec();
461   QStringList filenames = fd->selectedFiles();
462   delete fd;
463   qApp->processEvents();
464   return filenames;
465 }
466
467 /*!
468   Existing directory selection dialog [ static ]
469 */
470 QString SUIT_FileDlg::getExistingDirectory( QWidget*       parent,
471                                             const QString& initial,
472                                             const QString& caption, 
473                                             bool           showQuickDir )
474 {
475   SUIT_FileDlg* fd = new SUIT_FileDlg( parent, true, showQuickDir, true);
476   if ( !caption.isEmpty() )
477     fd->setCaption( caption );
478   if ( !initial.isEmpty() ) {
479     fd->processPath( initial ); // VSR 24/03/03 check for existing of directory has been added to avoid QFileDialog's bug
480   }
481   fd->setMode( DirectoryOnly );
482   fd->setFilters(tr("INF_DIRECTORIES_FILTER"));
483   fd->exec();
484   QString dirname = fd->selectedFile();
485   delete fd;
486   qApp->processEvents();
487   return dirname;
488   
489 }