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