/*! \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>
*/