Salome HOME
Merge branch 'V7_dev' into V8_0_0_BR
[modules/gui.git] / src / PyViewer / PyViewer_ViewWindow.cxx
1 // Copyright (C) 2015 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   : PyViewer_ViewWindow.cxx
20 // Author : Maxim GLIBIN, Open CASCADE S.A.S. (maxim.glibin@opencascade.com)
21 //
22
23 #include "PyViewer_ViewWindow.h"
24
25 #include "PyEditor_Editor.h"
26 #include "PyEditor_Settings.h"
27 #include "PyEditor_SettingsDlg.h"
28
29 #include <SUIT_Session.h>
30 #include <SUIT_ResourceMgr.h>
31
32 #include <QtxAction.h>
33 #include <QtxActionToolMgr.h>
34 #include <QtxMultiAction.h>
35
36 #include <QtGui>
37 #include <QLocale>
38 #include <QFileDialog>
39 #include <QMessageBox>
40 #include <QApplication>
41 #include <QStatusBar>
42
43 /*!
44   \class PyViewer_ViewWindow
45   \brief Python view window.
46 */
47
48 /*!
49   \brief Constructor.
50   \param theParent parent widget
51 */
52 PyViewer_ViewWindow::PyViewer_ViewWindow( SUIT_Desktop* theDesktop , PyViewer_Viewer* theModel ) :
53   SUIT_ViewWindow(theDesktop),
54   myModel(theModel)
55 {
56   my_IsExternal = (theDesktop == NULL);
57
58   if( isExternal() )
59     initLayout();
60 }
61
62 void PyViewer_ViewWindow::initLayout()
63 {
64   my_TextEditor = new PyEditor_Editor( my_IsExternal ,SUIT_Session::session()->resourceMgr(), this );
65   setCentralWidget( my_TextEditor );
66
67   createActions();
68   createToolBar();
69   setCurrentFile( QString() );
70     
71   if ( isExternal() )
72     {
73       connect( my_TextEditor->document(), SIGNAL( modificationChanged( bool ) ),
74                this, SLOT( setWindowModified( bool ) ) );
75       
76       statusBar()->showMessage( tr("STS_READY") );
77     }  
78 }
79
80 /*!
81   \brief Destructor.
82  */
83 PyViewer_ViewWindow::~PyViewer_ViewWindow()
84 {
85   my_CurrentFile.clear();
86   delete my_TextEditor;
87 }
88
89 /*!
90   \return \c true if the application is external
91  */
92 bool PyViewer_ViewWindow::isExternal()
93 {
94   return my_IsExternal;
95 }
96
97 /*!
98   \brief Creates actions of Python view window.
99 */
100 void PyViewer_ViewWindow::createActions()
101 {
102   QtxActionToolMgr* aMgr = toolMgr();
103   QtxAction* anAction;
104
105   // 1. File operations
106   // 1.1. Create New action
107   anAction = new QtxAction( tr( "MNU_PY_NEW" ), QIcon( ":/images/py_new.png" ),
108                                  tr( "MNU_PY_NEW" ), 0, this );
109   anAction->setStatusTip( tr( "DSC_PY_NEW" ) );
110   anAction->setShortcuts( QKeySequence::New );
111   connect( anAction, SIGNAL( triggered( bool ) ), this, SLOT( onNew() ) );
112   aMgr->registerAction( anAction, NewId );
113
114   // 1.2 Create Open action
115   anAction = new QtxAction( tr( "MNU_PY_OPEN" ), QIcon( ":/images/py_open.png" ),
116                                   tr( "MNU_PY_OPEN" ), 0, this );
117   anAction->setStatusTip( tr( "DSC_PY_OPEN" ) );
118   anAction->setShortcuts( QKeySequence::Open );
119   connect( anAction, SIGNAL( triggered( bool ) ), this, SLOT( onOpen() ) );
120   aMgr->registerAction( anAction, OpenId );
121
122   // 1.3. Create Save action
123   anAction = new QtxAction( tr( "MNU_PY_SAVE" ), QIcon( ":/images/py_save.png" ),
124                                   tr( "MNU_PY_SAVE" ), 0, this );
125   anAction->setStatusTip( tr( "DSC_PY_SAVE" ) );
126   anAction->setShortcuts( QKeySequence::Save );
127   connect( anAction, SIGNAL( triggered( bool ) ), this, SLOT( onSave() ) );
128   aMgr->registerAction( anAction, SaveId );
129   // Set default statement for Save action
130   anAction->setEnabled( my_TextEditor->document()->isModified() );
131   connect( my_TextEditor->document(), SIGNAL( modificationChanged( bool ) ),
132     anAction, SLOT( setEnabled( bool ) ) );
133
134   // 1.4. Create SaveAs action
135   anAction = new QtxAction( tr( "MNU_PY_SAVEAS" ), QIcon( ":/images/py_save_as.png" ),
136                                     tr( "MNU_PY_SAVEAS" ), 0, this );
137   anAction->setStatusTip( tr( "DSC_PY_SAVEAS" ) );
138   anAction->setShortcut( Qt::CTRL + Qt::SHIFT + Qt::Key_S );
139   connect( anAction, SIGNAL( triggered( bool ) ), this, SLOT( onSaveAs() ) );
140   aMgr->registerAction( anAction, SaveAsId );
141
142   // 1.5 Create multi-action for file operations
143   /*QtxMultiAction* aFileAction = new QtxMultiAction( this );
144   aFileAction->insertAction( aMgr->action( NewId ) );
145   aFileAction->insertAction( aMgr->action( OpenId ) );
146   aFileAction->insertAction( aMgr->action( SaveId ) );
147   aFileAction->insertAction( aMgr->action( SaveAsId ) );
148   aMgr->registerAction( aFileAction, FileOpId );*/
149
150   // 1.6. Create Close action
151   if (isExternal())
152   {
153     anAction = new QtxAction( tr( "MNU_PY_CLOSE" ), QIcon( ":/images/py_close.png" ),
154                               tr( "MNU_PY_CLOSE" ), 0, this );
155     anAction->setStatusTip( tr( "DSC_PY_CLOSE" ) );
156     anAction->setShortcut( Qt::CTRL + Qt::Key_Q );
157     connect( anAction, SIGNAL( triggered( bool ) ), this, SLOT( close() ) );
158     aMgr->registerAction( anAction, CloseId );
159   }
160
161   // 2. Edit operations
162   // 2.1. Create Undo action
163   anAction = new QtxAction( tr( "MNU_PY_UNDO" ), QIcon( ":/images/py_undo.png" ),
164                             tr( "MNU_PY_UNDO" ), 0, this );
165   anAction->setStatusTip( tr( "DSC_PY_UNDO" ) );
166   anAction->setShortcuts( QKeySequence::Undo );
167   connect( anAction, SIGNAL( triggered( bool ) ), my_TextEditor, SLOT( undo() ) );
168   aMgr->registerAction( anAction, UndoId );
169   // Set default statement for Undo action
170   anAction->setEnabled( my_TextEditor->document()->isUndoAvailable() );
171   connect( my_TextEditor->document(), SIGNAL( undoAvailable( bool ) ),
172            anAction, SLOT( setEnabled( bool ) ) );
173
174   // 2.2. Create Redo action
175   anAction = new QtxAction( tr( "MNU_PY_REDO" ), QIcon( ":/images/py_redo.png" ),
176                             tr( "MNU_PY_REDO" ), 0, this );
177   anAction->setStatusTip( tr( "DSC_PY_REDO" ) );
178   anAction->setShortcuts( QKeySequence::Redo );
179   connect( anAction, SIGNAL( triggered( bool ) ), my_TextEditor, SLOT( redo() ) );
180   aMgr->registerAction( anAction, RedoId );
181   // Set default statement for Redo action
182   anAction->setEnabled( my_TextEditor->document()->isRedoAvailable() );
183   connect( my_TextEditor->document(), SIGNAL( redoAvailable( bool ) ),
184            anAction, SLOT( setEnabled( bool ) ) );
185
186   // 2.3. Create Cut action
187   anAction = new QtxAction( tr( "MNU_PY_CUT" ), QIcon( ":/images/py_cut.png" ),
188                             tr( "MNU_PY_CUT" ), 0, this );
189   anAction->setStatusTip( tr( "DSC_PY_CUT" ) );
190   anAction->setShortcuts( QKeySequence::Cut );
191   connect( anAction, SIGNAL( triggered( bool ) ), my_TextEditor, SLOT( cut() ) );
192   aMgr->registerAction( anAction, CutId );
193   // Set default statement for Cut action
194   anAction->setEnabled( false );
195   connect( my_TextEditor, SIGNAL( copyAvailable( bool ) ),
196            anAction, SLOT( setEnabled( bool ) ) );
197
198   // 2.4. Create Copy action
199   anAction = new QtxAction( tr( "MNU_PY_COPY" ), QIcon( ":/images/py_copy.png" ),
200                             tr( "MNU_PY_COPY" ), 0, this );
201   anAction->setStatusTip( tr( "DSC_PY_COPY" ) );
202   anAction->setShortcuts( QKeySequence::Copy );
203   connect( anAction, SIGNAL( triggered( bool ) ), my_TextEditor, SLOT( copy() ) );
204   aMgr->registerAction( anAction, CopyId );
205   // Set default statement for Copy action
206   anAction->setEnabled( false );
207   connect( my_TextEditor, SIGNAL( copyAvailable( bool ) ),
208            anAction, SLOT( setEnabled( bool ) ) );
209
210   // 2.5. Create Paste action
211   anAction = new QtxAction( tr( "MNU_PY_PASTE" ), QIcon( ":/images/py_paste.png" ),
212                             tr( "MNU_PY_PASTE" ), 0, this );
213   anAction->setStatusTip( tr( "DSC_PY_PASTE" ) );
214   anAction->setShortcuts( QKeySequence::Paste );
215   connect( anAction, SIGNAL( triggered( bool ) ), my_TextEditor, SLOT( paste() ) );
216   aMgr->registerAction( anAction, PasteId );
217
218   // 2.6. Create Delete action
219   anAction = new QtxAction( tr( "MNU_PY_DELETE" ), QIcon( ":/images/py_delete.png" ),
220                             tr( "MNU_PY_DELETE" ), 0, this );
221   anAction->setStatusTip( tr( "DSC_PY_DELETE" ) );
222   anAction->setShortcuts( QKeySequence::Delete );
223   connect( anAction, SIGNAL( triggered( bool ) ), my_TextEditor, SLOT( deleteSelected() ) );
224   aMgr->registerAction( anAction, DeleteId );
225   // Set default statement for Delete action
226   anAction->setEnabled( false );
227   connect( my_TextEditor, SIGNAL( copyAvailable( bool ) ),
228            anAction, SLOT( setEnabled( bool ) ) );
229
230   // 2.7. Create SelectAll action
231   anAction = new QtxAction( tr( "MNU_PY_SELECTALL" ), QIcon( ":/images/py_select_all.png" ),
232                             tr( "MNU_PY_SELECTALL" ), 0, this );
233   anAction->setStatusTip( tr( "DSC_PY_SELECT_ALL" ) );
234   anAction->setShortcuts( QKeySequence::SelectAll );
235   connect( anAction, SIGNAL( triggered( bool ) ), my_TextEditor, SLOT( selectAll() ) );
236   aMgr->registerAction( anAction, SelectAllId );
237
238   // 2.8. Create multi-action for edit operations
239   /*QtxMultiAction* anEditAction = new QtxMultiAction( this );
240   anEditAction->insertAction( aMgr->action( UndoId ) );
241   anEditAction->insertAction( aMgr->action( RedoId ) );
242   anEditAction->insertAction( aMgr->action( CutId ) );
243   anEditAction->insertAction( aMgr->action( CopyId ) );
244   anEditAction->insertAction( aMgr->action( PasteId ) );
245   anEditAction->insertAction( aMgr->action( DeleteId ) );
246   anEditAction->insertAction( aMgr->action( SelectAllId ) );
247   aMgr->registerAction( anEditAction, EditOpId );*/
248
249   // 3. Create Preference action
250   anAction = new QtxAction( tr( "MNU_PY_PREFERENCES" ), QIcon( ":/images/py_preferences.png" ),
251                             tr( "MNU_PY_PREFERENCES" ), 0, this );
252   anAction->setStatusTip( tr( "DSC_PY_PREFERENCES" ) );
253   connect( anAction, SIGNAL( triggered( bool ) ), this, SLOT( onPreferences() ) );
254   aMgr->registerAction( anAction, PreferencesId );
255
256   // 4. Help operations
257
258   // 4.1. Create Help action
259   anAction = new QtxAction( tr( "MNU_PY_BROWSER" ), QIcon( ":/images/py_browser.png" ),
260                             tr( "MNU_PY_BROWSER" ), 0, this );
261   anAction->setStatusTip( tr( "DSC_PY_BROWSER" ) );
262   connect( anAction, SIGNAL( triggered() ), this, SLOT( onBrowser() ) );
263   //aMgr->registerAction( anAction, BrowserId );
264
265   // 4.2. Create multi-action for help operations
266   /*QtxMultiAction* aHelpAction = new QtxMultiAction( this );
267   aHelpAction->insertAction( aMgr->action( BrowserId ) );
268   aMgr->registerAction( aHelpAction, HelpOpId );*/
269 }
270
271 /*!
272   \brief Create toolbar for the python view window.
273 */
274 void PyViewer_ViewWindow::createToolBar()
275 {
276   QtxActionToolMgr* aMgr = toolMgr();
277   int idTB = aMgr->createToolBar( tr("LBL_TOOLBAR_LABEL"),         // title (language-dependent)
278                                   QString( "PyEditorOperations" ), // name (language-independent)
279                                   false );                         // disable floatable toolbar
280   aMgr->append( NewId, idTB );
281   aMgr->append( OpenId, idTB );
282   aMgr->append( SaveId, idTB );
283   aMgr->append( SaveAsId, idTB );
284   if ( isExternal() )
285     aMgr->append( CloseId, idTB );
286   aMgr->append( aMgr->separator(), idTB );
287   aMgr->append( UndoId, idTB );
288   aMgr->append( RedoId, idTB );
289   aMgr->append( aMgr->separator(), idTB );
290   aMgr->append( CutId, idTB );
291   aMgr->append( CopyId, idTB );
292   aMgr->append( PasteId, idTB );
293   aMgr->append( DeleteId, idTB );
294   aMgr->append( SelectAllId, idTB );
295   aMgr->append( aMgr->separator(), idTB );
296   aMgr->append( PreferencesId, idTB );
297   aMgr->append( aMgr->separator(), idTB );
298   aMgr->append( BrowserId, idTB );
299
300 }
301
302 /*!
303   \brief Reimplemented class is to receive a window close request.
304   \param theEvent event
305 */
306 void PyViewer_ViewWindow::closeEvent( QCloseEvent* theEvent )
307 {
308   if ( whetherSave() )
309     theEvent->accept();
310   else
311     theEvent->ignore();
312 }
313
314 /*!
315   SLOT: Creates a new document
316  */
317 void PyViewer_ViewWindow::onNew()
318 {
319   if ( whetherSave() )
320   {
321     my_TextEditor->clear();
322     setCurrentFile( QString() );
323   }
324 }
325
326 /*!
327   SLOT: Open an existing python file
328  */
329 void PyViewer_ViewWindow::onOpen()
330 {
331   if ( whetherSave() )
332   {
333     QString aFilePath = QFileDialog::getOpenFileName( 
334       this, tr( "TIT_DLG_OPEN" ), QDir::currentPath(), "Python Files (*.py)" );
335
336     if ( !aFilePath.isEmpty() )
337       loadFile( aFilePath );
338   }
339 }
340
341 /*!
342   SLOT: Save the current file
343  */
344 bool PyViewer_ViewWindow::onSave()
345 {
346   if ( my_CurrentFile.isEmpty() )
347     return onSaveAs();
348   else
349     return saveFile( my_CurrentFile );
350 }
351
352
353 /*!
354   SLOT: Save the current file under a new name
355  */
356 bool PyViewer_ViewWindow::onSaveAs()
357 {
358   QString aFilePath = QFileDialog::getSaveFileName(
359     this, tr( "TIT_DLG_SAVEAS" ), QDir::currentPath(), "Python Files (*.py)" );
360
361   if ( !aFilePath.isEmpty() )
362     return saveFile( aFilePath );
363
364   return false;
365 }
366
367 /*!
368   SLOT: Open preferences dialog
369  */
370 void PyViewer_ViewWindow::onPreferences()
371 {
372   PyEditor_SettingsDlg* aPage = new PyEditor_SettingsDlg( my_TextEditor, this );
373   connect( aPage, SIGNAL( onHelpClicked() ),
374            this, SLOT( onHelp() ) );
375   aPage->exec();
376   delete aPage;
377 }
378
379 /*!
380   \brief Set preferece values for view.
381  */
382 void PyViewer_ViewWindow::setPreferences()
383 {
384   my_TextEditor->settings()->readSettings();
385   my_TextEditor->updateStatement();
386 }
387
388 /*!
389   \brief Associates the theFilePath with the python view.
390   \param theFilePath file path
391  */
392 void PyViewer_ViewWindow::setCurrentFile( const QString &theFilePath )
393 {
394   my_CurrentFile = theFilePath;
395   my_TextEditor->document()->setModified( false );
396
397   if ( isExternal() )
398   {
399     setWindowModified( false );
400
401     QString aShownName = my_CurrentFile;
402     if ( my_CurrentFile.isEmpty() )
403       aShownName = "untitled.py";
404     setWindowFilePath( aShownName );
405
406     // Set window title with associated file path
407     QFileInfo anInfo( aShownName );
408     setWindowTitle( "Python Viewer - " + anInfo.fileName() + "[*]" );
409   }
410 }
411
412 /*!
413   \brief Checks whether the file is modified.
414   If it has the modifications then ask the user to save it.
415   \return true if the document is saved.
416  */
417 bool PyViewer_ViewWindow::whetherSave()
418 {
419   if ( my_TextEditor->document()->isModified() )
420   {
421     QMessageBox::StandardButton aReturn;
422     aReturn = QMessageBox::warning(
423       this, tr( "TIT_DLG_SAVE" ),tr( "WRN_PY_SAVE_FILE" ),
424       QMessageBox::Save | QMessageBox::Discard | QMessageBox::Cancel );
425
426     if ( aReturn == QMessageBox::Save )
427       return onSave();
428     else if ( aReturn == QMessageBox::Cancel )
429       return false;
430   }
431   return true;
432 }
433
434 /*!
435   \brief Opens file.
436   \param theFilePath file path
437  */
438 void PyViewer_ViewWindow::loadFile( const QString &theFilePath )
439 {
440   QFile aFile( theFilePath );
441   if ( !aFile.open(QFile::ReadOnly | QFile::Text) )
442   {
443     QMessageBox::warning( this, tr( "NAME_PYEDITOR" ),
444       tr( "WRN_PY_READ_FILE" ).arg( theFilePath ).arg( aFile.errorString() ) );
445     return;
446   }
447
448   QTextStream anInput( &aFile );
449   QApplication::setOverrideCursor( Qt::WaitCursor );
450   my_TextEditor->setPlainText( anInput.readAll() );
451   QApplication::restoreOverrideCursor();
452
453   setCurrentFile( theFilePath );
454   aFile.close();
455   if ( isExternal() )
456     statusBar()->showMessage( tr( "STS_F_LOADED" ), 2000 );
457 }
458
459 /*!
460   \brief Saves file.
461   \param theFilePath file path
462  */
463 bool PyViewer_ViewWindow::saveFile( const QString &theFilePath )
464 {
465   QFile aFile( theFilePath );
466   if ( !aFile.open( QFile::WriteOnly | QFile::Text ) )
467   {
468     QMessageBox::warning( this, tr( "NAME_PYEDITOR" ),
469       tr( "WRN_PY_WRITE_FILE" ).arg( theFilePath ).arg( aFile.errorString() ) );
470     return false;
471   }
472
473   QTextStream anOutput( &aFile );
474   QApplication::setOverrideCursor( Qt::WaitCursor );
475   anOutput << my_TextEditor->toPlainText();
476   QApplication::restoreOverrideCursor();
477
478   setCurrentFile( theFilePath );
479   aFile.close();
480
481   if ( isExternal() )
482     statusBar()->showMessage( tr( "STS_F_SAVED" ), 2000 );
483
484   return true;
485 }
486
487 /*!
488   \brief Opens help browser with python view help information.
489  */
490 void PyViewer_ViewWindow::onBrowser()
491 {
492   QDir appDir = QApplication::applicationDirPath();
493   QStringList parameters;
494   parameters << QString( "--file=%1" ).arg( appDir.filePath( "pyeditor.html" ) );
495   QProcess::startDetached( "HelpBrowser", parameters );
496 }
497
498 /*!
499   Slot, called when user clicks "Help" button in "Preferences" dialog box.
500 */
501 void PyViewer_ViewWindow::onHelp()
502 {
503 #ifndef NO_SUIT
504   SUIT_Application* app = SUIT_Session::session()->activeApplication();
505   if ( app )
506     app->onHelpContextModule( "GUI", "python_viewer_page.html", "custom_python_preferences" );
507 #endif
508 }