I want to switch from one QToolButton to another in QToolBar. I have used QStackedWidget, their it is too simple to move from one widget to other but here I am not able to get how to move via using keyReleaseEvent.
mywindow::mywindow() : QMainWindow()
{
widget = new QWidget();
setCentralWidget(widget);
tool = new QToolBar();
vertical = new QVBoxLayout();
button1 = new QToolButton();
connect( button1, SIGNAL(clicked()), this, SLOT(fileNew()) );
button2 = new QToolButton();
button3 = new QToolButton();
button1->setIcon(QIcon("download.jpg"));
button1->setGeometry(0,0,100,200);
button2->setIcon(QIcon("images.jpg"));
button3->setIcon(QIcon("settings-icon.jpg"));
//stack1->addWidget(button1);
//stack1->addWidget(button2);
//stack1->addWidget(button3);
tool->addWidget(button1);
tool->addWidget(button2);
tool->addWidget(button3);
//tool->addWidget(stack1);
vertical->addWidget(tool);
widget->setLayout(vertical);
}
void mywindow::keyReleaseEvent(KeyEvent *event)
{
switch(event->key())
{
case:Qt::Key_Left:
}
}
You need to check against the focus, and shift that as appropriate. I would write something like this:
void mywindow::keyReleaseEvent(KeyEvent *event)
{
switch(event->key())
{
case:Qt::Key_Left:
if (button3->hasFocus())
button2->setFocus();
else if (button2->hasFocus())
button1->setFocus();
break;
case:Qt::Key_Right:
if (button1->hasFocus())
button2->setFocus();
else if (button2->hasFocus())
button3->setFocus();
break;
}
}
Note that this code can go tedious easily if you keep adding further buttons. I would place them into a container. Then, I would iterate through that container forward and reverse order depending on the focus switching logic.
See the documentation for further details:
focus : const bool
This property holds whether this widget (or its focus proxy) has the keyboard input focus.
By default, this property is false.
Note: Obtaining the value of this property for a widget is effectively equivalent to checking whether QApplication::focusWidget() refers to the widget.
Access functions:
bool hasFocus() const
Related
I'm working on a way of creating QWidgets that respond to touch, running as a plugin to a proprietary Qt 5.2 application. The application provides touch and gesture events (QTouchEvent, QGestureEvent), but no mouse events.
I have no control over the application itself, apart from what can be done at runtime, and I'm trying to be as minimally invasive as I can.
EDIT: To clarify the above paragraph, my code is being loaded automatically by Qt as a 'fake' image plugin. The host application appears to not have set Qt::AA_SynthesizeMouseForUnhandledTouchEvents, and when I tried to do it... things did not go well. The application has it's own set of touch and gesture handlers and Widgets it uses.
One option is to subclass each QWidget type and add custom event handling for touch events. I would rather not do this, however, as it would be tedious, and I don't really need advanced touch/gesture functionality for the most part.
Another option, which I've been trialing is to install an eventFilter on the widgets, which creates mouse events from the touch events, and sends the mouse event to the widget instead. This is working quite well for many simpler widgets, and even complex widgets like QFileDialog mostly work.
My problem comes with certain widget types (I've come across two so far), which either partially work, or don't work at all, and I'm not sure where I'm going wrong.
An example of a partially working widget with the eventFilter technique is the QComboBox. The combo box itself responds to the touch, and opens the dropdown list. However, the dropdown list itself just doesn't seem to work with the touch.
QDialogButtonBox is the most recent widget I've discovered that doesn't work. The eventFilter seems to trigger on the QDialogButtonBox itself, but not the button(s) for some reason.
Here are the relevant bits of the current (very) WIP experimental code:
From NDBWidgets.h
void setTouchFilter(QWidget *w);
class TouchFilter : public QObject
{
Q_OBJECT
public:
TouchFilter(QWidget* parent = nullptr);
protected:
bool eventFilter(QObject *obj, QEvent *event) override;
};
And from NDBWidgets.cc
static void installTouchFilter(QWidget *w)
{
if (!w->property(touchFilterSet).isValid()) {
w->setProperty(touchFilterSet, QVariant(true));
w->setAttribute(Qt::WA_AcceptTouchEvents);
auto ef = new TouchFilter(w);
w->installEventFilter(ef);
}
}
void setTouchFilter(QWidget *w)
{
if (!w) {
return;
}
auto children = w->children();
nh_log("installing eventFilter on the following children:");
for (int i = 0; i < children.size(); ++i) {
if (auto wi = qobject_cast<QWidget*>(children.at(i))) {
nh_log(" Class: %s Name: %s", children.at(i)->metaObject()->className(), children.at(i)->objectName().toUtf8().constData());
installTouchFilter(wi);
// if (auto cb = qobject_cast<QComboBox*>(wi)) {
// setTouchFilter(cb->view());
// }
}
}
installTouchFilter(w);
}
TouchFilter::TouchFilter(QWidget *parent) : QObject(parent) {}
bool TouchFilter::eventFilter(QObject *obj, QEvent *event)
{
// Only care about widgets...
auto widget = qobject_cast<QWidget*>(obj);
if (!widget) {
return false;
}
auto type = event->type();
if (type == QEvent::TouchBegin || type == QEvent::TouchUpdate || type == QEvent::TouchEnd) {
event->accept();
auto tp = static_cast<QTouchEvent*>(event)->touchPoints().at(0);
nh_log("eventFilter triggered for class: %s name: %s", widget->metaObject()->className(), widget->objectName().toUtf8().constData());
if (type == QEvent::TouchBegin) {
nh_log("event TouchBegin captured");
QMouseEvent md(QEvent::MouseButtonPress, tp.pos(), tp.screenPos(), Qt::LeftButton, Qt::LeftButton, Qt::NoModifier);
QCoreApplication::sendEvent(widget, &md);
} else if (type == QEvent::TouchUpdate) {
nh_log("event TouchUpdate captured");
QMouseEvent mm(QEvent::MouseMove, tp.pos(), tp.screenPos(), Qt::LeftButton, Qt::LeftButton, Qt::NoModifier);
QCoreApplication::sendEvent(widget, &mm);
} else if (type == QEvent::TouchEnd) {
nh_log("event TouchEnd captured");
QMouseEvent mu(QEvent::MouseButtonRelease, tp.pos(), tp.screenPos(), Qt::LeftButton, Qt::LeftButton, Qt::NoModifier);
QCoreApplication::sendEvent(widget, &mu);
}
return true;
}
return false;
}
An example usage:
void NDBDbus::dlgComboBox() {
NDB_DBUS_USB_ASSERT((void) 0);
auto d = new QDialog();
d->setAttribute(Qt::WA_DeleteOnClose);
auto vl = new QVBoxLayout();
auto cb = new QComboBox(d);
cb->addItem("Item 1", QVariant(1));
cb->addItem("Item 2", QVariant(2));
vl->addWidget(cb);
auto bb = new QDialogButtonBox(QDialogButtonBox::Close, d);
connect(bb, &QDialogButtonBox::rejected, d, &QDialog::reject);
vl->addWidget(bb);
d->setLayout(vl);
setTouchFilter(d);
d->open();
}
I'm still very new to Qt event handling, and eventFilters, and I've gotten to the point where I'm rather stumped, so if anyone has any suggestions or ideas, I would very much appreciate them!
So, TLDR, and the final question: Is it possible to transform touch events to mouse events inside an eventFilter for widgets such as QComboBox or QDialogButtonBox? And if so, how?
I have listwidgetitem's that have a custom widget (step_widget) which contains 3 QPushButtons and a QLabel.
When I press one of the buttons in the widget, it is not triggering itemClicked which i need to see the index of the listwidget that was clicked. If i click the QLabel, the signal is triggered and i am able to obtain the index. How can trigger the signal when one of the buttons is pressed? Why does it not trigger?
QListWidgetItem *item = new QListWidgetItem();
stepWidget *step_widget = new stepWidget();
ui->listWidget->addItem(item);
ui->listWidget->setItemWidget(item,step_widget);
The itemClicked() signal is not emmited because the QPushButton consumes the click event.
There is the simplest solution I've found.
First, in your class stepWidget add a signal that will be emitted when any QPushButton is clicked, for example:
signals:
void widgetClicked();
Then connect the clicked() signal of every QPushButton with that signal. This code is in the constructor of the widget stepWidget:
connect(ui->pushButton1, &QPushButton::clicked, this, &stepWidget::widgetClicked);
connect(ui->pushButton2, &QPushButton::clicked, this, &stepWidget::widgetClicked);
connect(ui->pushButton3, &QPushButton::clicked, this, &stepWidget::widgetClicked);
To test it I added 10 widgets to a QListWidget that reside in the MainWindow class. You must connect that signal to a slot (in this case I use a C++11 Lambda Function, but you can create a slot instead, it's fine) where you establish the current item as the item under the widget. This code is located in the constructor of MainWindow:
for (int i = 0; i < 10; ++i) {
QListWidgetItem *item = new QListWidgetItem;
stepWidget *step_Widget = new stepWidget;
ui->listWidget->addItem(item);
ui->listWidget->setItemWidget(item, step_Widget);
connect(step_Widget, &stepWidget::widgetClicked, [=]() {
ui->listWidget->setCurrentItem(item);
});
}
Now, this will not make the itemClicked() signal to be emitted because the user not clicked the item. But I advice you to connect instead the signal currentRowChanged() as it will be emitted with this code and also it has the advantage that it allows you to directly obtain the row. Once you have the row you can obtain the item and the widget, evidently.
However, this procedure has the inconvenient that the currentRowChanged() signal will be emitted even when the user selects a row with the keyboard or when the user press and slide the mouse over the QListWidget without releasing it.
On workaround could be, for example, using a flag as an attribute for your MainWindow class that is always false except during this connection:
connect(ste_pWidget, &stepWidget ::widgetClicked, [=]() {
buttonPressed = true;
ui->listWidget->setCurrentItem(item);
buttonPressed = false;
});
Then you must manipulate two signals from the QListWidget: clicked() and currentRowChanged(). I choose clicked() instead of itemClicked() because it gives almost directly access to the row:
void MainWindow::on_listWidget_clicked(const QModelIndex &index)
{
ui->statusBar->showMessage(tr("Row clicked: %1").arg(index.row()), 1000);
}
void MainWindow::on_listWidget_currentRowChanged(int currentRow)
{
if (buttonPressed) {
ui->statusBar->showMessage(tr("Row clicked: %1").arg(currentRow), 1000);
}
}
what you can simply do is, make your qpushbutton to qlabel and then qpushbutton will become clickable.
You can then override this method :- on_listWidget_itemClicked(QListWidgetItem *item) for double click on qpushbutton.
I tried it & it worked perfectly, its quite simple workaround.
I've made some QPushbuttons like QPushButton **btn and I want to know when the user clicks on one of them using QMouseEvent here is the code but this idea does not work at all any ideas??
void Game::mousePressEvent(QMouseEvent *ev)
{
if(ev->button() == Qt::LeftButton)
{
btn[ev->x()][ev->y()].setStyleSheet("background-color : black;");
}
else
{
btn[ev->x()][ev->y()].setStyleSheet("background-color : red;");
}
that else part is for right click
and here is the code that generates the buttons
void Game::MakeButton()
{
btn = new ApButton*[column];
hrztl = new QHBoxLayout[column];
hrztl->setSpacing(0);
for(int i=0; i<column;i++)
{
btn[i] = new ApButton[row];
for(int j=0; j<row; j++)
{
btn[i][j].setRowCol(i,j);
btn[i][j].setFixedSize(50,50);
hrztl[i].addWidget(&btn[i][j]);
}
ui->MainLayout->addLayout(&hrztl[i]);
}
ui->MainLayout->setSpacing(0);
}
ApButton is a class that inherits QPushButton
This is a good example of use for a QSignalMapper, as seen there: http://qt-project.org/doc/qt-5.0/qtcore/qsignalmapper.html#details
ButtonWidget::ButtonWidget(QStringList texts, QWidget *parent)
: QWidget(parent)
{
signalMapper = new QSignalMapper(this);
QGridLayout *gridLayout = new QGridLayout;
for (int i = 0; i < texts.size(); ++i) {
QPushButton *button = new QPushButton(texts[i]);
connect(button, SIGNAL(clicked()), signalMapper, SLOT(map()));
signalMapper->setMapping(button, texts[i]);
gridLayout->addWidget(button, i / 3, i % 3);
}
connect(signalMapper, SIGNAL(mapped(QString)),
this, SIGNAL(clicked(QString)));
setLayout(gridLayout);
}
In that example, every button is identified by its title, as a QString. The mapper allows you to retrieve the corresponding button's title when one of them is clicked.
Switch your
Game::mousePressEvent(QMouseEvent *e)
to
ApButton::mousePressEvent(QMouseEvent *e)
since you are trying to implement the Press Event of the Button. If you only want to have the moment of the button being pressed and not changing Button behaviour with this, use a SIGNAL/SLOT connection instead of reimplementing the event (add to your creation):
connect(btn[i][j], SIGNAL(pressed()), this, SLOT(slotButtonPressed());
void Game::slotButtonPressed(){
//doSomething
}
use a QButtonGroup or the QSignalMapper if you need to identify more then one Button in a single method or use QObject::sender(), but this can be tricky sometimes.
I had a similar situations some times ago.. I had a QDialog and I had to dinamically add some QPushButton.. then I need to know on which button the user pressed.. so I needed something like:
connect( btn, SIGNAL( clicked(int) ),
this, SLOT( handle(int) ));
for instance a signal-slot connection with the id of the clicked button and a function for handle the click. The function is the same for each buttons and can handle the specific button because of the id..
Implementing this is quite simple subclassing QPushButton adding an id and a new signal..
Hope it's some help!
If Apbutton inherits QPushButton, why don't connect to clicked()?
then you can call QObject::sender()
On slot:
ApButton *but = dynamic_cast<ApButton*>QObject::sender()
if(but)
{
but->setStyleSheet("background-color : black;");
}
to get the clicked buttonbutton and set its stylesheet
I have grid of QFrames in QGridLayout and a popup menu with some actions, which are targeted on the cell where mouse right click happens. On the implementation of ContextMenuEvent I get clicked QPoint using common event->pos() but how I get access to my correct cell object by that point? Or is there some better alternative solution path for this purpose?
void X::contextMenuEvent(QContextMenuEvent* event)
{ // QPoint target = event->pos();
// TODO: m_gridLayout-> ...
// myDerivedCell->setSomething();
}
There are a bunch of solutions here. The simplest is to go through your widgets, calling bool QWidget::underMouse () const. My favorite is this:
frame_i->setContextMenuPolicy(Qt::CustomContextMenu);
connect(frame_i, SIGNAL(customContextMenuRequested(QPoint))
, SLOT(onContextMenu(QPoint)));
...
void X::onContextMenu(const QPoint &pos)
{
QFrame *w = qobject_cast < QFrame * >(sender());
...
}
I've been looking for ways to implement the "Edit" menu of Qt application. The "Edit" menu contains items such as "Copy", "Cut", "Paste", etc. and which need to forward to the currently active widget.
I can't seem to find a standard or elegant way to do this. According to this question, it's not possible:
How to implement the "Edit" menu with "Undo", "Cut", "Paste" and "Copy"?
I recently had the idea to trigger a context menu event on the current active widget when the "Edit" menu is shown, via:
// create menus in MainWindow constructor
...
edit_menu = menuBar()->addMenu(tr("&Edit"));
connect(edit_menu, SIGNAL(aboutToShow()), this, SLOT(showEditMenu()));
...
// custom slot to handle the edit menu
void MainWindow::showEditMenu()
{
QWidget* w = QApplication::focusWidget();
// show the context menu of current focus widget in the menubar spot
QPoint global_pos = edit_menu->mapToGlobal(edit_menu->rect().bottomLeft());
QPoint pos = w->mapFromGlobal(global_pos);
QApplication::sendEvent(w, new QContextMenuEvent(QContextMenuEvent::Keyboard, pos, global_pos));
}
This shows the a context menu for the current widget great, but has some problems. For example, it takes focus away from the menubar, or if you click a different menubar item first, the menubar has focus, etc.
One partial solution would be to grab the context menu from the widget and copy it's items into the edit menu dynamically. Is there a way to do this?
Is there a better way build the edit menu in Qt?
Thanks for your help.
well, if you just need to create menu, you can always take actions from actions of a widget. To create edit actions for widgets, you can do something like this:
void MainWindow::addActions (QWidget* widget)
{
QAction * copyAction = new QAction("copy",widget);
if(connect(copyAction,SIGNAL(triggered()),widget,SLOT(copy())))
{
widget->addAction(copyAction);
qDebug()<<"success connection";
}
}
and
foreach (QObject * obj, centralWidget()->children())
{
QWidget * w = dynamic_cast<QWidget*>(obj);
if (w)
addActions(w);
}
then you always can update actions of edit menu with focused widget's actions
This may be not elegant, but it better, than imporssible. The main bad assumption in example is that copy slot is named copy
An elegant solution I guess would be to have a base class for the widgets you need to have copy/paste/... functionality, and have them register themselves with some parent class upon becoming active and unregistering when being deactivated. The actions can then just be connected to slots in the main window, which forwards them to the registered widget. You could even gray out the menu items if no widget is currently registered (e.g. because the active widget does not have the required functionality).
An example for registering/unregistering (untested):
class ActionWidget;
class ActionWidgetManager
{
public:
ActionWidgetManager() : actionWidget_(0){}
void registerWidget(ActionWidget* widget){ actionWidget_ = widget; }
void unregisterWidget(ActionWidget* widget)
{ if (actionWidget_ == widget) actionWidget_ = 0; }
bool hasActiveWidget() const{ return actionWidget_ != 0; }
ActionWidget* getActiveWidget(){ return actionWidget_; }
private:
ActionWidget* actionWidget_;
};
class ActionWidget : public QWidget
{
public:
ActionWidget(ActionWidgetManager* manager, QWidget* parent=0)
: manager_(manager), QWidget(parent) {}
~ActionWidget(){ manager_->unregisterWidget(this); }
void Widget::changeEvent(QEvent *event)
{
QWidget::changeEvent(event);
if(event->type() == QEvent::ActivationChange){
if(isActiveWindow()) {
manager_->registerWidget(this);
}
else {
manager_->unregisterWidget(this);
}
}
}
virtual void doCopy() = 0;
virtual void doPaste() = 0;
virtual void doUndo() = 0;
virtual void doCut() = 0;
private:
ActionWidgetManager* manager_;
};
Or something equivalent using signals and slots.