How to get the QLayouts to expand properly? - c++

My structure is as follows:
QWidget
-QHBoxLayout
-QLabel
-QVBoxLayout
-QLabel
-QWebView
I want the HBoxLayout to fill the width however large the container may be but go no more or less. However, I want the QVBoxLayout to expand to accommodate the size of its contents in the vertical direction.
+-------------+------------------------------+
| FixedTitle: | Expanding to Width Title +
| |------------------------------+
| | +
| | this is a test which wraps to+
| | the next line +
| | +
| | +
| | +
| | bla bla bla +
| | +
| | +
| | +
| | there are no vertical scroll +
| | bars here +
+-------------+------------------------------+
In this example, FixedTitle's width is however big it needs to be, but does not resize ever. Expanding to Width Title fills up the remaining horizontal space.
So far, I have:
this->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed);
QHBoxLayout *layout = new QHBoxLayout;
this->setLayout(layout);
layout->addWidget(new QLabel(QString("FixedTitle")), 0, Qt::AlignTop);
QVBoxLayout *v_layout = new QVBoxLayout;
v_layout->setSizeConstraint(QLayout::SetNoConstraint);
layout->addLayout(v_layout);
v_layout ->addWidget(new QLabel(QString("Expanding to Width Title")), 1, Qt::AlignTop | Qt::AlignLeft);
QWebView *view = new QWebView();
QTextEdit text;
text.setPlainText(QSString("\nthis is a test which wraps to the next line\n\n\nbla bla bla\n\n\nthere are no vertical scroll bars here"));
view->setHtml(text.toHtml());
int width = view->page()->mainFrame()->contentsSize().width();
int height = view->page()->mainFrame()->contentsSize().height();
view->page()->setViewportSize(QSize(width, height));
view->resize(width, height);
view->setFixedSize(width, height);
v_layout->addWidget(view);
There are two problems with this: 1. It ignores the width of the container and 2. It still doesnt get the height of the QWebView correct.
How do I fix this?

This is my take on an answer... and forgive me but its written in PyQt.
I feel that your shouldn't be thinking so much about resizing the containing widget to the contents of the QWebView, but rather just have the size policy set to expanding, turn off the scrollbars, and defer sizing to whatever layout this container is added to. It makes no sense to try and manually resize it.
from PyQt4 import QtCore, QtGui, QtWebKit
class WebWidget(QtGui.QWidget):
def __init__(self):
super(WebWidget, self).__init__()
layout = QtGui.QHBoxLayout(self)
title = QtGui.QLabel("FixedTitle:")
# title.setText("A much larger fixed title")
title.setSizePolicy(
QtGui.QSizePolicy.Preferred,
QtGui.QSizePolicy.Fixed)
layout.addWidget(title, 0, QtCore.Qt.AlignTop)
v_layout = QtGui.QVBoxLayout()
layout.addLayout(v_layout)
expandingTitle = QtGui.QLabel("Expanding to Width Title")
expandingTitle.setSizePolicy(
QtGui.QSizePolicy.Expanding,
QtGui.QSizePolicy.Fixed)
v_layout.addWidget(expandingTitle)
text = QtGui.QTextEdit()
view = QtWebKit.QWebView()
view.setSizePolicy(
QtGui.QSizePolicy.Expanding,
QtGui.QSizePolicy.Expanding)
view.page().mainFrame().setScrollBarPolicy(
QtCore.Qt.Vertical,
QtCore.Qt.ScrollBarAlwaysOff )
view.page().mainFrame().setScrollBarPolicy(
QtCore.Qt.Horizontal,
QtCore.Qt.ScrollBarAlwaysOff )
v_layout.addWidget(view, 1)
text.setPlainText("""
this is a test which wraps to the next line\n\n\n
bla bla bla\n\n\nthere are no vertical scroll bars here
""")
view.setHtml(text.toHtml())
v_layout.addStretch()
self.view = view
view.page().mainFrame().contentsSizeChanged.connect(self.updateWebSize)
def updateWebSize(self, size=None):
if size is None:
size = self.view.page().mainFrame().contentsSize()
self.view.setFixedSize(size)
def resizeEvent(self, event):
super(WebWidget, self).resizeEvent(event)
self.updateWebSize()
if __name__ == "__main__":
app = QtGui.QApplication([])
w = QtGui.QScrollArea()
w.resize(800,600)
web = WebWidget()
w.setWidget(web)
w.setWidgetResizable(True)
w.show()
app.exec_()
The left title is set to preferred width and fixed height, so that it gets the width it needs but doesn't grow vertically.
The expanding title gets an expanding width policy and a fixed height.
The QWebView gets expanding policies in both directions, and its scrollbars turned off.
And for an example. I just created a QScrollArea and set the WebWidget into it, so that you can see the parent layout will allow the Web view to grow as big as it wants to, but will handle overflow with scrollbars.

Related

How to properly use QLayouts (hbox, vbox, etc)?

I am building a custom widget based on QGraphicsWidget
Inside the widget, I need various other widgets in line with the size of the window. For this I am using
Layout = new QGraphicsLinearLayout();
Layout->setOrientation(Qt::Horizontal);
setLayout(Layout);
So I have a layout, and on each side of the layout I have a widget. So a widget on the left side, and a widget on the right side.
QGraphicsLinearLayout() below
500 px wide x
|--------------------------------|
| |
|left widget right widget| 100 px tall y
|--------------------------------|
I want the behaviour that when the window gets bigger, the widgets stay within x pixels of the edge. So if the window were to be resized to 10,000 pixels wide, left widget would always be 1 pixel from the left edge and right widget would always be 1 pixel from the right edge.
10000 px wide x
|---------------------------------------------------|
| |
|left widget right widget| 100 px tall y
|---------------------------------------------------|
The current behaviour is that left widget will stay in place and never move, and right widget will move away from the right edge.
To achieve this behaviour, I have tried the following:
Layout->setAlignment(leftWidget, Qt::AlignLeft);
That does absolutely nothing.
I also tried
Layout->addItem(leftWidget);
Layout->setStretchFactor(leftWidget, 0);
Layout->addItem(rightWidget);
Which gives the desired effect but overlaps the rightWidget with the border like so
500 px wide x
|--------------------------------|
| |
|left widget right widget 100 px tall y
|--------------------------------|
So how can I get my desired behaviour? QLayouts seem very confusing and the API's misleading thus far.
Solution
Use QGraphicsLinearLayout::addStretch between the left and right widgets in order to keep them to the sides and QGraphicsLayout::setContentsMargins to add a space between the widgets and the edges:
auto *l = new QGraphicsLinearLayout();
l->addItem(leftProxy);
l->addStretch();
l->addItem(rightProxy);
l->setContentsMargins(25, 1, 1, 1);
l->setSpacing(1);
Example
I have prepared a minimal example for you of how to use the proposed solution:
MainWindow::MainWindow(QWidget *parent) :
QMainWindow(parent)
{
auto *view = new QGraphicsView(this);
auto *widget = new QWidget();
auto *leftWidget = new QPushButton(tr("Left"));
auto *rightWidget = new QPushButton(tr("Right"));
auto *leftProxy = new QGraphicsProxyWidget();
auto *rightProxy = new QGraphicsProxyWidget();
auto *l = new QGraphicsLinearLayout();
auto *sizeGrip = new QSizeGrip(widget);
leftWidget->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Expanding);
rightWidget->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Expanding);
widget->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum);
widget->setMinimumSize(sizeGrip->sizeHint().width()
+ leftWidget->sizeHint().width()
+ rightWidget->sizeHint().width(),
leftWidget->sizeHint().height());
leftProxy->setWidget(leftWidget);
rightProxy->setWidget(rightWidget);
l->addItem(leftProxy);
l->addStretch();
l->addItem(rightProxy);
l->setContentsMargins(25, 1, 1, 1);
l->setSpacing(1);
view->setScene(new QGraphicsScene(this));
view->scene()->addWidget(widget)->setLayout(l);
widget->resize(400, 200);
setCentralWidget(view);
resize(640, 480);
}

Center the Text of QTextEdit horizontally and vertically

I want to center the text of my QTextEdit horizontally and vertically.
I tried this, but it didn't work.
m_myTextEdit = new QTextEdit("text edit", m_ui->centralWidget);
m_myTextEdit->setGeometry(5, 50, 400, 250);
m_myTextEdit->setReadOnly(true);
m_myTextEdit->setAlignment(Qt::AlignCenter);
Is there a opportunity to set it centered with a StyleSheet?
If you only need one line, you can use a QLineEdit instead:
QLineEdit* lineEdit = new QLineEdit("centered text");
lineEdit->setAlignment(Qt::AlignCenter);
If you only want to display the text, not allow the user to edit it, you can use a QLabel instead. This works with line wrapping, too:
QLabel* label = new QLabel("centered text");
lineEdit->setWordWrap(true);
lineEdit->setAlignment(Qt::AlignCenter);
Here is code from PySide that I use for this, for those that need to use QTextEdit rather than QLineEdit. It is based on my answer here:
https://stackoverflow.com/a/34569735/1886357
Here is the code, but the explanation is at the link:
import sys
from PySide import QtGui, QtCore
class TextLineEdit(QtGui.QTextEdit):
topMarginCorrection = -4 #not sure why needed
returnPressed = QtCore.Signal()
def __init__(self, fontSize = 10, verticalMargin = 2, parent = None):
QtGui.QTextEdit.__init__(self, parent)
self.setAttribute(QtCore.Qt.WA_DeleteOnClose)
self.setLineWrapMode(QtGui.QTextEdit.NoWrap)
self.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff)
self.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff)
self.setFontPointSize(fontSize)
self.setViewportMargins(0, self.topMarginCorrection , 0, 0) #left, top, right, bottom
#Set up document with appropriate margins and font
document = QtGui.QTextDocument()
currentFont = self.currentFont()
currentFont.setPointSize(fontSize)
document.setDefaultFont(currentFont)
document.setDocumentMargin(verticalMargin)
self.setFixedHeight(document.size().height())
self.setDocument(document)
def keyPressEvent(self, event):
'''stops retun from returning newline'''
if event.key() in (QtCore.Qt.Key_Enter, QtCore.Qt.Key_Return):
self.returnPressed.emit()
event.accept()
else:
QtGui.QTextEdit.keyPressEvent(self, event)
def main():
app = QtGui.QApplication(sys.argv)
myLine = TextLineEdit(fontSize = 15, verticalMargin = 8)
myLine.show()
sys.exit(app.exec_())
if __name__ == "__main__":
main()

QScrollBar + QScrollAera in QTabWidget

My question is simple : how can I set a QScrollBar in my QScrollArea. I have tested a lot of things but nothing work ... Maybe it's a problem to set a QScrollArea in a QTabWidget ? Here is the code :
void GamesWindow::createTabSucces()
{
std::string nameImg;
_succesPage = new QWidget(_tab);
_tab->addTab(_succesPage, " Succes ");
scrollArea = new QScrollArea(_succesPage);
scrollArea->setBackgroundRole(QPalette::Dark);
scrollArea->setFixedSize(500,500);
/* Integration of QScrollBar */
for (int i = 0; i < 45; i++)
{
nameImg = "img/allAchiv/";
nameImg += intToString(i + 1);
nameImg += ".jpg";
_imgSucc[i] = new QLabel(scrollArea);
_imgSucc[i]->setPixmap(QPixmap(QString(nameImg.c_str())));
_imgSucc[i]->setGeometry((14 + (85 * (i % 5))), 46 + ((i / 5) * 85), 60, 60);
}
}
In fact, I add pictures in a tab where is created a QScrollArea (like 8-9 lines of pictures) but only 5 are visible, the others are hide, because they are at the bottom, out of the defined zone.
Any idea ? Thank's.
You must:
add a layout to your page (QVBoxLayout)
add the scroll area to that page layout
add a layout to the viewport() widget in the scroll area (QVBoxLayout)
add your QLabels to that viewport layout
This way you won't need to call setGeometry on each label
You need to set a widget to your scroll area and add your pictures to the widget's layout. Check QScrollArea::setWidget(QWidget *widget)
I'm almost sure that you can't add scroll to a tab widget but my idea is just to try make more tabs that can be shown and see if the slider comes up by default.

QPropertyAnimation: Immedately jump to end of animation?

I use a QPropertyAnimation to animate user input to navigate in a widget. Namely, I use it to smooth zooming using the mouse wheel.
Currently, when the user gives a new input (rotates the mouse wheel), the current animation gets canceled and a new animation starts, beginning with the current value of my zoom property.
For example, if a zoom-in operation scales the view by factor 2, we can imagine the following scenarios:
User input | Zoom before the animation | Animation's end value
----------------------+-----------------------------+--------------------------
Mouse wheel up | 100 % | 200 %
(wait) | |
Mouse wheel up | 200 % | 400 %
(wait) | |
Mouse wheel up | 400 % | 800 %
But if the user doesn't wait for the animation to be finished:
User input | Zoom before the animation | Animation's end value
----------------------+-----------------------------+--------------------------
Mouse wheel up | 100 % | 200 %
Mouse wheel up | 110 % | 220 %
Mouse wheel up | 120 % | 240 %
What I want (again, the user doesn't wait):
User input | Zoom before the animation | Animation's end value
----------------------+-----------------------------+--------------------------
Mouse wheel up | 100 % | 200 %
Mouse wheel up | immediately jump to 200 % | 400 %
Mouse wheel up | immediately jump to 400 % | 800 %
I hate applications where there can't be made a further user input until the end of an animation, and therefore I mostly hate smooth animations. So what I want is: When the user gives another user input and there is currently an animation running, "skip" this animation by jumping to the end of this animation.
The simpliest solution would be to just use the end value of the previous animation for the start value of the new one, but I want to abstract the "type" of animation which is currently performed; it doesn't have to be a zooming animation, but may also be a scrolling, panning, whatever animation.
So is there a possibility, without further knowledge of the animation (I only have a pointer to a QPropertyAnimation), to make it immediately jump to the end?
Currently, my code looks like this:
class View : ...
{
// Property I want to animate using the mouse wheel
Q_PROPERTY(qreal scale READ currentScale WRITE setScale)
...
private:
// Pointer to the animation, which can also be another animation!
QPropertyAnimation *viewAnimation;
}
void View::wheelEvent(QWheelEvent *e)
{
qreal scaleDelta = pow(1.002, e->delta());
qreal newScale = currentScale() * scaleDelta;
if(viewAnimation)
{
// --- CODE SHOULD BE INSERTED HERE ---
// --- Jump to end of viewAnimation ---
// --- rather than canceling it ---
cancelViewAnimation();
}
viewAnimation = new QPropertyAnimation(this, "scale", this);
viewAnimation->setStartValue(currentScale());
viewAnimation->setEndValue(newScale);
viewAnimation->setDuration(100);
connect(viewAnimation, SIGNAL(finished()), SLOT(cancelViewAnimation()));
viewAnimation->start();
}
void View::cancelViewAnimation()
{
if(viewAnimation)
{
viewAnimation->stop();
viewAnimation->deleteLater();
viewAnimation = NULL;
}
}
I think this will do it:
if(viewAnimation)
{
// jump to end of animation, without any further knowledge of it:
viewAnimation->targetObject()->setProperty(viewAnimation->propertyName(),
viewAnimation->endValue());
cancelViewAnimation();
}

Minimum size/width of a QPushButton that is created from code

I created 2 rows of push buttons, each row is inside a QHBoxLayout.
I create the buttons in the code:
static const char* buttonText = "23456789TJQKA";
for (int ii = 0; buttonText[ii]; ii++)
{
QPushButton* pushButton = new QPushButton(this);
pushButton->setText(QString(buttonText[ii]));
ui->horizontalLayout_1->addWidget(pushButton);
}
for (int ii = 0; buttonText[ii]; ii++)
{
QPushButton* pushButton = new QPushButton(this);
pushButton->setText(QString(buttonText[ii]));
ui->horizontalLayout_2->addWidget(pushButton);
}
The problem is that they can't shrink (when the user resizes the dialog) beyond that size, even though their text would fit in a much smaller width. If I create the buttons manually in the resource editor instead of in the code, they can have smaller width than that.
This happens because the minimumSizeHint of the QPushButton does not allow the QLayout to resize it :
The default implementation of minimumSizeHint() returns an invalid
size if there is no layout for this widget, and returns the layout's
minimum size otherwise. Most built-in widgets reimplement
minimumSizeHint().
QLayout will never resize a widget to a size smaller than the minimum
size hint unless minimumSize() is set or the size policy is set to
QSizePolicy::Ignore. If minimumSize() is set, the minimum size hint
will be ignored.
The simple solution is to set the minimum width explicitly:
static const char* buttonText = "23456789TJQKA";
for (int ii = 0; buttonText[ii]; ii++)
{
QPushButton* pushButton = new QPushButton(this);
pushButton->setMinimumWidth(5);
pushButton->setText(QString(buttonText[ii]));
ui->horizontalLayout_1->addWidget(pushButton);
}
for (int ii = 0; buttonText[ii]; ii++)
{
QPushButton* pushButton = new QPushButton(this);
pushButton->setMinimumWidth(5);
pushButton->setText(QString(buttonText[ii]));
ui->horizontalLayout_2->addWidget(pushButton);
}
As pnezis wrote, you probably want to override the default minimum size calculated by the button. Here's a way you can do it while avoiding to choose an arbitrary size that might not work when conditions vary (different font or font size, UI style, etc):
QWidget* parent = /* some widget */
auto button = new QPushButton(QLatin1String("X"), parent);
auto textSize = button->fontMetrics().size(Qt::TextShowMnemonic, button->text());
QStyleOptionButton opt;
opt.initFrom(button);
opt.rect.setSize(textSize);
button->setMinimumSize(
button->style()->sizeFromContents(QStyle::CT_PushButton,
&opt,
textSize,
button));
The above was adapted and simplified from QPushButton's own code. You may want to look at the source of QPushButton::sizeHint for all the details.
setMaximumWidth works for me.
sample code is in pyqt, but it should translate directly to C++ without any problems.
from PyQt4 import QtGui
class Window(QtGui.QWidget):
def __init__(self):
super(Window, self).__init__()
layout = QtGui.QHBoxLayout()
texts = [":)",
"&Short",
"&Longer",
"&Different && text",
"More && text",
"Even longer button text", ]
for text in texts:
btn = QtGui.QPushButton(text)
double = text.count('&&')
text = text.replace('&', '') + ('&' * double)
width = btn.fontMetrics().boundingRect(text).width() + 7
btn.setMaximumWidth(width)
layout.addWidget(btn)
self.setLayout(layout)
if __name__ == '__main__':
import sys
app = QtGui.QApplication(sys.argv)
mainWin = Window()
mainWin.show()
sys.exit(app.exec_())