Slow repaint underneath dragged object on X... Can Qt force drag and drop operations to be internal only? - qt

I'm implementing Qt's drag and drop API across Windows and X. When I pick up an object in the app running on X and drag it, it leaves a white ghost trail of itself on the window underneath, as if the window underneath is being slow to repaint where the dragged object was previously obscuring part of itself.
I believe that this is symptomatic of the same problem that Qt has just solved with resizing windows causing flicker in child widgets on X windows - i.e. the dragged object is treated as a separate native window and therefore X handles the clipping from the dragged object to the window underneath. Since X does this in a different way to Qt, we get the ghosting effect.
Has anyone experienced the same problems? One solution that comes to mind is to use the same technique as detailed in the blog article linked above and stop the dragged object being treated as a native window, presumably at the cost of drag and drop being limited to my application only (I have no problem with this). Would anyone know how to force drag and drop operations to be internal only?
EDIT: I'm using QDrag::setPixmap to set the graphical representation of the dragged object - it is important that I retain this in favour of a standard drag cursor as this interface is being used on a touchscreen device and will hence have no visible cursor.

I'm now of the opinion that short of editing and then compiling my own build of Qt (not an option for me), I can't force drag and drop operations to be internal only.
Equally, I can't find any way of getting rid of the ghost trail by tweaking my window manager settings or using a compositing window manager (thanks anyway though #atomice). Using OpenGL as the renderer increases the screen repaint speed slightly, but is not perfect and introduces its own problems (see Starting a Qt drag operation on X11 w/ OpenGL causes screen flicker). I would still be very interested to hear any ideas though.
I have, however, got a workaround for my problem which works on both Windows and X. Here's a simplified version:
void DoDrag()
{
//Prepare the graphical representation of the drag
mDragRepresenter = new QWidget(QApplication::activeWindow());
mDragRepresenter->setAttribute(Qt::WA_TransparentForMouseEvents);
mDragRepresenter->SetPixmap(GenerateDragPixmap());
RepositionDragRepresenter();
mDragRepresenter->show();
QTimer UpdateTimer;
connect(&UpdateTimer, SIGNAL(timeout()), this, SLOT(RepositionDragRepresenter()));
UpdateTimer.start(40);
//Start the drag (modal operation)
Qt::DropAction ResultingAction = Drag->exec(Qt::CopyAction);
UpdateTimer.stop();
delete mDragRepresenter;
}
void RepositionDragRepresenter()
{
mDragRepresenter->move(QApplication::activeWindow()->mapFromGlobal(QCursor::pos()) - mDragRepresenterHotSpot);
}

An X11 window is only created for a drag operation if a QDrag::mimeData()->hasImage() is true. If you modify your code so it doesn't use an image then you will just get a drag cursor instead which won't trigger a repaint of the windows underneath.
You don't specify what kind of object you are dragging or how you are setting up the drag operation. Can you add some code to show that?

Related

Qt working Windows 8 style frameless custom window

I recently installed Github for Windows on my Windows 7 machine and loved the custom frame it had, it fit really well with the overall application theme and had it's own titlebar buttons which were really well layed out, very fluent, and seemed very natural to work with.
I did a bit of digging and found 2 flags that would clear out the border completely and after a bit of customization I got my app to also have a nicely customized look which was intuitive yet different from all the apps with the old Windows border.
The thing was it wasn't fluent and naturally responsive like the other windows, it was glitchy as heck, I easily got the window to move around with the mouse but it often glitched and was able to be moved on areas it shouldn't like clicking and dragging on a disabled button.
The maximize button which was linked to showMaximize method just enlarged the whole window to take up the entire desktop, you could still move it (wasnt really trully maximized).
The window responded to none of the system signals like clicking the taskbar to minimize it and such.
After a lot of fixing around I just finally gave up which was ashame cause I really liekd how it looked and it was very intuitive, much like github for Windows is very intuitive.
Is there any way I can accomplish this, I'm really not ready to give up yet.
I know that when making a raw Windows API application you have to link it to the XP built-in style because it inherits the Windows 95 style by default, maybe theres a Windows 8 style that Qt'S not connected to, I do't know didn'T go that far in research yet.
Minimize window by clicking on task bar
It seems that Qt::FramelessWindowHint's implementation is limited. When this flag is set, Windows thinks that this window cannot be minimized or maximized. I've tried this solution implemented in pure winapi. Minimizing and restoring frameless window by clicking on taskbar works fine. Apparently Qt sets some bad flags that block this functionality. May be there is a good reason for that, I don't know.
We can use winapi and Qt together but it is troublesome. Firstly, winapi code should be executed after you set window flags and show the window using Qt. Otherwise Qt will overwrite window flags.
Another problem is when we remove border using winapi, window geometry suddently changes, and Qt doesn't know about that. Rendering and event mapping (including mouse click positions) become invalid. I didn't find any documented way to update mapping. I've found that we can tell Qt that screen orientation has changed, and it forces it to recalculate window geometry. But this looks like a dirty hack. Also the QWidget::windowHandle function is missing in Qt 4 and "is subject to change" in Qt 5. So this method is not reliable. But anyway, it works now. Here is complete code (tested in Windows 8) that should be placed in the top window class constructor:
#include "windows.h"
#include <QWindow>
//...
show();
HWND hwnd = reinterpret_cast<HWND>(effectiveWinId());
LONG lStyle = GetWindowLong(hwnd, GWL_STYLE);
lStyle &= ~(WS_CAPTION | WS_THICKFRAME | WS_MINIMIZE | WS_MAXIMIZE | WS_SYSMENU);
SetWindowLong(hwnd, GWL_STYLE, lStyle);
setWindowFlags(windowFlags() | Qt::FramelessWindowHint);
windowHandle()->reportContentOrientationChange(Qt::PrimaryOrientation);
The true way to solve this problem is to modify the Window Qt platform plugin (see QWindowsWindow class in Qt sources). May be there is a way to inherit from the default implementation, modify it and use in your app. Also you can ask Qt developers is this behavior reasonable or is it a bug. I think that this issue can be fixed with a patch.
If you still intend to use this code and other OSs should be also supported, don't forget to wrap windows-specific implementation in #ifdef Q_OS_WIN.
Enable window dragging only when title bar is clicked and window is not maximized
Other problems can be fixed more easily. When you process mouse events to implement window dragging, check window state and event position and disable moving when it is unwanted.
void MainWindow::mousePressEvent(QMouseEvent *e) {
if (!isMaximized() &&
e->button() == Qt::LeftButton &&
ui->title->geometry().contains(e->pos())) {
window_drag_start_pos = e->pos();
}
}
void MainWindow::mouseReleaseEvent(QMouseEvent *e) {
window_drag_start_pos = QPoint(0, 0);
}
void MainWindow::mouseMoveEvent(QMouseEvent *e) {
if (!window_drag_start_pos.isNull()) {
move(pos() + e->pos() - window_drag_start_pos);
}
}
void MainWindow::on_minimize_clicked() {
showMinimized();
}
void MainWindow::on_maximize_clicked() {
if (isMaximized()) {
showNormal();
} else {
showMaximized();
}
}
Here ui->title is a label used for displaying fake title bar, and QPoint window_drag_start_pos is a class variable.
If you use Qt::FramelessWindowHint, you lose all Windows frame related features, such as docking, shortcuts, maximizing, etc. You can implement some of them yourself, with great effort in some cases, but other things will still just not work, including handling multiple monitors and all the capabilities of the Windows Key. If you need your app to behave like a regular Windows app, Qt::FramelessWindowHint is pretty much a dead end.
The only real solution is to use the DWM API that Windows provides for this purpose, Custom Window Frame Using DWM. They provide examples of how to do anything you might want in the frame area, while preserving all the standard windows-manager behaviors.
Applications such as Chrome do use DWM for this (see the AeroGlassFrame class in the Chromium source). Granted, Chrome isn't using Qt, so it doesn't have to do the work of sneaking around QMainWindow to handle the Windows-specific messages, and their code has plenty of comments that indicate how messy it is, like:
// Hack necessary to stop black background flicker, we cut out
// resizeborder here to save us from having to do too much
// addition and subtraction in Layout() ...
Similarly, the Github-for-Windows app you mention is built on a platform called Electron, which in turn uses, you guessed it, DWM.
I haven't found any working example of a DWM frame around a Qt application, but hopefully this provides a starting point.

Getting a mouse drop event in Qt 5 if mouse doesn't move before mouse release

Something seems to have changed in Qt 5: you can't get a drop or move event if you don't move at least one pixel from the start point where you were when QDrag::exec() was called. Try putting a breakpoint in the dropEvent of the Draggable Icons Sample, then click a boat and release it without moving the mouse. That generates an "ignore" without any drop signal.
(This is on Kubuntu 13.10 with Qt 5.1.)
When teaching how to start a drag operation, the documentation suggests you might use manhattanDistance() to determine if the mouse has moved enough to really qualify as "the user intending to start a drag". But you don't have to use that; you can start up a QDrag on the click itself.
Anyone know of a workaround to have that same kind of choice on the drop side, or is that choice gone completely? :-/
Why I care: I've long had frustrations trying to get a tight control on mouse behavior in GUI apps—Qt included. There seems to be no trustworthy state transition diagram you can draw of the invariants. It's a house of cards you can disprove very easily with simple tests like:
virtual void enterEvent(QEvent * event) {
Q_ASSERT(!_entered);
_entered = true;
}
virtual void leaveEvent(QEvent * event) {
Q_ASSERT(_entered);
_entered = false;
}
This breaks all kinds of ways, and how it breaks depends on the platform. (For the moment I'll talk about Kubuntu 13.10 with Qt 5.1.) If you press the mouse button and drag out of the widget, you'll receive a leaveEvent when you cross the boundary...and then another leaveEvent when the button is released. If you leave the window and activate another app in a window on screen and then click inside the widget to reactivate the Qt app, you'll get two consecutive enterEvents.
Repeat this pattern for every mouse event, and try and get a solid hold on the invariants...good luck! Nailing these down into a bulletproof app that "knows" it's state and doesn't fall apart (especially in the face of wild clicking and alt-Tabbing) is a bit of a lost cause.
This isn't good if your program does allocations and has heavy processing, and doesn't want to do a lot of sweeping under the rug (e.g. "Oh, I was doing some processing in response to being entered... but I just got entered again without a leave. Hm, I guess that happens! Throw the current calculations away and start again...")
In the past what I've done is to handle all my mouse operations (even simple clicking) with drag & drop. Getting the OS drag & drop facility involved in the operation tended to produce a more robust experience. I can only presume this is because the testers actually had to consider things like task switching with alt-Tab, etc. and not cause multiple drop operations or just forget that an operation had been started.
But the "baked in at a level deeper than the framework" aspect actually makes this one-pixel-move requirement impossible to change. I tried to hack around it by setting a timer event, then faking a QMouseEvent to bump the cursor to a new position once the drag was in effect. However, I surmise that the drag and drop is hooked in at the platform level, and doesn't consult the ordinary Qt event queue: src/plugins/platforms/xcb/qxcbdrag.cpp
The issue has--as of 1-May-2014--been acknowledged as a bug by the Qt team:
https://bugreports.qt-project.org/browse/QTBUG-34331
It seems that me bountying it here finally brought it to their attention, though it did not generate any SO answers I could accept to finalize the issue. So I'm writing and accepting my own. Good work, me. (?) Sorry for not having a better answer. :-/
There is another unfortunate side effect of the Qt5 change, pointed out by a "Dmitry Mordvinov":
Same problem here. Additionally app events are not handled till the first mouse event after drag started and this is really nasty bug. For example all app animations are suspended during that moment or application hangs up when you try to drag with touch monitor.
#dvvrd had to work around it, but felt the workaround was too ugly to share. So it seems that if you're affected by the problem, the right thing to do is go weigh in...and add your voice to the issue tracker to perhaps raise the priority of a solution.
(Or even better: patch it and submit the patch. 'tis open source, after all...)

Attach Qt windows?

Is there any way to attach two Qt windows together? For example, if window A is the main window and window B is another widget I want to be able to show window B to the side of A and have both windows move together if the windows are dragged.
Not that I am aware of, but you can try following the trail of QMoveEvent. When a given widget is moved void QWidget::moveEvent ( QMoveEvent * event ) is called, and the QMoveEvent contains both old and new pos. Using this information, you can inject a move event in the other widget as well, and make it follow.
Of course, I am speaking of two independent widgets, each one in its own window. If they are contained, you don't need anything but a Layout management (see QLayout and related classes).
I don't work with Qt since a long time, so there could be a better method, but if I had to do it right now, this is the strategy I would use.
Also, I have the feeling that the QMoveEvent will be invoked only at start and end, unless you enable mouse tracking. If the former is the case, you will obtain that the other widget will "teleport" at the end of the move, instead of following smoothly.
You might be after something like this:
http://doc.qt.io/archives/4.6/qt4-mainwindow.html
Window A would be a QMainWindow and window B would be a QDockWidget.

Flex-AIR: Make application with NO tab in taskbar?

I have an AIR app about half way done right now. I was informed by the client today that he does not want a tab to show up in his task bar. I already have this in place for new windows by making them lightweight. I do not know how to make the main window lightweight though. If there is not a way, is there a work around, like not not having a main window and just opening lightweight windows, don't know how that could be done either though? Anyone know how to do this?
Thanks!
Check this doc out. -- Yes, you can do this. In short, you have to hide the initial window - then display your application in a lightweight window.
Also - do note: On a Mac - the behavior is different. By convention, a window is not shown in the 'task bar' when it is displayed. When it is minimized it is in the bar. To hide the application when minimized on a Mac - you have to make the window 'invisible' instead of minimizing it. The doc mentioned above gives further details.
The key part of the doc for your case:
On the Windows operating system,
windows created with the types utility
or lightweight do not appear on the
taskbar. Invisible windows do not
appear on the taskbar, either.
Because the initial window is
necessarily of type, normal, in order
to create an application without any
windows appearing in the taskbar, you
must either close the inital window or
leave it invisible.
To close all
windows in your application without
terminating the application, set the
autoExit property of the
NativeApplication object to false
before closing the last window. To
simply prevent the intial window from
ever becoming visible, add
false to the
element of the
application descriptor file (and do
not set the visible property to true
or call the activate() method of the
window).
In new windows opened by the
application, set the type property of
the NativeWindowInitOption object
passed to the window constructor to
NativeWindowType.UTILITY or
NativeWindowType.LIGHTWEIGHT.

how to keep a nativewindow on top

I need to keep a NativeWindow I am creating on top of the main window of the application.
Currently I am using alwaysInFront = true, which is not limited to the windows in the application. I can successfully synchronize the minimize/restore/move/resize actions, so the top window behaves appropriately in those cases. Even though using this option has the drawback that if I alt-tab to other application the window goes on top of the other application.
Because of the above I am trying to get it to work without using the alwaysInFront. I have tried using orderInFrontOf and orderToFront, which gets it in place but when I click an area in the main window the top one becomes hidden i.e. air makes it the top one.
I have tried capturing activate/deactivate events but it only happens on the first click, so on the second click the top window becomes hidden again. I also tried making the top window active when the main one becomes active, but that causes the main one to loose focus and I can't click on anything.
Ps. I am doing this to improve the behavior of a HTMLOverlay I am using - see Flex Air HTMLLoader blank pop up window when flash content is loaded
Listening for Event.DEACTIVATE and calling event.preventDefault() should work. Not sure if that is what you have tried, but I have an app where that does the trick.
I ended up turning on/off the alwaysInFront option based on whether the main window or the top window were active i.e. if none where active I turned it off. This was additionally to what I mentioned in the question.
That way when the user switches to another application, the window doesn't go on top of the other apps. I still would prefer a solution where I don't have to use the alwaysInFront option, or even better an alternate solution to the flex loading flash in external sites issue I linked to above.
Ps. I will try to check with the owner of the HTMLOverlay to submit a patch (its an improvement, although its tied to an app that doesn't open extra windows when opening the overlay).
Update: I have committed the changes to the HTMLOverlay.
I'm trying to do something very similar. In an AIR application, I have one large full screen window which is essentially the "desktop". I always want this window to stay behind all other windows in my app. There are, however, some items on the "desktop" window that need to be clickable.
There appears to be no clean way to force a window to maintain its position in the window ordering.
What I've settled on so far, which isn't perfect, is to make all other windows in my app use the alwaysOnTop property but bind this to a global var (ugh) that I maintain to track the overall application level active/inactive state. This way, when I switch to another app, my windows don't float above the all other app windows - they correctly move behind as expected.
Then, I have a regular (alwaysOnTop=false) window that is fully transparent as an "overlay" to the desktop window on which I can place various interactive controls. This window is OK to come forward since it's transparent and my other windows are alwaysOnTop.
Finally, and crucially, I install three event listeners on the "desktop" window as follows:
protected function onApplicationComplete(event:Event):void
{
this.addEventListener(MouseEvent.MOUSE_DOWN, onClickHandler, true,1000,true);
this.addEventListener(MouseEvent.CLICK, onClickHandler, true,1000,true);
this.nativeWindow.addEventListener(Event.ACTIVATE, onActivateWindow,false,-1);
}
protected function onActivateWindow(event:Event):void
{
trace("sent via activate to back");
orderInBackOf(bigTransparentWindow);
}
protected function onClickHandler(event:MouseEvent):void
{
trace("sent via click to back");
orderInBackOf(bigTransparentWindow);
}
I'm not entirely happy with all this since there is still some occasionally noticeable flicker of objects in the overlay window - it appears that the "Desktop" window gets ordered in front of it, an update of some sort happens, and then it gets forced behind again.
Any better solutions welcome!

Resources