/*! \file chart/chart.pro */ /*! \file chart/element.h */ /*! \file chart/element.cpp */ /*! \file chart/main.cpp */ /*! \file chart/canvastext.h */ /*! \file chart/canvasview.h */ /*! \file chart/canvasview.cpp */ /*! \file chart/chartform.h */ /*! \file chart/chartform.cpp */ /*! \file chart/chartform_canvas.cpp */ /*! \file chart/chartform_files.cpp */ /*! \file chart/optionsform.h */ /*! \file chart/optionsform.cpp */ /*! \file chart/setdataform.h */ /*! \file chart/setdataform.cpp */ /*! \page tutorial2.html \title Tutorial #2 This tutorial presents a more "real world" example of Qt programming than the first tutorial. It introduces many aspects of Qt programming, including the creation of menus (including a recent files list), toolbars and dialogs, loading and saving user settings, etc. If you're completely new to Qt, please read \link how-to-learn-qt.html How to Learn Qt\endlink if you haven't already done so. \list \i \link tutorial2-01.html Introduction\endlink \i \link tutorial2-02.html The 'Big Picture'\endlink \i \link tutorial2-03.html Data Elements\endlink \i \link tutorial2-04.html Mainly Easy\endlink \i \link tutorial2-05.html Presenting the GUI\endlink \i \link tutorial2-06.html Canvas Control\endlink \i \link tutorial2-07.html File Handling\endlink \i \link tutorial2-08.html Taking Data\endlink \i \link tutorial2-09.html Setting Options\endlink \i \link tutorial2-10.html The Project File\endlink \i \link tutorial2-11.html Wrapping Up\endlink \endlist
*/ /*! \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« Contents | The 'Big Picture' »
*/ /*! \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.« Introduction | Contents | Data Elements »
*/ /*! \page tutorial2-03.html \title Data Elements We will use a C++ class called \c Element to provide storage and access for data elements. (Extracts from \c element.h.) \quotefile chart/element.h \skipto private \printline \skipto m_value \printto }; Each element has a value. Each value is displayed graphically with a particular color and fill pattern. Values may have a label associated with them; the label is drawn using the label color and for each type of chart has a (relative) position stored in the \c m_propoints array. \quotefile chart/element.h \skipto #include \printto class Although the \c Element class is a purely internal data class, it \c{#include}s four Qt classes. Qt is often perceived as a purely GUI toolkit, but it provides many non-GUI classes to support most aspects of application programming. We use \c qcolor.h so that we can hold the paint color and text color in the \c Element class. The use of \c qnamespace.h is slightly obscure. Most Qt classes are derived from the \link qt.html Qt\endlink superclass which contains various enumerations. The \c Element class does not derive from \link qt.html Qt\endlink, so we need to include \c qnamespace.h to have access to the Qt enum names. An alternative approach would have been to have made \c Element a \link qt.html Qt\endlink subclass. We include \c qstring.h to make use of Qt's Unicode strings. As a convenience we will \c typedef a vector container for \c{Element}s, which is why we pull in the \c qvaluevector.h header. \skipto QValueVector \printline Qt provides a number of containers, some value based like QValueVector, and others pointer based. (See \link collection.html Collection Classes\endlink.) Here we've just typedefed one container type; we will keep each data set of elements in one \c ElementVector. \skipto const double EPSILON \printline Elements may only have positive values. Because we hold values as doubles we cannot readily compare them with zero. Instead we specify a value, \c EPSILON, which is close to zero, and consider any value greater than \c EPSILON to be positive and valid. \skipto class \printto Element( We define three public enums for \c{Element}s. \c INVALID is used by the isValid() function. It is useful because we are going to use a fixed size vector of \c{Element}s, and can mark unused \c{Element}s by giving them \c INVALID values. The \c NO_PROPORTION enum is used to signify that the user has not positioned the Element's label; any positive proportion value is taken to be the text element's position proportional to the canvas's size. If we stored each label's actual x and y position, then every time the user resized the main window (and therefore the canvas), the text would retain its original (now incorrect) position. So instead of storing absolute (x, y) positions we store \e proportional positions, i.e. x/width and y/height. We can then multiply these positions by the current width and height respectively when we come to draw the text and the text will be positioned correctly regardless of any resizing. For example, if a label has an x position of 300 and the canvas is 400 pixels wide, the proportional x value is 300/400 = 0.75. The \c MAX_PROPOINTS enum is problematic. We need to store the x and y proportions for the text label for every chart type. And we have chosen to store these proportions in a fixed-size array. Because of this we must specify the maximum number of proportion pairs needed. This value must be changed if we change the number of chart types, which means that the \c Element class is strongly coupled to the number of chart types provided by the \c ChartForm class. In a larger application we might have used a vector to store these points and dynamically resized it depending on how many chart types are available. \printto Element() The constructor provides default values for all members of the \c Element class. New elements always have label text with no position. We use an init() function because we also provide a set() function which works like the constructor apart from leaving the proportional positions alone. \skipto isValid() \printline Since we are storing \c{Element}s in a fixed size vector we need to be able to check whether a particular element is valid (i.e. should be used in calculations and displayed) or not. This is easily achieved with the isValid() function. (Extracts from \c element.cpp.) \quotefile chart/element.cpp \skipto Element::proX \printuntil } Getters and setters are provided for all the members of \c Element. The proX() and proY() getters and the setProX() and setProY() setters take an index which identifies the type of chart the proportional position applies to. This means that the user can have labels positioned separately for the same data set for a vertical bar chart, a horizontal bar chart and for a pie chart. Note also that we use the \c Q_ASSERT macro to provide pre-condition tests on the chart type index; (see \link debug.html Debugging\endlink). \section1 Reading and Writing Data Elements (Extracts from \c element.h.) \quotefile chart/element.h \skipto QTextStream \printline \printline To make our \c Element class more self-contained we provide overloads for the \<\< and \>\> operators so that \c{Element}s may be written to and read from text streams. We could just as easily have used binary streams, but using text makes it possible for users to manipulate their data using a text editor and makes it easier to generate and filter the data using a scripting language. (Extracts from \c element.cpp.) \quotefile chart/element.cpp \skipto include \printto const Our implementation of the operators requires the inclusion of \c qtextstream.h and \c qstringlist.h. \printto Element The format we are using to store the data is colon separated fields and newline separated records. The proportional points are semi-colon separated, with their x, y pairs being comma separated. The field order is value, value color, value pattern, label color, label points, label text. For example: \code 20:#ff0000:14:#000000:0.767033,0.412946;0,0.75;0,0:Red :with colons:! 70:#00ffff:2:#ffff00:0.450549,0.198661;0.198516,0.125954;0,0.198473:Cyan 35:#0000ff:8:#555500:0.10989,0.299107;0.397032,0.562977;0,0.396947:Blue 55:#ffff00:1:#000080:0.0989011,0.625;0.595547,0.312977;0,0.59542:Yellow 80:#ff00ff:1:#000000:0.518681,0.694196;0.794063,0;0,0.793893:Magenta or Violet \endcode There's no problem having whitespace and field separators in label text due to the way we read \c Element data. \skipto &operator<< \printuntil return \printline Writing elements is straight-forward. Each member is written followed by a field separator. The points are written as comma separated (\c XY_SEP) x, y pairs, each pair separated by the \c PROPOINT_SEP separator. The final field is the label followed by a newline. \skipto &operator>> \printuntil return \printline To read an element we read one record (i.e. one line). We break the data into fields using QStringList::split(). Because it is possible that a label will contain \c FIELD_SEP characters we use QString::section() to extract all the text from the last field to the end of the line. If there are enough fields and the value, colors and pattern data is valid we use \c Element::set() to write this data into the element; otherwise we leave the element \c INVALID. We then iterate through the points. If the x and y proportions are valid and in range we set them for the element. If one or both proportions is invalid they will hold the value zero; this is not suitable so we change invalid (and out-of-range) proportional point values to \c NO_PROPORTION. Our \c Element class is now sufficient to store, manipulate, read and write element data. We have also created an element vector typedef for storing a collection of elements. We are now ready to create \c main.cpp and the user interface through which our users will create, edit and visualise their data sets. \table \row \i For more information on Qt's data streaming facilities see \link datastreamformat.html QDataStream Operators' Formats\endlink, and see the source code for any of the Qt classes mentioned that are similar to what you want to store. \endtable« The 'Big Picture' | Contents | Mainly Easy »
*/ /*! \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.« Data Elements | Contents | Presenting the GUI »
*/ /*! \page tutorial2-05.html \title Presenting the GUI \img chart-main2.png The chart application The \c chart application provides access to options via menus and toolbar buttons arranged around a central widget, a CanvasView, in a conventional document-centric style. (Extracts from \c chartform.h.) \quotefile chart/chartform.h \skipto public QMainWindow \printuntil optionsVerticalBarChartAction \printuntil }; We create a \c ChartForm subclass of QMainWindow. Our subclass uses the \c Q_OBJECT macro to support Qt's \link signalsandslots.html signals and slots\endlink mechanism. The public interface is very small; the type of chart being displayed can be retrieved, the chart can be marked 'changed' (so that the user will be prompted to save on exit), and the chart can be asked to draw itself (drawElements()). We've also made the options menu public because we are also going to use this menu as the canvas view's context menu. \table \row \i The QCanvas class is used for drawing 2D vector graphics. The QCanvasView class is used to present a view of a canvas in an application's GUI. All our drawing operations take place on the canvas; but events (e.g. mouse clicks) take place on the canvas view. \endtable Each action is represented by a private slot, e.g. \c fileNew(), \c optionsSetData(), etc. We also have 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 1 to 9 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.« Mainly Easy | Contents | Canvas Control »
*/ /*! \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« Presenting the GUI | Contents | File Handling »
*/ /*! \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.« Canvas Control | Contents | Taking Data »
*/ /*! \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().« File Handling | Contents | Setting Options »
*/ /*! \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().« Taking Data | Contents | The Project File »
*/ /*! \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 */ /*! \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 */