I'm trying to create a custom QGraphicsLineItem called Edge, but I'm not doing it well I'm new in Qt.
For now, I can't for example do scene->add(edgeItem) without error no viable conversion from 'Edge' to 'QGraphicsItem *'.
My edge.h
#ifndef EDGE_H
#define EDGE_H
#include <QWidget>
#include <QApplication>
#include <QGraphicsScene>
#include <QGraphicsView>
#include <QGraphicsItem>
class Edge:public QGraphicsLineItem
{
private:
QGraphicsEllipseItem * state1;
QGraphicsEllipseItem * state2;
public:
enum { Type = UserType + 2 };
int type() const override;
Edge(QGraphicsEllipseItem * s1, QGraphicsEllipseItem* s2);
QGraphicsEllipseItem *getState1() const;
void setState1(QGraphicsEllipseItem *value);
QGraphicsEllipseItem *getState2() const;
void setState2(QGraphicsEllipseItem *value);
QList<QPointF> centerPoints();
};
#endif // EDGE_H
My edge.cpp
#include "edge.h"
#include <QGraphicsEllipseItem>
Edge::Edge(QGraphicsEllipseItem* s1, QGraphicsEllipseItem* s2)
{
setState1(s1);
setState2(s2);
setFlag(QGraphicsItem::ItemIsMovable);
setFlag(QGraphicsItem::ItemSendsScenePositionChanges);
setFlag(QGraphicsItem::ItemIsSelectable);
}
int Edge::type() const
{
return Type;
}
QGraphicsEllipseItem *Edge::getState1() const
{
return state1;
}
void Edge::setState1(QGraphicsEllipseItem *value)
{
state1 = value;
}
QGraphicsEllipseItem *Edge::getState2() const
{
return state2;
}
void Edge::setState2(QGraphicsEllipseItem *value)
{
state2 = value;
}
QList<QPointF> Edge::centerPoints()
{
QPointF point1(state1->rect().x() + state1->rect().width()/2,state1->rect().y()+ state1-> rect().height()/2);
QPointF point2(state2->rect().x()+state2->rect().width()/2,state2->rect().y()+ state2-> rect().height()/2);
QList<QPointF> list;
list.append(point1);
list.append(point2);
return list;
}
Can someone show me how I can modify my Edge to make it a "real" QGraphicsItem ?
here is an example of proper inheritance
so .h
class Edge: public QGraphicsLineItem
{
public:
Edge(QGraphicsEllipseItem * s1 = nullptr, QGraphicsEllipseItem* s2= nullptr, QGraphicsItem* parent = nullptr);
//.....
};
and .cpp
Edge::Edge(QGraphicsEllipseItem * s1, QGraphicsEllipseItem* s2, QGraphicsItem* parent)
:QGraphicsLineItem( parent)
{
//.........
}
When creating your Edge class instance, do it this way.
Edge* someinstance = new Edge();
scene->addItem(someinstance);
I assume what you're doing is.
Edge instance;
scene->addItem(instance);
So compiler tries implicitly convert Edge object to QGraphicsItem* object wich is impossible.
Related
I have a QListWidget and a QGraphicsView both subclassed to overwrite some of their members. I prepared a minimal verifiable example showing the problem I have here
From the QListWidget I can drag and drop specific field (represented by a QTableWidget) and drop them into a QGraphicsView and in order to do that I am using a QGraphicsProxyWidget approach as shown below.
The Problem
Now, how do I connect 2 QRadioButton inside cell of a QTableWidget with another cell of another QTableWidget?
It is important to mention that the green QGraphicsRectItem it is used to move around the QTableWidget as well as adjusting its dimension.
Below the result I was able to arrive so far:
And below the expected result I have been trying to achieve:
Below the most important part of the code:
scene.h
#ifndef SCENE_H
#define SCENE_H
#include <QGraphicsScene>
class Scene : public QGraphicsScene
{
public:
Scene(QObject *parent = nullptr);
protected:
void dragEnterEvent(QGraphicsSceneDragDropEvent *event);
void dragMoveEvent(QGraphicsSceneDragDropEvent *event);
void dropEvent(QGraphicsSceneDragDropEvent *event);
};
#endif // SCENE_H
scene.cpp
#include "arrow.h"
#include <QGraphicsSceneDragDropEvent>
#include <QMimeData>
#include <QTableWidget>
#include <QGraphicsProxyWidget>
#include <QVBoxLayout>
#include <QMetaEnum>
#include <QEvent>
#include <QSizeGrip>
#include <QRadioButton>
Scene::Scene(QObject *parent)
{
setBackgroundBrush(Qt::lightGray);
}
void Scene::dragEnterEvent(QGraphicsSceneDragDropEvent *event) {
if (event->mimeData()->hasFormat("application/x-qabstractitemmodeldatalist"))
event->setAccepted(true);
}
void Scene::dragMoveEvent(QGraphicsSceneDragDropEvent *event) {
if (event->mimeData()->hasFormat("application/x-qabstractitemmodeldatalist"))
event->setAccepted(true);
}
void Scene::dropEvent(QGraphicsSceneDragDropEvent *event) {
QByteArray encoded =
event->mimeData()->data("application/x-qabstractitemmodeldatalist");
QDataStream stream(&encoded, QIODevice::ReadOnly);
QStringList rosTables;
QString newString;
while (!stream.atEnd()) {
int row, col;
QMap<int, QVariant> roleDataMap;
stream >> row >> col >> roleDataMap;
rosTables << roleDataMap[Qt::DisplayRole].toString();
}
for (const QString &tableType : rosTables) {
if (tableType == "Images") {
QPoint initPos(0, 0);
auto *wgt = new CustomTableWidget;
auto *proxyControl = addRect(0, 0, 0, 0, QPen(Qt::black),
QBrush(Qt::darkGreen));
auto *sizeGrip = new QSizeGrip(wgt);
auto *layout = new QHBoxLayout(wgt);
layout->setContentsMargins(0, 0, 0, 0);
layout->addWidget(sizeGrip, 0, Qt::AlignRight | Qt::AlignBottom);
connect(wgt, &CustomTableWidget::sizeChanged, [wgt, proxyControl](){
proxyControl->setRect(wgt->geometry().adjusted(-10, -10, 10, 10));
});
wgt->setColumnCount(4);
wgt->setRowCount(4);
for (int ridx = 0; ridx < wgt->rowCount(); ridx++) {
for (int cidx = 0; cidx < wgt->columnCount(); cidx++) {
QRadioButton *radio1, *radio2;
auto* item = new QTableWidgetItem();
item->setText(QString("%1").arg(ridx));
wgt->setItem(ridx,cidx,item);
radio1 = new QRadioButton;
radio2 = new QRadioButton;
wgt->setCellWidget(cidx, 0, radio1);
wgt->setCellWidget(cidx, 3, radio2);
Arrow *arrow = new Arrow;
}
}
auto *const proxy = addWidget(wgt);
proxy->setPos(initPos.x(), initPos.y()
+ proxyControl->rect().height());
proxy->setParentItem(proxyControl);
proxyControl->setPos(initPos.x(), initPos.y());
proxyControl->setFlag(QGraphicsItem::ItemIsMovable, true);
proxyControl->setFlag(QGraphicsItem::ItemIsSelectable, true);
proxyControl->setRect(wgt->geometry().adjusted(-10, -10, 10, 10));
}
}
}
diagramitem.h
#ifndef DIAGRAMITEM_H
#define DIAGRAMITEM_H
#include <QGraphicsPolygonItem>
class Arrow;
class DiagramItem : public QGraphicsPolygonItem
{
public:
DiagramItem(QMenu *contextMenu, QGraphicsItem *parent = Q_NULLPTR);
void removeArrow(Arrow *arrow);
void removeArrows();
void addArrow(Arrow *arrow);
QPixmap image() const;
protected:
void contextMenuEvent(QGraphicsSceneContextMenuEvent *event) override;
QVariant itemChange(GraphicsItemChange change, const QVariant &value) override;
private:
QPolygonF myPolygon;
QList<Arrow*> arrows;
QMenu *myContextMenu;
};
#endif // DIAGRAMITEM_H
diagramitem.cpp
#include "diagramitem.h"
#include "arrow.h"
#include <QPainter>
#include <QGraphicsScene>
#include <QGraphicsSceneContextMenuEvent>
#include <QMenu>
DiagramItem::DiagramItem(QMenu *contextMenu, QGraphicsItem *parent) : QGraphicsPolygonItem(parent)
{
myContextMenu = contextMenu;
setPolygon(myPolygon);
setFlag(QGraphicsItem::ItemIsMovable, true);
setFlag(QGraphicsItem::ItemIsSelectable, true);
setFlag(QGraphicsItem::ItemSendsGeometryChanges, true);
}
void DiagramItem::removeArrow(Arrow *arrow)
{
int index = arrows.indexOf(arrow);
if (index != -1)
arrows.removeAt(index);
}
void DiagramItem::removeArrows()
{
foreach (Arrow *arrow, arrows) {
arrow->startItem()->removeArrow(arrow);
arrow->endItem()->removeArrow(arrow);
scene()->removeItem(arrow);
delete arrow;
}
}
void DiagramItem::addArrow(Arrow *arrow)
{
arrows.append(arrow);
}
void DiagramItem::contextMenuEvent(QGraphicsSceneContextMenuEvent *event)
{
scene()->clearSelection();
setSelected(true);
myContextMenu->exec(event->screenPos());
}
QVariant DiagramItem::itemChange(QGraphicsItem::GraphicsItemChange change, const QVariant &value)
{
if (change == QGraphicsItem::ItemPositionChange) {
foreach (Arrow *arrow, arrows) {
arrow->updatePosition();
}
}
return value;
}
arrow.h
#ifndef ARROW_H
#define ARROW_H
#include <QGraphicsLineItem>
#include "diagramitem.h"
class Arrow : public QGraphicsLineItem
{
public:
enum { Type = UserType + 4 };
Arrow(DiagramItem *startItem, DiagramItem *endItem,
QGraphicsItem *parent = nullptr);
DiagramItem *startItem() const { return myStartItem; }
DiagramItem *endItem() const { return myEndItem; }
QPainterPath shape() const override;
void setColor(const QColor &color) {
myColor = color;
}
int type() const override { return Type; }
void updatePosition();
protected:
void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget = nullptr) override;
private:
QColor myColor;
DiagramItem *myStartItem;
DiagramItem *myEndItem;
QPolygonF arrowHead;
};
#endif // ARROW_H
arrow.cpp
#include "arrow.h"
#include <QPen>
#include <QPainter>
#include "qmath.h"
Arrow::Arrow(DiagramItem *startItem, DiagramItem *endItem, QGraphicsItem *parent) : QGraphicsLineItem(parent)
{
myStartItem = startItem;
myEndItem = endItem;
myColor = Qt::GlobalColor::black;
setPen(QPen(myColor, 2, Qt::SolidLine, Qt::RoundCap, Qt::RoundJoin));
setFlag(QGraphicsItem::ItemIsSelectable, true);
}
QPainterPath Arrow::shape() const
{
QPainterPath path = QGraphicsLineItem::shape();
path.addPolygon(arrowHead);
return path;
}
void Arrow::updatePosition()
{
QLineF line(mapFromItem(myStartItem, 0, 0), mapFromItem(myEndItem, 0, 0));
setLine(line);
}
void Arrow::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget)
{
Q_UNUSED(option)
Q_UNUSED(widget)
if (myStartItem->collidesWithItem(myEndItem))
return;
QPen myPen = pen();
myPen.setColor(myColor);
qreal arrowSize = 20;
painter->setPen(myPen);
painter->setBrush(myColor);
QLineF centerLine(myStartItem->pos(), myEndItem->pos());
QPolygonF endPolygon = myEndItem->polygon();
QPointF p1 = endPolygon.first() + myEndItem->pos();
QPointF p2;
QPointF intersectPoint;
QLineF polyLine;
for (int i = 1; i < endPolygon.count(); ++i) {
p2 = endPolygon.at(i) + myEndItem->pos();
polyLine = QLineF(p1, p2);
QLineF::IntersectType intersectType =
polyLine.intersect(centerLine, &intersectPoint);
if (intersectType == QLineF::BoundedIntersection)
break;
p1 = p2;
}
setLine(QLineF(intersectPoint, myStartItem->pos()));
double angle = std::atan2(-line().dy(), line().dx());
QPointF arrowP1 = line().p1() + QPointF(sin(angle + M_PI / 3) * arrowSize,
cos(angle + M_PI / 3) * arrowSize);
QPointF arrowP2 = line().p1() + QPointF(sin(angle + M_PI - M_PI / 3) * arrowSize,
cos(angle + M_PI - M_PI / 3) * arrowSize);
arrowHead.clear();
arrowHead << line().p1() << arrowP1 << arrowP2;
painter->drawLine(line());
painter->drawPolygon(arrowHead);
if (isSelected()) {
painter->setPen(QPen(myColor, 1, Qt::DashLine));
QLineF myLine = line();
myLine.translate(0, 4.0);
painter->drawLine(myLine);
myLine.translate(0,-8.0);
painter->drawLine(myLine);
}
}
What I have done so far to solve the problem:
1) I came across this post which was useful to understand the initial idea on how to do that, but it didn't really provide a way, or an implementation idea on how to best proceed
2) I researched the official documentation and before asking this question I went through the whole Diagram Scene example provided and understood how to create an Arrow object. The documentation about that was very good and made me understand how the graphics line item has to be formed.
However I was not able (coming back to my example) how to make "aware" the QRadioButton that I am trying to use its center as starting point for an arrow ad, therefore, how do I make "aware" the destination QRadioButton in another cell that it has to be connected there?
Below a particular of what I mean:
So basically the start point of the QRadioButton change color (or style) and the arrival point also change color.
3) I thought that the Arrow object has to be created inside the subclassed QGraphicsScene since it already handles the mouse events.
4) Despite what I tried so far I could not find any other useful help. Although I am still investigating how to do that.
If anyone has ever been in the same situation please provide guidance on how to better proceed to solve this problem and find a solution to this issue.
Solution
When a start and end radio buttons are checked, you need to create the arrow with those buttons as start and end nodes, e.g.:
void Backend::onInputRadioButton(bool checked)
{
m_endNode = checked ? static_cast<QRadioButton *>(sender()) : nullptr;
if (m_startNode && m_endNode)
m_scene->addItem(new ArrowItem(m_startNode, m_endNode));
}
Then you need to connect the signal of the top-most graphics items, which hold the tables, with the updatePosition slot of the ArrowItem, e.g.:
connect(m_startItem->property("item").value<MovableItem *>(),
&MovableItem::itemMoved, this, &ArrowItem::updatePosition);
connect(m_endItem->property("item").value<MovableItem *>(),
&MovableItem::itemMoved, this, &ArrowItem::updatePosition);
Note: I am using a property to hold a reference to the container item.
Finally, you need to update the arrow line, e.g.:
void ArrowItem::updatePosition()
{
QPointF offset(7, 15);
QPointF p1 = m_startItem->property("item").value<MovableItem *>()->pos()
+ m_startItem->parentWidget()->mapToParent(m_startItem->pos())
+ offset;
QPointF p2 = m_endItem->property("item").value<MovableItem *>()->pos()
+ m_endItem->parentWidget()->mapToParent(m_endItem->pos())
+ offset;
setLine(QLineF(p1, p2));
}
Example
I have dared to suggest improvements in your code. You can find the complete example I wrote for you on GitHub.
Result
The provided example produces the following result:
Note: The arrow heads are missing. Check once again the Diagram Scene Example to get an idea of how to draw them.
I have a specific string on a QLineEdit, this string is passed to a QListView via QPushButton. Those strings are choices of a QComboBox and they are very specific:
1) "[ INFO] Minimum Distance: 5",
2) "[ INFO] Minimum Distance: 10" and
3) "[ INFO] Minimum Distance: 15"
Here a perfectly working minimal verifiable example if you need to test it.
Currently I can successfully change the color of the QGraphicsView but by clicking or double-clicking on the QString entered in the QListView.
The problem: How can I detect the specific QString content inside a QListView in order to change the background color of a QGraphicsView?
What I mean I don't want to click or double-click on the entry of the QListView but I would like the QListView to see that there is a string "[ INFO] Minimum Distance: 5" and therefore change the color of the QGraphicsView automatically without me clicking or double-clicking on the QListView entry.
After I "Go to slot" my choices are the following below:
Below the MVE working code, you can copy /paste on your machine and it will work:
mainwindow.h
#include <QMainWindow>
#include <QGraphicsView>
#include <QGraphicsScene>
#include <QGraphicsTextItem>
#include <QStringListModel>
QT_BEGIN_NAMESPACE
namespace Ui { class MainWindow; }
QT_END_NAMESPACE
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
MainWindow(QWidget *parent = nullptr);
~MainWindow();
void changeColorDetection();
void updateListView();
void updateListView(const QString & message);
private slots:
void on_pushButton_clicked();
void on_listView_entered(const QModelIndex &index);
void on_listView_activated(const QModelIndex &index);
private:
Ui::MainWindow *ui;
QGraphicsView *mView;
QGraphicsScene *mScene;
QGraphicsTextItem *mText;
StringList *newString;
QStringListModel *model;
};
#endif // MAINWINDOW_H
mainwindow.cpp
#include "mainwindow.h"
#include "ui_mainwindow.h"
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
, ui(new Ui::MainWindow)
{
ui->setupUi(this);
mView = new QGraphicsView();
mScene = new QGraphicsScene();
ui->graphicsView->setScene(mScene);
QFont font;
font.setPixelSize(10);
font.setBold(false);
font.setFamily("Calibri");
mText = new QGraphicsTextItem;
mText->setPos(150,70);
mScene->addText(tr("Boat outside alarm area"))->setDefaultTextColor(Qt::black);
model = new QStringListModel();
ui->listView->setModel(model);
ui->listView->setEditTriggers(QAbstractItemView::NoEditTriggers);
emptyIndex();
connect(ui->listView, SIGNAL(loggingUpdated()), this, SLOT(updateListView(const QString &)));
connect(ui->graphicsView, SIGNAL(clicked(const QModelIndex &)), this, SLOT(on_listView_activated(const QModelIndex &index)));
}
MainWindow::~MainWindow()
{
delete ui;
}
void MainWindow::updateListView(const QString & message)
{
if(model->insertRow(model->rowCount())) {
QModelIndex index = model->index(model->rowCount() - 1, 0);
model->setData(index, message);
ui->listView->scrollTo(index);
}
}
void MainWindow::on_pushButton_clicked()
{
QString str = ui->lineEdit->text();
model->insertRow(model->rowCount());
QModelIndex index = model->index(model->rowCount()-1);
model->setData(index, str);
ui->listView->scrollToBottom();
}
void MainWindow::on_comboBox_currentIndexChanged(const QString &arg1)
{
QString list = ui->comboBox->currentText();
ui->lineEdit->setText(list);
Q_UNUSED(arg1)
}
void MainWindow::on_listView_activated(const QModelIndex &index)
{
QStringList allStrings = model->stringList();
QString last = allStrings.last();
if(last.startsWith("[ INFO] Minimum Distance: 5"))
{
ui->graphicsView->setBackgroundBrush(QColor(Qt::red));
}
else if(last.startsWith("[ INFO] Minimum Distance: 10"))
{
ui->graphicsView->setBackgroundBrush(QColor(Qt::yellow));
}
else if(last.startsWith("[ INFO] Minimum Distance: 15"))
{
ui->graphicsView->setBackgroundBrush(QColor(Qt::green));
}
Q_UNUSED(index)
}
EDIT 2
mainwindow.h
#include <QMainWindow>
#include <QGraphicsView>
#include <QGraphicsScene>
#include <QGraphicsTextItem>
#include <QStringListModel>
#include "listview.h"
QT_BEGIN_NAMESPACE
namespace Ui { class MainWindow; }
QT_END_NAMESPACE
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
MainWindow(QWidget *parent = nullptr);
~MainWindow();
public slots:
void setGraphicViewColor(QColor c);
private:
Ui::MainWindow *ui;
QGraphicsView *mView;
QGraphicsScene *mScene;
QGraphicsTextItem *mText;
QStringListModel *model;
ListView *myListView;
};
#endif // MAINWINDOW_H
mainwindow.cpp
#include "mainwindow.h"
#include "ui_mainwindow.h"
#include <QDebug>
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
, ui(new Ui::MainWindow)
{
ui->setupUi(this);
mView = new QGraphicsView();
mScene = new QGraphicsScene();
ui->graphicsView->setScene(mScene);
QFont font;
font.setPixelSize(10);
font.setBold(false);
font.setFamily("Calibri");
mText = new QGraphicsTextItem;
mText->setPos(150,70);
mScene->addText(tr("Boat outside alarm area"))->setDefaultTextColor(Qt::black);
model = new QStringListModel();
ui->listView->setModel(model);
ui->listView->setEditTriggers(QAbstractItemView::NoEditTriggers);
connect(ui->listView, SIGNAL(changeColor(QColor)), this, SLOT(setGraphicViewColor(QColor)));
}
MainWindow::~MainWindow()
{
delete ui;
}
void MainWindow::setGraphicViewColor(QColor c)
{
qDebug() << "Update your graphicsView backgroundBrush" << c;
ui->graphicsView->setBackgroundBrush(Qt::green);
}
listview.h
#ifndef LISTVIEW_H
#define LISTVIEW_H
#include <QListView>
#include <QStringListModel>
class ListView : public QListView
{
Q_OBJECT
public:
ListView(QWidget *parent = nullptr);
signals:
void changeColor(QColor c);
protected:
void dataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight, const QVector<int> &roles = QVector<int>()) override;
};
#endif // LISTVIEW_H
listview.cpp
#include "listview.h"
ListView::ListView(QWidget *parent)
: QListView(parent)
{}
void ListView::dataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight, const QVector<int> &roles)
{
QListView::dataChanged(topLeft, bottomRight, roles);
/**
* Assuming that you have just one item changed
* So topLeft == bottomRight
*/
if (topLeft.row() == model()->rowCount()-1){
QString last = topLeft.data().toString();
if(last.startsWith("[ INFO] Minimum Distance: 5")) {
emit changeColor(Qt::red);
} else if(last.startsWith("[ INFO] Minimum Distance: 10")) {
emit changeColor(Qt::yellow);
} else if(last.startsWith("[ INFO] Minimum Distance: 15")) {
emit changeColor(Qt::green);
}
}
}
Below the error and a screenshot of the ui that does not detect the change event:
The output of the .ui:
What I have done so far:
I have been doinf a lot of research about this problem and came across this source which was useful but could not solve the problem, but in addition it seems to use a QModelIndex and I am not sure this is exactly what I need for this small project.
Also I read this source which was useful to establish and capture the specific and unique string but in terms of changing colors I could not solve that.
Thank you very much for pointing in the right direction for solving this issue.
If I well understand, you want to change the backgroundBrush of your graphicsView if the last item of your QStringListModel starts with your specific strings.
To detect this, you can subclass QListView:
listview.h:
#ifndef LISTVIEW_H
#define LISTVIEW_H
#include <QListView>
#include <QStringListModel>
class ListView : public QListView
{
Q_OBJECT
public:
ListView(QWidget *parent = nullptr);
signals:
void changeColor(QColor c);
protected:
void dataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight, const QVector<int> &roles = QVector<int>()) override;
};
#endif // LISTVIEW_H
listview.cpp:
#include "listview.h"
ListView::ListView(QWidget *parent)
: QListView(parent)
{
}
void ListView::dataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight, const QVector<int> &roles)
{
QListView::dataChanged(topLeft, bottomRight, roles);
/**
* Assuming that you have just one item changed
* So topLeft == bottomRight
*/
if (topLeft.row() == model()->rowCount()-1){
QString last = topLeft.data().toString();
if(last.startsWith("[ INFO] Minimum Distance: 5")) {
emit changeColor(Qt::red);
} else if(last.startsWith("[ INFO] Minimum Distance: 10")) {
emit changeColor(Qt::yellow);
} else if(last.startsWith("[ INFO] Minimum Distance: 15")) {
emit changeColor(Qt::green);
}
}
}
Now you have all what you need, but you need to add a slot to connect the signal of your custom ListView with your QGraphicsView::brush
// Add this in your mainwindows.h
public slots:
void setGraphicViewColor(QColor c);
// This in the ctor of your MainWindow:
connect(ui->listView, SIGNAL(changeColor(QColor)), this, SLOT(setGraphicViewColor(QColor)));
// And the implementation of your custom slot in mainwindows.cpp
void MainWindow::setGraphicViewColor(QColor c)
{
qDebug() << "Update your graphicsView backgroundBrush" << c;
//ui->graphicsView->setBackgroundBrush(c);
}
I'm using a custom QTableView with a custom QAbstractTableModel and a QItemDelegate. I'd need to access the contents of the delegate's editor while the user is editing it, and after several attempts, I couldn't find anything satisfying.
Indeed, I've tried several things.
First: trying to access the delegate's current input (created through createEditor) through a property defined in QItemDelegate but... it seems that none exists. That's why I tried to add a QWidget* editor property and setting it in the createEditor.
Unfortunately, QItemDelegate's createEditor is supposed to be const, which makes me unable to set my property there (and since I don't control what calls createEditor, I can't do it before or after).
I don't really know what to do here. Actually, I also needed to know when the user started (or stopped) editing the cell content, which I eventually achieved by creating two const signals (editingStarted and editingStopped). I could probably create a const editorOpened(QWidget*) signal but it just feels bad and ugly...
I can't believe nothing "official" exists to achieve what I'm trying to do, hence this question. If I have everything wrong from the beginning, I'd be glad to know. If you have any other ideas, please suggest.
EDIT: Here is a minimal working example
MainWindow.h
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include <QMainWindow>
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
explicit MainWindow(QWidget *parent = nullptr);
~MainWindow();
};
#endif // MAINWINDOW_H
MainWindow.cpp
#include "mainwindow.h"
#include <QTableView>
#include "mytableview.h"
MainWindow::MainWindow(QWidget *parent) :
QMainWindow(parent)
{
auto tableView = new MyTableView(this);
setCentralWidget(tableView);
}
MainWindow::~MainWindow()
{
}
MyItemDelegate.h
#ifndef MYITEMDELEGATE_H
#define MYITEMDELEGATE_H
#include <QItemDelegate>
#include <QLineEdit>
#include <QStandardItemModel>
class MyItemDelegate : public QItemDelegate
{
Q_OBJECT
public:
MyItemDelegate(QObject* parent);
virtual QWidget* createEditor(QWidget* parent, const QStyleOptionViewItem& option, const QModelIndex& index) const;
virtual void onCloseEditor();
virtual ~MyItemDelegate() = default;
signals:
// Const signals trick
void editingStarted() const;
void editingFinished() const;
void editorOpened(const QWidget*) const;
};
#endif // MYITEMDELEGATE_H
MyItemDelegate.cpp
#include "myitemdelegate.h"
MyItemDelegate::MyItemDelegate(QObject* parent) : QItemDelegate(parent)
{
connect(this, &QItemDelegate::closeEditor, this, &MyItemDelegate::onCloseEditor);
}
QWidget* MyItemDelegate::createEditor(QWidget* parent, const QStyleOptionViewItem& option, const QModelIndex& index) const
{
auto lineEdit = new QLineEdit(parent);
emit editingStarted();
emit editorOpened(lineEdit);
return lineEdit;
}
void MyItemDelegate::onCloseEditor()
{
emit editingFinished();
}
MyTableView.h
#ifndef MYTABLEVIEW_H
#define MYTABLEVIEW_H
#include <QTableView>
#include <QDebug>
#include "myitemdelegate.h"
class MyTableView : public QTableView
{
Q_OBJECT
public:
explicit MyTableView(QWidget *parent = nullptr);
signals:
public slots:
};
#endif // MYTABLEVIEW_H
MyTableView.cpp
#include "mytableview.h"
MyTableView::MyTableView(QWidget *parent) : QTableView(parent)
{
MyItemDelegate* delegate = new MyItemDelegate(this);
QStandardItemModel* model = new QStandardItemModel(this);
setItemDelegate(delegate);
setModel(model);
QList<QList<QStandardItem*>> items;
for(int i = 0; i < 10; i++)
{
items << QList<QStandardItem*>();
for (int j = 'A'; j < 'E'; j++)
items[i] << new QStandardItem(QString("%1,%2").arg(i).arg(static_cast<char>(j)));
}
for (const auto& row : items)
model->appendRow(row);
connect(delegate, &MyItemDelegate::editingStarted, []() {
qDebug() << "Editing started";
});
connect(delegate, &MyItemDelegate::editingFinished, []() {
qDebug() << "Editing finished";
});
connect(delegate, &MyItemDelegate::editorOpened, [](const QWidget* editor) {
auto lineEdit = qobject_cast<const QLineEdit*>(editor);
connect(lineEdit, &QLineEdit::textChanged, [](const QString& text) {
qDebug() << text;
});
});
}
main.cpp
#include "mainwindow.h"
#include <QApplication>
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
MainWindow w;
w.show();
return a.exec();
}
The following solution maybe fits your needs. I just defined a new signal inside the delegate and connected to it inside the class owning the delegate.
MyItemDelegate.h
#ifndef MYITEMDELEGATE_H
#define MYITEMDELEGATE_H
#include <QStyledItemDelegate>
class MyItemDelegate : public QStyledItemDelegate
{
Q_OBJECT
public:
MyItemDelegate(QObject* parent);
QWidget* createEditor(QWidget* parent, const QStyleOptionViewItem& option, const QModelIndex& index) const;
virtual ~MyItemDelegate() = default;
signals:
void valueChanged(const QString&);
};
#endif // MYITEMDELEGATE_H
MyItemDelegate.cpp
#include "myitemdelegate.h"
#include <QLineEdit>
MyItemDelegate::MyItemDelegate(QObject* parent) : QStyledItemDelegate(parent)
{
}
QWidget* MyItemDelegate::createEditor(QWidget* parent, const QStyleOptionViewItem& option, const QModelIndex& index) const
{
auto lineEdit = new QLineEdit(parent);
connect(lineEdit, &QLineEdit::textChanged, this, &MyItemDelegate::valueChanged);
return lineEdit;
}
MyTableView.cpp
#include "mytableview.h"
#include <QStandardItemModel>
MyTableView::MyTableView(QWidget *parent) : QTableView(parent)
{
MyItemDelegate* delegate = new MyItemDelegate(this);
QStandardItemModel* model = new QStandardItemModel(this);
setItemDelegate(delegate);
setModel(model);
QList<QList<QStandardItem*>> items;
for(int i = 0; i < 10; i++)
{
items << QList<QStandardItem*>();
for (int j = 'A'; j < 'E'; j++)
items[i] << new QStandardItem(QString("%1,%2").arg(i).arg(static_cast<char>(j)));
}
for (const auto& row : items)
model->appendRow(row);
connect(delegate, &MyItemDelegate::valueChanged, [](auto v) { qDebug() << v; });
}
Well so I'm creating a Tower Defense game in Qt.
So my Tower is shouting and spawning a bullet class.
In this bullet class there is a polygon around it that allows me to detect if there's an enemy touching the bullet:
QList<QGraphicsItem*> colliding_items=collidingItems();
for(int i=0,n=colliding_items.size();i<n;i++){
if(typeid(*(colliding_items[i]))==typeid(Enemy))
{
//<------------ HERE ?
scene()->removeItem(this);
delete this;
return;
}
}
I created a function in the header file Enemy.h called receiveDamage(int damage):
void Enemy::reciveDamage(int damage){
m_health -= damage;
if(m_health <= 0){
game->scene->removeItem(this);
delete this;
return;
}
}
Content of Ennemy.h:
#ifndef ENEMY_H
#define ENEMY_H
#include <QGraphicsPixmapItem>
#include <QObject>
#include <QList> // list << element
#include <QPointF>
class Enemy: public QObject, public QGraphicsPixmapItem{
Q_OBJECT
public:
Enemy(QGraphicsItem * parent=0);
void rotateToPoint(QPointF p);
void reciveDamage(int damage);
public slots:
void move_forward();
private:
QList<QPointF> points;
QPointF dest;
int point_index;
int m_speed = 2;
int m_health = 100;
};
#endif // ENEMY_H
But I don't know how to call it in my code up there, any ideas ?
Yes, just dynamic_cast or qobject_cast the QGraphicsItem into a Enemy class and call getDamage on it.
If I suppose that the amount of damage is given by a method named sendDamage, the code could look like:
QList<QGraphicsItem*> colliding_items=collidingItems();
for(int i=0, n=colliding_items.size(); i<n; i++){
if(typeid(*(colliding_items[i])) == typeid(Enemy))
{
Enemy* enemy = qobject_cast<Enemy*> (colliding_items[i]);
if (enemy)
{
enemy->getDamage(this->sendDamage());
}
scene()->removeItem(this);
delete this;
return;
}
}
Horde3d come with 2 samples, compiled with GLFW. One of them, Knight, shows a particle emitter. I've ported the samples to Qt, with a thin layer I wrote that leave unchanged the applicative code (i.e. scene setup, rendering and events handling).
Indeed, the functionality is ok, except the particle emitter doesn't show. I can't see anything specific in GLFW initialization, and I tried some setting I found in Qt, without success. Horde3d takes care of OpenGL interface, and expose a higher level, clean C interface. Any clue?
EDIT : Most Rilevant Sources of qtKnight.pro
here (cleaned :) main.cpp
#include "glwidget.h"
#include <QApplication>
int main(int argc, char* argv[]) {
QApplication app(argc, argv);
GLWidget glw;
glw.show();
return app.exec();
}
here glWidget.h
#ifndef GL_WIDGET_H
#define GL_WIDGET_H
#include <QtOpenGL>
#include <QTimer>
#include <QKeyEvent>
#include <Horde3D.h>
#include <Horde3DUtils.h>
#include <sstream>
#include <app.h>
class GLWidget : public QGLWidget, Application {
Q_OBJECT
public:
GLWidget();
~GLWidget();
QSize minimumSizeHint() const { return sizeHint(); }
QSize sizeHint() const { return QSize(640, 480); }
protected:
void initializeGL();
void paintGL();
void resizeGL(int width, int height);
void mousePressEvent(QMouseEvent *event);
void mouseMoveEvent(QMouseEvent *event);
void mouseReleaseEvent(QMouseEvent *event);
void keyPressEvent(QKeyEvent *e) { keyEvent(e, true); QGLWidget::keyPressEvent(e); }
void keyReleaseEvent(QKeyEvent *e) { keyEvent(e, false); QGLWidget::keyReleaseEvent(e); }
void keyEvent(QKeyEvent *, bool);
public slots:
void appLoop() { updateGL(); }
private:
QPoint lastPos;
QTimer evloop;
};
#endif
and here glWidget.cpp
#include "glwidget.h"
#include <stdexcept>
#include <QtDebug>
#include <QTextStream>
#include <QGLFormat>
GLWidget::GLWidget() :
QGLWidget(QGLFormat(QGL::AlphaChannel | QGL::SampleBuffers)),
Application("/home/carlo/horde3d/SDK_1.0.0_Beta5/Horde3D/Binaries/Content")
{
connect(&evloop, SIGNAL(timeout()), this, SLOT(appLoop()));
evloop.start(0);
}
GLWidget::~GLWidget()
{
h3dutDumpMessages();
h3dRelease();
}
void GLWidget::initializeGL()
{
if (!init())
throw std::runtime_error("Could not initialize renderer");
}
void GLWidget::paintGL()
{
keyStateHandler();
mainLoop(30);
}
void GLWidget::resizeGL(int width, int height)
{
Application::resize(width, height);
}
void GLWidget::mousePressEvent(QMouseEvent *event)
{
lastPos = event->pos();
}
void GLWidget::mouseMoveEvent(QMouseEvent *event)
{
QPoint cPos = event->pos();
float dX = cPos.x() - lastPos.x(); //event->x() - lastPos.x();
float dY = cPos.y() - lastPos.y(); //event->y() - lastPos.y();
Application::mouseMoveEvent(dX, dY);
lastPos = cPos;
}
void GLWidget::mouseReleaseEvent(QMouseEvent * /* event */)
{
}
void GLWidget::keyEvent(QKeyEvent *k, bool on_off)
{
#define setK(X,Y) case Qt::X: setKeyState(Y, on_off); break;
#define setF(X) case Qt::Key_##X: setKeyState(X, on_off); break;
#define R(X, Y) (v >= #X[0] && v <= #Y[0])
int v = k->key();
switch (v) {
case Qt::Key_F1: if (on_off) showFullScreen(); break;
case Qt::Key_F2: if (on_off) showNormal(); break;
setF(F3)
setF(F6)
setF(F7)
setF(F8)
setK(Key_Space, SP)
default:
if (R(A, Z) || R(0, 9))
setKeyState(v, on_off);
}
}
Since you say it is only the particles that aren't shown, it could have to do with alpha blending. I also saw from your code that you don't specify a format when constructing the QGLWidget, in which case the default format is used which explicitly disables the alpha channel.
Though I don't know if this has any effect (shouldn't there always be an A in RGBA?), but maybe this really requests a pixel format where there is no storage for the A channel, in which case things like alpha blending (used for the transparent particles) won't work.
So just try to request it explicitly by using an appropriate format in the QGLWidget constructor:
GLWidget::GLWidget()
: QGLWidget(QGLFormat(QGL::AlphaChannel))
You actually were on the right track with your outcommented QGLFormat argument, but it hasn't anything to do with the HasOverlay option, which you don't really need, as Horde3d does it's own overlay rendering.