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