Salome HOME
Copyright update 2022
[modules/gui.git] / src / PyViewer / PyViewer_ViewWindow.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   : 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_Widget.h"
26 #include "PyEditor_SettingsDlg.h"
27
28 #include "SUIT_Session.h"
29 #include "SUIT_ResourceMgr.h"
30
31 #include "QtxAction.h"
32 #include "QtxActionToolMgr.h"
33
34 #include <QApplication>
35 #include <QCloseEvent>
36 #include <QFileDialog>
37 #include <QMessageBox>
38 #include <QTextStream>
39 #include <QVBoxLayout>
40
41 /*!
42   \class PyViewer_ViewWindow
43   \brief Python view window.
44 */
45
46 /*!
47   \brief Constructor.
48   \param desktop SALOME desktop window
49 */
50 PyViewer_ViewWindow::PyViewer_ViewWindow( SUIT_Desktop* desktop ) :
51   SUIT_ViewWindow( desktop )
52 {
53   // Create central widget.
54   myEditor = new PyEditor_Widget( this );
55   setCentralWidget( myEditor );
56
57   // Create actions.
58   SUIT_ResourceMgr* resMgr = SUIT_Session::session()->resourceMgr();
59   QtxAction* action;
60
61   // . New
62   action = new QtxAction( tr( "TTP_NEW" ),
63                           resMgr->loadPixmap( "PyViewer", tr( "ICON_NEW" ) ),
64                           tr( "ACT_NEW" ), 0, this );
65   action->setStatusTip( tr( "DSC_NEW" ) );
66   action->setShortcut( QKeySequence::New );
67   connect( action, SIGNAL( triggered( bool ) ), this, SLOT( onNew() ) );
68   toolMgr()->registerAction( action, NewId );
69   
70   // . Open
71   action = new QtxAction( tr( "TTP_OPEN" ),
72                           resMgr->loadPixmap( "PyViewer", tr( "ICON_OPEN" ) ),
73                           tr( "ACT_OPEN" ), 0, this );
74   action->setStatusTip( tr( "DSC_OPEN" ) );
75   action->setShortcut( QKeySequence::Open );
76   connect( action, SIGNAL( triggered( bool ) ), this, SLOT( onOpen() ) );
77   toolMgr()->registerAction( action, OpenId );
78   
79   // . Save
80   action = new QtxAction( tr( "TTP_SAVE" ),
81                           resMgr->loadPixmap( "PyViewer", tr( "ICON_SAVE" ) ),
82                           tr( "ACT_SAVE" ), 0, this );
83   action->setStatusTip( tr( "DSC_SAVE" ) );
84   action->setShortcut( QKeySequence::Save );
85   connect( action, SIGNAL( triggered( bool ) ), this, SLOT( onSave() ) );
86   action->setEnabled( false );
87   connect( myEditor, SIGNAL( modificationChanged( bool ) ),
88            action, SLOT( setEnabled( bool ) ) );
89   toolMgr()->registerAction( action, SaveId );
90   
91   // . SaveAs
92   action = new QtxAction( tr( "TTP_SAVEAS" ),
93                           resMgr->loadPixmap( "PyViewer", tr( "ICON_SAVEAS" ) ),
94                           tr( "ACT_SAVEAS" ), 0, this );
95   action->setStatusTip( tr( "DSC_SAVEAS" ) );
96   action->setShortcut( QKeySequence::SaveAs );
97   connect( action, SIGNAL( triggered( bool ) ), this, SLOT( onSaveAs() ) );
98   toolMgr()->registerAction( action, SaveAsId );
99
100   // . Undo
101   action = new QtxAction( tr( "TTP_UNDO" ),
102                           resMgr->loadPixmap( "PyViewer", tr( "ICON_UNDO" ) ),
103                           tr( "ACT_UNDO" ), 0, this );
104   action->setStatusTip( tr( "DSC_UNDO" ) );
105   action->setShortcut( QKeySequence::Undo );
106   connect( action, SIGNAL( triggered( bool ) ), myEditor, SLOT( undo() ) );
107   action->setEnabled( false );
108   connect( myEditor, SIGNAL( undoAvailable( bool ) ),
109            action, SLOT( setEnabled( bool ) ) );
110   toolMgr()->registerAction( action, UndoId );
111
112   // . Redo
113   action = new QtxAction( tr( "TTP_REDO" ), 
114                           resMgr->loadPixmap( "PyViewer", tr( "ICON_REDO" ) ),
115                           tr( "ACT_REDO" ), 0, this );
116   action->setStatusTip( tr( "DSC_REDO" ) );
117   action->setShortcut( QKeySequence::Redo );
118   connect( action, SIGNAL( triggered( bool ) ), myEditor, SLOT( redo() ) );
119   action->setEnabled( false );
120   connect( myEditor, SIGNAL( redoAvailable( bool ) ),
121            action, SLOT( setEnabled( bool ) ) );
122   toolMgr()->registerAction( action, RedoId );
123
124   // . Cut
125   action = new QtxAction( tr( "TTP_CUT" ),
126                           resMgr->loadPixmap( "PyViewer", tr( "ICON_CUT" ) ),
127                           tr( "ACT_CUT" ), 0, this );
128   action->setStatusTip( tr( "DSC_CUT" ) );
129   action->setShortcut( QKeySequence::Cut );
130   connect( action, SIGNAL( triggered( bool ) ), myEditor, SLOT( cut() ) );
131   action->setEnabled( false );
132   connect( myEditor, SIGNAL( copyAvailable( bool ) ),
133            action, SLOT( setEnabled( bool ) ) );
134   toolMgr()->registerAction( action, CutId );
135
136   // . Copy
137   action = new QtxAction( tr( "TTP_COPY" ), 
138                           resMgr->loadPixmap( "PyViewer", tr( "ICON_COPY" ) ),
139                           tr( "ACT_COPY" ), 0, this );
140   action->setStatusTip( tr( "DSC_COPY" ) );
141   action->setShortcut( QKeySequence::Copy );
142   connect( action, SIGNAL( triggered( bool ) ), myEditor, SLOT( copy() ) );
143   action->setEnabled( false );
144   connect( myEditor, SIGNAL( copyAvailable( bool ) ),
145            action, SLOT( setEnabled( bool ) ) );
146   toolMgr()->registerAction( action, CopyId );
147
148   // . Paste
149   action = new QtxAction( tr( "TTP_PASTE" ),
150                           resMgr->loadPixmap( "PyViewer", tr( "ICON_PASTE" ) ),
151                           tr( "ACT_PASTE" ), 0, this );
152   action->setStatusTip( tr( "DSC_PASTE" ) );
153   action->setShortcut( QKeySequence::Paste );
154   connect( action, SIGNAL( triggered( bool ) ), myEditor, SLOT( paste() ) );
155   toolMgr()->registerAction( action, PasteId );
156
157   // . Delete
158   action = new QtxAction( tr( "TTP_DELETE" ),
159                           resMgr->loadPixmap( "PyViewer", tr( "ICON_DELETE" ) ),
160                           tr( "ACT_DELETE" ), 0, this );
161   action->setStatusTip( tr( "DSC_DELETE" ) );
162   action->setShortcut( QKeySequence::Delete );
163   connect( action, SIGNAL( triggered( bool ) ), myEditor, SLOT( deleteSelected() ) );
164   action->setEnabled( false );
165   connect( myEditor, SIGNAL( copyAvailable( bool ) ),
166            action, SLOT( setEnabled( bool ) ) );
167   toolMgr()->registerAction( action, DeleteId );
168
169   // . SelectAll
170   action = new QtxAction( tr( "TTP_SELECT_ALL" ),
171                           resMgr->loadPixmap( "PyViewer", tr( "ICON_SELECT_ALL" ) ),
172                           tr( "ACT_SELECT_ALL" ), 0, this );
173   action->setStatusTip( tr( "DSC_SELECT_ALL" ) );
174   action->setShortcut( QKeySequence::SelectAll );
175   connect( action, SIGNAL( triggered( bool ) ), myEditor, SLOT( selectAll() ) );
176   toolMgr()->registerAction( action, SelectAllId );
177
178   // . Find
179   action = new QtxAction( tr( "TTP_FIND" ),
180                           resMgr->loadPixmap( "PyViewer", tr( "ICON_FIND" ) ),
181                           tr( "ACT_FIND" ), 0, this );
182   action->setStatusTip( tr( "DSC_FIND" ) );
183   action->setShortcut( QKeySequence::Find );
184   action->setShortcutContext( Qt::WidgetShortcut );
185   connect( action, SIGNAL( triggered( bool ) ), myEditor, SLOT( find() ) );
186   toolMgr()->registerAction( action, FindId );
187
188   // . Replace
189   action = new QtxAction( tr( "TTP_REPLACE" ),
190                           resMgr->loadPixmap( "PyViewer", tr( "ICON_REPLACE" ) ),
191                           tr( "ACT_REPLACE" ), 0, this );
192   action->setStatusTip( tr( "DSC_REPLACE" ) );
193   action->setShortcuts( QList<QKeySequence>() << QKeySequence( "Ctrl+H" ) << QKeySequence( QKeySequence::Replace ) );
194   action->setShortcutContext( Qt::WidgetShortcut );
195   connect( action, SIGNAL( triggered( bool ) ), myEditor, SLOT( replace() ) );
196   toolMgr()->registerAction( action, ReplaceId );
197
198   // . Preferences
199   action = new QtxAction( tr( "TTP_PREFERENCES" ),
200                           resMgr->loadPixmap( "PyViewer", tr( "ICON_PREFERENCES" ) ),
201                           tr( "ACT_PREFERENCES" ), 0, this );
202   action->setStatusTip( tr( "DSC_PREFERENCES" ) );
203   connect( action, SIGNAL( triggered( bool ) ), this, SLOT( onPreferences() ) );
204   toolMgr()->registerAction( action, PreferencesId );
205
206   // . Help
207   action = new QtxAction( tr( "TTP_HELP" ),
208                           resMgr->loadPixmap( "PyViewer", tr( "ICON_HELP" ) ),
209                           tr( "ACT_HELP" ), 0, this );
210   action->setStatusTip( tr( "DSC_HELP" ) );
211   connect( action, SIGNAL( triggered() ), this, SLOT( onHelp() ) );
212   toolMgr()->registerAction( action, HelpId );
213
214   // Create toolbar.
215   int idTB = toolMgr()->createToolBar( tr("TOOLBAR_LABEL"), QString( "PythonEditor" ), false );
216   toolMgr()->append( NewId, idTB );
217   toolMgr()->append( OpenId, idTB );
218   toolMgr()->append( SaveId, idTB );
219   toolMgr()->append( SaveAsId, idTB );
220   toolMgr()->append( toolMgr()->separator(), idTB );
221   toolMgr()->append( UndoId, idTB );
222   toolMgr()->append( RedoId, idTB );
223   toolMgr()->append( toolMgr()->separator(), idTB );
224   toolMgr()->append( CutId, idTB );
225   toolMgr()->append( CopyId, idTB );
226   toolMgr()->append( PasteId, idTB );
227   toolMgr()->append( DeleteId, idTB );
228   toolMgr()->append( SelectAllId, idTB );
229   toolMgr()->append( toolMgr()->separator(), idTB );
230   toolMgr()->append( FindId, idTB );
231   toolMgr()->append( ReplaceId, idTB );
232   toolMgr()->append( toolMgr()->separator(), idTB );
233   toolMgr()->append( PreferencesId, idTB );
234   toolMgr()->append( toolMgr()->separator(), idTB );
235   toolMgr()->append( HelpId, idTB );
236
237   // Set current file.
238   setCurrentFile( QString() );
239 }
240
241 /*!
242   \brief Destructor.
243 */
244 PyViewer_ViewWindow::~PyViewer_ViewWindow()
245 {
246 }
247
248 /*!
249   \brief Manage window close request.
250   \param event close event
251 */
252 void PyViewer_ViewWindow::closeEvent( QCloseEvent* event )
253 {
254   if ( whetherSave() )
255     event->accept();
256   else
257     event->ignore();
258 }
259
260 /*!
261   SLOT: Create new document
262 */
263 void PyViewer_ViewWindow::onNew()
264 {
265   if ( whetherSave() )
266   {
267     myEditor->clear();
268     setCurrentFile( QString() );
269   }
270 }
271
272 /*!
273   SLOT: Open existing Python file
274 */
275 void PyViewer_ViewWindow::onOpen()
276 {
277   if ( whetherSave() )
278   {
279     QString filter = tr( "TIT_PY_FILES" );
280     filter += " (*.py)";
281     QString aFilePath = QFileDialog::getOpenFileName( this,
282                                                       tr( "TIT_DLG_OPEN" ),
283                                                       QDir::currentPath(),
284                                                       filter );
285
286     if ( !aFilePath.isEmpty() )
287       loadFile( aFilePath );
288   }
289 }
290
291 /*!
292   SLOT: Save current document
293 */
294 bool PyViewer_ViewWindow::onSave()
295 {
296   if ( myURL.isEmpty() )
297     return onSaveAs();
298   else
299     return saveFile( myURL );
300 }
301
302
303 /*!
304   SLOT: Save current document under a new name
305 */
306 bool PyViewer_ViewWindow::onSaveAs()
307 {
308   QString filter = tr( "TIT_PY_FILES" );
309   filter += " (*.py)";
310   QString url = myURL.isEmpty() ? defaultName() : myURL;
311   QString aFilePath = QFileDialog::getSaveFileName( this,
312                                                     tr( "TIT_DLG_SAVE" ),
313                                                     url,
314                                                     filter );
315
316   if ( !aFilePath.isEmpty() )
317     return saveFile( aFilePath );
318
319   return false;
320 }
321
322 /*!
323   SLOT: Open preferences dialog
324 */
325 void PyViewer_ViewWindow::onPreferences()
326 {
327   PyEditor_SettingsDlg dlg( myEditor->editor(), true, this );
328   connect( &dlg, SIGNAL( help() ), this, SLOT( onHelp() ) );
329   dlg.exec();
330 }
331
332 /*!
333   \brief Associate \a filePath with the current document
334   \param filePath document's file path
335 */
336 void PyViewer_ViewWindow::setCurrentFile( const QString& filePath )
337 {
338   myURL = filePath;
339   myEditor->setModified( false );
340 }
341
342 /*!
343   \brief Check whether the file is modified.
344   If it has the modifications then ask the user to save it.
345   \return true if the document is saved.
346 */
347 bool PyViewer_ViewWindow::whetherSave()
348 {
349   if ( myEditor->isModified() )
350   {
351     QMessageBox::StandardButton answer =  QMessageBox::warning( this,
352                                                                 tr( "NAME_PYEDITOR" ),
353                                                                 tr( "WRN_SAVE_FILE" ),
354                                                                 QMessageBox::Save |
355                                                                 QMessageBox::Discard |
356                                                                 QMessageBox::Cancel );
357     switch( answer )
358     {
359     case QMessageBox::Save:
360       return onSave();
361     case QMessageBox::Cancel:
362       return false;
363     default:
364       break;
365     }
366   }
367   return true;
368 }
369
370 /*!
371   \brief Open file.
372   \param filePath file path
373 */
374 void PyViewer_ViewWindow::loadFile( const QString& filePath )
375 {
376   QFile aFile( filePath );
377   if ( !aFile.open(QFile::ReadOnly | QFile::Text) )
378   {
379     QMessageBox::warning( this, tr( "NAME_PYEDITOR" ),
380                           tr( "WRN_READ_FILE" ).arg( filePath ).arg( aFile.errorString() ) );
381     return;
382   }
383
384   QTextStream anInput( &aFile );
385   QApplication::setOverrideCursor( Qt::WaitCursor );
386   myEditor->setText( anInput.readAll() );
387   QApplication::restoreOverrideCursor();
388
389   setCurrentFile( filePath );
390   aFile.close();
391
392 }
393
394 /*!
395   \brief Saves file.
396   \param theFilePath file path
397 */
398 bool PyViewer_ViewWindow::saveFile( const QString& filePath )
399 {
400   QFile aFile( filePath );
401   if ( !aFile.open( QFile::WriteOnly | QFile::Text ) )
402   {
403     QMessageBox::warning( this, tr( "NAME_PYEDITOR" ),
404                           tr( "WRN_WRITE_FILE" ).arg( filePath ).arg( aFile.errorString() ) );
405     return false;
406   }
407
408   QTextStream anOutput( &aFile );
409   QApplication::setOverrideCursor( Qt::WaitCursor );
410   anOutput << myEditor->text();
411   QApplication::restoreOverrideCursor();
412
413   setCurrentFile( filePath );
414   aFile.close();
415
416   return true;
417 }
418
419 /*!
420   Slot, called when user clicks "Help" button in "Preferences" dialog box.
421 */
422 void PyViewer_ViewWindow::onHelp()
423 {
424   SUIT_Application* app = SUIT_Session::session()->activeApplication();
425   if ( app ) {
426     QString page = "python_viewer.html";
427     if ( qobject_cast<QWidget*>( sender() ) )
428       page += "#custom-python-preferences";
429     app->onHelpContextModule( "GUI", page );
430   }
431 }
432
433 /*!
434   Get default name for Python file 
435   \return default name
436 */
437 QString PyViewer_ViewWindow::defaultName() const
438 {
439   return tr( "NONAME" );
440 }