How to get QGridLayout item by clicked QPoint? - c++

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

Related

How to create Mouse right click menu option using eventFilter in Qt?

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

Access coordinates from the parent of a widget

I have a QGraphicsView inside the MainWindow that has implemented a QGraphicScene. I need to pop up a widget when I right click the mouse on a certain portion of the QGraphicScene. The parent of the widget needs to be MainWindow.
My problem is that I need to verify the validity of the portion on witch I clicked inside a mousePressEvent in QGraphicScene and to pop up the widget at the exact same location but the coordinates of the QGraphicScene and MainWindow are obvious not the same. For that I use a custom signal that trigger a slot inside MainWindow and get the coordinates from the mousePressEvent of the MainWindow. The problem is that the mouseEvent from the QGraphicsScene is triggered before the mouseEvent from MainWindow. This makes perfect sense and works if I right click twice but I need it to work from the first right click.
I can not implement a filter or change the focus because I have tons of events in the application.
QGraphicScene:
void CGraphicScene :: mousePressEvent(QGraphicsSceneMouseEvent *event)
{
if(event -> button() == Qt::RightButton)
{
//test stuff
emit signalChangeContextualMenuPosition();
m_contextualMenu -> show();
}
}
MainWindow:
CGraphicScene *scene;
CContextualMenu *m_contextualMenu;
m_contextualMenu = new CContextualMenu(this);
m_contextualMenu ->close();
scene = new CGraphicScene(m_contextualMenu);
ui->gvInterface -> setScene(scene);
connect(scene, SIGNAL(signalChangeContextualMenuPosition()), this, SLOT(openPopUp()));
void MainWindow :: openPopUp()
{
m_contextualMenu ->move(m_xCoordPopMenu, m_yCoordPopMenu);
}
void MainWindow :: mousePressEvent(QMouseEvent *event)
{
if(event -> button() == Qt::RightButton)
{
m_xCoordPopMenu = event -> x();
m_yCoordPopMenu = event -> y();
}
}
Use the QGraphicsView::mapFromScene() to map scene coordinates to the view widget coordinates and then QWidget::mapToParent() to map the coordinates to it's parent widget, which is probably your main window. You can also find useful the method QWidget::mapTo().

Click event for QGraphicsView Qt

I have made a GUI in Qt that is basically a widget with a QGraphicsView on it i have a function:
void GUI::mousePressEvent(QMouseEvent *event)
{
if(event->button() == Qt::LeftButton)
{
QPointF mousePoint = ui->graphicsView->mapToScene(event->pos());
qDebug() << mousePoint;
}
}
which links to a public slot:
void mousePressEvent(QMouseEvent *event);
this shows me on the console the x,y coordinate of where i have clicked, however currently this is working on the entire widget and i ideally would like x,y(0,0) to be the top left of the QGraphicsView instead of the top left of the entire widget. does anyone have any idea how to make this work, i thought from my code that this is what it was doing but it turns out this is not so, iv had a look around for a while now but im coming up with nothing
any help would be really appreciated thanks.
Reimplement the mousePressEvent(QMouseEvent *event) of the QGraphicsView not your widget would be the 'proper' way of doing it. Otherwise you can hack it with:
// Detect if the click is in the view.
QPoint remapped = ui->graphicsView->mapFromParent( event->pos() );
if ( ui->graphicsView->rect().contains( remapped ) )
{
QPointF mousePoint = ui->graphicsView->mapToScene( remapped );
}
This assumes that the widget is the parent of the QGraphicsView.

Remove scroll functionality on mouse wheel QGraphics view

I have a QGraphicsView window on my widget and have just put in an event for mouse wheel which zooms in on the image.
However as soon as i zoom in scroll bars are displayed and the scroll functionality on the mouse wheel overrides the zoom function i have.
i was wondering if there is any way that i can remove scrolling all together and add a drag to move option or maybe a CTRL and mouse wheel to zoom and mouse wheel alone would control scrolling
here is my zoom function (Which im aware isnt perfect) but if anyone could shed some light on that it would be a bonus
cheers in advance
void Test::wheelEvent(QWheelEvent *event)
{
if(event->delta() > 0)
{
ui->graphicsView->scale(2,2);
}
else
{
ui->graphicsView->scale(0.5,0.5);
}
}
You reimplemented wheelEvent for QWidget/QMainWindow that contains your QGraphicsView, however, wheelEvent of QGraphicsView remains intact.
You can derive from QGraphicsView, reimplement wheelEvent for derived class and use derive class instead of QGraphicsView - this way you won't even need wheelEvent in your QWidget/QMainWindow, and you can customize reimplemented wheelEvent to do what you want. Something like that:
Header file:
class myQGraphicsView : public QGraphicsView
{
public:
myQGraphicsView(QWidget * parent = nullptr);
myQGraphicsView(QGraphicsScene * scene, QWidget * parent = nullptr);
protected:
virtual void wheelEvent(QWheelEvent * event);
};
Source file:
myQGraphicsView::myQGraphicsView(QWidget * parent)
: QGraphicsView(parent) {}
myQGraphicsView::myQGraphicsView(QGraphicsScene * scene, QWidget * parent)
: QGraphicsView(scene, parent) {}
void myQGraphicsView::wheelEvent(QWheelEvent * event)
{
// your functionality, for example:
// if ctrl pressed, use original functionality
if (event->modifiers() & Qt::ControlModifier)
{
QGraphicsView::wheelEvent(event);
}
// otherwise, do yours
else
{
if (event->delta() > 0)
{
scale(2, 2);
}
else
{
scale(0.5, 0.5);
}
}
}
Scrolling can be disabled with the following code:
ui->graphicsView->verticalScrollBar()->blockSignals(true);
ui->graphicsView->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
ui->graphicsView->horizontalScrollBar()->blockSignals(true);
ui->graphicsView->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
I think your question has a bit simpler answer.. To disable scroll bars just set scroll bar policy (QGraphicsView is just QScrollView), so step 1)
setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
that will disable scroll bars..
step 2) (if you want to keep it simple)
QGraphicsView * pView; // pointer to your graphics view
pView->setInteractive(true);
pView->setDragMode(QGraphicsView::ScrollHandDrag);
thats the fastest way to get results you want

Automatically populate "Edit" menu in menubar with current focus widget context menu

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.