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