Stretching a QLabel in a QGridLayout - c++

I am manually creating a set of QLabels that are being put into a QGridLayout, and should be distributed evenly. When I create a test form using the QT Designer and add a series of labels, and put them in a QGridLayout, the labels occupy the full size of their cells in the grid. When I do this manually in c++, the labels don't expand and say the minimum size for the text. I am able to get these labels to expand vertically, but not horizontally.
Here is how I'm creating the QGridLayout:
m_layout = new QGridLayout;
m_layout->setHorizontalSpacing( 1 );
m_layout->setVerticalSpacing( 1 );
m_layout->setContentsMargins(0,0,0,0);
m_layout->setMargin(0);
m_layout->setSizeConstraint( QGridLayout::SetDefaultConstraint );
//m_layout->setSizeConstraint( QGridLayout::SetMaximumSize );
When I change the size constraint, it does not affect the size of the labels at all, so then I assumed that the issue may lie on how I'm creating the labels, which happens like this:
QLabel *label = new QLabel;
label->setAlignment( Qt::AlignCenter );
label->setFrameStyle( QFrame::Raised );
label->setMinimumSize( QSize(0,0) );
label->setMaximumSize( QSize(16777215, 16777215) );
label->setSizePolicy( QSizePolicy::Preferred, QSizePolicy::Preferred );
Right now, all the QLabels are the same size, but eventually they will have differing sizes, which is why I'm using a packed-bin algorithm. I designed the algorithm around classes like this:
struct ButtonFittingNode
{
ButtonFittingNode( QRect rectangle, QGridLayout *layout )
: used( false )
, node( rectangle )
, down( NULL )
, right( NULL )
, m_layout( layout )
{ }
~ButtonFittingNode()
{
if ( down ) delete down;
if ( right ) delete right;
}
ButtonFittingNode *findNode( QLabel *btn )
{
int w = 1;
int h = 1;
if ( this->used )
{
ButtonFittingNode *bfn = NULL;
if ( this->right ) bfn = this->right->findNode( btn );
if ( this->down && !bfn ) bfn = this->down->findNode( btn );
return bfn;
}
else if ( ( w <= node.width() ) && ( h <= node.height() ) )
{
qDebug() << "Placing at " << node.x() << node.y();
m_layout->addWidget( btn, node.y(), node.x(), w, h, Qt::AlignJustify );
this->used = true;
this->splitNode( QSize( w, h ) );
return this;
}
return NULL;
}
void splitNode( QSize sz )
{
int w = 0, h = 0;
// Create a down node
w = this->node.width();
h = this->node.height() - sz.height();
if ( h > 0 )
{
QRect n( this->node.x(), this->node.y() + sz.height(), w, h );
this->down = new ButtonFittingNode( n, m_layout );
}
// Create a right node
w = this->node.size().width() - sz.width();
h = sz.height();
if ( w > 0 )
{
QRect n( this->node.x() + sz.width(), this->node.y(), w, h );
this->right = new ButtonFittingNode( n, m_layout );
}
}
bool used;
QRect node;
ButtonFittingNode *down;
ButtonFittingNode *right;
private:
QGridLayout *m_layout;
};
Using a 3x2 grid, I get the following output:
Sorry, I had to blur the text, since this is a work related project, but grey is background, white is the label's background. As you can see, they are tall, but skinny. I want these labels to be tall and fat. Am I setting some of the attributes wrong?

This is all really simple. The size constraint of the layout has got nothing to do with what you're seeing. You're also including a bunch of useless boilerplate code. Get rid of explicit setting of minimum and maximum sizes unless you have some non-default sizes in mind. This is completely useless:
label->setMinimumSize( QSize(0,0) );
label->setMaximumSize( QSize(16777215, 16777215) );
You need to set the label's sizePolicy to Expanding in the horizontal direction. That's all there's to it:
label->setSizePolicy( QSizePolicy::Expanding, QSizePolicy::Preferred );
I see what you want from your bin packing system, but it won't work very well as-is, since the column/row sizes in the grid will vary as the grid gets resized. Your packer will choose some column/row spans based on then current row/column sizes. Upon resizing, the packer's decisions won't be adequate anymore.
What you really want is a custom layout that does all the packing on the fly. The flow layout example may be very close to what you want.

Related

How to know header text width in QStyle?

I have a QTreeView and use ProxyStyle for that.
The pic above is just the header. Now I need to draw the up/down arrow (for sorting items) beside header label as in figure. In order to put the arrow in the correct postion I need to know:
the left margin = distance between the text and left border
the text width
the right margin = distance between the text and the arrow
How can I calculate the text width in this case? I thought about QFontMetrics but dont know how to receive the text to calculate.
In my style I use only drawPrimitive function
void MyStyle::drawPrimitive( PrimitiveElement p_pe, const QStyleOption *p_option, QPainter *p_painter, const QWidget *p_widget ) const
{
int leftmargin = 10;
int rightmargin = 10;
if ( p_pe == PE_IndicatorHeaderArrow )
{
if ( const QStyleOptionHeader *header = qstyleoption_cast<const QStyleOptionHeader *>( p_option ) )
{
QPixmap pix;
if ( header->sortIndicator & QStyleOptionHeader::SortUp )
{
pix = QPixmap( ":/sortUp.png" );
}
else if ( header->sortIndicator & QStyleOptionHeader::SortDown )
{
pix = QPixmap( ":/sortDown.png" );
}
p_painter->drawPixmap( header->rect.left() + leftmargin+ subElementRect( SE_HeaderLabel, p_option, p_widget ).width() + rightmargin, header->rect.top() + pix.height(), pix );
}
}
else
{
QProxyStyle::drawPrimitive( p_pe, p_option, p_painter, p_widget );
}
}
I use subElementRect( SE_HeaderLabel, p_option, p_widget ).width() in this case but it is wrong. How can I calculate the width of the text?
It is all contained in the QStyleOptionHeader. The text width could be obtained by calling:
int textWidth = header->fontMetrics.boundingRect(header->text).width();

Qt Window incorrect size until user event

I'm creating a screen where users can add certain tiles to use in an editor, but when adding a tile the window does not correctly resize to fit the content. Except that when I drag the window or resize it even just a little then it snaps to the correct size immediately.
And when just dragging the window it snaps to the correct size.
I tried using resize(sizeHint()); which gave me an incorrect size and the following error, but the snapping to correct size still happens when resizing/dragging.
QWindowsWindow::setGeometry: Unable to set geometry 299x329+991+536 on QWidgetWindow/'TileSetterWindow'. Resulting geometry: 299x399+991+536 (frame: 8, 31, 8, 8, custom margin: 0, 0, 0, 0, minimum size: 259x329, maximum size: 16777215x16777215).
I also tried using updateGeometry() and update(), but it didn't seem to do much if anything.
When setting the window to fixedSize it will immediately resize, but then the user cannot resize the window anymore. What am I doing wrong here and where do I start to solve it?
Edit
Minimal verifiable example and the .ui file.
selected_layout is of type Flowlayout
The flowlayout_placeholder_1 is only there because I can't place a flowlayout directly into the designer.
Edit2
Here is a minimal Visual Studio example. I use Visual Studio for Qt development. I tried creating a project in Qt Creator, but I didn't get that to work.
Edit3
Added a little video (80 KB).
Edit4
Here is the updated Visual Studio example. It has the new changes proposed by jpo38. It fixes the issue of the bad resizing. Though now trying to downsize the windows causes issues. They don't correctly fill up vertical space anymore if you try to reduce the horizontal space even though there is room for more rows.
Great MCVE, exactly what's needed to easily investigate the issue.
Looks like this FlowLayout class was not designed to have it's minimum size change on user action. Layout gets updated 'by chance' by QWidget kernel when the window is moved.
I could make it work smartly by modifying FlowLayout::minimumSize() behaviour, here are the changes I did:
Added QSize minSize; attribute to FlowLayout class
Modifed FlowLayout::minimumSize() to simply return this attribute
Added a third parameter QSize* pMinSize to doLayout function. This will be used to update this minSize attribute
Modified doLayout to save computed size to pMinSize parameter if specified
Had FlowLayout::setGeometry pass minSize attribute to doLayout and invalidate the layout if min size changed
The layout then behaves as expected.
int FlowLayout::heightForWidth(int width) const {
const int height = doLayout(QRect(0, 0, width, 0), true,NULL); // jpo38: set added parameter to NULL here
return height;
}
void FlowLayout::setGeometry(const QRect &rect) {
QLayout::setGeometry(rect);
// jpo38: update minSize from here, force layout to consider it if it changed
QSize oldSize = minSize;
doLayout(rect, false,&minSize);
if ( oldSize != minSize )
{
// force layout to consider new minimum size!
invalidate();
}
}
QSize FlowLayout::minimumSize() const {
// jpo38: Simply return computed min size
return minSize;
}
int FlowLayout::doLayout(const QRect &rect, bool testOnly,QSize* pMinSize) const {
int left, top, right, bottom;
getContentsMargins(&left, &top, &right, &bottom);
QRect effectiveRect = rect.adjusted(+left, +top, -right, -bottom);
int x = effectiveRect.x();
int y = effectiveRect.y();
int lineHeight = 0;
// jpo38: store max X
int maxX = 0;
for (auto&& item : itemList) {
QWidget *wid = item->widget();
int spaceX = horizontalSpacing();
if (spaceX == -1)
spaceX = wid->style()->layoutSpacing(QSizePolicy::PushButton, QSizePolicy::PushButton, Qt::Horizontal);
int spaceY = verticalSpacing();
if (spaceY == -1)
spaceY = wid->style()->layoutSpacing(QSizePolicy::PushButton, QSizePolicy::PushButton, Qt::Vertical);
int nextX = x + item->sizeHint().width() + spaceX;
if (nextX - spaceX > effectiveRect.right() && lineHeight > 0) {
x = effectiveRect.x();
y = y + lineHeight + spaceY;
nextX = x + item->sizeHint().width() + spaceX;
lineHeight = 0;
}
if (!testOnly)
item->setGeometry(QRect(QPoint(x, y), item->sizeHint()));
// jpo38: update max X based on current position
maxX = qMax( maxX, x + item->sizeHint().width() - rect.x() + left );
x = nextX;
lineHeight = qMax(lineHeight, item->sizeHint().height());
}
// jpo38: save height/width as max height/xidth in pMinSize is specified
int height = y + lineHeight - rect.y() + bottom;
if ( pMinSize )
{
pMinSize->setHeight( height );
pMinSize->setWidth( maxX );
}
return height;
}
I was having the same exact issue (albeit on PySide2 rather than C++).
#jpo38's answer above did not work directly, but it un-stuck me by giving me a new approach.
What worked was storing the last geometry, and using that geometry's width to calculate the minimum height.
Here is an untested C++ implementation based on the code in jpo38's answer (I don't code much in C++ so apologies in advance if some syntax is wrong):
int FlowLayout::heightForWidth(int width) const {
const int height = doLayout(QRect(0, 0, width, 0), true);
return height;
}
void FlowLayout::setGeometry(const QRect &rect) {
QLayout::setGeometry(rect);
// e-l: update lastSize from here
lastSize = rect.size();
doLayout(rect, false);
}
QSize FlowLayout::minimumSize() const {
// e-l: Call heightForWidth from here, my doLayout is doing things a bit differently with regards to margins, so might have to add or not add the margins here to the height
QSize size;
for (const QLayoutItem *item : qAsConst(itemList))
size = size.expandedTo(item->minimumSize());
const QMargins margins = contentsMargins();
size += QSize(margins.left() + margins.right(), margins.top() + margins.bottom());
size.setHeight(heightForWidth(qMax(lastSize.width(), size.width())));
return size;
}
int FlowLayout::doLayout(const QRect &rect, bool testOnly) const {
int left, top, right, bottom;
getContentsMargins(&left, &top, &right, &bottom);
QRect effectiveRect = rect.adjusted(+left, +top, -right, -bottom);
int x = effectiveRect.x();
int y = effectiveRect.y();
int lineHeight = 0;
for (auto&& item : itemList) {
QWidget *wid = item->widget();
int spaceX = horizontalSpacing();
if (spaceX == -1)
spaceX = wid->style()->layoutSpacing(QSizePolicy::PushButton, QSizePolicy::PushButton, Qt::Horizontal);
int spaceY = verticalSpacing();
if (spaceY == -1)
spaceY = wid->style()->layoutSpacing(QSizePolicy::PushButton, QSizePolicy::PushButton, Qt::Vertical);
int nextX = x + item->sizeHint().width() + spaceX;
if (nextX - spaceX > effectiveRect.right() && lineHeight > 0) {
x = effectiveRect.x();
y = y + lineHeight + spaceY;
nextX = x + item->sizeHint().width() + spaceX;
lineHeight = 0;
}
if (!testOnly)
item->setGeometry(QRect(QPoint(x, y), item->sizeHint()));
x = nextX;
lineHeight = qMax(lineHeight, item->sizeHint().height());
}
int height = y + lineHeight - rect.y() + bottom;
return height;
}

Object coordinates

I'm working at QT application that have a OSGWidget. How could i get coordinates of picked object in osg viewer and transfer it to QT textEdit in Dialog window. Could you give advice how to extract osg::Vec3d of selected object from viewer convert it to string and show it off? For example. I have these scene:
And when i click on add button it opens a dialog window where i have a textEdit object. Transfer coords in this editText. How could be this done? Forget to add that this cubes are imported from ads file. Probably it can help in any place.
In order to retrieve an object's coordinates from your scene, you'll need to add new event handler to your viewer. Let's call it a PickHandler.
Here's a basic code that will get you started. You'll need to add the "includes" and modify it to suit your needs. (Please note that I haven't tested it. I wrote it "by memory", but if there are any errors the should be very easy to fix).
PickHandler.h
class PickHandler: public QObject, public osgGA::GUIEventHandler {
Q_OBJECT
public:
PickHandler();
virtual bool handle( const osgGA::GUIEventAdapter& ea,
osgGA::GUIActionAdapter& aa );
signals:
void query( osg::Vec3f );
protected:
virtual ~PickHandler()
{
}
bool pick( const double x, const double y, osgViewer::Viewer* viewer );
private:
bool getPickedPoint( const double x, const double y, float buffer,
osgViewer::Viewer* viewer, osg::Vec3f& point );
};
PickHandler.cpp
PickHandler::PickHandler() : osgGA::GUIEventHandler()
{
}
bool PickHandler::handle( const osgGA::GUIEventAdapter &ea,
osgGA::GUIActionAdapter &aa )
{
osgViewer::View* viewer = dynamic_cast<osgViewer::Viewer*>( &aa );
if( !viewer ) {
return false;
}
switch( ea.getEventType() ) {
default:
break;
case osgGA::GUIEventAdapter::RELEASE: {
if( pick(ea.getXnormalized(), ea.getYnormalized(),
viewer ) )
{
return true;
}
break;
}
}
return false;
}
bool PickHandler::pick( const double x, const double y,
osgViewer::Viewer* viewer )
{
if( !viewer->getSceneData() ) {
return false;
}
osg::Vec3f point;
float buffer = 0.005f;
if( getPickedPoint( x, y, buffer, viewer, point ) ) {
emit query( point );
}
return false;
}
bool PickHandler::getPickedPoint( const double x, const double y, float buffer,
osgViewer::Viewer* viewer,
osg::Vec3f& point )
{
osg::ref_ptr<osgUtil::PolytopeIntersector> intersector( 0 );
try {
intersector = new osgUtil::PolytopeIntersector(
osgUtil::Intersector::PROJECTION,
x - buffer, y - buffer, x + buffer,
y + buffer )
;
} catch( const std::bad_alloc& ) {
return false;
}
// DimZero = check only for points
intersector->setDimensionMask( osgUtil:: PolytopeIntersector::DimZero );
//
intersector->setIntersectionLimit( osgUtil::Intersector::LIMIT_NEAREST );
osgUtil::IntersectionVisitor iv( intersector );
viewer->getCamera()->accept( iv );
if( intersector->containsIntersections() ) {
osgUtil::PolytopeIntersector::Intersection intersection =
*( intersector->getIntersections().begin() )
;
const osg::Vec3f& P = intersection.intersectionPoints[ 0 ];
if( P.isNaN() ) {
return false;
}
point.set( P[ 0 ], P[ 1 ], P[ 2 ] );
return true;
}
return false;
}
I'm using a PolytopeIntersector, since I don't have any solid models, like the cessna in the OSG's example data; only a lot of points and using a LineIntersector (the fastest) is almost impossible to get a hit. The Polytope will build a frustrum volume intersects with anything in the area you have specified (with the parameters when constructing the Polytope).
Also, you might need to play with the parameters you send to the pick function, like the buffer size. I use ea.getXNormalized() and inside pick() an osgUtil::Intersector::PROJECTION value.
If you use, say an osgUtil::Intersector::WINDOW value, you don't need to normalize the mouse values. If you don't have any "strange" transformations in your view, most likely the PROJECTION value is what you need.
Last thing, this code is rather old. With newer osg versions, maybe some of it will be seen as deprecated. I'm not sure as I haven't updated yet my picker code.
Now, with this code, when an itersection is found, it retrieves the FIRST one and send the point values via an emit. You just have to connect this emit to your SLOT and receive the cliked point coords.
Lastly, for converting something to a string, you can use the Qt String function, QString::number(...).
For example:
const QString x = QString::number( point[0], 'd', 6 );
will stringify the x-coord of the point using fixed-point format with 6 decimal places.

Printing QTableView using render method

I am trying to print a table view. To fill a table view I have created my own model. To print table I am doing following:
QPrinter printer;
QPrintDialog printDialog( &printer, 0);
if( QDialog::Accepted == printDialog.exec() ) {
if( QPrinter::Landscape != printer.orientation() ) {
printer.setOrientation(QPrinter::Landscape);
}
QPoint startPoint = QPoint(20, 20);
QRegion printRegion = QRegion( 20, 20, printer.paperRect().width(),printer.paperRect().height() );
for( int i = 0; i < m_tables.size(); ++i ) {
tableView->render( &printer, startPoint, printRegion, QWidget::DrawChildren );
}
}
The issue is that I am printing into PDF file and there I am able to see only a small part of the table. I thought that changing the region parameter could help, but in the fact not. Any suggestions how to fix this?
Ok, here is my solution. Would be nice to hear your opinion.
PrintTableModel* pTableModel = new PrintTableModel();
QTableView* pTableView = new QTableView;
pTableView->setModel(pTableModel);
int width = 0;
int height = 0;
int columns = pTableModel->columnCount();
int rows = pTableModel->rowCount();
pTableView->resizeColumnsToContents();
for( int i = 0; i < columns; ++i ) {
width += pTableView->columnWidth(i);
}
for( int i = 0; i < rows; ++i ) {
height += pTableView->rowHeight(i);
}
pTableView->setFixedSize(width, height);
pTableView->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
pTableView->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
This code helped me. To print the table correctly, you just can perform a render call:
pTableView->render(printer);
You might try void QPrinter::setResolution ( int dpi ) to force a number of widget pixels per printer inches, effectively zooming your widget on the printout.

Get intermediate color from a gradient

Say I have a liner gradient as shown:
QLinearGradient linearGrad(QPointF(0, 0), QPointF(0, 100));
linearGrad.setColorAt(1, Qt::red);
linearGrad.setColorAt(0.5, Qt::yellow);
linearGrad.setColorAt(0, Qt::green);
How to get the color of the point QPointF(0, 28.5) in this gradient?
Indeed I want to have this kind of color distribution to be able to choose intermediate colors. I don't care if it is done by using QLinearGradient or something else.
I store the colors of gradient in one QList and then compute with color interpolation.
QColor ColorGradient::getColor(double value)
{
qDebug()<< "ColorGradient::getColor:";
//Asume mGradientColors.count()>1 and value=[0,1]
double stepbase = 1.0/(mGradientColors.count()-1);
int interval=mGradientColors.count()-1; //to fix 1<=0.99999999;
for (int i=1; i<mGradientColors.count();i++)//remove begin and end
{
if(value<=i*stepbase ){interval=i;break;}
}
double percentage = (value-stepbase*(interval-1))/stepbase;
QColor color(interpolate(mGradientColors[interval],mGradientColors[interval-1],percentage));
return color;
}
QColor ColorGradient::interpolate(QColor start,QColor end,double ratio)
{
int r = (int)(ratio*start.red() + (1-ratio)*end.red());
int g = (int)(ratio*start.green() + (1-ratio)*end.green());
int b = (int)(ratio*start.blue() + (1-ratio)*end.blue());
return QColor::fromRgb(r,g,b);
}
Mason Zhang answer does work, and very well !
Let controlPoints() return a QMap<qreal,QColor>, with a key between 0.0 and 1.0.
Here is how i did (thanks to Mason Zhang)
QColor getColor(qreal key) const
{
// key must belong to [0,1]
key = Clip(key, 0.0, 1.0) ;
// directly get color if known
if(controlPoints().contains(key))
{
return controlPoints().value(key) ;
}
// else, emulate a linear gradient
QPropertyAnimation interpolator ;
const qreal granularite = 100.0 ;
interpolator.setEasingCurve(QEasingCurve::Linear) ;
interpolator.setDuration(granularite) ;
foreach( qreal key, controlPoints().keys() )
{
interpolator.setKeyValueAt(key, controlPoints().value(key)) ;
}
interpolator.setCurrentTime(key*granularite) ;
return interpolator.currentValue().value<QColor>() ;
}
There is only way to make it:
There is a static member in QPixmap class
QPixmap QPixmap::grabWindow( WId window, int x = 0, int y = 0, int width = -1, int height = -1 )
1) draw your gradient on your widget;
2) grab you widget's surface into pixmap using that function; WId can be received from QWidget::effectiveWinId ();
3) convert token pixmap into QImage (there is an constructor available);
4) int QImage::pixelIndex( int x, int y ) returns the pixel index at (x, y) in QImage's color table. In your case you must calculate percentage value from widget's height ( pWidget->height() / 100 * 28.5 ).
5) QRgb QImage::color( int i ) returns the color in the color table at index i.
So returned Color is the color you were seeking.
QVariantAnimation has the similar functionality, and QVariantAnimation::keyValueAt can return the value you need. You may step into the code of QVariantAnimation and see how keyValueAt works.