Extend mousePressEvent() for dragging but retain previous behaviour for clicking - qt

I'm trying to add the Drag and Drop functionality to a QListWidget. The first milestone was reached with the following code.
//draglist.h
#include <QListWidget>
class SourceList : public QListWidget
{
public:
SourceList(QWidget * parent = 0);
protected:
void mousePressEvent(QMouseEvent *event);
};
//draglist.cpp
#include "draglists.h"
#include <QMouseEvent>
#include <QDebug>
#include <QMimeData>
#include <QDrag>
SourceList::SourceList(QWidget * parent)
:QListWidget(parent)
{
}
void SourceList::mousePressEvent(QMouseEvent *event)
{
//Data from the selected QListWidgetItem
QString itemData = currentItem()->data(Qt::DisplayRole).toString();
QMimeData *mimeData = new QMimeData;
mimeData->setText(itemData);
QDrag *drag = new QDrag(this);
drag->setMimeData(mimeData);
drag->exec();
}
It enabled the drag functionality for the list items, but they can't be clicked any more.
How to retain the functionality in which one left click selects the item in the list (this is the behaviour if mousePressEvent() isn't overridden) ?
Possible solution
Check the source code for the original QAbstractItemView::mousePressEvent(QMouseEvent *event) and recopy the needed code int the SourceList::mousePressEvent(QMouseEvent *event) implementation. I'm looking for an alternative to that, if existent.

To preserve the default functionality you can propagate the event up the the parent class:
void SourceList::mousePressEvent(QMouseEvent *event)
{
// Your handling of mouse event.
[..]
QListWidget::mousePressEvent(event);
}
If you need to handle the left mouse button press, you can use QMouseEvent::button() function and compare returning value with one or more Qt::MouseButton values. For example:
if (event->button() & Qt::LeftButton) {
// Handle the left mouse button click.
}

Related

How to simulate a drag and drop action using QTest

In order to create a test case for a drag and drop bug in the QTreeView widget I tried to simulate a drag and drop mouse movement behavior.
I basically select the first element in the QTreeView and want it to drag and drop on the third element. I did this by using a combination of QTest::mousePress and QTest::mouseMove. At the end there should be of course a QTest::mouseRelease, but I already failed in reproducing the mousePress and mouseMove.
These are steps necessary to reproduce:
User moves mouse on center of first item
User presses the left mouse button
User holds the left mouse button pressed and moves the mouse to the center of the third item
User releases the left mouse button
If one does these action as described I can see, that the QTreeView Widgets reacts appropriately, and indicating special highlights and vertical lines, in case the item will be move between to items.
Unfortunately, my automatized test fails to reproduce this behavior. It seems that calling QTest::mousePress in sequence does something different. Also using a pair of QTest::mousePress and QTest::mouseMove is something differently.
This is my code:
main.cpp
#include "TestObject.h"
#include <QTest>
QTEST_MAIN(TestObject)
TestObject.h
#include <QtTest/QtTest>
class TestObject : public QObject
{
Q_OBJECT
private slots:
void dragAndDrop();
};
TestObject.cpp
#include "TestObject.h"
#include "TestObject.moc"
#include <QTest>
#include <QTreeView>
#include <QStandardItemModel>
#include <QPropertyAnimation>
#include "MouseMover.h"
void TestObject::dragAndDrop() {
qDebug() << "Hello";
QStandardItemModel model;
QTreeView view;
view.setModel(&model);
view.show();
view.setHeaderHidden(true);
view.setDragDropMode(QAbstractItemView::DragDropMode::DragDrop);
view.setDefaultDropAction(Qt::DropAction::MoveAction);
view.setColumnHidden(1, true);
for (auto rowIter = 0; rowIter < 3; rowIter++) {
QList<QStandardItem*> items;
for (auto colIter = 0; colIter < 2; colIter++) {
items << new QStandardItem(QString("%1-%2").arg(rowIter).arg(colIter));
}
model.appendRow(items);
}
MouseMover mover;
mover.setWidget(view.viewport());
QPropertyAnimation anim(&mover, "mousePosition");
QTimer::singleShot(0, [&]() {
auto startValue = view.visualRect(model.index(0, 0)).center();
auto endValue = view.visualRect(model.index(2, 0)).center();
QTest::mousePress(view.viewport(), Qt::MouseButton::LeftButton, Qt::KeyboardModifier::NoModifier, startValue);
anim.setStartValue(startValue);
anim.setEndValue(endValue);
anim.setDuration(500);
anim.start();
});
qApp->exec();
}
MouseMover.h
#pragma once
#include <QObject>
#include <QTest>
class MouseMover : public QObject {
Q_OBJECT
public:
Q_PROPERTY(QPoint mousePosition READ mousePosition WRITE setMousePosition MEMBER mMousePosition)
void setWidget(QWidget* widget) {
mWidget = widget;
}
QPoint mousePosition() const {
return mMousePosition;
}
void setMousePosition(const QPoint& pos) {
mMousePosition = pos;
if (mWidget) {
QTest::mousePress(mWidget, Qt::MouseButton::LeftButton, Qt::KeyboardModifier::NoModifier, mMousePosition);
QTest::mouseMove(mWidget, mMousePosition);
}
}
private:
QPoint mMousePosition;
QWidget* mWidget{ nullptr };
};
MouseMover.cpp
#include "MouseMover.h"
mouseMoveEvent() handler starts and blocks at QDrag.exec(). In order to simulate the drop, you have to schedule it e.g. with QTimer:
def complete_qdrag_exec():
QTest.mouseMove(drop_target)
QTest.qWait(50)
QTest.mouseClick(drop_target, Qt.MouseButton.LeftButton)
QTimer.singleShot(1000, complete_qdrag_exec)
QTest.mousePress(drag_source, Qt.MouseButton.LeftButton, Qt.KeyboardModifier.NoModifier, QPoint(10, 10))
QTest.mouseMove(drag_source, QPoint(50, 50)) # Ensure distance sufficient for DND start threshold
Above is plain Python (PyQt6/PySide6). I assume it's similar for C++ ...

Qt rightclick QPushButton

I'm using Qt Creator to create a gui for a mineseeper game.
How can I know a QpushButton clicked with rightclick? for flag in the game.
In other word, which signal used for rightclick?
Create your own button with filter at mousePressEvent slot.
qrightclickbutton.h
#ifndef QRIGHTCLICKBUTTON_H
#define QRIGHTCLICKBUTTON_H
#include <QPushButton>
#include <QMouseEvent>
class QRightClickButton : public QPushButton
{
Q_OBJECT
public:
explicit QRightClickButton(QWidget *parent = 0);
private slots:
void mousePressEvent(QMouseEvent *e);
signals:
void rightClicked();
public slots:
};
#endif // QRIGHTCLICKBUTTON_H
qrightclickbutton.cpp
#include "qrightclickbutton.h"
QRightClickButton::QRightClickButton(QWidget *parent) :
QPushButton(parent)
{
}
void QRightClickButton::mousePressEvent(QMouseEvent *e)
{
if(e->button()==Qt::RightButton)
emit rightClicked();
}
Now connect like this
QRightClickButton *button = new QRightClickButton(this);
ui->gridLayout->addWidget(button);
connect(button, SIGNAL(rightClicked()), this, SLOT(onRightClicked()));
Create a slot in MainWindow.cpp.
void MainWindow::onRightClicked()
{
qDebug() << "User right clicked me";
}
It works for me!
I think QPushButton is internally implemented to listen to left mouse clicks only. But you can easily extend QPushButton and re-implement let's say the mouse release event and do your thing if the right mouse button was pressed, e.g. emit a custom rightClicked() signal for example:
signals:
void rightClicked();
protected:
void mouseReleaseEvent(QMouseEvent *e) {
if (e->button() == Qt::RightButton) emit rightClicked();
else if (e->button() == Qt::LeftButton) emit clicked();
}
... or you can create an overload of the clicked signal that forwards the mouseEvent pointer so you can do the same check outside of the button.
signals:
void clicked(QMouseEvent *);
protected:
void mouseReleaseEvent(QMouseEvent *e) {
emit clicked(e);
}
Then you do the check in the slot you connect the button's clicked(QMouseEvent *) signal to and proceed accordingly.
I just wrote this little helper adapter to make any existing button right-clickable with no need to subclass it:
class CRightClickEnabler : public QObject
{
public:
CRightClickEnabler(QAbstractButton * button): QObject(button), _button(button) {
button->installEventFilter(this);
};
protected:
inline bool eventFilter(QObject *watched, QEvent *event) override {
if (event->type() == QEvent::MouseButtonPress)
{
auto mouseEvent = (QMouseEvent*)event;
if (mouseEvent->button() == Qt::RightButton)
_button->click();
}
return false;
}
private:
QAbstractButton* _button;
};
Usage:
connect(ui->pushButton, &QPushButton::clicked, [](){qDebug() << "Button clicked";});
new CRightClickEnabler(ui->pushButton);
From now on, the clicked signal will be triggered by the right click as well as left click. There's no need to delete this object - it uses ui->pushButton as parent and will be auto-deleted by Qt when the parent is destroyed.
Obviously, you can write 2 lines of code (literally) to declare a new signal here and emit that signal upon right click instead of clicked, if desired.
I'd like to suggest this option as well, without need for event filter/other stuffs...
self.button.released.connect(self.doStuff)
self.button.setContextMenuPolicy(Qt.ContextMenuPolicy.CustomContextMenu)
self.button.customContextMenuRequested.connect(partial(self.doStuff, False))
def doStuff(self,state=True,p=QPoint()):
print("True for left, False for right!",state)

How to change a parent widget's background when a child widget has focus?

I would like to highlight a QFrame, if one of it's child widgets has focus (so the users know where to look for the cursor ;-)
using something along
ui->frame->setFocusPolicy(Qt::StrongFocus);
ui->frame->setStyleSheet("QFrame:focus {background-color: #FFFFCC;}");
highlights the QFrame when I click on it, but it loses its focus once one of its child widgets is selected.
Possible approaches:
I could connect() QApplication::focusChanged(old,now) and check each new object if it is a child of my QFrame, but this gets messy.
I could also subclass each child widget and reimplement focusInEvent()/focusOutEvent() and react on that, but with a lot of different widgets, this is also a lot of work.
Is there a more elegant solution?
Well, you can extend QFrame to make it listen on focus change of its children widgets.
Or you can also install an event filter on children widgets to catch QFocusEvent.
Here is an example:
MyFrame.h
#ifndef MYFRAME_H
#define MYFRAME_H
#include <QFrame>
class MyFrame : public QFrame
{
Q_OBJECT
public:
explicit MyFrame(QWidget* parent = 0, Qt::WindowFlags f = 0);
void hookChildrenWidgetsFocus();
protected:
bool eventFilter(QObject *object, QEvent *event);
private:
QString m_originalStyleSheet;
};
#endif // MYFRAME_H
MyFrame.cpp
#include <QEvent>
#include "MyFrame.h"
MyFrame::MyFrame(QWidget *parent, Qt::WindowFlags f)
: QFrame(parent, f)
{
m_originalStyleSheet = styleSheet();
}
void MyFrame::hookChildrenWidgetsFocus()
{
foreach (QObject *child, children()) {
if (child->isWidgetType()) {
child->installEventFilter(this);
}
}
}
bool MyFrame::eventFilter(QObject *object, QEvent *event)
{
if (event->type() == QEvent::FocusIn) {
setStyleSheet("background-color: #FFFFCC;");
} else if (event->type() == QEvent::FocusOut) {
setStyleSheet(m_originalStyleSheet);
}
return QObject::eventFilter(object, event);
}
MainWindow.cpp
#include <QHBoxLayout>
#include <QVBoxLayout>
#include <QLineEdit>
#include "MyFrame.h"
#include "mainwindow.h"
MainWindow::MainWindow(QWidget *parent) :
QMainWindow(parent)
{
setWindowTitle(tr("Test"));
MyFrame *frame1 = new MyFrame(this);
frame1->setLayout(new QVBoxLayout());
frame1->layout()->addWidget(new QLineEdit());
frame1->layout()->addWidget(new QLineEdit());
frame1->layout()->addWidget(new QLineEdit());
frame1->hookChildrenWidgetsFocus();
MyFrame *frame2 = new MyFrame(this);
frame2->setLayout(new QVBoxLayout());
frame2->layout()->addWidget(new QLineEdit());
frame2->layout()->addWidget(new QLineEdit());
frame2->layout()->addWidget(new QLineEdit());
frame2->hookChildrenWidgetsFocus();
QHBoxLayout *centralLayout = new QHBoxLayout();
centralLayout->addWidget(frame1);
centralLayout->addWidget(frame2);
QWidget *centralWidget = new QWidget();
centralWidget->setLayout(centralLayout);
setCentralWidget(centralWidget);
}
I believe the both answers you were given are wrong. They work for simple cases but are extremely fragile and clumsy. I believe that the best solution is what you actually suggested in your question. I would go for connecting to QApplication::focusChanged(from, to). You simply connect your main frame object to this signal and in the slot you check if the to object (the one which received focus) is a child of your frame object.
Frame::Frame(...)
{
// ...
connect(qApp, &QApplication::focusChanged, this, &Frame::onFocusChanged);
// ...
}
// a private method of your Frame object
void Frame::onFocusChanged(QWidget *from, QWidget *to)
{
auto w = to;
while (w != nullptr && w != this)
w = w->parentWidget();
if (w == this) // a child (or self) is focused
setStylesheet(highlightedStylesheet);
else // something else is focused
setStylesheet(normalStylesheet);
}
The advantage is obvious. This code is short and clean. You connect only one signal-slot, you do not need to catch and handle events. It responds well to any layout changes done after the object is created. And if you want to optimize away unnecessary redrawing, you should cache the information whether any child is focused and change the stylesheet only and only if this cached value gets changed. Then the solution would be prefect.
First, create a simple subclass of QFrame which reimplements the eventFilter(QObject*, QEvent*) virtual function:
class MyFrame : public QFrame {
Q_OBJECT
public:
MyFrame(QWidget *parent = 0, Qt::WindowFlags f = 0);
~MyFrame();
virtual bool eventFilter(QObject *watched, QEvent *event);
};
Use MyFrame instead of QFrame to contain your widgets. Then, somewhere in your code where you create the widgets contained in MyFrame, install an event filter on those widgets:
// ...
m_myFrame = new MyFrame(parentWidget);
QVBoxLayout *layout = new QVBoxLayout(myFrame);
m_button = new QPushButton("Widget 1", myFrame);
layout->addWidget(m_button);
m_button->installEventFilter(myFrame);
//...
At that point, MyFrame::eventFilter() will be called before any event is delivered to the widget, letting you act on it before the widget is aware of it. Within MyFrame::eventFilter(), return true if you want to filter the event out (i.e. you don't want the widget to process the event), or return false otherwise.
bool MyFrame::eventFilter(QObject *watched, QEvent *event)
{
if (watched == m_button) { // An event occured on m_button
switch (event -> type()) {
case QEvent::FocusIn:
// Change the stylesheet of the frame
break;
case QEvent::FocusOut:
// Change the stylesheet back
break;
default:
break;
}
}
return false; // We always want the event to propagate, so always return false
}

How to remove an Item from a List View?

I have a QLineEdit, 2 QPushButtons (Add & Remove Buttons) and a QListView.
I want to add the QLineEdit text to the QListView when the add button is clicked. Same way, I have to delete an item from the QListView if the remove button is clicked.
I'm using a QStringListModel to add the QLineEdit text to the QListView. But I don't know how to delete the QListView item. How can I do this? Plz Help.. Thanks in Advance.
#ifndef EXAMPLE_H
#define EXAMPLE_H
#include <QWidget>
#include <QStringList>
#include <QStringListModel>
class EXAMPLE : public QWidget
{
Q_OBJECT
public:
explicit EXAMPLE(QWidget *parent = 0);
~EXAMPLE();
private slots:
void on_addButton_released();
void on_removeButon_released();
private:
Ui::EXAMPLE *ui;
QStringList stringList;
};
#endif // EXAMPLE_H
EXAMPLE.CPP
#include "EXAMPLE.h"
#include <QStringListModel>
EXAMPLE::EXAMPLE(QWidget *parent) :
QWidget(parent),
ui(new Ui::EXAMPLE)
{
ui->setupUi(this);
ui->listView->setModel(new QStringListModel(stringList));
}
EXAMPLE::~EXAMPLE()
{
delete ui;
}
void EXAMPLE::on_addButton_released()
{
stringList.append(ui->lineEdit->text());
((QStringListModel*) ui->listView->model())->setStringList(stringList);
ui->lineEdit->clear();
}
void EXAMPLE::on_removeButon_released()
{
}
If your list view is only using single selection, the following will work:
void EXAMPLE::on_removeButton_released()
{
QModelIndexList selected = ui->listView->selectionModel()->selectedIndexes();
if (!selected.isEmpty())
{
stringList.removeAt(selected.first().row()-i);
((QStringListModel*) ui->listView->model())->setStringList(stringList);
}
}
If your list view allows for multiple items to be selected, then you'll need something slightly more complex:
void EXAMPLE::on_removeButton_released()
{
QModelIndexList selected = ui->listView->selectionModel()->selectedIndexes();
if (!selected.isEmpty())
{
qSort(selected);
for (int i=0; i<selected.count(); ++i)
{
stringList.removeAt(selected.at(i).row()-i);
}
((QStringListModel*) ui->listView->model())->setStringList(stringList);
}
}
There is also a means of removing the items directly from the item model (as opposed to removing it from the string list and then setting the string list on the model). Refer to the documentation for QAbstractItemModel::removeRow. If you go down this road, your method of adding items will also need to change.

QDateEdit calendar popup

I'm trying to get a QDateEdit to allow the QCalendarWidget to show when requested (rather than just on clicking the down arrow). For example, somewhere in my class I should be able to say:
ui.datepicker.showCalendar()
and it should load up the calendar that appears right below the date picker.
It looks like I need to sub-class QDateEdit, as this doesn't work:
QDateEdit *de = new QDateEdit();
de->calendarWidget()->show();
I've also tried sending keyboard commands as dictated when you go through the QDateTimeEdit.cpp source for Qt, but seems my keyboard shortcuts are disabled or something.
Any ideas on what I have to do to sub-class to get this to work? I was thinking of something like:
class MyDateEdit : QDateEdit
{
Q_OBJECT
protected:
void mouseEvent(QEvent *event) {
this.calendarWidget().show();
}
};
But alas that also doesn't seem to compile in or work correctly.
Enable "setCalendarPopup ( bool enable )" in QDateTimeEdit allows to popup the calendar
I was able to figure it out on my own - still no sure how to get QDateEdit to work properly, but I used a QLineEdit and it suited my needs. Just connect QCalendarWidget's "onClick(QDate)" to a slot you create that does a:
setText(date.toString("M/d/yyyy"));
ui->calendar->hide();
Then add an event filter to the QLineEdit using the "OnFocusIn" event that does a "ui->calendar->show();" See: Get a notification/event/signal when a Qt widget gets focus
#Rob S answer
You were right with event filter approach we would do same with QDateEdit.
I am writing the code which extends your approach with QDateEdit :
In mainwindow.h I created a QCalendar pointer (Using QtCreator)
Following is the code of mainwindow.cpp (I am giving out fullcode so that rookies like me can benifit from it)
Make sure you set buttonSymbol and calendarpopup property to false to make it work correctly
#include "mainwindow.h"
#include "ui_mainwindow.h"
#include <QCalendarWidget>
MainWindow::MainWindow(QWidget *parent) :
QMainWindow(parent),
ui(new Ui::MainWindow)
{
ui->setupUi(this);
ui->dateEdit->setDate(QDate::currentDate());
widget=new QCalendarWidget(); //widget is QCalendar pointer
ui->verticalLayout->addWidget(widget);
widget->setWindowFlags(Qt::Popup); // we need widget to popup
ui->dateEdit->installEventFilter(this);
connect(widget,SIGNAL(clicked(QDate)),ui->dateEdit,SLOT(setDate(QDate)));
}
MainWindow::~MainWindow()
{
delete ui;
}
bool MainWindow::eventFilter(QObject *object, QEvent *event)
{
if (event->type() == QEvent::InputMethodQuery)
{
if (object == ui->dateEdit)
{
if(widget->isVisible()==false && ui->dateEdit->calendarWidget()->isVisible()==false) // this done to avoid conflict
{
qWarning(QString().number(event->type()).toStdString().c_str());
qWarning(object->objectName().toLatin1().data());
widget->move(ui->dateEdit->mapToGlobal(QPoint(0,ui->dateEdit->height())));
widget->show();
}
}
}
return false;
}
OR :: Alternatively we can use QCalendarWidget provided by dateEdit, though its not much efficient as turing it to Popup will mess with its internal. Give it a shot if you want
#include "mainwindow.h"
#include "ui_mainwindow.h"
#include <QCompleter>
#include <QCalendarWidget>
#include <QMouseEvent>
MainWindow::MainWindow(QWidget *parent) :
QMainWindow(parent),
ui(new Ui::MainWindow)
{
ui->setupUi(this);
ui->dateEdit->setDate(QDate::currentDate());
widget = ui->dateEdit->calendarWidget();
widget->setWindowFlags(Qt::Popup);
ui->dateEdit->installEventFilter(this);
//connecting widget with dateedit
ui->dateEdit->setButtonSymbols(QAbstractSpinBox::NoButtons);
ui->dateEdit->setCalendarPopup(true);
connect(widget,SIGNAL(clicked(QDate)),ui->dateEdit,SLOT(setDate(QDate)));
}
MainWindow::~MainWindow()
{
delete ui;
}
bool MainWindow::eventFilter(QObject *object, QEvent *event)
{
if (object == ui->dateEdit)
{
if (event->type() == QEvent::FocusIn || event->type() == QEvent::MouseButtonPress)
{
// WE NEED MOUSE EVENT TO AVOID INTERFERNCE WITH CALENDAR POPUP BUTTON SITUATED AT CORNER OF dateEdit WIDGET
if(widget->isVisible()==false && ( ((QMouseEvent* )event)->x()< (ui->dateEdit->width()-10)))
{
widget->move(ui->dateEdit->mapToGlobal(QPoint(0,ui->dateEdit->height())));
widget->show();
}
}
}
return false;
}
I'd like to offer option similar to #Dr. Xperience's answer that encapsulates calendar widget in QDateEdit subclass:
#include <QDateEdit>
#include <QCalendarWidget>
class DateEdit : public QDateEdit {
Q_OBJECT
public:
explicit DateEdit(QWidget *parent = nullptr);
protected:
virtual void focusInEvent(QFocusEvent *event) override;
private:
QCalendarWidget *calendar = new QCalendarWidget(this);
};
DateEdit::DateEdit(QWidget *parent) : QDateEdit (parent) {
setButtonSymbols(QAbstractSpinBox::NoButtons);
setCalendarPopup(false);
setDate(QDate::currentDate());
calendar->setWindowFlags(Qt::Popup);
connect(calendar, &QCalendarWidget::clicked, this, [&](const QDate &date) {
setDate(date);
calendar->hide();
});
}
void DateEdit::focusInEvent(QFocusEvent *event) {
if (!calendar->isVisible()) {
calendar->setSelectedDate(date());
calendar->move(mapToGlobal(QPoint(0, height())));
calendar->show();
}
return QDateEdit::focusInEvent(event);
}
Warning: If you place this widget using QtDesigner, it will override buttonSymbols and calendarPopup properties, so you have to set it manually to hide QDateEdit's buttons.
Here is my hacky approach to the issue. After fighting for quite a while to have something clean, I read the source code of QDateEditor (which in fact is just a simplified QDateTimeEditor) and it seems to be no clean solution. The following is code for toggle() rather than show(), but still:
// Enable the calendar popup
date_editor->setCalendarPopup(true);
// Show the calendar popup by default
// There seems to be no proper interface to achieve that
// Fake a mouse click on the right-hand-side button
QPointF point = date_editor->rect().bottomRight() - QPointF{5, 5};
QCoreApplication::postEvent(
date_editor,
new QMouseEvent(QEvent::MouseButtonPress, point, Qt::LeftButton,
Qt::LeftButton, Qt::NoModifier));
Using something like this you can keep relying on the editor's validation features.
BTW, another annoying thing about the built-in editor that makes a QLineEdit tempting is that (at least in my case) the keyboard cursor is not shown by default. This is very confusing. To solve this I did:
// Select a section so that the cursor is be visible
date_editor->setSelectedSection(QDateTimeEdit::DaySection);
This or course selects the day section of the date, but if you use keyboard arrows the selection vanished, but you can see the keyboard cursor.

Resources