PDA

View Full Version : [SOLVED] Detachable QDockWidget tabs



Kryzon
22nd January 2015, 16:54
Here follows an example project of how to achieve detachable (as in, click-drag to tear off) QDockWidget tabs, something which is quite standard in commercial software but somehow not implemented in Qt.

Qt does not support detachable dock widget tabs nor does it expose much of its docking system for us to implement it. However, by using some [stable] tricks and some research into the Qt source code we can achieve this behaviour. The inspiration for this was Toon Boom, a commercial software used for animation that was built with Qt, and it has detachable dock tabs. Some more sophisticated docking systems can be found in Adobe software such as Photoshop and Flash.

This thread (Dragable-QDockWidget-tabs-with-no-title-bar (http://www.qtcentre.org/threads/54193-Dragable-QDockWidget-tabs-with-no-title-bar)) makes an important point: "Displaying the title in the tab and the title bar seems ugly and a waste of screen space."
The original Qt docking system looks like this, as seen in Krita, a famous open-source digital painting application:


http://s27.postimg.org/5th5j4iqr/krita_interface.png

You can see highlighted in red that you have the name of the panel not only on the tab but also on the dock widget title bar, wasting vertical space.

This example project hides the title bars of dock widgets when they are tabified to compensate for that. Then it allows the user to detach the dock widget by dragging the tab or by double-clicking it.
It also implements the concept of a 'workspace,' which is nothing more than different QMainWindow states. A suggestion is to use this workspace system to allow the user to save their own custom dock arrangements.

Notes:

- For this system to work it's necessary to synthesize OS-level mouse events. This is platform specific, and in the attached example you will find the implementation for Windows (using the SendInput function). There's still the need to implement and test for OS X and Linux (X11). The system is stable nonetheless.

- This system works with graphics tablets (as in, you can detach and drag docks with the stylus). I tested it with a Wacom Bamboo graphics tablet and as such it can be used in software that supports this kind of input device.

- This is my first major C++ usage. If you have any recommendations for improvements in the code for clarity etc. please share your thoughts here or send me a PM. I will appreciate it. The source code is heavily commented with the C++ \ Qt novice in mind. This system can probably be ported to Python with ease.

Here's a screenshot of how it looks on my system, Windows XP:


http://s13.postimg.org/fotirrohj/dock_Example.png

If you have the time, please share a screenshot of how it looks on your system. The tab close button graphic is native, but it can probably be styled with a custom pixmap using the QTabBar::close-button (http://doc.qt.io/qt-5/stylesheet-reference.html#close-button-sub) CSS subcontrol.

References (these helped a lot):

- http://qt-project.org/faq/answer/how_can_i_check_which_tab_is_the_current_one_in_a_ tabbed_qdockwidget
- http://www.qtcentre.org/threads/14282-how-to-hide-the-title-bar-when-a-QDockWidget-is-docked
- http://www.qtcentre.org/wiki/index.php?title=Movable_Tabs
- http://blog.qt.digia.com/blog/2009/03/20/moving-top-level-window-by-dragging/

NIteLordz
22nd January 2015, 17:25
This is exciting !

however, compiling the project throws the following error

SyntheticMouseEvent.obj:-1: error: LNK2019: unresolved external symbol __imp__SendInput@12 referenced in function "void __cdecl sendOSMouseEvent(enum SynthMouseEvent,int,int)" (?sendOSMouseEvent@@YAXW4SynthMouseEvent@@HH@Z)

Kryzon
22nd January 2015, 18:09
Hello.
Just for reference, I compiled that project with the MinGW kit.

That LNK2019 error seems to be from Visual Studio (reference (https://msdn.microsoft.com/en-us/library/799kze2z(v=vs.80).aspx)).
The fix seems to be to add "LIBS += -luser32" to your PRO file, as per this thread here: http://qt-project.org/forums/viewthread/16794

(The PRO file is the Qt project file that you open with Qt Creator and can manually edit the project settings.)

NIteLordz
22nd January 2015, 21:29
Add that to the pro file worked. Time to integrate this into my system and remove those nasty title bars.

Well done !

Alundra
23rd January 2015, 03:38
Great works there, I saw the cross platform code is not finished and the code can be cleaned but apparently that works without problem.

wysota
23rd January 2015, 07:28
Here follows an example project of how to achieve detachable (as in, click-drag to tear off) QDockWidget tabs, something which is quite standard in commercial software but somehow not implemented in Qt.

Qt supports detachable dock tabs since 4.0 (which was released like 9 years ago). They are used by e.g. Qt Designer.

Alundra
23rd January 2015, 13:41
Can you explain how? I always saw only dock with titlebar possible, not like the example shown here.

wysota
23rd January 2015, 14:33
Can you explain how? I always saw only dock with titlebar possible, not like the example shown here.

Just launch Qt Designer and start dragging the tabs around.

In code I think this is controlled via QMainWindow::dockOptions property (and friends). MainWindow::AllowTabbedDocks is the flag that should be set to enable tabbing.

Kryzon
23rd January 2015, 16:02
From what I experimented, in Qt Designer -- and as is offered by default with Qt -- you can detach tabified dock widgets by click-dragging their title bar. Clicking the tabs themselves only switches the currently visible dock widget, there's no further interaction.

What the example project above does is filter the events sent to the tab bars used by the internal docking system to enable the user to detach them by click-dragging the tabs or double-clicking them. It also "hides" the title bars when the dock widgets are tabified to save vertical space from the redundant information, as can be seen below (the left being the default behaviour):


http://s23.postimg.org/4mm009mdn/dock_Example2.png http://s9.postimg.org/dlg5pyh4f/dock_Example1.png


Great works there, I saw the cross platform code is not finished and the code can be cleaned but apparently that works without problem.
I'm glad that it works. If you can, please send me a private message (http://www.qtcentre.org/private.php?do=newpm&u=64632) with your suggestions on cleaning the code.

Alundra
23rd January 2015, 17:29
Just launch Qt Designer and start dragging the tabs around.
In code I think this is controlled via QMainWindow::dockOptions property (and friends). MainWindow::AllowTabbedDocks is the flag that should be set to enable tabbing.
I tried, the title bar is automatically removed by default but then you must reshow it to undock and you don't have the cross on the tab bar, that looks to be basic behavior of Qt, just the automatic hiding added. The example shown in this topic add the cross and allows undock when click on the tab name and automatic hide the titlebar.
Stuff can be changed :
- less empty line in code.
- empty code in destructor is not needed (DetachableDock::~DetachableDock()).
- friend is not necessary to be used in DetachableDock, a lot of professionnal dislike to use friend, I'm in the group.
- why pointer for tabBarList of DetachableDockManager ?
- That's better to write DetachableDock generic and inherit it for the sample.
- I would prefer include the code of SyntheticMouseEvent inside global namespace of DockManager since that's the only place used.

wysota
23rd January 2015, 22:10
From what I experimented, in Qt Designer -- and as is offered by default with Qt -- you can detach tabified dock widgets by click-dragging their title bar. Clicking the tabs themselves only switches the currently visible dock widget, there's no further interaction.

What the example project above does is filter the events sent to the tab bars used by the internal docking system to enable the user to detach them by click-dragging the tabs or double-clicking them. It also "hides" the title bars when the dock widgets are tabified to save vertical space from the redundant information, as can be seen below (the left being the default behaviour):



Great. Just don't claim Qt doesn't offer or neglects the need for detachable dock widgets as it is not true.

shinichy
26th January 2015, 02:23
It works great on Windows!
I tried to run it on Mac by implementing sendOSMouseEvent for Mac but it didn't work (it crashed).
Any idea?

My env is Mac 10.9.5 + Qt 5.4.0.

Kryzon
26th January 2015, 14:26
I tried to run it on Mac by implementing sendOSMouseEvent for Mac but it didn't work (it crashed).
Any idea?
Thank you for your interest.
I don't have a Mac system for testing, so I can't help much. The first thing that I would do is run it in QtCreator under Debug mode so that I could identify the exact call that is causing the crash. This is the best information that we can have on the problem.

I've looked at your implementation of sendOSMouseEvent and it seems absolutely fine.
According to the documentation, the CGPoint event location is in global coordinates and we've been using relative so far, so I would add an #ifdef in the event filter function to use global coordinates. The following is in the eventFilter function, right when detaching the dock.


if ( ( mouseEvent->pos() - clickPos ).manhattanLength() > QApplication::startDragDistance() )
{
if ( pressedTabIndex != -1 )
{
consumed = true;

/* In this release event I don't think the coordinates matter since the tab bar has
mouse focus, but experimentation is required. It might need an ifdef just like below. */
sendOSMouseEvent( SynthMouseEvent::MouseRelease, 0, 0 );

DetachableDock* detachingDock = dockFromTab( pressedTabBar, pressedTabIndex );
detachingDock->setFloating( true );

QPoint cursorPos = QCursor::pos();
detachingDock->centerTitle( cursorPos );

QApplication::processEvents();
QCursor::setPos( cursorPos );

#ifdef Q_OS_WIN
// Under Windows we use relative coordinates.
sendOSMouseEvent( SynthMouseEvent::MousePress, 0, 0 );
#endif

#ifdef Q_OS_OSX
// Under OSX we use global coordinates.
sendOSMouseEvent( SynthMouseEvent::MousePress, cursorPos.x(), cursorPos.y() );
#endif
}
}
I don't think the coordinates matter on the release event because at that moment the tab bar will be under focus, but the press event does need accurate coordinates or else we might be clicking something other than the dock widget and it won't start its dragging mode.

shinichy
31st January 2015, 12:27
I tried global coordinates for only MousePress and both MouseRelease and MousePress, but both cases didn't work.
I got this warning message and stacktrace (it's too long to post here, so I attached the full stack trace).



QNSView mouseDragged: Internal mouse button tracking invalid (missing Qt::LeftButton)
QNSView mouseDragged: Internal mouse button tracking invalid (missing Qt::LeftButton)
QNSView mouseDragged: Internal mouse button tracking invalid (missing Qt::LeftButton)
The program has unexpectedly finished.




Exception Type: EXC_BAD_ACCESS (SIGSEGV)
Exception Codes: KERN_INVALID_ADDRESS at 0x0000000000000008

Thread 0 Crashed:: Dispatch queue: com.apple.main-thread
0 org.qt-project.QtWidgets 0x000000010bb40f47 QDockWidget::setFloating(bool) + 23
1 com.example.DetachDockExample 0x000000010b9dd624 DetachableDockManager::eventFilter(QObject*, QEvent*) + 484 (DetachableDockManager.cpp:418)
2 org.qt-project.QtCore 0x000000010c8a72f9 QCoreApplicationPrivate::sendThroughObjectEventFil ters(QObject*, QEvent*) + 217
3 org.qt-project.QtWidgets 0x000000010ba2adab QApplicationPrivate::notify_helper(QObject*, QEvent*) + 235
4 org.qt-project.QtWidgets 0x000000010ba2e65f QApplication::notify(QObject*, QEvent*) + 9551
5 org.qt-project.QtCore 0x000000010c8a6fb3 QCoreApplication::notifyInternal(QObject*, QEvent*) + 115
6 org.qt-project.QtWidgets 0x000000010ba2b73b QApplicationPrivate::sendMouseEvent(QWidget*, QMouseEvent*, QWidget*, QWidget*, QWidget**, QPointer<QWidget>&, bool) + 987
7 org.qt-project.QtWidgets 0x000000010ba85161 QWidgetWindow::handleMouseEvent(QMouseEvent*) + 1217
8 org.qt-project.QtWidgets 0x000000010ba8439f QWidgetWindow::event(QEvent*) + 111
9 org.qt-project.QtWidgets 0x000000010ba2adbb QApplicationPrivate::notify_helper(QObject*, QEvent*) + 251
10 org.qt-project.QtWidgets 0x000000010ba2e110 QApplication::notify(QObject*, QEvent*) + 8192
11 org.qt-project.QtCore 0x000000010c8a6fb3 QCoreApplication::notifyInternal(QObject*, QEvent*) + 115
12 org.qt-project.QtGui 0x000000010c0e2d3d QGuiApplicationPrivate::processMouseEvent(QWindowS ystemInterfacePrivate::MouseEvent*) + 2189
13 org.qt-project.QtGui 0x000000010c0e2671 QGuiApplicationPrivate::processMouseEvent(QWindowS ystemInterfacePrivate::MouseEvent*) + 449
14 org.qt-project.QtGui 0x000000010c0e1bd3 QGuiApplicationPrivate::processWindowSystemEvent(Q WindowSystemInterfacePrivate::WindowSystemEvent*) + 131
15 org.qt-project.QtGui 0x000000010c0cf51a QWindowSystemInterface::sendWindowSystemEvents(QFl ags<QEventLoop::ProcessEventsFlag>) + 346
16 libqcocoa.dylib 0x000000010ecd36ff QCocoaEventDispatcher::processEvents(QFlags<QEventLoop::ProcessEventsFlag>) + 1631
17 com.example.DetachDockExample 0x000000010b9dd64f DetachableDockManager::eventFilter(QObject*, QEvent*) + 527 (DetachableDockManager.cpp:422)
18 org.qt-project.QtCore 0x000000010c8a72f9 QCoreApplicationPrivate::sendThroughObjectEventFil ters(QObject*, QEvent*) + 217
19 org.qt-project.QtWidgets 0x000000010ba2adab QApplicationPrivate::notify_helper(QObject*, QEvent*) + 235
20 org.qt-project.QtWidgets 0x000000010ba2e65f QApplication::notify(QObject*, QEvent*) + 9551
21 org.qt-project.QtCore 0x000000010c8a6fb3 QCoreApplication::notifyInternal(QObject*, QEvent*) + 115
22 org.qt-project.QtWidgets 0x000000010ba2b73b QApplicationPrivate::sendMouseEvent(QWidget*, QMouseEvent*, QWidget*, QWidget*, QWidget**, QPointer<QWidget>&, bool) + 987
23 org.qt-project.QtWidgets 0x000000010ba85161 QWidgetWindow::handleMouseEvent(QMouseEvent*) + 1217
24 org.qt-project.QtWidgets 0x000000010ba8439f QWidgetWindow::event(QEvent*) + 111
25 org.qt-project.QtWidgets 0x000000010ba2adbb QApplicationPrivate::notify_helper(QObject*, QEvent*) + 251
26 org.qt-project.QtWidgets 0x000000010ba2e110 QApplication::notify(QObject*, QEvent*) + 8192
27 org.qt-project.QtCore 0x000000010c8a6fb3 QCoreApplication::notifyInternal(QObject*, QEvent*) + 115
28 org.qt-project.QtGui 0x000000010c0e2d3d QGuiApplicationPrivate::processMouseEvent(QWindowS ystemInterfacePrivate::MouseEvent*) + 2189
29 org.qt-project.QtGui 0x000000010c0e2671 QGuiApplicationPrivate::processMouseEvent(QWindowS ystemInterfacePrivate::MouseEvent*) + 449
30 org.qt-project.QtGui 0x000000010c0e1bd3 QGuiApplicationPrivate::processWindowSystemEvent(Q WindowSystemInterfacePrivate::WindowSystemEvent*) + 131
31 org.qt-project.QtGui 0x000000010c0cf51a QWindowSystemInterface::sendWindowSystemEvents(QFl ags<QEventLoop::ProcessEventsFlag>) + 346
32 org.qt-project.QtGui 0x000000010c0cd611 QWindowSystemInterface::flushWindowSystemEvents(QF lags<QEventLoop::ProcessEventsFlag>) + 673
33 libqcocoa.dylib 0x000000010ecc9341 -[QNSView updateGeometry] + 545
34 com.apple.CoreFoundation 0x00007fff8c346e0c __CFNOTIFICATIONCENTER_IS_CALLING_OUT_TO_AN_OBSERV ER__ + 12
35 com.apple.CoreFoundation 0x00007fff8c23a82d _CFXNotificationPost + 2893
36 com.apple.Foundation 0x00007fff91d15e4a -[NSNotificationCenter postNotificationName:object:userInfo:] + 68
37 com.apple.AppKit 0x00007fff8b0f6f81 -[NSWindow _setFrameCommon:display:stashSize:] + 1958
38 com.apple.AppKit 0x00007fff8b276492 __25-[NSWindow setStyleMask:]_block_invoke + 878
39 com.apple.AppKit 0x00007fff8b1001e8 NSPerformWithScreenUpdatesDisabled + 65
40 com.apple.AppKit 0x00007fff8b2760e5 -[NSWindow setStyleMask:] + 172
41 libqcocoa.dylib 0x000000010ecc4350 QCocoaWindow::setWindowFlags(QFlags<Qt::WindowType>) + 96
42 org.qt-project.QtGui 0x000000010c0ecfc3 QWindow::setFlags(QFlags<Qt::WindowType>) + 35
43 org.qt-project.QtWidgets 0x000000010ba582f9 QWidgetPrivate::create_sys(unsigned long long, bool, bool) + 649
44 org.qt-project.QtWidgets 0x000000010ba57119 QWidget::create(unsigned long long, bool, bool) + 425
45 org.qt-project.QtWidgets 0x000000010ba6cf14 QWidgetPrivate::setParent_sys(QWidget*, QFlags<Qt::WindowType>) + 1668
46 org.qt-project.QtWidgets 0x000000010ba576f8 QWidget::setParent(QWidget*, QFlags<Qt::WindowType>) + 952
47 org.qt-project.QtWidgets 0x000000010ba6c7be QWidgetPrivate::setWindowFlags(QFlags<Qt::WindowType>) + 158
48 org.qt-project.QtWidgets 0x000000010bb42263 QDockWidgetPrivate::setWindowState(bool, bool, QRect const&) + 307
49 org.qt-project.QtWidgets 0x000000010bb433b3 QDockWidget::setTitleBarWidget(QWidget*) + 115
50 com.example.DetachDockExample 0x000000010b9dc3dc DetachableDock::changeTitleBar(bool) + 60 (DetachableDock.cpp:44)
51 com.example.DetachDockExample 0x000000010b9dd08b DetachableDockManager::topLevelUpdateDock(Detachab leDock*, DetachableDockManager::TopLevelChange) + 123 (DetachableDockManager.cpp:311)
52 com.example.DetachDockExample 0x000000010b9dcfb6 DetachableDockManager::onDockTopLevelChanged(bool) + 1302 (DetachableDockManager.cpp:179)
53 com.example.DetachDockExample 0x000000010b9e0992 DetachableDockManager::qt_static_metacall(QObject* , QMetaObject::Call, int, void**) + 114 (moc_DetachableDockManager.cpp:83)
54 org.qt-project.QtCore 0x000000010c8d99db QMetaObject::activate(QObject*, int, int, void**) + 2987
55 org.qt-project.QtWidgets 0x000000010bb422e8 QDockWidgetPrivate::setWindowState(bool, bool, QRect const&) + 440
56 org.qt-project.QtWidgets 0x000000010bb41001 QDockWidget::setFloating(bool) + 209
57 com.example.DetachDockExample 0x000000010b9dd624 DetachableDockManager::eventFilter(QObject*, QEvent*) + 484 (DetachableDockManager.cpp:418)
58 org.qt-project.QtCore 0x000000010c8a72f9 QCoreApplicationPrivate::sendThroughObjectEventFil ters(QObject*, QEvent*) + 217
59 org.qt-project.QtWidgets 0x000000010ba2adab QApplicationPrivate::notify_helper(QObject*, QEvent*) + 235
60 org.qt-project.QtWidgets 0x000000010ba2e65f QApplication::notify(QObject*, QEvent*) + 9551
61 org.qt-project.QtCore 0x000000010c8a6fb3 QCoreApplication::notifyInternal(QObject*, QEvent*) + 115
62 org.qt-project.QtWidgets 0x000000010ba2b73b QApplicationPrivate::sendMouseEvent(QWidget*, QMouseEvent*, QWidget*, QWidget*, QWidget**, QPointer<QWidget>&, bool) + 987
63 org.qt-project.QtWidgets 0x000000010ba85161 QWidgetWindow::handleMouseEvent(QMouseEvent*) + 1217
64 org.qt-project.QtWidgets 0x000000010ba8439f QWidgetWindow::event(QEvent*) + 111
65 org.qt-project.QtWidgets 0x000000010ba2adbb QApplicationPrivate::notify_helper(QObject*, QEvent*) + 251
66 org.qt-project.QtWidgets 0x000000010ba2e110 QApplication::notify(QObject*, QEvent*) + 8192
67 org.qt-project.QtCore 0x000000010c8a6fb3 QCoreApplication::notifyInternal(QObject*, QEvent*) + 115
68 org.qt-project.QtGui 0x000000010c0e2d3d QGuiApplicationPrivate::processMouseEvent(QWindowS ystemInterfacePrivate::MouseEvent*) + 2189
69 org.qt-project.QtGui 0x000000010c0e2671 QGuiApplicationPrivate::processMouseEvent(QWindowS ystemInterfacePrivate::MouseEvent*) + 449

... continued (Please check the attached text to see the full stack trace)

Kryzon
31st January 2015, 17:58
Do you know what code line caused the crash?
I would keep the global coordinates, since it's the correct way. I don't think they're influencing the warning or the crash.

I'm not sure how to fix this on OS X since I don't have a system to test it on. It must have to do with the order or the events.
The warning is created here:
https://qt.gitorious.org/qt/qtbase/source/73a1e8c60d894701f34806cc4b847aa2814bf389:src/plugins/platforms/cocoa/qnsview.mm#L765

It probably has to do with us sending a synthetic mouse release event while the button is down, even if it's just for a quick moment, and then moving the cursor with setPos(), which causes a mouse drag event with an invalid button.

I'll try to think of a different event order. If I'm not mistaken the crash happens in that block that detaches the dock, inside the event filter?

shinichy
31st January 2015, 23:32
The crash happens below line. I can't step into further because I can't attach Qt source code in my Qt Creator.



detachingDock->setFloating( true );


I added debug messages around that line, then I got the following output.



qDebug("before setFloating");
detachingDock->setFloating( true );
qDebug("after setFloating");




Starting /Users/shinichi/Code/build-DetachDockExample-Desktop_Qt_5_4_0_clang_64bit-Debug/DetachDockExample.app/Contents/MacOS/DetachDockExample...
before setFloating
after setFloating
QNSView mouseDragged: Internal mouse button tracking invalid (missing Qt::LeftButton)
QNSView mouseDragged: Internal mouse button tracking invalid (missing Qt::LeftButton)
before setFloating
before setFloating
after setFloating
QNSView mouseDragged: Internal mouse button tracking invalid (missing Qt::LeftButton)
before setFloating
before setFloating
after setFloating
QNSView mouseDragged: Internal mouse button tracking invalid (missing Qt::LeftButton)
before setFloating
The program has unexpectedly finished.

Kryzon
1st February 2015, 00:42
Thank you for the information.
I've changed the event order when the dock is detached. It still works under Windows XP, so hopefully it works now on OS X.

I noticed a title bar bug when undocking on a certain occasion, but I've been unable to reproduce it. So there may be improvements left to be made to this system.

The new event order in DetachableDockManager::eventFilter is as below:


if ( ( mouseEvent->pos() - clickPos ).manhattanLength() > QApplication::startDragDistance() )
{
// The user wants to detach a dock tab.

if ( pressedTabIndex != -1 )
{
consumed = true;

// *** Trying a different event order to avoid a crash on OS X.

QApplication::processEvents(); // Process all pending events to start with a clean queue.

/* It is essential to release the mouse button that is currently holding on to the tab bar
so that the dock widget can be dragged. This needs to be done with an OS level event. */

#ifdef Q_OS_WIN

// On Windows we use relative mouse coordinates.

sendOSMouseEvent( SynthMouseEvent::MouseRelease, 0, 0 );

#endif

#ifdef Q_OS_OSX

// On OS X we use absolute mouse coordinates.

QPoint cursorPos = QCursor::pos();
sendOSMouseEvent( SynthMouseEvent::MouseRelease, cursorPos.x(), cursorPos.y() );

#endif

/* Detach the dock that the user wants.
This restores the default title bar from the top level change, and also
restores the title bar of its tab bar neighbour if they were in a two-page tab bar. */

DetachableDock* detachingDock = dockFromTab( pressedTabBar, pressedTabIndex );
detachingDock->setFloating( true );

// Process events right after floating the dock to avoid an animation glitch.

QApplication::processEvents();

// Centre the dock title bar on the latest cursor position so the mouse press doesn't miss it.

detachingDock->centreTitle( QCursor::pos() );

// OPTIONAL. Process events again after centering the dock so it's redrawn immediately.

QApplication::processEvents();

// Synthesize a mouse press to grab the dock that is placed on the cursor, so it can be dragged.

#ifdef Q_OS_WIN
sendOSMouseEvent( SynthMouseEvent::MousePress, 0, 0 );
#endif

#ifdef Q_OS_OSX
sendOSMouseEvent( SynthMouseEvent::MousePress, cursorPos.x(), cursorPos.y() );
#endif
QApplication::processEvents(); // Make sure that these events are processed right now to avoid surprises.
}
}
If it still doesn't work, try adding a 'QApplication::processEvents()' call right before the 'detachingDock->setFloating( true )' call.

You can also download the full project with these modifications, as attached.

shinichy
1st February 2015, 01:07
I tried your new project but it still doesn't work. Even with 'QApplication::processEvents()' call right before the 'detachingDock->setFloating( true )' call.
Here's the output and the stack trace. Both seem same as before.



QNSView mouseDragged: Internal mouse button tracking invalid (missing Qt::LeftButton)
QNSView mouseDragged: Internal mouse button tracking invalid (missing Qt::LeftButton)
QNSView mouseDragged: Internal mouse button tracking invalid (missing Qt::LeftButton)
QNSView mouseDragged: Internal mouse button tracking invalid (missing Qt::LeftButton)
QNSView mouseDragged: Internal mouse button tracking invalid (missing Qt::LeftButton)
QNSView mouseDragged: Internal mouse button tracking invalid (missing Qt::LeftButton)
The program has unexpectedly finished.




Thread 0 Crashed:: Dispatch queue: com.apple.main-thread
0 org.qt-project.QtWidgets 0x000000010e950f47 QDockWidget::setFloating(bool) + 23
1 com.example.DetachDockExample 0x000000010e7f16e0 DetachableDockManager::eventFilter(QObject*, QEvent*) + 528
2 org.qt-project.QtCore 0x000000010f6b62f9 QCoreApplicationPrivate::sendThroughObjectEventFil ters(QObject*, QEvent*) + 217
3 org.qt-project.QtWidgets 0x000000010e83adab QApplicationPrivate::notify_helper(QObject*, QEvent*) + 235
4 org.qt-project.QtWidgets 0x000000010e83e65f QApplication::notify(QObject*, QEvent*) + 9551
5 org.qt-project.QtCore 0x000000010f6b5fb3 QCoreApplication::notifyInternal(QObject*, QEvent*) + 115
6 org.qt-project.QtWidgets 0x000000010e83b73b QApplicationPrivate::sendMouseEvent(QWidget*, QMouseEvent*, QWidget*, QWidget*, QWidget**, QPointer<QWidget>&, bool) + 987
7 org.qt-project.QtWidgets 0x000000010e895161 QWidgetWindow::handleMouseEvent(QMouseEvent*) + 1217
8 org.qt-project.QtWidgets 0x000000010e89439f QWidgetWindow::event(QEvent*) + 111
9 org.qt-project.QtWidgets 0x000000010e83adbb QApplicationPrivate::notify_helper(QObject*, QEvent*) + 251
10 org.qt-project.QtWidgets 0x000000010e83e110 QApplication::notify(QObject*, QEvent*) + 8192
11 org.qt-project.QtCore 0x000000010f6b5fb3 QCoreApplication::notifyInternal(QObject*, QEvent*) + 115
12 org.qt-project.QtGui 0x000000010eeeed3d QGuiApplicationPrivate::processMouseEvent(QWindowS ystemInterfacePrivate::MouseEvent*) + 2189
13 org.qt-project.QtGui 0x000000010eeee671 QGuiApplicationPrivate::processMouseEvent(QWindowS ystemInterfacePrivate::MouseEvent*) + 449
14 org.qt-project.QtGui 0x000000010eeedbd3 QGuiApplicationPrivate::processWindowSystemEvent(Q WindowSystemInterfacePrivate::WindowSystemEvent*) + 131
15 org.qt-project.QtGui 0x000000010eedb51a QWindowSystemInterface::sendWindowSystemEvents(QFl ags<QEventLoop::ProcessEventsFlag>) + 346
16 libqcocoa.dylib 0x0000000111adb6ff QCocoaEventDispatcher::processEvents(QFlags<QEventLoop::ProcessEventsFlag>) + 1631
17 com.example.DetachDockExample 0x000000010e7f166c DetachableDockManager::eventFilter(QObject*, QEvent*) + 412
18 org.qt-project.QtCore 0x000000010f6b62f9 QCoreApplicationPrivate::sendThroughObjectEventFil ters(QObject*, QEvent*) + 217
19 org.qt-project.QtWidgets 0x000000010e83adab QApplicationPrivate::notify_helper(QObject*, QEvent*) + 235
20 org.qt-project.QtWidgets 0x000000010e83e65f QApplication::notify(QObject*, QEvent*) + 9551
21 org.qt-project.QtCore 0x000000010f6b5fb3 QCoreApplication::notifyInternal(QObject*, QEvent*) + 115
22 org.qt-project.QtWidgets 0x000000010e83b73b QApplicationPrivate::sendMouseEvent(QWidget*, QMouseEvent*, QWidget*, QWidget*, QWidget**, QPointer<QWidget>&, bool) + 987
23 org.qt-project.QtWidgets 0x000000010e895161 QWidgetWindow::handleMouseEvent(QMouseEvent*) + 1217
24 org.qt-project.QtWidgets 0x000000010e89439f QWidgetWindow::event(QEvent*) + 111
25 org.qt-project.QtWidgets 0x000000010e83adbb QApplicationPrivate::notify_helper(QObject*, QEvent*) + 251
26 org.qt-project.QtWidgets 0x000000010e83e110 QApplication::notify(QObject*, QEvent*) + 8192
27 org.qt-project.QtCore 0x000000010f6b5fb3 QCoreApplication::notifyInternal(QObject*, QEvent*) + 115
28 org.qt-project.QtGui 0x000000010eeeed3d QGuiApplicationPrivate::processMouseEvent(QWindowS ystemInterfacePrivate::MouseEvent*) + 2189
29 org.qt-project.QtGui 0x000000010eeee671 QGuiApplicationPrivate::processMouseEvent(QWindowS ystemInterfacePrivate::MouseEvent*) + 449
30 org.qt-project.QtGui 0x000000010eeedbd3 QGuiApplicationPrivate::processWindowSystemEvent(Q WindowSystemInterfacePrivate::WindowSystemEvent*) + 131
31 org.qt-project.QtGui 0x000000010eedb51a QWindowSystemInterface::sendWindowSystemEvents(QFl ags<QEventLoop::ProcessEventsFlag>) + 346
32 libqcocoa.dylib 0x0000000111adb6ff QCocoaEventDispatcher::processEvents(QFlags<QEventLoop::ProcessEventsFlag>) + 1631
33 com.example.DetachDockExample 0x000000010e7f16fc DetachableDockManager::eventFilter(QObject*, QEvent*) + 556
34 org.qt-project.QtCore 0x000000010f6b62f9 QCoreApplicationPrivate::sendThroughObjectEventFil ters(QObject*, QEvent*) + 217
35 org.qt-project.QtWidgets 0x000000010e83adab QApplicationPrivate::notify_helper(QObject*, QEvent*) + 235
36 org.qt-project.QtWidgets 0x000000010e83e65f QApplication::notify(QObject*, QEvent*) + 9551
37 org.qt-project.QtCore 0x000000010f6b5fb3 QCoreApplication::notifyInternal(QObject*, QEvent*) + 115
38 org.qt-project.QtWidgets 0x000000010e83b73b QApplicationPrivate::sendMouseEvent(QWidget*, QMouseEvent*, QWidget*, QWidget*, QWidget**, QPointer<QWidget>&, bool) + 987
39 org.qt-project.QtWidgets 0x000000010e895161 QWidgetWindow::handleMouseEvent(QMouseEvent*) + 1217
40 org.qt-project.QtWidgets 0x000000010e89439f QWidgetWindow::event(QEvent*) + 111
41 org.qt-project.QtWidgets 0x000000010e83adbb QApplicationPrivate::notify_helper(QObject*, QEvent*) + 251
42 org.qt-project.QtWidgets 0x000000010e83e110 QApplication::notify(QObject*, QEvent*) + 8192
43 org.qt-project.QtCore 0x000000010f6b5fb3 QCoreApplication::notifyInternal(QObject*, QEvent*) + 115
44 org.qt-project.QtGui 0x000000010eeeed3d QGuiApplicationPrivate::processMouseEvent(QWindowS ystemInterfacePrivate::MouseEvent*) + 2189
45 org.qt-project.QtGui 0x000000010eeee671 QGuiApplicationPrivate::processMouseEvent(QWindowS ystemInterfacePrivate::MouseEvent*) + 449
46 org.qt-project.QtGui 0x000000010eeedbd3 QGuiApplicationPrivate::processWindowSystemEvent(Q WindowSystemInterfacePrivate::WindowSystemEvent*) + 131
47 org.qt-project.QtGui 0x000000010eedb51a QWindowSystemInterface::sendWindowSystemEvents(QFl ags<QEventLoop::ProcessEventsFlag>) + 346
48 libqcocoa.dylib 0x0000000111adb6ff QCocoaEventDispatcher::processEvents(QFlags<QEventLoop::ProcessEventsFlag>) + 1631
49 com.example.DetachDockExample 0x000000010e7f166c DetachableDockManager::eventFilter(QObject*, QEvent*) + 412
50 org.qt-project.QtCore 0x000000010f6b62f9 QCoreApplicationPrivate::sendThroughObjectEventFil ters(QObject*, QEvent*) + 217
...

Kryzon
1st February 2015, 23:50
From my observations on Windows, floating the dock only crashes the program if the tab bar still has the mouse focus when this happens. That is, the mouse release event for it isn't being recognised properly for some reason.

I'll have to rely on someone's OS X expertise on this one, it's difficult to remotely debug.
If you happen to find a solution by experimenting, please share it.

Regards.

Kryzon
3rd February 2015, 00:17
Hello. Here's another try.
I noticed that a synthesised mouse movement event is necessary to avoid having a miss-click when trying to grab the dock widget when the user is dragging the mouse really fast.
In order to avoid that warning message on OS X, we can try to synthesize a mouse drag event with the left button set as "pressed." We can also test if the tab bar still has mouse focus after the synthesised mouse release, as a debugging measure.

Modifications were made to the following files:

- DetachableDockManager.cpp
- SendOSMouseEvent.h
- SendOSMouseEvent.mm
- SyntheticMouseEvent.cpp

The full project is attached.

Kryzon
4th February 2015, 01:12
Continuing with the OS X tests, on that 1.2 project please replace the contents of the sendMouseMoveEvent function in the SendOSMouseEvent.mm file with the following.

The modifications to SendOSMouseEvent.mm are the header of 'ApplicationServices' and the content of 'sendMouseMoveEvent.'


#import <Cocoa/Cocoa.h>
#import <CoreFoundation/CoreFoundation.h>
#import <ApplicationServices/ApplicationServices.h>
#import "sendOSMouseEvent.h"

void sendMousePressEvent(int x, int y) {
CGPoint location = CGPointMake(x, y);
CGEventRef mouseDown = CGEventCreateMouseEvent(NULL, kCGEventLeftMouseDown, location, kCGMouseButtonLeft);
CGEventPost(kCGHIDEventTap, mouseDown);
CFRelease(mouseDown);
}

void sendMouseReleaseEvent(int x, int y) {
CGPoint location = CGPointMake(x, y);
CGEventRef mouseUp = CGEventCreateMouseEvent(NULL, kCGEventLeftMouseUp, location, kCGMouseButtonLeft);
CGEventPost(kCGHIDEventTap, mouseUp);
CFRelease(mouseUp);
}

void sendMouseMoveEvent(int x, int y) {
CGPoint location = CGPointMake(x, y);
CGError ret = CGWarpMouseCursorPosition(location); // Move the mouse without generating a mouse move or drag event.
if (ret == kCGErrorSuccess)
CGAssociateMouseAndMouseCursorPosition(true); // If successful, re-associate mouse and cursor position to avoid a delay.
}
It uses CGWarpMouseCursorPosition, which seems like a better solution that changes the mouse location without emitting events at all.
What's left is to test this detachable dock system with a Wacom (or other brand) digitizer tablet.

shinichy
4th February 2015, 23:14
It still doesn't work. Both the output and the stack trace seem same.
Does it work on your Mac?



QNSView mouseDragged: Internal mouse button tracking invalid (missing Qt::LeftButton)
QNSView mouseDragged: Internal mouse button tracking invalid (missing Qt::LeftButton)
QNSView mouseDragged: Internal mouse button tracking invalid (missing Qt::LeftButton)
QNSView mouseDragged: Internal mouse button tracking invalid (missing Qt::LeftButton)
The program has unexpectedly finished.

Kryzon
5th February 2015, 00:04
Hello.
I don't have a Mac, so I was infering solutions based on what you reported.

That Q_ASSERT_X in the eventFilter of project 1.2 (make sure you're using that, for information) and the CGWarpMouseCursorPosition from the modification above are all I can think of, for the moment. Thank you for testing.

tomkulaga
2nd July 2015, 02:02
For me when I drag the tab, it seems to trigger the resize event for the floated dock widget.

Anyone else have the same?

Win7

Kryzon
2nd July 2015, 02:21
I wrote that under Windows XP and it was working there.
Now I tested under Windows 8.1 and I'm having the same as you: the widget is entering resize mode once it's detached.

The reason for that is the function "DetachableDock::centreTitle," it tries to compute the title bar height based on 'frameGeometry - geometry.' But they're both the same value, so the title bar is not being properly centered on the mouse cursor. According to the documentation it should be working, so we'll need to find a different way to estimate the title bar height.

EDIT: Alright, here's the solution (thanks to this fellow Qt user (https://forum.qt.io/topic/14130/getting-titlebar-height-of-a-qmdisubwindow/2))

Inside "DetachableDock::centreTitle" in DetachableDock.cpp, add the following:


#include <QStyle> // Add this include at the top, with the other includes.

[...]

void DetachableDock::centreTitle( QPoint here )
{
int centreTitleX = frameGeometry().width() / 2;
int centreTitleY = style()->pixelMetric( QStyle::PM_TitleBarHeight, 0, this ) / 2; // Corrected line, it uses the standard pixel metric.

QPoint centreTitlePos = here - QPoint( centreTitleX, centreTitleY );
move( centreTitlePos );
}

While this fixes the detaching, the docking back doesn't seem to work anymore under Windows 8.1.
The standard dock widgets example works fine, so it's this customisation that is broken.

vipvipx5
8th October 2015, 09:53
I have tried implement sendOSMouseEvent() for UBUNTU 14 as below, But it DO NOT run correctly, any one please help me...


void sendOSMouseEvent( SynthMouseEvent eventType, int x, int y )
{
// Create and setting up the event
Display *display = XOpenDisplay(NULL);

// Create and setting up the event
XEvent event;
memset (&event, 0, sizeof (event));
event.xbutton.button = Button1;
event.xbutton.x = x;
event.xbutton.y = y;
event.xbutton.same_screen = True;
event.xbutton.subwindow = DefaultRootWindow (display);
while (event.xbutton.subwindow)
{
event.xbutton.window = event.xbutton.subwindow;
XQueryPointer (display, event.xbutton.window,
&event.xbutton.root, &event.xbutton.subwindow,
&event.xbutton.x_root, &event.xbutton.y_root,
&event.xbutton.x, &event.xbutton.y,
&event.xbutton.state);
}

switch ( eventType )
{
case MousePress:
// Send a Windows left mouse button press event using SendInput.
event.type = ButtonPress;
if (XSendEvent (display, PointerWindow, True, ButtonPressMask, &event) == 0)
qDebug("Error to send the event!");
XFlush (display);
break;

case MouseRelease:
// Send a Windows left mouse button release event using SendInput.
event.type = ButtonRelease;
if (XSendEvent (display, PointerWindow, True, ButtonReleaseMask, &event) == 0)
qDebug("Error to send the event!");
XFlush (display);
break;

case MouseMove:
// Not used right now.
break;
}
}