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
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
1134
1135
1136
1137
1138
1139
1140
1141
1142
1143
1144
1145
1146
1147
1148
1149
1150
1151
1152
1153
1154
1155
1156
1157
1158
1159
1160
1161
1162
1163
1164
1165
1166
1167
1168
1169
1170
1171
1172
1173
1174
1175
1176
1177
1178
1179
1180
1181
1182
1183
1184
1185
1186
1187
1188
1189
1190
1191
1192
1193
1194
1195
1196
1197
1198
1199
1200
1201
1202
1203
1204
1205
1206
1207
1208
1209
1210
1211
1212
1213
1214
1215
1216
1217
1218
1219
1220
1221
1222
1223
1224
1225
1226
1227
1228
1229
1230
1231
1232
1233
1234
1235
1236
1237
1238
1239
1240
1241
1242
1243
1244
1245
1246
1247
1248
1249
1250
1251
1252
1253
1254
1255
1256
1257
1258
1259
1260
1261
1262
1263
1264
1265
1266
1267
1268
1269
1270
1271
1272
1273
1274
1275
1276
1277
1278
1279
1280
1281
1282
1283
1284
1285
1286
1287
1288
1289
1290
1291
1292
1293
1294
1295
1296
1297
1298
1299
1300
1301
1302
1303
1304
1305
1306
1307
1308
1309
1310
1311
1312
1313
1314
1315
1316
1317
1318
1319
1320
1321
1322
1323
1324
1325
1326
1327
1328
1329
1330
1331
1332
1333
1334
1335
1336
1337
1338
1339
1340
1341
1342
1343
1344
1345
1346
1347
1348
1349
1350
1351
1352
1353
1354
1355
1356
1357
1358
1359
1360
1361
1362
1363
1364
1365
1366
1367
1368
1369
1370
1371
1372
1373
1374
1375
1376
1377
1378
1379
1380
1381
1382
1383
1384
1385
1386
1387
1388
1389
1390
1391
1392
1393
1394
1395
1396
1397
1398
1399
1400
1401
1402
1403
1404
1405
1406
1407
1408
1409
1410
1411
1412
1413
1414
1415
1416
1417
1418
1419
1420
1421
1422
1423
1424
1425
1426
1427
1428
1429
1430
1431
1432
1433
1434
1435
|
/*! \file chart/chart.pro */
/*! \file chart/element.h */
/*! \file chart/element.cpp */
/*! \file chart/main.cpp */
/*! \file chart/canvastext.h */
/*! \file chart/canvasview.h */
/*! \file chart/canvasview.cpp */
/*! \file chart/chartform.h */
/*! \file chart/chartform.cpp */
/*! \file chart/chartform_canvas.cpp */
/*! \file chart/chartform_files.cpp */
/*! \file chart/optionsform.h */
/*! \file chart/optionsform.cpp */
/*! \file chart/setdataform.h */
/*! \file chart/setdataform.cpp */
/*!
\page tutorial2.html
\title Tutorial #2
This tutorial presents a more "real world" example of Qt programming
than the first tutorial. It introduces many aspects of Qt programming,
including the creation of menus (including a recent files list),
toolbars and dialogs, loading and saving user settings, etc.
If you're completely new to Qt, please read \link how-to-learn-ntqt.html
How to Learn Qt\endlink if you haven't already done so.
\list
\i \link tutorial2-01.html Introduction\endlink
\i \link tutorial2-02.html The 'Big Picture'\endlink
\i \link tutorial2-03.html Data Elements\endlink
\i \link tutorial2-04.html Mainly Easy\endlink
\i \link tutorial2-05.html Presenting the GUI\endlink
\i \link tutorial2-06.html Canvas Control\endlink
\i \link tutorial2-07.html File Handling\endlink
\i \link tutorial2-08.html Taking Data\endlink
\i \link tutorial2-09.html Setting Options\endlink
\i \link tutorial2-10.html The Project File\endlink
\i \link tutorial2-11.html Wrapping Up\endlink
\endlist
<p align="right">
<a href="tutorial2-01.html">Introduction »</a>
</p>
*/
/*!
\page tutorial2-01.html
\title Introduction
In this tutorial we will develop a single application called \c chart,
which is used to display simple pie and bar charts based on data that
the user enters.
The tutorial gives an overview of the development of the application
and includes code snippets and accompanying explanations. The complete
source for the application is in \c examples/chart.
\img chart-main.png The chart application
<p align="right">
<a href="tutorial2.html">« Contents</a> |
<a href="tutorial2-02.html">The 'Big Picture' »</a>
</p>
*/
/*!
\page tutorial2-02.html
\title The 'Big Picture'
\img chart-forms.png The chart application's dialogs
The \c chart program allows users to create, save, load and visualise
simple data sets. Each data element that the user enters can be given
a color and pattern for the pie segment or bar, some label text and
the text's position and color. The \c Element class is used to
represent data elements.
The program consists of a simple \c main.cpp that loads the chart
form. The chart form has a menubar and toolbar which provide access to
the program's functionality. The program provides two dialogs, one to
set options, and the other to create and edit a data set. Both dialogs
are launched from chart form menu options or toolbar buttons.
The chart form's main widget is a QCanvasView which displays the
QCanvas on which we draw the pie chart or bar graph. We subclass
QCanvasView to obtain some specialised behaviour. Similarly we
subclass the QCanvasText class (used to place text items on a canvas)
since we require slightly more than the standard class provides.
The project file, \c chart.pro, is used to create the Makefile that is
used to build the application.
<p align="right">
<a href="tutorial2-01.html">« Introduction</a> |
<a href="tutorial2.html">Contents</a> |
<a href="tutorial2-03.html">Data Elements »</a>
</p>
*/
/*!
\page tutorial2-03.html
\title Data Elements
We will use a C++ class called \c Element to provide storage and
access for data elements.
(Extracts from \c element.h.)
\quotefile chart/element.h
\skipto private
\printline
\skipto m_value
\printto };
Each element has a value. Each value is displayed graphically with a
particular color and fill pattern. Values may have a label associated
with them; the label is drawn using the label color and for each type
of chart has a (relative) position stored in the \c m_propoints array.
\quotefile chart/element.h
\skipto #include
\printto class
Although the \c Element class is a purely internal data class, it
\c{#include}s four Qt classes. Qt is often perceived as a purely GUI
toolkit, but it provides many non-GUI classes to support most aspects
of application programming. We use \c ntqcolor.h so that we can hold the
paint color and text color in the \c Element class. The use of \c
ntqnamespace.h is slightly obscure. Most Qt classes are derived from the
\link ntqt.html Qt\endlink superclass which contains various
enumerations. The \c Element class does not derive from \link ntqt.html
Qt\endlink, so we need to include \c ntqnamespace.h to have access to
the Qt enum names. An alternative approach would have been to have
made \c Element a \link ntqt.html Qt\endlink subclass. We include \c
ntqstring.h to make use of Qt's Unicode strings. As a convenience we
will \c typedef a vector container for \c{Element}s, which is why we
pull in the \c ntqvaluevector.h header.
\skipto QValueVector
\printline
Qt provides a number of containers, some value based like
QValueVector, and others pointer based. (See \link collection.html
Collection Classes\endlink.) Here we've just typedefed one container
type; we will keep each data set of elements in one \c ElementVector.
\skipto const double EPSILON
\printline
Elements may only have positive values. Because we hold values as
doubles we cannot readily compare them with zero. Instead we specify a
value, \c EPSILON, which is close to zero, and consider any value
greater than \c EPSILON to be positive and valid.
\skipto class
\printto Element(
We define three public enums for \c{Element}s. \c INVALID is used by
the isValid() function. It is useful because we are going to use a
fixed size vector of \c{Element}s, and can mark unused \c{Element}s by
giving them \c INVALID values. The \c NO_PROPORTION enum is used to
signify that the user has not positioned the Element's label; any
positive proportion value is taken to be the text element's position
proportional to the canvas's size.
If we stored each label's actual x and y position, then every time the
user resized the main window (and therefore the canvas), the text
would retain its original (now incorrect) position. So instead of
storing absolute (x, y) positions we store \e proportional positions,
i.e. x/width and y/height. We can then multiply these positions by
the current width and height respectively when we come to draw the
text and the text will be positioned correctly regardless of any
resizing. For example, if a label has an x position of 300 and the
canvas is 400 pixels wide, the proportional x value is 300/400 = 0.75.
The \c MAX_PROPOINTS enum is problematic. We need to store the x and y
proportions for the text label for every chart type. And we have
chosen to store these proportions in a fixed-size array. Because of
this we must specify the maximum number of proportion pairs needed.
This value must be changed if we change the number of chart types,
which means that the \c Element class is strongly coupled to the
number of chart types provided by the \c ChartForm class. In a
larger application we might have used a vector to store these points
and dynamically resized it depending on how many chart types are
available.
\printto Element()
The constructor provides default values for all members of the \c
Element class. New elements always have label text with no position.
We use an init() function because we also provide a set() function
which works like the constructor apart from leaving the proportional
positions alone.
\skipto isValid()
\printline
Since we are storing \c{Element}s in a fixed size vector we need to be
able to check whether a particular element is valid (i.e. should be
used in calculations and displayed) or not. This is easily achieved
with the isValid() function.
(Extracts from \c element.cpp.)
\quotefile chart/element.cpp
\skipto Element::proX
\printuntil }
Getters and setters are provided for all the members of \c Element.
The proX() and proY() getters and the setProX() and setProY() setters
take an index which identifies the type of chart the proportional
position applies to. This means that the user can have labels
positioned separately for the same data set for a vertical bar chart,
a horizontal bar chart and for a pie chart. Note also that we use the
\c Q_ASSERT macro to provide pre-condition tests on the chart type
index; (see \link debug.html Debugging\endlink).
\section1 Reading and Writing Data Elements
(Extracts from \c element.h.)
\quotefile chart/element.h
\skipto QTextStream
\printline
\printline
To make our \c Element class more self-contained we provide overloads
for the \<\< and \>\> operators so that \c{Element}s may be written to
and read from text streams. We could just as easily have used binary
streams, but using text makes it possible for users to manipulate
their data using a text editor and makes it easier to generate and
filter the data using a scripting language.
(Extracts from \c element.cpp.)
\quotefile chart/element.cpp
\skipto include
\printto const
Our implementation of the operators requires the inclusion of \c
ntqtextstream.h and \c ntqstringlist.h.
\printto Element
The format we are using to store the data is colon separated fields
and newline separated records. The proportional points are semi-colon
separated, with their x, y pairs being comma separated. The field
order is value, value color, value pattern, label color, label points,
label text. For example:
\code
20:#ff0000:14:#000000:0.767033,0.412946;0,0.75;0,0:Red :with colons:!
70:#00ffff:2:#ffff00:0.450549,0.198661;0.198516,0.125954;0,0.198473:Cyan
35:#0000ff:8:#555500:0.10989,0.299107;0.397032,0.562977;0,0.396947:Blue
55:#ffff00:1:#000080:0.0989011,0.625;0.595547,0.312977;0,0.59542:Yellow
80:#ff00ff:1:#000000:0.518681,0.694196;0.794063,0;0,0.793893:Magenta or Violet
\endcode
There's no problem having whitespace and field separators in label
text due to the way we read \c Element data.
\skipto &operator<<
\printuntil return
\printline
Writing elements is straight-forward. Each member is written followed
by a field separator. The points are written as comma separated (\c
XY_SEP) x, y pairs, each pair separated by the \c PROPOINT_SEP
separator. The final field is the label followed by a newline.
\skipto &operator>>
\printuntil return
\printline
To read an element we read one record (i.e. one line). We break the
data into fields using QStringList::split(). Because it is possible
that a label will contain \c FIELD_SEP characters we use
QString::section() to extract all the text from the last field to the
end of the line. If there are enough fields and the value, colors and
pattern data is valid we use \c Element::set() to write this data into
the element; otherwise we leave the element \c INVALID. We then
iterate through the points. If the x and y proportions are valid and
in range we set them for the element. If one or both proportions is
invalid they will hold the value zero; this is not suitable so we
change invalid (and out-of-range) proportional point values to \c
NO_PROPORTION.
Our \c Element class is now sufficient to store, manipulate, read and
write element data. We have also created an element vector typedef for
storing a collection of elements.
We are now ready to create \c main.cpp and the user interface through
which our users will create, edit and visualise their data sets.
\table
\row
\i For more information on Qt's data streaming facilities see \link
datastreamformat.html QDataStream Operators' Formats\endlink, and see
the source code for any of the Qt classes mentioned that are similar
to what you want to store.
\endtable
<p align="right">
<a href="tutorial2-02.html">« The 'Big Picture'</a> |
<a href="tutorial2.html">Contents</a> |
<a href="tutorial2-04.html">Mainly Easy »</a>
</p>
*/
/*!
\page tutorial2-04.html
\title Mainly Easy
(\c main.cpp.)
\quotefile chart/main.cpp
\printuntil return
\printline
We have kept the main() function simple and small. We create a
QApplication object and pass it the command line arguments. We are
allowing users to invoke the program with \c{chart mychart.cht}, so if
they've added a filename we pass that through to the chart form
constructor. Most of the action takes place within the chart form
which we'll review next.
<p align="right">
<a href="tutorial2-03.html">« Data Elements</a> |
<a href="tutorial2.html">Contents</a> |
<a href="tutorial2-05.html">Presenting the GUI »</a>
</p>
*/
/*!
\page tutorial2-05.html
\title Presenting the GUI
\img chart-main2.png The chart application
The \c chart application provides access to options via menus and
toolbar buttons arranged around a central widget, a CanvasView, in a
conventional document-centric style.
(Extracts from \c chartform.h.)
\quotefile chart/chartform.h
\skipto public QMainWindow
\printuntil optionsVerticalBarChartAction
\printuntil };
We create a \c ChartForm subclass of QMainWindow. Our subclass uses
the \c TQ_OBJECT macro to support Qt's \link signalsandslots.html
signals and slots\endlink mechanism.
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.
\table
\row
\i The QCanvas class is used for drawing 2D vector graphics. The
QCanvasView 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.
\endtable
Each action is represented by a private slot, e.g. \c fileNew(), \c
optionsSetData(), 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.
For the sake of convenience and compilation speed the chart form's
implementation is split over three files, \c chartform.cpp for the
GUI, \c chartform_canvas.cpp for the canvas handling and \c
chartform_files.cpp for the file handling. We'll review each in turn.
\section1 The Chart Form GUI
(Extracts from \c chartform.cpp.)
\quotefile chart/chartform.cpp
\skipto file_new.xpm
\printto file_save.xpm
\skipto options_piechart.xpm
\printline
All the images used by \c chart have been created as \c .xpm files
which we've placed in the \c images subdirectory.
\section1 The Constructor
\skipto ChartForm::ChartForm
\printuntil QMainWindow
\c{ ...}
\skipto fileNewAction
\printuntil fileSaveAction
For each user action we declare a QAction pointer. Some actions are
declared in the header file because they need to be referred to
outside of the constructor.
\table
\row
\i 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.
\endtable
\skipto fileNewAction
\printuntil connect
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 \c this).
When the user clicks a toolbar button or clicks a menu option the \c
activated() signal is emitted. We connect() this signal to the
action's slot, in the snippet shown above, to fileNew().
The chart types are all mutually exclusive: you can have a pie chart
\e or a vertical bar chart \e or 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 QActionGroup and placing the
chart type actions in the group.
\skipto new QActionGroup
\printuntil setExclusive
The action group becomes a child of the form (\c this) and the
exlusive behaviour is achieved by the setExclusive() call.
\skipto optionsPieChartAction
\printuntil setToggleAction
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.
\table
\row
\i 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. \e After we have set the
canvas type we will connect the group.
\endtable
Once we've created all our user actions we can create the toolbars and
menu options that will allow the user to invoke them.
\skipto new QToolBar
\printuntil fileSaveAction
\c{ ...}
\skipto new QPopupMenu
\printuntil fileSaveAction
Toolbar actions and menu options are easily created from QActions.
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.
\skipto QSettings
\printuntil PIE
\skipto QFont
\printuntil updateRecentFilesMenu
The QSettings 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 \c{#ifdef}ed.
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.
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).
\skipto connect
\printuntil updateChartType
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.
\skipto resize
\printuntil move
And now that we know the window size and position we can resize and
move the chart form's window accordingly.
\skipto new QCanvas
\printuntil show
We create a new QCanvas and set its size to that of the chart form
window's client area. We also create a \c CanvasView (our own subclass
of QCanvasView) to display the QCanvas. We make the canvas view the
chart form's main widget and show it.
\skipto isEmpty
\printuntil drawElements
\printline
If we have a file to load we load it; otherwise we initialise our
elements vector and draw a sample chart.
\skipto statusBar
\printline
It is \e vital that we call statusBar() in the constructor, since the
call ensures that a status bar is created for this main window.
\section2 init()
\skipto ::init()
\printuntil m_elements[2]
\c{ ...}
We use an init() function because we want to initialise the canvas and
the elements (in the \c m_elements \c ElementVector) when the form is
constructed, and also whenever the user loads an existing data set or
creates a new data set.
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).
\section1 The File Handling Actions
\section2 okToClear()
\skipto ::okToClear()
\printuntil return TRUE;
\printline
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.
\section2 fileNew()
\quotefile chart/chartform.cpp
\skipto ::fileNew()
\printuntil drawElements
\printline
\printline
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.
\table
\row
\i 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.
\endtable
\section2 fileOpen()
\skipto ::fileOpen()
\printuntil statusBar
\printline
We check that it is okToClear(). If it is we use the static
QFileDialog::getOpenFileName() function to get the name of the file
the user wishes to load. If we get a filename we call load().
\section2 fileSaveAs()
\skipto ::fileSaveAs(
\printuntil statusBar
\printline
This function calls the static QFileDialog::getSaveFileName() to get
the name of the file to save the data in. If the file exists we use a
QMessageBox::warning() 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 \link
tutorial2-07.html File Handling\endlink) to perform the save.
\section1 Managing a list of Recently Opened Files
\quotefile chart/chartform.h
\skipto QStringList m_recentFiles
\printline
We hold the list of recently opened files in a string list.
\quotefile chart/chartform.cpp
\skipto ::updateRecentFilesMenu()
\printuntil SLOT
\printline
\printline
\printline
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. \c{Alt+F, 2} 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.
\quotefile chart/chartform.cpp
\skipto ::updateRecentFiles(
\printuntil }
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.
\quotefile chart/chartform.cpp
\skipto ::fileOpenRecent(
\printuntil }
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
\c m_recentFiles list we can simply load the file indexed by the menu
item id.
\section1 Quiting
\skipto ::fileQuit(
\printuntil exit
\printline
\printline
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.
\skipto ::saveOptions(
\printuntil }
Saving the user's options using QSettings is straight-forward.
\section1 Custom Dialogs
We want the user to be able to set some options manually and to create
and edit values, value colors, etc.
\quotefile chart/chartform.cpp
\skipto ::optionsSetOptions
\printuntil setFont
\skipto exec()
\printto RadioButton
\skipto drawElements
\printuntil delete optionsForm
\printline
The form for setting options is provided by our custom \c OptionsForm
covered in \link tutorial2-09.html Setting Options\endlink. 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.
\quotefile chart/chartform.cpp
\skipto ::optionsSetData
\printuntil delete setDataForm
\printline
The form for creating and editing chart data is provided by our custom
\c SetDataForm covered in \link tutorial2-08.html Taking Data\endlink.
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 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>
*/
/*!
\page tutorial2-06.html
\title Canvas Control
We draw pie segments (or bar chart bars), and any labels, on a canvas.
The canvas is presented to the user through a canvas view. The
drawElements() function is called to redraw the canvas when necessary.
(Extracts from \c chartform_canvas.cpp.)
\section1 drawElements()
\quotefile chart/chartform_canvas.cpp
\skipto ::drawElements(
\printuntil delete
The first thing we do in drawElements() is delete all the existing
canvas items.
\skipto 16ths
\printuntil width()
Next we calculate the scale factor which depends on the type of chart
we're going to draw.
\skipto biggest
\printto switch
We will need to know how many values there are, the biggest value and
the total value so that we can create pie segments or bars that are
correctly scaled. We store the scaled values in the \c scales array.
\printuntil }
\printline
Now that we have the necessary information we call the relevant
drawing function, passing in the scaled values, the total and the
count.
\skipto update
\printline
Finally we update() the canvas to make the changes visible.
\section2 drawHorizontalBarChart()
We'll review just one of the drawing functions, to see how canvas
items are created and placed on a canvas since this tutorial is about
Qt rather than good (or bad) algorithms for drawing charts.
\skipto ::drawHorizontalBarChart(
\printuntil total
\printline
To draw a horizontal bar chart we need the array of scaled values, the
total value (so that we can calculate and draw percentages if
required) and a count of the number of values.
\skipto width
\printuntil int y
We retrieve the width and height of the canvas and calculate the
proportional height (\c proheight). We set the initial \c y position
to 0.
\skipto QPen
\printuntil NoPen
We create a pen that we will use to draw each bar (rectangle); we set
it to \c NoPen so that no outlines are drawn.
\skipto MAX_ELEMENTS
\printuntil extent
We iterate over every element in the element vector, skipping invalid
elements. The extent of each bar (its length) is simply its scaled
value.
\printuntil show
We create a new QCanvasRectangle for each bar with an x position of 0
(since this is a horizontal bar chart every bar begins at the left), a
y value that starts at 0 and grows by the height of each bar as each
one is drawn, the height of the bar and the canvas that the bar should
be drawn on. We then set the bar's brush to the color and pattern that
the user has specified for the element, set the pen to the pen we
created earlier (i.e. to \c NoPen) and we place the bar at position 0
in the Z-order. Finally we call show() to draw the bar on the canvas.
\printto valueLabel
If the user has specified a label for the element or asked for the
values (or percentages) to be shown, we also draw a canvas text item.
We created our own CanvasText class (see later) because we want to
store the corresponding element index (in the element vector) in each
canvas text item. We extract the proportional x and y values from the
element. If either is \< 0 then they have not been positioned by the
user so we must calculate positions for them. We set the label's x
value to 0 (left) and the y value to the top of the bar (so that the
label's top-left will be at this x, y position).
\printline
We then call a helper function valueLabel() which returns a string
containing the label text. (The valueLabel() function adds on the
value or percentage to the textual label if the user has set the
appropriate options.)
\printuntil setProY
We then create a CanvasText item, passing it the index of this element
in the element vector, and the label, font and canvas to use. We set
the text item's text color to the color specified by the user and set
the item's x and y positions proportional to the canvas's width and
height. We set the Z-order to 1 so that the text item will always be
above (in front of) the bar (whose Z-order is 0). We call show() to
draw the text item on the canvas, and set the element's relative x and
y positions.
\printuntil proheight
After drawing a bar and possibly its label, we increment y by the
proportional height ready to draw the next element.
\printline
\printline
\printline
\section1 Subclassing QCanvasText
(Extracts from \c canvastext.h.)
\quotefile chart/canvastext.h
\skipto public QCanvasText
\printuntil private
\printuntil };
Our CanvasText subclass is a very simple specialisation of
QCanvasText. All we've done is added a single private member \c
m_index which holds the element vector index of the element associated
with this text item, and provided a getter and setter for this value.
\section1 Subclassing QCanvasView
(Extracts from \c canvasview.h.)
\quotefile chart/canvasview.h
\skipto public QCanvasView
\printuntil private
\printuntil };
We need to subclass QCanvasView so that we can handle:
\list 1
\i Context menu requests.
\i Form resizing.
\i Users dragging labels to arbitrary positions.
\endlist
To support these we store a pointer to the canvas item that is being
moved and its last position. We also store a pointer to the element
vector.
\section2 Supporting Context Menus
(Extracts from \c canvasview.cpp.)
\quotefile chart/canvasview.cpp
\skipto ::contentsContextMenuEvent
\printuntil }
When the user invokes a context menu (e.g. by right-clicking on most
platforms) we cast the canvas view's parent (which is the chart form)
to the right type and then exec()ute the options menu at the cursor
position.
\section2 Handling Resizing
\skipto ::viewportResizeEvent
\printuntil }
To resize we simply resize the canvas that the canvas view is
presenting to the width and height of the form's client area, then
call drawElements() to redraw the chart. Because drawElements() draws
everything relative to the canvas's width and height the chart is
drawn correctly.
\section2 Dragging Labels into Position
When the user wants to drag a label into position they click it, then
drag and release at the new position.
\skipto ::contentsMousePressEvent
\printuntil return
\printuntil movingItem
\printuntil }
When the user clicks the mouse we create a list of canvas items that
the mouse click "collided" with (if any). We then iterate over this
list and if we find a \c CanvasText item we set it as the moving item
and record its position. Otherwise we set there to be no moving item.
\skipto ::contentsMouseMoveEvent
\printuntil update
\printuntil }
\printuntil }
As the user drags the mouse, move events are generated. If there is a
moving item we calculate the offset from the last mouse position and
move the item by this offset amount. We record the new position as the
last position. Because the chart has now changed we call setChanged()
so that the user will be prompted to save if they attempt to exit or
to load an existing chart or to create a new chart. We also update the
element's proportional x and y positions for the current chart type to
the current x and y positions proportional to the width and height
respectively. We know which element to update because when we create
each canvas text item we pass it the index position of the element it
corresponds to. We subclassed QCanvasText so that we could set and get
this index value. Finally we call update() to make the canvas redraw.
\table
\row
\i A QCanvas has no visual representation. To see the contents of a
canvas you must create a QCanvasView to present the canvas. Items only
appear in the canvas view if they have been show()n, and then, only if
QCanvas::update() has been called. By default a QCanvas's background
color is white, and by default shapes drawn on the canvas, e.g.
QCanvasRectangle, QCanvasEllipse, etc., have their fill color set to
white, so setting a non-white brush color is highly recommended!
\endtable
<p align="right">
<a href="tutorial2-05.html">« Presenting the GUI</a> |
<a href="tutorial2.html">Contents</a> |
<a href="tutorial2-07.html">File Handling »</a>
</p>
*/
/*!
\page tutorial2-07.html
\title File Handling
(Extracts from \c chartform_files.cpp.)
\section1 Reading Chart Data
\quotefile chart/chartform_files.cpp
\skipto ::load(
\printuntil isValid
\printline
\skipto file.close
\printline
\skipto setCaption
\printuntil }
Loading a data set is very easy. We open the file and create a text
stream. While there's data to read we stream an element into \c
element and if it is valid we insert it into the \c m_elements vector.
All the detail is handled by the \c Element class. Then we close
the file and update the caption and the recent files list. Finally we
draw the chart and mark it as unchanged.
\section1 Writing Chart Data
\skipto ::fileSave
\printline
\printline
\skipto QFile
\printuntil FALSE
\printline
Saving data is equally easy. We open the file and create a text
stream. We then stream every valid element to the text stream. All the
detail is handled by the \c Element class.
<p align="right">
<a href="tutorial2-06.html">« Canvas Control</a> |
<a href="tutorial2.html">Contents</a> |
<a href="tutorial2-08.html">Taking Data »</a>
</p>
*/
/*!
\page tutorial2-08.html
\title Taking Data
\img chart-setdata.png The set data dialog
The set data dialog allows the user to add and edit values, and to
choose the color and pattern used to display values. Users can also
enter label text and choose a label color for each label.
(Extracts from \c setdataform.h.)
\quotefile chart/setdataform.h
\skipto public QDialog
\printuntil };
The header file is simple. The constructor takes a pointer to the
element vector so that this "smart" dialog can display and edit the
data directly. We'll explain the slots as we look through the
implementation.
(Extracts from \c setdataform.cpp.)
\quotefile chart/setdataform.cpp
\skipto pattern01
\printuntil pattern02
We have created a small \c .XPM image to show each brush pattern that
Qt supports. We'll use these in the pattern combobox.
\section1 The Constructor
\skipto SetDataForm::SetDataForm
\printuntil m_decimalPlaces
We pass most of the arguments to the QDialog superclass. We assign the
elements vector pointer and the number of decimal places to display to
member variables so that they are accessible by all SetDataForm's
member functions.
\skipto setCaption
\printuntil resize
We set a caption for the dialog and resize it.
\skipto tableButtonBox
\printline
The layout of the form is quite simple. The buttons will be grouped
together in a horizontal layout and the table and the button layout
will be grouped together vertically using the tableButtonBox layout.
\skipto QTable
\printuntil addWidget
We create a new QTable with five columns, and the same number of rows
as we have elements in the elements vector. We make the color and
pattern columns read only: this is to prevent the user typing in them.
We will make the color changeable by the user clicking on a color or
navigating to a color and clicking the Color button. The pattern will
be in a combobox, changeable simply by the user selecting a different
pattern. Next we set suitable initial widths, insert labels for each
column and finally add the table to the tableButtonBox layout.
\skipto QHBoxLayout
\printline
We create a horizontal box layout to hold the buttons.
\skipto QPushButton
\printuntil addWidget
We create a color button and add it to the buttonBox layout. We
disable the button; we will only enable it when the focus is actually
on a color cell.
\skipto QSpacerItem
\printuntil addItem
Since we want to separate the color button from the OK and Cancel
buttons we next create a spacer and add that to the buttonBox layout.
\skipto QPushButton
\printuntil addWidget( cancelPushButton
The OK and Cancel buttons are created and added to the buttonBox. We
make the OK button the dialog's default button, and we make the \c Esc
key an accelerator for the Cancel button.
\skipto addLayout
\printline
We add the buttonBox layout to the tableButtonBox and the layout is
complete.
\skipto connect
\printuntil reject
We now "wire up" the form.
\list
\i If the user clicks a cell we call the setColor() slot; this will
check that the cell is one that holds a color, and if it is, will
invoke the color dialog.
\i We connect the QTable's currentChanged() signal to our own
currentChanged() slot; this will be used to enable/disable the color
button for example, depending on which column the user is in.
\i We connect the table's valueChanged() signal to our own
valueChanged() slot; we'll use this to display the value with the
correct number of decimal places.
\i If the user clicks the Color button we call a setColor() slot.
\i The OK button is connected to the accept() slot; we will update the
elements vector in this slot.
\i The Cancel button is connected to the QDialog reject() slot, and
requires no further code or action on our part.
\endlist
\skipto QPixmap
\printline
\printline
\printline
We create a pixmap for every brush pattern and store them in the \c
patterns array.
\skipto QRect
\printline
\printline
We obtain the rectangle that will be occupied by each color cell and
create a blank pixmap of that size.
\skipto ChartForm::MAX_ELEMENTS
\printuntil labelColor
\printuntil setText
For each element in the element vector we must populate the table.
If the element is valid we write its value in the first column (column
0, Value), formatting it with the specified number of decimal places.
We read the element's value color and fill the blank pixmap with that
color; we then set the color cell to display this pixmap. We need to
be able to read back the color later (e.g. if the user changes it).
One way of doing this would be to examine a pixel in the pixmap;
another way would be to subclass QTableItem (in a similar way to our
CanvasText subclass) and store the color there. But we've taken a
simpler route: we set the cell's text to the name of the color.
Next we populate the pattern combobox with the patterns. We will use
the position of the chosen pattern in the combobox to determine which
pattern the user has selected. QTable can make use of QComboTableItem
items; but these only support text, so we use setCellWidget() to
insert \l{QComboBox}'s into the table instead.
Next we insert the element's label. Finally we set the label color in
the same way as we set the value color.
\section1 The Slots
\skipto ::currentChanged(
\printuntil setEnabled
\printline
As the user navigates through the table currentChanged() signals are
emitted. If the user enters column 1 or 4 (value color or label color)
we enable the colorPushButton; otherwise we disable it.
\skipto ::valueChanged(
\printuntil ?
\printline
\printline
If the user changes the value we must format it using the correct
number of decimal places, or indicate that it is invalid.
\skipto ::setColor(
\printuntil }
If the user presses the Color button we call the other setColor()
function and put the focus back into the table.
\skipto ::setColor(
\printuntil setText
\printline
\printline
If this function is called with the focus on a color cell we call
the static QColorDialog::getColor() dialog to get the user's choice of
color. If they chose a color we fill the color cell's pixmap with that
color and set the cell's text to the new color's name.
\skipto ::accept(
\printuntil QDialog
\printline
If the user clicks OK we must update the elements vector. We iterate
over the vector and set each element's value to the value the user has
entered or \c INVALID if the value is invalid. We set the value color
and the label color by constructing QColor temporaries that take a
color name as argument. The pattern is set to the pattern combobox's
current item with an offset of 1 (since our pattern numbers begin at
1, but the combobox's items are indexed from 0).
Finally we call QDialog::accept().
<p align="right">
<a href="tutorial2-07.html">« File Handling</a> |
<a href="tutorial2.html">Contents</a> |
<a href="tutorial2-09.html">Setting Options »</a>
</p>
*/
/*!
\page tutorial2-09.html
\title Setting Options
\img chart-options.png The options dialog
We provide an options dialog so that the user can set options that
apply to all data sets in one place.
(Extracts from \c optionsform.h.)
\quotefile chart/optionsform.h
\skipto public QDialog
\printuntil };
The layout of this dialog is slightly more complicated than for the
set data form, but we only need a single slot. Unlike the "smart" set
data form this is a "dumb" dialog that simply provides the widgets for
the caller to set and read. The caller is responsible for updating
things based on the changes the user makes.
(Extracts from \c optionsform.cpp.)
\quotefile chart/optionsform.cpp
\skipto options_horizontalbarchart
\printline
\printline
\printline
We include some some pixmaps to use in the chart type combobox.
\section1 The Constructor
\skipto OptionsForm::OptionsForm
\printuntil resize
We pass all the arguments on to the QDialog constructor, set a caption
and set an initial size.
The layout of the form will be to have the chart type label and combo
box in a horizontal box layout, and similarly for the font button and
font label, and for the decimal places label and spinbox. The
buttons will also be placed in a horizontal layout, but with a spacer
to move them to the right. The show values radio buttons will be
vertically aligned within a frame. All of these will be laid out in a
vertical box layout.
\skipto optionsFormLayout
\printline
All the widgets will be laid out within the form's vertical box layout.
\skipto chartTypeLayout
\printline
The chart type label and combobox will be laid out side by side.
\skipto QLabel
\printuntil addLayout
We create the chart type label (with an accelerator which we'll relate
to the chart type combobox later). We also create a chart type
combobox, populating it with both pixmaps and text. We add them both
to the horizontal layout and add the horizontal layout to the form's
vertical layout.
\skipto fontLayout
\printuntil addLayout
We create a horizontal box layout to hold the font button and font
label. The font button is straight-forward. We add a spacer to improve
the appearance. The font text label is initially empty (since we don't
know what font the user is using).
\skipto QFrame
\printuntil addWidget( addValuesButtonGroup
The user may opt to display their own labels as they are or to add the
values at the end of each label, either as-is or as percentages.
We create a frame to present the radio buttons in and create a layout
for them. We create a button group (so that Qt will take care of
handling the exclusive radio button behaviour automatically). Next we
create the radio buttons, making "No" the default.
The decimal places label and spin box are laid out just like the other
horizontal layouts, and the buttons are laid out in a very similar way
to the buttons in the set data form.
\skipto connect
\printuntil reject
We only need three connections:
\list 1
\i When the user clicks the font button we execute our own
chooseFont() slot.
\i If the user clicks OK we call QDialog::accept(); it is up to the
caller to read the data from the dialog's widgets and perform any
necessary actions.
\i If the user clicks Cancel we call QDialog::reject().
\endlist
\skipto setBuddy
\printline
\printline
We use the setBuddy() function to associate widgets with label
accelerators.
\section1 The Slots
\skipto ::chooseFont
\printuntil }
When the user clicks the Font button this slot is invoked. It simply
calls the static QFontDialog::getFont() function to obtain the user's
choice of font. If they chose a font we call our setFont() slot which
will present a textual description of the font in the font label.
\skipto ::setFont
\printuntil }
This function displays a textual description of the chosen font in the
font label and holds a copy of the font in the \c m_font member. We
need the font in a member so that we can provide a default font for
chooseFont().
<p align="right">
<a href="tutorial2-08.html">« Taking Data</a> |
<a href="tutorial2.html">Contents</a> |
<a href="tutorial2-10.html">The Project File »</a>
</p>
*/
/*!
\page tutorial2-10.html
\title The Project File
(\c chart.pro.)
\quotefile chart/chart.pro
\printuntil main.cpp
By using a project file we can insulate ourselves from having to
create Makefiles for the platforms we wish to target. To generate a
Makefile all we need do is run \link qmake-manual.book qmake\endlink,
e.g.
\code
qmake -o Makefile chart.pro
\endcode
<p align="right">
<a href="tutorial2-09.html">« Setting Options</a> |
<a href="tutorial2.html">Contents</a> |
<a href="tutorial2-11.html">Wrapping Up »</a>
</p>
*/
/*!
\page tutorial2-11.html
\title Wrapping Up
The \c chart application shows how straight-forward it is to create
applications and their dialogs with Qt. Creating menus and toolbars is
easy and Qt's \link signalsandslots.html signals and slots\endlink
mechanism considerably simplifies GUI event handling.
Manually creating layouts can take some time to master, but there is
an easy alternative: \link designer-manual.book Qt Designer\endlink.
\link designer-manual.book Qt Designer\endlink includes simple but
powerful layout tools and a code editor. It can automatically generate
\c main.cpp and the \c .pro project file.
The \c chart application is ripe for further development and
experimentation. You might consider implementing some of the following
ideas:
\list
\i Use a QValidator subclass to ensure that only valid doubles are
entered as values.
\i Adding more chart types, e.g. line graph, area graph and hi-lo
graph.
\i Allowing the user to set top, bottom left and right margins.
\i Allowing the user to specify a title which they can drag into
position like the labels.
\i Providing an axis drawing and labelling option.
\i Providing an option to provide a key (or legend) instead of labels.
\i Adding a 3D look option to all chart types.
\endlist
<p align="right">
<a href="tutorial2-10.html">« The Project File</a> |
<a href="tutorial2.html">Contents »</a>
</p>
*/
|