Drawing an overlay on top of an application's window - qt

I want to be able to paint on top of my application's window so that I can annotate all the widgets with some extra diagnostic information, similar to the CSS developer tools in Firefox (eg add widget classes, styles, highlight borders etc).
I can walk the widget tree and extract the relevant information, but the question is how can I overlay all the application windows with this information?
One way would be to override my QMainWindow's paint event, but this has to be done for all top level windows. Is there an alternative method where you can paint on the QDesktopWidget for instance? Or any hooks into each QWidget's paint method? Anything that involves subclassing QWidget itself won't work with the standard widgets.
This follows on from my previous question:
Are there any useful tools for diagnosing Qt layout and spacing problems?
cheers
Mandrill
EDIT:
Thanks to Dmitry I've now got a really simple method that is easily extensible:
class DiagnosticStyle : public QWindowsVistaStyle
{
Q_OBJECT
public:
typedef QWindowsVistaStyle BaseStyle;
void drawControl(ControlElement element, const QStyleOption* option, QPainter* painter, const QWidget* widget) const;
};
void DiagnosticStyle::drawControl(ControlElement element, const QStyleOption* option, QPainter* painter, const QWidget* widget) const
{
BaseStyle::drawControl(element, option, painter, widget);
if (widget && painter) {
// draw a border around the widget
painter->setPen(QColor("red"));
painter->drawRect(widget->rect());
// show the classname of the widget
QBrush translucentBrush(QColor(255,246,240, 100));
painter->fillRect(widget->rect(), translucentBrush);
painter->setPen(QColor("darkblue"));
painter->drawText(widget->rect(), Qt::AlignLeft | Qt::AlignVCenter, widget->metaObject()->className());
}
}
qApp->setStyle(new DiagnosticStyle());

You can create own style class based on QMotifStyle or other ... and paint on any widget/control related to him information.
void MyStyle::drawPrimitive(PrimitiveElement element, const QStyleOption *option,QPainter *painter, const QWidget *widget) const
{
QStyle::State flags = option->state;
QRect rect = option->rect;
QPalette pal = option->palette;
QBrush brush;
switch (element)
{
case PE_FrameTabWidget:
{
painter->save();
// for example: draw anything on TabWidget
painter->drawPixmap(rect,centerPm,centerPm.rect());
painter->restore();
}
break;
default:
QMotifStyle::drawPrimitive(element, option, painter, widget);
break;
}
}

Somewhere in Qt5 the styles (GTK, Windows, etc) were made internal. Now you need to use QCommonStyle.
If anyone's wondering how to do this with Qt5+. Here's a self-contained version of #the_mandrill's code above.
class DiagnosticStyle : public QCommonStyle
{
Q_OBJECT
public:
typedef QStyle BaseStyle;
void drawControl(ControlElement element, const QStyleOption* option, QPainter* painter, const QWidget* widget) const
{
QCommonStyle::drawControl(element, option, painter, widget);
if (widget && painter) {
// draw a border around the widget
painter->setPen(QColor("red"));
painter->drawRect(widget->rect());
// show the classname of the widget
QBrush translucentBrush(QColor(255,246,240, 100));
painter->fillRect(widget->rect(), translucentBrush);
painter->setPen(QColor("darkblue"));
painter->drawText(widget->rect(), Qt::AlignLeft | Qt::AlignVCenter, widget->metaObject()->className());
}
};
};
Then, in your main window constructor call
qApp->setStyle(new DiagnosticStyle());

Related

Change highlight color of text inside a QGraphicsTextItem

I would like to change the highlight color of the selected text inside a QGraphicsTextItem.
I have the paint method subclassed, so I thought it can be as simple as setting a different palette on the QStyleOptionGraphicsItem - but I can't see any examples, and what I try is not working:
void TextItem::paint(QPainter* painter,
const QStyleOptionGraphicsItem* option,
QWidget* widget)
{
QStyleOptionGraphicsItem opt(*option);
opt.palette.setColor(QPalette::HighlightedText, Qt::green);
QGraphicsTextItem::paint(painter, &opt, widget);
}
This has no effect....
How can I change the highlight color of the selected text inside an item ?
The default implementation of QGraphicsTextItem::paint() doesn't care about QStyleOptionGraphicsItem::palette. You have to implement a custom painting if you want different color.
This is a simplified way how to do it:
class CMyTextItem : public QGraphicsTextItem
{
public:
virtual void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) override
{
QAbstractTextDocumentLayout::PaintContext ctx;
if (option->state & QStyle::State_HasFocus)
ctx.cursorPosition = textCursor().position();
if (textCursor().hasSelection())
{
QAbstractTextDocumentLayout::Selection selection;
selection.cursor = textCursor();
// Set the color.
QPalette::ColorGroup cg = option->state & QStyle::State_HasFocus ? QPalette::Active : QPalette::Inactive;
selection.format.setBackground(option->state & QStyle::State_HasFocus ? Qt::cyan : ctx.palette.brush(cg, QPalette::Highlight));
selection.format.setForeground(option->state & QStyle::State_HasFocus ? Qt::blue : ctx.palette.brush(cg, QPalette::HighlightedText));
ctx.selections.append(selection);
}
ctx.clip = option->exposedRect;
document()->documentLayout()->draw(painter, ctx);
if (option->state & (QStyle::State_Selected | QStyle::State_HasFocus))
highlightSelected(this, painter, option);
}
};
However, this solution is not perfect. Not-blinking text cursor is one imperfection. There are probably others. But I believe that improving it a little will be not that big deal for you.

Painting QPixmap in the center of QTableView cell

I have a QTableView that works very well, the first column holds some thumbnails, in each cell of this column the thumbnails are vertically centered, but not horizontally centered.
Do I really need to use a delegate?
If yes, How to center them horizontally using QStyledItemDelegate?
Construct your own delegate and inherit QStyledItemDelegate. Override the paint method.
Then do something like this:
void
MyDelegate::paint(QPainter* painter, const QStyleOptionViewItem& option,
const QModelIndex& index) const
{
QPixmap pixmap;
pixmap.load("Your pixmap file path");
pixmap = pixmap.scaled(option.rect.width(), option.rect.height(), Qt::KeepAspectRatio);
// Position our pixmap
const int x = option.rect.center().x() - pixmap.rect().width() / 2;
const int y = option.rect.center().y() - pixmap.rect().height() / 2;
if (option.state & QStyle::State_Selected) {
painter->fillRect(option.rect, option.palette.highlight());
}
painter->drawPixmap(QRect(x, y, pixmap.rect().width(), pixmap.rect().height()), pixmap);
}
Drawing by yourself is not necessary, but a custom delegate - is. The styled item delegate uses the style's control element drawing code to draw a CE_ItemViewItem - see the source code for Qt 5.5.0. The drawing code takes the style option's decorationAlignment member into account. Unfortunately, there's no data role that would pass that alignment to the styles's implementation. Instead, you have to override the alignment in your delegate:
class DecorationAligningDelegate : public QStyledItemDelegate {
Q_OBJECT
Qt::Alignment const m_alignment;
public:
explicit DecorationAligningDelegate(Qt::Alignment alignment, QObject * parent = 0) :
QStyledItemDelegate(parent), m_alignment(alignment) {}
Qt::Alignment alignment() const { return m_alignment; }
void paint(QPainter * painter, const QStyleOptionViewItem & option, const QModelIndex & index) const {
auto opt = option;
opt.decorationAlignment = m_alignment;
QStyledItemDelegate::paint(painter, opt, index);
}
};
Then, to center the thumbnails:
view.setItemDelegateForColumn(0,
new DecorationAligningDelegate(Qt::AlignHCenter, &view));
//or
view->setItemDelegateForColumn(0,
new DecorationAligningDelegate(Qt::AlignHCenter, view));
If you really wished to paint it all yourself, even though it's unnecessary, the rectangle of the item to be painted is given in the style option (option.rect). To draw the pixmap centered in the item's rectangle, you could do as follows:
QStyleOption option;
QPixmap pix;
QPainter painter;
...
painter.save();
auto loc = option.rect.center() - pix.rect().center()
painter.drawPixmap(loc, pix);
painter.restore();
I will just leave my version that literary is a combination of the two answers.
class DecorationAligningDelegate : public QStyledItemDelegate
{
Q_OBJECT
public:
explicit DecorationAligningDelegate(Qt::Alignment alignment, QObject *parent = nullptr)
: QStyledItemDelegate(parent), m_alignment(alignment) {}
Qt::Alignment alignment() const { return m_alignment; }
void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const
{
QIcon icon = QIcon(qvariant_cast<QIcon>(index.data(Qt::DecorationRole)));
if (option.state & QStyle::State_Selected)
{
painter->fillRect(option.rect, option.palette.highlight());
}
icon.paint(painter, option.rect, m_alignment);
}
private:
Q_DISABLE_COPY(VDecorationAligningDelegate)
Qt::Alignment const m_alignment;
};
I assume you define your item like this:
auto *item = new QTableWidgetItem();
item->setIcon(QIcon("Your pixmap file path"));
Don't forget about setting a delegate.

How to animate the outline of a QGraphicsItem in real time?

I am designing a timer with Qt. With QGraphicsEllipseItem, I drew a circle and now I need to animate the QPen around this circle (change color) every second. I found QGraphicsPathItem, but I need some examples on how to move forward. Can anyone show me an example?
You have two problems:
QGraphicsEllipseItem is not a QObject so QPropertyAnimation can't be used directly on this item
QGraphicsItemAnimation doesn't cover property you want to animate.
What you can do?
IMO best approach is to provide some custom QObject on which you could do this animation. You can inherit QObject or use fake QGraphicsObject (which is a QObject).
class ShapeItemPenAnimator : public QGraphicsObject {
Q_OBJECT
private:
QAbstractGraphicsShapeItem *mParent;
QPropertyAnimation *mAnimation;
public:
QPROPERTY(QColor penColor
READ penColor
WRITE setPenColor)
explicit ShapeItemPenAnimator(QAbstractGraphicsShapeItem * parent)
: QGraphicsObject(parent)
, mParent(parent) {
setFlags(QGraphicsItem::ItemHasNoContents);
mAnimation = new QPropertyAnimation(this, "penColor", this);
}
QColor penColor() const {
return mParent->pen().color();
}
public slots:
void setPenColor(const QColor &color) {
QPen pen(mParent->pen());
pen.setColor(color);
mParent->setPen(pen);
}
public:
void paint(QPainter * painter, const QStyleOptionGraphicsItem * option, QWidget * widget = 0) {
}
QRectF boundingRect() const {
return QRectF();
}
QPropertyAnimation *animation() const {
return mAnimation;
}
}
Now you just attach this object to your QGraphicsEllipseItem and set animation you need.
// yourEllipse
ShapeItemPenAnimator *animator = new ShapeItemPenAnimator(yourEllipse);
animator->animation()->setEndValue(....);
animator->animation()->setStartValue(....);
animator->animation()->setDuration(....);
animator->animation()->setEasingCurve(....);
There are several classes helping with animations of QGraphicsItem in Qt. I suggest looking into QGraphicsItemAnimation and QPropertyAnimation. You can use the second one to animate the color of an item. Here is an example of using QPropertyAnimation:
How to make Qt widgets fade in or fade out?

QToolButton with icon + text: How to center both?

I am using multiple QToolButtons in a custom QGridLayout widget. The buttons are set to display icon + text based on an assigned default QAction. The only issue is that the content (icon + text) is always left-aligned.
The content (icon + text, marked as a red box in the screenshot), should be center in the button (indicated by the blue box).
For most cases this is just fine, given that Qt automatically tries to render that button with the minimal size. However I am stretching the button to fit nicely into my QGridLayout.
QToolButton* pButton = new QToolButton(0);
pButton->addDefaultAction(pAction);
pButton->setToolButtonStyle(Qt::ToolButtonTextBesideIcon);
pButton->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred);
QGridLayout *pActionAreaLayout = new QGridLayout;
pActionAreaLayout->addWidget(pSomeOtherWidget, 0, 0, 1, 2);
pActionAreaLayout->addWidget(pButton , 1, 0, 1, 1);
Is there a way to force the content to be centered in the button?
PS: I found the following comment in another forum, which however seems quite invasive and is not really clear to me yet:
You can try doing the horizontal alignment using a stylesheet, but you probably have to implement a QStyle proxy and reimplement drawControl() for QStyle::CE_ToolButtonLabel
Or derive from QToolButton, overwrite paintEvent() and call the style for everything other than the label.
As I suggest in answer to you another question. https://stackoverflow.com/a/28630318/1917249 Do not use QToolButton, just use QPushButton, and add popup menu if needed.
Then you wont have different sizes of QToolButton and QPushButton widgets. And you will have centered icon and text.
Popupmenu can be easily added to QPushButton ( only small arrow wont be shown )
QPushButton *pushButton = new QPushButton(toolAction->icon(), "PushButton", window);
// window - widget where button is placed ( to get correct QMenu position )
QObject::connect(pushButton, &QPushButton::released, [window, pushButton, action](){
QMenu menu;
menu.addAction(action);
QPoint pos = window->mapToGlobal(pushButton3->pos());
pos += QPoint(0, pushButton->height());
menu.exec(pos);
});
Or you can subclass QPushButton and add Popup menu handling there. Much better then try to center text with icon in QToolButton or have in same size of QPushButton and QToolButton
For complex example please see my answer: https://stackoverflow.com/a/28630318/1917249
The following class does the job for me:
class CenteredToolButtonStyle : public QProxyStyle
{
Q_OBJECT
public:
CenteredToolButtonStyle(QToolButton* b, const QSize& sIcon);
virtual void drawItemPixmap(QPainter *painter, const QRect &rect, int, const QPixmap &pixmap) const
override { m_pic = pixmap; m_ny = rect.y(); Draw(painter); }
virtual void drawItemText(QPainter *painter, const QRect &rect, int flags, const QPalette &pal, bool enabled,
const QString &text, QPalette::ColorRole textRole = QPalette::NoRole) const override;
void Draw(QPainter *painter) const;
const QToolButton* B;
const QSize SICON;
mutable QString m_s;
mutable QPixmap m_pic;
mutable QRect m_r;
mutable int m_nf, m_ny;
mutable bool m_bEnabled;
mutable QPalette m_pal;
mutable QPalette::ColorRole m_textRole;
};
CenteredToolButtonStyle::CenteredToolButtonStyle(QToolButton* b, const QSize& sIcon)
: QProxyStyle(), B(b), SICON(sIcon), m_nf(0), m_bEnabled(true), m_ny(0)
{
b->setToolButtonStyle(Qt::ToolButtonTextBesideIcon);
setParent(b);
}
void CenteredToolButtonStyle::drawItemText(QPainter *painter, const QRect &rect, int flags, const QPalette &pal,
bool enabled, const QString &text, QPalette::ColorRole textRole/* = QPalette::NoRole*/) const
{
m_s = text;
m_r = rect;
m_nf = flags | Qt::AlignCenter;
m_bEnabled = enabled;
m_pal = pal;
m_textRole = textRole;
Draw(painter);
}
void CenteredToolButtonStyle::Draw(QPainter *painter) const
{
if (m_ny) {
if (m_r.y() != m_ny) return;
auto r = m_r;
r.adjust(-SICON.width() - 8, m_ny = 0, -itemTextRect(B->fontMetrics(), m_r, m_nf, m_bEnabled, m_s).width(), 0);
QProxyStyle::drawItemPixmap(painter, r, Qt::AlignCenter, m_pic);
}
QProxyStyle::drawItemText(painter, m_r, m_nf, m_pal, m_bEnabled, m_s, m_textRole);
}
Sample use:
foreach(auto b, ui.mainToolBar->findChildren<QToolButton*>())
b->setStyle(new CenteredToolButtonStyle(b, ui.mainToolBar->iconSize()));

QTreeView draw drop indicator

I need to implement rows moving via drag-n-drop in QTreeView and show the drop indicator between rows. I am wondering if there is a way to override indicator drawing, so it is displayed for all levels of hierarchy between rows only (not the rectangle around the item), the line must be as wide as the entire row (not as the one column).
It is possible by modyfing style used to draw widget. My attempt seemed to work well, but it's a bit of cheating the qt's style system, so i cannot guarante that it will work under all possible styles on all platforms. So here it is:
class myViewStyle: public QProxyStyle{
public:
myViewStyle(QStyle* style = 0);
void drawPrimitive ( PrimitiveElement element, const QStyleOption * option, QPainter * painter, const QWidget * widget = 0 ) const;
};
myViewStyle::myViewStyle(QStyle* style)
:QProxyStyle(style)
{}
void myViewStyle::drawPrimitive ( PrimitiveElement element, const QStyleOption * option, QPainter * painter, const QWidget * widget) const{
if (element == QStyle::PE_IndicatorItemViewItemDrop && !option->rect.isNull()){
QStyleOption opt(*option);
opt.rect.setLeft(0);
if (widget) opt.rect.setRight(widget->width());
QProxyStyle::drawPrimitive(element, &opt, painter, widget);
return;
}
QProxyStyle::drawPrimitive(element, option, painter, widget);
}
myView::myView(QWidget *parent) :
QTreeView(parent)
{
setStyle(new myViewStyle(style()));
}

Resources