How to center a QDialog in QT? - c++

I have this example code:
QDialog *dialog = new QDialog(this);
QPoint dialogPos = dialog->mapToGlobal(dialog->pos());
QPoint thisPos = mapToGlobal(this->pos());
dialog->exec();
But the Dialog is not centered on his parent. Thanks in advance.
UPDATE:
I'm calling Dialog from constructor in MainWindow:
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
{
this->panelInferior = new WidgetTabsInferior;
this->acciones = new Acciones(this);
crearAcciones();
crearBarraMenu();
crearToolbar();
crearTabsEditor();
crearArbolDir();
crearDockWindows();
crearStatusBar();
setWindowIcon(QIcon(":imgs/logo.png"));
connect(this->pestanasEditor , SIGNAL(currentChanged(int)),this,SLOT(cambioTab(int)));
this->dialogo = new AcercaDe(this);
this->dialogo->move(x() + (width() - dialogo->width()) / 2,
y() + (height() - dialogo->height()) / 2);
this->dialogo->show();
this->dialogo->raise();
this->dialogo->activateWindow();
}
But I get is:

I have this code in github
inline void CenterWidgets(QWidget *widget, QWidget *host = 0) {
if (!host)
host = widget->parentWidget();
if (host) {
auto hostRect = host->geometry();
widget->move(hostRect.center() - widget->rect().center());
}
else {
QRect screenGeometry = QApplication::desktop()->screenGeometry();
int x = (screenGeometry.width() - widget->width()) / 2;
int y = (screenGeometry.height() - widget->height()) / 2;
widget->move(x, y);
}
}
Hope it helps
edit
fix the deprecation warning issued from recent Qt versions:
#include <QScreen>
#include <QWidget>
#include <QGuiApplication>
inline void CenterWidgets(QWidget *widget, QWidget *host = Q_NULLPTR) {
if (!host)
host = widget->parentWidget();
if (host) {
auto hostRect = host->geometry();
widget->move(hostRect.center() - widget->rect().center());
}
else {
QRect screenGeometry = QGuiApplication::screens()[0]->geometry();
int x = (screenGeometry.width() - widget->width()) / 2;
int y = (screenGeometry.height() - widget->height()) / 2;
widget->move(x, y);
}
}

Thought I would post my own solution here. #CapelliC's solution works, but is deprecated since Qt5.11. Infact, the documentation says the QDesktopWidget class is obsolete.
Solution (which is a litte crude) is to use the QGuiApplication::screenAt()
Context: class inheriting QMainWindow, but can can extend for any QWidget
// Get current screen size
QRect rec = QGuiApplication::screenAt(this->pos())->geometry();
// Using minimum size of window
QSize size = this->minimumSize();
// Set top left point
QPoint topLeft = QPoint((rec.width() / 2) - (size.width() / 2), (rec.height() / 2) - (size.height() / 2));
// set window position
setGeometry(QRect(topLeft, size));
Hope it helps.

You have to change the geometry of the QDialog:
dialog->move(x() + (width() - dialog->width()) / 2,
y() + (height() - dialog->height()) / 2);
The move() function moves respect the parent, so it is not necessary to map to global.
On constructor the position and size of parent are not set yet. You can try executing the dialog in a separate method or, if needed on constructor, try with something like
QTimer::singleShot(0, [=]() {
// ... your dialog code
});
It will be shown on the next iteration of the event loop.

I think it's a Qt4 bug. I used Qt4 on Ubuntu and it doesn't respect the parent widget center.
However, when I use Qt5, It seems to work fine.
You can also use move() to arrive to your position.

Related

QScrollArea - Resize content widgets by keeping the aspect ratio

I have a layout that looks like this.
Where:
Blue: rectangle it's a ScrollArea
Orange: rectangles are the widgets from that ScrollArea
My code:
#include <QtWidgets>
///////////////////////////////////////////////////////////////////////////////////////
class RoundedPolygon : public QPolygon {
public:
RoundedPolygon() { SetRadius(10); }
void SetRadius(unsigned int iRadius) { m_iRadius = iRadius; }
const QPainterPath &GetPath() {
m_path = QPainterPath();
if (count() < 3) {
qDebug() << "!! Polygon should have at least 3 points !!";
return m_path;
}
QPointF pt1;
QPointF pt2;
for (int i = 0; i < count(); i++) {
pt1 = GetLineStart(i);
if (i == 0)
m_path.moveTo(pt1);
else
m_path.quadTo(at(i), pt1);
pt2 = GetLineEnd(i);
m_path.lineTo(pt2);
}
// close the last corner
pt1 = GetLineStart(0);
m_path.quadTo(at(0), pt1);
return m_path;
}
private:
QPointF GetLineStart(int i) const {
QPointF pt;
QPoint pt1 = at(i);
QPoint pt2 = at((i + 1) % count());
float fRat = m_iRadius / GetDistance(pt1, pt2);
if (fRat > 0.5f)
fRat = 0.5f;
pt.setX((1.0f - fRat) * pt1.x() + fRat * pt2.x());
pt.setY((1.0f - fRat) * pt1.y() + fRat * pt2.y());
return pt;
}
QPointF GetLineEnd(int i) const {
QPointF pt;
QPoint pt1 = at(i);
QPoint pt2 = at((i + 1) % count());
float fRat = m_iRadius / GetDistance(pt1, pt2);
if (fRat > 0.5f)
fRat = 0.5f;
pt.setX(fRat * pt1.x() + (1.0f - fRat) * pt2.x());
pt.setY(fRat * pt1.y() + (1.0f - fRat) * pt2.y());
return pt;
}
float GetDistance(QPoint pt1, QPoint pt2) const {
int fD = (pt1.x() - pt2.x()) * (pt1.x() - pt2.x()) + (pt1.y() - pt2.y()) * (pt1.y() - pt2.y());
return sqrtf(fD);
}
private:
QPainterPath m_path;
unsigned int m_iRadius{};
};
class PolygonButtonWidget : public QWidget {
Q_OBJECT
public:
explicit PolygonButtonWidget(QWidget *parent = nullptr) : QWidget(parent) {}
~PolygonButtonWidget() override = default;
protected:
void resizeEvent(QResizeEvent *event) override {
float ratioW = 8;
float ratioH = 3;
// ui->scrollAreaWidgetContents->setFixedSize(5000, h);
float thisAspectRatio = (float) event->size().width() / event->size().height();
if (thisAspectRatio < ratioW / ratioH) {
float w = event->size().height() * ratioW / ratioH;
float h = event->size().height();
qDebug() << hasHeightForWidth() << " " << w << " " << h;
this->resize(w, h);
if (m_nrButtons != 0) {
this->move((w + 20) * m_nrButtons, this->y());
}
}
QWidget::resizeEvent(event);
}
int m_nrButtons{};
public:
void setMNrButtons(int mNrButtons) {
m_nrButtons = mNrButtons;
}
protected:
void paintEvent(QPaintEvent *event) override {
int offset = 50;
m_polygon.clear();
m_polygon.emplace_back(0, height()); //DOWN-LEFT
m_polygon.emplace_back(width() - offset, height()); //DOWN-RIGHT
m_polygon.emplace_back(width(), 0); //TOP-RIGHT
m_polygon.emplace_back(0 + offset, 0);
QPainter painter(this);
painter.setRenderHint(QPainter::Antialiasing);
RoundedPolygon poly;
poly.SetRadius(15);
for (QPoint point: m_polygon) {
poly << point;
}
QBrush fillBrush;
fillBrush.setColor(Qt::darkBlue);
fillBrush.setStyle(Qt::SolidPattern);
QPainterPath path;
path.addPath(poly.GetPath());
painter.fillPath(path, fillBrush);
}
void mousePressEvent(QMouseEvent *event) override {
auto cursorPos = mapFromGlobal(QCursor::pos());
qDebug() << "X: " << cursorPos.x() << " Y: " << cursorPos.y();
inside(cursorPos, m_polygon);
qDebug() << "Pressed";
}
private:
std::vector<QPoint> m_polygon;
bool inside(QPoint point, std::vector<QPoint> polygon) {
auto x = point.x();
auto y = point.y();
auto inside = false;
auto i = 0;
auto j = polygon.size() - 1;
while (i < polygon.size()) {
auto xi = polygon[i].x();
auto yi = polygon[i].y();
auto xj = polygon[j].x();
auto yj = polygon[j].y();
auto intersect = ((yi > y) != (yj > y)) && (x < (xj - xi) * (y - yi) / (yj - yi) + xi);
if (intersect) inside = !inside;
j = i++;
}
qDebug() << inside;
return inside;
}
};
///////////////////////////////////////////////////////////////////////////////////////
int main(int argc, char *argv[]) {
QApplication app(argc, argv);
QWidget root;
QHBoxLayout layout{&root};
for (int i = 0; i < 10; ++i) {
auto p = new PolygonButtonWidget();
p->setMinimumSize(100, 100);
p->setMNrButtons(i);
layout.addWidget(p);
}
root.setStyleSheet("background-color: rgb(19,19,19);");
QScrollArea view;
view.setWidget(&root);
view.show();
app.exec();
}
#include "main.moc"
The problem arises when I'm trying to resize the window. In the moment of resizing, I want my widgets to keep their aspect ratio. But that's not going to happen.
I have scroll list of widgets which is looking like this (if it's expended on X way too much)
If I will scale it on Y-axis it's going to look like this.
After I've changed the resizeEvent now it's going to look something like this
or like this
How can I fix this? For some reason, some of my widgets are going to disappear, what should be my approach in order to fix this issue?
The problem is caused by the assumption that there's any mechanism that will automatically resize the widgets for you. There isn't. A QScrollArea acts as a layout barrier and any layouts inside of it are isolated from its size, and thus from any resize events.
You must resize the container widget (the one with blue outline on your diagram) yourself anytime the scroll area changes size, and you need first to prepare a test case for the widgets such that their size changes are properly managed when placed in the layout of your choice, and said layout is resized.
Finally, the pet peeve of mine: It's unlikely that you actually need the QMainWindow for anything. It's just a silly Qt Creator template. But unless you want an MDI interface and docking, you shouldn't be using the QMainWindow - and especially not when making a self-contained example. All you need here is QScrollArea as a top-level widget. That's literally all. Any QWidget can be a top-level window!
For future submissions, please provide all the code needed in a single main.cpp file that begins with #include <QtWidgets> and ends with #include "main.moc". You won't need any other includes for Qt classes, and you can write class definitions Java-style, with all the methods defined within the class declaration itself. This provides for short code - after all, a SO question isn't an Enterprise project. It's supposed to be minimal, and that really means that anything not necessary must be removed. No need for header files, multiple includes, nor other fluff - i.e. use Qt containers instead of C++ STL so that you don't need more includes etc.
Your example should look roughly as follows:
#include <QtWidgets>
class PolygonButtonWidget : public QAbstractButton {
Q_OBJECT
/* without seeing the code here, your question is unanswerable */
};
int main(int argc, char* argv[]) {
QApplication app(argc, argv);
QWidget root;
QHBoxLayout layout{&root};
PolygonButtonWidget buttons[10];
for (auto &button : buttons)
layout.addWidget(&button);
QScrollArea view;
view.setWidget(&root);
view.show();
app.exec();
view.takeWidget();
}
#include "main.moc"
Without such an example, your question is hard to answer, since:
How can we debug it? Debugging means using a debugger. If your code cannot be immediately compiled, then it's quite unlikely that someone will bother debugging it, and debugging by inspection is often error-prone.
How can we provide a tested answer if we'd have to first write the entire "test case" for it?
How can we know what's inside your button widget? The behavior of that widget does affect the ultimate solution.
It'd also help if you described a few use cases that you'd expect to work. That is, mock up (with a drawing) the state of the widgets before and after the view is resized, so that we can easily see what it is that you expect to happen. A lot of it is very easy to miss when explaining your needs in words. Use cases are a lingua franca of software specifications. If you don't use them, it's highly likely that you yourself don't know what behavior you expect in all cases.

QGroupBox's child restricts shrink the form

I have a multiple screen video player, and I just want to keep 16:9 ratio. There is a qgroupbox as a container of a qwidget which plays video in it. I also use qgroupbox to show selected frame by painting border to green. I can't do this on qwidget because rendered video overlaps that. When I have done with resize, I emit a signal with mouseup event to be able to informed about the resize operation completed. Then I calculate new bounds for qwidget to keep 16:9 ratio and apply this values for qwidget. Here is the image to show you how my app looks like:
And here is the code that I use to resize qwidgets:
void playBack::OnWindowResized()
{
float ratio = 16.0f / 9.0f;
float w = playBackplayer_contList.at(0)->size().width(); //qgroupbox's width
float h = playBackplayer_contList.at(0)->size().height();//qgroupbox's height
float currentRatio = w / h;
float newW = 0;
float newH = 0;
if (currentRatio > ratio)
{
newH = h;
newW = h*ratio;
}
else if (currentRatio < ratio)
{
newW = w;
newH = w / ratio;
}
qDebug() << "NEW WIDGET SIZE: " << (int)newW << " x " << (int)newH;
for (int i = 0; i < playBackplayer_widgtList.count(); i++)
{
playBackplayer_widgtList.at(i)->setMinimumSize(newW, newH);
//playBackplayer_widgtList.at(i)->resize(newW, newH);
}
}
This code works perfectly when I enlarge form, but When I want to shrink, It doesn't allow me to do that. Because I set a minimum value for qwidgets. If I don't use setMinimumSize, use resize(w,h) instead, than orientation problems occur. And here is a example for this issue:
This code below shows ctor and this is where I set the layout:
playBack::playBack()
{
playback_player_1_widget = new QWidget;
playback_player_2_widget = new QWidget;
playback_player_3_widget = new QWidget;
playback_player_4_widget = new QWidget;
playback_player_1_widget_cont = new QGroupBox;
playback_player_2_widget_cont = new QGroupBox;
playback_player_3_widget_cont = new QGroupBox;
playback_player_4_widget_cont = new QGroupBox;
playBackplayer_widgtList.append(playback_player_1_widget);
playBackplayer_widgtList.append(playback_player_2_widget);
playBackplayer_widgtList.append(playback_player_3_widget);
playBackplayer_widgtList.append(playback_player_4_widget);
playBackplayer_contList.append(playback_player_1_widget_cont);
playBackplayer_contList.append(playback_player_2_widget_cont);
playBackplayer_contList.append(playback_player_3_widget_cont);
playBackplayer_contList.append(playback_player_4_widget_cont);
int rowcnt = 0;
int colcnt = 0;
for (int i = 0; i < 4; i++)
{
playBackplayer_contList.at(i)->setStyleSheet(QString("border:1px solid #000;background-color:#000;"));
playBackplayer_widgtList.at(i)->setStyleSheet(QString("background-color:#f00;"));
QGridLayout* layout = new QGridLayout;
layout->setRowStretch(0, 1);
layout->setColumnStretch(0, 1);
layout->setRowStretch(2, 1);
layout->setColumnStretch(2, 1);
playBackplayer_widgtList.at(i)->setMinimumWidth(100);
playBackplayer_widgtList.at(i)->setMinimumHeight(100);
playBackplayer_widgtList.at(i)->setMaximumWidth(10000);
playBackplayer_widgtList.at(i)->setMaximumHeight(10000);
layout->addWidget(playBackplayer_widgtList.at(i),1,1);
layout->setMargin(0);
layout->setSpacing(0);
playBackplayer_contList.at(i)->setLayout(layout);
mainLayout->addWidget(playBackplayer_contList.at(i), colcnt, rowcnt);
rowcnt++;
if (rowcnt % 2 == 0)
{
rowcnt = 0;
colcnt++;
}
playBackplayer_widgtList.at(i)->setAcceptDrops(true);
}
}
I have tried various things, I have tried to set size 0 for qwidget before resize, (in mousedownevent) that didn't work, I have tried deleting layout for qgroupbox, after resize happens, create new layout and set it for groupbox, that didn't work, I have tried layout()->adjustSize(), update(), repaint(), all that stuff didn't work. What am I missing? I need helps from you. Any help would be appreciated. Thank you in advance.
Do away with the grid layout inside the container group boxes. Instead, align and resize the video widget with setGeometry
Here is a simple subclass of QGroupBox I made that keeps your desired ratio and always stays in the center:
class RatioGroupBox : public QGroupBox{
Q_OBJECT
public:
RatioGroupBox(QWidget *parent = nullptr) : QGroupBox (parent){
setFlat(true);
setStyleSheet("border:1px solid #000;background-color:#000;");
setMinimumSize(100, 100);
setMaximumSize(10000, 10000);
ratio = 16.0f/9.0f;
ratioWidget = new QWidget(this);
ratioWidget->setStyleSheet("background: #f00;");
ratioWidget->setAcceptDrops(true);
}
protected:
void resizeEvent(QResizeEvent *){//or you can use your own resize slot
float w = width();
float h = height();
float currentRatio = w/h;
float newW(0);
float newH(0);
if (currentRatio > ratio){
newH = h;
newW = h*ratio;
}
else if (currentRatio < ratio){
newW = w;
newH = w / ratio;
}
ratioWidget->setGeometry((w-newW)/2, (h-newH)/2, newW, newH);
}
private:
QWidget *ratioWidget;
float ratio;
};
Your entire ctor will become something like:
playBack::playBack()
{
for(int r=0; r<2; r++){
for(int c=0; c<2; c++){
RatioGroupBox* playback_player_cont = new RatioGroupBox;
mainLayout->addWidget(playback_player_cont, c, r);
playBackplayer_contList.append(playback_player_cont);
}
}
}
You can of course access your video widgets by exposing ratioWidget any way you like. Either by making it public or creating a getter function.

Dynamically change font size of QLabel to fit available space

I'm trying to make an horizontal layout with 3 QLabel scale use all its available space. More specifically, this is what I have
this is what I am aiming for
At the moment, the second image is achieved by changing the stylesheet of the qlabels with a slider. Additionally, since I have the the three labels in a layout inside a groupbox, the groupbox resizes to fit its contents, cool.
Now I wanted to drop the slider approach and instead autofit the space available when moving the splitters. In this question, OP reimplements the resizeEvent, and I've seen other posts suggesting the same, changing point by point with this while( !doesFit ) or something similar.
I tried using this approach, both on the resize event and on the splitterMoved event. However, this approach is way prone to feedback loops and other display errors caused. In the other question, they suggest enabling ignoreSizePolicy to prevent the size policy retriggering the sizeevent, but I like how qt handles the size of the layout, how it keeps a minimum size and then it folds the widget if the user insists. Maybe it would work if the HLayout would ignore the resize events triggered by the QLabels, still IMHO unclean thought.
I was wondering if that's the recommended way of achieving this, and wether a less unstable solution exists, maybe using using stylesheets. There are some behaviours that I could also drop, the minimum size limit (so the user could potentially hide the groupbox).
If that's the recommended way of doing it, how should I use the fontmetrics if I have three separate labels, one of which (the number) changes its text dynamically and rapidly? It should not have an impact on performance, and that while loop makes me wary.
It doesn't sound like the while(!fit) approach is going to cut it. Or does it?
--- Edit regarding the duplicate question
Another post creates an event filter, which might also work if reworked to deal with a layout with 3 labels. I finally used a version of the first mentioned post with the variation of the post mentioned in the comments. I'll post the answer if the question is reopened.
One could apply the Newton's method approach from this answer to work on all widgets in a given layout. It will work on any widget with a settable font, not only on a QLabel.
The Newton's algorithm converges reasonably quickly when given a good starting point, e.g. when resizing interactively. It's not atypical to have the loop execute only once. On the other hand, QWidget::sizeHint is integer-valued and and widgets may round fractional font sizes, thus sometimes the iteration is a bit slower than one would expect. The number of iterations is capped to ensure decent performance.
A custom replacement for the label, that provided a QSizeF sizeHintF(), would work better here.
The minimum sizing for the widgets is a bit of a stretch, as the size is not updated as the widget contents change. This could be remedied easily, though.
// https://github.com/KubaO/stackoverflown/tree/master/questions/label-text-size-vert-40861305
#include <QtWidgets>
class LabelStretcher : public QObject {
Q_OBJECT
static constexpr const char kMinimumsAcquired[] = "ls_minimumsAcquired";
static constexpr const char kStretcherManaged[] = "ls_stretcherManaged";
public:
LabelStretcher(QObject *parent = 0) : QObject(parent) {
apply(qobject_cast<QWidget*>(parent));
}
void apply(QWidget *widget) {
if (!widget) return;
setManaged(widget);
setMinimumSize(widget);
widget->installEventFilter(this);
}
void setManaged(QWidget *w, bool managed = true) {
w->setProperty(kStretcherManaged, managed);
}
protected:
bool eventFilter(QObject * obj, QEvent * ev) override {
auto widget = qobject_cast<QWidget*>(obj);
if (widget && ev->type() == QEvent::Resize)
resized(widget);
return false;
}
private:
void onLayout(QLayout *layout, const std::function<void(QWidget*)> &onWidget) {
if (!layout) return;
auto N = layout->count();
for (int i = 0; i < N; ++i) {
auto item = layout->itemAt(i);
onWidget(item->widget());
onLayout(item->layout(), onWidget);
}
}
void setFont(QLayout *layout, const QFont &font) {
onLayout(layout, [&](QWidget *widget){ setFont(widget, font); });
}
void setFont(QWidget *widget, const QFont &font) {
if (!widget || !widget->property(kStretcherManaged).toBool()) return;
widget->setFont(font);
setFont(widget->layout(), font);
}
void setMinimumSize(QWidget *widget) {
if (widget->layout()) return;
widget->setMinimumSize(widget->minimumSizeHint());
}
static int dSize(const QSizeF & inner, const QSizeF & outer) {
auto dy = inner.height() - outer.height();
auto dx = inner.width() - outer.width();
return std::max(dx, dy);
}
qreal f(qreal fontSize, QWidget *widget) {
auto font = widget->font();
font.setPointSizeF(fontSize);
setFont(widget, font);
auto d = dSize(widget->sizeHint(), widget->size());
qDebug() << "f:" << fontSize << "d" << d;
return d;
}
qreal df(qreal fontSize, qreal dStep, QWidget *widget) {
fontSize = std::max(dStep + 1.0, fontSize);
return (f(fontSize + dStep, widget) - f(fontSize - dStep, widget)) / dStep;
}
void resized(QWidget *widget) {
qDebug() << "pre: " << widget->minimumSizeHint() << widget->sizeHint() << widget->size();
if (!widget->property(kMinimumsAcquired).toBool()) {
onLayout(widget->layout(), [=](QWidget *widget){ setMinimumSize(widget); });
widget->setProperty(kMinimumsAcquired, true);
}
// Newton's method
auto font = widget->font();
auto fontSize = font.pointSizeF();
qreal dStep = 1.0;
int i;
for (i = 0; i < 10; ++i) {
auto prevFontSize = fontSize;
auto d = df(fontSize, dStep, widget);
if (d == 0) {
dStep *= 2.0;
continue;
}
fontSize -= f(fontSize, widget)/d;
fontSize = std::max(dStep + 1.0, fontSize);
auto change = fabs(prevFontSize - fontSize)/fontSize;
qDebug() << "d:" << d << " delta" << change;
if (change < 0.01) break; // we're within 1% of target
}
font.setPointSizeF(fontSize);
setFont(widget, font);
qDebug() << "post:" << i << widget->minimumSizeHint() << widget->sizeHint() << widget->size();
}
};
constexpr const char LabelStretcher::kMinimumsAcquired[];
constexpr const char LabelStretcher::kStretcherManaged[];
int main(int argc, char ** argv) {
QApplication app{argc, argv};
QWidget w;
QGridLayout layout{&w};
LabelStretcher stretch{&w};
QLabel labels[6];
QString texts[6] = {"V", "30.0", "kts", "H", "400.0", "ft"};
int i = 0, j = 0, k = 0;
for (auto & label : labels) {
stretch.setManaged(&label);
label.setFrameStyle(QFrame::Box);
label.setText(texts[k++]);
if (j == 0) label.setAlignment(Qt::AlignRight | Qt::AlignVCenter);
else if (j == 1) label.setAlignment(Qt::AlignCenter);
layout.addWidget(&label, i, j++);
if (j >= 3) { i++; j=0; }
}
w.show();
return app.exec();
}
#include "main.moc"
Althought I consider KubaOber's answer better, I'll post this in case it's helpful to someone who wants a solution in the line of the answers mentioned in the post.
Note that the sampletext could be retrieved from the labels as well, the font from the stylesheet, and the code could potentially be placed on a resizeEvent of the groupbox or layout. It wouldn't work on the resizeEvent of the labels since they would compete for the space.
That is one reason why KubaOber answer is superior. Other reasons I can think of is stability given that the 3 labels space differs from the sampletext, thus the font size is not as accurate as it could be. Therefore a resize event could potentially be triggered again by the font change.
static void fitGroupBoxLabels(QGroupBox* groupbox, const QFont &samplefont, const QLayout* const samplelayout)
{
groupbox->setSizePolicy(QSizePolicy::Ignored, QSizePolicy::Ignored);
QString sampletext = "V 1000.0 kts";
QRect availablerect = samplelayout->contentsRect();
if(samplefont.pointSizeF() <= 0) return; //not initalized yet, return
QRect textrect = QFontMetrics(samplefont).boundingRect(sampletext);
if(!textrect.isValid() || !availablerect.isValid()) return; //not initalized yet, return
float factorh = availablerect.width() / (float)textrect.width();
float factorw = availablerect.height() / (float)textrect.height();
float factor = std::min(factorh, factorw);
if (factor < 0.95 || factor > 1.05)
{
float fontSize = samplefont.pointSizeF()*factor;
QString groupBoxStyle = QString("QGroupBox{font-size:8pt} QLabel{font-size:%1pt}").arg(fontSize);
groupbox->setStyleSheet(groupBoxStyle);
}
}
After struggling with this issue, I create DynamicFontSizeLabel and DynamicFontSizePushButton widgets. Hope it helps.
https://github.com/jonaias/DynamicFontSizeWidgets/

Custom placeholder in QLineEdit

I want to have a QLineEdit with the specific placeholder text format: it needs to have left aligned and right aligned text. Here is an example:
Any ideas?
Unfortunately, this seems to be all hard coded in void QLineEdit::paintEvent(QPaintEvent *) as follows:
if (d->shouldShowPlaceholderText()) {
if (!d->placeholderText.isEmpty()) {
QColor col = pal.text().color();
col.setAlpha(128);
QPen oldpen = p.pen();
p.setPen(col);
QRect ph = lineRect.adjusted(minLB, 0, 0, 0);
QString elidedText = fm.elidedText(d->placeholderText, Qt::ElideRight, ph.width());
p.drawText(ph, va, elidedText);
p.setPen(oldpen);
}
}
You could reimplement this on your own in a subclass if you wish.
Naturally, you could also "cheat" with space and font sizes, but that would require a bit more work, and would be nastier in the end, too, let alone long-term reliability.
You could also contribute to the Qt Project to make this class more flexible, but they could reject it with the reason of not being common case enough. It is up to the maintainer(s).
Thanks, #lpapp ! His advice is right. Here is the code, I created from the Qt sources suggested by #lpapp :
void LineEdit::paintEvent(QPaintEvent *e) {
QLineEdit::paintEvent(e);
if (!text().isEmpty()) {
return;
}
QPainter p(this);
QStyleOptionFrameV2 panel;
initStyleOption(&panel);
QRect r = style()->subElementRect(QStyle::SE_LineEditContents, &panel, this);
r.setX(r.x() + textMargins().left());
r.setY(r.y() + textMargins().top());
r.setRight(r.right() - textMargins().right());
r.setBottom(r.bottom() - textMargins().bottom());
QFontMetrics fm = fontMetrics();
int minLB = qMax(0, -fm.minLeftBearing());
int minRB = qMax(0, -fm.minRightBearing());
int vscroll = r.y() + (r.height() - fm.height() + 1) / 2;
static const int horizontalMargin = 2; // QLineEditPrivate::horizontalMargin
QRect lineRect(r.x() + horizontalMargin, vscroll, r.width() - 2*horizontalMargin, fm.height());
QRect ph = lineRect.adjusted(minLB, 0, -minRB, 0);
QColor col = palette().text().color();
col.setAlpha(128);
p.setPen(col);
QString left = fm.elidedText("left", Qt::ElideRight, ph.width());
Qt::Alignment leftAlignment = QStyle::visualAlignment(Qt::LeftToRight, QFlag(Qt::AlignLeft));
p.drawText(ph, leftAlignment, left);
QString right = fm.elidedText("right", Qt::ElideRight, ph.width());
Qt::Alignment rightAlignment = QStyle::visualAlignment(Qt::LeftToRight, QFlag(Qt::AlignRight));
p.drawText(ph, rightAlignment, right);
}
I don't know an easy way to do this. You could try to calculate the pixel width (using QFontMetrics) of both placeholder-parts and calculate the number of spaces you need to insert between the placeholder-parts to let them appear aligned. You wuld need to set/update the calculated placeholder whenever the size of the widget changes.

How to add lines numbers to QTextEdit?

I am writing a Visual Basic IDE, and I need to add lines numbers to QTextEdit and highlight current line. I have found this tutorial, but it is written in Java and I write my project in C++.
I know that Qt tutorial recommends using QPlainTextEdit for text editor implementations, and that the question (except as mentioned in the title), is more general than dealing (absolutely) with a QTextEdit widget, but I succeeded in implementing the behaviour (line numbers + current line number highlight), and I think this might be helpful for some people (like me) who really want to keep going with the Rich Text widget, and want to share my implementation (which is far from perfect - quite fast coded...).
LineNumberArea.h : (Same as "QPlainTextEdit" tutorial)
class LineNumberArea : public QWidget
{
Q_OBJECT
public:
LineNumberArea(QTextEdit *editor);
QSize sizeHint() const;
protected:
void paintEvent(QPaintEvent *event);
private:
QTextEdit *codeEditor;
};
LineNumberArea.cpp : (Same as "QPlainTextEdit" tutorial)
LineNumberArea::LineNumberArea(QTextEdit *editor) : QWidget(editor) {
codeEditor = editor;
}
QSize LineNumberArea::sizeHint() const {
return QSize(((QTextEditHighlighter *)codeEditor)->lineNumberAreaWidth(), 0);
}
void LineNumberArea::paintEvent(QPaintEvent *event) {
((QTextEditHighlighter *)codeEditor)->lineNumberAreaPaintEvent(event);
}
>> qtextedithighlighter.h :
class QTextEditHighlighter : public QTextEdit
{
Q_OBJECT
public:
explicit QTextEditHighlighter(QWidget *parent = 0);
int getFirstVisibleBlockId();
void lineNumberAreaPaintEvent(QPaintEvent *event);
int lineNumberAreaWidth();
signals:
public slots:
void resizeEvent(QResizeEvent *e);
private slots:
void updateLineNumberAreaWidth(int newBlockCount);
void updateLineNumberArea(QRectF /*rect_f*/);
void updateLineNumberArea(int /*slider_pos*/);
void updateLineNumberArea();
private:
QWidget *lineNumberArea;
};
>> qtextedithighlighter.cpp :
#include "qtextedithighlighter.h"
QTextEditHighlighter::QTextEditHighlighter(QWidget *parent) :
QTextEdit(parent)
{
// Line numbers
lineNumberArea = new LineNumberArea(this);
///
connect(this->document(), SIGNAL(blockCountChanged(int)), this, SLOT(updateLineNumberAreaWidth(int)));
connect(this->verticalScrollBar(), SIGNAL(valueChanged(int)), this, SLOT(updateLineNumberArea/*_2*/(int)));
connect(this, SIGNAL(textChanged()), this, SLOT(updateLineNumberArea()));
connect(this, SIGNAL(cursorPositionChanged()), this, SLOT(updateLineNumberArea()));
///
updateLineNumberAreaWidth(0);
}
int QTextEditHighlighter::lineNumberAreaWidth()
{
int digits = 1;
int max = qMax(1, this->document()->blockCount());
while (max >= 10) {
max /= 10;
++digits;
}
int space = 13 + fontMetrics().width(QLatin1Char('9')) * (digits);
return space;
}
void QTextEditHighlighter::updateLineNumberAreaWidth(int /* newBlockCount */)
{
setViewportMargins(lineNumberAreaWidth(), 0, 0, 0);
}
void QTextEditHighlighter::updateLineNumberArea(QRectF /*rect_f*/)
{
QTextEditHighlighter::updateLineNumberArea();
}
void QTextEditHighlighter::updateLineNumberArea(int /*slider_pos*/)
{
QTextEditHighlighter::updateLineNumberArea();
}
void QTextEditHighlighter::updateLineNumberArea()
{
/*
* When the signal is emitted, the sliderPosition has been adjusted according to the action,
* but the value has not yet been propagated (meaning the valueChanged() signal was not yet emitted),
* and the visual display has not been updated. In slots connected to this signal you can thus safely
* adjust any action by calling setSliderPosition() yourself, based on both the action and the
* slider's value.
*/
// Make sure the sliderPosition triggers one last time the valueChanged() signal with the actual value !!!!
this->verticalScrollBar()->setSliderPosition(this->verticalScrollBar()->sliderPosition());
// Since "QTextEdit" does not have an "updateRequest(...)" signal, we chose
// to grab the imformations from "sliderPosition()" and "contentsRect()".
// See the necessary connections used (Class constructor implementation part).
QRect rect = this->contentsRect();
lineNumberArea->update(0, rect.y(), lineNumberArea->width(), rect.height());
updateLineNumberAreaWidth(0);
//----------
int dy = this->verticalScrollBar()->sliderPosition();
if (dy > -1) {
lineNumberArea->scroll(0, dy);
}
// Addjust slider to alway see the number of the currently being edited line...
int first_block_id = getFirstVisibleBlockId();
if (first_block_id == 0 || this->textCursor().block().blockNumber() == first_block_id-1)
this->verticalScrollBar()->setSliderPosition(dy-this->document()->documentMargin());
// // Snap to first line (TODO...)
// if (first_block_id > 0)
// {
// int slider_pos = this->verticalScrollBar()->sliderPosition();
// int prev_block_height = (int) this->document()->documentLayout()->blockBoundingRect(this->document()->findBlockByNumber(first_block_id-1)).height();
// if (dy <= this->document()->documentMargin() + prev_block_height)
// this->verticalScrollBar()->setSliderPosition(slider_pos - (this->document()->documentMargin() + prev_block_height));
// }
}
void QTextEditHighlighter::resizeEvent(QResizeEvent *e)
{
QTextEdit::resizeEvent(e);
QRect cr = this->contentsRect();
lineNumberArea->setGeometry(QRect(cr.left(), cr.top(), lineNumberAreaWidth(), cr.height()));
}
int QTextEditHighlighter::getFirstVisibleBlockId()
{
// Detect the first block for which bounding rect - once translated
// in absolute coordinated - is contained by the editor's text area
// Costly way of doing but since "blockBoundingGeometry(...)" doesn't
// exists for "QTextEdit"...
QTextCursor curs = QTextCursor(this->document());
curs.movePosition(QTextCursor::Start);
for(int i=0; i < this->document()->blockCount(); ++i)
{
QTextBlock block = curs.block();
QRect r1 = this->viewport()->geometry();
QRect r2 = this->document()->documentLayout()->blockBoundingRect(block).translated(
this->viewport()->geometry().x(), this->viewport()->geometry().y() - (
this->verticalScrollBar()->sliderPosition()
) ).toRect();
if (r1.contains(r2, true)) { return i; }
curs.movePosition(QTextCursor::NextBlock);
}
return 0;
}
void QTextEditHighlighter::lineNumberAreaPaintEvent(QPaintEvent *event)
{
this->verticalScrollBar()->setSliderPosition(this->verticalScrollBar()->sliderPosition());
QPainter painter(lineNumberArea);
painter.fillRect(event->rect(), Qt::lightGray);
int blockNumber = this->getFirstVisibleBlockId();
QTextBlock block = this->document()->findBlockByNumber(blockNumber);
QTextBlock prev_block = (blockNumber > 0) ? this->document()->findBlockByNumber(blockNumber-1) : block;
int translate_y = (blockNumber > 0) ? -this->verticalScrollBar()->sliderPosition() : 0;
int top = this->viewport()->geometry().top();
// Adjust text position according to the previous "non entirely visible" block
// if applicable. Also takes in consideration the document's margin offset.
int additional_margin;
if (blockNumber == 0)
// Simply adjust to document's margin
additional_margin = (int) this->document()->documentMargin() -1 - this->verticalScrollBar()->sliderPosition();
else
// Getting the height of the visible part of the previous "non entirely visible" block
additional_margin = (int) this->document()->documentLayout()->blockBoundingRect(prev_block)
.translated(0, translate_y).intersect(this->viewport()->geometry()).height();
// Shift the starting point
top += additional_margin;
int bottom = top + (int) this->document()->documentLayout()->blockBoundingRect(block).height();
QColor col_1(90, 255, 30); // Current line (custom green)
QColor col_0(120, 120, 120); // Other lines (custom darkgrey)
// Draw the numbers (displaying the current line number in green)
while (block.isValid() && top <= event->rect().bottom()) {
if (block.isVisible() && bottom >= event->rect().top()) {
QString number = QString::number(blockNumber + 1);
painter.setPen(QColor(120, 120, 120));
painter.setPen((this->textCursor().blockNumber() == blockNumber) ? col_1 : col_0);
painter.drawText(-5, top,
lineNumberArea->width(), fontMetrics().height(),
Qt::AlignRight, number);
}
block = block.next();
top = bottom;
bottom = top + (int) this->document()->documentLayout()->blockBoundingRect(block).height();
++blockNumber;
}
}
Hope this can help...
Here's the equivalent tutorial in C++:
Qt4: http://doc.qt.io/qt-4.8/qt-widgets-codeeditor-example.html
Qt5: http://doc.qt.io/qt-5/qtwidgets-widgets-codeeditor-example.html
I was looking for a line numbers painting solution for QTextEdit (not QPlainTextEdit), and I found the previous answer with sample code for QTextEdit is useful, but when we set custom line height in QTextEdit's associated SyntaxHighligher, it doesn't work reliably.
To fix that problem, I figured out a simpler way to determine the y coordinate of each block rect by using this code:
// Here is the key to obtain the y coordinate of the block start
QTextCursor blockCursor(block);
QRect blockCursorRect = this->cursorRect(blockCursor);
And then we can draw line number of each block via:
painter.drawText(-5, blockCursorRect.y() /* + a little offset to align */,
m_lineNumberArea->width(), fixedLineHeight,
Qt::AlignRight, number);
This seems much simpler and more reliable than calculating the block y coordinate by adding previous block height up.
Hope it helps for someone who is looking for similar solutions.
Here is an easy example to determine the text positions for drawing line numbers for the QTextEdit derived classes.
code_browser::code_browser(QWidget *parent): QTextBrowser(parent)
, m_line_number_area(new LineNumberArea(this))
, m_show_line_numbers(false)
{
connect(document(), SIGNAL(blockCountChanged(int)), this, SLOT(updateLineNumberAreaWidth(int)));
connect(verticalScrollBar(), SIGNAL(valueChanged(int)), this, SLOT(vertical_scroll_value(int)));
connect(this, SIGNAL(updateRequest(QRect,int)), this, SLOT(updateLineNumberArea(QRect,int)));
connect(this, SIGNAL(cursorPositionChanged()), this, SLOT(highlightCurrentLine()));
updateLineNumberAreaWidth(blockCount());
highlightCurrentLine();
}
void code_browser::vertical_scroll_value(int value)
{
Q_EMIT updateRequest(contentsRect(), value);
}
void code_browser::lineNumberAreaPaintEvent(QPaintEvent *event)
{
QPainter painter(m_line_number_area);
painter.fillRect(event->rect(), Qt::lightGray);
int top = 0;
QTextBlock block = firstVisibleBlock(top);
int blockNumber = block.blockNumber();
QRectF block_rect = blockBoundingRect(block);
int bottom = top + qRound(block_rect.height());
while (block.isValid() && top <= event->rect().bottom())
{
if (block.isVisible() && bottom >= event->rect().top())
{
QString number = QString::number(blockNumber + 1);
painter.setPen(Qt::black);
painter.drawText(0, top, m_line_number_area->width(), fontMetrics().height(), Qt::AlignRight, number);
}
block = block.next();
top = bottom;
block_rect = blockBoundingRect(block);
bottom = top + qRound(block_rect.height());
++blockNumber;
}
}
int code_browser::blockCount() const
{
return document()->blockCount();
}
QTextBlock code_browser::firstVisibleBlock(int& diff)
{
QPointF content_offset = contentOffset();
for (QTextBlock block = document()->begin(); block.isValid(); block = block.next())
{
if (block.isVisible())
{
QRectF block_rect = blockBoundingRect(block);
if (block_rect.top() >= content_offset.y())
{
diff = block_rect.top() - content_offset.y();
return block;
}
}
}
diff = -1;
return document()->begin();
}
QRectF code_browser::blockBoundingRect(const QTextBlock &block) const
{
QAbstractTextDocumentLayout *layout = document()->documentLayout();
return layout->blockBoundingRect(block);
}
QPointF code_browser::contentOffset() const
{
return QPointF(horizontalScrollBar()->value(), verticalScrollBar()->value());
}
Python's adaptation:
QTextEditHighlighter.py
from PySide6.QtCore import QRectF, QRect, Qt
from PySide6.QtGui import QResizeEvent, QTextCursor, QPaintEvent, QPainter, QColor
from PySide6.QtWidgets import QTextEdit, QApplication
from LineNumberArea import LineNumberArea
class QTextEditHighlighter(QTextEdit):
def __init__(self):
# Line numbers
QTextEdit.__init__(self)
self.lineNumberArea = LineNumberArea(self)
self.document().blockCountChanged.connect(self.updateLineNumberAreaWidth)
self.verticalScrollBar().valueChanged.connect(self.updateLineNumberArea)
self.textChanged.connect(self.updateLineNumberArea)
self.cursorPositionChanged.connect(self.updateLineNumberArea)
self.updateLineNumberAreaWidth(0)
def lineNumberAreaWidth(self):
digits = 1
m = max(1, self.document().blockCount())
while m >= 10:
m /= 10
digits += 1
space = 13 + self.fontMetrics().horizontalAdvance('9') * digits
return space
def updateLineNumberAreaWidth(self, newBlockCount: int):
self.setViewportMargins(self.lineNumberAreaWidth(), 0, 0, 0)
def updateLineNumberAreaRect(self, rect_f: QRectF):
self.updateLineNumberArea()
def updateLineNumberAreaInt(self, slider_pos: int):
self.updateLineNumberArea()
def updateLineNumberArea(self):
"""
When the signal is emitted, the sliderPosition has been adjusted according to the action,
but the value has not yet been propagated (meaning the valueChanged() signal was not yet emitted),
and the visual display has not been updated. In slots connected to self signal you can thus safely
adjust any action by calling setSliderPosition() yourself, based on both the action and the
slider's value.
"""
# Make sure the sliderPosition triggers one last time the valueChanged() signal with the actual value !!!!
self.verticalScrollBar().setSliderPosition(self.verticalScrollBar().sliderPosition())
# Since "QTextEdit" does not have an "updateRequest(...)" signal, we chose
# to grab the imformations from "sliderPosition()" and "contentsRect()".
# See the necessary connections used (Class constructor implementation part).
rect = self.contentsRect()
self.lineNumberArea.update(0, rect.y(), self.lineNumberArea.width(), rect.height())
self.updateLineNumberAreaWidth(0)
dy = self.verticalScrollBar().sliderPosition()
if dy > -1:
self.lineNumberArea.scroll(0, dy)
# Addjust slider to alway see the number of the currently being edited line...
first_block_id = self.getFirstVisibleBlockId()
if first_block_id == 0 or self.textCursor().block().blockNumber() == first_block_id-1:
self.verticalScrollBar().setSliderPosition(dy-self.document().documentMargin())
# # Snap to first line (TODO...)
# if first_block_id > 0:
# slider_pos = self.verticalScrollBar().sliderPosition()
# prev_block_height = (int) self.document().documentLayout().blockBoundingRect(self.document().findBlockByNumber(first_block_id-1)).height()
# if (dy <= self.document().documentMargin() + prev_block_height)
# self.verticalScrollBar().setSliderPosition(slider_pos - (self.document().documentMargin() + prev_block_height))
def resizeEvent(self, event: QResizeEvent):
QTextEdit.resizeEvent(self, event)
cr = self.contentsRect()
self.lineNumberArea.setGeometry(QRect(cr.left(), cr.top(), self.lineNumberAreaWidth(), cr.height()))
def getFirstVisibleBlockId(self) -> int:
# Detect the first block for which bounding rect - once translated
# in absolute coordinated - is contained by the editor's text area
# Costly way of doing but since "blockBoundingGeometry(...)" doesn't
# exists for "QTextEdit"...
curs = QTextCursor(self.document())
curs.movePosition(QTextCursor.Start)
for i in range(self.document().blockCount()):
block = curs.block()
r1 = self.viewport().geometry()
r2 = self.document().documentLayout().blockBoundingRect(block).translated(
self.viewport().geometry().x(), self.viewport().geometry().y() - (
self.verticalScrollBar().sliderPosition()
)).toRect()
if r1.contains(r2, True):
return i
curs.movePosition(QTextCursor.NextBlock)
return 0
def lineNumberAreaPaintEvent(self, event: QPaintEvent):
self.verticalScrollBar().setSliderPosition(self.verticalScrollBar().sliderPosition())
painter = QPainter(self.lineNumberArea)
painter.fillRect(event.rect(), Qt.lightGray)
blockNumber = self.getFirstVisibleBlockId()
block = self.document().findBlockByNumber(blockNumber)
if blockNumber > 0:
prev_block = self.document().findBlockByNumber(blockNumber - 1)
else:
prev_block = block
if blockNumber > 0:
translate_y = -self.verticalScrollBar().sliderPosition()
else:
translate_y = 0
top = self.viewport().geometry().top()
# Adjust text position according to the previous "non entirely visible" block
# if applicable. Also takes in consideration the document's margin offset.
if blockNumber == 0:
# Simply adjust to document's margin
additional_margin = self.document().documentMargin() -1 - self.verticalScrollBar().sliderPosition()
else:
# Getting the height of the visible part of the previous "non entirely visible" block
additional_margin = self.document().documentLayout().blockBoundingRect(prev_block) \
.translated(0, translate_y).intersect(self.viewport().geometry()).height()
# Shift the starting point
top += additional_margin
bottom = top + int(self.document().documentLayout().blockBoundingRect(block).height())
col_1 = QColor(90, 255, 30) # Current line (custom green)
col_0 = QColor(120, 120, 120) # Other lines (custom darkgrey)
# Draw the numbers (displaying the current line number in green)
while block.isValid() and top <= event.rect().bottom():
if block.isVisible() and bottom >= event.rect().top():
number = f"{blockNumber + 1}"
painter.setPen(QColor(120, 120, 120))
if self.textCursor().blockNumber() == blockNumber:
painter.setPen(col_1)
else:
painter.setPen(col_0)
painter.drawText(-5, top,
self.lineNumberArea.width(), self.fontMetrics().height(),
Qt.AlignRight, number)
block = block.next()
top = bottom
bottom = top + int(self.document().documentLayout().blockBoundingRect(block).height())
blockNumber += 1
if __name__ == '__main__':
app = QApplication([])
w = QTextEditHighlighter()
w.show()
app.exec()
LineNumberArea.py
from PySide6.QtCore import QSize
from PySide6.QtWidgets import QWidget
class LineNumberArea(QWidget):
def __init__(self, editor):
QWidget.__init__(self, editor)
self.codeEditor = editor
def sizeHint(self) -> QSize:
return QSize(self.codeEditor.lineNumberAreaWidth(), 0)
def paintEvent(self, event):
self.codeEditor.lineNumberAreaPaintEvent(event)