Qt GUI application on macOS: how to find the currently active screen? - c++

We are developing a macOS application whose GUI is relying on Qt.
At startup, we want to show() the QMainWindow at a specific location on the currently active screen (with multi screen systems in mind).
Is there a way to get the QScreen representing the currently active screen?
From our test, QGuiApplication::primaryScreen() is the first screen (which is consistent with the name), but we cannot find an equivalent for the active screen.

Qt5 provides functionality to do so, the QWindow::setScreen method sets the screen on which the window should be shown.
Any widget provides access to this pointer via QWidget::windowHandle():
QWidget * widget = new QWidget();
auto screens = qApp->screens();
// compute the index
widget->windowHandle()->setScreen(screens[index]);
widget->showFullScreen();
To get the screen number, you can use the mouse position and assume that the screen with the mouse is the one with the current focus:
QPoint globalCursorPos = QCursor::pos();
int mouseScreen = qApp->desktop()->screenNumber(globalCursorPos);
So the final code can be something like that:
QWidget * widget = new QWidget();
const auto globalCursorPos = QCursor::pos();
const auto mouseScreen = qApp->desktop()->screenNumber(globalCursorPos);
widget->windowHandle()->setScreen(qApp->screens()[mouseScreen]);
widget->showFullScreen();
Windows
If this approach does not fit your needs, you will need to perform some OS calls.
For instance, on Windows, you can use MonitorFromWindow:
HMONITOR active_monitor_number = MonitorFromWindow(GetActiveWindow(), MONITOR_DEFAULTTONEAREST);
If you need more information about the screen, you can use Qt or GetMonitorInfo.
I am not a Mac OS X developer, but it may exist a similar API

I did it in the following way:
#if (QT_VERSION >= QT_VERSION_CHECK(5, 10, 0))
QPoint topLeft = QPoint(0, 0);
QScreen* currentScreen = QGuiApplication::screenAt(QCursor::pos());
if (nullptr != currentScreen) {
topLeft = currentScreen->geometry().topLeft();
}
#else
QPoint topLeft =
qApp->desktop()
->screenGeometry(qApp->desktop()->screenNumber(QCursor::pos()))
.topLeft();
#endif
someWidget->move(mapFromGlobal(topLeft) + QPoint(offset, offset));
Note that, sometimes, you may get nullptr for currentScreen (in my case if primary screen is at the bottom and mouse pos at the bottom or left edge on the primary screen).

Related

Jittery movement of QScrollArea from custom touchscreen driver

So a few years ago, I wrote a custom touchscreen driver specifically for a particular application which ran on a Scientific Linux 6.4 (CentOS 6 based) OS, which did not have native touch support, but the kernel supported touch events, so I was able to directly read the raw data from the touchscreen in /dev/input/event* and read the event data to generate mouse events with Qt to control the application to mimic a multi-touch touchscreen. More recently, we've finally migrated to RedHat 8.4, but I had to disable the native touch driver, because as far as I know, the native Qt touch events didn't allow the same degree of control over the application that my driver did.
Recently during a trial, a technician reported that when using the touchscreen to manipulate one of the application's image display areas, the movement was very "jittery". The image display area is simply a QScrollArea with a QImage inside, and hidden scroll bars. The way it works is that on a mouseMoveEvent, it manipulates the scrollbars according to the delta value on the mouse event.
void PanArea::mouseMoveEvent(QMouseEvent* e)
{
SafeStr str;
if (m_pw)
{
if (m_disable_panning == false &&
e->buttons().testFlag(Qt::RightButton))
{
QPoint delta = e->pos() - m_last_pos;
horizontalScrollBar()->setValue(horizontalScrollBar()->value() -
delta.x());
verticalScrollBar()->setValue(verticalScrollBar()->value() -
delta.y());
m_last_pos = e->pos();
emit signalScrollPositionChanged(getScrollPosition());
// This force update has been added because
// when fast panning cause black to appear within the image
// because some pan widget updates were being skipped.
// Performance seems acceptable with this here.
m_pw->update();
}
else if (...)
{
// irrelevant code removed to save space
}
}
}
This works fine when simply right-click dragging on the scroll area. And then here is the relevant function that dispatches the mouse press and mouse move events from the touchscreen driver:
void InputHandler::panStart(TouchPoint* tp, QWidget* widget)
{
QPoint pos(tp->cx(), tp->cy());
QWidget* target = widget;
if (target == NULL) target = QApplication::widgetAt(pos);
if (target != NULL)
{
QPoint local = target->mapFromGlobal(pos);
QMouseEvent* press =
new QMouseEvent(QEvent::MouseButtonPress, local, pos,
Qt::RightButton, Qt::RightButton, Qt::NoModifier);
QApplication::postEvent(widget, press);
}
}
void InputHandler::panMove(TouchPoint* tp, QWidget* widget)
{
QPoint pos(tp->cx(), tp->cy());
QWidget* target = widget;
if (target == NULL) target = QApplication::widgetAt(pos);
if (target != NULL)
{
QPoint local = target->mapFromGlobal(pos);
QMouseEvent* move =
new QMouseEvent(QEvent::MouseMove, local, pos, Qt::NoButton,
Qt::RightButton, Qt::NoModifier);
QApplication::postEvent(widget, move);
}
}
When I touch and drag on the widget, it DOES move, but it jumps all over the place as if I were rapidly dragging it in random directions.
Is there some caveat of how Qt5 mouse events work that could explain this behavior? Or something to do with the way the widget is moving the image around?

wxStyledTextCtrl - Size of AutoComp

I was just wondering if it is possible to find the size (in pixels) of the autocompletion control shown by the wxStyledTextCtrl.
My goal is to show a help window associated with the entry when a selection happens. Therefore, I need the location and also the width of the autocompletion control. It seems location can be found from m_STC->AutoCompPosStart() but there seems to be no way of finding the width. I am using the following code:
auto StartPos = m_STC->ToPhys(m_STC->PointFromPosition(m_STC->AutoCompPosStart()));
int MaxChars = m_STC->AutoCompGetMaxWidth(); //returns 0 unless set to a fixed value
int w, h;
m_STC->GetTextExtent(wxString("A", MaxChars), &w, &h);
return wxPoint(StartPos.x + w, StartPos.y);
I am using Windows and wxWidgets 3.2.
There is no way to get this information from the styled text control because the autocomp window is completely managed by Scintilla. And unfortunately, Scintilla doesn't make any methods available for getting this info.
As a hack-around, the popup is currently implemented as a child window of the styled text control. So you could do something like this:
const wxWindowList& childred = m_stc->GetChildren();
for ( auto it = childred.begin() ; it != childred.end() ; ++it )
{
// We're assuming the styled text control has at most 1 child -
// namely the autocomp popup. It might be better to check that
// the window found is in fact the auto comp popup somehow.
// win->GetPosition() will return screen coordinates, so to get client
// coordinates, ScreenToClient must be called.
wxPoint psn = m_stc->ScreenToClient(win->GetPosition());
wxSize sz = win->GetSize();
// Do something with size and position here.
}
However, this isn't guaranteed to always work. If in the future, the auto comp popup implementation is changed to use a top level window instead of a child of the control, this method will fail.

Gtk / Gtkmm Kinetic Scrolling

I am designing a GUI that requires kinetic scrolling for my application and I am using GTK (coming from Qt) and I have the following code:
Gtk::Window * wnd = new Gtk::Window();
Gtk::ScrolledWindow * scr = new Gtk::ScrolledWindow();
Gtk::Layout * lay = new Gtk::Layout();
Gtk::VBox * vbox = new Gtk::VBox();
wnd->add(*scr);
scr->add(*vbox);
for (int i = 0; i < 20; i++)
{
Gtk::Button * btn = new Gtk::Button();
btn->set_label("Click Me");
vbox->pack_start(*btn);
btn->show();
}
scr->set_kinetic_scrolling(true);
wnd->show();
scr->show();
lay->show();
vbox->show();
and this is what you get:
This link tells me the following:
void Gtk::ScrolledWindow::set_kinetic_scrolling ( bool
kinetic_scrolling = true )
Turns kinetic scrolling on or off.
Kinetic scrolling only applies to devices with source
Gdk::SOURCE_TOUCHSCREEN.
Since gtkmm 3.4:
Parameters
kinetic_scrolling true to enable kinetic scrolling.
I tried grabbing the button with the mouse and to kinetically scroll but as expected, it does not work. I don't have a touch screen yet and I need to experiment with my mouse, Is there any way I can do this?
I also found the following:
enum InputSource {
SOURCE_MOUSE,
SOURCE_PEN,
SOURCE_ERASER,
SOURCE_CURSOR,
SOURCE_KEYBOARD,
SOURCE_TOUCHSCREEN,
SOURCE_TOUCHPAD,
SOURCE_TRACKPOINT,
SOURCE_TABLET_PAD
}
An enumeration describing the type of an input device in general terms.
Is there any way I can emulate the type of device? I want to see it working.
You must have a touch event. Use GtkInspector (Ctrl+Shift+I) then in the Visual tab, set Simulate touchscreen (as shown below):
It should work. More on Gtk Inspector

Qt 5, get the mouse position in a screen

First of all, I'd like to mention that I found that related post How to get the mouse position on the screen in Qt? but it "just didn't work" for me. I made some tests, and the results didn't work as I expected, so I decided to make a new post to talk about the test I made and to find an alternative solution.
That's the code I used to make the test:
QScreen *screen0 = QApplication::screens().at(0);
QScreen *screen1 = QApplication::screens().at(1);
printf("screen0 %s \n", screen0->name().toStdString().c_str());
printf("screen1 %s \n", screen1->name().toStdString().c_str());
// Position on first screen.
QPoint pos0 = QCursor::pos(screen0);
// Position on second screen.
QPoint pos1 = QCursor::pos(screen1);
printf("pos 0: %d, %d \n", pos0.x(), pos0.y());
printf("pos 1: %d, %d \n", pos1.x(), pos1.y());
// Get position without screen.
QPoint pos = QCursor::pos();
printf("pos: %d, %d \n", pos.x(), pos.y());
What I was expecting, is that only one screen would return a valid position, since the cursor is only at one screen, not on both. But it's not the case, the both positions (pos0 and pos1) has the exactly same value, as we can see on the output:
screen0 DVI-D-0
screen1 HDMI-0
pos 0: 1904, 1178
pos 1: 1904, 1178
pos: 1904, 1178
Since the both positions has the same values, I can't know at which screen is the cursor. I don't know if that's a normal behavior or a bug, since the documentation doesn't say what happens when the screen argument isn't the screen where the mouse is.
My idea, is to open/launch an application (executed by a Qt daemon that must detect the selected screen) to the screen where the mouse is. I know that with libX11 it's possible, because I did it in the past, but I need to work with Qt 5, and I can't figure out how to do detect the selected screen with Qt.
I also made other tests, using QApplication and QDesktopWidget classes with no luck.
That's really weird. As a workaround, you could try this:
QPoint globalCursorPos = QCursor::pos();
int mouseScreen = qApp->desktop()->screenNumber(globalCursorPos);
Now you know which screen the cursor is in. Then you could find the cursor position within that screen doing this:
QRect mouseScreenGeometry = qApp->desktop()->screen(mouseScreen)->geometry();
QPoint localCursorPos = globalCursorPos - mouseScreenGeometry.topLeft();
This may seem like a trivial solution, but on my KDE it works (I ran into the same problems originally).
If you want to determine the local mouse coordinates with respect to a widget (this will be in device pixels and relative to the top left corner of the widget I believe) you can use
QWidget::mapFromGlobal(QCursor::pos());
i.e. call this->mapFromGlobal.
To figure out on which screen you are, you can iterate throught QGuiApplication::screens() and check whether the cursor fits in the geometry of the screen.
Here is a more complex example to compute the native cursor position (note the additional work needed to work with High DPI screens):
QPoint getNativeCursorPosition()
{
QPoint pos = cursorPosToNative(QCursor::pos());
// Cursor positions from Qt are calculated in a strange way, the offset to
// the origin of the current screen is in device-independent pixels while
// the origin itself is native!
for (QScreen *screen : QGuiApplication::screens()) {
QRect screenRect = screen->geometry();
if (screenRect.contains(pos)) {
QPoint origin = screenRect.topLeft();
return origin + (pos - origin) * screen->devicePixelRatio();
}
}
// should not happen, but try to find a good fallback.
return pos * qApp->devicePixelRatio();
}
This may work for you?
It did for me
QDesktopWidget *widget = QApplication::desktop();
QPosition globalCursorPosition = widget->cursor().pos();
Sice it seems that it can't be done with Qt (at least with my system configuration, and it seems that also in Windows) I decided to use the libX11 to make that implementation, which works like charm.
It's not an ideal solution because I wanted to only use Qt, but it works.
With QML you can use the properties of the Screen QML Type:
Screen.virtualX : The x coordinate of the screen within the virtual desktop.
Screen.virtualY : The y coordinate of the screen within the virtual desktop.
import QtQuick 2.6
import QtQuick.Window 2.2
console.log("Pos x : " + Screen.virtualX )
console.log("Pos y : " + Screen.virtualY )
This work with single screen as well multi-monitor systems.
I recently ran into a similar problem on Qt 5.15 + Windows + mixed DPI and needed this work around within a QWindow object.
QScreen* primaryScreen = QGuiApplication::primaryScreen();
QScreen* thisScreen = screen();
qreal primaryDPR = primaryScreen->devicePixelRatio();
qreal thisDPR = thisScreen->devicePixelRatio();
qreal scale = thisDPR / primaryDPR;
QPoint pos = scale * QCursor::pos();
I'm unsure if this works on other platforms.

Make a QDialog appear in a different screen

The title says it pretty much all:
I have two screens, and each time I create a QDialog it appears in the same screen as its parent.
How can I make it appear in a different screen? Or should I use a different type of top-level widget?
The code I use to create the dialog is:
QDialog my_dialog = new QDialog(this,
Qt::WindowMaximizeButtonHint |
Qt::WindowCloseButtonHint);
...
EDIT:
I have also tried using the QDesktopWidget which gives me a QScreen object that refers to the second screen. But then I don't find how to instruct the QDialog to use that QScreen (setting it as the parent doesn't work).
It is bad, that you edit your question without reading comments :(
// Your screen geometry:
QRect buildScreenGeometry()
{
auto desktop = QApplication::desktop();
QRect virtualRect;
const auto n = desktop->screenCount();
for ( auto i = 0; i < n; i++ )
virtualRect |= desktop->screenGeometry(i);
return virtualRect;
}
// Moving
auto dlg = new QDialog( someParent );
auto newPoint = QPoint( 2000, 0 ); // point on another screen
auto realPos = someParent->mapFromGlobal( newPoint );
dlg->move( realPos );
That's all.
UPDATE:
You should understand, that there are only ONE screen area with COMMON coordinate system, that contains ALL screens.
For example, you have 2 monitors with 800x600 resolution. First (main) monitor is standing left, and second standing right. In this case, coordinate system, that is available for your application is 1600x600. So, if your widget has 100x100 top-left position, on a first monitor, and you want to move it to another, you should call move(900x100); // 900 == screen1.width() + dialog.pos().x(). Then your widget will have 100x100 position on second monitor.
You should read Qt documentation.
You can use move on your QDialog, but be aware that move will set the QDialog position relative to it's parent.
You can get the Main Window's screen position and use that to setup the QDialog position. Just know that you're not guaranteed to have 2 screens on the end user machine.
For more information on move see: http://doc.qt.io/qt-5/application-windows.html#window-geometry