Salome HOME
Updated copyright comment
[modules/gui.git] / src / Qtx / QtxMainWindow.cxx
1 // Copyright (C) 2007-2024  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 //  File:      QtxMainWindow.cxx
23 //  Author:    Sergey TELKOV
24
25 #include "QtxMainWindow.h"
26
27 #include "QtxToolBar.h"
28
29 #include <QEvent>
30 #include <QPoint>
31 #include <QTimer>
32 #include <QLayout>
33 #include <QMenuBar>
34 #include <QStatusBar>
35 #include <QRubberBand>
36 #include <QMouseEvent>
37 #include <QApplication>
38 #include <QDesktopWidget>
39 #include <cstdio>
40 /*!
41   \class QtxMainWindow::Filter
42   \internal
43   \brief Internal object used to filter child removal events for 
44          specified widget from parent widget.
45 */
46
47 class QtxMainWindow::Filter : public QObject
48 {
49 public:
50   Filter( QWidget*, QtxMainWindow*, QObject* = 0 );
51   virtual ~Filter();
52
53   virtual bool eventFilter( QObject*, QEvent* );
54
55 private:
56   QMainWindow* myMain;      //!< parent main window
57   QWidget*     myWidget;    //!< widget being watched
58 };
59
60 /*!
61   \brief Constructor.
62   \param wid widget to be watched
63   \param mw parent main window
64   \param parent parent object (in terms of QObject)
65 */
66 QtxMainWindow::Filter::Filter( QWidget* wid, QtxMainWindow* mw, QObject* parent )
67 : QObject( parent ),
68   myMain( mw ),
69   myWidget( wid )
70 {
71   QApplication::instance()->installEventFilter( this );
72 }
73
74 /*!
75   \brief Destructor.
76 */
77 QtxMainWindow::Filter::~Filter()
78 {
79 }
80
81 /*!
82   \brief Event filter.
83
84   Watches for the specified widget and prevents its removal from the
85   parent main window.
86
87   \param o recevier object
88   \param e event
89 */
90 bool QtxMainWindow::Filter::eventFilter( QObject* o, QEvent* e )
91 {
92   if ( myMain == o && e->type() == QEvent::ChildRemoved &&
93        myWidget == ((QChildEvent*)e)->child() )
94     return true;
95
96   return QObject::eventFilter( o, e );
97 }
98
99 /*!
100   \class QtxMainWindow::Resizer
101   \internal
102   \brief Internal object used to resize dock widgets.
103 */
104
105 class QtxMainWindow::Resizer : public QObject
106 {
107 public:
108   Resizer( const QPoint&, const Qt::Orientation, QtxMainWindow* );
109   virtual ~Resizer();
110
111   QMouseEvent*    finalEvent() const;
112   void            setFinalEvent( QMouseEvent* );
113
114   void            setPosition( const QPoint& );
115   virtual bool    eventFilter( QObject*, QEvent* );
116
117 private:
118   void            setFilters( bool );
119
120 private:
121   QPoint          myPos;
122   QMainWindow*    myMain;
123   QRubberBand*    myRubber;
124   Qt::Orientation myOrient;
125   QMouseEvent*    myFinEvent;
126 };
127
128 /*!
129   \brief Constructor.
130   \param mw parent main window
131 */
132 QtxMainWindow::Resizer::Resizer( const QPoint& p, const Qt::Orientation o, QtxMainWindow* mw )
133 : QObject( mw ),
134   myMain( mw ),
135   myOrient( o ),
136   myFinEvent( 0 )
137 {
138   setFilters( true );
139
140   myRubber = new QRubberBand( QRubberBand::Line, 0 );
141
142   setPosition( p );
143
144   myRubber->hide();
145 }
146
147 /*!
148   \brief Destructor.
149 */
150 QtxMainWindow::Resizer::~Resizer()
151 {
152   delete myRubber;
153
154   setFilters( false );
155 }
156
157 void QtxMainWindow::Resizer::setPosition( const QPoint& pos )
158 {
159   myPos = pos;
160   if ( myRubber ) {
161     QRect r;
162     QPoint min = myMain->mapToGlobal( myMain->rect().topLeft() );
163     QPoint max = myMain->mapToGlobal( myMain->rect().bottomRight() );
164     if ( myOrient == Qt::Horizontal ) {
165       int p = qMax( qMin( myPos.y(), max.y() ), min.y() );
166       r = QRect( myMain->mapToGlobal( QPoint( 0, 0 ) ).x(), p - 1, myMain->width(), 3 );
167     }
168     else {
169       int p = qMax( qMin( myPos.x(), max.x() ), min.x() );
170       r = QRect( p - 1, myMain->mapToGlobal( QPoint( 0, 0 ) ).y(), 3, myMain->height() );
171     }
172     myRubber->setGeometry( r );
173     if ( !myRubber->isVisible() )
174       myRubber->show();
175   }
176 }
177
178 QMouseEvent* QtxMainWindow::Resizer::finalEvent() const
179 {
180   return myFinEvent;
181 }
182
183 void QtxMainWindow::Resizer::setFinalEvent( QMouseEvent* e )
184 {
185   myFinEvent = e;
186 }
187
188 /*!
189   \brief Event filter.
190
191   \param o recevier object
192   \param e event
193 */
194 bool QtxMainWindow::Resizer::eventFilter( QObject* o, QEvent* e )
195 {
196   if ( e->type() == QEvent::Timer ) {
197     if ( !finalEvent() )
198       return true;
199
200     setFilters( false );
201     QApplication::postEvent( myMain, finalEvent() );
202     myFinEvent = 0;
203     deleteLater();
204   }
205
206   return QObject::eventFilter( o, e );
207 }
208
209 void QtxMainWindow::Resizer::setFilters( bool on )
210 {
211   if ( myMain ) {
212     if ( on )
213       myMain->layout()->installEventFilter( this );
214     else
215       myMain->layout()->removeEventFilter( this );
216   }
217
218   QTimer* t = myMain->layout()->findChild<QTimer*>();
219   if ( t ) {
220     if ( on )
221       t->installEventFilter( this );
222     else
223       t->removeEventFilter( this );
224   }
225 }
226
227 /*!
228   \class QtxMainWindow
229   \brief Enhanced main window which supports dockable menubar and status bar
230          plus geometry saving/restoring.
231 */
232
233 /*!
234   \brief Constructor.
235   \param parent parent widget
236   \param f widget flags (Qt::WindowFlags)
237 */
238 QtxMainWindow::QtxMainWindow( QWidget* parent, Qt::WindowFlags f )
239 : QMainWindow( parent, f ),
240   myMenuBar( 0 ),
241   myStatusBar( 0 ),
242   myFullScreenAllowed(true),
243   myMinimizeAllowed(true),
244   myOpaque( true ),
245   myResizer( 0 ),
246   myMouseMove( 0 )
247 {
248   //rnv: Enables tooltips for inactive windows.
249   //rnv: For details see http://bugtracker.opencascade.com/show_bug.cgi?id=20893
250   setAttribute(Qt::WA_AlwaysShowToolTips);
251 }
252
253 /*!
254   \brief Destructor.
255 */
256 QtxMainWindow::~QtxMainWindow()
257 {
258   setDockableMenuBar( false );
259   setDockableStatusBar( false );
260 }
261
262 /*!
263   \brief Check if the menu bar is dockable.
264   \return \c true if dockable menu bar exists
265 */
266 bool QtxMainWindow::isDockableMenuBar() const
267 {
268   return myMenuBar != 0;
269 }
270
271 /*!
272   \brief Set menu bar dockable/undockable.
273   \param on if \c true, make menu bar dockable, otherwise 
274             make menu bar undockable
275 */
276 void QtxMainWindow::setDockableMenuBar( const bool on )
277 {
278   if ( isDockableMenuBar() == on )
279     return;
280
281   QMenuBar* mb = menuBar();
282   if ( !mb )
283     return;
284
285   if ( on && !myMenuBar )
286   {
287     myMenuBar = new QtxToolBar( true, this );
288     new Filter( mb, this, myMenuBar );
289     myMenuBar->setObjectName( "menu_bar_container" );
290     myMenuBar->setWindowTitle( tr( "Menu bar" ) );
291     myMenuBar->addWidget( mb );
292     myMenuBar->setAllowedAreas( Qt::TopToolBarArea | Qt::BottomToolBarArea );
293
294     addToolBarBreak( Qt::TopToolBarArea );
295     addToolBar( Qt::TopToolBarArea, myMenuBar );
296     addToolBarBreak( Qt::TopToolBarArea );
297
298     connect( myMenuBar, SIGNAL( destroyed( QObject* ) ), this, SLOT( onDestroyed( QObject* ) ) );
299   }
300   else if ( !on && myMenuBar )
301   {
302     setMenuBar( mb );
303     disconnect( myMenuBar, SIGNAL( destroyed( QObject* ) ), this, SLOT( onDestroyed( QObject* ) ) );
304     delete myMenuBar;
305     myMenuBar = 0;
306   }
307 }
308
309 /*!
310   \brief Check if the status bar is dockable.
311   \return \c true if dockable status bar exists
312 */
313 bool QtxMainWindow::isDockableStatusBar() const
314 {
315   return myStatusBar;
316 }
317
318 /*!
319   \brief Set status bar dockable/undockable.
320   \param on if \c true, make status bar dockable, otherwise 
321             make status bar undockable
322 */
323 void QtxMainWindow::setDockableStatusBar( const bool on )
324 {
325   if ( isDockableStatusBar() == on )
326     return;
327
328   QStatusBar* sb = statusBar();
329   if ( !sb )
330     return;
331
332   if ( on && !myStatusBar )
333   {
334     sb->setMinimumWidth( 250 );
335     sb->setSizeGripEnabled( false );
336     myStatusBar = new QtxToolBar( true, this );
337     new Filter( sb, this, myStatusBar );
338     myStatusBar->setObjectName( "status_bar_container" );
339     myStatusBar->setWindowTitle( tr( "Status bar" ) );
340     myStatusBar->addWidget( sb );
341     myStatusBar->setAllowedAreas( Qt::TopToolBarArea | Qt::BottomToolBarArea );
342
343     addToolBar( Qt::BottomToolBarArea, myStatusBar );
344
345     connect( myStatusBar, SIGNAL( destroyed( QObject* ) ), this, SLOT( onDestroyed( QObject* ) ) );
346   }
347   else if ( !on && myStatusBar )
348   {
349     setStatusBar( sb );
350     disconnect( myStatusBar, SIGNAL( destroyed( QObject* ) ), this, SLOT( onDestroyed( QObject* ) ) );
351     delete myStatusBar;
352     myStatusBar = 0;
353
354     sb->setSizeGripEnabled( true );
355   }
356 }
357
358 bool QtxMainWindow::isOpaqueResize() const
359 {
360   return myOpaque;
361 }
362
363 void QtxMainWindow::setOpaqueResize( bool on )
364 {
365   myOpaque = on;
366 }
367
368 /*!
369   \brief Dump main window geometry to the string.
370   \return string represenation of the window geometry
371 */
372 QString QtxMainWindow::storeGeometry() const
373 {
374   QRect frame = frameGeometry();
375   QRect screen = QApplication::desktop()->availableGeometry( this );
376
377   QString x;
378   if ( frame.left() == screen.left() )
379     x = QString( "+0" );
380   else if ( frame.right() == screen.right() )
381     x = QString( "-0" );
382   else
383     x = QString( "+%1" ).arg( frame.left() );
384
385   QString y;
386   if ( frame.top() == screen.top() )
387     y = QString( "+0" );
388   else if ( frame.bottom() == screen.bottom() )
389     y = QString( "-0" );
390   else
391     y = QString( "+%1" ).arg( frame.top() );
392
393   QString geom = QString( "%1x%2%3%4" ).arg( width() ).arg( height() ).arg( x ).arg( y );
394
395   QString state;
396   switch ( windowState() )
397   {
398   case Qt::WindowMaximized:
399     state = QString( "max" );
400     break;
401   case Qt::WindowMinimized:
402     if ( isMinimizeAllowed() ) state = QString( "min" );
403     break;
404   case Qt::WindowFullScreen:
405     state = isFullScreenAllowed() ? QString( "full" ) : QString( "max" );
406     break;
407   }
408
409   if ( !state.isEmpty() )
410     geom += QString( ":" ) + state;
411
412   return geom;
413 }
414
415 /*!
416   \brief Restore main window geometry from the string.
417   \param str string represenation of the window geometry
418 */
419 void QtxMainWindow::retrieveGeometry( const QString& str )
420 {
421   QString geom = str;
422   geom.remove( '\t' );
423   geom.remove( ' ' );
424
425   QRect rect = geometry();
426   QRect screen = QApplication::desktop()->availableGeometry( this );
427
428   QRegExp szRx( "(\\d+%?)\\s*x\\s*(\\d+%?)" );
429   if ( szRx.indexIn( geom ) != -1 )
430   {
431     int w = -1;
432     bool wp = false;
433     int ws = geometryValue( szRx.cap( 1 ).trimmed(), w, wp );
434     bool wOk = ws != 0;
435     if ( wOk && wp )
436       w = screen.width() * qMax( qMin( w, 100 ), 0 ) / 100;
437     wOk = wOk && w;
438
439     int h = -1;
440     bool hp = false;
441     int hs = geometryValue( szRx.cap( 2 ).trimmed(), h, hp );
442     bool hOk = hs != 0;
443     if ( hOk && hp )
444       h = screen.height() * qMax( qMin( h, 100 ), 0 ) / 100;
445     hOk = hOk && h;
446
447     if ( wOk && hOk )
448       rect.setSize( QSize( w, h ) );
449   }
450
451   QRegExp posRx( "([+|-]\\d+%?)\\s*([+|-]\\d+%?)" );
452   if ( posRx.indexIn( geom ) != -1 )
453   {
454     int x = -1;
455     bool xp = false;
456     int xs = geometryValue( posRx.cap( 1 ).trimmed(), x, xp );
457     bool xOk = xs != 0;
458     if ( xOk )
459     {
460       if ( xp )
461         x = screen.width() * qMax( qMin( x, 100 ), 0 ) / 100;
462       x = ( xs > 0 ? x : screen.right() - x - rect.width() ) + frameGeometry().x() - geometry().x();
463     }
464
465     int y = -1;
466     bool yp = false;
467     int ys = geometryValue( posRx.cap( 2 ).trimmed(), y, yp );
468     bool yOk = ys != 0;
469     if ( yOk )
470     {
471       if ( yp )
472         y = screen.height() * qMax( qMin( y, 100 ), 0 ) / 100;
473       y = ( ys > 0 ? y : screen.bottom() - y - rect.height() ) + frameGeometry().y() - geometry().y();
474     }
475
476     if ( xOk && yOk )
477       rect.moveTo( x, y );
478   }
479
480   Qt::WindowState state = Qt::WindowNoState;
481
482   QRegExp stRx( ":(\\w+)" );
483   if ( stRx.indexIn( geom ) != -1 )
484   {
485     QString stStr = stRx.cap( 1 ).trimmed().toLower();
486     if ( stStr.startsWith( QString( "max" ) ) ) {
487       state = Qt::WindowMaximized;
488     }
489     else if ( stStr.startsWith( QString( "min" ) ) ) {
490       if ( isMinimizeAllowed() )
491         state = Qt::WindowMinimized;
492     }
493     else if ( stStr.startsWith( QString( "full" ) ) ) {
494       state = isFullScreenAllowed() ? Qt::WindowFullScreen : Qt::WindowMaximized;
495     }
496   }
497
498   resize( rect.size() );
499   move( rect.topLeft() );
500
501   if ( state != Qt::WindowNoState )
502     setWindowState( state );
503 }
504
505 /*!
506   \brief Retrieve numerical value from the string.
507   
508   Numerical value in the string have the structure [+|-]\d*[%],
509   that is one or more digits which can start from "+" or "-" and
510   can end with "%" symbol.
511
512   \param str string being converted
513   \param num returning value (> 0)
514   \param percent if string ends with "%" this parameter is equal to \c true after
515          returning from the function
516   \return -1 if value < 0, 1 if value > 0 and 0 in case of error
517 */
518 int QtxMainWindow::geometryValue( const QString& str, int& num, bool& percent ) const
519 {
520   num = -1;
521   int res = 1;
522   QString numStr = str;
523   if ( numStr.startsWith( "+" ) || numStr.startsWith( "-" ) )
524   {
525     res = numStr.startsWith( "+" ) ? 1 : -1;
526     numStr = numStr.mid( 1 );
527   }
528
529   percent = numStr.endsWith( "%" );
530   if ( percent )
531     numStr = numStr.mid( 0, numStr.length() - 1 );
532
533   bool ok = false;
534   num = numStr.toInt( &ok );
535   if ( !ok )
536     res = 0;
537
538   return res;
539 }
540
541 /*!
542   \brief Called when child object (menu bar, status bar) is destroyed.
543   
544   Clears internal pointer to prevent crashes.
545
546   \param obj signal sender (object being destroyed)
547 */
548 void QtxMainWindow::onDestroyed( QObject* obj )
549 {
550   QObject* o = 0;
551   if ( obj == myMenuBar )
552   {
553     myMenuBar = 0;
554     o = menuBar();
555   }
556   else if ( obj == myStatusBar )
557   {
558     myStatusBar = 0;
559     o = statusBar();
560   }
561
562   if ( o )
563   {
564     QChildEvent ce( QEvent::ChildRemoved, o );
565     QApplication::sendEvent( this, &ce );
566   }
567 }
568
569 bool QtxMainWindow::event( QEvent* e )
570 {
571 //   if ( e->type() == QEvent::WindowDeactivate ) {
572 //     printf( "----------------> Deactivated\n" );
573 //   }
574
575   if ( myResizer ) {
576     QMouseEvent* me = static_cast<QMouseEvent*>( e );
577     if ( ( e->type() == QEvent::MouseButtonRelease && me->button() == Qt::LeftButton ) || 
578          ( e->type() == QEvent::MouseButtonPress && me->button() != Qt::LeftButton ) ) {
579       if ( me->button() == Qt::LeftButton ) {
580         if ( myMouseMove ) {
581           QMainWindow::event( myMouseMove );
582           delete myMouseMove;
583           myMouseMove = 0;
584         }
585
586         QMouseEvent* me = static_cast<QMouseEvent*>( e );
587         myResizer->setFinalEvent( new QMouseEvent( QEvent::MouseButtonRelease, me->pos(), me->globalPos(),
588                                                    Qt::LeftButton, me->buttons(), me->modifiers() ) );
589         myResizer = 0;
590         return true;
591       }
592     }
593   }
594
595   if ( myResizer && e->type() == QEvent::MouseMove ) {
596     QMouseEvent* me = static_cast<QMouseEvent*>( e );
597     if ( myMouseMove )
598       delete myMouseMove;
599     myMouseMove = new QMouseEvent( me->type(), me->pos(), me->globalPos(),
600                                    me->button(), me->buttons(), me->modifiers() );
601     myResizer->setPosition( me->globalPos() );
602   }
603
604   bool ok = QMainWindow::event( e );
605
606   if ( !myResizer && e->type() == QEvent::MouseButtonPress ) {
607     QMouseEvent* me = static_cast<QMouseEvent*>( e );
608     if ( !isOpaqueResize() && ok && testAttribute( Qt::WA_SetCursor ) && me->button() == Qt::LeftButton ) {
609       bool status = true;
610       Qt::Orientation o;
611       switch ( cursor().shape() )
612         {
613         case Qt::SplitHCursor:
614           o = Qt::Vertical;
615           break;
616         case Qt::SplitVCursor:
617           o = Qt::Horizontal;
618           break;
619         default:
620           status = false;
621           break;
622         }
623       if ( status ) {
624         //myResizer = new Resizer( me->globalPos(), o, this ); // doesn't work with QT 5.12
625         myMouseMove = new QMouseEvent( me->type(), me->pos(), me->globalPos(),
626                                        me->button(), me->buttons(), me->modifiers() );
627       }
628     }
629   }
630
631   return ok;
632 }
633
634 /*!
635   \brief FullScreenAllowed flag allowed dump in the main window geometry 
636          Qt::WindowFullScreen parameter.
637   \return \c FullScreenAllowed flag.
638 */
639 bool QtxMainWindow::isFullScreenAllowed() const {
640   return myFullScreenAllowed;
641 }
642
643
644 /*!
645   \brief Set FullScreenAllowed flag.
646          The default value is true.
647   \param f value of the FullScreenAllowed flag.
648 */
649 void QtxMainWindow::setFullScreenAllowed( const bool f ) {
650     myFullScreenAllowed = f;
651 }
652
653 /*!
654   \brief MinimizeAllowed flag allowed dump in the main window geometry 
655          Qt::WindowMinimized parameter.
656   \return \c MinimizeAllowed flag.
657 */
658 bool QtxMainWindow::isMinimizeAllowed() const {
659   return myMinimizeAllowed;
660 }
661
662
663 /*!
664   \brief Set MinimizeAllowed flag.
665          The default value is true.
666   \param f value of the MinimizeAllowed flag.
667 */
668 void QtxMainWindow::setMinimizeAllowed( const bool f ) {
669     myMinimizeAllowed = f;
670 }