Painting custom QWidget based on style sheet pseudo-state value - qt

I have a custom QWidget (actually, derived from QAbstractButton) for which I have to implement my own paintEvent. How to I use the style sheet information?
For example, suppose someone defines the following stylesheet that applies (directly or via inheritance) to my custom class:
QAbstractButton { font-weight: bold; background-color: red }
QAbstractButton:checked { background-color: blue }
In my paintEvent method, how do I get the correct background color to show up for the checked state?
void MyButton::paintEvent(QPaintEvent */*event*/) {
ensurePolished(); // Don't think this is necessary...
qDebug() << Q_FUNC_INFO << isChecked(); // This is showing the right value
QStylePainter painter(this);
painter.fillRect(rect(), painter.background()); // always red, even if checked
}
I assume I have to something like:
if (isChecked()) {
// painter.fillRect(rect(), ???);
//
// style()->drawPrimitive(???, ...);
//
// QStyleOptionButton opt;
// opt.initFrom(this);
// QBrush bg_brush = opt.???
// painter.fillRect(rect(), bg_brush);
//
// ???
} else {
painter.fillRect(rect(), painter.background());
}
How do I get the brush to use for the checked-state background that Qt resolved from the style sheets?

I never could find out how to get the resolved color (and padding) information, but was able to work around it by painting sub-elements of other widgets into mine. This isn't exactly what I was trying to do, and may not work in other cases (if your widget can't be composed by mashing together stuff that Qt does know how to draw).
void MyButton::paintEvent(QPaintEvent */*event*/) {
QStylePainter painter(this);
QStyleOptionButton opt;
opt.initFrom(this);
opt.state |= isChecked() ? QStyle::State_On : QStyle::State_Off;
opt.text = text();
painter.drawPrimitive(QStyle::PE_Widget, opt);
QStyleOptionButton label_opt = opt;
label_opt.rect =
style()->subElementRect(QStyle::SE_CheckBoxContents, &opt, this);
painter.drawControl(QStyle::CE_CheckBoxLabel, label_opt);
// ... etc.
}
I still think there has to be a better way.

Related

QRubberBand and MPlayer in QLabel

I have already tried many solution that are provided on stackoverflow to make it transparent.
I want to make QRubberBand transparent and i am also facing problem regarding that green color which is due to mplayer.
#include "physician.h"
#include "ui_physician.h"
Physician::Physician(QWidget *parent) :
QMainWindow(parent),
ui(new Ui::Physician)
{
ui->setupUi(this);
ui->sendROIButton->setStyleSheet(
"background-color: #d9d9d9;"
"border-radius: 10px;"
"color: Black; "
"font-size: 15px;"
);
}
Physician::~Physician()
{
delete ui;
}
void Physician::mouseMoveEvent(QMouseEvent *e)
{
rubberBand->hide();
bottomRight = e->pos();
QRect rect = QRect(topLeft, bottomRight).normalized();
rubberBand->setGeometry(rect);//Area Bounding
QToolTip::showText(e->globalPos(), QString("%1,%2")
.arg(rubberBand->size().width())
.arg(rubberBand->size().height()), this);
}
void Physician::mousePressEvent(QMouseEvent *e)
{
wWidth=ui->videoShowLabel->width();
wHeight = ui->videoShowLabel->height();
rubberBand->setGeometry(QRect(0, 0, 0, 0).normalized());
rubberBand->hide();
topLeft = e->pos();
}
void Physician::mouseReleaseEvent(QMouseEvent *e){
rubberBand->show();
}
void Physician::on_manualROIRadioButton_clicked()
{
rubberBand = new QRubberBand(QRubberBand::Rectangle, this);
}
void Physician::on_autoROIRadioButton_clicked()
{
QString winNuber= QString::number((int)(ui->videoShowLabel->winId()));
QStringList argsList ;
argsList << "-slave" << "-quiet" << "-wid" << winNuber << "zoom" << "-
vo" << "gl" << "C:/../../../Physician21/PhotoshopQML.mkv";
mplayer_proc = new QProcess;
mplayer_proc-
>start("C:/../../../PhysicianTest/mplayer/mplayer.exe",argsList);
}
Firstly, regarding "QRubberBand should work only on that QLabel". You need to make the QLabel the parent of the QRubberBand to achieve that.
Secondly, regarding transparency I assume you mean that the output from mplayer should be visible through the rectangle painted by the QRubberBand? I'm not sure you will be able to do that. Doing so would required the rubber band painting logic to act as a compositor but in order to do that it needs to know both the source and destination(mplayer) images. Since mplayer draws directly on the underlying native window Qt has know knowledge of the current destination image and so can not merge the source image with it. I think your best bet would be to find/generate a style that causes QRubberBand to draw the rectangle outline only -- not sure if that's possible. You could subclass it and do your own painting with something like...
class rubber_band: public QRubberBand {
using super = QRubberBand;
public:
template<typename... Types>
explicit rubber_band (const Types &... args)
: super(args...)
{}
protected:
virtual void paintEvent (QPaintEvent *event) override
{
QPainter painter(this);
painter.setPen(Qt::red);
painter.setBrush(Qt::NoBrush);
painter.drawRect(rect().adjusted(0, 0, -1, -1));
}
};
The above could still leave visual artifacts on the widget though.

Qt stylesheet : set a specific QMenuBar::item background color

I have a QMenuBar with for example two QMenu items.
How can I only make the "Floors" item be blue, for example? I know how to change it for ALL the items with:
QMenuBar::item {
background: ...;
}
But I can't find a way to color a specific item. I tried to use setProperty on Qmenu, I tried with setPalette,... I just find nothing working. Is there a way to set a specific QMenuBar::item property in C++ code?
I finally found something.
Create your own object, for example WidgetMenuBar, inherited from QMenuBar.
Add a property to identify wich item should be colored differently:
for (int i = 0; i < this->actions().size(); i++){
actions().at(i)->setProperty("selection",false);
}
// Only the first item colored
actions().at(0)->setProperty("selection",true);
Reimplement void paintEvent(QPaintEvent *e) function of your widget:
void WidgetMenuBarMapEditor::paintEvent(QPaintEvent *e){
QPainter p(this);
QRegion emptyArea(rect());
// Draw the items
for (int i = 0; i < actions().size(); ++i) {
QAction *action = actions().at(i);
QRect adjustedActionRect = this->actionGeometry(action);
// Fill by the magic color the selected item
if (action->property("selection") == true)
p.fillRect(adjustedActionRect, QColor(255,0,0));
// Draw all the other stuff (text, special background..)
if (adjustedActionRect.isEmpty() || !action->isVisible())
continue;
if(!e->rect().intersects(adjustedActionRect))
continue;
emptyArea -= adjustedActionRect;
QStyleOptionMenuItem opt;
initStyleOption(&opt, action);
opt.rect = adjustedActionRect;
style()->drawControl(QStyle::CE_MenuBarItem, &opt, &p, this);
}
}
You can see here how to implement paintEvent function.

How to draw a Square between the text and the checkbox in QCheckBox

I need to customize the QCheckbox like this: add a square between the checkbox and the text :
So to do this, I inherit QCheckBox and override paintEvent(QPaintEvent*):
void customCheckBox::paintEvent(QPaintEvent*){
QPainter p(this);
QStyleOptionButton opt;
initStyleOption(&opt);
style()->drawControl(QStyle::CE_CheckBox,&opt,&p,this);
QFontMetrics font= this->fontMetrics();// try to get pos by bounding rect of text, but it ain't work :p
QRectF rec = font.boundingRect(this->text());
p.fillRect(rect,QBrush(QColor(125,125,125,128)));
p.drawRect(rect);
}
I have a problem that I don't know how the position to place the QRectF rec. And I can't set the size too small, eg: it will disappear when size of rec < QSize(10,10).
Any ideas are appreciated.
PS: I use OpenSUSE 13.1 with Qt 4.8.5
The main idea is to copy default implementation of checkbox drawing and modify it according to your needs. We get label rectangle in default implementation, so we just need to draw new element in this place and shift label to the right. Also we need to adjust size hint so that both new element and default content fit in minimal size.
class CustomCheckBox : public QCheckBox {
Q_OBJECT
public:
CustomCheckBox(QWidget* parent = 0) : QCheckBox(parent) {
m_decoratorSize = QSize(16, 16);
m_decoratorMargin = 2;
}
QSize minimumSizeHint() const {
QSize result = QCheckBox::minimumSizeHint();
result.setWidth(result.width() + m_decoratorSize.width() + m_decoratorMargin * 2);
return result;
}
protected:
void paintEvent(QPaintEvent*) {
QPainter p(this);
QStyleOptionButton opt;
initStyleOption(&opt);
QStyleOptionButton subopt = opt;
subopt.rect = style()->subElementRect(QStyle::SE_CheckBoxIndicator, &opt, this);
style()->drawPrimitive(QStyle::PE_IndicatorCheckBox, &subopt, &p, this);
subopt.rect = style()->subElementRect(QStyle::SE_CheckBoxContents, &opt, this);
p.fillRect(QRect(subopt.rect.topLeft() + QPoint(m_decoratorMargin, 0),
m_decoratorSize), QBrush(Qt::green));
subopt.rect.translate(m_decoratorSize.width() + m_decoratorMargin * 2, 0);
style()->drawControl(QStyle::CE_CheckBoxLabel, &subopt, &p, this);
if (opt.state & QStyle::State_HasFocus) {
QStyleOptionFocusRect fropt;
fropt.rect = style()->subElementRect(QStyle::SE_CheckBoxFocusRect, &opt, this);
fropt.rect.setRight(fropt.rect.right() +
m_decoratorSize.width() + m_decoratorMargin * 2);
style()->drawPrimitive(QStyle::PE_FrameFocusRect, &fropt, &p, this);
}
}
private:
QSize m_decoratorSize;
int m_decoratorMargin;
};
Note that this solution may be not portable because checkboxes are drawn with drastic differences on different platforms. I tested it on Windows only. I used default implementation provided by QCommonStyle.
QAbstractButton has a property called icon, which is drawn differently depending on what subclass is instantiated.
Luckily for you, the position of the icon in a QCheckBox is exactly in the position you illustrated in your picture.
So, all you need to do for your customised QCheckBox is define what the icon should be and the default paintEvent will take care of the rest.
For simplicity's sake, I'm assuming the icon size should be the same size as the check box itself:
class CheckBox : public QCheckBox {
public:
CheckBox() {
QStyleOptionButton option;
initStyleOption(&option);
QSize indicatorSize = style()->proxy()->subElementRect(QStyle::SE_CheckBoxIndicator, &option, this).size();
QPixmap pixmap(indicatorSize);
pixmap.fill(Qt::green);
QIcon icon;
icon.addPixmap(pixmap);
setIcon(icon);
}
};
I have tested this on a Windows 8.1 machine with Qt 5.2.1 and it works.

Adding a QSizeGrip to the corner of a QLabel

I'm attempting to produce a widget that consists of a text display that can be resized by the user grabbing the lower right corner. So far I've been able to generate this:
I've applied a red background to the layout to make it more obvious what's going on. I've used the following code to generate this:
m_sizeGrip = new QSizeGrip( this );
m_layout = new QHBoxLayout( this );
m_label = new QLabel( this );
m_layout->setContentsMargins( QMargins() );
m_layout->setSpacing( 0 );
m_layout->addWidget( m_label );
m_layout->addWidget( m_sizeGrip, 0, Qt::AlignBottom | Qt::AlignRight );
setWindowFlags( Qt::SubWindow );
Basically, it's a horizontal layout with the label and grip added to it, which is then installed on a QWidget. My problem is that I'd like the grip to be on the lower right corner of the label, rather than the parent widget. I'd also like to make it invisible while keeping it enabled.
Or perhaps I'm going about this the wrong way. My ultimate goal is to have a textual display widget that can be resized by the user either horizontally or vertically, but doesn't have a visible grip that would obscure the text. Am I already on the right track with the code above, or is there a better way to achieve this?
You could create a custom QLabel for that. The idea would be to track mouse move events (which by default only fire when a mouse button is pressed), and resize based on how much the mouse traveled since the last event.
This allows you to control exactly how to display the "gripper" (if at all) and what shape it should have. You can constrain the resizing to vertical or horizontal (or not).
Here's a demo of how you could do that (resizes both ways). Warning: this might wreak havoc in your layout.
#include <QtGui>
class GripLabel: public QLabel
{
Q_OBJECT
public:
GripLabel(QString const& title, QWidget* parent = 0)
: QLabel(title, parent),
resizing(false),
gripSize(10, 10)
{
// Prevent the widget from disappearing altogether
// Bare minimum would be gripSize
setMinimumSize(100, 30);
}
QSize sizeHint() const
{
return minimumSize();
}
protected:
bool mouseInGrip(QPoint mousePos)
{
// "handle" is in the lower right hand corner
return ((mousePos.x() > (width() - gripSize.width()))
&& (mousePos.y() > (height() - gripSize.height())));
}
void mousePressEvent(QMouseEvent *e)
{
// Check if we hit the grip handle
if (mouseInGrip(e->pos())) {
oldPos = e->pos();
resizing = true;
} else {
resizing = false;
}
}
void mouseMoveEvent(QMouseEvent *e)
{
if (resizing) {
// adapt the widget size based on mouse movement
QPoint delta = e->pos() - oldPos;
oldPos = e->pos();
setMinimumSize(width()+delta.x(), height()+delta.y());
updateGeometry();
}
}
void paintEvent(QPaintEvent *e)
{
QLabel::paintEvent(e);
QPainter p(this);
p.setPen(Qt::red);
p.drawRect(width()-gripSize.width(), height()-gripSize.height(),
gripSize.width(), gripSize.height());
}
private:
bool resizing;
QSize gripSize;
QPoint oldPos;
};
Sample main:
#include "griplabel.h"
int main(int argc, char **argv)
{
QApplication app(argc, argv);
QWidget *w = new QWidget;
QPushButton *b = new QPushButton("button");
GripLabel *l = new GripLabel("Hello");
QHBoxLayout *y = new QHBoxLayout;
y->addWidget(b);
y->addWidget(l);
y->setSizeConstraint(QLayout::SetFixedSize);
w->setLayout(y);
w->show();
return app.exec();
}

Detect if a QPushButton is being clicked

I'm trying to find out whether a button is being pressed or not from within the paintEvent(), so that I can draw the "down" state. However, I don't know where to find this information. I tried QStyleOptionButton::state but it doesn't tell whether the button is being clicked or not.
The output of the debug statement is always something like "QStyle::State( "Active | Enabled | HasFocus | MouseOver" )" so nothing about a MouseDown state.
void XQPushButton::mousePressEvent(QMouseEvent* event) {
QPushButton::mousePressEvent(event);
QStyleOptionButton options;
options.initFrom(this);
qDebug() << (options.state);
}
void XQPushButton::paintEvent(QPaintEvent* event) {
QPushButton::paintEvent(event);
QStyleOptionButton options;
options.initFrom(this);
qDebug() << (options.state);
}
So any idea how I can detect if the button is being clicked?
QPushButton inherits QAbstractButton, which provides the down property:
This property holds whether the button is pressed down.
The documentation of the QStyleOption parent class contains an example that uses this property:
void MyPushButton::paintEvent(QPaintEvent *)
{
QStyleOptionButton option;
option.initFrom(this);
option.state = isDown() ? QStyle::State_Sunken : QStyle::State_Raised;
//...
}
In other words, the sunken/raised state is not initialized by initFrom(). This makes some sense, since initFrom is inherited from QStyleOption and takes a QWidget:
void initFrom ( const QWidget * widget )
– and a generic QWidget has no notion of "raised" or "sunken".
At least this is how I read the docs.

Resources