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