I wrote this post, because I have a problem with selection area.
If you click on your windows desktop, and drag mouse, you will see selection area. I'm trying to achieve generally similar thing.
Do you have any ideas, how to achieve this?
Thanks for any suggestion.
You can use QRubberBand. Here is an example from the Qt documentation when you want to implement it in your widget :
void Widget::mousePressEvent(QMouseEvent *event)
{
origin = event->pos();
if (!rubberBand)
rubberBand = new QRubberBand(QRubberBand::Rectangle, this);
rubberBand->setGeometry(QRect(origin, QSize()));
rubberBand->show();
}
void Widget::mouseMoveEvent(QMouseEvent *event)
{
rubberBand->setGeometry(QRect(origin, event->pos()).normalized());
}
void Widget::mouseReleaseEvent(QMouseEvent *event)
{
rubberBand->hide();
// determine selection, for example using QRect::intersects()
// and QRect::contains().
}
If you are implementing it in an other class and want to be displayed in a widget you should be careful about the coordinate system. That's because event->pos() is in a different coordinate system than that of your widget, so instead of event->pos() you should use :
myWidget->mapFromGlobal(this->mapToGlobal(event->pos()))
It is called "rubber band". You need to find an example for using QRubberBand class. I cannot separate a small sample from relatively big project but overall it is not very complex and simply works.
Related
I am creating a drawing application with Qt and want to include undo and redo commands for whatever is drawn on a QImage. For this, I want to at least try and incorporate the Qt undo framework, since manually doing it will probably be above my skill level.
I have a MainWindow class where the main work is done and where I have the menu with undo and redo QActions.
The painting is done in a QWidget class called DrawingArea like this (PenShape is an enum of shapes):
void DrawingArea::drawLine(const QPoint &endPoint) {
QPainter painter(&image);
//some pen config code
switch (currentShape){
case PenShape::Polygon:
painter.drawPolygon(&endPoint, 5, Qt::OddEvenFill);
break;
case PenShape::Line:
painter.drawLine(lastPoint, endPoint);
break;
//other shapes and their draw methods..
}
update()
}
So as can be seen, the painter can draw in multiple shapes. I know I would have to somehow transfer this to a QUndoCommand class, but how? The drawline method is called only in DrawingArea's mouse event methods like this:
void DrawingArea::mousePressEvent(QMouseEvent *event){
if (event->button() == Qt::LeftButton) {
lastPoint = event->pos();
}
}
The QtUndoStack is located in MainWindow and the QActions are also there, but somehow I would have to connect the drawLine QtUndoCommand class and the mousePressEvent and mainWindow QAction with its connected slot method all together. This is what I don't know how to do.
Any help is appreciated!
Edit, here's the menu button actions:
m_undo = undoStack->createUndoAction(this, tr("&Undo"));
m_undo->setShortcuts(QKeySequence::Undo);
m_redo = undoStack->createRedoAction(this, tr("&Redo"));
m_redo->setShortcuts(QKeySequence::Redo);
I have no QUndoCommand classes because I'm unsure of how to create them yet.
In my MainWindow, I have a undoStack declared like this:
undoStack = new QUndoStack(this);
I have derived from both QGraphicsView and QGraphicsRectItem. I overloaded the contextMenuEvent on both classes to provide popup menus. I want the QGraphicsView context menu when you click on white space the the QGraphicsItem popup menu when you click on an item.
At first implementation, I got the QGraphicsView popup no matter where I clicked. So I modified the contextMenuEvent as follows:
void CustomGraphicsView::contextMenuEvent(QContextMenuEvent* event)
{
if (QGraphicsItem *item = itemAt(event->pos())) {
MyRect* rect = dynamic_cast<MyRect*>(item);
QGraphicsSceneContextMenuEvent* context_event = dynamic_cast<QGraphicsSceneContextMenuEvent*>(event);
if (rect && context_event)
rect->contextMenuEvent(context_event);
}
else {
QMenu menu;
... create the QGraphicsView popup menu
}
}
The dynamic_cast for the QGraphicsSceneContextMenuEvent fails so I never call the contextMenuEvent for the rect. It won't compile if I just try to pass the event to the rect->contextMenu(), so I tried the cast.
What is the right way to do this?
This is a learning project to just create/move/rotate/delete 2D shapes using Qt. If someone wants to look at the whole thing, let me know.
OK, so I figured it out. Just make sure to pass the event through the base class method. Simple! This also works for the mousePressEvent(), mouseMoveEvent(), and mouseReleaseEvent functions.
void CustomGraphicsView::contextMenuEvent(QContextMenuEvent* event)
{
// if the event is on a GGraphicsItem just pass the event along
if (itemAt(event->pos())) {
QGraphicsView::contextMenuEvent(event);
}
else
{
QMenu menu;
... create popup for the CustomGraphicsView
I am creating a simple gauge in Qt 4.7.4, and everything is working wonderfully. Except for the fact that, for the life of me, I cannot get the dial shape to paint over the text labels when it passes over them. It always paints it behind the label. I am just using a simple drawpolygon() method.
I'm thinking this has something to do about paint events? I am drawing everything inside a QFrame inside a MainWindow. I am using QFrame's paintEvent.
Edit:
The QLabels are created on start up with new QLabel(this). They are only created once, and never touched again ( Similar to manually adding them on the Ui with Designer). The drawpolygon() is in the QFrame's Paint event.
"myclass.h"
class gauge : public QFrame
{
Q_OBJECT
public:
explicit gauge(QWidget *parent = 0);
~gauge();
void setValues(int req, int Limit, bool extra=false);
private:
void drawDial();
protected:
void paintEvent(QPaintEvent *e);
};
"myclass.cpp"
void gauge::paintEvent(QPaintEvent *e)
{
Q_UNUSED(e);
drawDial();
return;
}
void gauge::drawDial()
{
QPainter Needle(this);
Needle.save();
Needle.setRenderHint(Needle.Antialiasing, true); // Needle was Staggered looking, This will make it smooth
Needle.translate(centrePt); // Center of Widget
Needle.drawEllipse(QPoint(0,0),10,10);
Needle.restore();
Needle.end();
}
If the gauge widget and the QLabels are siblings, then you can move the gauge widget to the front by calling its raise() method.
If the QLabels are children of the gauge widget, on the other hand, then they will always display in front of it. In that case you can either reorganize your widget hierarchy so that they are siblings instead, or you can get rid of the QLabels and simply call drawText() from your paintEvent() method instead (after drawDial() returns)
I use MarbleWidget with OpenStreetMap on Qt.
Wheel zoom shows blurry images on the map. Therefore, I want to synchronize the mouse wheel with ZoomIn() and ZoomOut() inorder user to get sharp images on the map.
I want to do something like this:
QObject::connect( MarbleWidget, SIGNAL(??????), this, SLOT(wheelEvent(wheelEvent)) );
void MainWindow::wheelEvent(QWheelEvent *event){
//....
}
Is there any signal or event that I can use from MarbleWidget for ??????? above line?
And, how can I disable the mouse zoom on the MarbleWidget?
You can make your own input handler and tell MarbleWidget to use it. This will allow you to intercept mouse wheel events in the way you are asking.
Create a custom input handler
MarbleWidget uses a default input handler. Inside of MarbleInputHandler.cpp there is a function eventFilter(QObject*, QEvent*) that handles (among other things) the QEvent::Wheel event. Derive from this class and override eventFilter:
class MyMarbleInputHandler : public MarbleWidgetDefaultInputHandler
{
Q_OBJECT
public:
explicit MyMarbleInputHandler(MarbleWidget* mw) :
MarbleWidgetDefaultInputHandler(mw) {}
virtual bool eventFilter(QObject *o, QEvent *e);
signals:
void wheelEvent(QWheelEvent *event);
};
Basically, you want to intercept QEvent::Wheel and emit your own signal. Anything you don't handle yourself should be passed along to the base class.
bool MyMarbleInputHandler::eventFilter(QObject *o, QEvent *e)
{
if (e->type() == QEvent::Wheel)
{
emit wheelEvent(static_cast<QWheelEvent*>(e));
return true;
}
return MarbleWidgetDefaultInputHandler::eventFilter(o, e);
}
Create a custom MarbleWidget
The constructor below shows how you can set the input handler defined above. You'll also have to wire the signal/slot.
class MyMarbleWidget : public MarbleWidget
{
Q_OBJECT
public:
explicit MyMarbleWidget()
{
MyMarbleInputHandler *myMarbleInputHandler = new MyMarbleInputHandler(this);
setInputHandler(myMarbleInputHandler);
connect(myMarbleInputHandler, SIGNAL(wheelEvent(QWheelEvent*)),
this, SLOT(handleWheelEvent(QWheelEvent*)));
}
public slots:
void handleWheelEvent(QWheelEvent *event)
{
if (event->delta() > 0) zoomIn();
else zoomOut();
}
};
handleWheelEvent() provides the code to zoom in/out. Not all scroll wheels work the same, so you'll have to figure out how much movement of the mouse wheel it will take to zoom in/out by one step. In this example, it zooms in/out one step based on each event, paying attention only to the sign of delta() and ignoring its magnitude.
You might also check out MarbleDefaultInputHandler::handleWheel() to see what's going on with the default behavior. They use interpolated/stretched bitmap images between vector layers to provide a smoother animation when zooming. Note that the plus+ and minus- keys on the keyboard will allow you to zoom to non-interpolated map levels, whereas the mouse wheel zooms using animated ("blurry") interpolated layers. This behavior is documented in a bug report.
I have 2 custom qgraphicsitems on a qgraphicsScene, rendered by a qgraphicsview. Now I want to be able to drag and drop one of the 2 items to the other kind. But which events should I reimplement for this? The documentation is a bit confusing on this.
also I want the qgraphicsitem to jump back to its original position if the user drags it to another area than the qgraphicsitem it should be dropped on.
As far as i know this is not implemented in the QGraphicsScene itself.
You must derive your own class from QGraphicsView or QGraphicsScene and then overload:
class MyGraphicsView : public QGraphicsView
{
Q_OBJECT;
protected:
virtual void mousePressEvent(QMouseEvent* event);
virtual void mouseMoveEvent(QMouseEvent* event);
virtual void mouseReleaseEvent(QMouseEvent* event);
...
private:
QGraphicsItem *currentDraggedItem;
};
QGraphicsView gives works with view/window coordinates while QGraphicsScene works with Scene coordinates.
Add code like:
void MyGraphicsView::mousePressEvent(QMouseEvent* event)
{
currentDraggedItem = itemAt(event->pos());
QGraphicsView::mousePressEvent(event);
}
void MyGraphicsView::mouseReleaseEvent(QMouseEvent* event)
{
QGraphicsItem *foundItem = itemAt(event->pos());
if(foundItem && currentDraggedItem &&
foundItem != currentDraggedItem)
{
// Handle DragDrop Here
}
QGraphicsView::mouseReleaseEvent(event);
}
This does the job for one QGaphicsScene. If you have two of them - the both have to know each other and you must translate coordinates from the one QGraphicsView to the other QGraphicsView. using mapTo...().
The key to this is checking the QGraphicsItems rect and seeing if they intersect.
So, when the mouse down is pressed on an item, store its current position. You can now move it and wait for the mouse release. On the release of the mouse button, check if the bounding rects of the items intersect with QRect::contains(const QRectF). If they do, then you've dropped one onto the other. If not, then animate the graphics item back to the previously stored position.
Just make sure that when you're checking the bounding rects for intersection that you're doing this with both of them in scene space coordinates. Either convert them, or use QGraphicsItem::sceneBoundingRect().