I'm trying to accomplish the following with a Qt (C++) app, cross OS.
Upon running the program a new window pops up, a fullscreen QWidget. Now I want this to be see-through/transparent, so the user won't actually see it. On this 'layer' a user can drag his/her mouse to draw a (red) rectangle to select an area which is - when the mouse is released - taken a screenshot of.
The problem:
The issue lays in the whole transparent layer thing since this doesn't appear to work well cross OS. Because when I click on where the layer is, to invoke the mousePressEvent(), I click through it on to the window below it as if it isn't even there. On Ubuntu, however, I do not. I want the same effect on Windows, but thus far I got nothing...
(Making another GUI object appear, such as button, would only make the button a clickable part of the layer)
Tested on Ubuntu 11.04 32-bit and Windows 7 Home Premium 64-bit. (Trying to get around to a Mac one, would this issue be solved.)
So does anyone know how this would work?
I've included my code thus far. (Clearing out all my 100 other attempts.)
main.cpp
Here I set the translucentBackground, here I probably miss a setting or something is not configured right.
#include <QtGui/QApplication>
#include "widget.h"
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
Widget w;
//Fullscreen app
w.showFullScreen();
w.setAttribute(Qt::WA_NoSystemBackground);
w.setAttribute(Qt::WA_TranslucentBackground);
w.setMouseTracking(true);
w.setWindowFlags(Qt::Window | Qt::CustomizeWindowHint | Qt::WindowStaysOnTopHint | Qt::FramelessWindowHint);
//Show
w.show();
return a.exec();
}
widget.cpp
#include "widget.h"
#include "ui_widget.h"
#include "QDebug"
Widget::Widget(QWidget *parent) : QWidget(parent), ui(new Ui::Widget)
{
this->setWindowFlags(Qt::CustomizeWindowHint | Qt::WindowStaysOnTopHint | Qt::FramelessWindowHint);
QPalette palette(Widget::palette());
palette.setColor(backgroundRole(), Qt::white);
setPalette(palette);
this->clicked = false;
ui->setupUi(this);
}
Widget::~Widget()
{
delete ui;
}
void Widget::mousePressEvent ( QMouseEvent * event )
{
//Record start
if (event->button() == Qt::LeftButton){
x = event->globalX();
y = event->globalY();
this->clicked = true;
width = 0;
height = 0;
}
}
void Widget::mouseMoveEvent ( QMouseEvent * event )
{
if (this->clicked == true){
int x2 = event->globalX();
int y2 = event->globalY();
if(x < x2){
width = x2 - x;
}else{
width = x - x2;
//Resetting startpoint when dragging to the left side on your screen, copy from java so this doesn't actually works yet.
x = x - width-2;
}
if(y < y2){
height = y2 - y;
}else{
height = y - y2;
//Resetting startpoint when dragging to the left side on your screen, copy from java so this doesn't actually works yet.
y = y - height-2;
}
//Redraw rectangle
update();
qDebug("wa");
}
}
void Widget::mouseReleaseEvent ( QMouseEvent * event )
{
if (event->button() == Qt::LeftButton)
{
//Record end
qDebug("-------");
this->clicked = false;
}
}
void Widget::paintEvent(QPaintEvent *)
{
QPainter painter(this);
painter.setRenderHint(QPainter::Antialiasing);
painter.setPen(Qt::red);
painter.drawRect(x,y,width,height);
}
widget.h
#ifndef WIDGET_H
#define WIDGET_H
#include <QWidget>
#include <QtGui>
#include<QMouseEvent>
namespace Ui {
class Widget;
}
class Widget : public QWidget
{
Q_OBJECT
public:
explicit Widget(QWidget *parent = 0);
~Widget();
private:
Ui::Widget *ui;
bool clicked;
int x,y,width,height;
void mousePressEvent ( QMouseEvent * event );
void mouseMoveEvent ( QMouseEvent * event );
void mouseReleaseEvent ( QMouseEvent * event );
protected:
void paintEvent(QPaintEvent *);
};
#endif // WIDGET_H
Also, I think I've gone through every result Google will find on this subject, same as the API docs of Qt. I've seriously run out of options for this one. (I started this project in Java, but C++ with Qt seems to be, thus far, far less work.)
Any help would very much be appreciated!
This is a complete and total hack but it's the only way that I know of to do it. Take a screenshot of the screen and then use that as your widget's background, making sure that the right piece of the screen shows in the window, especially on resizes or moves, etc. At least it's cross platform.
However, it was also the way that KDE 3's fake transparency was implemented before such things like ARGB visuals and what not existed in X11.
Related
I'm trying to implement code that will zoom in an image as well as move the image towards the cursor, but not completely re-center on the cursor (similar to how Google Maps works, or exactly like how Picasa Photo Viewer used to work if anyone's seen that). The key is that it needs to zoom in the image, and have the same pixels that were under the mouse cursor, still be under it.
The sample code provided in the accepted answer of this question is close but does not completely solve the problem, as that was a question of moreso how to zoom in with the cursor and sidesteps the problem inherent in QGraphicsView.
The problem is: as seen in the sample code's comments in the header, it is not possible to perform this function if the view is bigger than the image. In other words, if you zoom out the image such that there's no scroll bars, then zoom in, the image will zoom in to the center, not to the mouse cursor; only by zooming in enough that there are horizontal and vertical scroll bars, will it zoom-in to the cursor and keep the same pixel underneath the cursor.
This seems to be a fundamental problem with QGraphicsView: QGraphicsView::setTransformationAnchor(QGraphicsView::AnchorUnderMouse) can achieve almost the exact same result, but the Qt docs report the same problem:
Note that the effect of this property is noticeable when only a part of the scene is visible (i.e., when there are scroll bars). Otherwise, if the whole scene fits in the view, QGraphicsScene uses the view alignment to position the scene in the view.
I suspect this is because both methods are scrolling the viewport, but if the image is too small compared to the viewport, there's no way to actually scroll it.
I suspect the solution may require abandoning scrolling and actually moving the image, but I can't figure out how to move the image such that the same pixel stays underneath the mouse cursor.
Here's some code that I've implemented and does not work:
Graphics_view_zoom.h:
#include <QObject>
#include <QGraphicsView>
/*!
* This class adds ability to zoom QGraphicsView using mouse wheel. The point under cursor
* remains motionless while it's possible.
*
* Note that it becomes not possible when the scene's
* size is not large enough comparing to the viewport size. QGraphicsView centers the picture
* when it's smaller than the view. And QGraphicsView's scrolls boundaries don't allow to
* put any picture point at any viewport position.
*
* When the user starts scrolling, this class remembers original scene position and
* keeps it until scrolling is completed. It's better than getting original scene position at
* each scrolling step because that approach leads to position errors due to before-mentioned
* positioning restrictions.
*
* When zommed using scroll, this class emits zoomed() signal.
*
* Usage:
*
* new Graphics_view_zoom(view);
*
* The object will be deleted automatically when the view is deleted.
*
* You can set keyboard modifiers used for zooming using set_modified(). Zooming will be
* performed only on exact match of modifiers combination. The default modifier is Ctrl.
*
* You can change zoom velocity by calling set_zoom_factor_base().
* Zoom coefficient is calculated as zoom_factor_base^angle_delta
* (see QWheelEvent::angleDelta).
* The default zoom factor base is 1.0015.
*/
class Graphics_view_zoom : public QObject {
Q_OBJECT
public:
Graphics_view_zoom(QGraphicsView* view);
void gentle_zoom(double factor);
void set_modifiers(Qt::KeyboardModifiers modifiers);
void set_zoom_factor_base(double value);
private:
QGraphicsView* _view;
Qt::KeyboardModifiers _modifiers;
double _zoom_factor_base;
QPointF target_scene_pos, target_viewport_pos;
bool eventFilter(QObject* object, QEvent* event);
signals:
void zoomed();
};
Graphics_view_zoom.cpp:
#include "Graphics_view_zoom.h"
#include <QMouseEvent>
#include <QApplication>
#include <QScrollBar>
#include <qmath.h>
Graphics_view_zoom::Graphics_view_zoom(QGraphicsView* view)
: QObject(view), _view(view)
{
_view->viewport()->installEventFilter(this);
_view->setMouseTracking(true);
_modifiers = Qt::ControlModifier;
_zoom_factor_base = 1.0015;
}
void Graphics_view_zoom::gentle_zoom(double factor) {
_view->scale(factor, factor);
_view->centerOn(target_scene_pos);
QPointF delta_viewport_pos = target_viewport_pos - QPointF(_view->viewport()->width() / 2.0,
_view->viewport()->height() / 2.0);
QPointF viewport_center = _view->mapFromScene(target_scene_pos) - delta_viewport_pos;
_view->centerOn(_view->mapToScene(viewport_center.toPoint()));
emit zoomed();
}
void Graphics_view_zoom::set_modifiers(Qt::KeyboardModifiers modifiers) {
_modifiers = modifiers;
}
void Graphics_view_zoom::set_zoom_factor_base(double value) {
_zoom_factor_base = value;
}
bool Graphics_view_zoom::eventFilter(QObject *object, QEvent *event) {
if (event->type() == QEvent::MouseMove) {
QMouseEvent* mouse_event = static_cast<QMouseEvent*>(event);
QPointF delta = target_viewport_pos - mouse_event->pos();
if (qAbs(delta.x()) > 5 || qAbs(delta.y()) > 5) {
target_viewport_pos = mouse_event->pos();
target_scene_pos = _view->mapToScene(mouse_event->pos());
}
} else if (event->type() == QEvent::Wheel) {
QWheelEvent* wheel_event = static_cast<QWheelEvent*>(event);
if (QApplication::keyboardModifiers() == _modifiers) {
if (wheel_event->orientation() == Qt::Vertical) {
double angle = wheel_event->angleDelta().y();
double factor = qPow(_zoom_factor_base, angle);
gentle_zoom(factor);
return true;
}
}
}
Q_UNUSED(object)
return false;
}
main.cpp
#include "mainwindow.h"
#include <QApplication>
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
MainWindow w;
w.show();
return a.exec();
}
mainwindow.h:
#pragma once
#include <QMainWindow>
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
MainWindow(QWidget *parent = nullptr);
};
mainwindow.cpp:
#include "mainwindow.h"
#include <QGraphicsScene>
#include <QGraphicsView>
#include <QPixmap>
#include <Graphics_view_zoom.h>
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
{
this->setFixedSize(1000,1000);
QGraphicsScene* s = new QGraphicsScene(this);
QGraphicsView* v = new QGraphicsView(this);
Graphics_view_zoom* z = new Graphics_view_zoom(v);
z->set_modifiers(Qt::NoModifier);
v->setScene(s);
v->setFixedSize(1000,1000);
QPixmap pix;
if(pix.load("C:\\full\\path\\to_an_image_file.jpg"))
{
s->addPixmap(pix);
}
}
I've also tried the fix found here but this also has the exact same problem: the pixel will only remain under the cursor if the image is zoomed in far enough that there are both vertical and horizontal scroll options available.
I have a "standard" Qt5 QWidgets application, with a MainWindow that includes a QGraphicsView in the mainwindow.ui created in QtCreator. That QGraphicsView has its scene set to a simple subclass of QGraphicsScene, which has a big rectangle in the background that is a subclass of QGraphicsRectItem which reimplements the mousePressEvent() and mouseReleaseEvent() handlers of the QGraphicsRectItem. Running on Ubuntu 18.04, which shouldn't matter, but just incase...
Everything is working, except... the 2nd and later times I press the left (or any) mouse button, the coordinates reported in the mousePressEvent's QGraphicsSceneMouseEvent buttonDownScenePos are "stale" - the same as the previous mouse click, not the new location where the mouse is when the new click happened. The mouseReleaseEvent reports coordinates as expected.
Is there any way to get the buttonDownScenePos of the mousePressEvent to stay current with the actual position of the mouse when clicked, instead of the previous mouse location?
I feel like I have dealt with a similar issue in the past which had something to do with double-click processing, that the event is reported before it knows if a double-click has happened or not. In this instance, double-click events are not important, but it would be nice to be able to respond to the single click as soon as it happens, instead of waiting for the release event.
Relevant code:
mainwindow.h
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
class Board;
class BoardScene;
#include <QMainWindow>
#include <QPointer>
#include "board.h"
#include "boardscene.h"
namespace Ui {
class MainWindow;
}
class MainWindow : public QMainWindow
{ Q_OBJECT
public:
explicit MainWindow(QWidget *parent = nullptr);
~MainWindow();
void drawBoard();
private:
Ui::MainWindow *ui;
QPointer<Board> board;
QPointer<BoardScene> boardScene;
};
#endif // MAINWINDOW_H
mainwindow.cpp
#include "mainwindow.h"
#include "ui_mainwindow.h"
MainWindow::MainWindow(QWidget *parent) :
QMainWindow(parent),
ui(new Ui::MainWindow)
{
ui->setupUi(this);
board = new Board( this );
boardScene = new BoardScene( board, this );
ui->boardView->setScene( boardScene );
ui->boardView->setDragMode( QGraphicsView::ScrollHandDrag );
ui->boardView->scale( 40.0, 40.0 );
drawBoard();
}
MainWindow::~MainWindow()
{
delete ui;
}
void MainWindow::drawBoard()
{ }
boardscene.h
#ifndef BOARDSCENE_H
#define BOARDSCENE_H
class Board;
#include <QGraphicsScene>
#include "board.h"
#include "boardrect.h"
class BoardScene : public QGraphicsScene
{ Q_OBJECT
public:
BoardScene( Board *pbp, QObject *parent = nullptr );
void drawGrid();
Board *bp;
QBrush backBrush,blackBrush,whiteBrush;
QPen linePen;
};
#endif // BOARDSCENE_H
boardscene.cpp
#include "boardscene.h"
#include <QGraphicsLineItem>
#include <QGraphicsRectItem>
BoardScene::BoardScene( Board *pbp, QObject *parent ) : QGraphicsScene ( parent )
{ bp = pbp;
backBrush = QBrush( QColor( 224,152, 64 ) );
blackBrush = QBrush( QColor( 0, 0, 0 ) );
whiteBrush = QBrush( QColor( 255,255,255 ) );
linePen = QPen ( QColor( 0, 0, 0 ) );
linePen.setWidth( 0 );
drawGrid();
}
void BoardScene::drawGrid()
{ QGraphicsLineItem *lip;
BoardRect *rip;
setBackgroundBrush( blackBrush );
rip = new BoardRect( QRectF( -2.0, -2.0, (qreal)(bp->Xsize +3), (qreal)(bp->Ysize + 3) ), nullptr );
rip->setBrush( backBrush );
rip->setPen( linePen );
addItem( rip );
for ( int x = 0; x < bp->Xsize; x++ )
{ lip = addLine( QLineF( (qreal)x, 0.0, (qreal)x, (qreal)(bp->Ysize - 1) ), linePen );
lip->setAcceptedMouseButtons( Qt::NoButton );
}
for ( int y = 0; y < bp->Ysize; y++ )
{ lip = addLine( QLineF( 0.0, (qreal)y, (qreal)(bp->Xsize - 1), (qreal)y ), linePen );
lip->setAcceptedMouseButtons( Qt::NoButton );
}
}
boardrect.h
#ifndef BOARDRECT_H
#define BOARDRECT_H
#include <QGraphicsRectItem>
#include <QGraphicsSceneMouseEvent>
class BoardRect : public QGraphicsRectItem
{
public:
BoardRect( const QRectF &rect, QGraphicsItem *parent = nullptr );
~BoardRect() {}
protected:
void mousePressEvent(QGraphicsSceneMouseEvent *event);
void mouseReleaseEvent(QGraphicsSceneMouseEvent *event);
};
#endif // BOARDRECT_H
boardrect.cpp
#include "boardrect.h"
BoardRect::BoardRect( const QRectF &rect, QGraphicsItem *parent ) : QGraphicsRectItem( rect, parent )
{}
void BoardRect::mousePressEvent(QGraphicsSceneMouseEvent *event)
{ QString msg = QString("press %1 %2").arg(event->buttonDownScenePos(event->button()).rx())
.arg(event->buttonDownScenePos(event->button()).ry());
qDebug( qPrintable( msg ) );
QGraphicsRectItem::mousePressEvent(event);
event->accept();
}
void BoardRect::mouseReleaseEvent(QGraphicsSceneMouseEvent *event)
{ QString msg = QString("release %1 %2").arg(event->buttonDownScenePos(event->button()).rx())
.arg(event->buttonDownScenePos(event->button()).ry());
qDebug( qPrintable( msg ) );
QGraphicsRectItem::mousePressEvent(event);
event->accept();
}
On the first click after running, the reported coordinates agree well with the location on the grid where the mouse was clicked, both for press and release - they both show where the button went down.
However, on the 2nd and later clicks, the mousePressEvent reports the same coordinates as the previous mousePress and Release events, while the mouseReleaseEvent reports the coordinates where the mouse button "went down" in the current event.
One final bit of weirdness: when clicking left, then right, then left again, the coordinate reported by mousePressEvent for the 2nd left click is the previous left click coordinate, skipping over the right click coordinate to go back to where the mouse button went down on the last left click.
Any ideas? Thanks.
I have an app using the same components: a qgraphicsview and a qgraphicsscene composed by some number of qgraphicsitems. It is a virtual MIDI piano keyboard just in case you want to take a look to the code. In my case, all the qgraphicsitems (the piano keys) have setAcceptedMouseButtons(Qt::NoButton), and the mouse events are handled at the scene level instead of the graphics item. I've never observed a problem like yours.
QGraphicsSceneMouseEvent::buttonDownPos(Qt::MouseButton button)
Returns the mouse cursor position in item coordinates where the
specified button was clicked.
It returns the coordinates from the graphics item you are clicking on (as the documentation says). Maybe you just click in the same spot?
If you want the scene position, just use mapToScene or QGraphicsSceneMouseEvent::buttonDownScenePos(Qt::MouseButton button) or event->scenePos().
PS.
and use QPointF::x() and QPointF::y() instead of rx() and ry(). You don't need a reference and manipulate the position.
I have derived the class of QTabBar to implement "+" (new tab button) button using QToolButton (similar to google chrome). However, it is working in my Linux machine but doesn't work in my windows machine. By not working I mean QToolButton is not visible in my windows machine but it is visible in my Linux machine (Ubuntu). I am not able to debug it further as I have tried few experiments to understand the reason but it didn't work.
My Source file:
#include "tabbar.h"
TabBar::TabBar(QWidget *parent) : QTabBar(parent)
{
new_button_ = new QToolButton(this);
new_button_->setObjectName(QStringLiteral("AddButton"));
new_button_->setText("+");
new_button_->setFixedSize(QSize(20, 20));
connect(new_button_, SIGNAL(released()), this, SLOT(emit_new()));
movePlusButton();
}
QSize TabBar::sizeHint(void) const
{
QSize old = QTabBar::sizeHint();
return QSize(old.width() + 45, old.height());
}
void TabBar::emit_new(void)
{
emit newClicked();
}
void TabBar::movePlusButton(void)
{
quint64 totalWidth = 0;
for (long i=0; i < count(); i++)
totalWidth += tabRect(i).width();
quint64 h = geometry().top();
quint64 tab_height = height();
quint64 w = width();
if (totalWidth > w)
new_button_->move(w-40, tab_height - 30);
else
new_button_->move(totalWidth + 5, tab_height - 30);
}
void TabBar::resizeEvent(QResizeEvent *p_evt)
{
QTabBar::resizeEvent(p_evt);
movePlusButton();
}
void TabBar::tabLayoutChange(void)
{
QTabBar::tabLayoutChange();
movePlusButton();
}
My Header File:
#ifndef TABBAR_H
#define TABBAR_H
#include <QObject>
#include <QToolButton>
#include <QTabBar>
#include <QResizeEvent>
#include <QLabel>
class TabBar : public QTabBar {
Q_OBJECT
public:
TabBar(QWidget *parent=nullptr);
~TabBar() { }
void movePlusButton(void);
void resizeEvent(QResizeEvent *p_evt) override;
void tabLayoutChange(void) override;
QSize sizeHint(void) const override;
private slots:
void emit_new(void);
signals:
void newClicked(void);
private:
QToolButton *new_button_;
};
#endif // TABBAR_H
EDIT:
I have tried few more experiments and got to know QToolButton is hiding behind region next to Tab bars. Please refer the screenshot.
Apparently, your application uses a stylesheet or something to customize the display and this is incompatible with your TabBar class.
Downloaded your code, wrote a simple main:
#include <QApplication>
#include <QMainWindow>
#include "tabbar.h"
int main( int argc, char* argv[] )
{
QApplication app(argc, argv);
QMainWindow wnd;
TabBar* tabBar = new TabBar(&wnd);
wnd.setCentralWidget( tabBar );
tabBar->addTab( "Foo" );
wnd.show();
return app.exec();
}
compiled and executed on Windows and got that (tested classic and aero style):
So apparently your code is fine. However, if you customized the QTabBar rendering through a stylesheet (what I suspect when I see how it looks in your GUI), you may need to adapt yourcode (probably movePlusButton as it has some values hardcoded that may be incompatible with the current display style):
if (totalWidth > w)
new_button_->move(w-40, tab_height - 30);
else
new_button_->move(totalWidth + 5, tab_height - 30);
I have QGraphicsView, QGraphicsScene and QGraphicsRectItem.
QGraphicsRectItem in the QGraphicsScene and the last one in the QGraphicsView. I want to move QGraphicsRectItem with mouse by clicking on it only! But in my implementation it moves if I click on any position on my QGraphicsScene. Whether it is my QGraphicsRectItem or some other place. And the second issue. The item has been moved to the center of the scene. Clicking on it again it starts to move from the home location.
void Steer::mousePressEvent(QMouseEvent *click)
{
offset = click->pos();
}
void Steer::mouseMoveEvent(QMouseEvent *event)
{
if(event->buttons() & Qt::LeftButton)
{
p1->setPos(event->localPos() - offset); //p1 movable item
}
}
What do I do wrong?
UPDATE:
main.cpp
#include "widget.h"
#include <QApplication>
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
Steer w;
w.show();
return a.exec();
}
widget.h
#ifndef STEER_H
#define STEER_H
#include <QGraphicsView>
#include <QGraphicsScene>
#include <QMouseEvent>
#include <QPoint>
#include <QGraphicsRectItem>
class Steer : public QGraphicsView
{
Q_OBJECT
private:
QGraphicsScene *scene;
QGraphicsRectItem *p1;
QPoint offset;
public:
explicit Steer(QGraphicsView *parent = 0);
~Steer(){}
public slots:
void mousePressEvent(QMouseEvent * click);
void mouseMoveEvent(QMouseEvent * event);
};
#endif // STEER_H
widget.cpp
#include "widget.h"
#include <QBrush>
Steer::Steer(QGraphicsView *parent)
: QGraphicsView(parent)
{
scene = new QGraphicsScene;
p1 = new QGraphicsRectItem;
//add player
p1->setRect(760, 160, 10, 80);
//add scene
scene->setSceneRect(0, 0, 800, 400);
//add moveable item
scene->addItem(p1);
//set scene
this->setScene(scene);
this->show();
}
void Steer::mousePressEvent(QMouseEvent *click)
{
offset = click->pos();
}
void Steer::mouseMoveEvent(QMouseEvent *event)
{
if(event->buttons() & Qt::LeftButton)
{
p1->setPos(event->localPos() - offset);
}
}
I'd try a different approach that is a little easier to understand:
#include <QtWidgets>
class Steer : public QGraphicsView
{
public:
Steer()
{
scene = new QGraphicsScene;
p1 = new QGraphicsRectItem;
//add player
p1->setRect(0, 0, 10, 80);
p1->setX(760);
p1->setY(160);
//add scene
scene->setSceneRect(0, 0, 800, 400);
//add moveable item
scene->addItem(p1);
//set scene
this->setScene(scene);
this->show();
}
protected:
void mousePressEvent(QMouseEvent * click)
{
if (p1->contains(p1->mapFromScene(click->localPos()))) {
lastMousePos = click->pos();
} else {
lastMousePos = QPoint(-1, -1);
}
}
void mouseMoveEvent(QMouseEvent * event)
{
if(!(event->buttons() & Qt::LeftButton)) {
return;
}
if (lastMousePos == QPoint(-1, -1)) {
return;
}
p1->setPos(p1->pos() + (event->localPos() - lastMousePos));
lastMousePos = event->pos();
}
private:
QGraphicsScene *scene;
QGraphicsRectItem *p1;
QPoint lastMousePos;
};
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
Steer w;
w.show();
return a.exec();
}
There are a few things to point out here:
Don't use setRect() to set the position of a QGraphicsRectItem. It doesn't work the way you think it might. Always use setPos() to change the position of an item.
Rename offset to something more descriptive. I chose lastMousePos. Instead of just updating it once when the mouse is pressed, also update it whenever the mouse is moved. Then, it's simply a matter of getting the difference between the two points and adding that to the position of the item.
Check if the mouse is actually over the item before reacting to move events. If the mouse isn't over the item, you need some way of knowing that, hence the QPoint(-1, -1). You may want to use a separate boolean flag for this purpose. This solves the problem that you saw, where it was possible to click anywhere in the scene to get the item to move.
Also, note the mapFromScene() call: the contains() function works in local coordinates, so we must map the mouse position which is in scene coordinates before testing if it's over the item.
The event functions are not slots, they're virtual, protected functions.
You could also consider handling these events in the items themselves. You don't need to do it from within QGraphicsView, especially if you have more than one of these items that need to be dragged with the mouse.
I have a Windows & Mac program that switches into full-screen mode on multiple monitors. In Qt 4, it seems (I can't find explicit documentation on how to do this) like the 'correct' way to go about this is by creating N QMainWindow's for the N monitors on the machine, calling QWidget::move() to the N monitor's top-left x,y coordinates, and then calling QWidget::setWindowState(Qt::WindowFullScreen). I don't know whether this is The Right Thing To Do - again, I can't find any documentation or examples anywhere that do this in Qt.
This seems to be 'broken' (if it was ever the Right Thing To Do in the first place) in Qt 5.4.1, especially on Windows 7. I'm still trying to isolate the problem, but it seems like the QMainWindows are dropping out of full-screen mode.
Just so I'm clear about this, what is the right way to do this? I found this forum post which seems to suggest that I should be setting the QScreen on the underlying QWindow objects that are held by the QMainWindows, but this doesn't seem to work in my tests. Here's an example program that I wrote:
app.h:
#include <vector>
#include <QObject>
class QMainWindow;
class app : public QObject
{
Q_OBJECT
public:
int run(int argc, char** argv);
public slots:
void add_window();
void remove_window();
void windows_go_to_screens();
void windows_go_to_screens_old();
void windows_go_to_primary_screen();
void windows_fullscreen();
void windows_nonfullscreen();
private:
QMainWindow * create_window(const char * id);
void init_menus( QMainWindow * w );
std::vector<QMainWindow *> m_windows;
};
app.cpp:
#include <assert.h>
#include <algorithm>
#include <iostream>
#include <vector>
#include <QObject>
#include <QMainWindow>
#include <QApplication>
#include <QMenubar>
#include <QAction>
#include <QScreen>
#include <QWindow>
#include <QLayout>
#include <QLabel>
#include <QStyle>
#include "app.h"
using namespace std;
int app::run(int argc, char** argv)
{
QApplication a(argc, argv);
QMainWindow * w = create_window("0");
m_windows.push_back(w);
w->show();
return a.exec();
}
void app::add_window()
{
static const char * nums[] = {"0", "1", "2", "3", "4", "5", "6", "7", "8", "9"};
m_windows.push_back(create_window(nums[m_windows.size()]));
m_windows.back()->show();
}
void app::remove_window()
{
if (m_windows.size() > 1)
{
QMainWindow * w = m_windows.back();
m_windows.pop_back();
w->close();
w->deleteLater();
}
}
void app::windows_go_to_screens()
{
QList<QScreen*> screens = qApp->screens();
for (unsigned i = 0; i < std::min((unsigned)m_windows.size(), (unsigned)screens.size()); ++i)
{
QMainWindow * mw = m_windows[i];
QScreen * screen = screens[i];
QWindow * wh = mw->windowHandle();
wh->setScreen(screen);
}
}
void app::windows_go_to_screens_old()
{
QList<QScreen*> screens = qApp->screens();
for (unsigned i = 0; i < std::min((unsigned)m_windows.size(), (unsigned)screens.size()); ++i)
{
QMainWindow * mw = m_windows[i];
QScreen * screen = screens[i];
mw->move(screen->geometry().left(), screen->geometry().top());
}
}
void app::windows_go_to_primary_screen()
{
QList<QScreen*> screens = qApp->screens();
for (unsigned i = 0; i < std::min((unsigned)m_windows.size(), (unsigned)screens.size()); ++i)
{
QMainWindow * mw = m_windows[i];
QScreen * screen = screens[0];
QWindow * wh = mw->windowHandle();
wh->setScreen(screen);
}
}
void app::windows_fullscreen()
{
for (unsigned i = 0; i < m_windows.size(); ++i)
{
QMainWindow * mw = m_windows[i];
mw->showFullScreen();
}
}
void app::windows_nonfullscreen()
{
for (unsigned i = 0; i < m_windows.size(); ++i)
{
QMainWindow * mw = m_windows[i];
mw->showNormal();
}
}
QMainWindow * app::create_window(const char * id)
{
QMainWindow * w = new QMainWindow(NULL);
init_menus(w);
QWidget * cw = new QWidget(w);
w->setCentralWidget(cw);
QHBoxLayout * l = new QHBoxLayout(cw);
cw->setLayout(l);
QLabel * lab = new QLabel(id, cw);
QPalette pal(lab->palette());
pal.setColor(QPalette::Background, Qt::red);
lab->setAutoFillBackground(true);
lab->setPalette(pal);
lab->setScaledContents(true);
lab->setAlignment(Qt::AlignCenter);
l->addWidget( lab );
return w;
}
void app::init_menus( QMainWindow * w )
{
QMenuBar * menubar = w->menuBar();
QMenu * view_menu = new QMenu(tr("View"), w);
view_menu->addAction("Add Window", this, SLOT(add_window()));
view_menu->addAction("Remove Window", this, SLOT(remove_window()));
view_menu->addAction("Windows Go To Screens", this, SLOT(windows_go_to_screens()));
view_menu->addAction("Windows Go To Screens (old method)", this, SLOT(windows_go_to_screens_old()));
view_menu->addAction("Windows Go To Primary Screen", this, SLOT(windows_go_to_primary_screen()));
view_menu->addAction("Windows Fullscreen", this, SLOT(windows_fullscreen()));
view_menu->addAction("Windows Non-Fullscreen", this, SLOT(windows_nonfullscreen()));
menubar->addMenu(view_menu);
}
main.cpp:
#include "app.h"
int main(int argc, char** argv)
{
app a;
return a.run(argc, argv);
}
When I run this program on OS X, the "Windows Go To Screens" function does nothing - none of the windows move. Neither does the "Windows Go To Primary Screen" (poorly named - should be 0 screen?). Creating more than N windows on an N window Mac is interesting - in that case calling "Windows Fullscreen" several times will actually switch the QMainWindows into fullscreen mode one at a time?!
Even more interesting is what happens on a multi-monitor OS X machine when you do this: "Add Window" until you have as many windows as displays. "Windows Go To Screens (old method)" will send each window to the top-left of each monitor. "Windows Fullscreen" will make all windows go full-screen on all monitors. "Remove Window" until you have only 1 window left. Then "Windows Non-FullScreen", and you'll get an interesting surprise. Go into Mission Control to see what's going on.
Can anyone tell me what the RIGHT way of doing this is? I've looked through the Qt5 examples - there's the player application that seems to be thoroughly broken (it can play a video in full-screen mode once, and then subsequent plays are in a separate desktop window), the sub game only maximizes to a single display, and none of the other examples seem to utilize full-screen mode, and certainly not on multiple monitors.
One way of doing it in Qt5 is to use QWindow::setScreen to set the screen on which the window should be shown. QWidget has a windowHandle() that returns the pointer to the QWindow. So you can get that pointer for each window and set a different screen.
Here is how to show your widget in the last screen in full-screen mode :
QWidget * widget = new QWidget();
widget->show();
widget->windowHandle()->setScreen(qApp->screens().last());
widget->showFullScreen();
Or in the second screen :
QWidget * widget = new QWidget();
widget->show();
widget->windowHandle()->setScreen(qApp->screens()[1]);
widget->showFullScreen();