I want to generate the right click menu from the entry of a QTreeView. Currently I tried this, but I don't want the whole treeView to generate the right click and then me to filter the position on which the mouse is. I want that the signal to be generated from the entry.
connect(mtreeView, SIGNAL(customContextMenuRequested(const QPoint&)),
this, SLOT(showContextMenu(const QPoint&)));
Thanks!
Method 1
It is better to use the ContextMenuEvent rather than MouseReleaseEvent as it is a more portable way to trigger the context menu, will support accessibility on certain platforms, etc... The right click is not the only way to open a context menu.
If you do not want to subclass QTreeView , install an event handler from the main window:
ui->myTreeView->installEventFilter(this);
Then handle the event in the main window filterEvent
bool MainWindow::eventFilter(QObject *target, QEvent *event)
{
if (target == ui->myTreeView)
{
QContextMenuEvent* m = dynamic_cast<QContextMenuEvent*>(event);
if (event->type() == QEvent::ContextMenu && e!=0)
{
//Create context menu here
return true;
}
}
return false;
}
Method 2
Change the context menu mode to a signal:
ui->myTreeView->setContextMenuPolicy(Qt::CustomContextMenu);
connect(ui->myTreeView, SIGNAL(customContextMenuRequested(QPoint)),
this, SLOT(treeCustomMenu(QPoint)));
Then implement your slot:
void MainWindow::treeCustomMenu(const QPoint & pos)
{
//Implement your menu here using myTreeView->itemAt(pos);
}
What I do is to override mouseReleaseEvent and check manually.
void MyTreeView::mouseReleaseEvent(QMouseEvent *e) {
if (e->button() == Qt::RightButton) {
QTreeWidgetItem *item = itemAt(e->pos());
if (item) {
QMenu m;
m.addAction("hello");
m.addAction("world");
QAction *selected = m.exec(mapToGlobal(e->pos()));
if (selected) {
qDebug() << "selected" << selected->text();
}
}
} else {
QTreeView::mouseReleaseEvent(e);
}
}
What you mean by the entry is not represented by a QObject in Qt. Only the item model is a QObject, but the individual tree nodes are not QObjects in Qt item/view system.
Therefore, they cannot emit any signal
Related
I have a QGraphicsView which contains many QGraphicsItem. If I click mouse right click on any QGraphicsItem, the item should get select and right menu options should appear and then I will choose one of the options among them.To do that I have installed eventFilter and through it, I am using ContextMenu to create right click menu. Right click menu are getting cretaed properly. But propblem is I am not getting how to connect them to some function so that I can write logic for it.
It means if I clicked on save option that particular QGraphicsItem should get select and I should be able to go to some function where I will write logic for saving.
bool myClass::eventFilter(QObject *watched, QEvent *event)
{
switch(event->type())
{
case QEvent::ContextMenu:
{
QMouseEvent *mouseEvent = static_cast<QMouseEvent*> (event);
menu = new QMenu(this);
option = menu->addMenu("CopyOption");
option->addAction("save");
menu->exec(mouseEvent->globalPos());
break;
}
default:
break;
}
}
In your approach you show a context menu when you have no information if any item is selected. It is rather bad idea. You don't want to show the context menu in any location of view. You have to check if a cursor mouse is over an item.
Why not to derive from QGraphicsItem and just overload mousePressEvent method. Inside this method check if right button of mouse is clicked. If so, show context menu and test which action is clicked. Minimal code would be:
class TItem : public QGraphicsItem
{
bool _selected = false;
public:
TItem(QGraphicsItem* parent = nullptr) : QGraphicsItem(parent) {}
QRectF boundingRect() const override { return QRectF(0,0,20,20); }
void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) override {
painter->fillRect(QRectF(0,0,20,20),_selected ? QColor(0,255,255) : QColor(255,255,0));
}
void mousePressEvent(QGraphicsSceneMouseEvent* e) override {
QGraphicsItem::mousePressEvent(e);
if (e->button() & Qt::RightButton) {
QMenu menu;
QAction* a1 = menu.addAction(QString("test1"));
QAction* a2 = menu.addAction(QString("test2"));
if(a2 == menu.exec(e->screenPos())) {
test2();
_selected = true;
update();
}
}
}
void test2() {
QMessageBox::information(nullptr,"test2","test2");
}
};
All the job with checking is an item is under mouse is done by QT, mousePressEvent is invoked only if it is necessary.
Another approach would be override mousePressEvent on QGraphicsView. Inside which:
get all items belonging to the scene
iterate over them, checking if an item is under mouse -> QGraphicsItem has isUnderMouse method
if any item is under mouse, create QMenu and show it
check selected QAction, if it is save call a proper method which doing the save and mark the item as selected
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 am using visual Studio with Qt.
i do not have access to Qt designer. its all done through coding (C++);
i have an opensource software called easypaint.
i got stuck at trying to rename tabs. I want to be able to rename tabs when user double clicks on the tab itself.
i created a new function to filter the doubleClick event :
bool MainWindow::eventFilter(QObject *obj, QEvent *event)
`enter code here`{
if (event->type() == QEvent::MouseButtonDblClick) {
return true;
} else {
// standard event processing
return QObject::eventFilter(obj, event);
}
}
then i added this line to a function that initializes the TabWidget:
installEventFilter(mTabWidget);
can anyone please guide me through this.
Thank you
Most likely Qt doesn't allow an inline editor to open on the tab's name. So you'd most likely have to create and run a very small QDialog to query for the new name:
bool MainWindow::eventFilter(QObject *obj, QEvent *event)
{
if (obj == mTabWidget &&
event->type() == QEvent::MouseButtonDblClick) {
// query and set tab(s) names
QTabWidget *tab = qobject_cast<QTabWidget *>(obj);
if(tab)
{
QDialog dlg;
QVBoxLayout la(&dlg);
QLineEdit ed;
la.addWidget(&ed);
QDialogButtonBox bb(QDialogButtonBox::Ok | QDialogButtonBox::Cancel);
la.addWidget(&bb);
dlg.setLayout(&la);
if(dlg.exec() == QDialog::Accepted)
{
tab->setTabText(0, ed.text());
return true;
}
}
}
// Standard event processing
return QObject::eventFilter(obj, event);
}
It might be that Qt's dynamic memory management doesn't like the local class instances, so you'd have to convert all those class instances created to pointers created with new, but then please don't forget to tell the QDialog to delete on close or call dlg->deleteLater() after you queried the new name.
Another way to solve this via a fake inline editor would need a bit more work:
create a QLineEdit
move it right above the tab's, bring it up front and set keyboard focus to it
wire signals and slots
pressing enter should use the contents of the QLineEdit
leaving focus from the line edit should be treated as "abort" and delete the line editor
implement the slots to do what's needed.
You can write the event filter in the fallowing way:
bool MainWindow::eventFilter(QObject *obj, QEvent *event)
{
if (obj == mTabWidget &&
event->type() == QEvent::MouseButtonDblClick) {
QTabWidget *tab = qobject_cast<QTabWidget *>(obj);
// Set tab(s) names
tab->setTabText(0, "New Name");
}
// Standard event processing
return QObject::eventFilter(obj, event);
}
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.
I have a QListWidget on a dialog that I want to do something (for example, open a QFileDialog window) when a user double-clicks on the QListWidget. Unfortunately, the void doubleClicked (const QModelIndex & index) only fires when there are items in the list.
Is it possible to get the widget to fire the signal whenever a double-click event is received, anywhere within the widget? Or is a different approach required?
You can install an event filter to the listwidget's viewport widget, something like this:
listWidget->viewport()->installEventFilter(this); // "this" could be your window object.
In the eventFilter method check for the QEvent::MouseButtonDblClick event:
bool YourWindowClass::eventFilter(QObject *obj, QEvent *event)
{
if (event->type() == QEvent::MouseButtonDblClick)
{
QMouseEvent *mouseEvent = static_cast<QMouseEvent*>(event);
qDebug("Mouse double click %d %d", mouseEvent->x(), mouseEvent->y());
return true;
}
else
{
return QMainWindow::eventFilter(obj, event);
}
}
I hope this helps.