Salome HOME
initial implementation of automatic rotation in SHAPER's OCC viewer
[modules/gui.git] / src / SUIT / SUIT_ViewWindow.cxx
1 // Copyright (C) 2007-2023  CEA, EDF, OPEN CASCADE
2 //
3 // Copyright (C) 2003-2007  OPEN CASCADE, EADS/CCR, LIP6, CEA/DEN,
4 // CEDRAT, EDF R&D, LEG, PRINCIPIA R&D, BUREAU VERITAS
5 //
6 // This library is free software; you can redistribute it and/or
7 // modify it under the terms of the GNU Lesser General Public
8 // License as published by the Free Software Foundation; either
9 // version 2.1 of the License, or (at your option) any later version.
10 //
11 // This library is distributed in the hope that it will be useful,
12 // but WITHOUT ANY WARRANTY; without even the implied warranty of
13 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14 // Lesser General Public License for more details.
15 //
16 // You should have received a copy of the GNU Lesser General Public
17 // License along with this library; if not, write to the Free Software
18 // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
19 //
20 // See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com
21 //
22
23 // SUIT_ViewWindow.cxx: implementation of the SUIT_ViewWindow class.
24 //
25 #include "SUIT_ViewWindow.h"
26
27 #include "SUIT_Tools.h"
28 #include "SUIT_Session.h"
29 #include "SUIT_Study.h"
30 #include "SUIT_Desktop.h"
31 #include "SUIT_MessageBox.h"
32 #include "SUIT_Application.h"
33 #include "SUIT_ViewManager.h"
34 #include "SUIT_ResourceMgr.h"
35 #include "SUIT_FileDlg.h"
36 #include "QtxActionToolMgr.h"
37 #include "QtxMultiAction.h"
38
39 #include <QEvent>
40 #include <QIcon>
41 #include <QMenu>
42 #include <QApplication>
43 #include <QContextMenuEvent>
44
45 /*!\class SUIT_ViewWindow
46  * Class provide view window.
47  */
48
49 /*! Dump view custom event*/
50 const int DUMP_EVENT = QEvent::User + 123;
51
52 /*! Constructor.*/
53 SUIT_ViewWindow::SUIT_ViewWindow( SUIT_Desktop* theDesktop )
54   : QMainWindow( theDesktop ), myManager( 0 ), myIsDropDown( true ), mySyncAction( 0 )
55 {
56   myDesktop = theDesktop;
57
58   setWindowIcon( myDesktop ? myDesktop->windowIcon() : QApplication::windowIcon() );
59
60   setAttribute( Qt::WA_DeleteOnClose );
61
62   myToolMgr = new QtxActionToolMgr( this );
63
64   setProperty( "VectorsMode", false );
65 }
66
67 /*! Destructor.*/
68 SUIT_ViewWindow::~SUIT_ViewWindow()
69 {
70 }
71
72 /*!
73   Sets new view manager for window
74   \param theManager - new view manager
75 */
76 void SUIT_ViewWindow::setViewManager( SUIT_ViewManager* theManager )
77 {
78   myManager = theManager;
79 }
80
81 /*!
82   \return view manager of window
83 */
84 SUIT_ViewManager* SUIT_ViewWindow::getViewManager() const
85 {
86   return myManager;
87 }
88
89 /*!
90   \return QImage, containing all scene rendering in window
91 */
92 QImage SUIT_ViewWindow::dumpView()
93 {
94   return QImage();
95 }
96
97 /*!
98   Saves image to file according to the format
99   \param image - image
100   \param fileName - name of file
101   \param format - string contains name of format (for example, "BMP"(default) or "JPEG", "JPG")
102 */
103 bool SUIT_ViewWindow::dumpViewToFormat( const QImage& img, const QString& fileName, const QString& format )
104 {
105   if( img.isNull() )
106     return false;
107
108   QString fmt = format;
109   if( fmt.isEmpty() )
110     fmt = QString( "BMP" ); // default format
111   else if( fmt == "JPG" )
112     fmt = "JPEG";
113
114   QApplication::setOverrideCursor( Qt::WaitCursor );
115   bool res = img.save( fileName, fmt.toLatin1() );
116   QApplication::restoreOverrideCursor();
117   return res;
118 }
119
120 /*!
121   Saves scene rendering in window to file
122   \param fileName - name of file
123   \param format - string contains name of format (for example, "BMP"(default) or "JPEG", "JPG")
124 */
125 bool SUIT_ViewWindow::dumpViewToFormat( const QString& fileName, const QString& format )
126 {
127   Qtx::Localizer loc;
128   return dumpViewToFormat( dumpView(), fileName, format );
129 }
130
131 /*!
132   Set or clear flag Qt::WDestructiveClose
133 */
134 void SUIT_ViewWindow::setDestructiveClose( const bool on )
135 {
136   setAttribute( Qt::WA_DeleteOnClose, on );
137 }
138
139 /*! Close event \a theEvent.
140 */
141 void SUIT_ViewWindow::closeEvent( QCloseEvent* e )
142 {
143   e->ignore();
144   emit tryClosing( this );
145   if ( closable() ) emit closing( this );
146 }
147
148 /*! Context menu requested for event \a e.
149 */
150 void SUIT_ViewWindow::contextMenuEvent( QContextMenuEvent* e )
151 {
152   e->ignore();
153
154   QMainWindow::contextMenuEvent( e );
155
156   if ( e->isAccepted() )
157     return;
158
159   if ( e->reason() != QContextMenuEvent::Mouse )
160     emit contextMenuRequested( e );
161 }
162
163 /*! Post events on dump view.
164 */
165 void SUIT_ViewWindow::onDumpView()
166 {
167   // VSV (TRIPOLI dev): next line commented: causes error messages
168   //QApplication::postEvent( this, new QPaintEvent( QRect( 0, 0, width(), height() ) ) );
169   QApplication::postEvent( this, new QEvent( (QEvent::Type)DUMP_EVENT ) );
170 }
171
172 /*!
173   \return filters for image files
174 */
175 QString SUIT_ViewWindow::filter() const
176 {
177   return tr( "TLT_IMAGE_FILES" );
178 }
179
180 /*! Reaction view window on event \a e.
181 */
182 bool SUIT_ViewWindow::event( QEvent* e )
183 {
184   if ( e->type() == DUMP_EVENT )
185   {
186     bool bOk = false;
187     SUIT_Application* app = NULL;
188     if (myManager && myManager->study() && myManager->study()->application())
189       app = myManager->study()->application();
190     QString fileName;
191     if (app)
192       fileName = app->getFileName( false, QString(), filter(), tr( "TLT_DUMP_VIEW" ), 0 ); //old way
193     else
194     {
195       QStringList fls = filter().split( ";;", QString::SkipEmptyParts );
196       fileName = SUIT_FileDlg::getFileName( NULL, QString(), fls, tr( "TLT_DUMP_VIEW" ), false, true );
197     }
198     if ( !fileName.isEmpty() )
199     {
200       QString fmt = SUIT_Tools::extension( fileName ).toUpper();
201       QImage im = dumpView();   
202       Qtx::Localizer loc;
203       bOk = dumpViewToFormat( im, fileName, fmt );
204     }
205     else
206       bOk = true; // cancelled
207
208     if ( !bOk )
209       SUIT_MessageBox::critical( this, tr( "ERROR" ), tr( "ERR_CANT_DUMP_VIEW" ) );
210
211     return true;
212   }
213   return QMainWindow::event( e );
214 }
215
216 /*! Called by SUIT_Accel::onActivated() when a key accelerator was activated and this window was active
217 */
218 bool SUIT_ViewWindow::onAccelAction( int _action )
219 {
220   return action( _action );
221 }
222
223 /*! action  handle standard action (zoom, pan) or custom action.  to be redefined in successors.
224 */
225 bool SUIT_ViewWindow::action( const int  )
226 {
227   return true;
228 }
229
230 /*! Returns \c true if view window can be closed by the user
231 */
232 bool SUIT_ViewWindow::closable() const
233 {
234   QVariant val = property( "closable" );
235   return !val.isValid() || val.toBool();
236 }
237
238 /*! Set / reset "closable" option of the view window
239 */
240 bool SUIT_ViewWindow::setClosable( const bool on )
241 {
242   bool prev = closable();
243   setProperty( "closable", on );
244   return prev;
245 }
246
247 /*!
248   \return string containing visual parameters of window
249 */
250 QString SUIT_ViewWindow::getVisualParameters()
251 {
252   return "empty";
253 }
254
255 /*!
256   Sets visual parameters of window by its string representation
257   \param parameters - string with visual parameters
258 */
259 void SUIT_ViewWindow::setVisualParameters( const QString& /*parameters*/ )
260 {
261 }
262
263 /*!
264   \return associated tool bar manager
265 */
266 QtxActionToolMgr* SUIT_ViewWindow::toolMgr() const
267 {
268   return myToolMgr;
269 }
270
271 /*!
272   \brief Set buttons mode to drop-down (\a on = \c true) or ligned (\a on = \c false)
273   \param on new buttons mode
274   \sa dropDownButtons()
275 */
276 void SUIT_ViewWindow::setDropDownButtons( bool on )
277 {
278   if ( myIsDropDown != on ) {
279     myIsDropDown = on;
280     if ( myIsDropDown ) {
281       ActionsMap::const_iterator it;
282       for( it = myMultiActions.constBegin(); it != myMultiActions.constEnd(); ++it )
283       {
284         int tid = it.key();
285         const QList<QtxMultiAction*>& mlist = it.value();
286         QList<QtxMultiAction*>::const_iterator mit;
287         for ( mit = mlist.constBegin(); mit != mlist.constEnd(); ++mit )
288         {
289           QtxMultiAction* ma = *mit;
290           const QList<QAction*> alist = ma->actions();
291           if ( alist.isEmpty() ) continue;
292           int idx = toolMgr()->index( toolMgr()->actionId( alist[0] ), tid );
293           if ( idx == -1 ) continue;
294           foreach ( QAction* a, alist ) toolMgr()->remove( toolMgr()->actionId( a ), tid );
295           toolMgr()->insert( ma, tid, idx );
296         }
297       }
298       myMultiActions.clear();
299     }
300     else {
301       QIntList tblist = toolMgr()->toolBarsIds();
302       QIntList alist  = toolMgr()->idList();
303       foreach( int aid, alist )
304       {
305         QtxMultiAction* ma = qobject_cast<QtxMultiAction*>( toolMgr()->action( aid ) );
306         if ( !ma ) continue;
307         foreach( int tid, tblist )
308         {
309           int idx = toolMgr()->index( aid, tid );
310           if ( idx >= 0 )
311           {
312             myMultiActions[ tid ].append( ma );
313             toolMgr()->remove( aid, tid );
314             foreach( QAction* a, ma->actions() ) toolMgr()->insert( a, tid, idx++ );
315           }
316         }
317       }
318     }
319   }
320 }
321
322 /*!
323   \brief Get current buttons mode
324   \return current buttons mode
325   \sa setDropDownButtons()
326 */
327 bool SUIT_ViewWindow::dropDownButtons() const
328 {
329   return myIsDropDown;
330 }
331
332 void SUIT_ViewWindow::enableAutoRotation( const bool aEnable )
333 {
334   myIsAutoRotation = aEnable;
335 }
336
337 /*!
338   \return automatic rotation enable flag
339 */
340 bool SUIT_ViewWindow::isAutoRotationEnabled() const
341 {
342   return myIsAutoRotation;
343 }
344
345 /*!
346   \return window unique identifier
347 */
348 int SUIT_ViewWindow::getId() const
349 {
350   return int(reinterpret_cast<long long>(this)); // todo: unsafe - converting pointer to int can give non-unique result
351 }
352
353 /*!
354   Get camera properties for the view window.
355   \return shared pointer on camera properties. Base implementation
356           returns null properties.
357 */
358 SUIT_CameraProperties SUIT_ViewWindow::cameraProperties()
359 {
360   return SUIT_CameraProperties();
361 }
362
363 /*!
364   Synchronize this view window's camera properties with specified
365   view window.
366
367   This method is a part of general views synchronization mechanism.
368   It should be redefined in successors. Base imlementation does nothing.
369
370   \param otherWindow other view window
371 */
372 void SUIT_ViewWindow::synchronize( SUIT_ViewWindow* /*otherWindow*/ )
373 {
374   // base implementation does nothing
375 }
376
377 /*!
378   Get action for views syncronization.
379
380   This method is a part of general views synchronization mechanism.
381   It creates an action that can be inserted, for instance, to the toolbar.
382
383   \return action for views synchronization
384 */
385 QAction* SUIT_ViewWindow::synchronizeAction()
386 {
387   if ( !mySyncAction ) {
388     SUIT_ResourceMgr* resMgr = SUIT_Session::session()->resourceMgr();
389     mySyncAction = new QtxAction( tr( "MNU_SYNCHRONIZE_VIEW" ),
390                                   resMgr->loadPixmap( "SUIT", tr( "ICON_VIEW_SYNC" ) ),
391                                   tr( "MNU_SYNCHRONIZE_VIEW" ), 0, this );
392     mySyncAction->setStatusTip( tr( "DSC_SYNCHRONIZE_VIEW" ) );
393     mySyncAction->setMenu( new QMenu( this ) );
394     mySyncAction->setCheckable( true );
395     connect( mySyncAction->menu(), SIGNAL( aboutToShow() ),     this, SLOT( updateSyncViews() ) );
396     connect( mySyncAction,         SIGNAL( triggered( bool ) ), this, SLOT( onSynchronizeView( bool ) ) );
397   }
398   return mySyncAction;
399 }
400
401 /*!
402   Emit notification signal that the view is transformed.
403   Other views can use the signal for synchronization.
404 */
405 void SUIT_ViewWindow::emitViewModified()
406 {
407   emit viewModified( this );
408 }
409
410 /*!
411   Update list of available view for the "Synchronize View" action
412 */
413 void SUIT_ViewWindow::updateSyncViews()
414 {
415   SUIT_CameraProperties props = cameraProperties();
416   if ( !props.isValid() )
417     return;
418
419   QAction* anAction = synchronizeAction();
420   if ( anAction && anAction->menu() ) {
421     int currentId = anAction->data().toInt();
422     anAction->menu()->clear();
423     SUIT_Application* app = SUIT_Session::session()->activeApplication();
424     if ( app ) {
425       SUIT_Desktop* d = app->desktop();
426       QList<SUIT_ViewWindow*> allViews = d->findChildren<SUIT_ViewWindow*>();
427       foreach( SUIT_ViewWindow* vw, allViews ) {
428         if ( !vw || vw == this ) continue; // skip invalid views and this one
429         SUIT_CameraProperties otherProps = vw->cameraProperties();
430         if ( otherProps.isCompatible( props ) ) {
431           QAction* a = anAction->menu()->addAction( vw->windowTitle() );
432           if ( vw->getId() == currentId ) {
433             QFont f = a->font();
434             f.setBold( true );
435             a->setFont( f );
436           }
437           a->setData( vw->getId() );
438           connect( a, SIGNAL( triggered( bool ) ), this, SLOT( onSynchronizeView( bool ) ) );
439         }
440         else if ( vw->getId() == currentId ) {
441           // other view, this one is being currently synchronized to, seems has become incompatible
442           // we have to break synchronization
443           vw->disconnect( SIGNAL( viewModified( SUIT_ViewWindow* ) ), this, SLOT( synchronize( SUIT_ViewWindow* ) ) );
444           this->disconnect( SIGNAL( viewModified( SUIT_ViewWindow* ) ), vw, SLOT( synchronize( SUIT_ViewWindow* ) ) );
445           // 
446           bool blocked = anAction->blockSignals( true );
447           anAction->setChecked( false );
448           anAction->blockSignals( blocked );
449           anAction->setData( 0 );
450           //
451           QAction* a = vw->synchronizeAction();
452           if ( a ) {
453             blocked = a->blockSignals( true );
454             a->setChecked( false );
455             a->blockSignals( blocked );
456           }
457         }
458       }
459     }
460     if ( anAction->menu()->actions().isEmpty() ) {
461       anAction->setData( 0 );
462       anAction->menu()->addAction( tr( "MNU_SYNC_NO_VIEW" ) );
463     }
464   }
465 }
466
467 /*!
468   "Synchronize View" action slot.
469 */
470 void SUIT_ViewWindow::onSynchronizeView( bool /*checked*/ )
471 {
472   QAction* a = qobject_cast<QAction*>( sender() );
473   if ( a ) {
474     synchronizeView( this, a->data().toInt() );
475   }
476 }
477
478 /*!
479   Synchronize camera properties of view \a viewWindow with
480   camera properties of view specified via \a id
481 */
482 void SUIT_ViewWindow::synchronizeView( SUIT_ViewWindow* viewWindow, int id )
483 {
484   SUIT_ViewWindow* sourceView = 0;
485   QList<SUIT_ViewWindow*> otherViews;
486
487   bool isSync = viewWindow->synchronizeAction() && viewWindow->synchronizeAction()->isChecked();
488
489   int vwid = viewWindow->getId();
490
491   SUIT_Application* app = SUIT_Session::session()->activeApplication();
492   if ( !app ) return;
493   SUIT_Desktop* d = app->desktop();
494   if ( !d ) return;
495
496   QList<SUIT_ViewWindow*> allViews = d->findChildren<SUIT_ViewWindow*>();
497   foreach( SUIT_ViewWindow* vw, allViews ) {
498     if ( !vw->cameraProperties().isValid() )
499       continue;                    // omit views not supporting camera properties
500     if ( vw->getId() == id )
501       sourceView = vw;             // remember source view
502     else if ( vw != viewWindow )
503       otherViews.append( vw );     // collect all remaining views
504   }
505
506   if ( isSync && id ) {
507     // remove all possible disconnections
508     foreach( SUIT_ViewWindow* vw, otherViews ) {
509       // disconnect target view
510       vw->disconnect( SIGNAL( viewModified( SUIT_ViewWindow* ) ), viewWindow, SLOT( synchronize( SUIT_ViewWindow* ) ) );
511       viewWindow->disconnect( SIGNAL( viewModified( SUIT_ViewWindow* ) ), vw, SLOT( synchronize( SUIT_ViewWindow* ) ) );
512       if ( sourceView ) {
513         // disconnect source view
514         vw->disconnect( SIGNAL( viewModified( SUIT_ViewWindow* ) ), sourceView, SLOT( synchronize( SUIT_ViewWindow* ) ) );
515         sourceView->disconnect( SIGNAL( viewModified( SUIT_ViewWindow* ) ), vw, SLOT( synchronize( SUIT_ViewWindow* ) ) );
516       }
517       QAction* a = vw->synchronizeAction();
518       if ( a ) {
519         int anid = a->data().toInt();
520         if ( a->isChecked() && ( anid == id || anid == vwid ) ) {
521           bool blocked = a->blockSignals( true );
522           a->setChecked( false );
523           a->blockSignals( blocked );
524         }
525       }
526     }
527     if ( sourceView ) {
528       // reconnect source and target views
529       sourceView->disconnect( SIGNAL( viewModified( SUIT_ViewWindow* ) ), viewWindow, SLOT( synchronize( SUIT_ViewWindow* ) ) );
530       viewWindow->disconnect( SIGNAL( viewModified( SUIT_ViewWindow* ) ), sourceView, SLOT( synchronize( SUIT_ViewWindow* ) ) );
531       sourceView->connect( viewWindow, SIGNAL( viewModified( SUIT_ViewWindow* ) ), SLOT( synchronize( SUIT_ViewWindow* ) ) );
532       viewWindow->connect( sourceView, SIGNAL( viewModified( SUIT_ViewWindow* ) ), SLOT( synchronize( SUIT_ViewWindow* ) ) );
533       // synchronize target view with source view
534       viewWindow->synchronize( sourceView );
535       if ( viewWindow->synchronizeAction() )
536         viewWindow->synchronizeAction()->setData( sourceView->getId() );
537       QAction* sourceAction = sourceView->synchronizeAction();
538       if ( sourceAction ) {
539         sourceAction->setData( viewWindow->getId() );
540         if ( !sourceAction->isChecked() ) {
541           bool blocked = sourceAction->blockSignals( true );
542           sourceAction->setChecked( true );
543           sourceAction->blockSignals( blocked );
544         }
545       }
546     }
547   }
548   else if ( sourceView ) {
549     // reconnect source and target view
550     sourceView->disconnect( SIGNAL( viewModified( SUIT_ViewWindow* ) ), viewWindow, SLOT( synchronize( SUIT_ViewWindow* ) ) );
551     viewWindow->disconnect( SIGNAL( viewModified( SUIT_ViewWindow* ) ), sourceView, SLOT( synchronize( SUIT_ViewWindow* ) ) );
552     viewWindow->synchronize( sourceView );
553     if ( viewWindow->synchronizeAction() )
554       viewWindow->synchronizeAction() ->setData( sourceView->getId() );
555     QAction* sourceAction = sourceView->synchronizeAction();
556     if ( sourceAction ) {
557       if ( sourceAction->data().toInt() == viewWindow->getId() && sourceAction->isChecked() ) {
558         bool blocked = sourceAction->blockSignals( true );
559         sourceAction->setChecked( false );
560         sourceAction->blockSignals( blocked );
561       }
562     }
563   }
564 }
565
566 void SUIT_ViewWindow::setVisible( bool on )
567 {
568   // This is a workaround to avoid showing view window as a top-level window
569   // before re-parenting it to workstack (issue #23467).
570   // See SUIT_Desktop::childEvent().
571   QApplication::sendPostedEvents( 0, QEvent::ChildRemoved );
572   QApplication::sendPostedEvents( 0, QEvent::ChildAdded );
573   QApplication::sendPostedEvents( 0, QEvent::ChildPolished );
574   if ( !property( "blockShow" ).toBool() )
575     QMainWindow::setVisible( on );
576 }