Search code examples
qtorientationtoolbarqpushbuttonqtoolbar

QToolbar in QMainWindow - signal toolbar about its position change


I would like to be able to detect any change in both the position and orientation of a toolbar which is part of a main window. The toolbar has a QPushButton called nodes_button and is part of a main window. I am using both the default QMainWindow and QToolBar. nodes_button contains a QMenu and the purpose of this whole exercise is to place the menu indicator of nodes_button in a way that visually fits the toolbar.

A default toolbar in a default main window has 5 locations where it can be places - top, bottom, left, right and floating. Based on the area where the toolbar is located it also changes its orientation to either horizontal (floating, top and bottom) or vertical (left or right).

Determining the orientation is not difficult since a toolbars actually provides QToolBar::orientation() (for retrieving the orientation) and QToolBar::orientationChanged(Qt::Orientation) (a signal emitted every time the orientation of the toolbar changes). Using the signal I can connect a slot like this:

connect(toolbar, SIGNAL(orientationChanged(Qt::Orientation)), this, SLOT(toolbarAdjust()));

Currently toolbarAdjust() is triggered only when the toolbar's orientation changes however I also want to use it when the location of the toolbar changes:

void MainWindow::toolbarAdjust()
{
  Qt::Orientation toolbarOrientation = toolbar->orientation();
  Qt::ToolBarArea toolbarPosition = this->toolBarArea(toolbar);

  if(toolbarOrientation == Qt::Horizontal) {
    if(toolbarPosition == Qt::NoToolBarArea || toolbarPosition == Qt::TopToolBarArea) {
      nodes->setStyleSheet("QPushButton#nodes_button::menu-indicator {image: url(:node_menu_top); subcontrol-position: bottom center; subcontrol-origin: padding; bottom: -7px}");
    }
    else if(toolbarPosition == Qt::BottomToolBarArea) {
      nodes->setStyleSheet("QPushButton#nodes_button::menu-indicator {image: url(:node_menu_bottom); subcontrol-position: top center; subcontrol-origin: padding; top: -7px}");
    }
  }
  else if (toolbarOrientation == Qt::Vertical) {
    if(toolbarPosition == Qt::LeftToolBarArea) {
      nodes->setStyleSheet("QPushButton#nodes_button::menu-indicator {image: url(:node_menu_right); subcontrol-position: left center; subcontrol-origin: padding; right: -6px;}");
    }
    else if(toolbarPosition == Qt::RightToolBarArea) {
      nodes->setStyleSheet("QPushButton#nodes_button::menu-indicator {image: url(:node_menu_left); subcontrol-position: right center; subcontrol-origin: padding; left: -6px;}");
    }
  }
}

This doesn't work all the time for many reasons for example because of the fact that for both left and right / top and bottom toolbar area the orientation of the toolbar is the same - vertical / horizontal.

So I need something like

connect(this, SIGNAL(toolbarLocationChanged()), this, SLOT(toolbarAdjust()));

Using the orientation and location of the toolbar I can place my menu indicator in a way that fits the current location of the toolbar. As you can see below the orientation in a combination with the corresponding variation of the stylesheet

QPushButton#nodes_button::menu-indicator {
  image: url(:node_menu_top);         // bottom, right, left
  subcontrol-position: bottom center; // top, right, left
  subcontrol-origin: padding;
  bottom: -7px                        // top, left, right
}

leads to

enter image description here enter image description here enter image description here enter image description here

I can actually omit the whole orientation part and do the following:

void MainWindow::toolbarAdjust()
{
  Qt::ToolBarArea toolbarPosition = this->toolBarArea(drawingToolBar);

  switch(toolbarPosition) {
    case Qt::NoToolBarArea:;
    case Qt::TopToolBarArea:
      nodes->setStyleSheet("QPushButton#nodes_button::menu-indicator {image: url(:node_menu_top); subcontrol-position: bottom center; subcontrol-origin: padding; bottom: -7px}");
      break;
    case Qt::BottomToolBarArea:
      nodes->setStyleSheet("QPushButton#nodes_button::menu-indicator {image: url(:node_menu_bottom); subcontrol-position: top center; subcontrol-origin: padding; top: -7px}");
      break;
    case Qt::LeftToolBarArea:
      nodes->setStyleSheet("QPushButton#nodes_button::menu-indicator {image: url(:node_menu_right); subcontrol-position: left center; subcontrol-origin: padding; right: -6px;}");
      break;
    case Qt::RightToolBarArea:
      nodes->setStyleSheet("QPushButton#nodes_button::menu-indicator {image: url(:node_menu_left); subcontrol-position: right center; subcontrol-origin: padding; left: -6px;}");
      break;
  }
}

because of the way a toolbar behaves when you dock it/float it.

EDIT: I found this post where a user tells the OP about QDockWidget::dockLocationChanged() and that maybe such a functionality has been added to QToolBar too. At the end the OP post an email which shows that such a feature has been requested. The post is from 2007...


Solution

  • I found that the QToolBar::topLevelChanged(bool) signal helps me solve the problem. Whenever the user wants to change the toolbar area of a toolbar he/she has to click on the toolbar, drag it to that area and then drop it. Based on the Qt documentation:

    This signal is emitted when the floating property changes. The topLevel parameter is true if the toolbar is now floating; otherwise it is false.

    So whenever the user is dragging the toolbar it is obviously also floating. Once the toolbar is docked it is not floating. Detecting this change in the floating property of the toolbar can be used to adjust its contents.

    Here is the result if anyone's interested:

    Top placement (indicator pointing downwards)

    enter image description here

    Bottom placement (indicator pointing upwards)

    enter image description here

    Left placement (indicator pointing to the right)

    enter image description here

    Right placement (indicator pointing to the left)

    enter image description here