How to make QTextBrowser to have biggest size? - c++

I have QScrollArea which has QVBoxLayout where I place widgets and one of them is QTextBrowser and I want to make QTextBrowser to have size of its content and to remove its scrollbars.
I inherited QTextBrowser, changed sizePolicy, hid scrollbars and overrided sizeHint() like this:
TextBrowserWidget::TextBrowserWidget(QWidget* parent)
: QTextBrowser(parent)
{
setSizePolicy(
QSizePolicy::Preferred,
QSizePolicy::Minimum);
setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
}
QSize TextBrowserWidget::sizeHint() const
{
if (document()) {
auto docSize = document()->size();
return QSize(docSize.width(), docSize.height() + 10);
} else
return QTextBrowser::sizeHint();
}
But this works with delay, at first widget becomes small and after 1-2 seconds it becomes bigger.
I am not sure if it's good solution. What is the right way to do it?

TextBrowserWidget::TextBrowserWidget(QWidget* parent)
: QTextBrowser(parent)
{
setSizePolicy(
QSizePolicy::Minimum,
QSizePolicy::MinimumExpanding);
connect(
this, &TextBrowserWidget::textChanged,
this, &TextBrowserWidget::updateSize);
setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
}
QSize TextBrowserWidget::sizeHint()
{
updateSize();
return QTextBrowser::sizeHint();
}
void TextBrowserWidget::updateSize()
{
document()->setTextWidth(viewport()->size().width());
auto docSize = document()->size().toSize();
setMinimumWidth(docSize.width());
setMinimumHeight(docSize.height() + 10);
}

Related

How to modify a widget parent and after restore it?

I'm trying to animate a widget that is inside a grid and a container, whenever hovered it is animated being expanded horizontally.
As the widget is inside of a container it gets cut because it cant expand above the parent widget area.
What I thought: when the widget is hovered, the code changes her parent to the centralWidget then it can expand above other widgets, and when it's no longer hovered restore her 'original' parent.
An example:
As it could be seen in the gif below, before hovering:
The widget is 'attached' to the GridLayout and inside of the Widget container.
After the mouse leaves:
It's no longer attached to the GridLayout / Widget.
I tried to 'restore' the widget to her parent with:
this->setParent(widgetParent);
But it didn't work or I may be doing something wrong.
I could not think of another way of doing all this (expanding a widget inside of a container above her parent limits), any different suggestion is welcome.
class AnimatedButton : public QPushButton
{
Q_OBJECT
public:
QPropertyAnimation* anim;
struct WidgetPos { int x = 0; int y = 0; int w = 0; int h = 0; };
WidgetPos wp;
QWidget* widgetParent;
QWidget* centralwidget;
void CreateAnimation(QByteArray propertyName)
{
if (propertyName == "geometry")
{
anim = new QPropertyAnimation(this, propertyName);
this->anim->setDuration(100);
this->anim->setEasingCurve(QEasingCurve::Linear);
this->wp.x = this->x();
this->wp.y = this->y();
this->wp.w = this->width();
this->wp.h = this->height();
}
}
AnimatedButton(QWidget* parent = 0) : QPushButton(parent)
{
widgetParent = parent;
this->installEventFilter(this);
}
bool eventFilter(QObject* obj, QEvent* event)
{
if (event->type() == QEvent::Enter)
{
if (!this->wp.x)
this->CreateAnimation("geometry");
// setParent to the central widget so the widget can resize above her default parent area.
this->setParent(this->centralwidget);
this->show();
this->anim->stop();
this->anim->setStartValue(
QRect(this->x(), this->y(), this->width(), this->height()));
this->anim->setEndValue(
QRect(this->x(), this->y(), (this->wp.w + 200) - this->width(), this->height()));
this->anim->start();
}
else if (event->type() == QEvent::Leave)
{
this->anim->stop();
this->anim->setStartValue(
QRect(this->x(), this->y(), (this->wp.w + 200) - this->width(), this->height()));
this->anim->setEndValue(
QRect(this->wp.x, this->wp.y, this->wp.w, this->wp.h));
this->anim->start();
// Restore default parent (--------NOT WORKING---------)
this->setParent(widgetParent);
this->show();
}
return QWidget::eventFilter(obj, event);
}
};
QtWidgetsApplication::QtWidgetsApplication(QWidget *parent)
: QMainWindow(parent)
{
ui.setupUi(this);
// Pass the central widget to the AnimatedButton class.
ui.pushButton->centralwidget = ui.centralWidget;
return;
}

QPainting QPixmap using clipping to gain performance

I'm trying to create a simple Image Viewer in Qt with zooming supported.
To display an image file I load it into a QImage and create a QPixmap.
class NN: public QWidget{
Q_OBJECT
Q_DISABLE_COPY(NN)
public:
NN(QWidget* parent = nullptr) : QWidget(parent){
}
const QPixmap& pixmap() const
{
return m_pixmap;
}
void setPixmap(const QPixmap& px)
{
m_pixmap = px;
update();
}
protected:
void paintEvent(QPaintEvent*)
{
QPainter painter(this);
painter.setRenderHint(QPainter::Antialiasing, false);
style()->drawItemPixmap(&painter, rect(), Qt::AlignCenter, m_pixmap.scaled(rect().size()));
}
private:
QPixmap m_pixmap;
};
(This Widget is part of a ScrollArea)
This works fine, but when I try to load large images and zoom in, the performance starts to decrease (lag).
I thought of applying a clip to the drawItemPixmap() method, but I am not quite sure how and whether it would help increasing the performance.
My question is whether the clipping idea would work, and if so how. If not, maybe there is another way to gain performance?
When m_pixmap and/or rect() are very large, the bulk of your slowdown is likely coming from here:
m_pixmap.scaled(rect().size())
Here you are you are asking Qt to create a new QPixmap object the same size as rect(), which is a potentially very expensive operation; and passing that QPixmap object into the call to drawItemPixmap() which will draw just a small portion of the pixmap, after which the QPixmap object will get discarded, and the whole procedure will have to be done again the next time you want to redraw your object.
Needless to say, that can be very inefficient.
A more efficient approach would be to call QPainter::drawPixmap(const QRect & target, const Pixmap & pixmap, const QRect & source), like this:
painter.drawPixmap(rect(), m_pixmap, srcRect);
... and drawPixmap() will draw a scaled pixmap of size rect() (i.e. just the size of your widget) by rescaling the content of m_pixmap that is inside srcRect; much more efficient than rescaling the entire m_pixmap image.
You'll need to calculate the correct left/top/width/height values for srcRect, of course, but that should be straightforward with a little bit of algebra. (Basically just figure out what portion of the pixmap should currently be visible based on your widget's current zoom/pan state)
As I pointed out in this answer, it is better to use QGraphicsView for image scaling so I will translate the code to C++:
#include <QtWidgets>
class ImageViewer: public QGraphicsView{
public:
ImageViewer(QWidget *parent=nullptr):QGraphicsView(parent){
setRenderHints(QPainter::Antialiasing | QPainter::SmoothPixmapTransform);
// setAlignment(Qt::AlignLeft | Qt::AlignTop);
setAlignment(Qt::AlignCenter);
setBackgroundRole(QPalette::Dark);
QGraphicsScene *scene = new QGraphicsScene(this);
setScene(scene);
pixmapItem = new QGraphicsPixmapItem;
scene->addItem(pixmapItem);
}
bool setPixmap(const QPixmap & pixmap){
if(pixmap.isNull())
return false;
pixmapItem->setPixmap(pixmap);
return true;
}
void zoom(qreal f){
scale(f, f);
}
void zoomIn(){
zoom(factor);
}
void zoomOut(){
zoom(1.0 / factor);
}
void resetZoom(){
resetTransform();
}
void fitToWindow(){
fitInView(sceneRect(), Qt::KeepAspectRatio);
}
private:
qreal factor = 2.0;
QGraphicsPixmapItem * pixmapItem;
};
class MainWindow: public QMainWindow{
Q_OBJECT
public:
MainWindow(QWidget *parent=nullptr):QMainWindow(parent),
view(new ImageViewer)
{
setCentralWidget(view);
createActions();
createMenus();
resize(640, 480);
}
private Q_SLOTS:
void open(){
QStringList l;
for(const QByteArray & ba: QImageReader::supportedImageFormats()){
l << ("*." + QString::fromUtf8(ba));
}
QString filter = QString("Image Files(%1)").arg(l.join(" "));
QString fileName = QFileDialog::getOpenFileName(
this,
tr("Open Image"),
QDir::currentPath(),
filter
);
if(!fileMenu->isEmpty()){
bool loaded = view->setPixmap(QPixmap(fileName));
fitToWindowAct->setEnabled(loaded);
updateActions();
}
}
void fitToWindow(){
if(fitToWindowAct->isChecked())
view->fitToWindow();
else
view->resetZoom();
updateActions();
}
void about(){
QMessageBox::about(this, "ImageViewer", "ImageViewer");
}
private:
void createActions(){
openAct = new QAction("&Open...", this);
openAct->setShortcut(QKeySequence("Ctrl+O"));
connect(openAct, &QAction::triggered, this, &MainWindow::open);
exitAct = new QAction("E&xit", this);
exitAct->setShortcut(QKeySequence("Ctrl+Q"));
connect(exitAct, &QAction::triggered, this, &MainWindow::close);
zoomInAct = new QAction(tr("Zoom &In (25%)"), this);
zoomInAct->setShortcut(QKeySequence("Ctrl++"));
zoomInAct->setEnabled(false);
connect(zoomInAct, &QAction::triggered, view, &ImageViewer::zoomIn);
zoomOutAct = new QAction(tr("Zoom &Out (25%)"), this);
zoomOutAct->setShortcut(QKeySequence("Ctrl+-"));
zoomOutAct->setEnabled(false);
connect(zoomOutAct, &QAction::triggered, view, &ImageViewer::zoomOut);
normalSizeAct = new QAction(tr("&Normal Size"), this);
normalSizeAct->setShortcut(QKeySequence("Ctrl+S"));
normalSizeAct->setEnabled(false);
connect(normalSizeAct, &QAction::triggered, view, &ImageViewer::resetZoom);
fitToWindowAct = new QAction(tr("&Fit to Window"), this);
fitToWindowAct->setShortcut(QKeySequence("Ctrl+F"));
fitToWindowAct->setEnabled(false);
fitToWindowAct->setCheckable(true);
connect(fitToWindowAct, &QAction::triggered, this, &MainWindow::fitToWindow);
aboutAct = new QAction(tr("&About"), this);
connect(aboutAct, &QAction::triggered, this, &MainWindow::about);
aboutQtAct = new QAction(tr("About &Qt"), this);
connect(aboutQtAct, &QAction::triggered, qApp, &QApplication::aboutQt);
}
void createMenus(){
fileMenu = new QMenu(tr("&File"), this);
fileMenu->addAction(openAct);
fileMenu->addSeparator();
fileMenu->addAction(exitAct);
viewMenu = new QMenu(tr("&View"), this);
viewMenu->addAction(zoomInAct);
viewMenu->addAction(zoomOutAct);
viewMenu->addAction(normalSizeAct);
viewMenu->addSeparator();
viewMenu->addAction(fitToWindowAct);
helpMenu = new QMenu(tr("&Help"), this);
helpMenu->addAction(aboutAct);
helpMenu->addAction(aboutQtAct);
menuBar()->addMenu(fileMenu);
menuBar()->addMenu(viewMenu);
menuBar()->addMenu(helpMenu);
}
void updateActions(){
zoomInAct->setEnabled(not fitToWindowAct->isChecked());
zoomOutAct->setEnabled(not fitToWindowAct->isChecked());
normalSizeAct->setEnabled(not fitToWindowAct->isChecked());
}
ImageViewer *view;
QAction *openAct;
QAction *exitAct;
QAction *zoomInAct;
QAction *zoomOutAct;
QAction *normalSizeAct;
QAction *fitToWindowAct;
QAction *aboutAct;
QAction *aboutQtAct;
QMenu *fileMenu;
QMenu *viewMenu;
QMenu *helpMenu;
};
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
MainWindow w;
w.show();
return a.exec();
}
#include "main.moc"

Make QWidget transparent

I have a QWidget-based overlay widget which should paint some text and take place over the central widget of my application. The problem is that I can't set background of overlay widget to be transparent. What I already tried:
setPalette(Qt::transparent);
setAttribute( Qt::WA_TranslucentBackground, true );
setAttribute( Qt::WA_OpaquePaintEvent, true );
setAutoFillBackground(false);
setStyleSheet("QWidget{background-color: transparent;}");
setAttribute(Qt::WA_NoSystemBackground);
My best guess to show an overlay widget, is convert the widget to a window, resize it to it's contents and move them to the desired position manually.
MainWindow Example, showing the overlay widget in the center of the video widget:
Mwindow::Mwindow()
{
widget = new Widget(this);
}
void Mwindow::widgetSizeMove()
{
if (widget->width() <= videoWidget->width() && widget->height() <= videoWidget->height())
{
widget->setWindowOpacity(1); // Show the widget
QPoint p = videoWidget->mapToGlobal(videoWidget->pos());
int x = p.x() + (videoWidget->width() - widget->width()) / 2;
int y = p.y() + (videoWidget->height() - widget->height()) / 2;
widget->move(x, y);
widget->raise();
}
else
{
widget->setWindowOpacity(0); // Hide the widget
}
}
bool Mwindow::event(QEvent *event)
{
switch (event->type())
{
case QEvent::Show:
widget->show();
QTimer::singleShot(50, this, SLOT(widgetSizeMove()));
//Wait until the Main Window be shown
break;
case QEvent::WindowActivate:
case QEvent::Resize:
case QEvent::Move:
widgetSizeMove();
break;
default:
break;
}
return QMainWindow::event(event);
}
Widget Example:
Widget::Widget(QWidget *parent) : QWidget(parent)
{
setWindowFlags(Qt::Window | Qt::FramelessWindowHint);
setAttribute(Qt::WA_NoSystemBackground);
setAttribute(Qt::WA_TranslucentBackground);
setAttribute(Qt::WA_PaintOnScreen);
setAttribute(Qt::WA_TransparentForMouseEvents);
}
void Widget::paintEvent(QPaintEvent*)
{
QPainter p(this);
QString text = "Some foo goes here";
QFontMetrics metrics(p.font());
resize(metrics.size(0, text));
p.drawText(rect(), Qt::AlignCenter, text);
}
Example when showing a video with LibVLC:
On Linux works with:
setWindowFlags(Qt::FramelessWindowHint);
setAttribute(Qt::WA_NoSystemBackground);
setAttribute(Qt::WA_TranslucentBackground);
setAttribute(Qt::WA_TransparentForMouseEvents);
The best solution is provided by Gökmen Göksel in one of the comments of this article
setStyleSheet("background-color: rgba(0,0,0,0)");

Qt Display button on top of image

I need to display a button on top of an image. Something similar to
The background is a QPixmap/QImage and the button is a QPushbutton. I need to be able to dynamically change the image - so I am not sure if a stylesheet would be suitable for the task. I tried this, but could not get it to work.
Any solutions?
Subclass QWidget and implement paintEent where you can paint your image at the background. Set and change background image by stylesheet also possible.
Add layout with button to this widget.
There are something like this:
class WidgetWithButton
: public QWidget
{
Q_OBJECT
QImage m_bgImage;
public:
WidgetWithButton(QWidget* aParent)
: QWidget(aParent)
{
QHBoxLayout* l = new QHBoxLayout(this);
QPushButton* myButton = new QPushButton(tr("Close"));
l->addWidget( myButton, 0, Qt::AlignCenter );
}
void setImage(const QImage& aImage)
{
m_image = aImage;
update();
}
protected:
virtual void paintEvent(QPaintEvent* aPainEvent)
{
if (m_image.isValid())
{
QPainter painter(this);
painter.drawImage(rect(), m_image);
}
else
QWidget::paintEvent(aPainEvent);
}
};

QWidget - resize animation

Say I have a QHBoxLayout where there are 2 QTextEdits and between them a button with an arrow to the right. When you click on the button, the right-side QTextEdit gradually closes by moving the left border until it meets the right one. Simultaneously, the right border of the left QTextEdit takes the place which the right QTextEdit released. And after pressing on the button, the state of the system is coming to the former one.
EDIT: In order to organize this I have done the following:
1) In header file:
class MyWidget : public QWidget
{
Q_OBJECT
QTextEdit *m_textEditor1;
QTextEdit *m_textEditor2;
QPushButton *m_pushButton;
QHBoxLayout *m_layout;
int m_deltaX;
public:
MyWidget(QWidget * parent = 0);
~MyWidget(){}
private slots:
void closeOrOpenTextEdit2(bool isClosing);
};
2) In the source file:
MyWidget::MyWidget(QWidget * parent):QWidget(parent),m_deltaX(0)
{
m_pushButton = new QPushButton(this);
m_pushButton->setText(">");
m_pushButton->setCheckable(true);
connect(m_pushButton, SIGNAL(clicked(bool)), this, SLOT(closeOrOpenTextEdit2(bool)));
m_textEditor1 = new QTextEdit(this);
m_textEditor1->setText("AAAAA AAAAAAAAAAA AAAAAAAAAAA AAAAAAA AAAAAAAAAAA AAAAAAAAAAA AA");
m_textEditor2 = new QTextEdit(this);
m_layout = new QHBoxLayout;
m_layout->addWidget(m_textEditor1);
m_layout->addWidget(m_pushButton);
m_layout->addWidget(m_textEditor2);
setLayout(m_layout);
}
void MyWidget::closeOrOpenTextEdit2(bool isClosing)
{
QPropertyAnimation *animation1 = new QPropertyAnimation(m_textEditor2, "geometry");
QPropertyAnimation *animation2 = new QPropertyAnimation(m_pushButton, "geometry");
QPropertyAnimation *animation3 = new QPropertyAnimation(m_textEditor1, "geometry");
if(isClosing) //close the second textEdit
{
m_pushButton->setText("<");
QRect te2_1 = m_textEditor2->geometry();
m_deltaX = te2_1.width()-3;
QRect te2_2(te2_1.x()+m_deltaX, te2_1.y(), 3 ,te2_1.height());
QRect pb_1 = m_pushButton->geometry();
QRect pb_2(pb_1.x()+m_deltaX, pb_1.y(), pb_1.width() ,pb_1.height());
QRect te1_1 = m_textEditor1->geometry();
QRect te1_2(te1_1.x(), te1_1.y(), te1_1.width()+m_deltaX, te1_1.height());
//animation->setDuration(10000);
animation1->setStartValue(te2_1);
animation1->setEndValue(te2_2);
animation2->setStartValue(pb_1);
animation2->setEndValue(pb_2);
animation3->setStartValue(te1_1);
animation3->setEndValue(te1_2);
}
else //open
{
m_pushButton->setText(">");
QRect te2_1 = m_textEditor2->geometry();
QRect te2_2(te2_1.x()-m_deltaX, te2_1.y(), 3+m_deltaX ,te2_1.height());
QRect pb_1 = m_pushButton->geometry();
QRect pb_2(pb_1.x()-m_deltaX, pb_1.y(), pb_1.width() ,pb_1.height());
QRect te1_1 = m_textEditor1->geometry();
QRect te1_2(te1_1.x(), te1_1.y(), te1_1.width()-m_deltaX, te1_1.height());
//animation->setDuration(10000);
animation1->setStartValue(te2_1);
animation1->setEndValue(te2_2);
animation2->setStartValue(pb_1);
animation2->setEndValue(pb_2);
animation3->setStartValue(te1_1);
animation3->setEndValue(te1_2);
}
animation1->start();
animation2->start();
animation3->start();
}
EDIT:
And I have the following problem:
When I close the second QTextEdit (by clicking on the button) and resize the MyWidget, then the QTextEdit restores its state (but it should stay closed of course). How can I solve this problem?
Please provide me with a code snippet.
Qt's Animation framework sounds like a good place to start. You could just try to follow their tutorials, adapting for you use case. I have used it already, and it seemed quite straight forward.
1) You could replace your button with a vertical layout, place the button inside this layout and finally add a vertical spacer below the button (in same the layout).
...
QVBoxLayout* m_buttonLayout = new QVBoxLayout();
m_layout = new QHBoxLayout();
m_layout->addWidget(m_textEditor1);
m_layout->addLayout(m_buttonLayout);
m_layout->addWidget(m_textEditor2);
m_buttonLayout->addWidget(m_pushButton);
m_buttonLayout->addItem( new QSpacerItem(1, 1, QSizePolicy::Minimum, QSizePolicy::Expanding) );
2) I guess you could (and should) animate widget's maximumSize (or just maximumWidth) property and let the layout take care of calculating actual geometries. This would also simplify your calculations. E.g.
QPropertyAnimation *animation1 = new QPropertyAnimation(m_textEditor2, "maximumWidth");
QPropertyAnimation *animation2 = new QPropertyAnimation(m_textEditor, "maximumWidth");
if (isClosing)
{
int textEdit2_start = m_textEditor2->maximumWidth();
int textEdit2_end = 0;
int textEdit_start = m_textEditor->maximumWidth();
int textEdit_end = textEdit_start + textEdit2_start;
animation1->setStartValue(textEdit2_start);
...
}
Also, now you don't have to animate buttons geometry at all (assuming that you have set fixed size to it).
PS. I didn't compile codes so there might be minor errors but you should get the idea.
Here what I wanted:
Header file
class MyWidget : public QWidget
{
Q_OBJECT
QTextEdit *m_textEditor1;
QTextEdit *m_textEditor2;
QPushButton *m_pushButton;
QHBoxLayout *m_layout;
QVBoxLayout *m_buttonLayout;
int m_deltaX;
bool m_isClosed;
public:
MyWidget(QWidget * parent = 0);
~MyWidget(){}
void resizeEvent( QResizeEvent * event );
private slots:
void closeOrOpenTextEdit2(bool isClosing);
};
Source file
MyWidget::MyWidget(QWidget * parent):QWidget(parent),m_deltaX(0)
{
m_pushButton = new QPushButton(this);
m_pushButton->setText(">");
m_pushButton->setCheckable(true);
m_pushButton->setFixedSize(16,16);
connect(m_pushButton, SIGNAL(clicked(bool)), this, SLOT(closeOrOpenTextEdit2(bool)));
m_textEditor1 = new QTextEdit(this);
m_textEditor1->setText("AAAAA AAAAAAAAAAA AAAAAAAAAAA AAAAAAA AAAAAAAAAAA AAAAAAAAAAA AA");
m_textEditor2 = new QTextEdit(this);
m_buttonLayout = new QVBoxLayout();
m_buttonLayout->addWidget(m_pushButton);
m_buttonLayout->addItem( new QSpacerItem(1, 1, QSizePolicy::Minimum, QSizePolicy::Expanding) );
m_layout = new QHBoxLayout;
m_layout->addWidget(m_textEditor1, 10);
m_layout->addSpacing(15);
m_layout->addLayout(m_buttonLayout);
m_layout->setSpacing(0);
m_layout->addWidget(m_textEditor2, 4);
setLayout(m_layout);
resize(800,500);
}
void MyWidget::closeOrOpenTextEdit2(bool isClosing)
{
m_isClosed = isClosing;
QPropertyAnimation *animation1 = new QPropertyAnimation(m_textEditor2, "maximumWidth");
if(isClosing) //close the second textEdit
{
m_textEditor2->setMaximumWidth(m_textEditor2->width());
int textEdit2_start = m_textEditor2->maximumWidth();
m_deltaX = textEdit2_start;
int textEdit2_end = 3;
animation1->setDuration(500);
animation1->setStartValue(textEdit2_start);
animation1->setEndValue(textEdit2_end);
m_pushButton->setText("<");
}
else //open
{
int textEdit2_start = m_textEditor2->maximumWidth();
int textEdit2_end = m_deltaX;
animation1->setDuration(500);
animation1->setStartValue(textEdit2_start);
animation1->setEndValue(textEdit2_end);
m_pushButton->setText(">");
}
animation1->start();
}
void MyWidget::resizeEvent( QResizeEvent * event )
{
if(!m_isClosed)
m_textEditor2->setMaximumWidth( QWIDGETSIZE_MAX );
}