Tải bản đầy đủ (.pdf) (45 trang)

The book of qt 4 the art of building qt applications - phần 4 pot

Bạn đang xem bản rút gọn của tài liệu. Xem và tải ngay bản đầy đủ của tài liệu tại đây (605.67 KB, 45 trang )

4 Developing aGUI Application Ba sedonaMainWin dow
Qt::RightDockWidgetArea);
templateDocker->setObjectName("TemplateDocker");
templateDocker->setWindowTitle(tr("Templates"));
addDockWidget(Qt::LeftDockWidgetArea,templateDocker);
QListView
*
view=newQListView();
templateDocker->setWidget(view);
newTemplateHandler(view,textEdit,this);
Themainwindowrequires theobje ct name to savethe windowproperties. We
willdiscuss this topicatthe endofSection 4.8. If thenameismissing,Qtcom-
plains at runtimeonthe standardoutput. Unfortunately,itisnot possible to set
thewindowTitle attribute of QDockWidgetinthe Designer,which is whyit is im-
portant that this mustbedoneseparatelyin theconstructor.windowTitle labels
thewindowandalsogives anametothe toggleactionthatisgenerated bytog-
gleViewAction().
In thefinalstep webreathe lifeintothe widgetbyfillingitwithalistview.Wewill
later findthe templates in this view.The TemplateHandler classnowinstantiated
is responsible for fillingthe listand for insertingtemplates at thecurrent cursor
positioninthe editor window:
// cuteedit2/templatehandler.cpp
TemplateHandler::TemplateHandler(QListView
*
view,QTextEdit
*
textEdit,
QObject
*
parent) :QObject( parent),mTextEdit(textEdit)
{


mModel =newQStringListModel(this);
QStringList templates;
templates<< "<html>"<< "</html>"<< "<body>"<< "</body>";
mModel->setStringList( templates);
view->setModel(mModel);
connect(view,SIGNAL(clicked(const QModelIndex&)),
SLOT(insertText(const QModelIndex&)));
}
In Qt 4, listviewsworkonthe basisofthe model/view principleintroducedin
Chapter 8: A model is responsible for obtainingdata, while the view displaysthe
data. In thecaseofour templates,one modelisenough, which takesdatadir ectly
from aQStringList.Asbefore, theseare fedwithseveral templates,inthiscasefor
HTML.
16
.
We passthe listcreated in this wayto themodelvia setStringList()and turn this
into thereference modelfor ourview,the listview.The listviewis nowfilled, and
16
In aproperapplicationthe templatesare notcompiledstatically,ofcourse, butare loaded from
afile.
134
4.7 Dock Windows
wejustneed to includethe selected templateinthe editor window.Todothis,
weconnect theclicked()signalofthe viewandimplement theinsertText()method
(which wemustfirstdeclare in theclass definitionasaslot,ofcourse) :
// cuteedit2/templatehandler.cpp (continued)
void TemplateHandler::insertText( const QModelIndex&index)
{
QString text =mModel->data(index,Qt::DisplayRole).toString();
QTextCursorcursor=mTextEdit->textCursor();

cursor.insertText(text);
mTextEdit->setTextCursor(cursor);
}
Themodelindexpassedrepresentsthe selected lineinour model. Using thedata()
method, wecan obtain thedataasQVariantfromthis, which wemuststill convert
into aQString.QVariantworks in asimilarwayto aunion in C++. Theclass can
also convertvarious types—both Qt-specificdatatypessuchasQString andQSize,
as wellasC++ typessuchasint or double—fromone to another.
Figure 4.13:
Thetemplate dock
window in doc ked
condition: Aclick
insertsthe text into
thecorresponding
lineinthe editor
window.
Themodel/viewconceptofQthas manydifferent roles for amodelindex(see table
8.1onpage 209; forinstance, manyviewscan displayan icon (Qt::DecorationRole),
in additiontonormaltext(Qt::DisplayRole). At themoment, however,onlyQt::
DisplayRole is relevanttous.
135
4 Developing aGUI Application Ba sedonaMainWin dow
ThetextCursor()method of thetextwindowrepresents thecurrent positionofthe
writing cursor.
17
We passthe text, which it should insert at thecursorposition, to
theinstance. Nowwemustinsertthe textcursortothe currentcursorposition
again, usingsetTextCursor, to updatethe cursor.
18
Ourdockwindowis nowcompletelyimplemented.Thankstothe QObject base

classand thefact that wepassthe main windowas theparentobject,wedonot
need to delete th einstanceofTemplateHandler manually.The result is shownin
Figure 4.13.
4.8SavingPreferences
Last butnot least, ourprogram should be able to keep thesettings made bythe
user,even after theprogram is restarted.Tothisend,different conventionshave
become establishedondifferent operating systems.
Dependingonthe platform, applicationdatamaybe stored in theWindowsReg-
istry(inthe user scope HKEY_LOCAL_MACHINE\Software) or in thesystem scope
HKEY_CURRENT_USER\Software),inanXML-based .plist fileunder MacOSX,orin
/etc/xdg,
19
(system-widesettings)or~/.config(user-definedsettings)under Unix.
Qt encapsulates accesstothese configuration storage systemswiththe help of
theQSettingsclass.Everyfilingsystem in this is a backend .QSettingsobjects
can be created either on theheaporonthe stack.Since littleworkisneeded
to instan tiatethem, werecommend that you create them on thestack, if this is
necessary.
In casetwoormoreQSettingsinstances areworking withthe same data, theclass
ensuresthatdatabetween different instancesisalwayscorrectlysynchronized,in
casetwoormoreQSettingsobjectsare working withthe same file. Thesame
appliesfor twothreads,bothofwhich containaQSettingsobject withthe linkto
thesamefile,and even for twodifferent processes, in casebothare usingQSettings
linkedtoacommonfile.Qtusesinternallocking mechanisms for this purpose.
TheQSettingsconstructor normallyrequ ires twoparametersfor theinstantiation
in ordertogeneratethe appropriate entryin theconfiguration storage system:the
name of theorganizationfor which theprogrammerworks,and thenameofthe
program.InWindows,
17
TheQTextCursorcla ss in generaldoesnot havetodescribe thecurrentlyvisible cursor,but it

canmanipulatetextatanypositionatall.
18
This is necessarybecauseQTextCursorworks notinapointer-based manner, butinavalue-
basedone,and wetherefore workwithacopycreated withthe allocation to thecursorvariable.
19
Thedirectoryname standsfor an abbreviation of XDesktop Group thenow-obsolete
generictermfor theFreedesktop.orgdevelopers.See alsohttp://www.r edhat.com/archives/xdg-
list/2003-March/msg00041.html.
136
4.8 Saving Preferences
QSettingssettings("OpenSourcePress","CuteEdit");
would referencethe registrypath
HKEY CURRENT USER\ Software \ OpenSourcePress\ CuteEdit
If aprogrammergenerates such QSettingsinstances at manylocationsinthe code,
it would be agood idea nottohavetoconstantlypassthe parameters. This is
possible if wefeed theapplicationitselfwithprogram andorganizationdetails,
preferablystraight in themain() function:
QCoreApplication::setOrganizationName("OpenSourcePress");
QCoreApplication::setOrganizationDomain("OpenSourcePress.de");
QCoreApplication::setApplicationName("CuteEdit");
From nowon,QSettingsmakesuse of thesedetails,sothataninstancewithout
parametersisall that is needed:
QSettingssettings;
It is surprising th at setOrganizationDomain() method exists,since wehavejust
managedwithout it.But it is justified throughthe waythat MacOSXstores its
settings: it triestosortthe organizations according to an inverted domainname
pattern. If thedomaindetails aremissing,QSettingscreates artificialdetails from
theorganizationname. If setOrganizationDomain() is specified correctly,the file-
namesinOSXareasfollows:
$HOME/Library/Preferences/de.OpenSourcePress.CuteEdit.plist

$HOME/Library/Preferences/de.OpenSourcePress.plist
/Library/Preferences/de.OpenSourcePress.CuteEdit.plist
/Library/Preferences/de.OpenSourcePress.plist
It is notabsolutelyessentialtospecifythedomain, butitshouldnot be left out
in casethe organizationhas arealdomainname. Thefirsttwoparts specifythe
user scope,and thelasttwospecifythesystem scope,adistinctionthat—as hinted
above—concerns allthree platforms.
In theuserscope (QSettings::UserScope)anapplicationsaves allthe applications
involvingjustthatuser, while in thesystem scope (QSettings::SystemScope )itsaves
datathatare important for allusers. Because writing in thesystem scope generally
requires root or administrator rights,the followingconstructor is normallyrelevant
onlyfor installation programs:
20
20
Never assume that theuserhas administrator rights,even if this is standardpracticeinmany
Windowshomeinstallations.
137
4 Developing aGUI Application Ba sedonaMainWin dow
QSettingssettings(QSettings::SystemScope);
QSettingsnowignoresthe user scope andreads andwrites exclusivelyin thesys-
tem scope.Ifyou specifyQSettings::UserScope instead, th eclass behaves as if it
was calledvia thestandardconstructor.QSettingslooksinthisfor asetting, first
in theuserscope.Ifthe object is notfound there, it then looksfor it in thesystem
scope.
To write theactualdata, QSettingsprovides thesetValue() call, which expectsa
keyandthe actualvalue.The value itself is of theQVarianttype,withwhich we
arealreadyfamiliar. Thefollowingcode first stores avalue in thesystem-specific
configuration backend an dthenreads it out:
// configtest/main.cpp
// manufacturer,product

QSettingssettings("OpenSourcePress","ConfigTest");
QString hello ="Hello, world!";
// storeavalue
settings.setValue("Greeting",hello);
// resetvariable
hello ="";
// readvalueand assign tovariable
hello =settings.value("Greeting").toString();
qDebug() << hello;//prints "Hello, world!"
Theexplicit conversion to aQString usingtoString()isnecessarybecause C++is
notinapositiontocorrectlyconvertthe QVariant value returned byQt because
QStringhas no knowledge of QVariant,and thus it doesnot provideanassignment
operator.
Afteritisrun, theprogram generates afile in Unixcalled~/.config/OpenSource
Press/ConfigTest.conf withthe contents
[General]
Greeting=Hello, world!
Since wehavenot specified anygroup, QSettingsstoresthe keyin the[General]
standardgroup.There aregenerallytwomethods of naming aspecific group. On
onehand, wecan specifythedesired groupbeforeone or more setValue() calls,but
wemustremovethissettingafterwardifwewanttocontinue usingthe object for
otherpurposes:
settings.beginGroup("MyGroup");
settings.setValue("Greeting",hello);
settings.endGroup();
138
4.8 Saving Preferences
On theother hand,wecan simplyplacethe name of th egroup in frontofthe ke y,
separated byaslash:
settings.setValue("MyGroup/Greeting",hello);

In both cases theresultlookslikethis:
[MyGroup]
Greeting=Hello, world!
UnderWindows, groups aresubpaths of thecurrent applicationpathinthe Reg-
istry,whereas MacOSXstructures them th roughXML tags.
4.8.1Extending CuteEdit
To useQSettingsinCuteEdit, wefirstset up twomethods for reading andwriting
in MainWindow:readSettings() andwriteSettings().
We callwriteSettings()inthe destructor.Thisgenerates anewQSettingsobject and
saves thesizeofthe currentwindowin theSizekeyof theMainWindowgroup. In
thenextstep wesaveall internalsettings for theMainWindow;for instance,the
positions of thetoolbars anddockwindows. To do this,QMainWindowprovides
thesaveState()method, which converts thesepropertiesintoaQByteArray:
// cuteedit2/mainwindow.cpp (continued)
void MainWindow::writeSettings()
{
QSettingssettings;
settings.setValue("MainWindow/Size",size());
settings.setValue("MainWindow/Properties",saveState());
}
We callits counterpart,readSettings(), as thefinalstep in theconstructor of the
class. It reads thesettings andappliesthemtothe finishedmainwindow,using
restoreState(). restoreState()restoresthe internalstatusofthe main window,us-
ingthe read-outQByteArray.But first wemustconvertthe QVariant returned by
value() into aQSizeorQByteArray:
// cuteedit2/mainwindow.cpp (continued)
void MainWindow::readSettings()
{
QSettingssettings;
resize(settings.value("MainWindow/Size",sizeHint()).toSize());

139
4 Developing aGUI Application Ba sedonaMainWin dow
restoreState(settings.value("MainWindow/Properties").toByteArray());
}
Thesecondparameter that wepasstovalue()—sizeHint()—is also unusual. It is the
defaultvalue if thebackendcannotfind thekey.Inspecific cases it ensuresthat
theeditorwindowhasanappropriate initialsize.
140
5
Chapter
LayingOut Widgets
Even if you leaveituptoQttoarrange thewidgets in adialogormainwindow
(aswehavedonesofar), thereisnothing preventing you from doing thelayout
manuallyusingthe classlibraryin specialcases.Inpracticethisisseldom done,
butacloser look at manuallayoutprovides an understanding of theQtlayout
mechanism, which wewillexaminebelow.
5.1ManualLayout
In thetraditional version of GUI design,each widgetis“attached byhand”toa
point in theoverlyingwindowor widget(that is,the widgetthathas been specified
as aparentobject for thegiven GUI element) andfixed valuesfor itsheightand
width aredefined. TheQWidgetclass provides thesetGeometry() method as abasis
classfor nearlyallgraphical elements.Thisexpectsfourinteger parameters: first
141
5 LayingOut Widgets
thevaluesfor thexan dypositions relativetothe parentwidget, followed bythe
height an dwidth.Atthispoint in time theparentwidgetdoe snot havetodisplay
itsownfinalsize.
As an example, wecan look at awindowderived from QWidget(Figure 5.1):
// manually/window.cpp
#include <QtGui>

#include "window.h"
Window::Window(QWidget
*
parent) :QWidget(parent)
{
setFixedSize(640,480);
QTextEdit
*
txt =newQTextEdit(this);
txt->setGeometry(20,20,600,400);
QPushButton
*
btn=newQPushButton(tr("&Close"),this);
btn->setGeometry(520,440,100,20);
}
Figure 5.1:
Asimple, manually
laid outwidget
ThesetFixedSize()method instructsthe windowto acceptafixed,unchanged size.
Then wepositionaneditorwindow(a QTextEditwidget
1
)and abutton.
From thesesetGeometry() calls it is alreadyevident that it is quitedifficulttoguess
thecorrect values. Getting alayoutconstructed in this mannertoworkisacontin-
uous cycleofchoosingcandidatevalues, compiling, andthenadjusting th evalues
to improvethe appearance. It can also be quiteawkwardifthe widgetordialog
1
Forall thosewho havenot (yet)read,orsofar merelybrowsedthrough Chapter4:The QTextEdit
classprovides amultiple-lineinput field fortext, which canbeformattedvia theAPI. In addition
to pure text, it canalsoloadstructuredHTML.

142
5.2 AutomaticLayout
changes: If you wanttoadd anewbuttoninthe middleofanarrangement,for
example, thepositionofall elements placed beneaththe newelementmustbe
modified.
Now,itcan be argued that none of this is aprobleminpractice, sincethe Qt
Designerconsiderablysimplifies thepositioning workinvolved.But even aGUI
designer cannotsolveall problems without usingautomatic layouts.
Oneofthese problems concerns widgets that would look better if theycouldshrink
or grow:Inaninflexible layoutand without additionalaids, such elements—like the
editor windowin theexample—alwaysretain thesamesize, although it would be
nice if theywould adjusttothe available screen sizeoratleast givethe user the
optionofchangingtheir dimensions.
To keep thesizeofthe dialog flexible,wecould replacethe setFixedSize()callwith
theresize()method, which also expectstwointeger parametersoraQSizeparam-
eter.Thisonlyadjusts thesize, anddoesnot fixit.The user can nowchange the
dimensions of th edialogwiththe mouse, although thewidgets that it contains
retain theirdimen sion s.
Alternatively,you couldreimplement theQWidgetmethod resizeEvent(): Qt always
invokes this method whenthe widgetsizechanges.You couldwrite code to com-
pute th enewsizes andpositions of thewindowelements on each resizeeven t.
Butthisprocedure is muchtoo complexin most cases,and also requires manual
calculation of thewidgetproportions.
2
In addition,reimplementingresizeEvent()poses aparticularproblemincombina-
tion withinternationalization: With localized software, thedimensionsofalabeled
widgetmaydepend on thelanguage in which it is displayed.Abuttoncalled Close
in Englishhas amuchlongerlabel in th eGermantranslation ( Schließen), andthe
textwill be cutoff unlessspecial precautionarymeasures aretaken.
Ultimately,wecan onlypatch up thesymptoms in this way.Toactuallysolvethe

underlyingproblem, wecannotavoidusing automaticlayout.
5.2Automatic Layout
TheQLayoutclass andspecialized layouts derived from it help thedeveloperto
positionwidgets dynamically.For this to succeed,each graphic elementderived
from QWidgethas asizeHint()method, which returnshowmuchspacethe widget
would liketooccupyunder normal circumstances.Inthe same way,there is a
minimumSizeHint()method—awidgetmayunder no circumstances be smallerthan
thevalue returned byminimumSizeHint(). BothsizeHintand minimumSizeHintare
properties, which can be changedwiththe corresponding setmeth od.
2
In some casesthispro cedure is veryuseful,however.Anumber of KDEprogramsuse re-
sizeEvent()todisplaystatuswindowsonthe currentlayoutatthe lower-right edge of the
window.
143
5 LayingOut Widgets
Each widgetalsohas a size policy ,which thedevelopercan setfor thehorizontal
andvertical valuesusing setSizePolicy(). Thepurposeofthiscan best be explained
bymeansofanexample: TheQTextEditobject from Figure 5.1should, if possible,
useall of thespaceinthe windownotrequiredbyotherwidgets—thatis, thefully
available width andheight. Since this appliesnot on lyhere,but in generalfor
editor windows, thestandardsettingfor this widgettype defines thesizepolicy
QSizePolicy::Expanding for both directions (thatis, “windowsfor this widgettype
should expandasmuchaspossible”).
Abutton, on th eother hand,shouldonlytake up as muchspaceverticallyas is
specified in thesizeHint(). This is ensuredbyQSizePolicy::Preferred(that is,widgets
of this type should occupytheideal size, if possible). QPushButtons expandin
width as far as possible,because for this directionTrolltechspecifiesQSizePol-
icy::Expanding.
Figure 5.2:
Alllayouts inherit

from the QLayout
baseclass.
QHBoxLayout QVBoxLayout
QBoxLayout QGridLayout QStackedLayout
QLayout
5.2.1Horizontal andVertical Layout
QLayoutasanabstract base classonlycoversthe basicfunctionsfor layouts.Spe-
cific strategies, such as thealr eadyfamiliarhorizontalorvertical layout, arelooked
after bythespecial Qt clustersshowninFigure5.2 inheriting from QLayout.
Thus theQVBoxLayoutclass ,usedinthe exampleonpage 29 andthe following
pages, arranges widgets amongthemselves,vertically.Herethe orderinwhich the
widgets areincludedinthe layoutusing addWidget()iscrucial.
Theexamplefrompage 142nowappears as follows:
// vertically/window.cpp
#include <QtGui>
#include "window.h"
Window::Window(QWidget
*
parent) :QWidget(parent)
{
resize(640,480);
QVBoxLayout
*
lay=newQVBoxLayout(this);
144
5.2 AutomaticLayout
QTextEdit
*
txt =newQTextEdit(this);
lay->addWidget(txt);

QPushButton
*
btn=newQPushButton(tr("&Close"),this);
lay->addWidget(btn);
}
Theresize()instruction is notabsolutelynecessary.Without it,Qtadds themini-
mum sizes of theeditorwindowandthe buttonsuggested byminimumSizeHint()
to the spacing inserted bythelayout, that is,the distance between twowidgets in
alayout. In addition it addsama rgin for thelayoutand fixes thewindowsizeto
thetotal.
Figure 5.3:
Thewidgetwitha
verticallayout
Figure 5.3clearlyshowsthe weaknessesofthe vertical layout: Thebuttontakes
over thefullwidth,which is notwhatwehad in mind.
Thereare twowaysofovercoming this problem. In thefirstcasewemake useof
somethingweare alreadyfamiliarwith, andtake alook at theAPI documenta-
tion of QBoxLayout,
3
theclass from which QVBoxLayoutinherits:The addWidget()
method actuallyhastwoother parameters, stretchand alignment.The latter looks
after thehorizontalalignment of awidget. It is nowpossible to arrangethe button
corr ectly,thankstoQt::AlignRight.
To do this,wesimplyreplacethe last twolines of code abovewiththe following:
QPushButton
*
btn=newQPushButton(tr("&Close"),this);
lay->addWidget(btn, 0,Qt::AlignRight);
Youshouldtrythis method, particularlyif you hadtrouble withthe grid layout
describedinChapter 5.2.2. Gridlayouts remain thebetter choice,particularlyfor

3
See />145
5 LayingOut Widgets
more complexlayouts,inwhich you can easilylose track of whatislined up where
whenusing boxlayouts.
Boxlayouts haveanadditional propertywhich wehavesofar ignored, theso-
called stretch factor. If this doesnot equal0,itdeterminesthe proportional space
occupied bythewidgetinthe overalllayout, in thedirection of theboxlayout.
This assumes, of course,thatthe widgetisinterested in spreading outinthispar-
ticulardirection.Itdoesnot make an ysensefor abutton, for example, to stretch
outverticallyabovethe height or belowthedepth of th etextorthe icon that it
displays.
If this should still be necessary,however,the size policy can be adjusted usingset-
SizePolicy(). Themethod expectstwoparametersherefromthe QSizePolicy::Policy
enumerator(seeTable 5.1),which definethe sizeguidelines for thehorizontaland
vertical stretches.
Table5.1:
TheEnumerator Policy
ValueMeaning
QSizePolicy::Fixed Thewidgetmaynever haveasizeother
than sizeHint().
QSizePolicy::Minimum sizeHint()isthe smallest acceptable size
for thewidget, butthe widgetmaybe en-
larged as muchasyou want.
QSizePolicy::Maximum sizeHint()isthe largestacceptable sizefor
thewidget, butthe widgetmaybe re-
ducedinsizeasmuchasyou want.
QSizePolicy::PreferredsizeHint()isthe optimal size, butthe
widgetmaybe either larger or smaller
than this value (default for QWidget).

QSizePolicy::Expanding As Preferred, butthe widgetdemands any
available space in th elayout.
QSizePolicy::MinimumExpanding As Minimum, butthe widgetabsolutely
demandsanyavailable space in thelay-
out.
QSizePolicy::Ignored IgnoresizeHint()—thewidgetisgiven as
muchspaceaspossible in thelayout.
Butlet’sreturn to thestretchfactor:Thisisillustrated bythefollowingcode ex-
ample, which places fivestretchable textedits nexttoeach other, butassignsa
different stretchtoeach of them:
146
5.2 AutomaticLayout
// stretchfactors/main.cpp
#include <QtGui>
intmain(intargc, char
*
argv[])
{
QApplication a(argc, argv);
QWidgetw;
QHBoxLayout lay(&w);
QTextEdit
*
txtEdit=0;
for(intstretch =1;stretch <= 5;stretch++) {
txtEdit=newQTextEdit(&w);
lay.addWidget(txtEdit,stretch);
}
w.show();
returna.exec();

}
We chooseahorizontallayoutinthisexampleand insert dynamicallygenerated
textfields into it.These containastretchfactor of 1to5,accordingtothe status
of theloop counter.Ascan be seen in Figure 5.4, thewidgets nowtake up more
space,increasingfromlefttoright according to theirfactor.Thus thesecondtext
field hastwiceasmuchspaceasthe first one, thethird,three timesasmuch, and
so on.
Thetexteditbehaves strangelyas soon as thehorizontalwindowsizeisreduced:
Althoughall widgets become proportionatelysmaller, theyalwaystryto attain
theirsmallest possible size(minimumSize()), which is alwaysthe same despitethe
stretchfact or.Therefore, allthe textfields in ourexampleare thesamesizeassoon
as thewindowhasreached itsminimum size.
Figure 5.4:
Stretchfactors
provideindividual
widgets with more
space.
While horizontalstretchesseldom cause problems,hardlyanywidgetwants to be
stretchedvertically.However,ifthe user expands adialoglengthways, thelayouts
insert uglyspacesbetween allthe GUI elements containedinthe dialog window.
This can also be avoidedusing manuallydefined spaces. Oneapproachistodefine
astretchfactor in oneofthe addWiget()calls for asingleposition, to definea
147
5 LayingOut Widgets
preteterminedbreaking point,sotospeak.Analternativeistouse addStretch() to
add astretchofanysizetothe endofthe layout(that is,atthe lower or rightedge,
dependingonthe layouttype).
5.2.2GridLayout
Thebestwayto describe thegridlayoutinQtisprobablyas akindoftable,such
as frequentlyencountered,for example, in HTML or spreadsheet calculations. In

contrast to QBoxLayoutderivatives,the grid layoutclass QGridLayoutalsorequires
information on the column and row in which thelayoutshouldinsertawidget.
As can be seen in Figure 5.2onpage 144, QGridLayoutinherits directlyfrom QLay-
out, so it haspropertiesdiffering from thoseofQBoxLayout-basedlayouts.
In particular, theseinclude anotheraddWidget()method that requires,inaddition
to thewidge ttobeinserted,atleast twomoredetails,namelytherowandthe
column of theinsertion point.For widgets that should take up more space than one
cell, an overloadedversion of themethod exists that expectsfourextraparameters:
thecoordinates of thefirstcelland thenumber of cells that thewidgetshould
cover in each direction.
In addition thesetColumnStretch() andsetRowStretch() methods allowstretchfac-
tors to be setfor individualcolumns or rows. Here thefirstparameter specifies the
rowor column, andthe second parameter specifies therelevantstretchfactor.
Thefollowingexampleimplementsour inputdialogusing agridlayout. Through
addWidget(), it positions thetextfieldatthe coordinates (0, 0) andspecifiesfor it
awidth of twocolumns andaheight of onerow.
Thebuttonisplacedonthe second rowandinthe second column, withcoordinates
(1, 1),because westart from zerowhencountingpositions,asisusual in informa-
tion technology.Stretching thefirstcolumn withaddColumnStretch() then ensures
that th esecondcolumn, in which thebuttonislocated,issquashedup. Using this
trick, thelayoutisrestricted to theoptimal width:
// grid/window.cpp
#include <QtGui>
#include "window.h"
Window::Window(QWidget
*
parent) :QWidget(parent)
{
resize(640,480);
QGridLayout

*
lay=newQGridLayout(this);
QTextEdit
*
txt =newQTextEdit(this);
lay->addWidget(txt,0,0,1,2);
148
5.2 AutomaticLayout
QPushButton
*
btn=newQPushButton(tr("&Close"),this);
lay->addWidget(btn, 1, 1);
lay->setColumnStretch(0,1);
}
5.2.3NestedLayouts
Sometimesitisusefultonestlayouts inside oneanother,for instance if you need
to includeanewlayout, withall itswidgets,inanexisting one. Forthisreason,
QLayoutclassesprovideawayof includingother layouts,withaddLayout(). This
method expectsthe same parametersasthe addWidget()method of thesamelay-
outobject.
Formorecomplexlayouts in particular,the clearhierarchycreated in this wayturns
outtobeveryuseful,especiallyif you wanttoarrange several buttons,asinthe
followingcode:
// nested/main.cpp
#include <QtGui>
intmain(intargc, char
*
argv[])
{
QApplication app(argc, argv);

QWidget
*
w=newQWidget;
QHBoxLayout
*
mainLayout =newQHBoxLayout(w);
QTextEdit
*
txtEdit=newQTextEdit(w);
mainLayout->addWidget(txtEdit);
QVBoxLayout
*
buttonLayout =newQVBoxLayout;
QPushButton
*
cancelBtn=newQPushButton(QObject::tr("&Cancel"),w);
QPushButton
*
okBtn=newQPushButton(QObject::tr("&OK"),w);
QPushButton
*
defaultBtn=newQPushButton(QObject::tr("&Default"),w);
buttonLayout->addWidget(defaultBtn);
buttonLayout->addWidget(cancelBtn);
buttonLayout->addWidget(okBtn);
buttonLayout->addStretch();
mainLayout->addLayout(buttonLayout);
w->show();
returnapp.exec();
}

Byplacingthe individualbuttons in aseparatelayout(buttonLayout),theyare
made to appearasone unittothe overlyinglayoutmainLayout. Youcan nowuse
addLayout()toinsertthe buttonLayoutintomainLayout.
149
5 LayingOut Widgets
Within buttonLayout, useisagainmade of addStretch(): Thevariable emptyspace
created bythis forcesthe buttons upwards andtakesupthe remainingspace.
5.3Splitter
Althoughhorizontaland vertical layouts aredynamic, theycannotbechanged
direct lybytheuser. Butsometimesheshouldbeable to adjustthe spacing between
twoormorewidgets interactively.
This need is fulfilledbytheQSplitter classthat, just likethe standardlayouts,have
an addWidget()(butnoaddLayout()) method. Thepartial widgets inserted using
this method areseparated byaso-called handle,which can be pickedupand
moved byusingthe mouse(Figure 5.5).
Figure 5.5:
Twotext fieldsmoved
with thesplitter
In contrast to theQBoxLayoutclass,there arenospecialized classesfor QSplitter
that de termine whether vertical or horizontallayoutisused. Insteadthe orien-
tation propertydeterminesthe alignment.Itcan be setinthe constructor, or set
later on.Ifnoorientation is specified,Qtcreates horizontalsplitters.
5.3.1BehaviorDuringSizeChanges
Thefreedom of movementallowed theuserbythesplitter is restricted bythe
widgets involved:The smallest sizeisspecifiedbytheminimumSizeHintor(if set)
theminimumSizeproperty.Ifthe user triestoshrinkthe widgetmorethanthis, the
splitter is completelyhiddenbythewidget. This is knownasacollapsible widget.
If you wanttopreventthe user from so “gettingrid of thewidgets,” you can disable
this behaviorwithsetCollapsible(0,false), where 0stands forthe first widgetfrom
theleftfor ahorizontalsplitter,or, withvertical splitters, for thetop widgetinthe

splitter.
150
5.3 Splitter
TheisCollapsible() method, which takesone integer argument,provides information
on whether thewidgetwiththe specified number is collapsible or not. Another
propertyof theadjacentwidget, maximumSize, ensuresthatthe corresponding
area abovethe splitter cannotbemade anysmalleroncethe neighboringwidget
hasachieved itsmaximum size.
Splitterscan react in twowaysifthe user pulls thehandleinone directionwhile
holdingdownthe mousebutton: Theyeither drawagraylineatthe point where
thehandlewould come if themouse buttonisreleased, or else actuallymove
thehandletothe corresponding location.Thislatter method is knownasopaque
resizing(that is,asizechangethat“lets no light in”).
Normallyopaqueresizingisabetter choice,since theusercan directlyseethe re-
sultsofhis actions.Since th is techniquecan often triggeraresizeEvent()under
certaincircumstances,however,uglyartifacts can appear if oneofthe widgets
controlledbythesplitter performs verycomplexdrawingoperations, or is notop-
timallyprogrammed. In this caseitisoften better to disable opaqueresizing, with
setOpaqueResize(false).
5.3.2SavingSplitterPositions andDeterminingthe Widget
Size
To savethe positions of individualsplittersbeyondprogram sessions, theQSplitter
APIprovides themeth ods saveState()and restoreState().
Since saveState()storesall valuesinaQByteArray,the method is ideallysuited to
savingthe sizes of asplitter between oneprogram session andthe next. This is
doneusing theclass presented on page 136, QSettings. If wehadn’t implemented
thetemplates in CuteEditasdockwindowsinChapter 4, butseparated them from
thetextfieldwithasplitter,wecould savethe valuesofasplitter as akey/value
paircalledSplitterSizes in theconfiguration file, withthe followingcode:
QSettingssettings("OpenSourcePress","CuteEdit");

settings.setValue("SplitterSizes",splitter->saveState());
Conversely,the followingcode extract resets thesizeofthe splitterswhenthe
program is started:
QSettingssettings("OpenSourcePress","CuteEdit");
splitter->restoreState(settings.value("SplitterSizes").toByteArray());
Forsituationsinwhich,depending on thealignment of thesplitter,the widthsor
heightsofindividualwidgets arerequiredasindividualinteger values, theQSplitter
APIhas themethods sizes() andsetSizes(), which workwiththe listtype QList<int>.
This meansthatyou can read outthe sizes,for examplebyusingthe foreach macro
defined byQt:
151
5 LayingOut Widgets
foreach(intsize, splitter->sizes())
qDebug("Size: %i",size);
qDebug() is oneofthe debugging macros that works likethe Cfunction printf(),
andreturnsthe errormessage specified in theargument.Weuse it here to quickly
produce output.Details on debugging withQtare containedinAppendixA.
Analogoustoreading outthe currentsplitter sizes,itisalsopossible to change
them bypassing thenewvalueswithsetSizes() in listform:
QList<int>sizes;
sizes<< 20 << 60 << 20;
splitter->setSizes(sizes);
In this example, which assumesasplitter withthree widgets,these arenow20,
60,and 20pixelswide(for ahorizontalsplitter)orhigh(for averticallyarranged
splitter).
5.3.3DefiningRelativeSizes
Just likeanormal layout, QSplitter also provides awayof definingastretchfactor
foreach widgetinserted.Incontrasttolayouts,these mustbespecifiedfor splitters
afterwardusing thesetStretchFactor() method. Since this function also requires the
positionofthe widget, apartfromthe stretch, you first havetodefine theposition

of thewidgetusing theindexOf() method. This returnsthe correctpositionfor a
given widgetorahandle.
Theexamplebelow,documented in Figure 5.6, is derived from thestretchfactor
exampleonpage 147, butnowuses asplitter insteadofalayout. Sin ce splitters
arealignedhorizontallyif no otherdetails aregiven,the result more or less matches
that of aQHBoxLayout—withthe exceptionthatthe spacesbetween widgets now
carryhandleswhich can be used to definethe sizeofthe textedit.
// stretchfactorsplitter/main.cpp
#include <QtGui>
intmain(intargc, char
*
argv[])
{
QApplication a(argc, argv);
QSplitters;
QTextEdit
*
txtEdit=0;
for(intstretch =1;stretch <= 5;stretch++) {
txtEdit=newQTextEdit(&s);
s.addWidget(txtEdit);
s.setStretchFactor(s.indexOf(txtEdit),stretch);
152
5.3 Splitter
}
s.show();
returna.exec();
}
Figure 5.6:
Thestretch factor

example from Figure
5.4also workswith
splitters.
Splittersare often used,for example, to separateamain widgetfromapage bar.
With different-sized monitors, th erelativesizecreated here bytheuse of stretch
factors can quicklybecome anightmare:Ifthe space on thethe screen is too
small, thepage barwillappear toosmall, while on widescreen displays, cu rrently
verypopularonlaptops, theywill take up toomuchspace. In such cases it is better
to specifyafixed initialsizewithsetSizes() andtomanage thesizes defined bythe
user withsaveState()and restoreState().
5.3.4Customizing Handles
Thesplitter handle itself is implemented in theQSplitterHandle class. Butsome-
timesthe standardimplementationisnot enough,for example, if you wanttohave
thesplitter collapsewhenitisdouble-clicked, similartothe waythepage barofthe
Mozilla browserreacts. Then you havetouse yourownimplementation, derived
from QSplitterHandle.WewillcallthisClickSplitterHandle.
Since wewanttoreact to adouble-click, wemustalsoreimplement themouseDou-
bleClickEvent()method, as wellasthe constructor. To be able to usethisdouble-
clickable handle in asplitter,the design of QSplitter also forcesustocreatea
subclass of theactualsplitter.Amethod th at allowsaQSplitterHandle instance to
be setisnot enough,since asplitter—asalreadyexplained—can haveanynumber
of handles.
Because thecopyoperators of QWidget-basedwidgets aredis abled, theQSplitter
APIperforms atrick: Theclass hasaprotected method calledcreateHandle(), which
is allowed to overwrite subclasses. Theonlypurposeofthis factorymethod con-
sistsofcreatinganewinstance of QSplitterHandle or asubclass. QSplitter then
uses this method whencreatingnewhandles.
In thefollowingexamplewewillthereforecreate, beside sthe subclass of QSplitter-
Handle calledClickSplitterHandle,aclasswiththe name of ClickSplitter,which in-
153

5 LayingOut Widgets
herits directlyfrom QSplitter andwhich overwrites onlythecreat eHandle()method:
// clicksplitter/clicksplitter.h
#include <QSplitter>
#include <QSplitterHandle>
class ClickSplitterHandle :public QSplitterHandle
{
Q_OBJECT
public:
ClickSplitterHandle(Qt::Orientation o, QSplitter
*
parent=0);
void mouseDoubleClickEvent(QMouseEvent
*
e);
private:
intlastUncollapsedSize;
} ;
class ClickSplitter:public QSplitter
{
Q_OBJECT
friend class ClickSplitterHandle;
public:
ClickSplitter(Qt::Orientation o, QSplitter
*
parent=0)
:QSplitter(o, parent) {}
ClickSplitter(QSplitter
*
parent=0)

:QSplitter(parent) {}
protected:
QSplitterHandle
*
createHandle() {
returnnewClickSplitterHandle(orientation(),this);
}
} ;
Theimplementationiscentered on themouseDoubleClickEvent()method. In the
constructorweinitializeonlytheclass variable lastUncollapsedSize, which welater
requireinmouseDoubleClickEvent()sothatwecan remember howlargethe widget
was beforeitwas collapsed:
// clicksplitter/clicksplitter.cpp
#include "clicksplitter.h"
#include <QtGui>
ClickSplitterHandle::ClickSplitterHandle(Qt::Orientation o,
QSplitter
*
parent) :QSplitterHandle(o, parent)
{
lastUncollapsedSize=0;
}
void ClickSplitterHandle::mouseDoubleClickEvent(QMouseEvent
*
e)
154
5.3 Splitter
{
QSplitter
*

s=splitter();
Qt::Orientation o=s->orientation();
intpos=s->indexOf(this);
QWidget
*
w=s->widget(pos);
if (lastUncollapsedSize==0)
if (o==Qt::Horizontal)
lastUncollapsedSize=w->sizeHint().width();
else
lastUncollapsedSize=w->sizeHint().height();
intcurrSize=s->sizes().value(pos-1);
if (currSize==0)
moveSplitter(lastUncollapsedSize);
else {
lastUncollapsedSize=currSize;
moveSplitter(0);
}
}
In ClickSplitterHandle::mouseDoubleClickEvent()wefirstdetermine thealignment
of thesplitter.Weobtainthe positionofthe splitter,using theQSplitter::indexOf()
method. This is also thepositionofthe widgetlyingtothe rightof(or directly
beneath) thesplitter.
Forreasons of symmetry,azerothhandleexists in everysplitter,which QSplitter
never displays. This guaranteesthatindexOf() alwaysdeliversasensible position.
Thefunction makesadistinct ionbetween generalwidgets andsplitterswhendoing
this,and is able to determine thenumber of aspecific widgetorsplitter.Thus the
splitter can be defined for awidgetasfollows,

QSplitter

*
splitter=newQSplitter;
splitter->addWidget(newQWidget);
QLabel
*
lbl=newQLabel;
splitter->addWidget(lbl);
splitter->handle(splitter->indexOf(lbl));

while in th emouseDoubleClickEvent()method on page 154, welook for thewidget
to go withasplitter.
TheClickSplitterHandle classvariable lastUncollapsedSizeremembers thelastsize
of thewidgetinanuncollapsed state. If th is is 0for anyreason,the implementation
uses thevalue of therespectivesizeHint(). Thecurrent positionofour splitter
155
5 LayingOut Widgets
dependsonthe sizeofthe widget in frontof thesplitter,which is whythecode
detects thesizeofthe widgetthatoccupies thespaceuptothe positionpos-1,by
accessing sizes().
If theleftwidgetiscurrentlycollapsed,the last lines of code on page 154 open
it againusing thelastUncollapsedSizevariable.Otherwisethe widget“disappears”
to theleftofthe handle,but notwithout rememberingits currentsizeinlast-
UncollapsedSize.
5.3.5Layoutfor LanguagesWrittenfromRight to Left
Thewayin which texts in some languages, such as Hebrew,are read differsfun-
damentallyfrom European languagesinone respect: Theyarereadfromright to
left,and this mustalsobeaccounted for bysoftwareapplications.For such texts,
it is notthe topleft, butthe topright edge of thescreen that is thestartingpoint
for theeyewhenreading.Accordingly,atoolkitmustbeable to invertthe layout
horizontally.The KDE browserKonqueror in Figure 5.7mastersthistaskcorrectly,

because Qt managestomirrorall layouts horizontally,largelywithout thehelpof
theuser, if thelanguage configuredisoriented from righttoleft, or if thepro-
gram is passedthe -reverse option. TheQt-internalequivalentfor this optionis
theQApplication::setLayoutDirection(Qt::RightToLeft)call. This program optionis
mainlyused for testpurposes.
In thedevelopmentofyourownlayouts andwidgets,you mustalwaysmake your
ownprovisions in caseinverted layoutisused. Thus ourexamplefromaboveno
longer works correctlyin thecaseofaright-to-leftconfiguration:Itstill collapses
thewidgettothe left of thesplitter,althoughthe widgetthatshouldcollapseis
nowon theright side.
Figure 5.7:
Becauselanguages
likeHebrewrequire a
horizontally reflected
layout,developers of
widgets andlayouts
must take this case
into account andtest
it.
156
5.4 StackedLayouts
To examinesuchspecial cases,QApplicationhas thestaticmeth ods isLeftToRight()
andisRightToLeft(), which developers can easilyusetocheck thecurrent layout.
5.4Stacked Layouts
In stacked layouts ,several widgets areplacedontop of each otheronthe same
area—in contrast to otherlayouts,which arrangewidgets on asinglelevel.This
techniqueisusuallyappliedwhenimplementingcomplexconfiguration dialogs
(see Figure 5.8, page 160).
Theclass that implements this functionalityin Qt is calledQStackedLayout. New
widgets arealsoaddedinthisformoflayoutwiththe addWidget()method. You

should remember theIDreturned whenthisisdone: Thewidgets can be identified
later on withits help.Alternatively,you can savethe pointer to theinserted widget.
So that you can accessone of theinserted widgets again, QStackedLayouthas
twoslots:setCurrentIndex() expectsapositionofthe widgetasaninteger value,
whereas setCurrentWidget()acceptsapointer to an instance of aclass derived from
QWidget.
5.4.1The Alternative: StackedWidgets
AQStackedLayout, likeall layouts,requiresawidgetthatwillmanage it.Inmost
cases this needstobeadditionallycreated,and you havetoequip this widget
withalayout. To simplifythis,Qtprovides so-called stacked widgets withthe
QStackedWidgetclass ,which havethe same APIasQStackedLayout. Internally,
theseare widgets equippedwithastacked layout.
5.4.2WhentoUse StackedLayouts andWidgets
Belowwewilldevelop asimplevariation of such aconfiguration widgetourselves,
which of course can also be implemented as adialog.
4
Configuration dialogs such as thoseinKDE provideaverygood exampleofthe
useofaQStackedLayout: AstandardKDE configuration dialog consists of alistor
icon view,aswellasastacked layoutorastacked widget. Depending on which
entrytheuserselects from thelist, thestackedclass ensuresthatthe relevant
widgetcomes to thefront.Listand icon viewsinQtare normallybasedonthe
so-called model/view concept ,which is covered separatelyin Chapter 8. Forour
purposes,asimplified listviewthat is provided byQt withthe QListWidgetclass
willbesufficient.Each listentryis encapsulated in an instance of thelightweight
4
Exactlyhowdialogs function is explainedinChapter 6.
157
5 LayingOut Widgets
QListWidgetItem class. Each QListWidgetItem contains thetextbelonging to the
entry,aswellasadefinitionfor apossible icon.Inour configuration dialog we

associate apage in thestackedwidgettoeach widgetitem.
Theheart of ournewclass, which wederivedirectlyfrom QWidget, is theaddPage()
method, which adds newpagestothe stacked widget. In addition werequire this
verystacked widgetand alistviewas member variables:
// configwidget/configwidget.h
#include <QWidget>
class QListWidget;
class QStackedWidget;
class ConfigWidget:public QWidget
{
Q_OBJECT
public:
ConfigWidget(QWidget
*
parent=0);
void addPage(const QString&title, const QIcon&icon, QWidget
*
page);
private:
QStackedWidget
*
widgetStack;
QListWidget
*
contentsWidget;
} ;
In theconstructor weinitiallyarrangethe listviewto theright of th estacked
widgetand restrict itswidth to 180pixelssothatitdoesn’t take up toomuch
space.
Thelistviewregards each widgetitem as a row .Assoon as theuserselects another

item,itreports via thecurrentRowChanged(int) signal.Ithelps that stacked layouts
andwidgets savethe positions of theirwidgets as integers, in which thenumber
value corresponds to thenumber of thewidget. Theseclassesthereforehavethe
setCurrentIndex(int)slot, which causesthe widgetwiththe number specified as an
argument to be displayed.Weconnect this slot in thecon nect() instructioninthe
final lines of theconstructor to thecurrentRowChanged(int) signal.
Nowit is important to create theentryin thelistviewwhenthe stacked widget
incorporates theaccompanyingwidget. Since both indicesstart at zero, andsince
onlyaddPage() carries outchanges to both widgets,itisguaranteed that thelist
viewentryis associatedwiththe correctwidget.
// configwidget/configwidget.cpp
#include <QtGui>
#include "configwidget.h"
158

×