| Trolltech | Documentation | Qt Quarterly | « Qt 3.1's SQL Sequel | Prize Puzzle Solution » |
| Trading Height for Width |
| by Jasmin Blanchette |
Since the release of Qt 2.0, layouts have become an important part of Qt programming. Layouts relieve the programmer from having to specify the position of all of a form's child widgets, and usually result in more attractive forms. This article presents one problem that can arise with layouts, and for which no perfect solution exists: "height-for-width." It also presents the source code of a fish- and layout-friendly Aquarium widget.
| The Minimum-Size Paradox |
Qt's built-in widgets reimplement sizeHint() and minimumSizeHint() to help layout managers do their job. For example, an "OK" button might have a size hint of (40, 25), meaning that the layout should give it at least 40 pixels horizontally and 25 pixels vertically.
However, some kinds of widget have more advanced requirements. For example, here are screenshots of a QMenuBar at three different sizes:
The screenshots clearly show that a wide QMenuBar doesn't need to be tall, and that a tall one doesn't need to be wide. QLabel with word-wrapping turned on shows the same behavior:
Screenshots #1 and #2 show that the label can be reduced in size to 102 pixels horizontally or to 55 pixels vertically, so long as there is enough space in the other direction. Screenshot #3 shows what happens when the label is squeezed down to its minimum height and minimum width.
We would like QLabel to tell the layout that screenshot #1 and screenshot #2 are acceptable but that screenshot #3 is not. The sizeHint() and minimumSizeHint() functions cannot do this, so Qt provides a complementary mechanism: height-for-width.
Every widget's QSizePolicy contains a boolean height-for-width flag that indicates whether or not the widget is able to trade width for height and height for width. The layout will call the virtual function QWidget::heightForWidth() as necessary to determine the desired height for a height-for-width widget with a given width.
For the QMenuBar shown earlier, we have these values:
All of this is handled automatically by Qt, so you rarely need to intervene.
| Trouble at the Top |
Things start to get more complicated when height-for-width widgets are top-level widgets, because these are not managed by a layout class. While top-level QLabels, QMenuBars, or QTextEdits are rare, it is very common to have top-level forms made up of layouts that contain sublayouts that contain height-for-width QLabels, QMenuBars, or QTextEdits. The form inherits the layout behavior of its child widgets, which can lead to undesirable results. Here's an example Setup dialog:
While screenshots #1 and #2 look reasonable, screenshot #3 cuts off the buttons and some of the text. The best way to handle such forms is to allow the user to freely resize them. That's the approach taken by most modern applications, including Microsoft Visual Studio and Internet Explorer.
Before Qt 3.1, the layout classes set the form's minimum size to the minimum size hint, resulting in an undesirable "blocking" behavior. The solution was to add this line to the program:
dialog->layout()->setResizeMode( QLayout::FreeResize );
| Writing Widgets with Height-for-Width |
We will now write an Aquarium widget with height-for-width. The Aquarium widget contains a certain number of fish. For the fish to survive, they each need a certain amount of space. Unsurprisingly, this brings us back to height-for-width: A wide aquarium doesn't need to be deep, and a deep aquarium doesn't need to be wide.
The screenshots show how the layout adapts to the changing needs of the Aquarium widget each time we add a fish into it.
The Aquarium class inherits QFrame and reimplements three virtual functions. Its definition follows:
class Aquarium : public QFrame
{
Q_OBJECT
public:
Aquarium( int numFish, QWidget *parent = 0, const char *name = 0 );
virtual int heightForWidth( int width ) const;
virtual QSize sizeHint() const;
public slots:
void setCapacity( int numFish );
protected:
virtual void drawContents( QPainter *painter );
QPixmap fish;
int capacity;
};
Aquarium::Aquarium( int numFish, QWidget *parent, const char *name )
: QFrame( parent, name ), fish( "fish.png" ),
capacity( numFish )
{
setPalette( QPalette(QColor("light blue")) );
setFrameStyle( Box | Raised );
setSizePolicy( QSizePolicy(QSizePolicy::Preferred, QSizePolicy::Preferred, TRUE) );
}
The heightForWidth() function is reimplemented from QWidget to compute the space based on the number of fish:
int Aquarium::heightForWidth( int width ) const
{
return 10000 * capacity / QMAX( width, 1 );
}
The sizeHint() function is reimplemented from QWidget to provide a decent default size for the widget:
QSize Aquarium::sizeHint() const
{
int w = (int) ( 100 * sqrt(capacity) );
return QSize( w, heightForWidth(w) );
}
void Aquarium::setCapacity( int numFish )
{
if ( capacity != numFish ) {
capacity = numFish;
updateGeometry();
update();
}
}
The drawContents() function is reimplemented from QFrame to draw the fish:
void Aquarium::drawContents( QPainter *painter )
{
srand( capacity );
QSize size( width() - fish.width(), height() - fish.height() );
size = size.expandedTo( QSize(1, 1) );
for ( int i = 0; i < capacity; i++ ) {
int x = rand() % size.width();
int y = rand() % size.height();
painter->drawPixmap( x, y, fish );
}
}
| The Tall and the Short of It |
The base QWidget implementation of heightForWidth() returns 0 to signify that a widget's height and width are independent. This is appropriate for most QWidget subclasses. Classes like QLabel, QMenuBar, and QTextEdit reimplement heightForWidth().
Implementing a heightForWidth() function is not difficult. In the case of the Aquarium class, we reimplemented heightForWidth() as a monotonically decreasing function. But Qt's layout system makes no such assumption, so we can reimplement heightForWidth() as we like -- for example, to ensure that a widget maintains a constant aspect ratio.
This document is licensed under the
Creative Commons Attribution-Share Alike
2.5 license.
| Copyright © 2002 Trolltech. | Trademarks | Prize Puzzle Solution » |