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