Qt Commercial Support Weekly #16: Floating Widgets and How to Find their Way Back Home

 

Have you ever wondered how nice it would be to get the widgets on your application to float for a while and then return them back to their original position on the application? It is easy to achieve by reparenting the widgets!

 

Note: This blog is best viewed with Mozilla Firefox. Internet Explorer is giving errors on the code snippet.

I wrote a small example application which demonstrates the basic idea how to create floating widgets. The example has a QTabWidget with two very simple tabs (tab1 and tab2) – both of them contain only one QLabel widget (label1 and label2). There is nothing tricky on the constructor of Widget class which initializes the QTabWidget:

 

Widget::Widget(QWidget *parent) :

    QWidget(parent), m_tabWidget(0), m_currentWidget(0)

  , m_label_firstTab(0), m_label_secondTab(0)

{

    m_label_firstTab = new MyLabel ("Label 1: click me!");

    m_label_secondTab = new MyLabel ("Label 2: click me!");

    m_tabWidget = new QTabWidget;

    m_tabWidget->addTab(m_label_firstTab, "Tab 1");

    m_tabWidget->addTab(m_label_secondTab, "Tab 2");

As you noticed, there is a QLabel derived class MyLabel for the tab labels. There is a certain reason for that, which I will cover a bit later. But before that, let’s see how we can make the label of the first tab to float. When the user clicks the tab1 the widget label1 on the tab will be reparented:

void Widget::mousePressEvent ( QMouseEvent * event )

{

    // left mouse button pressed --> reparent the current widget

    if (event->button() == Qt::LeftButton) {

        m_currentWidget = m_tabWidget->currentWidget();

        if (m_currentWidget) {

            m_currentWidget->setParent(0);

            m_currentWidget->move(event->globalPos());

            m_currentWidget->show();

        }

    }

We can do the reparenting by using Qwidget::setParent(Qwidget *parent). The interesting thing is that the new parent is 0. What does this mean? It is the most important thing when creating the floating widget. When we set a widget's parent to be 0 it has no parent at all – which means that it has become a window. Hurray, we have a floating widget!

Reparenting makes the widget invisible – even if it has earlier been visible – so we need to remember to call QWidget::show() for the reparented widget. Now, the user can see two windows on the screen. The other one is the original application window which now includes QTabWidget with only one tab, tab2. Another window is the one we just created by reparenting the widget label1 on the tab1. Therefore, this new window includes one widget, label1.

Thge user might want to move the floating widget (a window!) back to its original position on the original application. This can also be done easily. If the original application has access to the floating widget, it can reparent the widget again so that it has a proper widget as a parent instead of a zero parent.

To achieve this on my QTabWidget example I implemented a QLabel derived class MyLabel for the labels (label1, label2) which were added to the QTabWidget's tabs (tab1, tab2).  The custom label class implements three event handling functions for controlling the mouse events:

void MyLabel::mousePressEvent ( QMouseEvent * event )

{

    QPoint xy = mapToGlobal(QPoint(0,0));

    // mouse press relative position

    relative_x = event->globalPos().x() - xy.x();

    relative_y = event->globalPos().y() - xy.y();

    QLabel::mousePressEvent(event);

}

void MyLabel::mouseMoveEvent ( QMouseEvent * event )

{

    // move the widget and cursor so that the cursor's relative position does not change

    move(QPoint(event->globalPos().x() - relative_x, event->globalPos().y() - relative_y));

    QCursor::setPos(event->globalPos());

}

void MyLabel::mouseReleaseEvent ( QMouseEvent * /*event*/ )

{

    emit mouseReleasedForTabWidget();

}

Implementation of the mousePressEvent(QMouseEvent *) and mouseMoveEvent(QMouseEvent *) functions enable the dragging of the floating widget with a mouse. When the user releases the mouse the custom label class emits a signal on mouseReleaseEvent(QMouseEvent*) function. This  signal has been connected to a slot on the original application:

void Widget::addNewTab()

{

    QPoint cursor = m_tabWidget->mapFromGlobal(QCursor::pos());

    if (m_tabWidget->rect().contains(cursor) ) {

        MyLabel *label = qobject_cast<MyLabel *>(sender());

        m_tabWidget->addTab(label, "New tab");

    }

}

The application checks at first if the user has released the mouse on top of the QTabWidget. If that is the case, the label is added as a new tab to the QTabWidget in the same way it was done on the constructor phase. I am utilizing QObject::sender() for getting access to the floating widget as QObject::sender() returns a pointer to the object that sent the signal, if called in a slot activated by a signal. And now the floating widget does not exist anymore – label1 has become again part of the original application!

So, it is actually quite simple to create floating widgets. Just reparent the widget so that its parent is 0 and remember to call show() for the widget to make it visible. To return the widget back to its original position is easy too. You just need to ensure that the application has access to the floating widget so that it can do the reparenting again when the floating widget is no longer needed. If you would like to test my example the code can be found here.

Have fun with Qt and please feel free to contact the Qt Commercial support team via the Qt Commercial support portal.


Comments