Qt Troubles with painting selection box - c++

I'm having troubles forcing a repaint/update of my Qt Widget (it extends the QGraphicsView class). What I want is a rectangular selection box to be drawn which will highlight the target selection area as the user presses and moves the mouse.
The basic workflow:
MousePressEvent sets the making_selection_box flag, and stores the start point (working).
MouseMoveEvent checks to see if the display needs to be updated. If it does, it tries to do so (not working).
MouseReleaseEvent handles gets the resulting selection box and handles it accordingly. making_selection_box is reset. Screen should be updated to remove the selection box artifact (not working).
The overrided mouseMoveEvent:
void QSchematic::mouseMoveEvent(QMouseEvent *event)
{
if(making_selection_box)
{
// get selection box
qDebug() << "updating selection box";
curr_selection_end = event->pos();
repaint(box(drag_select_start, curr_selection_end));
}
// propogate event
QGraphicsView::mouseMoveEvent(event);
}
My overrided paintEvent:
void QSchematic::paintEvent(QPaintEvent *event)
{
qDebug() << "paintEvent";
if(making_selection_box)
{
qDebug() << "drawing selection box";
QPainter painter(viewport());
painter.setPen(Qt::black);
painter.drawRect(box(drag_select_start, curr_selection_end));
painter.end();
}
// propogate event
QGraphicsView::paintEvent(event);
}
Box is just a small helper function I wrote to create the correct QRect for different selection box start/end points.
static QRect box(const QPoint& p1, const QPoint &p2)
{
int min_x = p1.x();
int min_y = p1.y();
int max_x = p2.x();
int max_y = p2.y();
if(max_x < min_x)
{
max_x = min_x;
min_x = p2.x();
}
if(max_y < min_x)
{
max_y = min_y;
min_y = p2.y();
}
return QRect(min_x, min_y, max_x - min_x, max_y - min_y);
}
I've verified that mouseMoveEvent is being triggered correctly when the user presses a button and moves the mouse around.
I've also verified that paintEvent is being called by the system when I perform various standard operations such as resize the window, minimize/maximize it, etc.
I've verified that the method I'm using to paint to my widget will work correctly with other paintEvent triggers, I just can't manage to trigger a repaint in my code.
I've also tried forcing the update by using the update() method instead of repaint(), but no luck.
As a side note, am I going about creating this selection box functionality the wrong/hard way? Is there a better way to get a selection box without having to manually implement the mouse listeners and painting code?
I'm testing with Qt 4.8.4 on Windows 7 x64, using the Visual Studio 2010 MSVC compiler.

After looking through the QGraphicsScene API I found an easy workaround for having to manually manage the selection box: The drag mode needs to be set to RubberBandDrag.
edit:
To further expand my answer which allows painting on the QGraphicsView for other purposes, it's the viewport which needs to receive the update/redraw, not my QGraphicsView object.
void QSchematic::mouseMoveEvent(QMouseEvent *event)
{
if(making_selection_box)
{
// get selection box
qDebug() << "updating selection box";
curr_selection_end = event->pos();
viewport()->repaint(box(drag_select_start, curr_selection_end));
}
// propogate event
QGraphicsView::mouseMoveEvent(event);
}

Related

How to disable scrolling in QGraphicsView?

I am trying to implement mouse wheel zoom in/out. It works BUT when I zoom in/out, the image is getting smaller AND scrolls up/down at the same time with zoom function. Look like events exist at the same time and work together.
I cannot find how to disable scrolling with mouse wheel. May be there is a way to make possible scrolling only with mouse cursor (by clicking on scrollbar).
I was overriding the main method of mouse wheel but it was causing the effect I wrote above
void MainWindow::wheelEvent( QWheelEvent* event )
Solved by using event filter. The code below provides zoom in/out with holding ctrl button.
bool MainWindow::eventFilter(QObject *obj, QEvent *event)
{
if (event->type() == QEvent::GraphicsSceneWheel)
{
ui->GV_image->setTransformationAnchor(QGraphicsView::AnchorUnderMouse);
double scaleFactor = 1.15;
bool ok = QApplication::keyboardModifiers() & Qt::ControlModifier;
if (ok)
{
QGraphicsSceneWheelEvent *scrollevent = static_cast<QGraphicsSceneWheelEvent *>(event);
if (scrollevent->delta() > 0)
{
ui->GV_image->scale(scaleFactor, scaleFactor);
}
else
{
ui->GV_image->scale(1/scaleFactor, 1/scaleFactor);
}
}
event->accept();
return true;
}
return false;
}
Put this line into your constructor or other your init function
QGraphicsView *GV_image;
...
ui->GV_image->scene()->installEventFilter(this);

not calling mousemove event on widgets and graphics view

I have created a set of wizard pages inherited from qwizardpage. in one of my wizard page i have a graphics view and in that using graphics scene and a qrect a rectangle has been created and now i am trying to catch the callback event of mouse move when the mouse is in the graphicsview area, but outside this area only call back is happening. But mouse press event is functioning properly. could anyone suggest me any solution.
WizardPage::WizardPage(QWidget* parent)
{
m_pScene = new QGraphicsScene(this);
graphicsView->setScene(m_pScene);
m_pRect = new QRect(-25, 25, 100, 40);
m_pScene->addRect(*m_pRect);
setMouseTracking(true);
}
void EgoLeverWizardPage::mouseMoveEvent(QMouseEvent *event)
{
qDebug() << "move";
}

Programmatically invoke Snap/Aero maximize

Is there a way to programmatically invoke the Aera maximize effect using C or C++ for a specific window/window ID?
For example:
or
(source: thebuzzmedia.com)
I am using a border-less Qt window and Qt has an API for getting the window ID. I want to programmatically trigger the windows effects without the known triggers.
I don't want to talk about every single detail involved in achieving this effect, not only there's a lot that goes on but you also mentioned you understand the logic to place the windows at their specific locations. In this answer I'll address what I believe are the 2 main challenges:
How to receive and handle a maximize event?
How to create an approximation of the aero snap effect?
In order to answer the first question, we must analyze which event handlers are triggered when the window is maximized:
void resizeEvent(QResizeEvent* evt); // Invoked first,
void paintEvent(QPaintEvent* event); // then second,
void changeEvent(QEvent* evt); // and at last.
A Qt application is first notified of a resizeEvent(), which is followed by a paintEvent() to draw the window (or widget), and only after everything has been displayed, changeEvent() is invoked to let you know the widget was maximized (maybe it's a little bit late to receive such notification, I don't know).
Of all these, the only one we care about is resizeEvent(). This event handler informs the new window/widget size that can be used for comparison with the desktop size, thus allowing us to know if the event was actually a maximize request. Once we identify a maximize request, we can figure out whether the application should be maximized (and anchored) to right, left or to the center of the screen.
This would be the time to create the aero snap widget and place it on the screen as a visual clue to the user.
To answer the second question, I don't think is possible to call the native Windows API and ask it politely to perform this effect on your window. The only other logical choice is to write a code that approximates this effect ourselves.
The visual appearance can be replicated by drawing a transparent window with a shadow-ish border. The approach demonstrated in the source code below, creates and customizes a QWidget to make it behave and look like a aero snap window:
It's not the most beautiful thing in the world, I know. This demo creates a regular window for the user to interact with, and once it's maximized, it places itself to the left of the screen. To the right size of the screen it displays something that resembles an aero snap window (shown above).
The idea behind the aero snap widget is very simple: a QWidget with transparent background and a custom painting procedure. In other words, it's a transparent window which draws a rounded rectangle with a shadow and that's it.
To make it a bit more realistic, you should add some animation to resize the widget little by little. A for loop might do the trick, but if you need something fancy you'll end up using timers. If you take a look here, you can see the quickest & dirtiest method to perform animation with Qt in action, and better ways to deal with animation. However, for simple tasks like this, stick with frame-based animation.
main.cpp:
#include "window.h"
#include <QApplication>
int main(int argc, char* argv[])
{
QApplication app(argc, argv);
Window window;
window.show();
return app.exec();
}
window.h:
#pragma once
#include "snapwindow.h"
#include <QMainWindow>
#include <QEvent>
class Window : public QMainWindow
{
public:
Window();
void resizeEvent(QResizeEvent* evt);
//void paintEvent(QPaintEvent* event);
void changeEvent(QEvent* evt);
private:
SnapWindow* _sw;
};
window.cpp:
#include "window.h"
#include "snapwindow.h"
#include <QDebug>
#include <QWindowStateChangeEvent>
#include <QApplication>
#include <QDesktopWidget>
Window::Window()
{
setWindowTitle("AeroSnap");
resize(300, 300);
_sw = new SnapWindow(this);
_sw->hide();
}
void Window::changeEvent(QEvent* evt)
{
if (evt->type() == QEvent::WindowStateChange)
{
QWindowStateChangeEvent* event = static_cast<QWindowStateChangeEvent*>(evt);
if (event->oldState() == Qt::WindowNoState &&
windowState() == Qt::WindowMaximized)
{
qDebug() << "changeEvent: window is now maximized!";
}
}
}
// resizeEvent is triggered before window_maximized event
void Window::resizeEvent(QResizeEvent* evt)
{
qDebug() << "resizeEvent: request to resize window to: " << evt->size();
QSize desktop_sz = QApplication::desktop()->size();
//qDebug() << "resizeEvent: desktop sz " << desktop_sz.width() << "x" << desktop_sz.height();
// Apparently, the maximum size a window can have in my system (1920x1080)
// is actually 1920x990. I suspect this happens because the taskbar has 90px of height:
desktop_sz.setHeight(desktop_sz.height() - 90);
// If this not a request to maximize the window, don't do anything crazy.
if (desktop_sz.width() != evt->size().width() ||
desktop_sz.height() != evt->size().height())
return;
// Alright, now we known it's a maximize request:
qDebug() << "resizeEvent: maximize this window to the left";
// so we update the window geometry (i.e. size and position)
// to what we think it's appropriate: half width to the left
int new_width = evt->size().width();
int new_height = evt->size().height();
int x_offset = 10;
setGeometry(x_offset, 45, new_width/2, new_height-45); // y 45 and height -45 are due to the 90px problem
/* Draw aero snap widget */
_sw->setGeometry(new_width/2-x_offset, 0, new_width/2, new_height);
_sw->show();
// paintEvent() will be called automatically after this method ends,
// and will draw this window with the appropriate geometry.
}
snapwindow.h:
#pragma once
#include <QWidget>
class SnapWindow : public QWidget
{
public:
SnapWindow(QWidget* parent = 0);
void paintEvent(QPaintEvent *event);
};
snapwindow.cpp:
#include "snapwindow.h"
#include <QPainter>
#include <QGraphicsDropShadowEffect>
SnapWindow::SnapWindow(QWidget* parent)
: QWidget(parent)
{
// Set this widget as top-level (i.e. owned by user)
setParent(0);
/* Behold: the magic of creating transparent windows */
setWindowFlags(Qt::Widget | Qt::FramelessWindowHint);
setStyleSheet("background:transparent;");
setAttribute(Qt::WA_NoSystemBackground, true); // speed up drawing by removing unnecessary background initialization
setAttribute(Qt::WA_TranslucentBackground);
//setAutoFillBackground(true);
/* Use Qt tricks to paint stuff with shadows */
QGraphicsDropShadowEffect* effect = new QGraphicsDropShadowEffect();
effect->setBlurRadius(12);
effect->setOffset(0);
effect->setColor(QColor(0, 0, 0, 255));
setGraphicsEffect(effect);
}
void SnapWindow::paintEvent(QPaintEvent *event)
{
QWidget::paintEvent(event);
/* Lazy way of painting a shadow */
QPainter painter(this);
QPen pen(QColor(180, 180, 180, 200));
pen.setWidth(3);
painter.setPen(pen);
// Offset 6 and 9 pixels so the shadow shows up properly
painter.drawRoundedRect(QRect(6, 6, (width()-1)-9, (height()-1)-9), 18, 18);
}
This is just a quick demo to point you to the right direction. It is by no means a complete implementation of the effect you are looking for.
Maybe it is not what you need, but this effect is just resizing and moving window then try use Qt methods to do this.
bool left = false;
QSize size = QApplication::desktop()->size();//resolution of current screen
if(left)
{//left side
this->setGeometry(0, 0, size.width()/2, size.height());//(maybe need do some changes)
}
else
{//right side
this->setGeometry(size.width()/2, 0, size.width()/2, size.height());
}
With QApplication::desktop() it will work properly on screen with different resolutions.
In web I found something similar in winapi, but it didn't work properly:
HWND act = GetForegroundWindow();
PostMessage((HWND)act,WM_NCLBUTTONDBLCLK, HTTOP, 0);
The best way
Combine this approaches. For example:
HWND act = GetForegroundWindow();
bool left = false;
QSize size = QApplication::desktop()->size();
if(left)
{
this->move(0,0);
PostMessage((HWND)act,WM_NCLBUTTONDBLCLK, HTTOP, 0);
this->resize(size.width()/2,QApplication::desktop()->height());
}
else
{
this->move(size.width()/2,0);
PostMessage((HWND)act,WM_NCLBUTTONDBLCLK, HTTOP, 0);
this->resize(size.width()/2,QApplication::desktop()->height());
}
Why? Because move() regulate left and right sides, but PostMessage (winapi) set window's height properly on every screen (window will not locate lower then taskbar, as in your example)
EDIT
I changed code a little and now it is better. Yes, it is resizing again, but now it hasn't winapi code (PostMessage etc), so Photoshop doesn't catch it, there is one interesting method in Qt which called availableGeometry. It return normal height of screen which we need, with this method borderless windows perfectly simulates Aero Snap effects in different directions. It is works, maybe don't so good, but as I can see, there isn't API for Aero effects. Maybe this approach will be normal for yoo.
There is Aero Peek in Qt : http://qt-project.org/doc/qt-5/qtwinextras-overview.html , but it is can't solve this problem too.
Code:
bool left = true;
bool upper = true;
if(upper)
{
QRect rect = QApplication::desktop()->availableGeometry(-1);
this->setGeometry(rect);
}
else if(left)
{
QRect rect = QApplication::desktop()->availableGeometry(-1);
rect.setWidth(rect.width()/2);
this->setGeometry(rect);
}
else
{
QRect rect = QApplication::desktop()->availableGeometry(-1);
int half = rect.width()/2;
rect.setX(half);
rect.setWidth(half);
this->setGeometry(rect);
}
Try it with frameless window! You should choose one direction or let user choose it.

Weird control boundaries detection on QGraphicsProxyWidget

Hello in my application i'm making a QGraphicsView and set the scene on it:
QGraphicsProxyWidget *rotateItemIcon;
HoverFilter *hv = new HoverFilter(); // my hover filter class
connect(hv,SIGNAL(SignalHover(QObject*)),this,SLOT(ObjectHover(QObject*)));
connect(hv,SIGNAL(SignalHoverLeave(QObject*)),this,SLOT(ObjectHoverLeave(QObject*)));
ui->TestIcon->installEventFilter(hv);
...
scene = new QGraphicsScene(this);
scene->setSceneRect(0, 0, 661, 255);
ui->TestIcon->setParent(NULL);
rotateItemIcon = scene->addWidget(ui->TestIcon); // here i add my control to the scene and receive QGraphicsProxyWidget object
rotateItemIcon->setTransformOriginPoint(ui->TestIcon->width()/2,
ui->TestIcon->height()/2);
ui->graphicsViewFive->setScene(scene); //QGraphicsView on my form
ui->graphicsViewFive->show();
my HoverFilter.cpp
#include "hoverfilter.h"
#include "QDebug"
HoverFilter::HoverFilter()
{
}
bool HoverFilter::eventFilter( QObject *dist, QEvent *event )
{
if( event->type() == QEvent::Enter )
{
emit SignalHover(dist);
return true;
}
if( event->type() == QEvent::Leave )
{
emit SignalHoverLeave(dist);
return true;
}
return false;
}
rotateItemIcon is my QGraphicsProxyWidget and the problem is that it has weird boundaries, i need to implement some animation on hover of my control TestIcon, (i done that using event filter) mouse enter and mouse leave fires when i drag my mouse on a random places, not only on my TestIcon control. If do not add my control to the QGraphicsScene hover detection works fine, so i assume this is a scene/proxywidget problem. Is there a way i can set size or boundaries to QGraphicsProxyWidget to stop that?
I'm not sure if I completely understand your question, but it sounds like you're have a widget placed in a scene via a QGraphicsProxyWidget and when you try to detect if your mouse is over the widget you want it to animate.
By 'weird boundaries' I assume you mean the extents to which the proxy widget is accepting the mouse as being over the widget. If so, I suggest either calling setGeometry on the QGraphicsProxyWidget to set its position and dimensions, or inherit from QGraphicsProxyWidget and implement the boundingRect function to define the area of the proxy widget.

Click event for QGraphicsView Qt

I have made a GUI in Qt that is basically a widget with a QGraphicsView on it i have a function:
void GUI::mousePressEvent(QMouseEvent *event)
{
if(event->button() == Qt::LeftButton)
{
QPointF mousePoint = ui->graphicsView->mapToScene(event->pos());
qDebug() << mousePoint;
}
}
which links to a public slot:
void mousePressEvent(QMouseEvent *event);
this shows me on the console the x,y coordinate of where i have clicked, however currently this is working on the entire widget and i ideally would like x,y(0,0) to be the top left of the QGraphicsView instead of the top left of the entire widget. does anyone have any idea how to make this work, i thought from my code that this is what it was doing but it turns out this is not so, iv had a look around for a while now but im coming up with nothing
any help would be really appreciated thanks.
Reimplement the mousePressEvent(QMouseEvent *event) of the QGraphicsView not your widget would be the 'proper' way of doing it. Otherwise you can hack it with:
// Detect if the click is in the view.
QPoint remapped = ui->graphicsView->mapFromParent( event->pos() );
if ( ui->graphicsView->rect().contains( remapped ) )
{
QPointF mousePoint = ui->graphicsView->mapToScene( remapped );
}
This assumes that the widget is the parent of the QGraphicsView.