Triggering an action based on its custom shortcut - qt

Suppose I have some action to happen. For that I can create a QAction object and connect its triggered() signal to the slot that executes the desired function. Also, I can have a shortcut associated with the action; by changing the shortcut I'll be able to execute the same action with that shortcut.
My problem now is that the "shortcut" I wanna set to the action, contains also a mouse button press (and mouse events cannot be assigned to action shortcuts); say I want Shift+Left mouse button. Maybe this sounds a little bit harsh but bear with me.
What do I need? Well, I have a button, and an action (say "execute a script"). I want the script to execute when Shift+Left click is clicked, and I want this "shortcut" to be customized, i.e. the user should be able to change to shortcut to, say Ctrl+Left click (from some GUI element, e.g. button text), and now Ctrl+Left click should execute the script.
How can I achieve this?

Note: I as a user would expect an action triggered by a mouse button to be position dependent. If so, the following gets a bit simpler.
Qt doesn't have an option to specify such a shortcut.
You can roll your own by reacting to mouse events:
Maybe you have an event handler mousePressEvent(),
or a generic eventFilter(QObject *obj, QEvent *evt),
or utilize QApplication::notify
Whichever, at some place you need to catch a QMouseEvent *mouseEvt.
Choose the widget (or qApp) that is as outmost as needed.
There, compare mouseEvt->button() and mouseEvt->modifiers() to your list of actions and trigger the selected action. When the user chooses another trigger method, adjust your list of actions.
Let's put this to practice:
class MainWindow : public QWidget {
Q_OBJECT
public:
QMap<QPair<Qt::MouseButton, Qt::KeyboardModifiers>, QAction*> mapMouseShortcuts;
QAction *pLaunchScript;
MainWindow() : QWidget() {
mapMouseShortcuts.insert(qMakePair(Qt::LeftButton, Qt::ControlModifier), pLaunchScript);
}
void mousePressEvent(QMouseEvent *me) {
QAction *action = mapMouseShortcuts.value(qMakePair(me->button(), me->modifiers()), Q_NULLPTR);
if(action != Q_NULLPTR) {
action->trigger();
me->accept(); // optional
}
// optional:
if(!me->isAccepted()) {
QWidget::mousePressEvent(me);
}
}
};

Related

How to find subsequent (all) SLOT from a subclass

I need to block specific buttons on an MMI.
I implemented a button blocking function in a subclass of QPushButton.
For this, I used the clicked() signal and blocked the button with blockSignals(true).
This means that with each button clicked on my MMI, 2 SLOTS are always called.
But when calling the blocking of a specific button, I get the first SLOT (clicked()) of my subclass, in which I block the button, then I then arrive in the original SLOT linked to this button, which is still called despite the blocking (the first time only).
How can I in my QPushButton subclass know the subsequent SLOTs linked to this button and avoid them (delete them)?
void QbtnStandardButton::slotButtonClicked(void)
{
if (modeProtection)
{
// Special mode to protect/unprotect the button
if (isProtected())
{
// Reset the protection
this->blockSignals(false);
}
else
{
// Set the protection: button will be unclickable
this->blockSignals(true);
}
modeProtection = false;
}
if (isProtected())
{
QMessageBox *pMsgBox = new QMessageBox(QMessageBox::Information,
"Protection",
"This button is protected!",
QMessageBox::Ok);
pMsgBox->exec();
pMsgBox->deleteLater();
// Here: remove subsequent SLOT of this button ?
}
}
I think it's very difficult if not impossible to find SLOTS linked to a button.
I worked around the problem by using an eventFilter() instead of a SIGNAL() in my base class.
In this case, I can filter the "clicked()" event before it is reissued.

How to hide a QWidget when mouse clicked on out side that widget

I want hide a QWidget when mouse clicks out of that widget just like it have a popup flag:
auto widget = new QWidget(this);
widget->setWindowFlag(Qt::Popup); // this widget will hide, when mouse click out of that widget.
For some reason, I can't set these flags and must implement some thing myself which behaves like this.
Or can I get a mouse event out of that widget?
Solution:
As I said, I can't use Qt flags and have to implement the similar behavior myself.My solution is installEventFilter to QApplication, in the override method eventFilter,I filter the QMouseEvent and send a signal.
Yes, you can get a mouse event out of the widget.
Make a custom widget and reimplement mousePressEvent, which will catch the last click (the ouside-the-widget click that hides the "popup"). But be careful to add the call to QWidget::mousePressEvent(event) in the end, otherwise the last click will be lost and your widget will remain onscreen.
CustomWidget::CutomWidget(QWidget *parent) : QWidget(parent)
{
setWindowFlags(Qt::Popup);
}
void CustomWidget::mousePressEvent(QMouseEvent *event)
{
if (!this->underMouse()) {
//if the click is not on the widget, i.e. if it's the click that hides it,
// you caught it, do what you want to do here.
}
QWidget::mousePressEvent(event);
}
Hope it helps.

"please wait" during long operation in Qt?

I need to do an operation after a user clicking a button, and that may take some time. How can I display a "busy" or "please wait" icon/message (like a spinning circle etc) and prevent a user from clicking the button again (or some other buttons) during the time? Thanks.
Use your QApplication object's static function setOverrideCursor(const QCursor &cursor)
Set the wait cursor, to make the application "busy", as shown below.
QApplication::setOverrideCursor(QCursor(Qt::WaitCursor));
And restore using below function, when you feel the work is done
QApplication::restoreOverrideCursor();
some documentation help:
http://doc.qt.io/qt-5/qguiapplication.html#setOverrideCursor
Now to disable the window, override the event filter function of your window class.
declare a global variable, that says "busy" or "notbusy". ex: _isBusy.
In the event filter do something as shown below.
eventfilter is a virtual function of QObject.
So you can override in your window class as shown below.
bool MainWindow::eventFilter(QObject *obj, QEvent *event)
{
if (_isBusy) //your global variable to check "busy or "notbusy"
{
//Just bypass key and mouse events
switch(event->type())
{
case QEvent::MouseButtonPress:
case QEvent::MouseButtonRelease:
case QEvent::KeyPress:
case QEvent::KeyRelease:
return true;
}
}
}
Note: "MainWindow" in the above function declaration is your class name. Also in the switch statement, I limited the count to only 4 events, add as many events you want.

Qt Menu with QLinEdit action

I want to have a line edit field in a popup menu I've got. I'm basically letting the user pick from one of several common sizes for something, but I want them to be able to enter a custom size as the last entry in the menu.
So I've got something like this (snipped from larger code, new_menu is the menu of interest):
QWidget *widget = new QWidget(new_menu);
QHBoxLayout *layout = new QHBoxLayout;
QLineEdit* le = new QLineEdit;
le->setPlaceholderText("Custom");
le->setFixedWidth(100);
ayout->addWidget(le);
widget->setLayout(layout);
QWidgetAction* wa = new QWidgetAction(new_menu);
wa->setActionGroup(group);
wa->setDefaultWidget(widget);
new_menu->addAction(wa);
connect(le, SIGNAL(returnPressed()), this, SLOT(leslot()));
Which works great, the LineEdit shows up nice and centered in the menu, it's got the placeholder text, I can click it and edit, everything. However, when I hit enter on the textBox, it emits the returnPressed signal and the menu emits a triggered signal with one of the other actions on the list, so at best I'm changing my configuration twice and at worst things break.
Additionally, when I click off the edge of the LineEdit (still in the menu though, but not in the editable area), the menu emits a triggered signal with the QWidgetAction associated with it, which isn't what I want.
So two questions:
1) Can I get the return to work the way I want. It's fine if the menu closes when it's hit, but it can't emit another action too.
2) Can I get it to not emit an action at all when the lineEdit is clicked?
Here's what I ended up doing for anyone that follows. I subclassed QLineEdit thusly:
class EnterLineEdit : public QLineEdit {
Q_OBJECT
public:
void keyPressEvent(QKeyEvent *evt) {
if (evt->key() == Qt::Key_Enter || evt->key() == Qt::Key_Return) {
emit returnPressed();
} else {
QLineEdit::keyPressEvent(evt);
}
}
};
This lets me manually emit the returnPressed signal when enter/return is hit and not pass it up the widget hierarchy, so the menu never sees it when enter is hit over the lineedit. I connected the returnPressed signal to the hide() slot of the menu so that the menu will still close, but without triggering an action.

Cannot change cursor in Qt main window from menu action

In my MainWindow, I have a push button and a menu bar item whose signals are both connected to the same slot. In the slot function, I have written:
mainWindow->setCursor(QCursor(Qt::WaitCursor));
This works as expected when the slot function is invoked via the button; however, when the same function is invoked from the menu, the wait cursor doesn't appear. Any idea why?
I also considered using QApplication::setOverrideCursor; however, that causes other problems.
Any recommendations? Thanks!
(I am using Qt 4.7 and doing my development on Windows 7 using Qt Creator with the default MinGW compiler.)
Here's more detail.
in MainWindow constructor: this->setCursor(Qt::CrossCursor);
signal/slot connections:
QObject::connect(button, SIGNAL(clicked()), MainWindow, SLOT(showMessageBox()));
QObject::connect(action, SIGNAL(triggered()), MainWindow, SLOT(showMessageBox()));
showMessageBox function:
void MainWindow::showMessageBox()
{
this->setCursor(Qt::WaitCursor);
// display wait cursor briefly before showing message box
for (int i = 0; i < 1<<30; ) {++i;}
QMessageBox msgBox;
msgBox.setText("Hello!");
msgBox.setStandardButtons(QMessageBox::Ok);
msgBox.setCursor(Qt::PointingHandCursor);
msgBox.exec();
this->setCursor(Qt::CrossCursor);
}
When showMessageBox is invoked with 'button', the wait cursor is displayed as expected.
When showMessageBox is invoked through 'action', the wait cursor does not appear; instead the cursor changes from Qt::CrossCursor to a Qt::ArrowCursor as soon as the user selects the 'action' menu item, and then changes to Qt::PointingHandCursor once the message box opens. The wait cursor never appears.
Your code is synchronous and uses a delay loop. When you're in the delay loop, there's no way for any Qt code to execute. A cursor change requires the event loop to be spinning -- so it appears from the symptoms you give.
Here's how to do it correctly -- remember, if you use delays/sleeps and other blocking calls in your GUI code, your users will hate you, and rightly so. Using exec() in message/dialog boxes is also bad style. Your application is asynchronous, code it so. Make sure your slots are declared as such (in the protected slots: section of MainWindow declaration).
void MainWindow::showMessageBox()
{
this->setCursor(Qt::WaitCursor);
QTimer::singleSlot(200, this, SLOT(slot1()); // fire slot1 after 200ms
}
void MainWindow::slot1()
{
QMessageBox * msgBox = new QMessageBox(this);
msgBox->setText("Hello!");
msgBox->setStandardButtons(QMessageBox::Ok);
msgBox->setCursor(Qt::PointingHandCursor);
msgBox->show();
connect(msgBox, buttonClicked(QAbstractButton*), SLOT(slot2(QAbstractButton*)));
}
void MainWindow::slot2(QAbstractButton* button)
{
// a button was clicked on the message box
this->setCursor(Qt::CrossCursor);
}

Resources