1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
|
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
<!-- /home/espenr/tmp/qt-3.3.8-espenr-2499/qt-x11-free-3.3.8/doc/tutorial2.doc:349 -->
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1">
<title>Presenting the GUI</title>
<style type="text/css"><!--
fn { margin-left: 1cm; text-indent: -1cm; }
a:link { color: #004faf; text-decoration: none }
a:visited { color: #672967; text-decoration: none }
body { background: #ffffff; color: black; }
--></style>
</head>
<body>
<table border="0" cellpadding="0" cellspacing="0" width="100%">
<tr bgcolor="#E5E5E5">
<td valign=center>
<a href="index.html">
<font color="#004faf">Home</font></a>
| <a href="classes.html">
<font color="#004faf">All Classes</font></a>
| <a href="mainclasses.html">
<font color="#004faf">Main Classes</font></a>
| <a href="annotated.html">
<font color="#004faf">Annotated</font></a>
| <a href="groups.html">
<font color="#004faf">Grouped Classes</font></a>
| <a href="functions.html">
<font color="#004faf">Functions</font></a>
</td>
<td align="right" valign="center"><img src="logo32.png" align="right" width="64" height="32" border="0"></td></tr></table><h1 align=center>Presenting the GUI</h1>
<p>
<p> <center><img src="chart-main2.png" alt="The chart application"></center>
<p> The <tt>chart</tt> application provides access to options via menus and
toolbar buttons arranged around a central widget, a CanvasView, in a
conventional document-centric style.
<p> (Extracts from <tt>chartform.h</tt>.)
<p>
<pre> class ChartForm: public <a href="qmainwindow.html">QMainWindow</a>
{
<a href="metaobjects.html#Q_OBJECT">Q_OBJECT</a>
public:
enum { MAX_ELEMENTS = 100 };
enum { MAX_RECENTFILES = 9 }; // Must not exceed 9
enum ChartType { PIE, VERTICAL_BAR, HORIZONTAL_BAR };
enum AddValuesType { NO, YES, AS_PERCENTAGE };
ChartForm( const <a href="qstring.html">QString</a>& filename );
~ChartForm();
int chartType() { return m_chartType; }
void setChanged( bool changed = TRUE ) { m_changed = changed; }
void drawElements();
<a href="qpopupmenu.html">QPopupMenu</a> *optionsMenu; // Why public? See canvasview.cpp
protected:
virtual void closeEvent( <a href="qcloseevent.html">QCloseEvent</a> * );
private slots:
void fileNew();
void fileOpen();
void fileOpenRecent( int index );
void fileSave();
void fileSaveAs();
void fileSaveAsPixmap();
void filePrint();
void fileQuit();
void optionsSetData();
void updateChartType( <a href="qaction.html">QAction</a> *action );
void optionsSetFont();
void optionsSetOptions();
void helpHelp();
void helpAbout();
void helpAboutQt();
void saveOptions();
private:
void init();
void load( const <a href="qstring.html">QString</a>& filename );
bool okToClear();
void drawPieChart( const double scales[], double total, int count );
void drawVerticalBarChart( const double scales[], double total, int count );
void drawHorizontalBarChart( const double scales[], double total, int count );
<a href="qstring.html">QString</a> valueLabel( const <a href="qstring.html">QString</a>& label, double value, double total );
void updateRecentFiles( const <a href="qstring.html">QString</a>& filename );
void updateRecentFilesMenu();
void setChartType( ChartType chartType );
<a href="qpopupmenu.html">QPopupMenu</a> *fileMenu;
<a href="qaction.html">QAction</a> *optionsPieChartAction;
<a href="qaction.html">QAction</a> *optionsHorizontalBarChartAction;
<a href="qaction.html">QAction</a> *optionsVerticalBarChartAction;
<a href="qstring.html">QString</a> m_filename;
<a href="qstringlist.html">QStringList</a> m_recentFiles;
<a href="qcanvas.html">QCanvas</a> *m_canvas;
CanvasView *m_canvasView;
bool m_changed;
ElementVector m_elements;
<a href="qprinter.html">QPrinter</a> *m_printer;
ChartType m_chartType;
AddValuesType m_addValues;
int m_decimalPlaces;
<a href="qfont.html">QFont</a> m_font;
};
</pre>
<p> We create a <tt>ChartForm</tt> subclass of <a href="qmainwindow.html">QMainWindow</a>. Our subclass uses
the <a href="metaobjects.html#Q_OBJECT">Q_OBJECT</a> macro to support Qt's <a href="signalsandslots.html">signals and slots</a> mechanism.
<p> The public interface is very small; the type of chart being displayed
can be retrieved, the chart can be marked 'changed' (so that the user
will be prompted to save on exit), and the chart can be asked to draw
itself (drawElements()). We've also made the options menu public
because we are also going to use this menu as the canvas view's
context menu.
<p> <center><table cellpadding="4" cellspacing="2" border="0">
<tr bgcolor="#f0f0f0">
<td valign="top">The <a href="qcanvas.html">QCanvas</a> class is used for drawing 2D vector graphics. The
<a href="qcanvasview.html">QCanvasView</a> class is used to present a view of a canvas in an
application's GUI. All our drawing operations take place on the
canvas; but events (e.g. mouse clicks) take place on the canvas view.
</table></center>
<p> Each action is represented by a private slot, e.g. <tt>fileNew()</tt>, <tt>optionsSetData()</tt>, etc. We also have quite a number of private
functions and data members; we'll look at all these as we go through
the implementation.
<p> For the sake of convenience and compilation speed the chart form's
implementation is split over three files, <tt>chartform.cpp</tt> for the
GUI, <tt>chartform_canvas.cpp</tt> for the canvas handling and <tt>chartform_files.cpp</tt> for the file handling. We'll review each in turn.
<p> <h2> The Chart Form GUI
</h2>
<a name="1"></a><p> (Extracts from <tt>chartform.cpp</tt>.)
<p>
<pre> #include "images/file_new.xpm"
#include "images/file_open.xpm"
</pre><pre> #include "images/options_piechart.xpm"
</pre>
<p> All the images used by <tt>chart</tt> have been created as <tt>.xpm</tt> files
which we've placed in the <tt>images</tt> subdirectory.
<p> <h2> The Constructor
</h2>
<a name="2"></a><p> <pre> ChartForm::ChartForm( const <a href="qstring.html">QString</a>& filename )
: <a href="qmainwindow.html">QMainWindow</a>( 0, 0, WDestructiveClose )
</pre><tt>...</tt>
<pre> <a href="qaction.html">QAction</a> *fileNewAction;
<a href="qaction.html">QAction</a> *fileOpenAction;
<a href="qaction.html">QAction</a> *fileSaveAction;
</pre>
<p> For each user action we declare a <a href="qaction.html">QAction</a> pointer. Some actions are
declared in the header file because they need to be referred to
outside of the constructor.
<p> <center><table cellpadding="4" cellspacing="2" border="0">
<tr bgcolor="#d0d0d0">
<td valign="top">Most user actions are suitable as both menu items and as toolbar
buttons. Qt allows us to create a single QAction which can be added to
both a menu and a toolbar. This approach ensures that menu items and
toolbar buttons stay in sync and saves duplicating code.
</table></center>
<p> <pre> fileNewAction = new <a href="qaction.html">QAction</a>(
"New Chart", QPixmap( file_new ),
"&New", CTRL+Key_N, this, "new" );
<a href="qobject.html#connect">connect</a>( fileNewAction, SIGNAL( <a href="qaction.html#activated">activated</a>() ), this, SLOT( fileNew() ) );
</pre>
<p> When we construct an action we give it a name, an optional icon, a
menu text, and an accelerator short-cut key (or 0 if no accelerator is
required). We also make it a child of the form (by passing <tt>this</tt>).
When the user clicks a toolbar button or clicks a menu option the <tt>activated()</tt> signal is emitted. We connect() this signal to the
action's slot, in the snippet shown above, to fileNew().
<p> The chart types are all mutually exclusive: you can have a pie chart
<em>or</em> a vertical bar chart <em>or</em> a horizontal bar chart. This means
that if the user selects the pie chart menu option, the pie chart
toolbar button must be automatically selected too, and the other chart
menu options and toolbar buttons must be automatically unselected.
This behaviour is achieved by creating a <a href="qactiongroup.html">QActionGroup</a> and placing the
chart type actions in the group.
<p> <pre> <a href="qactiongroup.html">QActionGroup</a> *chartGroup = new <a href="qactiongroup.html">QActionGroup</a>( this ); // Connected later
chartGroup-><a href="qactiongroup.html#setExclusive">setExclusive</a>( TRUE );
</pre>
<p> The action group becomes a child of the form (<tt>this</tt>) and the
exlusive behaviour is achieved by the setExclusive() call.
<p> <pre> optionsPieChartAction = new <a href="qaction.html">QAction</a>(
"Pie Chart", QPixmap( options_piechart ),
"&Pie Chart", CTRL+Key_I, chartGroup, "pie chart" );
optionsPieChartAction-><a href="qaction.html#setToggleAction">setToggleAction</a>( TRUE );
</pre>
<p> Each action in the group is created in the same way as other actions,
except that the action's parent is the group rather than the form.
Because our chart type actions have an on/off state we call
setToggleAction(TRUE) for each of them. Note that we do not connect
the actions; instead, later on, we will connect the group to a slot
that will cause the canvas to redraw.
<p> <center><table cellpadding="4" cellspacing="2" border="0">
<tr bgcolor="#f0f0f0">
<td valign="top">Why haven't we connected the group straight away? Later in the
constructor we will read the user's options, one of which is the chart
type. We will then set the chart type accordingly. But at that point
we still won't have created a canvas or have any data, so all we want
to do is toggle the canvas type toolbar buttons, but not actually draw
the (at this point non-existent) canvas. <em>After</em> we have set the
canvas type we will connect the group.
</table></center>
<p> Once we've created all our user actions we can create the toolbars and
menu options that will allow the user to invoke them.
<p> <pre> <a href="qtoolbar.html">QToolBar</a>* fileTools = new <a href="qtoolbar.html">QToolBar</a>( this, "file operations" );
fileTools-><a href="qtoolbar.html#setLabel">setLabel</a>( "File Operations" );
fileNewAction-><a href="qaction.html#addTo">addTo</a>( fileTools );
fileOpenAction-><a href="qaction.html#addTo">addTo</a>( fileTools );
fileSaveAction-><a href="qaction.html#addTo">addTo</a>( fileTools );
</pre><tt>...</tt>
<pre> fileMenu = new <a href="qpopupmenu.html">QPopupMenu</a>( this );
<a href="qmainwindow.html#menuBar">menuBar</a>()->insertItem( "&File", fileMenu );
fileNewAction-><a href="qaction.html#addTo">addTo</a>( fileMenu );
fileOpenAction-><a href="qaction.html#addTo">addTo</a>( fileMenu );
fileSaveAction-><a href="qaction.html#addTo">addTo</a>( fileMenu );
</pre>
<p> Toolbar actions and menu options are easily created from QActions.
<p> As a convenience to our users we will restore the last window position
and size and list their recently used files. This is achieved by
writing out their settings when the application is closed and reading
them back when we construct the form.
<p> <pre> <a href="qsettings.html">QSettings</a> settings;
settings.<a href="qsettings.html#insertSearchPath">insertSearchPath</a>( QSettings::Windows, WINDOWS_REGISTRY );
int windowWidth = settings.<a href="qsettings.html#readNumEntry">readNumEntry</a>( APP_KEY + "WindowWidth", 460 );
int windowHeight = settings.<a href="qsettings.html#readNumEntry">readNumEntry</a>( APP_KEY + "WindowHeight", 530 );
int windowX = settings.<a href="qsettings.html#readNumEntry">readNumEntry</a>( APP_KEY + "WindowX", -1 );
int windowY = settings.<a href="qsettings.html#readNumEntry">readNumEntry</a>( APP_KEY + "WindowY", -1 );
setChartType( ChartType(
settings.<a href="qsettings.html#readNumEntry">readNumEntry</a>( APP_KEY + "ChartType", int(PIE) ) ) );
</pre><pre> m_font = QFont( "Helvetica", 18, QFont::Bold );
m_font.fromString(
settings.<a href="qsettings.html#readEntry">readEntry</a>( APP_KEY + "Font", m_font.toString() ) );
for ( int i = 0; i < MAX_RECENTFILES; ++i ) {
<a href="qstring.html">QString</a> filename = settings.<a href="qsettings.html#readEntry">readEntry</a>( APP_KEY + "File" +
QString::<a href="qstring.html#number">number</a>( i + 1 ) );
if ( !filename.<a href="qstring.html#isEmpty">isEmpty</a>() )
m_recentFiles.push_back( filename );
}
if ( m_recentFiles.count() )
updateRecentFilesMenu();
</pre>
<p> The <a href="qsettings.html">QSettings</a> class handles user settings in a platform-independent
way. We simply read and write settings, leaving QSettings to handle
the platform dependencies. The insertSearchPath() call does nothing
except under Windows so does not have to be <tt>#ifdef</tt>ed.
<p> We use readNumEntry() calls to obtain the chart form's last size and
position, providing default values if this is the first time it has
been run. The chart type is retrieved as an integer and cast to a
ChartType enum value. We create a default label font and then read the
"Font" setting, using the default we have just created if necessary.
<p> Although QSettings can handle string lists we've chosen to store each
recently used file as a separate entry to make it easier to hand edit
the settings. We attempt to read each possible file entry ("File1" to
"File9"), and add each non-empty entry to the list of recently used
files. If there are one or more recently used files we update the File
menu by calling updateRecentFilesMenu(); (we'll review this later on).
<p> <pre> <a href="qobject.html#connect">connect</a>( chartGroup, SIGNAL( <a href="qactiongroup.html#selected">selected</a>(QAction*) ),
this, SLOT( updateChartType(QAction*) ) );
</pre>
<p> Now that we have set the chart type (when we read it in as a user
setting) it is safe to connect the chart group to our
updateChartType() slot.
<p> <pre> <a href="qwidget.html#resize">resize</a>( windowWidth, windowHeight );
if ( windowX != -1 || windowY != -1 )
<a href="qwidget.html#move">move</a>( windowX, windowY );
</pre>
<p> And now that we know the window size and position we can resize and
move the chart form's window accordingly.
<p> <pre> m_canvas = new <a href="qcanvas.html">QCanvas</a>( this );
m_canvas-><a href="qcanvas.html#resize">resize</a>( <a href="qwidget.html#width">width</a>(), height() );
m_canvasView = new CanvasView( m_canvas, &m_elements, this );
<a href="qmainwindow.html#setCentralWidget">setCentralWidget</a>( m_canvasView );
m_canvasView-><a href="qwidget.html#show">show</a>();
</pre>
<p> We create a new <a href="qcanvas.html">QCanvas</a> and set its size to that of the chart form
window's client area. We also create a <tt>CanvasView</tt> (our own subclass
of <a href="qcanvasview.html">QCanvasView</a>) to display the QCanvas. We make the canvas view the
chart form's main widget and show it.
<p> <pre> if ( !filename.<a href="qstring.html#isEmpty">isEmpty</a>() )
load( filename );
else {
init();
m_elements[0].set( 20, red, 14, "Red" );
m_elements[1].set( 70, cyan, 2, "Cyan", darkGreen );
m_elements[2].set( 35, blue, 11, "Blue" );
m_elements[3].set( 55, yellow, 1, "Yellow", darkBlue );
m_elements[4].set( 80, magenta, 1, "Magenta" );
drawElements();
}
</pre>
<p> If we have a file to load we load it; otherwise we initialise our
elements vector and draw a sample chart.
<p> <pre> <a href="qmainwindow.html#statusBar">statusBar</a>()->message( "Ready", 2000 );
</pre>
<p> It is <em>vital</em> that we call statusBar() in the constructor, since the
call ensures that a status bar is created for this main window.
<p> <h3> init()
</h3>
<a name="2-1"></a><p> <pre> void ChartForm::init()
{
<a href="qwidget.html#setCaption">setCaption</a>( "Chart" );
m_filename = <a href="qstring.html#QString-null">QString::null</a>;
m_changed = FALSE;
m_elements[0] = Element( Element::INVALID, red );
m_elements[1] = Element( Element::INVALID, cyan );
m_elements[2] = Element( Element::INVALID, blue );
</pre><tt>...</tt>
<p> We use an init() function because we want to initialise the canvas and
the elements (in the <tt>m_elements</tt> <tt>ElementVector</tt>) when the form is
constructed, and also whenever the user loads an existing data set or
creates a new data set.
<p> We reset the caption and set the current filename to QString::null. We
also populate the elements vector with invalid elements. This isn't
necessary, but giving each element a different color is more
convenient for the user since when they enter values each one will
already have a unique color (which they can change of course).
<p> <h2> The File Handling Actions
</h2>
<a name="3"></a><p> <h3> okToClear()
</h3>
<a name="3-1"></a><p> <pre> bool ChartForm::okToClear()
{
if ( m_changed ) {
<a href="qstring.html">QString</a> msg;
if ( m_filename.isEmpty() )
msg = "Unnamed chart ";
else
msg = QString( "Chart '%1'\n" ).arg( m_filename );
msg += "has been changed.";
int x = QMessageBox::<a href="qmessagebox.html#information">information</a>( this, "Chart -- Unsaved Changes",
msg, "&Save", "Cancel", "&Abandon",
0, 1 );
switch( x ) {
case 0: // Save
fileSave();
break;
case 1: // Cancel
default:
return FALSE;
case 2: // Abandon
break;
}
}
return TRUE;
}
</pre>
<p> The okToClear() function is used to prompt the user to save their
values if they have any unsaved data. It is used by several other
functions.
<p> <h3> fileNew()
</h3>
<a name="3-2"></a><p>
<pre> void ChartForm::fileNew()
{
if ( okToClear() ) {
init();
drawElements();
}
}
</pre>
<p> When the user invokes the fileNew() action we call okToClear() to give
them the opportunity to save any unsaved data. If they either save or
abandon or have no unsaved data we re-initialise the elements vector
and draw the default chart.
<p> <center><table cellpadding="4" cellspacing="2" border="0">
<tr bgcolor="#d0d0d0">
<td valign="top">Should we also have invoked optionsSetData() to pop up the dialog
through which the user can create and edit values, colors etc? You
could try running the application as it is, and then try it having
added a call to optionsSetData() and see which you prefer.
</table></center>
<p> <h3> fileOpen()
</h3>
<a name="3-3"></a><p> <pre> void ChartForm::fileOpen()
{
if ( !okToClear() )
return;
<a href="qstring.html">QString</a> filename = QFileDialog::<a href="qfiledialog.html#getOpenFileName">getOpenFileName</a>(
QString::null, "Charts (*.cht)", this,
"file open", "Chart -- File Open" );
<a name="x2567"></a> if ( !filename.<a href="qstring.html#isEmpty">isEmpty</a>() )
load( filename );
else
<a href="qmainwindow.html#statusBar">statusBar</a>()->message( "File Open abandoned", 2000 );
}
</pre>
<p> We check that it is okToClear(). If it is we use the static
<a href="qfiledialog.html#getOpenFileName">QFileDialog::getOpenFileName</a>() function to get the name of the file
the user wishes to load. If we get a filename we call load().
<p> <h3> fileSaveAs()
</h3>
<a name="3-4"></a><p> <pre> void ChartForm::fileSaveAs()
{
<a href="qstring.html">QString</a> filename = QFileDialog::<a href="qfiledialog.html#getSaveFileName">getSaveFileName</a>(
QString::null, "Charts (*.cht)", this,
"file save as", "Chart -- File Save As" );
if ( !filename.<a href="qstring.html#isEmpty">isEmpty</a>() ) {
int answer = 0;
<a name="x2563"></a> if ( QFile::<a href="qfile.html#exists">exists</a>( filename ) )
<a name="x2566"></a> answer = QMessageBox::<a href="qmessagebox.html#warning">warning</a>(
this, "Chart -- Overwrite File",
QString( "Overwrite\n\'%1\'?" ).
arg( filename ),
"&Yes", "&No", QString::null, 1, 1 );
if ( answer == 0 ) {
m_filename = filename;
updateRecentFiles( filename );
fileSave();
return;
}
}
<a href="qmainwindow.html#statusBar">statusBar</a>()->message( "Saving abandoned", 2000 );
}
</pre>
<p> This function calls the static <a href="qfiledialog.html#getSaveFileName">QFileDialog::getSaveFileName</a>() to get
the name of the file to save the data in. If the file exists we use a
<a href="qmessagebox.html#warning">QMessageBox::warning</a>() to notify the user and give them the option of
abandoning the save. If the file is to be saved we update the recently
opened files list and call fileSave() (covered in <a href="tutorial2-07.html">File Handling</a>) to perform the save.
<p> <h2> Managing a list of Recently Opened Files
</h2>
<a name="4"></a><p>
<pre> <a href="qstringlist.html">QStringList</a> m_recentFiles;
</pre>
<p> We hold the list of recently opened files in a string list.
<p>
<pre> void ChartForm::updateRecentFilesMenu()
{
for ( int i = 0; i < MAX_RECENTFILES; ++i ) {
if ( fileMenu-><a href="qmenudata.html#findItem">findItem</a>( i ) )
fileMenu-><a href="qmenudata.html#removeItem">removeItem</a>( i );
if ( i < int(m_recentFiles.count()) )
fileMenu-><a href="qmenudata.html#insertItem">insertItem</a>( QString( "&%1 %2" ).
arg( i + 1 ).arg( m_recentFiles[i] ),
this, SLOT( fileOpenRecent(int) ),
0, i );
}
}
</pre>
<p> This function is called (usually via updateRecentFiles()) whenever the
user opens an existing file or saves a new file. For each file in the
string list we insert a new menu item. We prefix each filename with an
underlined number from <u>1</u> to <u>9</u> to support keyboard access
(e.g. <tt>Alt+F, 2</tt> to open the second file in the list). We give the
menu item an id which is the same as the index position of the item in
the string list, and connect each menu item to the fileOpenRecent()
slot. The old file menu items are deleted at the same time by going
through each possible recent file menu item id. This works because the
other file menu items had ids created by Qt (all of which are < 0);
whereas the menu items we're creating all have ids >= 0.
<p>
<pre> void ChartForm::updateRecentFiles( const <a href="qstring.html">QString</a>& filename )
{
if ( m_recentFiles.find( filename ) != m_recentFiles.end() )
return;
m_recentFiles.push_back( filename );
if ( m_recentFiles.count() > MAX_RECENTFILES )
m_recentFiles.pop_front();
updateRecentFilesMenu();
}
</pre>
<p> This is called when the user opens an existing file or saves a new
file. If the file is already in the list it simply returns. Otherwise
the file is added to the end of the list and if the list is too large
(> 9 files) the first (oldest) is removed. updateRecentFilesMenu() is
then called to recreate the list of recently used files in the File
menu.
<p>
<pre> void ChartForm::fileOpenRecent( int index )
{
if ( !okToClear() )
return;
load( m_recentFiles[index] );
}
</pre>
<p> When the user selects a recently opened file the fileOpenRecent() slot
is called with the menu id of the file they have selected. Because we
made the file menu ids equal to the files' index positions in the
<tt>m_recentFiles</tt> list we can simply load the file indexed by the menu
item id.
<p> <h2> Quiting
</h2>
<a name="5"></a><p> <pre> void ChartForm::fileQuit()
{
if ( okToClear() ) {
saveOptions();
qApp-><a href="qapplication.html#exit">exit</a>( 0 );
}
}
</pre>
<p> When the user quits we give them the opportunity to save any unsaved
data (okToClear()) then save their options, e.g. window size and
position, chart type, etc., before terminating.
<p> <pre> void ChartForm::saveOptions()
{
<a href="qsettings.html">QSettings</a> settings;
settings.<a href="qsettings.html#insertSearchPath">insertSearchPath</a>( QSettings::Windows, WINDOWS_REGISTRY );
settings.<a href="qsettings.html#writeEntry">writeEntry</a>( APP_KEY + "WindowWidth", width() );
settings.<a href="qsettings.html#writeEntry">writeEntry</a>( APP_KEY + "WindowHeight", height() );
settings.<a href="qsettings.html#writeEntry">writeEntry</a>( APP_KEY + "WindowX", x() );
settings.<a href="qsettings.html#writeEntry">writeEntry</a>( APP_KEY + "WindowY", y() );
settings.<a href="qsettings.html#writeEntry">writeEntry</a>( APP_KEY + "ChartType", int(m_chartType) );
settings.<a href="qsettings.html#writeEntry">writeEntry</a>( APP_KEY + "AddValues", int(m_addValues) );
settings.<a href="qsettings.html#writeEntry">writeEntry</a>( APP_KEY + "Decimals", m_decimalPlaces );
settings.<a href="qsettings.html#writeEntry">writeEntry</a>( APP_KEY + "Font", m_font.toString() );
for ( int i = 0; i < int(m_recentFiles.count()); ++i )
settings.<a href="qsettings.html#writeEntry">writeEntry</a>( APP_KEY + "File" + QString::number( i + 1 ),
m_recentFiles[i] );
}
</pre>
<p> Saving the user's options using <a href="qsettings.html">QSettings</a> is straight-forward.
<p> <h2> Custom Dialogs
</h2>
<a name="6"></a><p> We want the user to be able to set some options manually and to create
and edit values, value colors, etc.
<p>
<pre> void ChartForm::optionsSetOptions()
{
OptionsForm *optionsForm = new OptionsForm( this );
optionsForm->chartTypeComboBox->setCurrentItem( m_chartType );
optionsForm-><a href="qwidget.html#setFont">setFont</a>( m_font );
</pre><pre> if ( optionsForm-><a href="qdialog.html#exec">exec</a>() ) {
setChartType( ChartType(
optionsForm->chartTypeComboBox->currentItem()) );
m_font = optionsForm-><a href="qwidget.html#font">font</a>();
</pre><pre> drawElements();
}
delete optionsForm;
}
</pre>
<p> The form for setting options is provided by our custom <tt>OptionsForm</tt>
covered in <a href="tutorial2-09.html">Setting Options</a>. The
options form is a standard "dumb" dialog: we create an instance, set
all its GUI elements to the relevant settings, and if the user clicked
"OK" (exec() returns a true value) we read back settings from the GUI
elements.
<p>
<pre> void ChartForm::optionsSetData()
{
SetDataForm *setDataForm = new SetDataForm( &m_elements, m_decimalPlaces, this );
if ( setDataForm-><a href="qdialog.html#exec">exec</a>() ) {
m_changed = TRUE;
drawElements();
}
delete setDataForm;
}
</pre>
<p> The form for creating and editing chart data is provided by our custom
<tt>SetDataForm</tt> covered in <a href="tutorial2-08.html">Taking Data</a>.
This form is a "smart" dialog. We pass in the data structure we want
to work on, and the dialog handles the presentation of the data
structure itself. If the user clicks "OK" the dialog will update the
data structure and exec() will return a true value. All we need to do
in optionsSetData() if the user changed the data is mark the chart as
changed and call drawElements() to redraw the chart with the new and
updated data.
<p> <p align="right">
<a href="tutorial2-04.html">« Mainly Easy</a> |
<a href="tutorial2.html">Contents</a> |
<a href="tutorial2-06.html">Canvas Control »</a>
</p>
<p>
<!-- eof -->
<p><address><hr><div align=center>
<table width=100% cellspacing=0 border=0><tr>
<td>Copyright © 2007
<a href="troll.html">Trolltech</a><td align=center><a href="trademarks.html">Trademarks</a>
<td align=right><div align=right>Qt 3.3.8</div>
</table></div></address></body>
</html>
|