Sort Arrows Disappear When Subclassing QHeaderView - c++

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

Related

How to create a vertical (rotated) button in Qt with C++

I'd like to create a vertical button in Qt (using C++, not Python), with text rotated 90ยบ either clockwise or counterclockwise. It doesn't seem to be possible with a standard QPushButton.
How could I do it?
In order to create a vertical button in Qt, you can subclass QPushButton so that the dimensions reported by the widget are transposed, and also modify the drawing event to paint the button with the proper alignment.
Here's a class called OrientablePushButton that can be used as a drop-in replacement of the traditional QPushButton but also supports vertical orientation through the usage of setOrientation.
Aspect:
Sample usage:
auto anotherButton = new OrientablePushButton("Hello world world world world", this);
anotherButton->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Minimum);
anotherButton->setOrientation(OrientablePushButton::VerticalTopToBottom);
Header file:
class OrientablePushButton : public QPushButton
{
Q_OBJECT
public:
enum Orientation {
Horizontal,
VerticalTopToBottom,
VerticalBottomToTop
};
OrientablePushButton(QWidget * parent = nullptr);
OrientablePushButton(const QString & text, QWidget *parent = nullptr);
OrientablePushButton(const QIcon & icon, const QString & text, QWidget *parent = nullptr);
QSize sizeHint() const;
OrientablePushButton::Orientation orientation() const;
void setOrientation(const OrientablePushButton::Orientation &orientation);
protected:
void paintEvent(QPaintEvent *event);
private:
Orientation mOrientation = Horizontal;
};
Source file:
#include <QPainter>
#include <QStyleOptionButton>
#include <QDebug>
#include <QStylePainter>
OrientablePushButton::OrientablePushButton(QWidget *parent)
: QPushButton(parent)
{ }
OrientablePushButton::OrientablePushButton(const QString &text, QWidget *parent)
: QPushButton(text, parent)
{ }
OrientablePushButton::OrientablePushButton(const QIcon &icon, const QString &text, QWidget *parent)
: QPushButton(icon, text, parent)
{ }
QSize OrientablePushButton::sizeHint() const
{
QSize sh = QPushButton::sizeHint();
if (mOrientation != OrientablePushButton::Horizontal)
{
sh.transpose();
}
return sh;
}
void OrientablePushButton::paintEvent(QPaintEvent *event)
{
Q_UNUSED(event);
QStylePainter painter(this);
QStyleOptionButton option;
initStyleOption(&option);
if (mOrientation == OrientablePushButton::VerticalTopToBottom)
{
painter.rotate(90);
painter.translate(0, -1 * width());
option.rect = option.rect.transposed();
}
else if (mOrientation == OrientablePushButton::VerticalBottomToTop)
{
painter.rotate(-90);
painter.translate(-1 * height(), 0);
option.rect = option.rect.transposed();
}
painter.drawControl(QStyle::CE_PushButton, option);
}
OrientablePushButton::Orientation OrientablePushButton::orientation() const
{
return mOrientation;
}
void OrientablePushButton::setOrientation(const OrientablePushButton::Orientation &orientation)
{
mOrientation = orientation;
}

When customizing a Qt slider with a Style in code, the handle goes off the groove

In most examples, customizing the Qt slider is done like this (with a stylesheet):
mySlider = new QSlider(centralWidget);
mySlider->setObjectName(QStringLiteral("mySlider"));
mySlider->setGeometry(QRect(645, 678, 110, 21));
mySlider->setOrientation(Qt::Horizontal);
mySlider->setStyleSheet("QSlider::groove:horizontal {background-image:url(:/main/graphics/mySliderBackround.png);}"
"QSlider::handle:horizontal {background-image:url(:/main/graphics/mySliderHandle.png); height:21px; width: 21px;}");
This works fine for me as well.
I have a situation where I need to programmatically set the background using a dyamically created pixmap. Using the code below, this is how I accomplished it. The problem is that when I am on Fedora Linux, this slider works fine. When I'm on OSX or Windows, the slider handle goes off the goove.
Here's what it looks like on OSX. Notice how the handle is off the groove. The left side is customized with a stylesheet, the right is customized with the Style object below.
Create the slider and assign the style:
mySlider = new QSlider(centralWidget);
mySlider->setObjectName(QStringLiteral("mySlider"));
mySlider->setGeometry(QRect(645, 678, 110, 21));
mySlider->setOrientation(Qt::Horizontal);
mySlider->setStyle(new MySliderStyle(mySlider->style()));
The custom slider style code:
Header
#ifndef MYSTYLE_H
#define MYSTYLE_H
#include <QObject>
#include <QWidget>
#include <QProxyStyle>
#include <QPainter>
#include <QStyleOption>
#include <QtWidgets/QCommonStyle>
class MySliderStyle : public QProxyStyle
{
private:
QPixmap groovePixmap;
QPixmap handlePixmap;
public:
LightingSliderStyle(QStyle *style)
: QProxyStyle(style)
{
setColor(QColor::fromRgba(0));
this->handlePixmap = <snip initialize the pixmap>;
this->grooveMaskPixmap = <snip initialize the pixmap>;
}
void drawComplexControl(QStyle::ComplexControl control, const QStyleOptionComplex *option, QPainter *painter, const QWidget *widget) const;
QRect subControlRect(ComplexControl cc, const QStyleOptionComplex *opt, SubControl sc, const QWidget *widget) const;
void setColor(QColor color);
};
#endif // MYSTYLE_H
Implementation*
#include "MySliderStyle.h"
QRect MySliderStyle::subControlRect(ComplexControl control,
const QStyleOptionComplex *option,
SubControl subControl,
const QWidget *widget) const
{
QRect rect;
rect = QCommonStyle::subControlRect(control, option, subControl, widget);
if (control == CC_Slider && subControl == SC_SliderHandle)
{
// this is the exact pixel dimensions of the handle png files
rect.setWidth(21);
rect.setHeight(21);
}
else if (control == CC_Slider && subControl == SC_SliderGroove)
{
// this is the exact pixel dimensions of the slider png files
rect.setWidth(widget->width());
rect.setHeight(widget->height());
}
return rect;
}
void MySliderStyle::drawComplexControl(QStyle::ComplexControl control,
const QStyleOptionComplex *option,
QPainter *painter,
const QWidget *widget) const
{
if (control == CC_Slider)
{
if (const QStyleOptionSlider *slider = qstyleoption_cast<const QStyleOptionSlider *>(option))
{
QRect groove = subControlRect(CC_Slider, slider, SC_SliderGroove, widget);
QRect handle = subControlRect(CC_Slider, slider, SC_SliderHandle, widget);
if ((slider->subControls & SC_SliderGroove) && groove.isValid())
{
Qt::BGMode oldMode = painter->backgroundMode();
painter->setBackgroundMode(Qt::TransparentMode);
painter->drawPixmap(groove, groovePixmap);
painter->setBackgroundMode(oldMode);
}
if ((slider->subControls & SC_SliderHandle) && handle.isValid())
{
Qt::BGMode oldMode = painter->backgroundMode();
painter->setBackgroundMode(Qt::TransparentMode);
painter->drawPixmap(handle, handlePixmap);
painter->setBackgroundMode(oldMode);
}
}
}
else
{
QProxyStyle::drawComplexControl(control, option, painter, widget);
}
}
void MySliderStyle::setColor(QColor color)
{
QImage myGrooveImage;
// <snip>
// Code to create the custom pixmap
// <snip>
groovePixmap = QPixmap::fromImage(myGrooveImage);
}
UPDATE
The code for this project is open source and available here
A call to QCommonStyle::subControlRect and adjusting the width/height is not enough. You must also recalculate the x/y position.
You can therfore use the QCommonStyle::subControlRect function as reference to calculate the proper rectangle:
QRect LightingSliderStyle::subControlRect(ComplexControl control,
const QStyleOptionComplex *option,
SubControl subControl,
const QWidget *widget) const
{
if (control == CC_Slider)
{
if (const QStyleOptionSlider *slider = qstyleoption_cast<const QStyleOptionSlider *>(option)) {
QRect ret;
int tickOffset = 0;
int thickness = 21; // height
int len = 21; // width
switch (subControl) {
case SC_SliderHandle: {
int sliderPos = 0;
bool horizontal = slider->orientation == Qt::Horizontal;
sliderPos = sliderPositionFromValue(slider->minimum, slider->maximum+1,
slider->sliderPosition,
(horizontal ? slider->rect.width()
: slider->rect.height()) - len,
slider->upsideDown);
if (horizontal)
ret.setRect(slider->rect.x() + sliderPos, slider->rect.y() + tickOffset, len, thickness);
else
ret.setRect(slider->rect.x() + tickOffset, slider->rect.y() + sliderPos, thickness, len);
break; }
case SC_SliderGroove:
if (slider->orientation == Qt::Horizontal)
ret.setRect(slider->rect.x(), slider->rect.y() + tickOffset,
slider->rect.width(), thickness);
else
ret.setRect(slider->rect.x() + tickOffset, slider->rect.y(),
thickness, slider->rect.height());
break;
default:
break;
}
return visualRect(slider->direction, slider->rect, ret);
}
}
return QCommonStyle::subControlRect(control, option, subControl, widget);
}

Display Text On QtCharts

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.

qt custom qstyle for qtabbar

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.

Qt tableView - add color

How do I add color on the sides of a tableView?
You should subclass QHeaderView and implement your own class like:
#include<QtWidgets>
class HeaderView: public QHeaderView
{
public:
HeaderView():QHeaderView(Qt::Vertical)
{}
void paintSection(QPainter * painter, const QRect & rect, int logicalIndex) const
{
QBrush brush;
if(logicalIndex == 0)
brush.setColor(QColor(Qt::red));
else
brush.setColor(QColor(Qt::blue));
painter->fillRect(rect, brush);
QPen pen(Qt::white);
painter->setPen(pen);
painter->drawText(rect,QString("Row %1").arg(logicalIndex));
}
};
Next set an instance of HeaderView as the vertical header of the QTableView:
HeaderView vView;
tableview.setVerticalHeader(&vView);