| 1 | <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN"> | 
|---|
| 2 | <!-- /home/espenr/tmp/qt-3.3.8-espenr-2499/qt-x11-free-3.3.8/doc/tutorial2.doc:349 --> | 
|---|
| 3 | <html> | 
|---|
| 4 | <head> | 
|---|
| 5 | <meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1"> | 
|---|
| 6 | <title>Presenting the GUI</title> | 
|---|
| 7 | <style type="text/css"><!-- | 
|---|
| 8 | fn { margin-left: 1cm; text-indent: -1cm; } | 
|---|
| 9 | a:link { color: #004faf; text-decoration: none } | 
|---|
| 10 | a:visited { color: #672967; text-decoration: none } | 
|---|
| 11 | body { background: #ffffff; color: black; } | 
|---|
| 12 | --></style> | 
|---|
| 13 | </head> | 
|---|
| 14 | <body> | 
|---|
| 15 |  | 
|---|
| 16 | <table border="0" cellpadding="0" cellspacing="0" width="100%"> | 
|---|
| 17 | <tr bgcolor="#E5E5E5"> | 
|---|
| 18 | <td valign=center> | 
|---|
| 19 | <a href="index.html"> | 
|---|
| 20 | <font color="#004faf">Home</font></a> | 
|---|
| 21 | | <a href="classes.html"> | 
|---|
| 22 | <font color="#004faf">All Classes</font></a> | 
|---|
| 23 | | <a href="mainclasses.html"> | 
|---|
| 24 | <font color="#004faf">Main Classes</font></a> | 
|---|
| 25 | | <a href="annotated.html"> | 
|---|
| 26 | <font color="#004faf">Annotated</font></a> | 
|---|
| 27 | | <a href="groups.html"> | 
|---|
| 28 | <font color="#004faf">Grouped Classes</font></a> | 
|---|
| 29 | | <a href="functions.html"> | 
|---|
| 30 | <font color="#004faf">Functions</font></a> | 
|---|
| 31 | </td> | 
|---|
| 32 | <td align="right" valign="center"><img src="logo32.png" align="right" width="64" height="32" border="0"></td></tr></table><h1 align=center>Presenting the GUI</h1> | 
|---|
| 33 |  | 
|---|
| 34 |  | 
|---|
| 35 | <p> | 
|---|
| 36 | <p> <center><img src="chart-main2.png" alt="The chart application"></center> | 
|---|
| 37 | <p> The <tt>chart</tt> application provides access to options via menus and | 
|---|
| 38 | toolbar buttons arranged around a central widget, a CanvasView, in a | 
|---|
| 39 | conventional document-centric style. | 
|---|
| 40 | <p> (Extracts from <tt>chartform.h</tt>.) | 
|---|
| 41 | <p> | 
|---|
| 42 |  | 
|---|
| 43 | <pre>    class ChartForm: public <a href="qmainwindow.html">QMainWindow</a> | 
|---|
| 44 | { | 
|---|
| 45 | <a href="metaobjects.html#Q_OBJECT">Q_OBJECT</a> | 
|---|
| 46 | public: | 
|---|
| 47 | enum { MAX_ELEMENTS = 100 }; | 
|---|
| 48 | enum { MAX_RECENTFILES = 9 }; // Must not exceed 9 | 
|---|
| 49 | enum ChartType { PIE, VERTICAL_BAR, HORIZONTAL_BAR }; | 
|---|
| 50 | enum AddValuesType { NO, YES, AS_PERCENTAGE }; | 
|---|
| 51 |  | 
|---|
| 52 | ChartForm( const <a href="qstring.html">QString</a>& filename ); | 
|---|
| 53 | ~ChartForm(); | 
|---|
| 54 |  | 
|---|
| 55 | int chartType() { return m_chartType; } | 
|---|
| 56 | void setChanged( bool changed = TRUE ) { m_changed = changed; } | 
|---|
| 57 | void drawElements(); | 
|---|
| 58 |  | 
|---|
| 59 | <a href="qpopupmenu.html">QPopupMenu</a> *optionsMenu; // Why public? See canvasview.cpp | 
|---|
| 60 |  | 
|---|
| 61 | protected: | 
|---|
| 62 | virtual void closeEvent( <a href="qcloseevent.html">QCloseEvent</a> * ); | 
|---|
| 63 |  | 
|---|
| 64 | private slots: | 
|---|
| 65 | void fileNew(); | 
|---|
| 66 | void fileOpen(); | 
|---|
| 67 | void fileOpenRecent( int index ); | 
|---|
| 68 | void fileSave(); | 
|---|
| 69 | void fileSaveAs(); | 
|---|
| 70 | void fileSaveAsPixmap(); | 
|---|
| 71 | void filePrint(); | 
|---|
| 72 | void fileQuit(); | 
|---|
| 73 | void optionsSetData(); | 
|---|
| 74 | void updateChartType( <a href="qaction.html">QAction</a> *action ); | 
|---|
| 75 | void optionsSetFont(); | 
|---|
| 76 | void optionsSetOptions(); | 
|---|
| 77 | void helpHelp(); | 
|---|
| 78 | void helpAbout(); | 
|---|
| 79 | void helpAboutQt(); | 
|---|
| 80 | void saveOptions(); | 
|---|
| 81 |  | 
|---|
| 82 | private: | 
|---|
| 83 | void init(); | 
|---|
| 84 | void load( const <a href="qstring.html">QString</a>& filename ); | 
|---|
| 85 | bool okToClear(); | 
|---|
| 86 | void drawPieChart( const double scales[], double total, int count ); | 
|---|
| 87 | void drawVerticalBarChart( const double scales[], double total, int count ); | 
|---|
| 88 | void drawHorizontalBarChart( const double scales[], double total, int count ); | 
|---|
| 89 |  | 
|---|
| 90 | <a href="qstring.html">QString</a> valueLabel( const <a href="qstring.html">QString</a>& label, double value, double total ); | 
|---|
| 91 | void updateRecentFiles( const <a href="qstring.html">QString</a>& filename ); | 
|---|
| 92 | void updateRecentFilesMenu(); | 
|---|
| 93 | void setChartType( ChartType chartType ); | 
|---|
| 94 |  | 
|---|
| 95 | <a href="qpopupmenu.html">QPopupMenu</a> *fileMenu; | 
|---|
| 96 | <a href="qaction.html">QAction</a> *optionsPieChartAction; | 
|---|
| 97 | <a href="qaction.html">QAction</a> *optionsHorizontalBarChartAction; | 
|---|
| 98 | <a href="qaction.html">QAction</a> *optionsVerticalBarChartAction; | 
|---|
| 99 | <a href="qstring.html">QString</a> m_filename; | 
|---|
| 100 | <a href="qstringlist.html">QStringList</a> m_recentFiles; | 
|---|
| 101 | <a href="qcanvas.html">QCanvas</a> *m_canvas; | 
|---|
| 102 | CanvasView *m_canvasView; | 
|---|
| 103 | bool m_changed; | 
|---|
| 104 | ElementVector m_elements; | 
|---|
| 105 | <a href="qprinter.html">QPrinter</a> *m_printer; | 
|---|
| 106 | ChartType m_chartType; | 
|---|
| 107 | AddValuesType m_addValues; | 
|---|
| 108 | int m_decimalPlaces; | 
|---|
| 109 | <a href="qfont.html">QFont</a> m_font; | 
|---|
| 110 | }; | 
|---|
| 111 | </pre> | 
|---|
| 112 | <p> We create a <tt>ChartForm</tt> subclass of <a href="qmainwindow.html">QMainWindow</a>. Our subclass uses | 
|---|
| 113 | the <a href="metaobjects.html#Q_OBJECT">Q_OBJECT</a> macro to support Qt's <a href="signalsandslots.html">signals and slots</a> mechanism. | 
|---|
| 114 | <p> The public interface is very small; the type of chart being displayed | 
|---|
| 115 | can be retrieved, the chart can be marked 'changed' (so that the user | 
|---|
| 116 | will be prompted to save on exit), and the chart can be asked to draw | 
|---|
| 117 | itself (drawElements()). We've also made the options menu public | 
|---|
| 118 | because we are also going to use this menu as the canvas view's | 
|---|
| 119 | context menu. | 
|---|
| 120 | <p> <center><table cellpadding="4" cellspacing="2" border="0"> | 
|---|
| 121 | <tr bgcolor="#f0f0f0"> | 
|---|
| 122 | <td valign="top">The <a href="qcanvas.html">QCanvas</a> class is used for drawing 2D vector graphics. The | 
|---|
| 123 | <a href="qcanvasview.html">QCanvasView</a> class is used to present a view of a canvas in an | 
|---|
| 124 | application's GUI. All our drawing operations take place on the | 
|---|
| 125 | canvas; but events (e.g. mouse clicks) take place on the canvas view. | 
|---|
| 126 | </table></center> | 
|---|
| 127 | <p> Each action is represented by a private slot, e.g. <tt>fileNew()</tt>, <tt>optionsSetData()</tt>, etc. We also have quite a number of private | 
|---|
| 128 | functions and data members; we'll look at all these as we go through | 
|---|
| 129 | the implementation. | 
|---|
| 130 | <p> For the sake of convenience and compilation speed the chart form's | 
|---|
| 131 | implementation is split over three files, <tt>chartform.cpp</tt> for the | 
|---|
| 132 | GUI, <tt>chartform_canvas.cpp</tt> for the canvas handling and <tt>chartform_files.cpp</tt> for the file handling. We'll review each in turn. | 
|---|
| 133 | <p> <h2> The Chart Form GUI | 
|---|
| 134 | </h2> | 
|---|
| 135 | <a name="1"></a><p> (Extracts from <tt>chartform.cpp</tt>.) | 
|---|
| 136 | <p> | 
|---|
| 137 |  | 
|---|
| 138 | <pre>    #include "images/file_new.xpm" | 
|---|
| 139 | #include "images/file_open.xpm" | 
|---|
| 140 | </pre><pre>    #include "images/options_piechart.xpm" | 
|---|
| 141 | </pre> | 
|---|
| 142 | <p> All the images used by <tt>chart</tt> have been created as <tt>.xpm</tt> files | 
|---|
| 143 | which we've placed in the <tt>images</tt> subdirectory. | 
|---|
| 144 | <p> <h2> The Constructor | 
|---|
| 145 | </h2> | 
|---|
| 146 | <a name="2"></a><p> <pre>    ChartForm::ChartForm( const <a href="qstring.html">QString</a>& filename ) | 
|---|
| 147 | : <a href="qmainwindow.html">QMainWindow</a>( 0, 0, WDestructiveClose ) | 
|---|
| 148 | </pre><tt>...</tt> | 
|---|
| 149 | <pre>        <a href="qaction.html">QAction</a> *fileNewAction; | 
|---|
| 150 | <a href="qaction.html">QAction</a> *fileOpenAction; | 
|---|
| 151 | <a href="qaction.html">QAction</a> *fileSaveAction; | 
|---|
| 152 | </pre> | 
|---|
| 153 | <p> For each user action we declare a <a href="qaction.html">QAction</a> pointer. Some actions are | 
|---|
| 154 | declared in the header file because they need to be referred to | 
|---|
| 155 | outside of the constructor. | 
|---|
| 156 | <p> <center><table cellpadding="4" cellspacing="2" border="0"> | 
|---|
| 157 | <tr bgcolor="#d0d0d0"> | 
|---|
| 158 | <td valign="top">Most user actions are suitable as both menu items and as toolbar | 
|---|
| 159 | buttons. Qt allows us to create a single QAction which can be added to | 
|---|
| 160 | both a menu and a toolbar. This approach ensures that menu items and | 
|---|
| 161 | toolbar buttons stay in sync and saves duplicating code. | 
|---|
| 162 | </table></center> | 
|---|
| 163 | <p> <pre>        fileNewAction = new <a href="qaction.html">QAction</a>( | 
|---|
| 164 | "New Chart", QPixmap( file_new ), | 
|---|
| 165 | "&New", CTRL+Key_N, this, "new" ); | 
|---|
| 166 | <a href="qobject.html#connect">connect</a>( fileNewAction, SIGNAL( <a href="qaction.html#activated">activated</a>() ), this, SLOT( fileNew() ) ); | 
|---|
| 167 | </pre> | 
|---|
| 168 | <p> When we construct an action we give it a name, an optional icon, a | 
|---|
| 169 | menu text, and an accelerator short-cut key (or 0 if no accelerator is | 
|---|
| 170 | required). We also make it a child of the form (by passing <tt>this</tt>). | 
|---|
| 171 | When the user clicks a toolbar button or clicks a menu option the <tt>activated()</tt> signal is emitted. We connect() this signal to the | 
|---|
| 172 | action's slot, in the snippet shown above, to fileNew(). | 
|---|
| 173 | <p> The chart types are all mutually exclusive: you can have a pie chart | 
|---|
| 174 | <em>or</em> a vertical bar chart <em>or</em> a horizontal bar chart. This means | 
|---|
| 175 | that if the user selects the pie chart menu option, the pie chart | 
|---|
| 176 | toolbar button must be automatically selected too, and the other chart | 
|---|
| 177 | menu options and toolbar buttons must be automatically unselected. | 
|---|
| 178 | This behaviour is achieved by creating a <a href="qactiongroup.html">QActionGroup</a> and placing the | 
|---|
| 179 | chart type actions in the group. | 
|---|
| 180 | <p> <pre>        <a href="qactiongroup.html">QActionGroup</a> *chartGroup = new <a href="qactiongroup.html">QActionGroup</a>( this ); // Connected later | 
|---|
| 181 | chartGroup-><a href="qactiongroup.html#setExclusive">setExclusive</a>( TRUE ); | 
|---|
| 182 | </pre> | 
|---|
| 183 | <p> The action group becomes a child of the form (<tt>this</tt>) and the | 
|---|
| 184 | exlusive behaviour is achieved by the setExclusive() call. | 
|---|
| 185 | <p> <pre>        optionsPieChartAction = new <a href="qaction.html">QAction</a>( | 
|---|
| 186 | "Pie Chart", QPixmap( options_piechart ), | 
|---|
| 187 | "&Pie Chart", CTRL+Key_I, chartGroup, "pie chart" ); | 
|---|
| 188 | optionsPieChartAction-><a href="qaction.html#setToggleAction">setToggleAction</a>( TRUE ); | 
|---|
| 189 | </pre> | 
|---|
| 190 | <p> Each action in the group is created in the same way as other actions, | 
|---|
| 191 | except that the action's parent is the group rather than the form. | 
|---|
| 192 | Because our chart type actions have an on/off state we call | 
|---|
| 193 | setToggleAction(TRUE) for each of them. Note that we do not connect | 
|---|
| 194 | the actions; instead, later on, we will connect the group to a slot | 
|---|
| 195 | that will cause the canvas to redraw. | 
|---|
| 196 | <p> <center><table cellpadding="4" cellspacing="2" border="0"> | 
|---|
| 197 | <tr bgcolor="#f0f0f0"> | 
|---|
| 198 | <td valign="top">Why haven't we connected the group straight away? Later in the | 
|---|
| 199 | constructor we will read the user's options, one of which is the chart | 
|---|
| 200 | type. We will then set the chart type accordingly. But at that point | 
|---|
| 201 | we still won't have created a canvas or have any data, so all we want | 
|---|
| 202 | to do is toggle the canvas type toolbar buttons, but not actually draw | 
|---|
| 203 | the (at this point non-existent) canvas. <em>After</em> we have set the | 
|---|
| 204 | canvas type we will connect the group. | 
|---|
| 205 | </table></center> | 
|---|
| 206 | <p> Once we've created all our user actions we can create the toolbars and | 
|---|
| 207 | menu options that will allow the user to invoke them. | 
|---|
| 208 | <p> <pre>        <a href="qtoolbar.html">QToolBar</a>* fileTools = new <a href="qtoolbar.html">QToolBar</a>( this, "file operations" ); | 
|---|
| 209 | fileTools-><a href="qtoolbar.html#setLabel">setLabel</a>( "File Operations" ); | 
|---|
| 210 | fileNewAction-><a href="qaction.html#addTo">addTo</a>( fileTools ); | 
|---|
| 211 | fileOpenAction-><a href="qaction.html#addTo">addTo</a>( fileTools ); | 
|---|
| 212 | fileSaveAction-><a href="qaction.html#addTo">addTo</a>( fileTools ); | 
|---|
| 213 | </pre><tt>...</tt> | 
|---|
| 214 | <pre>        fileMenu = new <a href="qpopupmenu.html">QPopupMenu</a>( this ); | 
|---|
| 215 | <a href="qmainwindow.html#menuBar">menuBar</a>()->insertItem( "&File", fileMenu ); | 
|---|
| 216 | fileNewAction-><a href="qaction.html#addTo">addTo</a>( fileMenu ); | 
|---|
| 217 | fileOpenAction-><a href="qaction.html#addTo">addTo</a>( fileMenu ); | 
|---|
| 218 | fileSaveAction-><a href="qaction.html#addTo">addTo</a>( fileMenu ); | 
|---|
| 219 | </pre> | 
|---|
| 220 | <p> Toolbar actions and menu options are easily created from QActions. | 
|---|
| 221 | <p> As a convenience to our users we will restore the last window position | 
|---|
| 222 | and size and list their recently used files. This is achieved by | 
|---|
| 223 | writing out their settings when the application is closed and reading | 
|---|
| 224 | them back when we construct the form. | 
|---|
| 225 | <p> <pre>        <a href="qsettings.html">QSettings</a> settings; | 
|---|
| 226 | settings.<a href="qsettings.html#insertSearchPath">insertSearchPath</a>( QSettings::Windows, WINDOWS_REGISTRY ); | 
|---|
| 227 | int windowWidth = settings.<a href="qsettings.html#readNumEntry">readNumEntry</a>( APP_KEY + "WindowWidth", 460 ); | 
|---|
| 228 | int windowHeight = settings.<a href="qsettings.html#readNumEntry">readNumEntry</a>( APP_KEY + "WindowHeight", 530 ); | 
|---|
| 229 | int windowX = settings.<a href="qsettings.html#readNumEntry">readNumEntry</a>( APP_KEY + "WindowX", -1 ); | 
|---|
| 230 | int windowY = settings.<a href="qsettings.html#readNumEntry">readNumEntry</a>( APP_KEY + "WindowY", -1 ); | 
|---|
| 231 | setChartType( ChartType( | 
|---|
| 232 | settings.<a href="qsettings.html#readNumEntry">readNumEntry</a>( APP_KEY + "ChartType", int(PIE) ) ) ); | 
|---|
| 233 | </pre><pre>        m_font = QFont( "Helvetica", 18, QFont::Bold ); | 
|---|
| 234 | m_font.fromString( | 
|---|
| 235 | settings.<a href="qsettings.html#readEntry">readEntry</a>( APP_KEY + "Font", m_font.toString() ) ); | 
|---|
| 236 | for ( int i = 0; i < MAX_RECENTFILES; ++i ) { | 
|---|
| 237 | <a href="qstring.html">QString</a> filename = settings.<a href="qsettings.html#readEntry">readEntry</a>( APP_KEY + "File" + | 
|---|
| 238 | QString::<a href="qstring.html#number">number</a>( i + 1 ) ); | 
|---|
| 239 | if ( !filename.<a href="qstring.html#isEmpty">isEmpty</a>() ) | 
|---|
| 240 | m_recentFiles.push_back( filename ); | 
|---|
| 241 | } | 
|---|
| 242 | if ( m_recentFiles.count() ) | 
|---|
| 243 | updateRecentFilesMenu(); | 
|---|
| 244 | </pre> | 
|---|
| 245 | <p> The <a href="qsettings.html">QSettings</a> class handles user settings in a platform-independent | 
|---|
| 246 | way. We simply read and write settings, leaving QSettings to handle | 
|---|
| 247 | the platform dependencies. The insertSearchPath() call does nothing | 
|---|
| 248 | except under Windows so does not have to be <tt>#ifdef</tt>ed. | 
|---|
| 249 | <p> We use readNumEntry() calls to obtain the chart form's last size and | 
|---|
| 250 | position, providing default values if this is the first time it has | 
|---|
| 251 | been run. The chart type is retrieved as an integer and cast to a | 
|---|
| 252 | ChartType enum value. We create a default label font and then read the | 
|---|
| 253 | "Font" setting, using the default we have just created if necessary. | 
|---|
| 254 | <p> Although QSettings can handle string lists we've chosen to store each | 
|---|
| 255 | recently used file as a separate entry to make it easier to hand edit | 
|---|
| 256 | the settings. We attempt to read each possible file entry ("File1" to | 
|---|
| 257 | "File9"), and add each non-empty entry to the list of recently used | 
|---|
| 258 | files. If there are one or more recently used files we update the File | 
|---|
| 259 | menu by calling updateRecentFilesMenu(); (we'll review this later on). | 
|---|
| 260 | <p> <pre>        <a href="qobject.html#connect">connect</a>( chartGroup, SIGNAL( <a href="qactiongroup.html#selected">selected</a>(QAction*) ), | 
|---|
| 261 | this, SLOT( updateChartType(QAction*) ) ); | 
|---|
| 262 | </pre> | 
|---|
| 263 | <p> Now that we have set the chart type (when we read it in as a user | 
|---|
| 264 | setting) it is safe to connect the chart group to our | 
|---|
| 265 | updateChartType() slot. | 
|---|
| 266 | <p> <pre>        <a href="qwidget.html#resize">resize</a>( windowWidth, windowHeight ); | 
|---|
| 267 | if ( windowX != -1 || windowY != -1 ) | 
|---|
| 268 | <a href="qwidget.html#move">move</a>( windowX, windowY ); | 
|---|
| 269 | </pre> | 
|---|
| 270 | <p> And now that we know the window size and position we can resize and | 
|---|
| 271 | move the chart form's window accordingly. | 
|---|
| 272 | <p> <pre>        m_canvas = new <a href="qcanvas.html">QCanvas</a>( this ); | 
|---|
| 273 | m_canvas-><a href="qcanvas.html#resize">resize</a>( <a href="qwidget.html#width">width</a>(), height() ); | 
|---|
| 274 | m_canvasView = new CanvasView( m_canvas, &m_elements, this ); | 
|---|
| 275 | <a href="qmainwindow.html#setCentralWidget">setCentralWidget</a>( m_canvasView ); | 
|---|
| 276 | m_canvasView-><a href="qwidget.html#show">show</a>(); | 
|---|
| 277 | </pre> | 
|---|
| 278 | <p> We create a new <a href="qcanvas.html">QCanvas</a> and set its size to that of the chart form | 
|---|
| 279 | window's client area. We also create a <tt>CanvasView</tt> (our own subclass | 
|---|
| 280 | of <a href="qcanvasview.html">QCanvasView</a>) to display the QCanvas. We make the canvas view the | 
|---|
| 281 | chart form's main widget and show it. | 
|---|
| 282 | <p> <pre>        if ( !filename.<a href="qstring.html#isEmpty">isEmpty</a>() ) | 
|---|
| 283 | load( filename ); | 
|---|
| 284 | else { | 
|---|
| 285 | init(); | 
|---|
| 286 | m_elements[0].set( 20, red,    14, "Red" ); | 
|---|
| 287 | m_elements[1].set( 70, cyan,    2, "Cyan",   darkGreen ); | 
|---|
| 288 | m_elements[2].set( 35, blue,   11, "Blue" ); | 
|---|
| 289 | m_elements[3].set( 55, yellow,  1, "Yellow", darkBlue ); | 
|---|
| 290 | m_elements[4].set( 80, magenta, 1, "Magenta" ); | 
|---|
| 291 | drawElements(); | 
|---|
| 292 | } | 
|---|
| 293 | </pre> | 
|---|
| 294 | <p> If we have a file to load we load it; otherwise we initialise our | 
|---|
| 295 | elements vector and draw a sample chart. | 
|---|
| 296 | <p> <pre>        <a href="qmainwindow.html#statusBar">statusBar</a>()->message( "Ready", 2000 ); | 
|---|
| 297 | </pre> | 
|---|
| 298 | <p> It is <em>vital</em> that we call statusBar() in the constructor, since the | 
|---|
| 299 | call ensures that a status bar is created for this main window. | 
|---|
| 300 | <p> <h3> init() | 
|---|
| 301 | </h3> | 
|---|
| 302 | <a name="2-1"></a><p> <pre>    void ChartForm::init() | 
|---|
| 303 | { | 
|---|
| 304 | <a href="qwidget.html#setCaption">setCaption</a>( "Chart" ); | 
|---|
| 305 | m_filename = <a href="qstring.html#QString-null">QString::null</a>; | 
|---|
| 306 | m_changed = FALSE; | 
|---|
| 307 |  | 
|---|
| 308 | m_elements[0]  = Element( Element::INVALID, red ); | 
|---|
| 309 | m_elements[1]  = Element( Element::INVALID, cyan ); | 
|---|
| 310 | m_elements[2]  = Element( Element::INVALID, blue ); | 
|---|
| 311 | </pre><tt>...</tt> | 
|---|
| 312 | <p> We use an init() function because we want to initialise the canvas and | 
|---|
| 313 | the elements (in the <tt>m_elements</tt> <tt>ElementVector</tt>) when the form is | 
|---|
| 314 | constructed, and also whenever the user loads an existing data set or | 
|---|
| 315 | creates a new data set. | 
|---|
| 316 | <p> We reset the caption and set the current filename to QString::null. We | 
|---|
| 317 | also populate the elements vector with invalid elements. This isn't | 
|---|
| 318 | necessary, but giving each element a different color is more | 
|---|
| 319 | convenient for the user since when they enter values each one will | 
|---|
| 320 | already have a unique color (which they can change of course). | 
|---|
| 321 | <p> <h2> The File Handling Actions | 
|---|
| 322 | </h2> | 
|---|
| 323 | <a name="3"></a><p> <h3> okToClear() | 
|---|
| 324 | </h3> | 
|---|
| 325 | <a name="3-1"></a><p> <pre>    bool ChartForm::okToClear() | 
|---|
| 326 | { | 
|---|
| 327 | if ( m_changed ) { | 
|---|
| 328 | <a href="qstring.html">QString</a> msg; | 
|---|
| 329 | if ( m_filename.isEmpty() ) | 
|---|
| 330 | msg = "Unnamed chart "; | 
|---|
| 331 | else | 
|---|
| 332 | msg = QString( "Chart '%1'\n" ).arg( m_filename ); | 
|---|
| 333 | msg += "has been changed."; | 
|---|
| 334 |  | 
|---|
| 335 | int x = QMessageBox::<a href="qmessagebox.html#information">information</a>( this, "Chart -- Unsaved Changes", | 
|---|
| 336 | msg, "&Save", "Cancel", "&Abandon", | 
|---|
| 337 | 0, 1 ); | 
|---|
| 338 | switch( x ) { | 
|---|
| 339 | case 0: // Save | 
|---|
| 340 | fileSave(); | 
|---|
| 341 | break; | 
|---|
| 342 | case 1: // Cancel | 
|---|
| 343 | default: | 
|---|
| 344 | return FALSE; | 
|---|
| 345 | case 2: // Abandon | 
|---|
| 346 | break; | 
|---|
| 347 | } | 
|---|
| 348 | } | 
|---|
| 349 |  | 
|---|
| 350 | return TRUE; | 
|---|
| 351 | } | 
|---|
| 352 | </pre> | 
|---|
| 353 | <p> The okToClear() function is used to prompt the user to save their | 
|---|
| 354 | values if they have any unsaved data. It is used by several other | 
|---|
| 355 | functions. | 
|---|
| 356 | <p> <h3> fileNew() | 
|---|
| 357 | </h3> | 
|---|
| 358 | <a name="3-2"></a><p> | 
|---|
| 359 |  | 
|---|
| 360 | <pre>    void ChartForm::fileNew() | 
|---|
| 361 | { | 
|---|
| 362 | if ( okToClear() ) { | 
|---|
| 363 | init(); | 
|---|
| 364 | drawElements(); | 
|---|
| 365 | } | 
|---|
| 366 | } | 
|---|
| 367 | </pre> | 
|---|
| 368 | <p> When the user invokes the fileNew() action we call okToClear() to give | 
|---|
| 369 | them the opportunity to save any unsaved data. If they either save or | 
|---|
| 370 | abandon or have no unsaved data we re-initialise the elements vector | 
|---|
| 371 | and draw the default chart. | 
|---|
| 372 | <p> <center><table cellpadding="4" cellspacing="2" border="0"> | 
|---|
| 373 | <tr bgcolor="#d0d0d0"> | 
|---|
| 374 | <td valign="top">Should we also have invoked optionsSetData() to pop up the dialog | 
|---|
| 375 | through which the user can create and edit values, colors etc? You | 
|---|
| 376 | could try running the application as it is, and then try it having | 
|---|
| 377 | added a call to optionsSetData() and see which you prefer. | 
|---|
| 378 | </table></center> | 
|---|
| 379 | <p> <h3> fileOpen() | 
|---|
| 380 | </h3> | 
|---|
| 381 | <a name="3-3"></a><p> <pre>    void ChartForm::fileOpen() | 
|---|
| 382 | { | 
|---|
| 383 | if ( !okToClear() ) | 
|---|
| 384 | return; | 
|---|
| 385 |  | 
|---|
| 386 | <a href="qstring.html">QString</a> filename = QFileDialog::<a href="qfiledialog.html#getOpenFileName">getOpenFileName</a>( | 
|---|
| 387 | QString::null, "Charts (*.cht)", this, | 
|---|
| 388 | "file open", "Chart -- File Open" ); | 
|---|
| 389 | <a name="x2567"></a>    if ( !filename.<a href="qstring.html#isEmpty">isEmpty</a>() ) | 
|---|
| 390 | load( filename ); | 
|---|
| 391 | else | 
|---|
| 392 | <a href="qmainwindow.html#statusBar">statusBar</a>()->message( "File Open abandoned", 2000 ); | 
|---|
| 393 | } | 
|---|
| 394 | </pre> | 
|---|
| 395 | <p> We check that it is okToClear(). If it is we use the static | 
|---|
| 396 | <a href="qfiledialog.html#getOpenFileName">QFileDialog::getOpenFileName</a>() function to get the name of the file | 
|---|
| 397 | the user wishes to load. If we get a filename we call load(). | 
|---|
| 398 | <p> <h3> fileSaveAs() | 
|---|
| 399 | </h3> | 
|---|
| 400 | <a name="3-4"></a><p> <pre>    void ChartForm::fileSaveAs() | 
|---|
| 401 | { | 
|---|
| 402 | <a href="qstring.html">QString</a> filename = QFileDialog::<a href="qfiledialog.html#getSaveFileName">getSaveFileName</a>( | 
|---|
| 403 | QString::null, "Charts (*.cht)", this, | 
|---|
| 404 | "file save as", "Chart -- File Save As" ); | 
|---|
| 405 | if ( !filename.<a href="qstring.html#isEmpty">isEmpty</a>() ) { | 
|---|
| 406 | int answer = 0; | 
|---|
| 407 | <a name="x2563"></a>        if ( QFile::<a href="qfile.html#exists">exists</a>( filename ) ) | 
|---|
| 408 | <a name="x2566"></a>            answer = QMessageBox::<a href="qmessagebox.html#warning">warning</a>( | 
|---|
| 409 | this, "Chart -- Overwrite File", | 
|---|
| 410 | QString( "Overwrite\n\'%1\'?" ). | 
|---|
| 411 | arg( filename ), | 
|---|
| 412 | "&Yes", "&No", QString::null, 1, 1 ); | 
|---|
| 413 | if ( answer == 0 ) { | 
|---|
| 414 | m_filename = filename; | 
|---|
| 415 | updateRecentFiles( filename ); | 
|---|
| 416 | fileSave(); | 
|---|
| 417 | return; | 
|---|
| 418 | } | 
|---|
| 419 | } | 
|---|
| 420 | <a href="qmainwindow.html#statusBar">statusBar</a>()->message( "Saving abandoned", 2000 ); | 
|---|
| 421 | } | 
|---|
| 422 | </pre> | 
|---|
| 423 | <p> This function calls the static <a href="qfiledialog.html#getSaveFileName">QFileDialog::getSaveFileName</a>() to get | 
|---|
| 424 | the name of the file to save the data in. If the file exists we use a | 
|---|
| 425 | <a href="qmessagebox.html#warning">QMessageBox::warning</a>() to notify the user and give them the option of | 
|---|
| 426 | abandoning the save. If the file is to be saved we update the recently | 
|---|
| 427 | opened files list and call fileSave() (covered in <a href="tutorial2-07.html">File Handling</a>) to perform the save. | 
|---|
| 428 | <p> <h2> Managing a list of Recently Opened Files | 
|---|
| 429 | </h2> | 
|---|
| 430 | <a name="4"></a><p> | 
|---|
| 431 |  | 
|---|
| 432 | <pre>        <a href="qstringlist.html">QStringList</a> m_recentFiles; | 
|---|
| 433 | </pre> | 
|---|
| 434 | <p> We hold the list of recently opened files in a string list. | 
|---|
| 435 | <p> | 
|---|
| 436 |  | 
|---|
| 437 | <pre>    void ChartForm::updateRecentFilesMenu() | 
|---|
| 438 | { | 
|---|
| 439 | for ( int i = 0; i < MAX_RECENTFILES; ++i ) { | 
|---|
| 440 | if ( fileMenu-><a href="qmenudata.html#findItem">findItem</a>( i ) ) | 
|---|
| 441 | fileMenu-><a href="qmenudata.html#removeItem">removeItem</a>( i ); | 
|---|
| 442 | if ( i < int(m_recentFiles.count()) ) | 
|---|
| 443 | fileMenu-><a href="qmenudata.html#insertItem">insertItem</a>( QString( "&%1 %2" ). | 
|---|
| 444 | arg( i + 1 ).arg( m_recentFiles[i] ), | 
|---|
| 445 | this, SLOT( fileOpenRecent(int) ), | 
|---|
| 446 | 0, i ); | 
|---|
| 447 | } | 
|---|
| 448 | } | 
|---|
| 449 | </pre> | 
|---|
| 450 | <p> This function is called (usually via updateRecentFiles()) whenever the | 
|---|
| 451 | user opens an existing file or saves a new file. For each file in the | 
|---|
| 452 | string list we insert a new menu item. We prefix each filename with an | 
|---|
| 453 | underlined number from <u>1</u> to <u>9</u> to support keyboard access | 
|---|
| 454 | (e.g. <tt>Alt+F, 2</tt> to open the second file in the list). We give the | 
|---|
| 455 | menu item an id which is the same as the index position of the item in | 
|---|
| 456 | the string list, and connect each menu item to the fileOpenRecent() | 
|---|
| 457 | slot. The old file menu items are deleted at the same time by going | 
|---|
| 458 | through each possible recent file menu item id. This works because the | 
|---|
| 459 | other file menu items had ids created by Qt (all of which are < 0); | 
|---|
| 460 | whereas the menu items we're creating all have ids >= 0. | 
|---|
| 461 | <p> | 
|---|
| 462 |  | 
|---|
| 463 | <pre>    void ChartForm::updateRecentFiles( const <a href="qstring.html">QString</a>& filename ) | 
|---|
| 464 | { | 
|---|
| 465 | if ( m_recentFiles.find( filename ) != m_recentFiles.end() ) | 
|---|
| 466 | return; | 
|---|
| 467 |  | 
|---|
| 468 | m_recentFiles.push_back( filename ); | 
|---|
| 469 | if ( m_recentFiles.count() > MAX_RECENTFILES ) | 
|---|
| 470 | m_recentFiles.pop_front(); | 
|---|
| 471 |  | 
|---|
| 472 | updateRecentFilesMenu(); | 
|---|
| 473 | } | 
|---|
| 474 | </pre> | 
|---|
| 475 | <p> This is called when the user opens an existing file or saves a new | 
|---|
| 476 | file. If the file is already in the list it simply returns. Otherwise | 
|---|
| 477 | the file is added to the end of the list and if the list is too large | 
|---|
| 478 | (> 9 files) the first (oldest) is removed. updateRecentFilesMenu() is | 
|---|
| 479 | then called to recreate the list of recently used files in the File | 
|---|
| 480 | menu. | 
|---|
| 481 | <p> | 
|---|
| 482 |  | 
|---|
| 483 | <pre>    void ChartForm::fileOpenRecent( int index ) | 
|---|
| 484 | { | 
|---|
| 485 | if ( !okToClear() ) | 
|---|
| 486 | return; | 
|---|
| 487 |  | 
|---|
| 488 | load( m_recentFiles[index] ); | 
|---|
| 489 | } | 
|---|
| 490 | </pre> | 
|---|
| 491 | <p> When the user selects a recently opened file the fileOpenRecent() slot | 
|---|
| 492 | is called with the menu id of the file they have selected. Because we | 
|---|
| 493 | made the file menu ids equal to the files' index positions in the | 
|---|
| 494 | <tt>m_recentFiles</tt> list we can simply load the file indexed by the menu | 
|---|
| 495 | item id. | 
|---|
| 496 | <p> <h2> Quiting | 
|---|
| 497 | </h2> | 
|---|
| 498 | <a name="5"></a><p> <pre>    void ChartForm::fileQuit() | 
|---|
| 499 | { | 
|---|
| 500 | if ( okToClear() ) { | 
|---|
| 501 | saveOptions(); | 
|---|
| 502 | qApp-><a href="qapplication.html#exit">exit</a>( 0 ); | 
|---|
| 503 | } | 
|---|
| 504 | } | 
|---|
| 505 | </pre> | 
|---|
| 506 | <p> When the user quits we give them the opportunity to save any unsaved | 
|---|
| 507 | data (okToClear()) then save their options, e.g. window size and | 
|---|
| 508 | position, chart type, etc., before terminating. | 
|---|
| 509 | <p> <pre>    void ChartForm::saveOptions() | 
|---|
| 510 | { | 
|---|
| 511 | <a href="qsettings.html">QSettings</a> settings; | 
|---|
| 512 | settings.<a href="qsettings.html#insertSearchPath">insertSearchPath</a>( QSettings::Windows, WINDOWS_REGISTRY ); | 
|---|
| 513 | settings.<a href="qsettings.html#writeEntry">writeEntry</a>( APP_KEY + "WindowWidth", width() ); | 
|---|
| 514 | settings.<a href="qsettings.html#writeEntry">writeEntry</a>( APP_KEY + "WindowHeight", height() ); | 
|---|
| 515 | settings.<a href="qsettings.html#writeEntry">writeEntry</a>( APP_KEY + "WindowX", x() ); | 
|---|
| 516 | settings.<a href="qsettings.html#writeEntry">writeEntry</a>( APP_KEY + "WindowY", y() ); | 
|---|
| 517 | settings.<a href="qsettings.html#writeEntry">writeEntry</a>( APP_KEY + "ChartType", int(m_chartType) ); | 
|---|
| 518 | settings.<a href="qsettings.html#writeEntry">writeEntry</a>( APP_KEY + "AddValues", int(m_addValues) ); | 
|---|
| 519 | settings.<a href="qsettings.html#writeEntry">writeEntry</a>( APP_KEY + "Decimals", m_decimalPlaces ); | 
|---|
| 520 | settings.<a href="qsettings.html#writeEntry">writeEntry</a>( APP_KEY + "Font", m_font.toString() ); | 
|---|
| 521 | for ( int i = 0; i < int(m_recentFiles.count()); ++i ) | 
|---|
| 522 | settings.<a href="qsettings.html#writeEntry">writeEntry</a>( APP_KEY + "File" + QString::number( i + 1 ), | 
|---|
| 523 | m_recentFiles[i] ); | 
|---|
| 524 | } | 
|---|
| 525 | </pre> | 
|---|
| 526 | <p> Saving the user's options using <a href="qsettings.html">QSettings</a> is straight-forward. | 
|---|
| 527 | <p> <h2> Custom Dialogs | 
|---|
| 528 | </h2> | 
|---|
| 529 | <a name="6"></a><p> We want the user to be able to set some options manually and to create | 
|---|
| 530 | and edit values, value colors, etc. | 
|---|
| 531 | <p> | 
|---|
| 532 |  | 
|---|
| 533 | <pre>    void ChartForm::optionsSetOptions() | 
|---|
| 534 | { | 
|---|
| 535 | OptionsForm *optionsForm = new OptionsForm( this ); | 
|---|
| 536 | optionsForm->chartTypeComboBox->setCurrentItem( m_chartType ); | 
|---|
| 537 | optionsForm-><a href="qwidget.html#setFont">setFont</a>( m_font ); | 
|---|
| 538 | </pre><pre>        if ( optionsForm-><a href="qdialog.html#exec">exec</a>() ) { | 
|---|
| 539 | setChartType( ChartType( | 
|---|
| 540 | optionsForm->chartTypeComboBox->currentItem()) ); | 
|---|
| 541 | m_font = optionsForm-><a href="qwidget.html#font">font</a>(); | 
|---|
| 542 | </pre><pre>            drawElements(); | 
|---|
| 543 | } | 
|---|
| 544 | delete optionsForm; | 
|---|
| 545 | } | 
|---|
| 546 | </pre> | 
|---|
| 547 | <p> The form for setting options is provided by our custom <tt>OptionsForm</tt> | 
|---|
| 548 | covered in <a href="tutorial2-09.html">Setting Options</a>. The | 
|---|
| 549 | options form is a standard "dumb" dialog: we create an instance, set | 
|---|
| 550 | all its GUI elements to the relevant settings, and if the user clicked | 
|---|
| 551 | "OK" (exec() returns a true value) we read back settings from the GUI | 
|---|
| 552 | elements. | 
|---|
| 553 | <p> | 
|---|
| 554 |  | 
|---|
| 555 | <pre>    void ChartForm::optionsSetData() | 
|---|
| 556 | { | 
|---|
| 557 | SetDataForm *setDataForm = new SetDataForm( &m_elements, m_decimalPlaces, this ); | 
|---|
| 558 | if ( setDataForm-><a href="qdialog.html#exec">exec</a>() ) { | 
|---|
| 559 | m_changed = TRUE; | 
|---|
| 560 | drawElements(); | 
|---|
| 561 | } | 
|---|
| 562 | delete setDataForm; | 
|---|
| 563 | } | 
|---|
| 564 | </pre> | 
|---|
| 565 | <p> The form for creating and editing chart data is provided by our custom | 
|---|
| 566 | <tt>SetDataForm</tt> covered in <a href="tutorial2-08.html">Taking Data</a>. | 
|---|
| 567 | This form is a "smart" dialog. We pass in the data structure we want | 
|---|
| 568 | to work on, and the dialog handles the presentation of the data | 
|---|
| 569 | structure itself. If the user clicks "OK" the dialog will update the | 
|---|
| 570 | data structure and exec() will return a true value. All we need to do | 
|---|
| 571 | in optionsSetData() if the user changed the data is mark the chart as | 
|---|
| 572 | changed and call drawElements() to redraw the chart with the new and | 
|---|
| 573 | updated data. | 
|---|
| 574 | <p> <p align="right"> | 
|---|
| 575 | <a href="tutorial2-04.html">« Mainly Easy</a> | | 
|---|
| 576 | <a href="tutorial2.html">Contents</a> | | 
|---|
| 577 | <a href="tutorial2-06.html">Canvas Control »</a> | 
|---|
| 578 | </p> | 
|---|
| 579 | <p> | 
|---|
| 580 | <!-- eof --> | 
|---|
| 581 | <p><address><hr><div align=center> | 
|---|
| 582 | <table width=100% cellspacing=0 border=0><tr> | 
|---|
| 583 | <td>Copyright © 2007 | 
|---|
| 584 | <a href="troll.html">Trolltech</a><td align=center><a href="trademarks.html">Trademarks</a> | 
|---|
| 585 | <td align=right><div align=right>Qt 3.3.8</div> | 
|---|
| 586 | </table></div></address></body> | 
|---|
| 587 | </html> | 
|---|