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