How to create a resizable Qt thumbnail preview? - c++

I'm working on a basic image viewer/tagger that will need a thumbnail view to select an image. So far, I've used a QDockWidget enclosing a QScrollArea with a QHBoxLayout to contain a series of QLabels, each of which has its QPixMap set.
This seems very inelegant, and it only gets uglier to consider how I might implement auto-scaling of the thumbnails when the QDockWidget is resized. It's further complicated by the additional need to resize the thumbnails when the scroll bar appears and disappears.
There must be a better way to do this?

I've ran into a similar problem when trying to animate resizing a qlabel with a qpixmap. The method I found that worked best was to use a QWidget instead and re-implement the paintEvent function. Then your QWidget image will automatically be scaled if it's resized. Here is an example:
In my case I had the private variables in a private object called private_:
bool image_set_;
QImage image_;
QBrush paintbrush_;
void MyClass::paintEvent( QPaintEvent* event )
{
// if the QWidget has an image set, then we use our custom painting.
if( this->private_->image_set_ )
{
//I've made it so that my QWidget has a 1px white border
this->private_->paintbrush_.setTextureImage( this->private_->image_.scaled(QSize( this->width() - 2, this->height() - 2 ) ) );
QPainter painter( this );
QRect temp_rect = QRect( 1, 1, this->width()-2, this->height() - 2 );
painter.fillRect( this->rect(), Qt::white );
painter.fillRect( temp_rect, this->private_->paintbrush_ );
}
else
{
QWidget::paintEvent( event );
}
}

Related

qt remove layouts code

I have less than 1 day of experience in QT (that's why I do not know much of it) I have a window full of information (labels, text, buttons, etc) organized by layouts.
I need that after I press one button, all of the components in a window be hidden (which I already did) except for one label which should increase to barely the size of the whole window
Despite I tried modifying the "geometry" attribute (with code) the hidden layouts do not let the label to be increased. I thought also of using the option of layout breaking, but the label losses its dynamism. Could anyone please recommend me anything to do? Thanks.
Has anyone done something like this before. Thanks.
I once provided an answer to SO: Qt - How to create Image that scale with window, and keeps aspect ratio?. The actual intention was to scale an image in a QLabel with original aspect ratio to consume maximum available size.
However, I got the feedback that the suggested solution would not work properly when my Label would be used in a QGridLayout. (This sounds very similar to the issue of the OP.) Hence, I modified the sample to reproduce the issue and fiddled a little bit around with. For me, it seems that resize events of the main window are processed in the QGridLayout but affect layouted image label only partially. (Shrinking is applied but growing not.) Fortunately, I found a very simple work-around: Setting a non-empty frame to the QLabel solved the problem. I had a look into the source code on woboq.org. I hoped to get a hint what the changed frame style would activate (to apply this as fix for my resize issue). Finally, I was not patient enough and put it aside.
Beside of this QLabel in a QGridLayout resize issue, changing the visibility of widgets should cause a proper re-layout. I would prefer show/hide (instead of delete and re-new) as this is surely easier to implement, more efficient, and less error-prone.
I took the old sample code and added a tool button which can be used to toggle the visibilty of some of the layouted widgets:
// Qt header:
#include <QtWidgets>
class LabelImage: public QLabel {
private:
QPixmap _qPixmap, _qPixmapScaled;
public:
void setPixmap(const QPixmap &qPixmap) { setPixmap(qPixmap, size()); }
protected:
virtual void resizeEvent(QResizeEvent *pQEvent);
private:
void setPixmap(const QPixmap &qPixmap, const QSize &size);
};
void LabelImage::resizeEvent(QResizeEvent *pQEvent)
{
QLabel::resizeEvent(pQEvent);
setPixmap(_qPixmap, pQEvent->size());
}
void LabelImage::setPixmap(const QPixmap &qPixmap, const QSize &size)
{
_qPixmap = qPixmap;
_qPixmapScaled = _qPixmap.scaled(size, Qt::KeepAspectRatio);
QLabel::setPixmap(_qPixmapScaled);
}
int main(int argc, char **argv)
{
qDebug() << "Qt Version:" << QT_VERSION_STR;
// main application
QApplication app(argc, argv);
// setup GUI
QMainWindow qWin;
QToolBar qToolbar;
QAction qCmdTgl(QString::fromUtf8("Decoration"));
qCmdTgl.setCheckable(true);
qCmdTgl.setChecked(true);
qToolbar.addAction(&qCmdTgl);
qWin.addToolBar(&qToolbar);
QGroupBox qBox;
QGridLayout qGrid;
// a macro for the keyboard lazy:
#define Q_LBL_WITH_POS(ROW, COL) \
QLabel qLbl##ROW##COL(QString::fromLatin1(#ROW", "#COL)); \
/*qLbl##ROW##COL.setFrameStyle(QLabel::Raised | QLabel::Box);*/ \
qGrid.addWidget(&qLbl##ROW##COL, ROW, COL, Qt::AlignCenter)
Q_LBL_WITH_POS(0, 0);
Q_LBL_WITH_POS(0, 1);
Q_LBL_WITH_POS(0, 2);
Q_LBL_WITH_POS(1, 0);
LabelImage qLblImg;
qLblImg.setFrameStyle(QLabel::Raised | QLabel::Box);
qLblImg.setAlignment(Qt::AlignCenter);
//qLblImg.setMinimumSize(QSize(1, 1)); // seems to be not necessary
qLblImg.setSizePolicy(
QSizePolicy(QSizePolicy::Ignored, QSizePolicy::Ignored));
QPixmap qPM;
if (qPM.load("cats.jpg")) qLblImg.setPixmap(qPM);
else {
qLblImg.setText(
QString::fromLatin1("Sorry. Cannot find file 'cats.jpg'."));
}
qGrid.addWidget(&qLblImg, 1, 1, Qt::AlignCenter);
qGrid.setRowStretch(1, 1); // tell QGridLayout to stretch this cell...
qGrid.setColumnStretch(1, 1); // ...prior to other cells (w/ stretch 0)
Q_LBL_WITH_POS(1, 2);
Q_LBL_WITH_POS(2, 0);
Q_LBL_WITH_POS(2, 1);
Q_LBL_WITH_POS(2, 2);
qBox.setLayout(&qGrid);
qWin.setCentralWidget(&qBox);
qWin.show();
// install signal handlers
QObject::connect(&qCmdTgl, &QAction::triggered,
[&](bool on) {
qLbl00.setVisible(on); qLbl01.setVisible(on); qLbl02.setVisible(on);
qLbl10.setVisible(on); qLbl12.setVisible(on);
qLbl20.setVisible(on); qLbl21.setVisible(on); qLbl22.setVisible(on);
});
// run application
return app.exec();
}
I compiled and tested in VS2013 on Windows 10:
After toggling the Decoration tool button:
Note:
Out of curiosity, I commented the line which changes the frame style
qLblImg.setFrameStyle(QLabel::Raised | QLabel::Box);
and again, resizing of image didn't work properly anymore.
You can remove and hide widgets inside a layout using QLayout::removeWidget(*widget); but you do not need to actually remove it. You should use QWidget::hide() for the content to disappear and for the video label's cell to be able to take that space. I think you need to pay attention to the video label's size policy if it does not increase in size. Assuming you have a QGridLayout like so:
label1 label2 label3
label4 videoLabel label5
button1 button2 button3
And let's say, when you click button3, label1, label2 and label4 should all disappear and videoLabel takes the newly created space. I would group the widgets label1, label2, label4 and videoLabel into a single widget having its own sub-layout. I use QSizePolicy::Expaning to make sure my videoLabel takes the maximum space possible. Here is the implementation:
Widget::Widget(QWidget *parent) :
QWidget(parent)
{
setStyleSheet("QLabel{font-size:20px;}");
fullScreen = false; //current fullscreen state
//main grid layout
baseLayout = new QGridLayout(this);
baseLayout->setMargin(0);
baseLayout->setAlignment(Qt::AlignCenter);
setLayout(baseLayout);
//widget container for label1, label2, label4, videolabel
groupWidget = new QWidget();
//sub-layout inside the group layout
subLayout = new QGridLayout();
subLayout->setAlignment(Qt::AlignCenter);
subLayout->setMargin(0);
groupWidget->setLayout(subLayout);
//label and button instantializing. I set background colors to show their sizes
label1 = new QLabel("Label1");
label1->setStyleSheet("background-color:white;");
label2 = new QLabel("Label2");
label2->setStyleSheet("background-color:orange;");
label3 = new QLabel("Label3");
label4 = new QLabel("Label4");
label4->setStyleSheet("background-color:blue;color:white;");
label5 = new QLabel("Label5");
videoLabel = new QLabel("videoLabel");
videoLabel->setStyleSheet("background-color:red;");
videoLabel->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
button1 = new QPushButton("button1");
button2 = new QPushButton("button2");
button3 = new QPushButton("button3");
//the grouped widget spans for 2 rows and columns, hence "2,2"
baseLayout->addWidget(groupWidget, 0,0,2,2);
subLayout->addWidget(label1, 0, 0);
subLayout->addWidget(label2, 0, 1);
subLayout->addWidget(label4, 1, 0);
subLayout->addWidget(videoLabel, 1, 1);
//adding rest of the labels and buttons to the base grid
baseLayout->addWidget(label3, 0, 2);
baseLayout->addWidget(label5, 1, 2);
baseLayout->addWidget(button1, 2, 0);
baseLayout->addWidget(button2, 2, 1);
baseLayout->addWidget(button3, 2, 2);
//button3 toggles fullscreen
connect(button3, SIGNAL(clicked(bool)), this, SLOT(onButton3Clicked(bool)));
}
//slot for button3 click
void Widget::onButton3Clicked(bool)
{
if (!fullScreen){
//removing widget from layouts is not really necessary. Make sure to hide
/*subLayout->removeWidget(label1);
subLayout->removeWidget(label2);
subLayout->removeWidget(label4);*/
label1->hide();
label2->hide();
label4->hide();
fullScreen = true;
}
else{
label1->show();
label2->show();
label4->show();
/*subLayout->addWidget(label1, 0, 0);
subLayout->addWidget(label2, 0, 1);
subLayout->addWidget(label4, 1, 0);*/
fullScreen = false;
}
}
I got the following results for this:
Keep in mind there are other approaches to this question. This one need not be necessarily the best when it comes to memory, but it is quite easy to follow.

QPixmap, how to make sure it is 'always on top'

For Widgets I can call 'raise' which keeps the widget on top of anything else, but this doesn't seem to work for any QPixmap's that are rendered.
How can I ensure that a QPixmap remains on top of anything else?
In my paintEvent function:
QPainter objPainter(this);
if ( strImage.isEmpty() != true ) {
qint16 int16NudgeImageX = mpobjNode->int16GetAttr(clsXML::mscszAttrNudgeImageX)
,int16NudgeImageY = mpobjNode->int16GetAttr(clsXML::mscszAttrNudeImageY);
QPixmap pmImage(":/" + strImage);
QSize szImage = pmImage.size();
QPoint ptImage(rctGeom.center().x() - (szImage.width() / 2) + int16NudgeImageX
,rctGeom.center().y() - (szImage.height() / 2) + int16NudgeImageY);
QRect rctImage(ptImage, szImage);
objPainter.drawPixmap(rctImage, pmImage);
}
A QPixmap is not a widget (no parent, nor layout).
The QPixmap class is an off-screen image representation that can be used as a paint device.
Use a QLabel for use with a parent (and layout)
QLabel* label = new QLabel(parent);
label->setPixmap(pixmap);
If you want to make sure that your QPixmap is painted on top of everything else inside your widget, simply paint it last.
Keep in mind that other widgets may be painted above your widget.

Qt: QGraphicsView content not erased before drawForeground() is called

I'm facing an annoying issue using Qt QGraphicsView framework.
I write a basic image viewer to display large B&W tiff images.
Images are displayed using a QGraphicsPixmapItem and added to the scene.
I also need to draw a colored rectangle around the viewport.
void ImageView::drawForeground( QPainter * painter, const QRectF & rect )
{
if( m_image ) {
qDebug() << "drawForeground(), rect = " << rect;
QBrush br(m_image->isValidated() ? Qt::green : Qt::red);
qreal w = 40. / m_scaleFactor;
QPen pen(br, w);
painter->save();
painter->setOpacity(0.5);
painter->setPen(pen);
painter->drawRect(rect);
painter->restore();
}
}
It works fine, at first glance.
But when I scroll the viewport content, things are getting ugly.
drawForeground() method is effectively called but it seems the content of the viewport is not erased before. So the drawing becomes horrible on screen.
Is there a better way to achieve it?
EDIT
As leems mentioned it, Qt internals don't alow me to achieve it with drawForeground().
I found a workaround by using a QGraphicsRectItem which gets resized in the viewportEvent().
Loos like:
bool ImageView::viewportEvent(QEvent *ev)
{
QRect rect = viewport()->rect();
if( m_image ) {
QPolygonF r = mapToScene(rect);
QBrush br2(m_image->isValidated() ? Qt::green : Qt::red);
qreal w2 = 40. / m_scaleFactor;
QPen pen2(br2, w2);
m_rect->setPen(pen2);
m_rect->setOpacity(0.5);
m_rect->setRect(r.boundingRect());
}
return QGraphicsView::viewportEvent(ev);
}
The code is not finalized but will basically looks like that.
It works fine, though it blinks a little bit when scrolling too fast...

Resizing parent window after child sizes change

I have been messing with this problem for hours, and decided it's time to ask SO :)
I have a Qt program that rotates an image and then updates the size of the widget. Here is the code I'm using to do this currently.
void VideoSubWindow::showFrame(const QImage& frame)
{
QPixmap pixmap = QPixmap::fromImage(frame);
ui->videoFrameLabel->setPixmap(pixmap);
resizeWidgets(pixmap.size());
}
void VideoSubWindow::resizeWidgets(const QSize &size)
{
if(frameSize != size)
{
frameSize = size;
ui->videoFrameLabel->setFixedSize(size);
ui->scrollArea->setMinimumSize(size.width() + 2, size.height() + 2);
}
}
The widgets are structured as follows:
VideoSubWindow (QMainWindow)
-> centralWidget (QWidget) (Vertical layout is set on this)
-> scrollArea (QScrollArea)
-> videoFrameLabel (QLabel)
-> statusBar (QStatusBar)
-> menuBar (QMenuBar)
When the code above is executed, like rotating the image 90 degrees, the image will be rotated, but the window doesn't resize to fit the new pixmap size. I have tried to call adjustSize() and updateGeometry() on SubWindow and centralWidget, but those seem to have zero effect. But, if I manually resize the window with my mouse, the window snaps to the minimum size that was set for the scrollArea, so that seems to be taking effect.
Does anyone have experience with this? Thanks!
Try with the resize(...) function : Qt documentation
adjustSize() used sizeHint() function, so calling adjustSize() on SubWindow and centralWidget cannot have any effet

How to set background for QGraphicsSimpleTextItem (Qt C++)?

I have added to my QGraphicsScene a QGraphicsSimpleTextItem, but just a simple text is unreadable of current background. Therefore I'd like to set background color of the QGraphicsSimpleTextItem, but... there is no such method. What's the simplest solution?
It seems that the simplest solution is to use QGraphicsTextItem instead of QGraphicsSimpleTextIem and to call setHtml() in the constructor, for example:
this->setHtml(QString("<div style='background-color: #ffff00;'>") + text + "</div>");
To change the background of your whole scene:
myScene->setBackgroundBrush( Qt::red );
Or if you want to change the background of just your text item, you'll probably have to subclass QGraphicsSimpleTextItem and override the paint() method.
class MyTextItem : public QGraphicsSimpleTextIem {
public:
void paint( QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget=0 )
{
painter->setBrush( Qt::red );
painter->drawRect( boundingRect() );
QGraphicsSimpleTextItem::paint( painter, option, widget );
}
Here is how you can access the background color.
QPalette currentPalette = myGraphicScene.palette();
// Set a new color for the background, use QPalette::Window
// as QPalette::Background is obsolete.
currentPalette.setColor( QPalette::Window, Qt::red );
// Set the palette.
myGraphicScene.setPalette( currentPalette );