Salome HOME
6de177f1e2dcfedcadb92a3b0de42a887537ddf3
[modules/gui.git] / tools / PyEditor / src / PyEditor_Window.cxx
1 // Copyright (C) 2015-2022  OPEN CASCADE
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, or (at your option) any later version.
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/ or email : webmaster.salome@opencascade.com
18 //
19 // File   : PyEditor_Window.cxx
20 // Author : Vadim SANDLER, Open CASCADE S.A.S. (vadim.sandler@opencascade.com)
21 //
22
23 #include "PyEditor_Window.h"
24 #include "PyEditor_Widget.h"
25 #include "PyEditor_Settings.h"
26 #include "PyEditor_SettingsDlg.h"
27
28 #include <QAction>
29 #include <QApplication>
30 #include <QCloseEvent>
31 #include <QFileDialog>
32 #include <QMenuBar>
33 #include <QMessageBox>
34 #include <QStatusBar>
35 #include <QTextStream>
36 #include <QToolBar>
37
38 /*!
39   \class PyEditor_Window
40   \brief Python view window.
41 */
42
43 /*!
44   \brief Constructor.
45   \param parent parent widget
46 */
47 PyEditor_Window::PyEditor_Window( QWidget* parent ) :
48   QMainWindow( parent )
49 {
50   Q_INIT_RESOURCE( PyEditor );
51
52   // Create central widget.
53   myEditor = new PyEditor_Widget( this );
54   setCentralWidget( myEditor );
55
56   // Create actions.
57   QAction* action;
58
59   // . New
60   action = new QAction( QIcon( ":/images/py_new.png" ),
61                         tr( "ACT_NEW" ), this );
62   action->setToolTip( tr( "TTP_NEW" ) );
63   action->setStatusTip( tr( "DSC_NEW" ) );
64   action->setShortcut( QKeySequence::New );
65   connect( action, SIGNAL( triggered( bool ) ), this, SLOT( onNew() ) );
66   myActions[ NewId ] = action;
67
68   // . Open
69   action = new QAction( QIcon( ":/images/py_open.png" ),
70                         tr( "ACT_OPEN" ), this );
71   action->setToolTip( tr( "TTP_OPEN" ) );
72   action->setStatusTip( tr( "DSC_OPEN" ) );
73   action->setShortcut( QKeySequence::Open );
74   connect( action, SIGNAL( triggered( bool ) ), this, SLOT( onOpen() ) );
75   myActions[ OpenId ] = action;
76
77   // . Save
78   action = new QAction( QIcon( ":/images/py_save.png" ),
79                         tr( "ACT_SAVE" ), this );
80   action->setToolTip( tr( "TTP_SAVE" ) );
81   action->setStatusTip( tr( "DSC_SAVE" ) );
82   action->setShortcut( QKeySequence::Save );
83   connect( action, SIGNAL( triggered( bool ) ), this, SLOT( onSave() ) );
84   action->setEnabled( false );
85   connect( myEditor, SIGNAL( modificationChanged( bool ) ),
86            action, SLOT( setEnabled( bool ) ) );
87   myActions[ SaveId ] = action;
88
89   // . SaveAs
90   action = new QAction( QIcon( ":/images/py_save_as.png" ),
91                         tr( "ACT_SAVEAS" ), this );
92   action->setToolTip( tr( "TTP_SAVEAS" ) );
93   action->setStatusTip( tr( "DSC_SAVEAS" ) );
94   action->setShortcut( QKeySequence::SaveAs );
95   connect( action, SIGNAL( triggered( bool ) ), this, SLOT( onSaveAs() ) );
96   myActions[ SaveAsId ] = action;
97
98   // . Exit
99   action = new QAction( QIcon( ":/images/py_exit.png" ),
100                         tr( "ACT_EXIT" ), this );
101   action->setToolTip( tr( "TTP_EXIT" ) );
102   action->setStatusTip( tr( "DSC_EXIT" ) );
103   action->setShortcut( QKeySequence::Quit );
104   connect( action, SIGNAL( triggered( bool ) ), this, SLOT( close() ) );
105   myActions[ ExitId ] = action;
106
107   // . Undo
108   action = new QAction( QIcon( ":/images/py_undo.png" ),
109                         tr( "ACT_UNDO" ), this );
110   action->setToolTip( tr( "TTP_UNDO" ) );
111   action->setStatusTip( tr( "DSC_UNDO" ) );
112   action->setShortcut( QKeySequence::Undo );
113   connect( action, SIGNAL( triggered( bool ) ), myEditor, SLOT( undo() ) );
114   action->setEnabled( false );
115   connect( myEditor, SIGNAL( undoAvailable( bool ) ),
116            action, SLOT( setEnabled( bool ) ) );
117   myActions[ UndoId ] = action;
118
119   // . Redo
120   action = new QAction( QIcon( ":/images/py_redo.png" ),
121                         tr( "ACT_REDO" ), this );
122   action->setToolTip( tr( "TTP_REDO" ) );
123   action->setStatusTip( tr( "DSC_REDO" ) );
124   action->setShortcut( QKeySequence::Redo );
125   connect( action, SIGNAL( triggered( bool ) ), myEditor, SLOT( redo() ) );
126   action->setEnabled( false );
127   connect( myEditor, SIGNAL( redoAvailable( bool ) ),
128            action, SLOT( setEnabled( bool ) ) );
129   myActions[ RedoId ] = action;
130
131   // . Cut
132   action = new QAction( QIcon( ":/images/py_cut.png" ),
133                         tr( "ACT_CUT" ), this );
134   action->setToolTip( tr( "TTP_CUT" ) );
135   action->setStatusTip( tr( "DSC_CUT" ) );
136   action->setShortcut( QKeySequence::Cut );
137   connect( action, SIGNAL( triggered( bool ) ), myEditor, SLOT( cut() ) );
138   action->setEnabled( false );
139   connect( myEditor, SIGNAL( copyAvailable( bool ) ),
140            action, SLOT( setEnabled( bool ) ) );
141   myActions[ CutId ] = action;
142
143   // . Copy
144   action = new QAction( QIcon( ":/images/py_copy.png" ),
145                         tr( "ACT_COPY" ), this );
146   action->setToolTip( tr( "TTP_COPY" ) );
147   action->setStatusTip( tr( "DSC_COPY" ) );
148   action->setShortcut( QKeySequence::Copy );
149   connect( action, SIGNAL( triggered( bool ) ), myEditor, SLOT( copy() ) );
150   action->setEnabled( false );
151   connect( myEditor, SIGNAL( copyAvailable( bool ) ),
152            action, SLOT( setEnabled( bool ) ) );
153   myActions[ CopyId ] = action;
154
155   // . Paste
156   action = new QAction( QIcon( ":/images/py_paste.png" ),
157                         tr( "ACT_PASTE" ), this );
158   action->setToolTip( tr( "TTP_PASTE" ) );
159   action->setStatusTip( tr( "DSC_PASTE" ) );
160   action->setShortcut( QKeySequence::Paste );
161   connect( action, SIGNAL( triggered( bool ) ), myEditor, SLOT( paste() ) );
162   myActions[ PasteId ] = action;
163
164   // . Delete
165   action = new QAction( QIcon( ":/images/py_delete.png" ),
166                         tr( "ACT_DELETE" ), this );
167   action->setToolTip( tr( "TTP_DELETE" ) );
168   action->setStatusTip( tr( "DSC_DELETE" ) );
169   action->setShortcut( QKeySequence::Delete );
170   connect( action, SIGNAL( triggered( bool ) ), myEditor, SLOT( deleteSelected() ) );
171   action->setEnabled( false );
172   connect( myEditor, SIGNAL( copyAvailable( bool ) ),
173            action, SLOT( setEnabled( bool ) ) );
174   myActions[ DeleteId ] = action;
175
176   // . SelectAll
177   action = new QAction( QIcon( ":/images/py_select_all.png" ),
178                         tr( "ACT_SELECT_ALL" ), this );
179   action->setToolTip( tr( "TTP_SELECT_ALL" ) );
180   action->setStatusTip( tr( "DSC_SELECT_ALL" ) );
181   action->setShortcut( QKeySequence::SelectAll );
182   connect( action, SIGNAL( triggered( bool ) ), myEditor, SLOT( selectAll() ) );
183   myActions[ SelectAllId ] = action;
184
185   // . Find
186   action = new QAction( QIcon( ":/images/py_find.png" ),
187                         tr( "ACT_FIND" ), this );
188   action->setToolTip( tr( "TTP_FIND" ) );
189   action->setStatusTip( tr( "DSC_FIND" ) );
190   action->setShortcut( QKeySequence::Find );
191   action->setShortcutContext( Qt::WidgetShortcut );
192   connect( action, SIGNAL( triggered( bool ) ), myEditor, SLOT( find() ) );
193   myActions[ FindId ] = action;
194
195   // . Replace
196   action = new QAction( QIcon( ":/images/py_replace.png" ),
197                         tr( "ACT_REPLACE" ), this );
198   action->setToolTip( tr( "TTP_REPLACE" ) );
199   action->setStatusTip( tr( "DSC_REPLACE" ) );
200   action->setShortcuts( QList<QKeySequence>() << QKeySequence( "Ctrl+H" ) << QKeySequence( QKeySequence::Replace ) );
201   action->setShortcutContext( Qt::WidgetShortcut );
202   connect( action, SIGNAL( triggered( bool ) ), myEditor, SLOT( replace() ) );
203   myActions[ ReplaceId ] = action;
204
205   // . Preferences
206   action = new QAction( QIcon( ":/images/py_preferences.png" ),
207                         tr( "ACT_PREFERENCES" ), this );
208   action->setToolTip( tr( "TTP_PREFERENCES" ) );
209   action->setStatusTip( tr( "DSC_PREFERENCES" ) );
210   connect( action, SIGNAL( triggered( bool ) ), this, SLOT( onPreferences() ) );
211   myActions[ PreferencesId ] = action;
212
213   // . Help
214   action = new QAction( QIcon( ":/images/py_help.png" ),
215                         tr( "ACT_HELP" ), this );
216   action->setToolTip( tr( "TTP_HELP" ) );
217   action->setStatusTip( tr( "DSC_HELP" ) );
218   connect( action, SIGNAL( triggered() ), this, SLOT( onHelp() ) );
219   myActions[ HelpId ] = action;
220
221   // Create menu.
222   QMenu* menu = menuBar()->addMenu( tr( "MNU_FILE" ) );
223   menu->addAction( myActions[ NewId ] );
224   menu->addAction( myActions[ OpenId ] );
225   menu->addSeparator();
226   menu->addAction( myActions[ SaveId ] );
227   menu->addAction( myActions[ SaveAsId ] );
228   menu->addSeparator();
229   menu->addAction( myActions[ ExitId ] );
230
231   menu = menuBar()->addMenu( tr( "MNU_EDIT" ) );
232   menu->addAction( myActions[ UndoId ] );
233   menu->addAction( myActions[ RedoId ] );
234   menu->addSeparator();
235   menu->addAction( myActions[ CutId ] );
236   menu->addAction( myActions[ CopyId ] );
237   menu->addAction( myActions[ PasteId ] );
238   menu->addAction( myActions[ DeleteId ] );
239   menu->addSeparator();
240   menu->addAction( myActions[ SelectAllId ] );
241   menu->addSeparator();
242   menu->addAction( myActions[ FindId ] );
243   menu->addAction( myActions[ ReplaceId ] );
244   menu->addSeparator();
245   menu->addAction( myActions[ PreferencesId ] );
246
247   menu = menuBar()->addMenu( tr( "MNU_HELP" ) );
248   menu->addAction( myActions[ HelpId ] );
249
250   // Create toolbar.
251   QToolBar* toolbar = addToolBar( tr( "TOOLBAR_LABEL" ) );
252   toolbar->setObjectName("PythonEditor");
253   toolbar->addAction( myActions[ NewId ] );
254   toolbar->addAction( myActions[ OpenId ] );
255   toolbar->addAction( myActions[ SaveId ] );
256   toolbar->addAction( myActions[ SaveAsId ] );
257   toolbar->addSeparator();
258   toolbar->addAction( myActions[ ExitId ] );
259   toolbar->addSeparator();
260   toolbar->addAction( myActions[ UndoId ] );
261   toolbar->addAction( myActions[ RedoId ] );
262   toolbar->addSeparator();
263   toolbar->addAction( myActions[ CutId ] );
264   toolbar->addAction( myActions[ CopyId ] );
265   toolbar->addAction( myActions[ PasteId ] );
266   toolbar->addAction( myActions[ DeleteId ] );
267   toolbar->addAction( myActions[ SelectAllId ] );
268   toolbar->addSeparator();
269   toolbar->addAction( myActions[ FindId ] );
270   toolbar->addAction( myActions[ ReplaceId ] );
271   toolbar->addSeparator();
272   toolbar->addAction( myActions[ PreferencesId ] );
273   toolbar->addSeparator();
274   toolbar->addAction( myActions[ HelpId ] );
275
276   // Set current file.
277   setCurrentFile( QString() );
278
279   // Additional set-up for main window.
280   connect( myEditor, SIGNAL( modificationChanged( bool ) ),
281            this, SLOT( setWindowModified( bool ) ) );
282
283   // Initialize status bar.
284   statusBar()->showMessage( tr( "STS_READY" ) );
285 }
286
287 /*!
288   \brief Destructor.
289 */
290 PyEditor_Window::~PyEditor_Window()
291 {
292 }
293
294 /*!
295   \brief Manage window close request.
296   \param event close event
297 */
298 void PyEditor_Window::closeEvent( QCloseEvent* event )
299 {
300   if ( whetherSave() )
301     event->accept();
302   else
303     event->ignore();
304 }
305
306 /*!
307   SLOT: Create new document
308 */
309 void PyEditor_Window::onNew()
310 {
311   if ( whetherSave() )
312   {
313     myEditor->clear();
314     setCurrentFile( QString() );
315   }
316 }
317
318 /*!
319   SLOT: Open existing Python file
320 */
321 void PyEditor_Window::onOpen()
322 {
323   if ( whetherSave() )
324   {
325     QString filter = tr( "TIT_PY_FILES" );
326     filter += " (*.py)";
327     QString aFilePath = QFileDialog::getOpenFileName( this,
328                                                       tr( "TIT_DLG_OPEN" ),
329                                                       QDir::currentPath(),
330                                                       filter );
331
332     if ( !aFilePath.isEmpty() )
333       loadFile( aFilePath );
334   }
335 }
336
337 /*!
338   SLOT: Save current document
339 */
340 bool PyEditor_Window::onSave()
341 {
342   if ( myURL.isEmpty() )
343     return onSaveAs();
344   else
345     return saveFile( myURL );
346 }
347
348
349 /*!
350   SLOT: Save current document under a new name
351 */
352 bool PyEditor_Window::onSaveAs()
353 {
354   QString filter = tr( "TIT_PY_FILES" );
355   filter += " (*.py)";
356   QString url = myURL.isEmpty() ? defaultName() : myURL;
357   QString aFilePath = QFileDialog::getSaveFileName( this,
358                                                     tr( "TIT_DLG_SAVE" ),
359                                                     url,
360                                                     filter );
361
362   if ( !aFilePath.isEmpty() )
363     return saveFile( aFilePath );
364
365   return false;
366 }
367
368 /*!
369   SLOT: Open preferences dialog
370 */
371 void PyEditor_Window::onPreferences()
372 {
373   PyEditor_SettingsDlg dlg( myEditor->editor(), true, this );
374   connect( &dlg, SIGNAL( help() ), this, SLOT( onHelp() ) );
375   dlg.exec();
376 }
377
378 /*!
379   \brief Associate \a filePath with the current document
380   \param filePath document's file path
381 */
382 void PyEditor_Window::setCurrentFile( const QString& filePath )
383 {
384   myURL = filePath;
385   myEditor->setModified( false );
386
387   setWindowModified( false );
388
389   setWindowFilePath( myURL.isEmpty() ? defaultName() : myURL );
390 }
391
392 /*!
393   \brief Check whether the file is modified.
394   If it has the modifications then ask the user to save it.
395   \return true if the document is saved.
396 */
397 bool PyEditor_Window::whetherSave()
398 {
399   if ( myEditor->isModified() )
400   {
401     QMessageBox::StandardButton answer =  QMessageBox::warning( this,
402                                                                 tr( "NAME_PYEDITOR" ),
403                                                                 tr( "WRN_SAVE_FILE" ),
404                                                                 QMessageBox::Save |
405                                                                 QMessageBox::Discard |
406                                                                 QMessageBox::Cancel );
407     switch( answer )
408     {
409     case QMessageBox::Save:
410       return onSave();
411     case QMessageBox::Cancel:
412       return false;
413     default:
414       break;
415     }
416   }
417   return true;
418 }
419
420 /*!
421   \brief Open file.
422   \param filePath file path
423 */
424 void PyEditor_Window::loadFile( const QString& filePath, bool verbose )
425 {
426   QFile aFile( filePath );
427   if ( !aFile.open(QFile::ReadOnly | QFile::Text) )
428   {
429     if ( verbose )
430       QMessageBox::warning( this, tr( "NAME_PYEDITOR" ),
431                             tr( "WRN_READ_FILE" ).arg( filePath ).arg( aFile.errorString() ) );
432     return;
433   }
434
435   QTextStream anInput( &aFile );
436   QApplication::setOverrideCursor( Qt::WaitCursor );
437   myEditor->setText( anInput.readAll() );
438   QApplication::restoreOverrideCursor();
439
440   setCurrentFile( filePath );
441   aFile.close();
442
443   statusBar()->showMessage( tr( "STS_F_LOADED" ), 2000 );
444 }
445
446 /*!
447   \brief Save file.
448   \param filePath file path
449 */
450 bool PyEditor_Window::saveFile( const QString& filePath, bool verbose )
451 {
452   QFile aFile( filePath );
453   if ( !aFile.open( QFile::WriteOnly | QFile::Text ) )
454   {
455     if ( verbose )
456       QMessageBox::warning( this, tr( "NAME_PYEDITOR" ),
457                             tr( "WRN_WRITE_FILE" ).arg( filePath ).arg( aFile.errorString() ) );
458     return false;
459   }
460
461   QTextStream anOutput( &aFile );
462   QApplication::setOverrideCursor( Qt::WaitCursor );
463   anOutput << myEditor->text();
464   QApplication::restoreOverrideCursor();
465
466   setCurrentFile( filePath );
467   aFile.close();
468
469   statusBar()->showMessage( tr( "STS_F_SAVED" ), 2000 );
470
471   return true;
472 }
473
474
475 /*!
476   Get editor.
477   \return Editor widget.
478 */
479 PyEditor_Widget* PyEditor_Window::editor()
480 {
481   return myEditor;
482 }
483
484 /*!
485   Slot, called when user clicks "Help" button in "Preferences" dialog box.
486 */
487 void PyEditor_Window::onHelp()
488 {
489   QWidget* w = qobject_cast<QWidget*>( sender() );
490   if ( !w ) w = this;
491   QFile file(":/about.txt");
492   file.open(QFile::ReadOnly | QFile::Text);
493   QTextStream stream( &file );
494   QString about = stream.readAll();
495   file.close();
496   QMessageBox::about( w, tr( "NAME_PYEDITOR" ), about );
497 }
498
499 /*!
500   Get default name for Python file 
501   \return default name
502 */
503 QString PyEditor_Window::defaultName() const
504 {
505   return tr( "NONAME" );
506 }