QMenu Right Align Text - qt

I need the text in my QMenu to be right aligned instead of left aligned.
QMenu menu;
menu.setStyleSheet("QMenu{background: #2e353d; color: #bfbfbf; margin: 0px;}"
"QMenu::item{font-size: 10pt; padding: " + QString::number(6*globalDPI) + "px " + QString::number(30*globalDPI) + "px " + QString::number(6*globalDPI) + "px " + QString::number(20*globalDPI) + "px; border: 0px solid transparent; margin: 0px;}"
"QMenu::item:selected{background: #373f49; color: #fff;}");
menu.addAction("Debug Log");
menu.addAction("Website");
menu.addAction("Changelog");
menu.addAction("About");
menu.exec(myButton->mapToGlobal(QPoint(0,32*globalDPI)));
I've tried adding text-align: right to both the QMenu and QMenu::item in the stylesheet. I've tried setting the layout direction to right to left with setLayoutDirection. I've tried adding subcontrol-position: right; to the stylesheet. Nothing works, the text in the menu remains left aligned.
Any ideas?

You will not be able to change the alignment of the QMenu text as it is codified when drawing the QMenu using the QStyle. A workaround is to use a QProxyStyle by doing the painting by passing an empty text in the QStyleOptionMenuItem and then painting the text (the text painting I show is limited since I am not taking all cases such as a QMenu with multiple lines and other options sophisticated).
#include <QtWidgets>
class MenuProxyStyle: public QProxyStyle{
public:
using QProxyStyle::QProxyStyle;
void drawControl(ControlElement element, const QStyleOption *opt, QPainter *p, const QWidget *w) const override
{
if(element == QStyle::CE_MenuItem){
if(const QStyleOptionMenuItem *o = qstyleoption_cast<const QStyleOptionMenuItem *>(opt)){
QStyleOptionMenuItem menuitem = *o;
QString text = o->text;
menuitem.text = "";
QProxyStyle::drawControl(element, &menuitem, p, w);
if(o->text.isEmpty())
return;
int margin = 4;
int text_flags = Qt::AlignVCenter | Qt::AlignRight | Qt::TextDontClip | Qt::TextSingleLine;
p->drawText(menuitem.rect.adjusted(margin, margin, -margin, -margin), text_flags, text);
return;
}
}
QProxyStyle::drawControl(element, opt, p, w);
}
};
class Widget: public QWidget{
public:
Widget(QWidget *parent=nullptr): QWidget(parent){
setContextMenuPolicy(Qt::CustomContextMenu);
connect(this, &QWidget::customContextMenuRequested, this, &Widget::on_customContextMenuRequested);
}
private:
void on_customContextMenuRequested(const QPoint &pos){
QMenu menu;
menu.setStyle(new MenuProxyStyle(menu.style()));
menu.addAction("Debug Log");
menu.addAction("Website");
menu.addAction("Changelog");
menu.addAction("About");
menu.exec(mapToGlobal(pos));
}
};
int main(int argc, char *argv[]) {
QApplication app(argc, argv);
Widget w;
w.resize(640, 480);
w.show();
return app.exec();
}

Related

Get the position of a QWidget that is inside a QGraphicsItem

I have a QWidget with a QPushButton, at the same time, this QWidget is embedded into a QGraphicsItem, which is inside a QGraphicsScene.
I need to draw a line between two of those QGraphicsItems pointing to the QPushButton. For that, I need to get the position of the QPushButton. It looks like this:
I tried getting the position of the QPushButton inside the constructor of the QGraphicsItem, but it returns 0,0. I guess this is the position of the button inside the QWidget. I guess what I need is a way to get the position on the screen.
Minimal Example: Simplified as much as possible.
QWidget:
NodeFrame::NodeFrame()
{
setFixedSize(200,80);
setStyleSheet("QFrame { background-color: #2e4076; }");
// Creates and add a QPushButton to the frame.
// I need the position of this button on the QGraohicsScene
auto button = new QPushButton("B");
button->setFixedSize(40,20);
auto layout = new QHBoxLayout();
layout->addWidget(button);
setLayout(layout);
}
QGraphicsItem:
class Node : public QGraphicsItem
{
public:
Node();
QRectF boundingRect() const override;
void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) override;
};
Node::Node()
{
setFlag(ItemIsMovable);
// Create a GraphicsProxyWidget to insert the nodeFrame into the scene
auto proxyWidget = new QGraphicsProxyWidget(this);
auto frame = new NodeFrame();
proxyWidget->setWidget(frame);
// Center the widget(frame) at the center of the QGraphicsItem
proxyWidget->setPos(boundingRect().center() - proxyWidget->boundingRect().center());
}
QRectF Node::boundingRect() const
{
return QRectF(-10, -10, 280, 150);
}
void Node::paint(QPainter* painter, const QStyleOptionGraphicsItem* option, QWidget* widget)
{
QPainterPath path;
path.addRoundedRect(boundingRect(), 10, 10);
painter->drawPath(path);
}
Main:
int main(int argc, char* argv[])
{
QApplication app(argc, argv);
// Create scene and view
auto scene = new QGraphicsScene();
auto view = new QGraphicsView(scene);
view->setMinimumSize(800, 800);
// Create the QGraphicsItem and add it to the scene
auto item = new Node();
scene->addItem(item);
item->setPos(-50, -50);
// Show the the view
view->show();
return app.exec();
}
In nodeframe.cpp I add one function getButtonRect() :
#ifndef NODEFRAME_H
#define NODEFRAME_H
#include <QWidget>
#include <QPushButton>
#include <QRect>
class NodeFrame: public QWidget
{
public:
NodeFrame();
QRect getButtonRect();
private:
QPushButton *button;
QHBoxLayout *layout;
};
#endif // NODEFRAME_H
nodeframe.cpp
#include "nodeframe.h"
NodeFrame::NodeFrame()
{
setFixedSize(200, 80);
setStyleSheet("QFrame { background-color: #2e4076; }");
// Creates and add a QPushButton to the frame.
// I need the position of this button on the QGraohicsScene
button = new QPushButton("B");
button->setFixedSize(40, 20);
layout = new QHBoxLayout();
layout->addWidget(button);
setLayout(layout);
}
QRect NodeFrame::getButtonRect()
{
return layout->itemAt(0)->geometry();
}
and in Node pass this function to main.cpp because QGraphicsView is there:
node.cpp:
#include "node.h"
#include <QGraphicsProxyWidget>
#include <QPainter>
Node::Node()
{
setFlag(ItemIsMovable);
// Create a GraphicsProxyWidget to insert the nodeFrame into the scene
auto proxyWidget = new QGraphicsProxyWidget(this);
frame = new NodeFrame();
proxyWidget->setWidget(frame);
// Center the widget(frame) at the center of the QGraphicsItem
proxyWidget->setPos(boundingRect().center() - proxyWidget->boundingRect().center());
}
QRectF Node::boundingRect() const
{
return QRectF(-10, -10, 280, 150);
}
void Node::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget)
{
QPainterPath path;
path.addRoundedRect(boundingRect(), 10, 10);
painter->drawPath(path);
}
QRect Node::getButtonRect()
{
return frame->getButtonRect();
}
main.cpp
#include "node.h"
#include <QApplication>
#include <QGraphicsScene>
#include <QGraphicsView>
int main(int argc, char *argv[])
{
QApplication app(argc, argv);
// Create scene and view
auto scene = new QGraphicsScene();
auto view = new QGraphicsView(scene);
view->setMinimumSize(800, 800);
// Create the QGraphicsItem and add it to the scene
auto item = new Node();
scene->addItem(item);
item->setPos(0, 0);
// qDebug() << "RECT bottomLeft= " << view->mapToScene(item->getButtonRect().bottomLeft());
// qDebug() << "RECT bottomRight= " << view->mapToScene(item->getButtonRect().bottomRight());
// qDebug() << "RECT topLeft= " << view->mapToScene(item->getButtonRect().topLeft());
// qDebug() << "RECT topRight= " << view->mapToScene(item->getButtonRect().topRight());
auto btnRect = item->getButtonRect();
auto ellipse = new QGraphicsEllipseItem(QRect(view->mapToGlobal(btnRect.center()).x(), view->mapToGlobal(btnRect.center()).y(), 40, 40));
qDebug() << "Center" << view->mapToGlobal(btnRect.center());
scene->addItem(ellipse);
// Show the the view
view->show();
return app.exec();
}

Drawing extra item in QGraphicsItem during hover event

I would like to create coordinate points on a QGraphicsView. When the mouse hovers over the point, the coordinate will be shown.
I draw the coordinates by QGraphicsEllipseItem. In order to enable the hover event, I need to re-implement the QGraphicsEllipseItem. However, because the size of the QGraphicsEllipseItem is fixed when it was constructed, the hover text is not shown properly. How can I deal with this?
Here is my code:
The MainWindow:
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
{
view = new QGraphicsView(this);
view->setRenderHint(QPainter::Antialiasing);
scene = new QGraphicsScene(this);
view->fitInView(scene->sceneRect(), Qt::KeepAspectRatio);
view->setScene(scene);
setCentralWidget(view);
for (int y = 0; y < 900; y += 100)
for(int x = 0; x < 1400; x += 100)
drawPoint(x, y);
}
void MainWindow::drawPoint(int x, int y)
{
CoordinatePoint* point = new CoordinatePoint();
point->setRect(QRect(x, y, 3, 3));
point->setPen(QPen(Qt::red, 3, Qt::SolidLine));
point->setBrush(Qt::red);
scene->addItem(point);
}
The re-implement QGraphicsEllipseItem:
CoordinatePoint::CoordinatePoint(QGraphicsItem* parent)
:QGraphicsEllipseItem(parent)
{
setAcceptHoverEvents(true);
}
void CoordinatePoint::hoverEnterEvent(QGraphicsSceneHoverEvent* event)
{
hover = true;
mx = event->pos().x();
my = event->pos().y();
update();
}
void CoordinatePoint::hoverLeaveEvent(QGraphicsSceneHoverEvent* event)
{
hover = false;
update();
}
void CoordinatePoint::paint(QPainter* painter, const QStyleOptionGraphicsItem* option, QWidget* widget)
{
QGraphicsEllipseItem::paint(painter, option, widget);
if (hover)
{
painter->save();
painter->setPen(Qt::black);
painter->setBrush(Qt::black);
painter->drawText(mx + 2, my + 2,
"(" + QString::number(mx) + "," +
QString::number(my) + ")");
painter->restore();
}
}
I think that using a separate child item for the text will make your life a lot easier:
#include <QtWidgets>
class CoordinatePoint : public QGraphicsEllipseItem
{
public:
CoordinatePoint(QGraphicsItem* parent = Q_NULLPTR) :
QGraphicsEllipseItem(parent),
coordinateText(Q_NULLPTR)
{
setAcceptHoverEvents(true);
}
void hoverEnterEvent(QGraphicsSceneHoverEvent*)
{
if (!coordinateText) {
coordinateText = new QGraphicsTextItem(this);
coordinateText->setPlainText("(" + QString::number(x())
+ "," + QString::number(y()) + ")");
coordinateText->setX(2);
coordinateText->setY(2);
}
coordinateText->setVisible(true);
}
void hoverLeaveEvent(QGraphicsSceneHoverEvent*)
{
if (coordinateText) {
coordinateText->setVisible(false);
}
}
private:
QGraphicsTextItem *coordinateText;
};
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
QMainWindow window;
QGraphicsView *view = new QGraphicsView(&window);
view->setRenderHint(QPainter::Antialiasing);
QGraphicsScene *scene = new QGraphicsScene(&window);
view->fitInView(scene->sceneRect(), Qt::KeepAspectRatio);
view->setScene(scene);
window.setCentralWidget(view);
for (int y = 0; y < 900; y += 100) {
for(int x = 0; x < 1400; x += 100) {
CoordinatePoint* point = new CoordinatePoint();
point->setRect(QRect(0, 0, 3, 3));
point->setX(x);
point->setY(y);
point->setPen(QPen(Qt::red, 3, Qt::SolidLine));
point->setBrush(Qt::red);
scene->addItem(point);
}
}
window.show();
return a.exec();
}
If having an extra QGraphicsTextItem for each coordinate worries you, you could construct one of them and just share it amongst them all, reparenting it as each one is hovered. This should work fine, as there can only ever be one coordinate hovered at a time.
If you try to draw the text as part of the ellipse item, you'd have to:
Increase the size of the item so that it's large enough to contain the text, which means overriding boundingRect().
Only draw the ellipse within a certain part of that area.
In hoverEnterEvent(), check that the mouse cursor is within the ellipse's area.
A bunch of other stuff, probably.

Progress bar with differnet text color around the progress front?

How can one obtain the following effect for text in a QProgressBar from Qt? :
The ideea is that I must have a brighter color in the left part of the progress bar.
I would do the custom drawing as follows:
Derive your own progress bar class from QLabel.
Overwrite the paintEvent() function.
In paintEvent(), start drawing with a QPainter:
draw the secondary background rectangle
draw the text in the secondary color with drawText()
draw the first background rectangle (overdrawing first part of text)
draw the text in the first color within a rectangle matching the first background rectangle: http://qt-project.org/doc/qt-4.8/qpainter.html#drawText-11
You should end up with what you want to achieve. Due to Qt's default double buffering you should observe no flicker.
With the help of this,
QModernProgressBar.h
#include <QWidget>
#include <QProgressBar>
#include <QPaintEvent>
#include <QStylePainter>
#include <QStyleOption>
#include <QDebug>
class QModernProgressBar : public QProgressBar
{
Q_OBJECT
public:
explicit QModernProgressBar(QWidget *parent = nullptr);
~QModernProgressBar() Q_DECL_OVERRIDE;
protected:
void paintEvent(QPaintEvent *) Q_DECL_OVERRIDE;
public:
virtual QString text() const Q_DECL_OVERRIDE;
};
QModernProgressBar.cpp
#include "QModernProgressBar.h"
QModernProgressBar::QModernProgressBar(QWidget *parent) : QProgressBar(parent) {}
QModernProgressBar::~QModernProgressBar() {}
/* https://www.qtcentre.org/threads/70885-QProgressBar-with-in-decimal */
QString QModernProgressBar::text() const
{
QString result = format();
if(minimum() ==0 && maximum()==0)
{
if(result=="%p%")
{
return QString("");
}
else
{
return result;
}
}
else if ( minimum() == maximum() )
{
return QString("");
}
if(result.isEmpty())
{
return QString("%p%");
}
else
{
return result;
}
}
void QModernProgressBar::paintEvent(QPaintEvent *ev)
{
Q_UNUSED(ev)
QStylePainter paint(this);
QStyleOptionProgressBar opt;
initStyleOption(&opt);
paint.drawControl(QStyle::CE_ProgressBarGroove, opt);
paint.drawControl(QStyle::CE_ProgressBarContents, opt);
const QStyleOptionProgressBar *option = &opt;
QStylePainter *painter = &paint;
// Stolen from QFusionStyle::drawControl
if (const QStyleOptionProgressBar *pbar = qstyleoption_cast<const QStyleOptionProgressBar *>(option))
{
QRect leftRect;
QRect rect = pbar->rect;
QColor textColor = option->palette.text().color();
QColor alternateTextColor = option->palette.window().color();
painter->save();
bool vertical = false, inverted = false;
vertical = (pbar->orientation == Qt::Vertical);
inverted = pbar->invertedAppearance;
if (vertical)
rect = QRect(rect.left(), rect.top(), rect.height(), rect.width());
const auto totalSteps = qMax(Q_INT64_C(1), qint64(pbar->maximum) - pbar->minimum);
const auto progressSteps = qint64(pbar->progress) - pbar->minimum;
const int progressIndicatorPos = static_cast<int>(progressSteps * rect.width() / totalSteps);
if (progressIndicatorPos >= 0 && progressIndicatorPos <= rect.width())
leftRect = QRect(rect.left(), rect.top(), progressIndicatorPos, rect.height());
if (vertical)
leftRect.translate(rect.width() - progressIndicatorPos, 0);
bool flip = (!vertical && (((pbar->direction == Qt::RightToLeft) && !inverted) ||
((pbar->direction == Qt::LeftToRight) && inverted)));
QString formattedText = pbar->text;
formattedText.replace(QLatin1String("%m"), QString("%1").arg(totalSteps));
const auto progress = static_cast<int>((qint64(value()) - pbar->minimum) * 100.0 / totalSteps);
formattedText.replace(QLatin1String("%p"), QString("%1").arg(progress));
QRegion rightRect = rect;
rightRect = rightRect.subtracted(leftRect);
painter->setClipRegion(rightRect);
painter->setPen(flip ? alternateTextColor : textColor);
painter->drawText(rect, formattedText, QTextOption(Qt::AlignAbsolute |
Qt::AlignHCenter |
Qt::AlignVCenter));
if (!leftRect.isNull())
{
painter->setPen(flip ? textColor : alternateTextColor);
painter->setClipRect(leftRect);
painter->drawText(rect, formattedText, QTextOption(Qt::AlignAbsolute |
Qt::AlignHCenter |
Qt::AlignVCenter));
}
painter->restore();
}
}
main.cpp
#include <QApplication>
#include <QTimer>
#include <QTextStream>
#include <QTimer>
#include <QDebug>
#include <QVBoxLayout>
#include "QModernProgressBar.h"
int main(int argc, char* argv[])
{
QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
QCoreApplication::setApplicationName("QModernProgressbar Demo");
QApplication app(argc, argv);
app.setStyleSheet(QLatin1String(" \
QProgressBar { \
font-family : Fira Code; \
font-size : 12pt; \
font-style : normal; \
font-weight : bold; \
color : rgba(8, 218, 157, 100%); \
border : 1px solid rgba(8, 218, 157, 100%); \
border-radius : 0px; \
text-align : center; \
} \
\
QProgressBar::chunk { \
background-color : rgba(8, 218, 157, 100%); \
width : 1px; \
text-align : center; \
}")); \
QTimer timer;
QWidget mainWindow;
InvertProgressBar w1, w2, w3;
w1.setTextVisible(true);
w2.setTextVisible(true);
w3.setTextVisible(true);
int i=25;
int j=0;
w1.setRange(0,25);
w2.setRange(0,100);
w3.setRange(0,0);
w1.setFormat(QString("Searching... Timing out in %1 seconds.").arg(i));
w2.setFormat(QString("Flashing the controller ..."));
w3.setFormat(QString("Rebooting the controller ..."));
QVBoxLayout *finalLayout = new QVBoxLayout;
QObject::connect(&timer, &QTimer::timeout, [&]() {
w1.setFormat(QString("Searching... Timing out in %1 seconds...").arg(i));
w1.setValue(i);
if(--i==-1)
{
i=25;
}
w2.setFormat(QString("Flashing the controller ...%p%"));
w2.setValue(j);
if(j++==101)
{
j=0;
}
}
);
timer.start(500);
finalLayout->addWidget(&w1);
finalLayout->addWidget(&w2);
finalLayout->addWidget(&w3);
mainWindow.setFixedSize(500,150);
mainWindow.setLayout(finalLayout);
mainWindow.show();
return app.exec();
}
Please note that when the busy indicator is on, the dual tone of text is not effective.

How to indent the title of a QDockWidget using my own style

I want to indent the title of a QDockWidget w/o adding spaces.
#include <QStyle>
#include <QProxyStyle>
#include <iostream>
class my_style : public QProxyStyle
{
Q_OBJECT
public:
my_style (QStyle* style = 0) :
QProxyStyle (style)
{
}
virtual ~my_style ()
{
}
virtual QRect subElementRect (SubElement element, const QStyleOption * option, const QWidget * widget = 0) const
{
QRect rect = QProxyStyle::subElementRect (element, option, widget);
if (element == QStyle::SE_DockWidgetTitleBarText)
{
rect.adjust (50, 0, 0, 0);
}
//std::cerr << "debug:" << element << std::endl;
return rect;
}
};
I have no clue why but when I apply my style it's never running into that if. If I debug the method I only get an output for two different elements which are the buttons in the title bar.
subElementRect is not called to get the title area for all styles. At least, XP, Vista and MacOSX styles are using directly QStyleOption::rect which is passed as parameter to the drawControl function for CE_DockWidgetTitle.
To handle both cases, you should also reimplement drawControl:
void drawControl(ControlElement element, const QStyleOption *option,
QPainter *painter, const QWidget *widget) const
{
const QStyleOptionDockWidget *dockWidget;
if(element == CE_DockWidgetTitle &&
(dockWidget = qstyleoption_cast<const QStyleOptionDockWidget *>(option)))
{
QStyleOptionDockWidget copy = *dockWidget;
copy.rect.adjust(50,0,0,0);
// or you can add spaces in the title to avoid the border moving left too
// copy.title = QString(50 / copy.fontMetrics.width(" "), QChar(' ')) + copy.title;
QProxyStyle::drawControl(element, &copy, painter, widget);
return;
}
QProxyStyle::drawControl(element, option, painter, widget);
}
Alternatively you could use a style sheet, with a padding or a margin:
dockWidget->setStyleSheet("::title { position: relative; padding-left: 50px;"
" text-align: left center }");
The "position" rule does nothing, but is necessary, because strangely the style is only applied if some other rule categories are also present.
The text needs to be vertically realigned too because the alignment seem to be lost when using a style sheet.

How to make a QWidget alpha-transparent

I need to create an alpha transparent widget, it's basically a navigation bar with a shadow and the widgets below need to be partially visible through the shadow. The widget loads a PNG then draws it on the paint event. The problem is that the shadow is all black and is not alpha-transparent.
This is the code I'm currently using:
NavigationBar::NavigationBar(QWidget *parent) : XQWidget(parent) {
backgroundPixmap_ = new QPixmap();
backgroundPixmap_->load(FilePaths::skinFile("NavigationBarBackground.png"), "png");
setAttribute(Qt::WA_NoBackground, true); // This is supposed to remove the background but there's still a (black) background
}
void NavigationBar::paintEvent(QPaintEvent* event) {
QWidget::paintEvent(event);
QPainter painter(this);
int x = 0;
while (x < width()) {
painter.drawPixmap(x, 0, backgroundPixmap_->width(), backgroundPixmap_->height(), *backgroundPixmap_);
x += backgroundPixmap_->width();
}
}
Does anybody know what I need to change to make sure the widget is really transparent?
You're doing too much work :-)
The setAttribute call is not necessary. By default, a widget will not draw anything on its background (assuming Qt >= 4.1). Calling QWidget::paintEvent is also unnecessary - you don't want it to do anything.
Rather than doing the pattern fill yourself, let Qt do it with a QBrush:
NavigationBar::NavigationBar(QWidget *parent) : XQWidget(parent) {
backgroundPixmap_ = new QPixmap();
backgroundPixmap_->load(FilePaths::skinFile("NavigationBarBackground.png"), "png");
// debug check here:
if (!backgroundPixmap_->hasAlphaChannel()) {
// won't work
}
}
void NavigationBar::paintEvent(QPaintEvent* event) {
QPainter painter(this);
painter.fillRect(0, 0, width(), height(), QBrush(*backgroundPixmap));
}
Adjust the height parameter if you don't want the pattern to repeat vertically.
Are you sure your PNG file is actually transparent? The following (which is essentially what you are doing) is working for me. If this fails on your machine, perhaps include what version of Qt you are using, and what platform.
#include <QtGui>
class TransparentWidget : public QWidget {
public:
TransparentWidget()
: QWidget(),
background_pixmap_(":/semi_transparent.png") {
setFixedSize(400, 100);
}
protected:
void paintEvent(QPaintEvent *) {
QPainter painter(this);
int x = 0;
while (x < width()) {
painter.drawPixmap(x, 0, background_pixmap_);
x += background_pixmap_.width();
}
}
private:
QPixmap background_pixmap_;
};
class ParentWidget : public QWidget {
public:
ParentWidget() : QWidget() {
QVBoxLayout *layout = new QVBoxLayout;
layout->addWidget(new TransparentWidget);
layout->addWidget(new QPushButton("Button"));
setLayout(layout);
setBackgroundRole(QPalette::Dark);
setAutoFillBackground(true);
}
};
int main(int argc, char **argv) {
QApplication app(argc, argv);
ParentWidget w;
w.show();
return app.exec();
}

Resources