Should I use KeyPressEvent or QAction to implement keypresses? - qt

In Qt, either implementing keyPressEvent or creating a QAction and assigning it a key combination allow me to act based on the keyboard.
Which of these methods is generally preferred?

You should use QAction whenever the same event that is triggered by the key sequence you want may be triggered through other ways like from a menu, toolbar or other buttons. This way you can use the same action on several widgets that should do the same trick.
Excerpt from QAction doc:
The QAction class provides an abstract
user interface action that can be
inserted into widgets.
In applications many common commands
can be invoked via menus, toolbar buttons, and
keyboard shortcuts. Since the user
expects each command to be performed
in the same way, regardless of the
user interface used, it is useful to
represent each command as an action.

I'd prefer to overwrite the keyPressEvent. I don't like the idea of a QAction "lying around somewhere". Just overwrite the keyPressedEvent. I usually do it with a switch-case in which I check the pressed key. Just don't forget to call the keyPressEvent of the base class if you don't want to disable the standard behaviour of a key. Additionally you can check if a "modifier" is pressed while a keyPressEvent occurs. (e.g. Shift or Ctrl). IMHO for general purposes overwriting the keyPressEvent is better than creating invisible, secret actions, unless you want your application to contain all those actions visible for the user.
void my_widget::keyPressEvent( QKeyEvent* p_event )
{
bool ctrl_pressed = false;
if( p_event->modifiers() == Qt::ControlModifier )
{
ctrl_pressed = true;
}
switch( p_event->key() )
{
case Qt::Key_F:
focus_view();
break;
case Qt::Key_I:
if( ctrl_pressed )
{
toggle_interface();
}
else
{
QWidget::keyPressEvent( p_event );
}
break;
case Qt::Key_Return: // return key
case Qt::Key_Enter: // numpad enter key
update_something();
break;
default:
QSpinBox::keyPressEvent( p_event );
}
}

Would depend on what you need it for.
Is it for a menu like action that may be triggered by a menu, button, toolbar too, then go for the QAction. Especially if this action should work all over your program, not only in a single widget.
Is it more like a local activity in a single widget (say for example controlling movement in a game), I would use the keypress event.

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.

Triggering an action based on its custom shortcut

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);
}
}
};

"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.

How to filter out «back» events in a [Qt] web view?

I develop an application with WebKit-based forms and it’s important that, when on a form, a user can press Backspace without returning to previous page. How do I do this in QtWebKit?
I found out that one can inherit a class from QWebPage and overload QWebPage::triggerAction() to selectively filter out events, e.g. QWebPage::Back. Nevertheless, it works only on the first page, and if you open another page in the same webview child the triggerAction() overload will not be called.
For now, answer is «there is no simple way to do it». I've got and answer on Qt's bug tracker, see https://bugreports.qt-project.org/browse/QTBUG-35555
Hope this will be fixed.
You could try one of these alternatives:
1. eventFilter:
bool CWebView:eventFilter( QObject *pObj, QEvent *event )
{
QKeyEvent* pkeyEvent = (QKeyEvent*)event;
if (Qt::ControlModifier == pKeyEvent->state())
{
switch (pKeyEvent->key())
{
case Qt::Key_Backspace:
//something (eg. return false)
break;
}
}
return true;
}
2. clear history:
#include <QWebHistory>
...
QWebView* CWebView;
...
CWebView->history()->clear();
3. Javascript (I consider the best alternative):
window.location.replace(url);
after using replace() the current page will not be saved in session
history, meaning the user won't be able to use the Back button to
navigate to it.
source: https://stackoverflow.com/a/8969975/1518921

How to detect QTableWidget scroll source (code itself/user (wheel)/user (scrollbar))?

I'm writing a program using Qt 4.8 that displays a table (QTableWidget) filled with filenames and file's params. First an user adds files to the list and then clicks process. The code itself updates the contents of the table with simple progress description. I want the table by default to be scrolled automatically to show the last processed file and that code is ready.
If I want to scroll it by hand the widget is being scrolled automatically as soon as something changes moving the viewport to the last element. I want to be able to override the automated scroll if I detect that it was the user who wanted to change view.
This behavior can be seen in many terminal emulator programs. When there's a new line added the view is scrolled but when user forces the terminal to see some previous lines the terminal does not try to scroll down.
How could I do that?
Solution:
I created an object which filters event processed by my QTableWidget and QScrollBar embedded inside. If I spot the event that should turn off automatic scrolling I just set a flag and stop scrolling view if that flag is set.
Everything is implemented inside tableController class. Here are parts of three crucial methods.
bool tableController::eventFilter(QObject* object, QEvent* event)
{
switch (event->type())
{
case QEvent::KeyPress:
case QEvent::KeyRelease:
case QEvent::Wheel:
case QEvent::MouseButtonDblClick:
case QEvent::MouseButtonPress:
case QEvent::MouseButtonRelease:
_autoScrollEnabled = false;
default:
break;
}
return QObject::eventFilter(object, event);
}
void tableController::changeFile(int idx)
{
[...]
if (_autoScrollEnabled)
{
QTableWidgetItem* s = _table.item(_engine.getLastProcessed(), 1);
_table.scrollToItem(s);
}
[...]
}
void tableController::tableController()
{
[...]
_autoScrollEnabled = true;
_table.installEventFilter(this);
_table.verticalTableScrollbar()->installEventFilter(this);
[...]
}
Thanks for all the help. I hope somebody will find it useful :)
Subclass QTableWidget and overload its wheelEvent. You can use the parameters of the supplied QWheelEvent object in order to determine if the user scrolled up or down.
Then use a simple boolean flag which is set (or reset) in your wheelEvent override. The method which is responsible for calling scrollToBottom() should then consider this boolean flag.
You will have to find a way to figure out when to set or reset that flag, e.g. always set it when the user scrolls up and reset it when the user scrolls down and the currently displayed area is at the bottom.
connect(_table->view()->verticalScrollBar(), &QAbstractSlider::actionTriggered, this, [this](int) {
_autoScrollEnabled = false;
});

Resources