Jittery movement of QScrollArea from custom touchscreen driver - c++

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?

Related

Process only tiled event and not maximize event

I'm building a very basic calculator program using GTKMM
Basic Calculator screenshot
The layout is in landscape mode (buttons and display label in horizontal orientation) by design
I want to orient those two to portrait mode (ie., in vertical) when the user snaps/tiles the window to the right or left
Below is a sample code I used:
bool
BasicCalculator::on_calculator_window_state_changed(
GdkEventWindowState *window_state_event,
Gtk::Box *box)
{
if (
window_state_event->new_window_state &
(Gdk::WINDOW_STATE_RIGHT_TILED | Gdk::WINDOW_STATE_LEFT_TILED)
)
box->set_orientation(Gtk::ORIENTATION_VERTICAL);
else
box->set_orientation(Gtk::ORIENTATION_HORIZONTAL);
return true;
}
Window tiled left and right
Window maximized
The code works but my window orients vertically when maximized which is not my intention. I want it to be in horizontal orientation
How do I process only the tiled event and not the maximize event?
PS: My project repo in case you want to build & test
After hacking around a bit with the GdkEventWindowState enums, I figured I can compare against the tiled state with the window maximized state together
ie., make sure to switch orientation to vertical only if the window is tiled and not maximized
Because the code
...
if (
window_state_event->new_window_state &
(Gdk::WINDOW_STATE_RIGHT_TILED | Gdk::WINDOW_STATE_LEFT_TILED)
)
...
returns TRUE not just for the tiled state but also for maximized state also
Below is the improved code that works:
bool
BasicCalculator::on_calculator_window_state_changed(
GdkEventWindowState *window_state_event,
Gtk::Box *box)
{
bool is_window_tiled = window_state_event->new_window_state &
(Gdk::WINDOW_STATE_RIGHT_TILED | Gdk::WINDOW_STATE_LEFT_TILED);
bool is_window_maximized = window_state_event->new_window_state &
Gdk::WINDOW_STATE_MAXIMIZED;
if (is_window_tiled && !is_window_maximized)
box->set_orientation(Gtk::ORIENTATION_VERTICAL);
else
box->set_orientation(Gtk::ORIENTATION_HORIZONTAL);
return true;
}
Now the window keeps its default horizontal orientation even if maximized :)

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

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).

In Qt scribble example, how can I saving the drawing data

In Qt scribble example (located in Qt examples/widgets/scribble/), user can draw any shape with pen color and pen width with mouse event.
void ScribbleArea::mouseMoveEvent(QMouseEvent *event)
{
if ((event->buttons() & Qt::LeftButton) && scribbling)
drawLineTo(event->pos());
}
void ScribbleArea::mouseReleaseEvent(QMouseEvent *event)
{
if (event->button() == Qt::LeftButton && scribbling) {
drawLineTo(event->pos());
scribbling = false;
}
}
In the original example, the line drawn cannot be modified any more. I want to change it so that I can change line color/width after the lines are drawn, and also save the drawing as a "draft" so line color/width can be edited even open later.
My first thought is to save the points from mouse event and redraw shape every time an edit happens. There may be a lot of data to process if the drawing is not small and redrawing everything seems poor in efficiency. Is there a more elegant way out there to do this? Thank you.

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

How to prevent mouseMoveEvent on QCursor::setPos() using Qt?

I am currently developing on an image viewer application. In this application I have a so called "pan-zoom" feature. This means that, when holding a certain mouse button, the user can zoom the image by panning forth and back.
It works fine, but as the feature is used, the mouse (naturally) moves up and down on the screen and will at some point reach the screen borders, which will make it stop. Instead I would like a behaviour where the mouse remains stationary and only the image magnification changes.
I tried to achieve this by invoking QCursor::setPos inside the QWidget::mouseMoveEvent and reset the mouse to the initial position after I have processed the move. It works so far as that the mouse is staying nearly stationary (it's wiggling forth and back). However, this will cause the mouse move event to be called again effectively annulling the adjustment I just made. This will result in a "wiggling" effect. Every adjustment will immediately be reversed.
Here is a code snipped, so you get an idea of what I am doing:
void ImageView::mouseMoveEvent(QMouseEvent *e) {
//some code
if (_panZooming) {
//some code here
//doesn't work as expected because it invokes this event again
QCursor::setPos(mapToGlobal(_initialMousePosition.toPoint()));
}
}
Is there a way to prevent the mouse move event to happen when using QCursor::setPos?
Assuming you're not calling the base class mouseMoveEvent, you should accept the event to mark it as being handled. By default, they're accepted when you re-implement the event, but it's clearer to be explicit. Call e->accept( ).
It's also recommended that if you handle any of the mouse events, you should handle all, with the possible exception of mouse double click.
Here's an example of keeping the mouse still, though on OS X there's an occasional flicker which appears to be due to how Qt is handling the events
class MyWidget : public QWidget
{
void mousePressEvent(QMouseEvent* e)
{
m_pos = e->globalPos();
m_lastPos = m_pos;
QWidget::mousePressEvent(e);
}
void mouseMoveEvent(QMouseEvent* e)
{
// Calculate relative zoom factor
// scaled down ( / 10 ) for image zooming
m_zoomFactor += ((float)e->globalPos().y() - m_lastPos.y()) / 10;
QCursor::setPos(m_pos);
m_lastPos = m_pos;
e->accept();
qDebug() << m_zoomFactor << endl;
}
void mouseReleaseEvent(QMouseEvent* e)
{
QWidget::mouseReleaseEvent(e);
}
private:
QPoint m_pos;
QPoint m_lastPos;
float m_zoomFactor = 0; // C++ 11 initialisation
};
If you're not bothered at keeping the mouse stationary, take out the QCursor::setPos call and this will still receive move events when the cursor is outside the widget, whilst the mouse button is held down.
However, it may be a better user experience hiding the cursor when zooming.
I would have a flag to disable the event with will be false by default.
inside the event check if flag is false, then perform the zoom operation, set flag to true and reset cursor.
then the event will be called again and the flag will be true, so you set flag to false and you will be ready to handle the next event.
You just have to make sure you dont have two or more calls to the mouse event firing from the actual mouse before receiving the event from the setCursor call.
Don't use event->pos() in mouse events, use QCursor::pos() intead and check if it changed. Like this:
void MyWidget::mousePressEvent(QMouseEvent *)
{
mPrevPos=QCursor::pos();
mMoving=false;
}
void MyWidget::mouseMoveEvent(QMouseEvent *)
{
auto cursorPos=QCursor::pos();
if(mPressedPos==cursorPos){
return;
}
if(!mMoving
&& (cursorPos-mPrevPos).manhattanLength()>QApplication::startDragDistance()){
mMoving=true;
}
if(mMoving){
auto diff=cursorPos-mPrevPos;
// move something using diff
QCursor::setPos(mPrevPos);
}
}
void MyWidget::mouseReleaseEvent(QMouseEvent *)
{
mMoving=false;
}
void MyWidget::leaveEvent(QEvent *)
{
mMoving=false;
}