I'm trying to implement drag'n'drop for a QGraphicsScene. Here are the events I've overloaded:
void TargetScene::dragEnterEvent(QGraphicsSceneDragDropEvent *event) {
bool acceptDrag = false;
const QMimeData* mime = event->mimeData();
// Is an image present?
if (mime->hasImage()) {
QImage img = qvariant_cast<QImage>(mime->imageData());
dragPix = QPixmap::fromImage(img);
acceptDrag = !dragPix.isNull();
}
event->setAccepted(acceptDrag);
}
void TargetScene::dropEvent(QGraphicsSceneDragDropEvent *event) {
// Add dragged pixmap to scene
QGraphicsPixmapItem* newPix = this->addPixmap(dragPix);
newPix->setPos(event->pos().x(), event->pos().y());
}
The scene still won't accept drops. I'm guessing that's because I can't do setAcceptDrops(true) on my QGraphicsScene.
How do I accept drops on a graphics scene?
The trick here is to ALSO accept the event in the QGraphicsScene::dragMoveEvent()!
The reason is the DEFAULT implementation which ignores drag and drop events if there is no item under the mouse!
Also refer to: http://www.qtcentre.org/threads/8022-QGraphicsScene-doesn-t-accept-Drops
Cheers
Related
I have a QHBoxLayout in which I added some widgets. I need to be able to refresh the layout dynamically so I use this to clear the layout :
void ClearLayout(QLayout* layout)
{
if (!layout)
return;
QLayoutItem* item;
while ((item = layout->takeAt(0)) != nullptr)
{
delete item->widget();
ClearLayout(item->layout());
}
}
This indeed removes all widgets and layouts. After this layout->isEmpty() returns true and layout->count() returns 0.
However, when I try to add new widgets (same type of other previously added but new instance) It does not work !
AddWidget()
{
// DeviceWidget inherits QWidget
DeviceWidget* deviceWidget = new DeviceWidget;
deviceWidget->setFixedSize(150, 200);
connect(deviceWidget->GetSignalObject(), &DeviceObject::Selected, this,
&DeviceLayout::SelectedDevice);
layout->addWidget(deviceWidget, 0, Qt::AlignCenter);
}
This is the same function used previously to add the widgets to the layout and worked the first time at Construction:
MainLayout(QWidget* parent) : QHBoxLayout(parent)
{
layout = new QHBoxLayout;
addLayout(layout);
uint32 nb = GetDeviceNumber(); // returns 2
for (uint32 i = 0; i < deviceNb; ++i)
AddDeviceWidget();
}
After trying to add 2 widgets I have layout->isEmpty() returns true and layout->count() returns 2 so I'm confused …
thanks for any help provided :)
EDIT:
The problem seems to be comming from my DeviceWidget class since trying to add a simple QLabel to the cleared layout worked. Here's the DeviceWidget Constructor:
DeviceWidget::DeviceWidget(QWidget* parent) : QWidget(parent)
{
QVBoxLayout* vLayout = new QVBoxLayout;
QLabel* deviceIcon = new QLabel("DeviceIcon", this);
deviceIcon->setFixedSize(128, 128);
deviceIcon->setPixmap(QPixmap::fromImage(QImage("Resources/Icons/device.png")));
deviceIcon->setObjectName("DeviceIcon");
// StatusWidget inherits QWidget
// Just override paintEvent to display a colored filled disk
m_status = new StatusWidget(20, Status::Close, this);
m_status->setObjectName("DeviceStatus");
vLayout->addWidget(deviceIcon, 0, Qt::AlignCenter);
vLayout->addWidget(m_status, 0, Qt::AlignCenter);
// DeviceObjct inherits from QObject an add a signal
m_object = new DeviceObject(size());
// Function clearing the stylesheet background-color
Clear();
setLayout(vLayout);
installEventFilter(this);
setObjectName(QString("DeviceWidget"));
}
Commenting installEventFilter(this) make it work so I think I need to add an event filter to make it work but I don't know which one
As said in the Edit, The problem is coming from DeviceWidget added in the layout that override eventFilter. There is probably a way to add a case in eventFilter to make it work but in my case it was best either (1) or (2):
1. Remove eventFilter from class DeviceWidget and put it in class DeviceObject: m_object is present to emit a signal according to event:
DeviceObject.h:
DeviceObject(QObject* parent);
bool eventFilter(QObject* obj, QEvent* event) override;
signals:
void Select(uint32 i);
Then in class DeviceWidget still call installEventFilter but with m_object as parameter: installEventFilter(m_object);
For the other event (Enter/Leave) I overrode void enterEvent(QEvent* event) and void leaveEvent(QEvent* event) for class DeviceWidget. That's what lead me to the second option that seems better.
2. Completely remove eventFilter and installEventFilter as it is only used to emit a signal when widget is clicked and do things when cursor hovers the widget. Instead override enterEvent and leaveEvent for class DeviceWidget like said before for hover event.
Then in class DeviceObjecy override void mousePressEvent(QMouseEvent*) for clicked event.
I have a button in my Custom QDialog, I am emitting a signal when pushbutton 1 is clicked
void MyCustomDialog::on_pushButton_1()
{
this->hide(); //i need to hide this window before 'OnButton_1_Clicked' stuffs starts
emit button_1_clicked();
}
In my main window I have connected the slot and created the instance as shown below
void MainWindow::MainWindow()
{
MyCustomDialog *dlg = MyCustomDialog::getInstance(this); //only single instance created
connect(dlg, &MyCustomDialog::button_1_clicked, this, &MainWindow::OnButton_1_Clicked);
}
I am displaying my custom dialog from a function in mainwindow as below
void MainWindow::dispayCustomDialog()
{
MyCustomDialog *dlg = MyCustomDialog::getInstance();
dlg->show();
}
Below shows how my 'OnButton_1_Clicked' slot. In which I am capturing the screenshot using below line
void MainWindow::OnButton_1_Clicked()
{
//capture the screen shot
QScreen *screen = QGuiApplication::primaryScreen();
QPixmap *map = new QPixmap(screen->grabWindow(0));
bool result = map->save("D:/test.jpg", "JPG");
}
Once I captured screen using above function, I can still see my 'MyCustomDialog' in test.jpg file. Qt doc says QGuiApplication::primaryScreen captures the initial state of application. So i think, this is expected in my case. Do we have any other solution to grab screen with current state ?
What I am trying to achieve is grab the screen in OnButton_1_Clicked() function after hiding my 'MyCustomDialog'.
I found a solution. Used a singleslot timer with delay of 500 msec before capturing the screen as below. This wait for my custom dialog to hide properly.
void MainWindow::OnButton_1_Clicked()
{
QTimer::singleShot(500, this, &MainWindow::shootscreen);
}
void MainWindow::shootscreen()
{
//capture the screen shot
QScreen *screen = QGuiApplication::primaryScreen();
QPixmap map = screen->grabWindow(0);
bool result = map.save("D:/test.jpg", "JPG");
}
I have implemented the panning view on the QGraphicsView, using the mouse move event using
void View::mouseMoveEvent(QMouseEvent* event) {
pan();
QGraphicsView::mouseMoveEvent(event);
}
and in the scene of this view I have added few items where some of the items are resizable, so I implemented
void Item::mouseMoveEvent(QGraphicsSceneMouseEvent *event)
{
if(m_resizeMode)
{
resize();
e->accept();
}
}
I tried to filter the mouse move not to propagate to any further using e->accept()
but my View mouseMove event has been called first , so when ever I tried to resize the item, the view started to pan all the way.
How can I avoid this event propagation from view to scene.
You can call the base class implementation of the QGraphicsView and check if the event is accepted. However I would do this in the mousePressEvent instead of the mouseMoveEvent. After all this is where you determine if you should resize an item or do some panning. This is how I do something similar in my project:
void View::mousePressEvent(QMouseEvent *event)
{
...
QGraphicsView::mousePressEvent(event);
if(event->isAccepted())
move = false;
else
move = true;
}
void View::mouseMoveEvent(QMouseEvent *event)
{
if(!move)
{
QGraphicsView::mouseMoveEvent(event);
return;
}
... // you would do the panning here
QGraphicsView::mouseMoveEvent(event);
}
void View::mouseReleaseEvent(QMouseEvent *event)
{
if(!move)
{
QGraphicsView::mouseReleaseEvent(event);
return;
}
else
{
...
move = false;
}
QGraphicsView::mouseReleaseEvent(event);
}
The view will always receive the mouse event first.
So, in the view, check to see if the mouse is over an item before allowing it to pan by getting the mouse pos in scene coordinates and retrieving the items at that position with QGraphicsScene::items( )
How to make a QT dialog read-only? Any general way to implement it easily? For example
(1) set all its containing widgets disable. (how to implement it?)
(2) Intercept edit events like key pressed, mouse pressed but how not to intercept the one to close the dialog?
I think this feature should be very helpful.
Disabling the widgets can be done similar to the following:
void myDialog::disableWidgets()
{
QList<QWidget *> widgets = this->findChildren<QWidget *>();
foreach(QWidget* widget, widgets)
{
widget->setEnabled(false);
}
}
To intercept events, QDialog includes the function installEventFilter(QObject*).
This allows you to use a separate object to receive all events passed to the dialog. You can then choose to handle the event in the object, or pass it on to the dialog itself by calling the base class QObject::eventFilter
class MyEventHandler : public QObject
{
Q_OBJECT
protected:
bool MyEventHandler::eventFilter(QObject *obj, QEvent *event)
{
// handle key press events
if (event->type() == QEvent::KeyPress)
{
// Do something
// ...
return true; // event handled by the class
}
else
{ // ignore this event and pass it to the dialog as usual
return QObject::eventFilter(obj, event);
}
}
return false;
};
QDialog* dlg = new QDialog;
MyEventHandler evtHandler = new MyEventHandler;
dlg->installEventFilter(evtHandler);
Read-only is a strange term to apply to a dialog. Disabling all widgets as above does the trick. If you only wanted to make the input part of a QInputDialog read-only (while leaving scrollbars, buttons, etc. enabled), you could adapt that code as below:
QInputDialog dialog(this);
dialog.setOptions(QInputDialog::UsePlainTextEditForTextInput);
dialog.setWindowTitle("Title");
dialog.setLabelText("Label");
dialog.setTextValue("1\n2\n3\n");
QList<QWidget *> widgets = dialog.findChildren<QWidget *>();
foreach(QWidget* widget, widgets) {
if (strcmp(widget->metaObject()->className(),"QPlainTextEdit")==0) {
QPlainTextEdit *t = static_cast<QPlainTextEdit*>(widget);
t->setReadOnly(true);
}
}
dialog.exec();
I am writing an info-screen program. I created a full-screen widget and draw contents onto it.
In order to extend the life cycle of the TFT-display device, I want to implement a pixel-shifting feature. With other words, in every X minutes, I shift the screen to left/right/top/down for Y pixels.
My approach is as follows:
I use two layers (two QWidget).
I paint contents on the top layer.
When a pixel-shifting is performed, I just move the top layer for specified offset.
And then fill a background color to the bottom layer.
However, I found a problem:
If I move up the top layer for 10 pixels, the 10-pixel-content goes out of the screen. But when I move this layer down for 10 pixels. The 10-pixel-content will not be updated, it is gone.
How can I keep these 10-pixel-content? Is there any magic widget flag to solve this problem?
UPDATE 1:
The code is written in language D, but it is easy to understand:
class Canvas: QWidget
{
private QPixmap content;
this(QWidget parent)
{
super(parent);
setAttribute(Qt.WA_OpaquePaintEvent, true);
}
public void requestForPaint(QPixmap content, QRegion region)
{
this.content = content;
update(region);
}
protected override void paintEvent(QPaintEvent event)
{
if (this.content !is null)
{
QPainter painter = new QPainter(this);
painter.setClipping(event.region);
painter.fillRect(event.region.boundingRect, new QColor(0, 0, 0));
painter.drawPixmap(event.region.rect, this.content);
this.content = null;
painter.setClipping(false);
}
}
}
class Screen: QWidget
{
private Canvas canvas;
this()
{
super(); // Top-Level widget
setAutoFillBackground(True);
this.canvas = new Canvas(this);
showFullScreen();
}
public void requestForPaint(QPixmap content, QRegion region)
{
this.canvas.requestForPaint(content, region);
}
private updateBackgroundColor(QColor backgroundColor)
{
QPalette newPalette = palette();
newPalette.setColor(backgroundRole(), backgroundColor);
setPalette(newPalette);
}
public shiftPixels(int dx, int dy)
{
this.canvas.move(dx, dy);
updateBackgroundColor(new QColor(0, 0, 0)); // Just a demo background color
}
}
Screen screen = new Screen;
screen.requestForPaint(some_content, some_region);
screen.shiftPixels(0, -10);
screen.shiftPixels(0, 10);
Looking at the code, my first guess is that your region might be wrong. Try repainting the whole widget each time, and see if that solves the missing 10 pixel problem. If it does, then try working out why your region isn't covering the newly exposed portion.
One possibility along those lines: I notice in your Screen::requestForPaint method that you directly call the Canvas::requestForPaint without doing anything with the region. In Qt, the coordinates for anything like that are often assumed to be local, so if you don't account for the current position of the canvas widget, you might get an incorrect region.
Why not setting the position of the widget directly...? Another options might be using QPainter::translate(-1,-1) or something similar.