How to align text (alone) of a QToolButton - c++

I have a QToolButton. I'm using it instead of QPushButton because I need a label-like looking button. QPushButton is too chunky even after setting stylesheet's borders and paddings to None-0px.
I would like this QToolButton to contain a text (no-icon) align right.
However, text-align: right; is not working. .setAlignment(Qt.AlignRight) is also not working.
How can I align the text to right?
Thank you.

You can try to sub-class QStyle and re-implement QStyle::drawControl() to align the text to the right. Check the file qt/src/gui/styles/qcommonstyle.cpp to see how it's done. (Sorry I'm using C++ not Python)
case CE_ToolButtonLabel:
if (const QStyleOptionToolButton *toolbutton
= qstyleoption_cast<const QStyleOptionToolButton *>(opt)) {
QRect rect = toolbutton->rect;
int shiftX = 0;
int shiftY = 0;
if (toolbutton->state & (State_Sunken | State_On)) {
shiftX = proxy()->pixelMetric(PM_ButtonShiftHorizontal, toolbutton, widget);
shiftY = proxy()->pixelMetric(PM_ButtonShiftVertical, toolbutton, widget);
}
// Arrow type always overrules and is always shown
bool hasArrow = toolbutton->features & QStyleOptionToolButton::Arrow;
if (((!hasArrow && toolbutton->icon.isNull()) && !toolbutton->text.isEmpty())
|| toolbutton->toolButtonStyle == Qt::ToolButtonTextOnly) {
int alignment = Qt::AlignCenter | Qt::TextShowMnemonic;

This example of align button content (icon & text) to center, but you can adopt this example to your demands (align to right). Override QToolButoon::paintEvent as next:
void CMyToolButton::paintEvent( QPaintEvent* )
{
QStylePainter sp( this );
QStyleOptionToolButton opt;
initStyleOption( &opt );
const QString strText = opt.text;
const QIcon icn = opt.icon;
//draw background
opt.text.clear();
opt.icon = QIcon();
sp.drawComplexControl( QStyle::CC_ToolButton, opt );
//draw content
const int nSizeHintWidth = minimumSizeHint().width();
const int nDiff = qMax( 0, ( opt.rect.width() - nSizeHintWidth ) / 2 );
opt.text = strText;
opt.icon = icn;
opt.rect.setWidth( nSizeHintWidth );//reduce paint area to minimum
opt.rect.translate( nDiff, 0 );//offset paint area to center
sp.drawComplexControl( QStyle::CC_ToolButton, opt );
}

Related

How to align in the middle the icon and text of QTreeWidget header?

I 'partially' figured out how to align the Header columns but it's not being aligned correctly because I'm not knowing how to properly calculate the current middle position, especially:
int x = (headerRect.width() / 2) - (iconsize.width() / 2) + (headerRect.left()/2);
When I move the scrollbar it didn't get positioned correctly:
Another problem I'm struggling with is how to get the text width. I tried:
QString text = qvariant_cast<QString>(op->text);
//QFontMetrics font = qvariant_cast<QFontMetrics>(op->fontMetrics);
//auto w = op->fontMetrics->horizontalAdvance();
But looks like i cant cast or read the fontMetrics like that.
class HeaderStyle : public QProxyStyle
{
public:
void drawControl(ControlElement element,const QStyleOption * option, QPainter * painter, const QWidget * widget = 0) const
{
if (element == CE_HeaderLabel) {
QStyleOptionHeader *op = (QStyleOptionHeader *) option;
QIcon icon = qvariant_cast<QIcon>(op->icon);
QSize iconsize(20, 20);
QPixmap pixmap = icon.pixmap(iconsize.width(),iconsize.height());
QRect headerRect = op->rect;
int x = (headerRect.width() / 2) - (iconsize.width() / 2) + (headerRect.left()/2);
int y = (headerRect.height() / 2) - (iconsize.height() / 2);
painter->drawPixmap(QPoint(x, y), pixmap);
QString text = qvariant_cast<QString>(op->text);
//QFontMetrics font = qvariant_cast<QFontMetrics>(op->fontMetrics);
//auto w = op->fontMetrics->horizontalAdvance();
painter->drawText(QPoint(x, y + 30), text);
return;
}
QProxyStyle::drawControl(element, option, painter, widget);
}
};
class TreeWidgetLogin : public QTreeWidget
{
Q_OBJECT
public:
TreeWidgetLogin(QWidget* parent = 0) : QTreeWidget(parent)
{
HeaderStyle* style= new HeaderStyle();
this->header()->setStyle(style);
}
};

Set tab close button position on vertical tabs issue

I want to change the default tab widget close button and set my icon instead. The problem is that it draws the icon on the text. I want to draw the X to the right.
Code:
void AppTabBar::paintEvent(QPaintEvent *event)
{
QStylePainter painter(this);
QStyleOptionTab opt;
for (int i = 0; i < this->count(); i++) {
initStyleOption(&opt, i);
opt.text = painter.fontMetrics().elidedText(opt.text, Qt::ElideRight, 70);
painter.drawControl(QStyle::CE_TabBarTabShape, opt);
painter.save();
QSize s = opt.rect.size();
if (tabPos != AppTabPosition::Top && tabPos != AppTabPosition::Bottom) {
s.transpose();
}
QRect r(QPoint(), s);
r.moveCenter(opt.rect.center());
opt.rect = r;
QPoint c = tabRect(i).center();
painter.translate(c);
if (tabPos == AppTabPosition::Left) {
painter.rotate(90);
} else if (tabPos == AppTabPosition::Right) {
painter.rotate(270); //90 - left pos, 270 - right pos
}
painter.translate(-c);
painter.drawControl(QStyle::CE_TabBarTabLabel, opt);
painter.restore();
}
QWidget::paintEvent(event);
}
void AppTabStyle::drawPrimitive(QStyle::PrimitiveElement element, const QStyleOption *option, QPainter *painter, const QWidget *widget) const
{
if (element == PE_IndicatorTabClose) {
int size = proxy()->pixelMetric(QStyle::PM_SmallIconSize);
QIcon::Mode mode = option->state & State_Enabled ? (option->state & State_Raised ? QIcon::Active : QIcon::Normal) : QIcon::Disabled;
if (!(option->state & State_Raised) && !(option->state & State_Sunken) && !(option->state & QStyle::State_Selected)) {
mode = QIcon::Disabled;
}
QIcon::State state = option->state & State_Sunken ? QIcon::On : QIcon::Off;
QPixmap pixmap = QIcon(":/Icons/cross_icon.png").pixmap(size, mode, state);
proxy()->drawItemPixmap(painter, option->rect, Qt::AlignRight, pixmap);
} else {
QProxyStyle::drawPrimitive(element, option, painter, widget);
}
}
Screenshot:
This issue only appears on the vertical tabs (AppTabPosition::Left or AppTabPosition::Right). Any ideas how to draw it to the right?
Edited (15.05.2021):
I have set setTabsClosable(false);, then created the TabBarLabel class and set it to setTabButton method. It closes the tabs but the issue with overlapping the tab text still exists:
Screenshot:
It is a lot of code, so I have created and uploaded to Mega the test example to illustrate this issue. Here is my test example: TabExample.zip (6kb)
Any ideas how to change position of tab bar close button? Thank you.
Thank you.
Finally! I have fixed the issue with vertical tab close button position.
Code:
if (tabPos != AppTabPosition::Top && tabPos != AppTabPosition::Bottom) {
s.transpose();
if (this->tabsClosable()) { // check if tab is closable
QRect optRect = opt.rect;
optRect.setX(90); // set X pos of close button
optRect.setY(optRect.y() + 8); // calcs the Y pos of close button
optRect.setSize(QSize(12, 12));
this->tabButton(i, QTabBar::RightSide)->setGeometry(optRect);
}
}
Here I do not change the default opt.rect but instead I copy it to new QRect. Then I change the size and position of optRect and finally set geometry to tabButtton .
Screenshot:
The issue is resolved.

page x of y using QPrinter

im generating a pdf file from html code using qt:
QTextDocument *document = new QTextDocument();
document->setHtml(htmlContent);
QPrinter printer(QPrinter::HighResolution);
printer.setPageSize(QPrinter::A4);
printer.setOutputFormat(QPrinter::PdfFormat);
printer.setOutputFileName("filename.pdf");
document->print(printer);
Is it possible to have the page information "Page X of Y" instead of only the page number? If yes, how?
The solution I propose is based on this code. I have added the necessary support for HighResolution
static const int textMargins = 12; // in millimeters
static const int borderMargins = 10; // in millimeters
static double mmToPixels(QPrinter& printer, int mm)
{
return mm * 0.039370147 * printer.resolution();
}
static void paintPage(int pageNumber, int pageCount,
QPainter* painter, QTextDocument* doc,
const QRectF& textRect, qreal footerHeight)
{
painter->save();
// textPageRect is the rectangle in the coordinate system of the QTextDocument, in pixels,
// and starting at (0,0) for the first page. Second page is at y=doc->pageSize().height().
const QRectF textPageRect(0, pageNumber * doc->pageSize().height(), doc->pageSize().width(), doc->pageSize().height());
// Clip the drawing so that the text of the other pages doesn't appear in the margins
painter->setClipRect(textRect);
// Translate so that 0,0 is now the page corner
painter->translate(0, -textPageRect.top());
// Translate so that 0,0 is the text rect corner
painter->translate(textRect.left(), textRect.top());
doc->drawContents(painter);
painter->restore();
QRectF footerRect = textRect;
footerRect.setTop(textRect.bottom());
footerRect.setHeight(footerHeight);
painter->drawText(footerRect, Qt::AlignVCenter | Qt::AlignRight, QObject::tr("Page %1 of %2").arg(pageNumber+1).arg(pageCount));
}
static void printDocument(QPrinter& printer, QTextDocument* doc)
{
QPainter painter( &printer );
doc->documentLayout()->setPaintDevice(&printer);
doc->setPageSize(printer.pageRect().size());
QSizeF pageSize = printer.pageRect().size(); // page size in pixels
// Calculate the rectangle where to lay out the text
const double tm = mmToPixels(printer, textMargins);
const qreal footerHeight = painter.fontMetrics().height();
const QRectF textRect(tm, tm, pageSize.width() - 2 * tm, pageSize.height() - 2 * tm - footerHeight);
doc->setPageSize(textRect.size());
const int pageCount = doc->pageCount();
bool firstPage = true;
for (int pageIndex = 0; pageIndex < pageCount; ++pageIndex) {
if (!firstPage)
printer.newPage();
paintPage(pageIndex, pageCount, &painter, doc, textRect, footerHeight );
firstPage = false;
}
}
Example:
int main(int argc, char *argv[])
{
QGuiApplication app(argc, argv);
QTextDocument *document = new QTextDocument();
QTextCursor cursor(document);
QTextBlockFormat blockFormat;
for(int i=0; i < 10; i++){
cursor.insertBlock(blockFormat);
cursor.insertHtml(QString("<h1>This is the %1 page</h1>").arg(i+1));
blockFormat.setPageBreakPolicy(QTextFormat::PageBreak_AlwaysBefore);
}
QPrinter printer(QPrinter::HighResolution);
printer.setPageSize(QPrinter::A4);
printer.setOutputFormat(QPrinter::PdfFormat);
printer.setOutputFileName("filename.pdf");;
printDocument(printer, document);
return app.exec();
}
The above Is a good solution but will break your QTextDocument object if you work with it after the print. I have a QTextDocument that is a member of a dialog and when the calling function that provided the Printer (paintDevice) loses scope it crashes the document.
I suggest getting a poiner of the current paintDevice at the start of the function then restoring it at the end.
static void printDocument(QPrinter& printer, QTextDocument* doc)
{
QPaintDevice * oldDevice=doc->documentLayout()->paintDevice();
doc->documentLayout()->setPaintDevice(&printer);
doc->documentLayout()->setPaintDevice(oldDevice);
}

How to create qt label hover effect?

I must use hover event on Qt label, but I can't found no information about that.
I try use something like ui->label->setText("<a>ads</a>") and onLinkHovered but it's not work correct.
I must change text on hover.
The most flexible solution would be to create your own widget which inherits from QLabel. This way, you could override the enterEvent and leaveEvent #Jeremy and #Moe are writing about which are protected. As a part of these methods implementation you could change the text or decoration accordingly. For example:
class CustomLabel : public QLabel
{
Q_OBJECT
public:
CustomLabel(QWidget* parent = nullptr) : QLabel(parent){ }
protected:
void enterEvent(QEvent *ev) override
{
setStyleSheet("QLabel { background-color : blue; }");
}
void leaveEvent(QEvent *ev) override
{
setStyleSheet("QLabel { background-color : green; }");
}
};
Another approach, but a lot less flexible would be to set the href attribute for the link tag you have specified in label text. This way text would be treated as actual link and you could use the linkHovered signal to connect to. For example:
ui->label->setText("<a href='www.google.com'>link</a>");
connect(ui->label, &QLabel::linkHovered, this, [this](const QString&)
{
// do smth with the widget/text
});
However, please denote that this way you could only make a modification on the hover event.
So if you need to bring the label back to its original state, the first option is the way to go.
Make use of enterEvent and leaveEvent of QLabel.
Create a subclass of QLabel like this for example:
class MyLabel : public QLabel
{
public:
MyLabel();
MyLabel(char* text, MainWindow* w) : QLabel(text, w) { }
void enterEvent(QEvent *event);
void leaveEvent(QEvent *event);
};
and override enterEvent and leaveEvent like this:
void MyLabel::enterEvent(QEvent *event) {
qDebug() << "Entered!";
// Change your text here
}
void MyLabel::leaveEvent(QEvent *event) {
qDebug() << "Left!";
}
You can create instances of this class now like this:
MainWindow w;
MyLabel myLabel("A Test Label", &w);
If you want to determine whether mouse hovers over text inside the QLabel you can use
void mouseMoveEvent(QMouseEvent* event);
and check inside it whether mouse cursor is in bounding rectangle of text.
void PressLabel::mouseMoveEvent(QMouseEvent* event) {
QRect bRect = getTextComponentRectangle();
m_mouseCoord = event->pos();
if(bRect.contains(event->pos())) {
// Mouse pointer over text.
} else {
// Mouse pointer outside text.
}
QLabel::mouseMoveEvent(event);
}
Turn on mouse tracking for your widget to make mouseMoveEvent occur also when mouse button is not pressed.
setMouseTracking(true);
And finally the function that does the calculation of text bounding rectangle. Code below take into account some unexpected cases, too. I tried it with Qt 5.9.1.
For calculation of effective indent in negative case refer to http://doc.qt.io/archives/qt-4.8/qlabel.html#indent-prop .
QRect PressLabel::getTextComponentRectangle() const {
if(frameWidth() < 0) {
throw std::runtime_error("Negative frame width.");
}
int effectiveIndent = indent();
int trueMargin = margin();
if(effectiveIndent < 0) {
if(frameWidth() == 0 || margin() > 0) { // (1)
effectiveIndent = 0;
} else if(frameWidth() > 0) {
QFontMetrics fm(font());
effectiveIndent = fm.width(QChar('x')) / 2;
}
if(frameWidth() > 0 && margin() < 0) { // (2)
trueMargin = 0;
}
}
QFontMetrics fm(font());
QRect bRect = fm.boundingRect(text());
bRect.setWidth(fm.width(text()));
int indentOffset = effectiveIndent + trueMargin + frameWidth();
int offsetX = 0;
int offsetY = 0;
if(alignment() & Qt::AlignHCenter) {
offsetX = rect().width() / 2 - bRect.width() / 2;
} else if(alignment() & Qt::AlignRight) {
offsetX = rect().width() - bRect.width() - indentOffset;
} else if(alignment() & Qt::AlignJustify) {
offsetX = trueMargin + frameWidth();
} else if(alignment() & Qt::AlignLeft) {
offsetX = indentOffset;
}
if(alignment() & Qt::AlignVCenter) {
offsetY = rect().height() / 2 - bRect.height() / 2;
} else if(alignment() & Qt::AlignBottom) {
offsetY = rect().height() - bRect.height() - indentOffset;
} else if(alignment() & Qt::AlignTop) {
offsetY = indentOffset;
}
bRect.moveTopLeft(rect().topLeft());
bRect.setX(bRect.x() + offsetX);
bRect.setWidth(bRect.width() + offsetX);
bRect.setY(bRect.y() + offsetY);
bRect.setHeight(bRect.height() + offsetY);
return bRect;
}
Unexpected cases:
(1) For indent < 0 and margin > 0 effective indent is 0, not width('x')/2 as it is intended to be for negative indent.
(2) For indent < 0 and margin < 0 true margin is 0 and isn't summed to make offset.
I have found that you can also achieve this through the style sheet.
self.label = QtWidgets.QLabel("Toast")
self.label.setStyleSheet("QLabel{color: white;} QLabel:hover {color: blue;}")

Change color/text of QProgressBar on mouse over filled bar

How is it possible to change the color and/or text(format) of a QProgressBar if the mouse is over the filled part of the bar?
I have stacked multiple QProgressBars on top of each other, each showing more content than the previous.
I want to highlight the biggest bar still under the mouse on mouseover and show some bar-specific text. However, the bars are the same size and therefore I want to recognize the filled area of the bar.
E.g., if the mouse is over the third bar, I want to highlight the part from the left to the third bar and show text specific to the third bar.
This is the code I use for the stacked ProgressBars:
class MultiProgressBar : public QWidget
{
Q_OBJECT
public:
MultiProgressBar( QWidget* parent = Q_NULLPTR ) : QWidget( parent ), layout( new QStackedLayout( this ) )
{
layout->setMargin( 0 );
layout->setStackingMode( QStackedLayout::StackAll );
}
void insertBar( QColor const& color )
{
auto bar = new QProgressBar();
bar->setTextVisible( false );
bar->setRange( 0, 10000 );
QPalette palette = this->palette();
palette.setColor( QPalette::Highlight, QColor( color.red(), color.green(), color.blue(), 100 ) );
palette.setColor( QPalette::Base, QColor( color.red(), color.green(), color.blue(), 0 ) );
bar->setPalette( palette );
layout->addWidget( bar );
progress_bars.push_back( bar );
}
public slots:
void setValues( const std::vector< int >& values, const std::vector< std::string >& names )
{
if ( values.size() < progress_bars.size() )
{
for ( auto* widget : progress_bars )
{
layout->removeWidget( widget );
}
progress_bars.clear();
}
while ( progress_bars.size() < values.size() )
{
insertBar( QColor( 0x46, 0xA1, 0xD9, 255 ) );
}
for ( auto i = 0; i < progress_bars.size(); ++i )
{
progress_bars[ i ]->setValue( values[ i ] );
// progress_bars[ i ]->setFormat( QString::fromStdString( names[ i ] ) );
}
}
private:
std::vector< QProgressBar* > progress_bars;
QStackedLayout* layout;
};
1) To change color of filled area you can set new color to the palette of progressbar like this:
QPalette newPalette = bar.palette();
newPalette.setColor(QPalette::Highlight, "red"); // setting color to red
bar.setPalette(newPalette);
2.1) Assign text to progressbar you can with void setFormat(const QString &format); function like in your commented string
progress_bars[ i ]->setFormat( QString::fromStdString( names[ i ] ) );
2.2) Get this text you can by calling QProgressBar::text() function;
3) If you want to highlight specific progressbars you can reimplement
QWidget::mouseMoveEvent(QMouseEvent *event);
in which you can calculate margins of filled area:
void mouseMoveEvent(QMouseEvent *e){
highlightProgressBars(e->pos()); // passing position of mouse cursor
}
NOTE: Don't forget to enable mouse tracking for progressbars and your MultiProgressBar widget:
bar->setMouseTracking(true);
4) Function below gets position of mouse in parameter. It highlights area from left to pointed progressbar inclusively and shows in console text assigned to highlighted progressbars through format property
void highlightProgressBars(QPoint point){
int widthOfBar = ((QProgressBar*)progress_bars.at(0))->width();
int valueForPoint = 10000 / widthOfBar; // value of progressbar for width==1
for (auto pb = this->progress_bars.begin(); pb != this->progress_bars.end(); ++pb) { // iterating vector to paint progressbars
int leftMargin = 0; // "left margin" of current progressbar is in fact right margin of filled area of previous progressbar
if(pb != this->progress_bars.begin()){ // except first progressbar in vector which doesn't have previous progressbar
--pb; // get previous progressbar
leftMargin = ((QProgressBar*)*pb)->value() / valueForPoint; // get width of filled area
++pb; // return to current progressbar
}
if(leftMargin < point.x()) { // if position of cursor is to the right of "left margin" of current progressbar we highlight it
QPalette newPal = ((QProgressBar*)*pb)->palette(); //getting palette
newPal.setColor(QPalette::Highlight,"red"); // and setting color of filled area
((QProgressBar*)*pb)->setPalette(newPal); // finally setting palette to widget
qDebug() << ((QProgressBar*)*pb)->text(); // show text of highlighted progressbar in console
}
else{ // if not we highlight it another way
QPalette newPal = ((QProgressBar*)*pb)->palette();
newPal.setColor(QPalette::Highlight,"lightblue");
((QProgressBar*)*pb)->setPalette(newPal);
}
}
}
If you want to highlight only one progressbar you should change in code above this:
if(leftMargin < point.x()) {
to:
int rightMargin = ((QProgressBar*)*pb)->value() / valueForPoint;
if(leftMargin < point.x() && point.x() < rightMargin) {