How to connect right click menu action to some function in QGraphicsView? - c++

Currently in my QGraphicsScene, there are multiple items (like Text, Ellipse, Rectangle, Polyline etc ) I want to change the color of these items by right clicking on them and then choose "Color Me" option. Once clicked on "Color Me" option, color dialog box should get pop up and then, I should change the color of item.
But in my try, on right click, Color Me option is appearing but clicking on it ColorOption slot, is not getting called.
Widget::Widget(QWidget *parent)
: QGraphicsView(parent)
, ui(new Ui::Widget)
{
.....
myCustomAction1 = new QAction(tr("Color Me"), this);
connect(myCustomAction1, SIGNAL(triggered()), this, SLOT(ColorOption()));
.....
}
void Widget::ColorOption()
{
QColor color = QColorDialog::getColor(currentColor);
if(color.isValid())
currentColor = color;
.....
}
Widget.h
class Widget : public QGraphicsView
{
Q_OBJECT
public:
Widget(QWidget *parent = nullptr);
~Widget();
private:
Ui::Widget *ui;
QGraphicsScene* scene;
QGraphicsView* view;
QAction* myCustomAction1;
QColor currentColor;
};
I would appreciate if anyone could help.

In order for the signal-slot mechanism to work, the slot function must be declared in the class declaration. I.e. the following needs to be in Widget.h:
public slots:
void ColorOption();

Related

QGraphicsView artifacts when updating another views items/viewport in mouse move event handler

We are having an issue with artifacts in an application using multiple QGraphicsScene/QGraphicsViews. Essentially it seems the problem is when when in mousePosChanged event handler for a scene, and call setPos() on an item in a different scene, as well as update a region on the view for the other scene, it leaves artifacts.
I tried to set up a minimal example that hopefully will be easy to spot what is wrong
Essentially I have two scenes (scene 1 and scene 2), each with one ellipse item. When the mouse moves in the first scene, its ellipse item tracks the mouse. It also sends out a signal with mouse position. The second scene is connected to this signal, and updates the position of its ellipse item to the same location. The second graphics view is also connected to the signal, and it updates its viewport in an arbitrary location.
The second graphics view ends up with artifacts as you move the mouse around (see picture below)
This is the code from my minimal example:
class MyView : public QGraphicsView
{
Q_OBJECT
public:
MyView(QGraphicsScene *scene, QWidget *parent = 0);
public slots:
void onMouseMoveEvent();
};
class MyScene : public QGraphicsScene
{
Q_OBJECT
public:
MyScene(QObject *parent = 0);
public slots:
void setTrackerPos(const QPointF &pos);
signals:
void mousePosChanged(const QPointF &pos);
protected:
void mouseMoveEvent(QGraphicsSceneMouseEvent *mouseEvent);
private:
QGraphicsEllipseItem *mCursorTracker;
};
MyScene::MyScene(QObject *parent) :
QGraphicsScene(QRectF(0.,0.,1000.,1000.), parent),
mCursorTracker(new QGraphicsEllipseItem(0., 0., 50., 50.))
{
mCursorTracker->setFlag(QGraphicsItem::ItemSendsGeometryChanges);
mCursorTracker->setBrush(QBrush(Qt::red, Qt::SolidPattern));
addItem(mCursorTracker);
}
void MyScene::setTrackerPos(const QPointF &pos)
{
mCursorTracker->setPos(pos);
}
void MyScene::mouseMoveEvent(QGraphicsSceneMouseEvent *mouseEvent)
{
mCursorTracker->setPos(mouseEvent->scenePos());
emit mousePosChanged(mouseEvent->scenePos());
}
MyView::MyView(QGraphicsScene *scene, QWidget *parent) :
QGraphicsView(scene, parent)
{
setViewportUpdateMode(QGraphicsView::BoundingRectViewportUpdate);
setMouseTracking(true);
}
void MyView::onMouseMoveEvent(const QPointF &pos)
{
viewport()->update(QRect(0,0,250,250));
}
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
QWidget *w = new QWidget;
QHBoxLayout *layout = new QHBoxLayout(w);
MyScene *scene1 = new MyScene(w);
MyScene *scene2 = new MyScene(w);
MyView *view1 = new MyView(scene1, w);
MyView *view2 = new MyView(scene2, w);
layout->addWidget(view1);
layout->addWidget(view2);
QObject::connect(scene1, SIGNAL(mousePosChanged(QPointF)), scene2, SLOT(setTrackerPos(QPointF)));
QObject::connect(scene1, SIGNAL(mousePosChanged(QPointF)), view2, SLOT(onMouseMoveEvent()));
w->show();
return a.exec();
}
I understand that changing to a full update on the view will fix this. However in our real application it is too expensive to repaint the whole scene. The update on the viewport is for a small foreground layer, and only one graphics item position changes (like in this example)

How to undo-redo features like zoom in, zoom out using Command pattern in Qt?

I have a QGraphicsView, which contains rectangle, polyline, text etc. I also have some features to transform the view like zoom in, zoom out, Fit in view, change the color on mouse right click etc. I want to add few more features like Undo and Redo.
Undo means, user should cancel the effect of last command executed and
show previous transformation. So If user did transformation like zoom
in -> zoom in -> fit in -> zoom in and now pressed Undo then view's
fit in position should get displayed.
I have tried Command pattern to implement Undo-Redo feature. But I did it to add/remove rectangle,polyline in design. And it worked.
HCommand.h
#include <QUndoCommand>
#include<QGraphicsItem>
#include<QGraphicsScene>
class myCommand : public QUndoCommand
{
public:
HCommand(QGraphicsItem* mItem, QGraphicsScene* scene);
HCommand(QGraphicsScene* scene, QGraphicsView* mView);// for fitIn
private:
QGraphicsItem* mItem;
QGraphicsScene* mScene;
QGraphicsView* mView;
void undo();
void redo();
};
HCommand.cpp
#include "hcommand.h"
HCommand::HCommand(QGraphicsItem* item, QGraphicsScene* scene):
mItem(item), mScene(scene)
{}
void HCommand::undo()
{
if(mItem)
mScene->removeItem(mItem);
}
void HCommand::redo()
{
if(mItem)
mScene->addItem(mItem);
}
Widget.h
class Widget : public QGraphicsView
{
Q_OBJECT
public:
Widget(QWidget *parent = nullptr);
~Widget();
Rectangle r;
PolyLine p;
QUndoStack* undoStack;
void undo();
void redo();
private:
Ui::Widget *ui;
QGraphicsScene* scene;
QGraphicsView* view;
}
Widget.cpp
Widget::Widget(QWidget *parent)
: QGraphicsView(parent)
, ui(new Ui::Widget)
{
ui->setupUi(this);
scene = new QGraphicsScene(this);
view = new QGraphicsView(this);
view->setScene(scene);
undoStack = new QUndoStack(this);
ui->verticalLayout_2->addWidget(view);
}
void Widget::undo()
{
undoStack->undo();
}
void Widget::redo()
{
undoStack->redo();
}
void Widget::on_schematicBtn_clicked()
{
QGraphicsRectItem* in = r.createRect(20,160,20,20);
scene->addItem(in);
HCommand* com = new HCommand(in,scene);
undoStack->push(com);
// many rectangles and polyline code, same as above
}
But now I want to do Undo-Redo for Zoom In , Zoom Out, Fit In. But whenever I zoom in/out, I zoomed in/out full view
void Widget::on_zoomIn_clicked()
{
double sFactor = 1.2;
view->scale(sFactor,sFactor);
}
void Widget::on_zoomOut_clicked()
{
double sFactor = 1.2;
view->scale(1/sFactor,1/sFactor);
}
void Widget::on_fitInBtn_clicked()
{
view->resetTransform();
}
So the question is :
In above code I am pushing particular rectangle in stack so for zoom
in/out how to push full view in stack ? So that, later I can pull it
out from stack ? Or this can be implemented differently ?
Any help is appreciated.

Embed a QWebEngineView process inside QTabWidget

I'm trying to integrate a QWebEngineView widget that runs as a separate process(QProcess) inside a QTabWidget page. So far the QWebEngineView process is being started properly but its showing the webpage in a separate window instead of showing it inside the QTabWidget in the MainWindow application.
This is the Widget that is being added to the QTabWidget.
BrokersTerminal.h
class BrokersTerminal : public QWidget
{
Q_OBJECT
public:
explicit BrokersTerminal(QWidget *parent = 0);
~BrokersTerminal();
void startTerminal();
public slots:
void brokersTerminalStarted();
private:
Ui::BrokersTerminal *ui;
QProcess *brokers_process;
QString brokers_program_path;
QStringList arguments;
};
BrokersTerminal.cpp
BrokersTerminal::BrokersTerminal(QWidget *parent) :
QWidget(parent),
ui(new Ui::BrokersTerminal)
{
ui->setupUi(this);
brokers_process = new QProcess( this );
brokers_program_path = QApplication::applicationFilePath();
arguments << "--b";
connect( brokers_process, &QProcess::started, this , &BrokersTerminal::brokersTerminalStarted );
}
BrokersTerminal::~BrokersTerminal()
{
delete ui;
}
void BrokersTerminal::startTerminal()
{
brokers_process->start( brokers_program_path, arguments );
brokers_process->waitForStarted();
}
void BrokersTerminal::brokersTerminalStarted()
{
qDebug() << "Brokers terminal started";
}
This is the WebView Widget that is responsible for displaying the brokers website.
BrokersWebWidget.h
class BrokersWebWidget : public QWidget
{
Q_OBJECT
public:
explicit BrokersWebWidget(QWidget *parent = 0);
~BrokersWebWidget();
private:
Ui::BrokersWebWidget *ui;
QUrl brokers_url;
QWebEngineView *web_browser;
};
BrokersWebWidget.cpp
BrokersWebWidget::BrokersWebWidget(QWidget *parent) :
QWidget(parent),
ui(new Ui::BrokersWebWidget)
{
ui->setupUi(this);
brokers_url = "https://siteofbrokersapi.com/";
web_browser = new QWebEngineView( this );
web_browser->load( brokers_url );
}
BrokersWebWidget::~BrokersWebWidget()
{
delete ui;
}
Right now this BrokersWebWidget starts properly as a separate process but it opens in a separate window , but how can this be added in the BrokersTerminal Widget ?
Please let me know of any possible solutions. Thanks.
You cannot embed a widget running in one process into a window run in another. QWidgets can only work with widgets run in the GUI thread in the same process.

How to display image on specific coordinates in Qt?

I have this dialog window class in Qt:
class Board : public QDialog
{
Q_OBJECT
public:
explicit Board(QWidget *parent = 0);
~Board();
private:
Ui::Board *ui;
void mousePressEvent(QMouseEvent *mouseEvent);
};
I want to dislay a png image on user given coordinates in function mousePressEvent, which is called every time user clicks somewhere on the dialog window. So I need something like displayImage("path/to/image.png", coordX, coordY);. How can I do it?
The new code:
class Board : public QDialog
{
public:
Board(QWidget *parent = 0) :
QDialog(parent),
ui(new Ui::Board),
view(&scene)
{
// Set background image
/**************/
ui->setupUi(this);
QPixmap pix("path/background.png");
ui->label_board->setPixmap(pix);
/**************/
/ Set layout for displaying other images on the background
QVBoxLayout *layout = new QVBoxLayout(this);
layout->addWidget(&view);
//or set the layout and the view in the designer if using Qt Creator
}
protected:
virtual void mousePressEvent(QMouseEvent *mouseEvent) override
{
QGraphicsPixmapItem *item = new QGraphicsPixmapItem(QPixmap("path/to/image.png"));
scene.addItem(item);
item->setPos(coordX, coordY);
}
private:
Ui::Board *ui;
QGraphicsScene scene;
QGraphicsView view;
};
The label_board is a label 500x500 set to some position with Qt Designer.
You will need to use QGraphicsView (docs) and QGraphicsScene (docs):
class Board : public QDialog
{
public:
Board(QWidget *parent = 0) :
QDialog(parent),
ui(new Ui::Board),
view(&scene)
{
QVBoxLayout *layout = new QVBoxLayout(this);
layout->addWidget(&view);
//or set the layout and the view in the designer if using Qt Creator
//EDIT: add background like this first
QGraphicsPixmapItem *background = QGraphicsPixmapItem(QPixmap("path/to/background.png"));
scene.addItem(background);
background.setPos(0, 0); //position it to cover all the scene so at 0,0 which is the origin point
background.setScale(2.0); //scale the image to the scene rectangle to fill it
background.setZValue(-0.1); //to ensure it is always at the back
}
protected:
virtual void mousePressEvent(QMouseEvent *mouseEvent) override
{
QGraphicsPixmapItem *item = new QGraphicsPixmapItem(QPixmap("path/to/image.png"));
scene.addItem(item);
item->setPos(coordX, coordY);
}
private:
Ui::Board *ui;
QGraphicsScene scene;
QGraphicsView view;
};
And that is pretty much it. Also note that the position is that of upper left corner of the item (image). If you would want to center it you would need to adjust the position based on the proportions of the item (image).

How to access widgets in different tabs of QTabWidget?

I know how to create a new tab and add new widgets to a new tab of QTabWidget.
My Code:
QPlainTextEdit *plainTextEdit = new QPlainTextEdit;
ui->tabWidget->addTab(plainTextEdit , "New");
When I clicked the button, new tab and its own QPlainTextEdit widget will be created.
But I don't know how to set property to different QPlainTextEdit or save their contents.
For example, I want set font size of QPlainTextEdit in tab4 and save contents of QPlainTextEdit in tab5.
How to achieve these functions?
To get the widget at a tab index you can use the widget function of QTabWidget .
In your case in which QPlainTextEdit is the only widget of every tab page :
QPlainTextEdit* plainTextEdit = (QPlainTextEdit*) ui->tabWidget->widget(0); // for the first tab
plainTextEdit->setPlainText("Hello!");
If the QPlainTextEdit is not the only widget, you need to get the children of the widget and find the QPlainTextEdit in them :
QList<QPlainTextEdit *> allTextEdits = ui->tabWidget->widget(0)->findChildren<QPlainTextEdit *>();
if (allTextEdits.count() >0)
allTextEdits[0]->setPlainText("Hello!");;
You should have pointers to your QPlainTextEdits as members of the main widget class:
class Widget : public QWidget
{
Q_OBJECT
public:
Widget(QWidget *parent = 0);
~Widget();
void someFunction();
private:
QPlainTextEdit *plainTextEdit;
}
Widget::Widget(QWidget *parent)
: QWidget(parent)
{
...
plainTextEdit = new QPlainTextEdit;
ui->tabWidget->addTab(plainTextEdit , "New");
}
void Widget::someFunction()
{
plainTextEdit->setPlainText("Hello!");
}