QtWidget Disable Margin for overlay widget - c++

My situation: I have a grid layout with n*n widgets inside. Additionally I place an overlay widget in the grid with position 0,0 and span n,n.
Evertyhing fine with this, but there is a weird margin and I don't know what causes it...
Has anyone an idea how i can prevent this? I think I'm missing something trivial...
SudokuFieldWidget::SudokuFieldWidget(QWidget *parent) : QFrame(parent)
{
...
m_layout = new QGridLayout( this );
m_layout->setSpacing( 0 );
m_layout->setMargin( 1 );
this->initCells( true );
this->setLayout( m_layout );
m_markerOverlay = new SudokuMarkerOverlayWidget( this );
m_layout->addWidget( m_markerOverlay, 0, 0, m_fieldSize, m_fieldSize );
}
SudokuMarkerOverlayWidget::SudokuMarkerOverlayWidget(QWidget* parent) : QWidget(parent)
{
setAttribute(Qt::WA_NoSystemBackground);
setAttribute(Qt::WA_TransparentForMouseEvents);
...
}
void SudokuMarkerOverlayWidget::paintEvent(QPaintEvent*)
{
QPainter painter(this);
painter.fillRect( rect(), QColor( 255, 0, 0, 128 ) );
}

Had an error in my paint-Routine for SudokuFieldWidget which caused this misbehaviour... m_markerOverlay->setGeometry( this->geometry() );

Related

Why is my combobox overwriting my label in my grid layout in QT 5.15?

I have a form that was giving me a SegFault Exception so I broke it down and commented out everything but the mainwindow itself. Then I uncommented out the first 2 controls to display. I'm using a qgridlayout but it's not showing things correctly. When I show just the first item, it shows the label with the & instead of an underline - which makes sense since I don't show the "buddy". If I show both though or if I add the 'setBuddy' in then the buddy overwrites the label in the grid at 0,0. The label is supposed to be at 0,0 and the buddy/combo box is at 0, 1. I need to keep adding things in and my alternative is to use QHBoxLayouts repeatedly and I'm not nuts about that at all as things won't be aligned. Here's just the CPP code for the MainWindow to see:
QComboBox *MainWindow::getSourceComboBox()
{
if(mCboSource == nullptr)
{
mCboSource = new QComboBox(this);
mCboSource->addItems({{"URL", "Text", "Database"}});
mCboSource->setCurrentIndex(0);
mCboSource->setEditable(false);
//connect(mCboSource, SIGNAL(QComboBox::currentTextChanged(QString &)), this, SLOT(MainWindow::sourceChanged(QString &)));
}
return(mCboSource);
}
QLabel *MainWindow::getSourceLabel()
{
if(mLblSource == nullptr)
{
mLblSource = new QLabel("&Source:", this);
mLblSource->setBuddy(getSourceComboBox());
mLblSource->setAutoFillBackground(false);
}
return(mLblSource);
}
void MainWindow::initControls()
{
QGridLayout *layout = new QGridLayout(this);
layout->addWidget(this->getSourceLabel(), 0, 0, 1, 1);
layout->addWidget(this->getSourceComboBox(), 0, 1, 1, 1);
/*layout->addWidget(this->getConfigButton(), 0, 2);
layout->addWidget(this->getDBPathLabel(), 1, 0);
layout->addWidget(this->getDBPathText(), 1, 1);
layout->addWidget(this->getBrowseButton(), 1, 2);
layout->addWidget(this->getDataTable(), 2, 0, 1, 3);
layout->addWidget(this->getButtonPanel(), 3, 0, 1, 3);*/
setLayout(layout);
}
void MainWindow::initWindow()
{
setSizePolicy(QSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding));
initControls();
}
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
, mCboSource(nullptr)
//, mLblDBPath(nullptr)
, mLblSource(nullptr)
/*, mTxtDBPath(nullptr)
, mBtnBrowse(nullptr)
, mBtnConfig(nullptr)
, mTblData(nullptr)*/
{
initWindow();
}
MainWindow::~MainWindow()
{ }
But what could be wrong w/ the QT code???
You are adding items almost as a central widget so you need to create a central widget and add the items and layout to that then add that central widget to the main window. That should solve the problem!

Change color/text of QProgressBar on mouse over filled bar

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

Zooming in a QGraphicsScene makes my drawings disappear sometimes

I have a QGraphicsScene where I add a QPixmap composed of 4 images and the borders of each image.
I create a new QPixmap with the total size and then use a QPainter to draw each sub-image in the appropiate place in the bigger pixmap. After one sub-image is done, inmediately draw its borders (this may not be optimal but for now I don't mind).
Once the "final" pixmap is finished, I insert directly to the scene with
scene->addPixmap( total )
Here's the code for the pixmap composition:
QPixmap pixFromCube( PanoramicImages* lim ) const
{
const QSize img_size = getImageSize( lim );
const QSize pano_size( img_size.width() * 4, img_size.height() );
QPixmap toret( pano_size );
if( !toret.isNull() ) {
QPainter painter( &toret );
painter.setRenderHint( QPainter::Antialiasing );
int x( 0 );
QPixmap pix = lim->getCamera1Image();
if( !pix.isNull() ) {
painter.drawPixmap( 0, 0, pix.width(), pix.height(), pix );
drawPixBorder( painter, pix.rect() );
}
x += img_size.width();
pix = lim->getCamera2Image();
if( !pix.isNull() ) {
painter.drawPixmap( x, 0, pix.width(), pix.height(), pix );
drawPixBorder( painter, QRectF( x, 0, pix.width(),
pix.height() ) )
;
}
x += img_size.width();
pix = lim->getCamera3Image();
if( !pix.isNull() ) {
painter.drawPixmap( x, 0, pix.width(), pix.height(), pix );
drawPixBorder( painter, QRectF( x, 0, pix.width(),
pix.height() ) )
;
}
x += img_size.width();
pix = lim->getCamera4Image();
if( !pix.isNull() ) {
painter.drawPixmap( x, 0, pix.width(), pix.height(), pix );
drawPixBorder( painter, QRectF( x, 0, pix.width(),
pix.height() ) )
;
}
}
return toret;
}
And
void drawPixBorder( QPainter& painter, const QRectF rect ) const
{
const QBrush oldBrush = painter.brush();
const QPen oldPen = painter.pen();
QColor color( Qt::blue );
if( timer.isActive() ) {
color = Qt::green;
} else {
color = Qt::red;
}
const QBrush brush( color );
QPen pen( brush, 22 );
const QPointF points[ 5 ] = {
rect.topLeft(),
rect.topRight(),
rect.bottomRight(),
rect.bottomLeft(),
rect.topLeft()
};
painter.setBrush( brush );
painter.setPen( pen );
painter.drawPolyline( points, sizeof( points ) / sizeof( points[ 0 ] ) );
painter.setBrush( oldBrush );
painter.setPen( oldPen );
}
Here's the final pixmap when it's loaded for the first time:
And here after a few zoom-outs:
As you can see, at the right some of the borders are missing. When zooming back again to the inital position, the borders are displayed. If I use a smaller width for the lines (say, 5), the borders disappear sooner.
I've been reading other questions here and in the Qt Forums and tried some suggestions like:
pen.setCosmetic( true );
or
painter.setRenderHint( QPainter::NonCosmeticDefaultPen, false);
or:
painter.setRenderHint( QPainter::Antialiasing );
setting the pen width directly to 0
pen.setWidth( 0 )
and combinations.
Neither of them prevented the borders to disappear and using a bigger width just delays the problem.
Is there a way to show always the borders regardless of the zoom level?
Thanks to #Robert for his help. As he has stated in his answer, the solution was to draw directly in the scene, instead of doing it in the pixmap and then adding it.
For drawing in the scene, I decided to use a QPainterPath:
int x( 0 );
QPainterPath rectPath;
for( unsigned int i( 0 ); i < 4; ++i ) {
rectPath.addRect( QRectF( x, 0, width, height ) );
x += width;
}
QColor color( Qt::blue );
if( timer.isActive() ) {
color = Qt::green;
} else {
color = Qt::red;
}
scene->addPath( rectPath, QPen( color ) );
It is because the painter you are using to create the pixmap does not know anything about the transformations/scale of the graphic scene... A possible solutions would be to draw the rectangles within the scene and not directly to the pixmap.

QScrollArea with dynamically changing contents

I have a QScrollArea with some buttons in it, like shown on the picture.
The idea of the layout is:
1. The left and right button should be used for scrolling the buttons when they are too wide
2.The numbers of buttons in the scroll area can be changed dynamically
3. Any free space should be used to expand the scroll area as much as possible. If no such space exist navigation buttons should be used for scrolling.
With my current implementation when i increase the buttons i have this:
But there is free space on the right, so this should look like:
If i increase once more to 10 for example, then scrollbar should appear( because the layout is constained by the widget ).
I want to know if there is any other way aside from manual resizing of the widgets( because ui can be translated and buttons can change size hint also the real design is more complicated :(
Here is my implementation of the ScrollAreaTest widget:
#include "MainWidget.h"
#include <QLineEdit>
#include <QVBoxLayout>
#include <QHBoxLayout>
#include <QScrollArea>
#include <QPushButton>
#include <QDebug>
#include "ButtonWidget.h"
#include "CheckableButtonGroup.h"
MainWidget::MainWidget(QWidget *parent)
: QWidget(parent),
m_scrollArea( 0 ),
m_lineEdit( 0 ),
m_buttons( 0 )
{
QVBoxLayout* mainLayout = new QVBoxLayout( this );
QWidget* firstRow = new QWidget;
QHBoxLayout* firstRowLayout = new QHBoxLayout( firstRow );
QPushButton* left = new QPushButton;
QPushButton* right = new QPushButton;
m_buttons = new CheckableButtonGroup( Qt::Horizontal );
m_buttons->setSizePolicy( QSizePolicy::Expanding, QSizePolicy::Preferred );
m_buttons->setButtonsCount( 5 );
m_buttons->setStyleSheet( "border: none" );
QWidget* const buttonsContainer = new QWidget;
QHBoxLayout* const buttonsContainerLayout = new QHBoxLayout( buttonsContainer );
buttonsContainerLayout->setSpacing( 0 );
buttonsContainerLayout->setSizeConstraint( QLayout::SetMinAndMaxSize );
buttonsContainerLayout->setMargin( 0 );
buttonsContainerLayout->addWidget( m_buttons, 0, Qt::AlignLeft );
qDebug() << m_buttons->buttons()[ 0 ]->size();
m_scrollArea = new QScrollArea;
m_scrollArea->setContentsMargins( 0, 0, 0, 0 );
m_scrollArea->setWidget( buttonsContainer );
m_scrollArea->setWidgetResizable( true );
m_scrollArea->setStyleSheet( "border: 1px solid blue" );
m_scrollArea->setSizePolicy( QSizePolicy::Expanding, QSizePolicy::Preferred );
firstRowLayout->addWidget( left , 0, Qt::AlignLeft );
firstRowLayout->addWidget( m_scrollArea, 1, Qt::AlignLeft );
firstRowLayout->addWidget( right , 0, Qt::AlignLeft );
m_lineEdit = new QLineEdit;
QPushButton* button = new QPushButton;
QHBoxLayout* secondRowLayout = new QHBoxLayout;
secondRowLayout->addWidget( m_lineEdit );
secondRowLayout->addWidget( button );
connect( button, SIGNAL(clicked()), SLOT(setButtonsCount()) );
mainLayout->addWidget( firstRow, 1, Qt::AlignLeft );
mainLayout->addLayout( secondRowLayout );
button->setText( "Set buttons count" );
buttonsContainer->resize( m_buttons->buttonsOptimalWidth(), buttonsContainer->height() );
m_buttons->resize( m_buttons->buttonsOptimalWidth(), m_buttons->height() );
//area->resize( 100, area->height() );
//area->setHorizontalScrollBarPolicy( Qt::ScrollBarAlwaysOff );
}
MainWidget::~MainWidget()
{
}
void MainWidget::setButtonsCount()
{
m_buttons->setButtonsCount( m_lineEdit->text().toInt() );
}
And here is the whole Qt project containing the problem:
https://drive.google.com/file/d/0B-mc4aKkzWlxQzlPMEVuNVNKQjg/edit?usp=sharing
The essential steps are:
The container widget that holds the buttons (your CheckableButtonGroup) must have a QLayout::SetMinAndMaxSize size constraint set. Then it will be exactly large enough to hold the buttons. Its size policy doesn't matter, since you're simply putting it into a QScrollArea, not into another layout.
The scroll area needs to set its maximum size according to the size of the widget it holds. The default implementation doesn't do it, so one has to implement it by spying on resize events of the embedded widget.
The code below is a minimal example that works under both Qt 4.8 and 5.2.
// https://github.com/KubaO/stackoverflown/tree/master/questions/scrollgrow-21253755
#include <QtGui>
#if QT_VERSION >= QT_VERSION_CHECK(5,0,0)
#include <QtWidgets>
#endif
class ButtonGroup : public QWidget {
Q_OBJECT
QHBoxLayout m_layout{this};
public:
ButtonGroup(QWidget * parent = 0) : QWidget{parent} {
m_layout.setSizeConstraint(QLayout::SetMinAndMaxSize); // <<< Essential
}
Q_SLOT void addButton() {
auto n = m_layout.count();
m_layout.addWidget(new QPushButton{QString{"Btn #%1"}.arg(n+1)});
}
};
class AdjustingScrollArea : public QScrollArea {
bool eventFilter(QObject * obj, QEvent * ev) {
if (obj == widget() && ev->type() == QEvent::Resize) {
// Essential vvv
setMaximumWidth(width() - viewport()->width() + widget()->width());
}
return QScrollArea::eventFilter(obj, ev);
}
public:
AdjustingScrollArea(QWidget * parent = 0) : QScrollArea{parent} {}
void setWidget(QWidget *w) {
QScrollArea::setWidget(w);
// It happens that QScrollArea already filters widget events,
// but that's an implementation detail that we shouldn't rely on.
w->installEventFilter(this);
}
};
class Window : public QWidget {
QGridLayout m_layout{this};
QLabel m_left{">>"};
AdjustingScrollArea m_area;
QLabel m_right{"<<"};
QPushButton m_add{"Add a widget"};
ButtonGroup m_group;
public:
Window() {
m_layout.addWidget(&m_left, 0, 0);
m_left.setSizePolicy(QSizePolicy::Maximum, QSizePolicy::Preferred);
m_left.setStyleSheet("border: 1px solid green");
m_layout.addWidget(&m_area, 0, 1);
m_area.setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Preferred);
m_area.setStyleSheet("QScrollArea { border: 1px solid blue }");
m_area.setWidget(&m_group);
m_layout.setColumnStretch(1, 1);
m_layout.addWidget(&m_right, 0, 2);
m_right.setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred);
m_right.setStyleSheet("border: 1px solid green");
m_layout.addWidget(&m_add, 1, 0, 1, 3);
connect(&m_add, SIGNAL(clicked()), &m_group, SLOT(addButton()));
}
};
int main(int argc, char *argv[])
{
QApplication a{argc, argv};
Window w;
w.show();
return a.exec();
}
#include "main.moc"

QPixmap on QLabel doesn't show correctly

I am trying to draw some shape with QPainter class and save it to disk. As far as I know the easiest way is to use QPainter to draw into a QPixmap, visualize in the pixmap though a QLabel, and use QPixmap::save.
But when I run this test I see only a little black box inside the QWidget.
MyWidget::MyWidget()
{
std::cout << "MyWidget > ." << std::endl;
l = new QLabel();
l->setParent(this);
pixmap = new QPixmap(460, 480);
painter = new QPainter(pixmap);
}
MyWidget::~MyWidget()
{
delete pixmap;
delete painter;
}
void MyWidget::paintEvent(QPaintEvent *event)
{
std::cout << "dudee" << std::endl;
painter->begin(pixmap);
painter->drawLine(1,1,100,100);
QPen myPen(Qt::black, 2, Qt::SolidLine);
painter->setPen(myPen);
painter->drawLine(100,100,100,1);
painter->setRenderHint(QPainter::Antialiasing, true);
painter->setPen(QPen(Qt::black, 3, Qt::DashDotLine, Qt::RoundCap));
painter->setBrush(QBrush(Qt::green, Qt::SolidPattern));
painter->drawEllipse(200, 80, 400, 240);
painter->end();
l->setPixmap(*pixmap);
}
I have tried to add some l->update() but it doesn't change anything..
EDIT:
It should be an animation. I get the animation work through a QTimer that call every n msec the function for draw (not the paintEvent as the answer suggest)
You need instance of QPainter only during painting something. You don't need to keep it as class member.
Pixmap may be declared as class member, not as pointer.
Paint should be done once. It is bad idea to draw you external pixmap inside paintEvent, because you don't know, when exactly paintEvent will be called (and how much times).
You must not set pixmap for a label inside paint event, because call of l->setPixmap forces your widget to update => you will get infinite loop of draw->set->update->draw...
Solution:
Create somewhere a pixmap and paint on it necessary content.
Set content to a label, when it necessary (for example, after drawing).
Do not call update() - it will be called automatically, when you will set pixmap to label.
EDITED code:
Simple class for edited question:
AnimationSample.h
#ifndef ANIMATIONSAMPLE_H
#define ANIMATIONSAMPLE_H
#include <QWidget>
#include <QPixmap>
#include <QLabel>
#include <QPointer>
#include <QTimer>
class AnimationSample
: public QWidget
{
Q_OBJECT
public:
AnimationSample( QWidget *parent = NULL );
~AnimationSample();
private slots:
void onTick();
private:
QPointer< QLabel > m_label;
QPointer< QTimer > m_timer;
int m_salt;
};
#endif // ANIMATIONSAMPLE_H
AnimationSample.cpp
#include "AnimationSample.h"
#include <QPixmap>
#include <QPainter>
AnimationSample::AnimationSample( QWidget *parent )
: QWidget( parent )
, m_salt( 1 )
{
m_label = new QLabel( this );
m_label->setFixedSize( 100, 100 );
m_timer = new QTimer( this );
connect( m_timer, SIGNAL( timeout() ), SLOT( onTick() ) );
m_timer->start( 250 );
}
AnimationSample::~AnimationSample()
{
}
void AnimationSample::onTick()
{
QPixmap pic( 100, 100 );
QPainter p( &pic );
QPen myPen( Qt::black, 2, Qt::SolidLine );
p.setPen( myPen );
p.drawLine( 0, 0, m_salt, m_salt );
m_salt = (m_salt + 1) % 100;
m_label->setPixmap( pic );
}