My blog has been moved to ariya.ofilabs.com.

Friday, April 20, 2007

Custom toggle action for QDockWidget

Thanks to QDockWidget::toggleViewAction, if you want to create an action (and placed in in the menu, for example) to switch a dock widget on and off, it is very simple indeed. The text of the action will be the title of the dock. If you have more than dock widgets, this can be combined together in a sub menu "Show Docks", just as illustrated below:

When that menu item (and the corresponding action) is checked/unchecked by the user, magically the dock will show/disappear.

But what if you want to create your own toggle action? For example, you want to customize the text of the action to be a bit more descriptive, say "Show Function List". Or perhaps you just want the total control.

Well, you can create your own actions and get this result:

which can be realized by the following code:

  m_actions->showHistory = new QAction( tr("Show Expression &History"), this );
  m_actions->showFunctions = new QAction( tr("Show &Functions List"), this );
  m_actions->showHistory->setToggleAction( true );
  m_actions->showFunctions->setToggleAction( true );
  connect( m_actions->showHistory, SIGNAL( toggled(bool) ), 
    m_historyDock, SLOT( setVisible(bool) ) );
  connect( m_actions->showFunctions, SIGNAL( toggled(bool) ), 
    m_functionsDock, SLOT( setVisible(bool) ) );

So far so good. Everytime you toggle the menu item, the docks can disappear and reappear.

However, it's not completely foolproof. You can, infact, make the dock disappear by closing it manually, i.e. clicking on the X button on the dock title bar. But by doing that, the "checked" status of the action itself is not properly adjusted (i.e. it is still "checked" while the dock is long gone).

With Qt 4.3, the solution is easy: use visibilityChanged signal of QDockWidget:

  connect( m_historyDock, SIGNAL( visibilityChanged(bool) ), 
    m_actions->showHistory, SLOT( setChecked(bool) ) );
  connect( m_functionsDock, SIGNAL( visibilityChanged(bool) ), 
    m_actions->showFunctions, SLOT( setChecked(bool) ) );

If, for whatever reason, you're stucked with Qt 4.2, then you have to find another remedy since visibilityChanged does not exist there. There are many ways to do this.

A particularly complicated but looks-elegant solution is by hijacking trapping the show and hide event of the docks using eventFilter trick. So, hook the event filters during docks construction:

  m_historyDock->installEventFilter( this );
  m_functionsDock->installEventFilter( this );

and do something like this in the window's event filter:

bool MyWindow::eventFilter( QObject* object, QEvent* event )
{
  if( object == m_historyDock )
    if( event->type() == QEvent::Hide || event->type() == QEvent::Show )
      m_actions->showHistory->setChecked( event->type() == QEvent::Show );

  if( object == m_functionsDock )
    if( event->type() == QEvent::Hide || event->type() == QEvent::Show )
      m_actions->showFunctions->setChecked( event->type() == QEvent::Show );

  return false;
}

Looks good? Not really. Because for all this trouble, you can just do the same in these two lines of code (the simple and yet effective solution):

  connect( m_historyDock->toggleViewAction(), SIGNAL( toggled( bool ) ),
    m_actions->showHistory, SLOT( setChecked( bool ) ) );
  connect( m_functionsDock->toggleViewAction(), SIGNAL( toggled( bool ) ),
    m_actions->showFunctions, SLOT( setChecked( bool ) ) );

Any other solution, perhaps?