]> SALOME platform Git repositories - modules/gui.git/blob - src/Qtx/QtxPathDialog.cxx
Salome HOME
684d0a40dbbc4198f7070680cbcf07bfb78c2efe
[modules/gui.git] / src / Qtx / QtxPathDialog.cxx
1 //  Copyright (C) 2007-2008  CEA/DEN, EDF R&D, OPEN CASCADE
2 //
3 //  Copyright (C) 2003-2007  OPEN CASCADE, EADS/CCR, LIP6, CEA/DEN,
4 //  CEDRAT, EDF R&D, LEG, PRINCIPIA R&D, BUREAU VERITAS
5 //
6 //  This library is free software; you can redistribute it and/or
7 //  modify it under the terms of the GNU Lesser General Public
8 //  License as published by the Free Software Foundation; either
9 //  version 2.1 of the License.
10 //
11 //  This library is distributed in the hope that it will be useful,
12 //  but WITHOUT ANY WARRANTY; without even the implied warranty of
13 //  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14 //  Lesser General Public License for more details.
15 //
16 //  You should have received a copy of the GNU Lesser General Public
17 //  License along with this library; if not, write to the Free Software
18 //  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
19 //
20 //  See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com
21 //
22 // File:      QtxPathDialog.cxx
23 // Author:    Sergey TELKOV
24 //
25 #include "QtxPathDialog.h"
26
27 #include "QtxGridBox.h"
28 #include "QtxGroupBox.h"
29
30 #include <QDir>
31 #include <QLabel>
32 #include <QPixmap>
33 #include <QLayout>
34 #include <QLineEdit>
35 #include <QObjectList>
36 #include <QStringList>
37 #include <QFileDialog>
38 #include <QMessageBox>
39 #include <QPushButton>
40
41 static const char* open_icon[] = {
42 "16 16 5 1",
43 "  c none",
44 ". c #ffff00",
45 "# c #848200",
46 "a c #ffffff",
47 "b c #000000",
48 "                ",
49 "          bbb   ",
50 "         b   b b",
51 "              bb",
52 "  bbb        bbb",
53 " ba.abbbbbbb    ",
54 " b.a.a.a.a.b    ",
55 " ba.a.a.a.ab    ",
56 " b.a.abbbbbbbbbb",
57 " ba.ab#########b",
58 " b.ab#########b ",
59 " bab#########b  ",
60 " bb#########b   ",
61 " bbbbbbbbbbb    ",
62 "                ",
63 "                "
64 };
65
66 /*!
67   \class QtxPathDialog
68   \brief The QtxPathDialog class provides a simple convenience dialog to
69          enter a path to the file or to the directory.
70
71   The QtxPathDialog class adds possibility to browse the file system 
72   with help of standard Open/Save dialog boxes or enter the file/directory
73   path manually.
74
75   Default implementation provides only one "standard" file entry.
76   Sometimes it is necessary to select several different files/directories
77   from the same dialog box. In this case it is possible to derive from the
78   QtxPathDialog class and use createFileEntry() method to add required
79   number of file entries.
80 */
81
82 /*!
83   \brief Constructor.
84   \param import if \c true, the dialog box is shown for "open" mode, 
85          otherwise, it is shown in the "save" mode
86   \param parent parent widget
87   \param modal if \c true, the dialog box should be modal
88   \param resize if \c true, the dialog box is resizable
89   \param buttons required buttons (QtxDialog::ButtonFlags)
90   \param f window flags
91 */
92 QtxPathDialog::QtxPathDialog( const bool import, QWidget* parent, const bool modal,
93                               const bool resize, const int buttons, Qt::WindowFlags f )
94 : QtxDialog( parent, modal, resize, buttons, f ),
95   myDefault( -1 ),
96   myEntriesFrame( 0 ),
97   myOptionsFrame( 0 )
98 {
99   initialize();
100   
101   setWindowTitle( tr( import ? "Open file" : "Save file" ) );
102   
103   setDefaultEntry( createFileEntry( tr( "File name" ), import ? OpenFile : SaveFile ) );
104   QLineEdit* le = fileEntry( defaultEntry() );
105   if ( le )
106     le->setMinimumWidth( 200 );
107   
108   validate();
109
110   setFocusProxy( le );
111
112   updateVisibility();
113 }
114
115 /*!
116   \brief Constructor.
117   \param parent parent widget
118   \param modal if \c true, the dialog box should be modal
119   \param resize if \c true, the dialog box is resizable
120   \param buttons required buttons (QtxDialog::ButtonFlags)
121   \param f window flags
122 */
123 QtxPathDialog::QtxPathDialog( QWidget* parent, const bool modal,
124                               const bool resize, const int buttons, Qt::WindowFlags f )
125 : QtxDialog( parent, modal, resize, buttons, f ),
126   myDefault( -1 ),
127   myEntriesFrame( 0 ),
128   myOptionsFrame( 0 )
129 {
130   initialize();
131
132   updateVisibility();
133 }
134
135 /*!
136   \brief Destructor.
137 */
138 QtxPathDialog::~QtxPathDialog()
139 {
140 }
141
142 /*!
143   \brief Get selected file name.
144   \return file name
145 */
146 QString QtxPathDialog::fileName() const
147 {
148   return fileName( defaultEntry() );
149 }
150
151 /*!
152   \brief Set the file name.
153   \param txt new file name
154   \param autoExtension if \c true an extension is determined automatically by file
155 */
156 void QtxPathDialog::setFileName( const QString& txt, const bool autoExtension )
157 {
158   setFileName( defaultEntry(), txt, autoExtension );
159 }
160
161 /*!
162   \brief Get current file filter.
163   \return file filter
164 */
165 QString QtxPathDialog::filter() const
166 {
167   return filter( defaultEntry() );
168 }
169
170 /*!
171   \brief Change file filter.
172   
173   Filter is a list of file masks, separated by ';;'. For example, 
174   "*.h;;*.cxx"
175
176   \param fltr new file filter
177 */
178 void QtxPathDialog::setFilter( const QString& fltr )
179 {
180   setFilter( defaultEntry(), fltr );
181 }
182
183 /*!
184   \brief Show/hide the path dialog box/
185   \param on new visibility state
186 */
187 void QtxPathDialog::setVisible( bool on )
188 {
189   if ( on )
190     updateVisibility();
191
192   QtxDialog::setVisible( on );
193 }
194
195 /*!
196   \brief Called when user clicks a "browse" button 
197          to open standard file dialog.
198 */
199 void QtxPathDialog::onBrowse()
200 {
201   const QObject* obj = sender();
202
203   int id = -1;
204   
205   for ( FileEntryMap::Iterator it = myEntries.begin(); it != myEntries.end() && id == -1; ++it )
206   {
207     if ( it.value().btn == obj )
208       id = it.key();
209   }
210   
211   if ( id == -1 )
212     return;
213   
214   FileEntry& entry = myEntries[id];
215   
216   bool isDir = entry.mode != OpenFile && entry.mode != SaveFile;
217   
218   if ( !entry.dlg )
219   {
220     entry.dlg = new QFileDialog( this, windowTitle(), QDir::current().path() );
221     switch ( entry.mode )
222     {
223     case NewDir:
224     case OpenDir:
225     case SaveDir:
226       isDir = true;
227       entry.dlg->setFileMode( QFileDialog::DirectoryOnly );
228       break;
229     case SaveFile:
230       entry.dlg->setFileMode( QFileDialog::AnyFile );
231       break;
232     case OpenFile:
233     default:
234       entry.dlg->setFileMode( QFileDialog::ExistingFiles );
235       break;
236     }
237   }
238   
239   if ( !isDir )
240   {
241     QStringList fList = prepareFilters( entry.filter );
242     if ( !fList.isEmpty() )
243       entry.dlg->setFilters( fList );
244   }
245   entry.dlg->selectFile( fileName( id ) );
246
247   if ( entry.dlg->exec() != Accepted )
248     return;
249   
250   QStringList fileList = entry.dlg->selectedFiles();
251   QString fName = !fileList.isEmpty() ? fileList.first() : QString();
252   
253   if ( fName.isEmpty() )
254     return;
255   
256   if ( Qtx::extension( fName ).isEmpty() && !isDir )
257     fName = autoExtension( fName, entry.dlg->selectedFilter() );
258
259   fName = QDir::convertSeparators( fName );
260   QString prev = QDir::convertSeparators( fileName( id ) );
261   if ( isDir )
262   {
263     while ( prev.length() && prev.at( prev.length() - 1 ) == QDir::separator() )
264       prev.remove( prev.length() - 1, 1 );
265     while ( fName.length() && fName.at( fName.length() - 1 ) == QDir::separator() )
266       fName.remove( fName.length() - 1, 1 );
267   }
268   
269   if ( prev == fName )
270     return;
271   
272   setFileName( id, fName );
273   fileNameChanged( id, fName );
274   
275   if ( id == defaultEntry() )
276     emit fileNameChanged( fName );
277 }
278
279 /*!
280   \brief Called when user presses \c Return key being in the line edit.
281 */
282 void QtxPathDialog::onReturnPressed()
283 {
284   const QObject* obj = sender();
285   
286   int id = -1;
287   for ( FileEntryMap::Iterator it = myEntries.begin(); it != myEntries.end() && id == -1; ++it )
288   {
289     if ( it.value().edit == obj )
290       id = it.key();
291   }
292
293   if ( id == -1 )
294     return;
295   
296   fileNameChanged( id, fileName( id ) );
297   
298   if ( id == defaultEntry() )
299     emit fileNameChanged( fileName() );
300 }
301
302 /*!
303  \brief Called when the text in the line edit is changed by the user.
304  \param txt current text (not used)
305 */
306 void QtxPathDialog::onTextChanged( const QString& /*txt*/ )
307 {
308   validate();
309 }
310
311 /*!
312   \brief Check validity of the entered text and enable/disable standard
313   \c OK, \c Yes buttons.
314 */
315 void QtxPathDialog::validate()
316 {
317   setButtonEnabled( isValid(), OK | Yes );
318 }
319
320 /*!
321   \brief Check if the entered file/directory name is valid.
322   \return \c true if selected file name is valid
323 */
324 bool QtxPathDialog::isValid()
325 {
326   bool ok = true;
327   for ( FileEntryMap::Iterator it = myEntries.begin(); it != myEntries.end() && ok; ++it )
328   {
329     if ( it.value().edit->isEnabled() )
330       ok = !it.value().edit->text().trimmed().isEmpty();
331   }
332   
333   return ok;
334 }
335
336 /*!
337   \brief Check if the entered data is acceptable.
338   \return \c true if entered data is acceptable
339 */
340 bool QtxPathDialog::acceptData() const
341 {
342   bool ok = true;
343         
344   QWidget* parent = (QWidget*)this;
345
346   FileEntryMap::ConstIterator it;
347   for ( it = myEntries.begin(); it != myEntries.end() && ok; ++it )
348   {
349     const FileEntry& entry = it.value();
350     QFileInfo fileInfo( entry.edit->text() );
351     if ( entry.edit->text().isEmpty() )
352     {
353       QMessageBox::critical( parent, windowTitle(), tr( "File name not specified" ),
354                              QMessageBox::Ok, QMessageBox::NoButton );
355       ok = false;
356     }
357     else switch ( entry.mode )
358     {
359     case OpenFile:
360       if ( !fileInfo.exists() )
361       {
362         QMessageBox::critical( parent, windowTitle(), tr( "File \"%1\" does not exist" ).arg( fileInfo.filePath() ),
363                                QMessageBox::Ok, QMessageBox::NoButton );
364         ok = false;
365       }
366       break;
367     case SaveFile:
368       if ( fileInfo.exists() )
369         ok = QMessageBox::warning( parent, windowTitle(), tr( "File \"%1\" already exist. Do you want to overwrite it?" ).arg( fileInfo.filePath() ),
370                                    QMessageBox::Yes, QMessageBox::No ) == QMessageBox::Yes;
371       break;
372     case OpenDir:
373       if ( !fileInfo.exists() || !fileInfo.isDir() )
374       {
375         QMessageBox::critical( parent, windowTitle(), tr( "Directory \"%1\" does not exist" ).arg( fileInfo.filePath() ),
376                                QMessageBox::Ok, QMessageBox::NoButton );
377         ok = false;
378       }
379       break;
380     case SaveDir:
381       if ( fileInfo.exists() && !fileInfo.isDir() )
382       {
383         QMessageBox::critical( parent, windowTitle(), tr( "Directory \"%1\" can't be created because file with the same name exist" ).arg( fileInfo.filePath() ),
384                                QMessageBox::Ok, QMessageBox::NoButton );
385         ok = false;
386       }
387       break;
388     case NewDir:
389       if ( fileInfo.exists() )
390       {
391         if ( !fileInfo.isDir() )
392         {
393           QMessageBox::critical( parent, windowTitle(), tr( "Directory \"%1\" can't be created because file with the same name exist" ).arg( fileInfo.filePath() ),
394                                  QMessageBox::Ok, QMessageBox::NoButton );
395           ok = false;
396         }
397         else if ( QDir( fileInfo.filePath() ).count() > 2 )
398           ok = QMessageBox::warning( parent, windowTitle(), tr( "Directory \"%1\" not empty. Do you want to remove all files in this directory?" ).arg( fileInfo.filePath() ),
399                                      QMessageBox::Yes, QMessageBox::No ) == QMessageBox::Yes;
400       }
401       break;
402     default:
403       break;
404     }
405     
406     if ( !ok )
407       entry.edit->setFocus();
408   }
409   
410   return ok;
411 }
412
413 /*!
414   \brief Perform custom actions when the file name is changed.
415
416   This method can be redefined in the successor classes.
417   Default implementation does nothing.
418
419   \param id file entry
420   \param fileName file name
421 */
422 void QtxPathDialog::fileNameChanged( int /*id*/, QString /*fileName*/ )
423 {
424 }
425
426 /*!
427   \fn void QtxPathDialog::fileNameChanged( QString fileName );
428   \brief Emitted when the file name is changed.
429   \param fileName file name
430 */
431
432 /*!
433   \brief Get options grame widget.
434   \return options frame widget
435 */
436 QFrame* QtxPathDialog::optionsFrame()
437 {
438   return myOptionsFrame;
439 }
440
441 /*!
442   \brief Get file name from specified entry.
443   \param id file entry ID
444   \return file name or null string if \a id is invalid
445 */
446 QString QtxPathDialog::fileName( const int id ) const
447 {
448   QString res;
449   if ( myEntries.contains( id ) )
450     res = myEntries[id].edit->text();
451   return res;
452 }
453
454 /*!
455   \brief Change file name by specified file entry.
456   \param id file entry ID
457   \param txt new file name
458   \param autoExt if \c true, assign extension automatically
459 */
460 void QtxPathDialog::setFileName( const int id, const QString& txt, const bool autoExt )
461 {
462   int mode;
463   QLineEdit* le = fileEntry( id, mode );
464         
465   if ( le )
466   {
467     if ( autoExt && ( mode == OpenFile || mode == SaveFile ) )
468       le->setText( autoExtension( txt, filter( id ) ) );
469     else
470       le->setText( txt );
471   }
472 }
473
474 /*!
475   \brief Get file filter from the specified file entry.
476   \param id file entry ID
477   \return file filter or null string if \a id is invalid
478 */
479 QString QtxPathDialog::filter( const int id ) const
480 {
481   QString res;
482   if ( myEntries.contains( id ) )
483     res = myEntries[id].filter;
484   return res;
485 }
486
487 /*!
488   \brief Set file filter to the specified file entry.
489   \param id file entry ID
490   \param filter file filter or null string if \a id is invalid
491 */
492 void QtxPathDialog::setFilter( const int id, const QString& filter )
493 {
494   if ( myEntries.contains( id ) )
495     myEntries[id].filter = filter;
496 }
497
498 /*!
499   \brief Get line edit widget for the specified file entry.
500   \param id file entry ID
501   \return line edit widget or 0 if \a id is invalid
502 */
503 QLineEdit* QtxPathDialog::fileEntry( const int id ) const
504 {
505   QLineEdit* le = 0;
506   if ( myEntries.contains( id ) )
507     le = myEntries[id].edit;
508   
509   return le;
510 }
511
512 /*!
513   \brief Get line edit widget and file mode for the specified file entry.
514   \param id file entry ID
515   \param theMode to return file entry mode
516   \return line edit widget or 0 if \a id is invalid
517 */
518 QLineEdit* QtxPathDialog::fileEntry( const int theId, int& theMode ) const
519 {
520   QLineEdit* le = 0;
521   if ( myEntries.contains( theId ) )
522   {
523     le = myEntries[theId].edit;
524     theMode = myEntries[theId].mode;
525   }
526   
527   return le;
528 }
529
530 /*!
531   \brief Create new file entry.
532
533   If required file entry is already in use or if specified \a id is < 0,
534   new ID is generated and returned.
535
536   \param lab file entry title
537   \param mode file entry mode
538   \param id required file entry ID
539   \return created file entry ID
540 */
541 int QtxPathDialog::createFileEntry( const QString& lab, const int mode, 
542                                                             const QString& filter, const int id )
543 {
544   int num = id;
545   if ( num == -1 )
546   {
547     num--;
548     while ( myEntries.contains( num ) )
549       num--;
550   }
551   
552   FileEntry entry;
553   entry.dlg = 0;
554   entry.mode = mode;
555   entry.filter = filter;
556   
557   new QLabel( lab, myEntriesFrame );
558   entry.edit = new QLineEdit( myEntriesFrame );
559
560   entry.btn = new QPushButton( myEntriesFrame );
561   entry.btn->setAutoDefault( false );
562   entry.btn->setIcon( QPixmap( open_icon ) );
563
564   Qtx::PathType type = Qtx::PT_OpenFile;
565   switch ( mode )
566   {
567   case OpenFile:
568     type = Qtx::PT_OpenFile;
569     break;
570   case SaveFile:
571     type = Qtx::PT_SaveFile;
572     break;
573   case OpenDir:
574   case SaveDir:
575   case NewDir:
576     type = Qtx::PT_Directory;
577     break;
578   }
579   entry.edit->setCompleter( Qtx::pathCompleter( type, filter ) );
580
581   connect( entry.btn, SIGNAL( clicked() ), this, SLOT( onBrowse() ) );
582   connect( entry.edit, SIGNAL( returnPressed() ), this, SLOT( onReturnPressed() ) );
583   connect( entry.edit, SIGNAL( textChanged( const QString& ) ), this, SLOT( onTextChanged( const QString& ) ) );
584   
585   myEntries.insert( num, entry );
586   
587   return num;
588 }
589
590 /*!
591   \brief Get default file entry ID.
592   \return default entry ID
593 */
594 int QtxPathDialog::defaultEntry() const
595 {
596   return myDefault;
597 }
598
599 /*!
600   \brief Set default entry.
601   \param id new default entry ID
602 */
603 void QtxPathDialog::setDefaultEntry( const int id )
604 {
605   myDefault = id;
606 }
607
608 /*!
609   \brief Initialize dialog layout.
610 */
611 void QtxPathDialog::initialize()
612 {
613   setWindowTitle( tr( "File dialog" ) );
614
615   QVBoxLayout* main = new QVBoxLayout( mainFrame() );
616   main->setMargin( 0 );
617
618   QtxGroupBox* base = new QtxGroupBox( "", mainFrame() );
619   main->addWidget( base );
620   
621   QtxGridBox*  mainGroup = new QtxGridBox( 1, Qt::Horizontal, base, 0 );
622   base->setWidget( mainGroup );
623   
624   myEntriesFrame = new QtxGridBox( 3, Qt::Horizontal, mainGroup );
625   myOptionsFrame = new QFrame( mainGroup );
626 }
627
628 /*!
629   \brief Prepare file filters.
630   \param list of file masks, separated by ';;', for example, "*.h;;*.cxx"
631   \return list of processed file filters
632 */
633 QStringList QtxPathDialog::prepareFilters( const QString& filter ) const
634 {
635   QStringList res;
636   bool allFilter = false;
637   if ( !filter.isEmpty() )
638   {
639     res = filter.split( ";;" );
640     for ( QStringList::ConstIterator it = res.begin(); it != res.end() && !allFilter; ++it )
641     {
642       QStringList wildCards = filterWildCards( *it );
643       allFilter = wildCards.indexOf( "*.*" ) != -1;
644     }
645   }
646   
647   if ( !allFilter )
648     res.append( tr( "All files (*.*)" ) );
649   
650   return res;
651 }
652
653 /*!
654   \brief Get wildcards from the specified file filter.
655   \param theFilter file filter being processed
656   \return list of filters with filtered wild cards
657 */
658 QStringList QtxPathDialog::filterWildCards( const QString& theFilter ) const
659 {
660   QStringList res;
661
662   int b = theFilter.lastIndexOf( "(" );
663   int e = theFilter.lastIndexOf( ")" );
664   if ( b != -1 && e != -1 )
665   {
666     QString content = theFilter.mid( b + 1, e - b - 1 ).trimmed();
667     QStringList lst = content.split( " " );
668     for ( QStringList::ConstIterator it = lst.begin(); it != lst.end(); ++it )
669     {
670       if ( (*it).indexOf( "." ) != -1 )
671         res.append( (*it).trimmed() );
672     }
673   }
674   return res;
675 }
676
677 /*!
678   \brief Get file file name with automatically assigned extension.
679   \param theFileName file name being processed
680   \param theFilter list of file filters
681   \return file name with assigned extension
682 */
683 QString QtxPathDialog::autoExtension( const QString& theFileName, const QString& theFilter ) const
684 {
685   QString fName = theFileName;
686
687   if ( fName.isEmpty() )
688     return fName;
689   
690   QString filter;
691   QStringList filters = prepareFilters( theFilter );
692   if ( !filters.isEmpty() )
693     filter = filters.first();
694
695   QStringList wildCards = filterWildCards( filter );
696   if ( !wildCards.isEmpty() )
697   {
698     QString ext = wildCards.first();
699     if ( ext.indexOf( "." ) != -1 )
700       ext = ext.mid( ext.indexOf( "." ) + 1 );
701     
702     if ( !ext.isEmpty() && !ext.contains( "*" ) )
703       fName = QDir::convertSeparators( fName ) + QString( "." ) + ext;
704   }
705   
706   return fName;
707 }
708
709 /*!
710   \brief Check if there are visible child widgets.
711   \param wid parent widget being checked
712   \return \c true if widget \a wid has visible children
713 */
714 bool QtxPathDialog::hasVisibleChildren( QWidget* wid ) const
715 {
716   bool res = false;
717   if ( wid )
718   {
719     const QObjectList& aChildren = wid->children();
720     for ( QObjectList::const_iterator it = aChildren.begin(); it != aChildren.end() && !res; ++it )
721     {
722       if ( (*it)->isWidgetType() )
723         res = ((QWidget*)(*it))->isVisibleTo( wid );
724     }
725   }
726   return res;
727 }
728
729 /*!
730   \brief Upadte dialof box's child widgets visibility state.
731 */
732 void QtxPathDialog::updateVisibility()
733 {
734   if ( hasVisibleChildren( myEntriesFrame ) )
735     myEntriesFrame->show();
736   else
737     myEntriesFrame->hide();
738   
739   if ( hasVisibleChildren( myOptionsFrame ) )
740     myOptionsFrame->show();
741   else
742     myOptionsFrame->hide();
743 }