I have a QGraphicsScene which contains some symbols (many symbols are repeated) . These symbols consist of line. I have created a separate class for line which inherits from QGraphicsLineItem. All these lines are grouped under one entity using QGraphicsItemGroup.
Here is code structure:
I have a map which contains all the line co-ordinates. I am taking co-ordinates from map and giving those to Qt's API.
myGroup.h
class myGroup: public QGraphicsItemGroup
{
virtual void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget);
void PaintSymbol(QGraphicsItem *item, QPainter *painter, const
QStyleOptionGraphicsItem *option);
}
myGroup.cpp
void myGroup::paint(QPainter* painter, const QStyleOptionGraphicsItem* option, QWidget* widget)
{
auto copied_option = *option;
copied_option.state &= ~QStyle::State_Selected;
auto selected = option->state & QStyle::State_Selected;
myGroup::paint(painter, &copied_option, widget);
if (selected)
{
foreach (QGraphicsItem* item, _scene->selectedItems()) {
myGroup* inst = qgraphicsitem_cast<myGroup*>(item);
if (!inst)
continue;
QList<QGraphicsItem*> childItems = inst->childItems();
foreach (QGraphicsItem* chItem, childItems) {
if (myLine* line = dynamic_cast<myLine*>(chItem)) {
PaintSymbol(line, painter, option);
}
void myGroup::PaintSymbol(QGraphicsItem* item, QPainter* painter, const
QStyleOptionGraphicsItem* option)
{
painter->save();
painter->setPen(QPen(option->palette.windowText(), 0, Qt::SolidLine));
if(myLine* line = dynamic_cast<myLine*>(item))
painter->drawPath(line->shape());
painter->restore();
}
myLine.h
class myLine: public QGraphicsLineItem {
public:
myLine();
myLine(int x1, int y1, int x2, int y2, QGraphicsItem *parent = nullptr);
};
DrawFun().cpp
myGroup* GroupItem= new myGroup();
for( iterating over map )
{
// taking co-ordinates
myLine* line = new myLine(first, second, third, fourth);
GroupItem->addToGroup(line);
}
// Control comes here when I click on symbol on screen.
HighlightObject()
{
// here I find symbols under click. ( i.e. itemUnderClick )
myGroup* gItem = qgraphicsitem_cast<myGroup*>(itemUnderClick);
if (gItem)
gItem->setSelected(true); // from here control goes to paint() in myGroup
}
Now issue is :
For some symbols, selected variable in paint() gives value as ( in debugging I found)
QStyle::State_None (0x0000)
And for some symbols it gives value as :
QStyle::State_Selected (0x8000)
For value : QStyle::State_None (0x0000) paint() does not highlight object.
Where am I wrong in heighlighting all objects ?
Related
I am trying to create a digital gates symbols ( like AND,NAND etc ) using line, arc, circle. To do this I am using QGraphicsItemGroup also.
For example. If a symbol contains 3 lines, 2 arc and 1 circle, then I add everything in object of QGraphicsItemGroup and then I add this object in scene.
myGroupItem.h
class myGroupItem: public QGraphicsItemGroup
{
virtual void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget);
}
myGroupItem.cpp
void myGroupItem ::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget);
{
auto copied_option = *option;
copied_option.state &= ~QStyle::State_Selected;
auto selected = option->state & QStyle::State_Selected;
QGraphicsItemGroup::paint(painter, &copied_option, widget);
if (selected)
{
foreach (QGraphicsItem* item, _scene->selectedItems())
{
myGroupItem* inst = qgraphicsitem_cast<myGroupItem*>(item);
if (!inst)
continue;
QList<QGraphicsItem*> childItems = inst->childItems();
foreach (QGraphicsItem* chItem, childItems)
{
if (myPoly* poly = dynamic_cast<myPoly*>(chItem))
{
painter->save();
painter->setPen(QPen(option->palette.windowText(), 0, Qt::SolidLine));
painter->drawPath(poly->shape());
painter->restore();
}
} else {
if (myLine* line = dynamic_cast<myLine*>(chItem)) {
painter->save();
painter->setPen(QPen(option->palette.windowText(), 0, Qt::SolidLine));
painter->drawPath(line->shape());
painter->restore();
} else {
if (myEllipse* ell = dynamic_cast<myEllipse*>(chItem)) {
painter->save();
painter->setPen(QPen(option->palette.windowText(), 0, Qt::SolidLine));
painter->drawPath(ell->shape());
painter->restore();
}
}
}
}
}
}
}
myLine.h
class myLine : public QGraphicsLineItem
{
}
myEllipse.h
class myEllipse : public QGraphicsEllipseItem
{
}
class BuildDesign()
{
myGroupItem* groupItem = new myGroupItem();
// accessing all lines
myLine* line = new myLine(co-ordinate of lines);
groupItem ->addToGroup(line);
// accessing arc
groupItem ->addToGroup(arc);
// accessing circle
groupItem ->addToGroup(ellipse);
scene->addItem(groupItem);
}
This way I am creating a symbol and it is creating propely. Issue is when I want to select that
symbol and process on it.
// This function will check is there any item under mouse click. If yes then call another
// function to highlight it and if not then Refresh the scene.
void BuildDesign::SelectObject(QEvent* event)
{
QMouseEvent* mouseEvent = static_cast<QMouseEvent*>(event);
QPointF mousePoint = _view->mapToScene(mouseEvent->pos());
QGraphicsItem* itemUnderClick = _scene->itemAt(mousePoint, QTransform());
if (!itemUnderClick)
RefreshScene();
else
HighlightSelectedObject(itemUnderClick);
}
void BuildDesign::HighlightSelectedObject(QGraphicsItem* itemUnderClick)
{
// QGraphicsItemGroup* gItem = qgraphicsitem_cast<QGraphicsItemGroup*>(itemUnderClick);
myGroupItem* gItem = qgraphicsitem_cast<myGroupItem*>(itemUnderClick);
if (gItem) {
gItem->setSelected(true); // this never gets executed
}
}
Now after debugging I found that, itemUnderClick has some address but after type casting to gItem always shows address as 0x0.
I want to execute gItem->setSelected(true); ( which never execute ) and further I want to use
foreach (QGraphicsItem* currentItem, _scene->selectedItems()) and currentItem should be that selected symbol. But it is not happening.
As stated by the doc (https://doc.qt.io/qt-6/qgraphicsitem.html#qgraphicsitem_cast), qgraphicsitem_cast returns nullptr if the given item is not of type T, OR, if it cannot deduce its type.
The last case is the cause of your issue here, you have to reimplement the type() function for each custom QGraphicsItem subclass.
The following link shows how to do this, https://www.qtcentre.org/threads/54530-qgraphicsitem_cast-cannot-convert-seft-defined-types , see the answer of Santosh Reddy.
I am subclassing QHeaderView to add a filtering icon in the horizontal header of a QTableView. The QTableView has sorting capability activated consume a QSortFilterProxyModel, until now it works fine.
However when I try to subclass QHeaderView and use it as column header, only the first column shows the filter icon.
headerview_filter.h
#ifndef HEADERVIEW_FILTER_H
#define HEADERVIEW_FILTER_H
#include <QHeaderView>
class HeaderView_Filter : public QHeaderView
{
Q_OBJECT
public:
explicit HeaderView_Filter(Qt::Orientation orientation, QWidget * parent = nullptr);
void paintSection(QPainter *painter, const QRect &rect, int logicalIndex) const override;
private:
const QPolygonF _funel = QPolygonF({{22.0,36.0},{22.0,22.0},{10.0,10.0},{40.0,10.0},{28.0,22.0},{28.0,36.0}});
};
#endif // HEADERVIEW_FILTER_H
headerview_filter.cpp
#include "headerview_filter.h"
HeaderView_Filter::HeaderView_Filter(Qt::Orientation orientation, QWidget * parent)
: QHeaderView(orientation, parent)
{
setSectionsClickable(true);
}
void HeaderView_Filter::paintSection(QPainter *painter, const QRect &rect, int logicalIndex) const
{
painter->save();
QHeaderView::paintSection(painter, rect, logicalIndex);
painter->restore();
const double scale = 0.6*rect.height()/50.0;
painter->setBrush(Qt::black);
painter->translate(0,5);
painter->scale(scale, scale);
painter->drawPolygon(_funel);
painter->restore();
}
using it in form :
auto* tableView = _ui->tableView_Data;
tableView->setModel(_sortFilterProxyModel);
tableView->setSortingEnabled(true);
tableView->setHorizontalHeader(new HeaderView_Filter(Qt::Horizontal,tableView));
I found the solution while typing it and prefered to post the code for other to use.
The position of the drawing must be translated relative to the drawing rectangle provided as argument of paintSection :
void HeaderView_Filter::paintSection(QPainter *painter, const QRect &rect, int logicalIndex) const
{
painter->save();
QHeaderView::paintSection(painter, rect, logicalIndex);
painter->restore();
const double scale = 0.6*rect.height()/50.0;
painter->setBrush(Qt::black);
// Here
painter->translate(rect.x(),rect.y()+5);
//
painter->scale(scale, scale);
painter->drawPolygon(_funel);
painter->restore();
}
I am new at Qt and I want to write my custom QGraphicsItem which contains a rectangle and couple of buttons. I want to write a single custom component that could be easily added to QGraphicsScene and moved or resized with contents(buttons and rectangles) in it. In the end I want to add multiple customized QGraphicsItem to my QGraphicsScene. My question is how can I write this customized QGraphicsItem that contains buttons and rectangles which relative positions to each other are constant.
In this drawing green colored rectangles represent buttons and their relative position to each other always stays same (as if they are placed using qlayouts)
Thanks to #replete, from the example at http://doc.qt.io/qt-5/qtwidgets-graphicsview-dragdroprobot-example.html I was able to create a custom QGraphicsItem with clickable sub-parts in it. In code below BboxItem represents container QGraphicsItem and BboxItemContent represents childs of it. By emitting signals whith mause click events I was able to implement button like features. And I can move the BboxItem by setting its bounding rectangle.
BboxItem related source code:
BboxItemContent::BboxItemContent(QGraphicsItem *parent, int type, QColor color,QRectF *rect)
: QGraphicsObject(parent)
{
content_rectangle = rect;
content_type = type;
switch (type)
{
case 0:
rectangle_color = color;
icon = 0;
break;
case 1:
icon = new QImage(":/resource/assets/info_btn.png");
break;
case 2:
icon = new QImage(":/resource/assets/close_btn.png");
break;
}
}
BboxItemContent::~BboxItemContent()
{
delete icon;
}
QRectF BboxItemContent::boundingRect() const
{
return QRectF(content_rectangle->x(), content_rectangle->y(), content_rectangle->width(), content_rectangle->height());
}
void BboxItemContent::paint(QPainter *painter,
const QStyleOptionGraphicsItem *option, QWidget *widget)
{
if (icon == 0)
{
QPen pen(rectangle_color, 3);
painter->setPen(pen);
painter->drawRect(*content_rectangle);
}
else
{
painter->drawImage(*content_rectangle, *icon);
}
}
void BboxItemContent::mousePressEvent(QGraphicsSceneMouseEvent * event)
{
emit bboxContentClickedSignal();
}
void BboxItemContent::setRect(QRectF *rect)
{
content_rectangle = rect;
update();
}
BboxItem::BboxItem(QGraphicsItem *parent,QRectF *itemRect) : BboxItemContent(parent,0,Qt::red, itemRect)
{
setFlag(ItemHasNoContents);
bbox_area = new BboxItemContent(this, 0, Qt::red, itemRect);
info_btn = new BboxItemContent(this, 1, Qt::red, new QRectF(itemRect->x() - 30, itemRect->y(), 30, 30));
connect(info_btn, &BboxItemContent::bboxContentClickedSignal, this, &BboxItem::onInfoClickedSlot);
delete_btn= new BboxItemContent(this, 2, Qt::red, new QRectF((itemRect->x()+itemRect->width()), itemRect->y(), 30, 30));
connect(delete_btn, &BboxItemContent::bboxContentClickedSignal, this, &BboxItem::onDeleteClickedSlot);
}
void BboxItem::onDeleteClickedSlot()
{
//delete clicked actions
}
void BboxItem::onInfoClickedSlot()
{
//info clicked actions
}
void BboxItem::setRect(QRectF *rect)
{
bbox_area->setRect(rect);
info_btn->setRect(new QRectF(rect->x() - 30, rect->y(), 30, 30));
delete_btn->setRect(new QRectF((rect->x() + rect->width()), rect->y(), 30, 30));
}
Related Headers:
class BboxItemContent : public QGraphicsObject
{
Q_OBJECT
public:
BboxItemContent(QGraphicsItem *parent = 0, int type = 0, QColor color = Qt::red, QRectF *rect=nullptr);
~BboxItemContent();
// Inherited from QGraphicsItem
QRectF boundingRect() const override;
void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget = 0) override;
void setRect(QRectF *rect);
signals:
void bboxContentClickedSignal();
protected:
void mousePressEvent(QGraphicsSceneMouseEvent *event);
private:
QImage *icon;
QColor rectangle_color;
QRectF *content_rectangle;
int content_type;
};
class BboxItem : public BboxItemContent {
Q_OBJECT
public:
BboxItem(QGraphicsItem *parent = 0,QRectF *itemRect=nullptr);
void setRect(QRectF *rect);
private slots:
void onDeleteClickedSlot();
void onInfoClickedSlot();
private:
BboxItemContent *delete_btn;
BboxItemContent *bbox_area;
BboxItemContent *info_btn;
};
I am using the QtCharts class to make a line graph. I want to be able to add note/text on the graph itself. How can I do this? I found a example similar to what I want " https://doc.qt.io/qt-5/qtcharts-callout-example.html" is there a easier way?
This is my version. There may be errors
textitem.h
#pragma once
#include <QtCharts/QChartGlobal>
#include <QtWidgets/QGraphicsItem>
QT_CHARTS_BEGIN_NAMESPACE
class QChart;
class QAbstractSeries;
QT_CHARTS_END_NAMESPACE
QT_CHARTS_USE_NAMESPACE
class TextItem : public QGraphicsItem {
public:
TextItem(QString text, QPoint pos, QChart *chart, QAbstractSeries *series);
QRectF boundingRect() const override;
void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) override;
void setText(const QString &text);
void setAnchor(QPointF point);
private:
QChart *_chart;
QAbstractSeries *_series;
QString _text;
QRectF _textRect;
QPointF _anchor;
};
textitem.cpp
#include "textitem.h"
#include <QtCharts/QChart>
#include <QPainter>
#include <QRect>
TextItem::TextItem(QString text, QPoint pos, QChart *chart, QAbstractSeries *series)
: QGraphicsItem(chart), _chart(chart), _series(series), _anchor(pos) {
setText(text);
}
QRectF TextItem::boundingRect() const {
QPointF anchor = mapFromParent(_chart->mapToPosition(_anchor, _series));
QRectF rect;
rect.setLeft(qMin(_textRect.left(), anchor.x()));
rect.setRight(qMax(_textRect.right(), anchor.x()));
rect.setTop(qMin(_textRect.top(), anchor.y()));
rect.setBottom(qMax(_textRect.bottom(), anchor.y()));
return rect;
}
void TextItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) {
Q_UNUSED(option)
Q_UNUSED(widget)
QPointF anchor = mapFromParent(_chart->mapToPosition(_anchor, _series));
painter->drawText(anchor, _text);
}
void TextItem::setText(const QString &text) {
_text = text;
QFontMetrics metrics((QFont()));
_textRect = metrics.boundingRect(QRect(0, 0, 150, 150),
Qt::AlignLeft, _text);
_textRect.translate(5, 5);
prepareGeometryChange();
}
void TextItem::setAnchor(QPointF point) { _anchor = point; }
I know it's a late reply and not anymore relevant for you, though it might be for others:
As far as I can see, they already use a pretty easy way of doing this.
In their example, they use the QGraphicsTextItem to add text at a specific position in a chart.
Assume you have a series and want to add text to each point in this series which is visible in the chart.
Here is an (python PySide6) example of how you can do it (you can do it the same way in c++):
chart = QChart() # some chart
series = QScatterSeries() # some series which has been added to the chart
# create text items and add to chart
my_text_items = []
for i in range(series.count()):
text_item = QGraphicsTextItem("point with idx: {}".format(i), parent=chart)
my_text_items.append(text_item)
# define function to set/update the position of the text items (has to be called during resize)
def update_position_of_text_items(text_items, series):
for i, text_item in enumerate(text_items):
# get point at index i
point = series.at(i)
# map its position of the series to the position in the chart
position_in_chart = series.chart().mapToPosition(point, series)
# set the position of the text item at index i accordingly
text_item.setPos(position_in_chart)
update_position_of_text_items(my_text_items, series)
Make sure to call the update_position_of_text_items(..) when resizing your widget.
I am using the following tab style for drawing tabbar text horizontally for
east tab position. The text is drawn ok, as long as i dont set any margin for
QTabBar::tab. In that case the text orientation remains vertical with strange offset.
class TabStyle : public QProxyStyle {
public:
explicit TabStyle(Qt::Orientation orientation, QStyle *baseStyle = 0)
: QProxyStyle(baseStyle), mOrientation(orientation) {}
void drawControl(ControlElement element, const QStyleOption *option,
QPainter *painter, const QWidget *widget) const {
if (element == CE_TabBarTabLabel) {
if (const QStyleOptionTab *tab =
qstyleoption_cast<const QStyleOptionTab *>(option)) {
QStyleOptionTab opt(*tab);
opt.shape = QTabBar::RoundedNorth;
return QProxyStyle::drawControl(element, &opt, painter, widget);
}
}
QProxyStyle::drawControl(element, option, painter, widget);
}
private:
const Qt::Orientation mOrientation;
};
class TabWidget : public QTabWidget {
public:
explicit TabWidget(QWidget *parent = 0,
Qt::Orientation orientation = Qt::Horizontal)
: QTabWidget(parent) {
QTabBar *tabBar = new QTabBar;
tabBar->setStyle(new TabStyle(orientation));
setTabBar(tabBar);
}
};
Why don't you Qt style sheets ? In the designer you could check it on the fly and you don't need write any code for that.