diff options
Diffstat (limited to 'doc/tutorial2.doc')
-rw-r--r-- | doc/tutorial2.doc | 1435 |
1 files changed, 1435 insertions, 0 deletions
diff --git a/doc/tutorial2.doc b/doc/tutorial2.doc new file mode 100644 index 000000000..9d68e51fd --- /dev/null +++ b/doc/tutorial2.doc @@ -0,0 +1,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-qt.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 retquire 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 qcolor.h so that we can hold the +paint color and text color in the \c Element class. The use of \c +qnamespace.h is slightly obscure. Most Qt classes are derived from the +\link qt.html Qt\endlink superclass which contains various +enumerations. The \c Element class does not derive from \link qt.html +Qt\endlink, so we need to include \c qnamespace.h to have access to +the Qt enum names. An alternative approach would have been to have +made \c Element a \link qt.html Qt\endlink subclass. We include \c +qstring.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 qvaluevector.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 retquires the inclusion of \c +qtextstream.h and \c qstringlist.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 Q_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 tquite 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 +retquired). 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 tquits 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 +retquired) 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 tquite 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 +retquires 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> + +*/ |