From d796c9dd933ab96ec83b9a634feedd5d32e1ba3f Mon Sep 17 00:00:00 2001 From: Timothy Pearson Date: Tue, 8 Nov 2011 12:31:36 -0600 Subject: Test conversion to TQt3 from Qt3 8c6fc1f8e35fd264dd01c582ca5e7549b32ab731 --- doc/html/tutorial2-05.html | 587 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 587 insertions(+) create mode 100644 doc/html/tutorial2-05.html (limited to 'doc/html/tutorial2-05.html') diff --git a/doc/html/tutorial2-05.html b/doc/html/tutorial2-05.html new file mode 100644 index 000000000..9f9bbec06 --- /dev/null +++ b/doc/html/tutorial2-05.html @@ -0,0 +1,587 @@ + + + + + +Presenting the GUI + + + + + + + +
+ +Home + | +All Classes + | +Main Classes + | +Annotated + | +Grouped Classes + | +Functions +

Presenting the GUI

+ + +

+

The chart application
+

The 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 chartform.h.) +

+ +

    class ChartForm: public TQMainWindow
+    {
+        Q_OBJECT
+    public:
+        enum { MAX_ELEMENTS = 100 };
+        enum { MAX_RECENTFILES = 9 }; // Must not exceed 9
+        enum ChartType { PIE, VERTICAL_BAR, HORIZONTAL_BAR };
+        enum AddValuesType { NO, YES, AS_PERCENTAGE };
+
+        ChartForm( const TQString& filename );
+        ~ChartForm();
+
+        int chartType() { return m_chartType; }
+        void setChanged( bool changed = TRUE ) { m_changed = changed; }
+        void drawElements();
+
+        TQPopupMenu *optionsMenu; // Why public? See canvasview.cpp
+
+    protected:
+        virtual void closeEvent( TQCloseEvent * );
+
+    private slots:
+        void fileNew();
+        void fileOpen();
+        void fileOpenRecent( int index );
+        void fileSave();
+        void fileSaveAs();
+        void fileSaveAsPixmap();
+        void filePrint();
+        void fileQuit();
+        void optionsSetData();
+        void updateChartType( TQAction *action );
+        void optionsSetFont();
+        void optionsSetOptions();
+        void helpHelp();
+        void helpAbout();
+        void helpAboutTQt();
+        void saveOptions();
+
+    private:
+        void init();
+        void load( const TQString& filename );
+        bool okToClear();
+        void drawPieChart( const double scales[], double total, int count );
+        void drawVerticalBarChart( const double scales[], double total, int count );
+        void drawHorizontalBarChart( const double scales[], double total, int count );
+
+        TQString valueLabel( const TQString& label, double value, double total );
+        void updateRecentFiles( const TQString& filename );
+        void updateRecentFilesMenu();
+        void setChartType( ChartType chartType );
+
+        TQPopupMenu *fileMenu;
+        TQAction *optionsPieChartAction;
+        TQAction *optionsHorizontalBarChartAction;
+        TQAction *optionsVerticalBarChartAction;
+        TQString m_filename;
+        TQStringList m_recentFiles;
+        TQCanvas *m_canvas;
+        CanvasView *m_canvasView;
+        bool m_changed;
+        ElementVector m_elements;
+        TQPrinter *m_printer;
+        ChartType m_chartType;
+        AddValuesType m_addValues;
+        int m_decimalPlaces;
+        TQFont m_font;
+    };
+
+

We create a ChartForm subclass of TQMainWindow. Our subclass uses +the Q_OBJECT macro to support TQt's signals and slots 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. +

+ +
The TQCanvas class is used for drawing 2D vector graphics. The +TQCanvasView 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. +
+

Each action is represented by a private slot, e.g. fileNew(), optionsSetData(), etc. We also have tquite a number of private +functions and data members; we'll look at all these as we go through +the implementation. +

For the sake of convenience and compilation speed the chart form's +implementation is split over three files, chartform.cpp for the +GUI, chartform_canvas.cpp for the canvas handling and chartform_files.cpp for the file handling. We'll review each in turn. +

The Chart Form GUI +

+

(Extracts from chartform.cpp.) +

+ +

    #include "images/file_new.xpm"
+    #include "images/file_open.xpm"
+
    #include "images/options_piechart.xpm"
+
+

All the images used by chart have been created as .xpm files +which we've placed in the images subdirectory. +

The Constructor +

+

    ChartForm::ChartForm( const TQString& filename )
+        : TQMainWindow( 0, 0, WDestructiveClose )
+
... +
        TQAction *fileNewAction;
+        TQAction *fileOpenAction;
+        TQAction *fileSaveAction;
+
+

For each user action we declare a TQAction pointer. Some actions are +declared in the header file because they need to be referred to +outside of the constructor. +

+ +
Most user actions are suitable as both menu items and as toolbar +buttons. TQt allows us to create a single TQAction 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. +
+

        fileNewAction = new TQAction(
+                "New Chart", TQPixmap( file_new ),
+                "&New", CTRL+Key_N, this, "new" );
+        connect( fileNewAction, SIGNAL( activated() ), this, SLOT( fileNew() ) );
+
+

When we construct an action we give it a name, an optional icon, a +menu text, and an accelerator short-cut key (or 0 if no accelerator is +retquired). We also make it a child of the form (by passing this). +When the user clicks a toolbar button or clicks a menu option the 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 +or a vertical bar chart 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 TQActionGroup and placing the +chart type actions in the group. +

        TQActionGroup *chartGroup = new TQActionGroup( this ); // Connected later
+        chartGroup->setExclusive( TRUE );
+
+

The action group becomes a child of the form (this) and the +exlusive behaviour is achieved by the setExclusive() call. +

        optionsPieChartAction = new TQAction(
+                "Pie Chart", TQPixmap( options_piechart ),
+                "&Pie Chart", CTRL+Key_I, chartGroup, "pie chart" );
+        optionsPieChartAction->setToggleAction( TRUE );
+
+

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. +

+ +
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. After we have set the +canvas type we will connect the group. +
+

Once we've created all our user actions we can create the toolbars and +menu options that will allow the user to invoke them. +

        TQToolBar* fileTools = new TQToolBar( this, "file operations" );
+        fileTools->setLabel( "File Operations" );
+        fileNewAction->addTo( fileTools );
+        fileOpenAction->addTo( fileTools );
+        fileSaveAction->addTo( fileTools );
+
... +
        fileMenu = new TQPopupMenu( this );
+        menuBar()->insertItem( "&File", fileMenu );
+        fileNewAction->addTo( fileMenu );
+        fileOpenAction->addTo( fileMenu );
+        fileSaveAction->addTo( fileMenu );
+
+

Toolbar actions and menu options are easily created from TQActions. +

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. +

        TQSettings settings;
+        settings.insertSearchPath( TQSettings::Windows, WINDOWS_REGISTRY );
+        int windowWidth = settings.readNumEntry( APP_KEY + "WindowWidth", 460 );
+        int windowHeight = settings.readNumEntry( APP_KEY + "WindowHeight", 530 );
+        int windowX = settings.readNumEntry( APP_KEY + "WindowX", -1 );
+        int windowY = settings.readNumEntry( APP_KEY + "WindowY", -1 );
+        setChartType( ChartType(
+                settings.readNumEntry( APP_KEY + "ChartType", int(PIE) ) ) );
+
        m_font = TQFont( "Helvetica", 18, TQFont::Bold );
+        m_font.fromString(
+                settings.readEntry( APP_KEY + "Font", m_font.toString() ) );
+        for ( int i = 0; i < MAX_RECENTFILES; ++i ) {
+            TQString filename = settings.readEntry( APP_KEY + "File" +
+                                                   TQString::number( i + 1 ) );
+            if ( !filename.isEmpty() )
+                m_recentFiles.push_back( filename );
+        }
+        if ( m_recentFiles.count() )
+            updateRecentFilesMenu();
+
+

The TQSettings class handles user settings in a platform-independent +way. We simply read and write settings, leaving TQSettings to handle +the platform dependencies. The insertSearchPath() call does nothing +except under Windows so does not have to be #ifdefed. +

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 TQSettings 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). +

        connect( chartGroup, SIGNAL( selected(TQAction*) ),
+                 this, SLOT( updateChartType(TQAction*) ) );
+
+

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. +

        resize( windowWidth, windowHeight );
+        if ( windowX != -1 || windowY != -1 )
+            move( windowX, windowY );
+
+

And now that we know the window size and position we can resize and +move the chart form's window accordingly. +

        m_canvas = new TQCanvas( this );
+        m_canvas->resize( width(), height() );
+        m_canvasView = new CanvasView( m_canvas, &m_elements, this );
+        setCentralWidget( m_canvasView );
+        m_canvasView->show();
+
+

We create a new TQCanvas and set its size to that of the chart form +window's client area. We also create a CanvasView (our own subclass +of TQCanvasView) to display the TQCanvas. We make the canvas view the +chart form's main widget and show it. +

        if ( !filename.isEmpty() )
+            load( filename );
+        else {
+            init();
+            m_elements[0].set( 20, red,    14, "Red" );
+            m_elements[1].set( 70, cyan,    2, "Cyan",   darkGreen );
+            m_elements[2].set( 35, blue,   11, "Blue" );
+            m_elements[3].set( 55, yellow,  1, "Yellow", darkBlue );
+            m_elements[4].set( 80, magenta, 1, "Magenta" );
+            drawElements();
+        }
+
+

If we have a file to load we load it; otherwise we initialise our +elements vector and draw a sample chart. +

        statusBar()->message( "Ready", 2000 );
+
+

It is vital that we call statusBar() in the constructor, since the +call ensures that a status bar is created for this main window. +

init() +

+

    void ChartForm::init()
+    {
+        setCaption( "Chart" );
+        m_filename = TQString::null;
+        m_changed = FALSE;
+
+        m_elements[0]  = Element( Element::INVALID, red );
+        m_elements[1]  = Element( Element::INVALID, cyan );
+        m_elements[2]  = Element( Element::INVALID, blue );
+
... +

We use an init() function because we want to initialise the canvas and +the elements (in the m_elements 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 TQString::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). +

The File Handling Actions +

+

okToClear() +

+

    bool ChartForm::okToClear()
+    {
+        if ( m_changed ) {
+            TQString msg;
+            if ( m_filename.isEmpty() )
+                msg = "Unnamed chart ";
+            else
+                msg = TQString( "Chart '%1'\n" ).arg( m_filename );
+            msg += "has been changed.";
+
+            int x = TQMessageBox::information( this, "Chart -- Unsaved Changes",
+                                              msg, "&Save", "Cancel", "&Abandon",
+                                              0, 1 );
+            switch( x ) {
+                case 0: // Save
+                    fileSave();
+                    break;
+                case 1: // Cancel
+                default:
+                    return FALSE;
+                case 2: // Abandon
+                    break;
+            }
+        }
+
+        return TRUE;
+    }
+
+

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. +

fileNew() +

+

+ +

    void ChartForm::fileNew()
+    {
+        if ( okToClear() ) {
+            init();
+            drawElements();
+        }
+    }
+
+

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. +

+ +
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. +
+

fileOpen() +

+

    void ChartForm::fileOpen()
+    {
+        if ( !okToClear() )
+            return;
+
+        TQString filename = TQFileDialog::getOpenFileName(
+                                TQString::null, "Charts (*.cht)", this,
+                                "file open", "Chart -- File Open" );
+        if ( !filename.isEmpty() )
+            load( filename );
+        else
+            statusBar()->message( "File Open abandoned", 2000 );
+    }
+
+

We check that it is okToClear(). If it is we use the static +TQFileDialog::getOpenFileName() function to get the name of the file +the user wishes to load. If we get a filename we call load(). +

fileSaveAs() +

+

    void ChartForm::fileSaveAs()
+    {
+        TQString filename = TQFileDialog::getSaveFileName(
+                                TQString::null, "Charts (*.cht)", this,
+                                "file save as", "Chart -- File Save As" );
+        if ( !filename.isEmpty() ) {
+            int answer = 0;
+            if ( TQFile::exists( filename ) )
+                answer = TQMessageBox::warning(
+                                this, "Chart -- Overwrite File",
+                                TQString( "Overwrite\n\'%1\'?" ).
+                                    arg( filename ),
+                                "&Yes", "&No", TQString::null, 1, 1 );
+            if ( answer == 0 ) {
+                m_filename = filename;
+                updateRecentFiles( filename );
+                fileSave();
+                return;
+            }
+        }
+        statusBar()->message( "Saving abandoned", 2000 );
+    }
+
+

This function calls the static TQFileDialog::getSaveFileName() to get +the name of the file to save the data in. If the file exists we use a +TQMessageBox::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 File Handling) to perform the save. +

Managing a list of Recently Opened Files +

+

+ +

        TQStringList m_recentFiles;
+
+

We hold the list of recently opened files in a string list. +

+ +

    void ChartForm::updateRecentFilesMenu()
+    {
+        for ( int i = 0; i < MAX_RECENTFILES; ++i ) {
+            if ( fileMenu->findItem( i ) )
+                fileMenu->removeItem( i );
+            if ( i < int(m_recentFiles.count()) )
+                fileMenu->insertItem( TQString( "&%1 %2" ).
+                                        arg( i + 1 ).arg( m_recentFiles[i] ),
+                                      this, SLOT( fileOpenRecent(int) ),
+                                      0, i );
+        }
+    }
+
+

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. 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 TQt (all of which are < 0); +whereas the menu items we're creating all have ids >= 0. +

+ +

    void ChartForm::updateRecentFiles( const TQString& filename )
+    {
+        if ( m_recentFiles.find( filename ) != m_recentFiles.end() )
+            return;
+
+        m_recentFiles.push_back( filename );
+        if ( m_recentFiles.count() > MAX_RECENTFILES )
+            m_recentFiles.pop_front();
+
+        updateRecentFilesMenu();
+    }
+
+

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. +

+ +

    void ChartForm::fileOpenRecent( int index )
+    {
+        if ( !okToClear() )
+            return;
+
+        load( m_recentFiles[index] );
+    }
+
+

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 +m_recentFiles list we can simply load the file indexed by the menu +item id. +

Quiting +

+

    void ChartForm::fileQuit()
+    {
+        if ( okToClear() ) {
+            saveOptions();
+            qApp->exit( 0 );
+        }
+    }
+
+

When the user tquits we give them the opportunity to save any unsaved +data (okToClear()) then save their options, e.g. window size and +position, chart type, etc., before terminating. +

    void ChartForm::saveOptions()
+    {
+        TQSettings settings;
+        settings.insertSearchPath( TQSettings::Windows, WINDOWS_REGISTRY );
+        settings.writeEntry( APP_KEY + "WindowWidth", width() );
+        settings.writeEntry( APP_KEY + "WindowHeight", height() );
+        settings.writeEntry( APP_KEY + "WindowX", x() );
+        settings.writeEntry( APP_KEY + "WindowY", y() );
+        settings.writeEntry( APP_KEY + "ChartType", int(m_chartType) );
+        settings.writeEntry( APP_KEY + "AddValues", int(m_addValues) );
+        settings.writeEntry( APP_KEY + "Decimals", m_decimalPlaces );
+        settings.writeEntry( APP_KEY + "Font", m_font.toString() );
+        for ( int i = 0; i < int(m_recentFiles.count()); ++i )
+            settings.writeEntry( APP_KEY + "File" + TQString::number( i + 1 ),
+                                 m_recentFiles[i] );
+    }
+
+

Saving the user's options using TQSettings is straight-forward. +

Custom Dialogs +

+

We want the user to be able to set some options manually and to create +and edit values, value colors, etc. +

+ +

    void ChartForm::optionsSetOptions()
+    {
+        OptionsForm *optionsForm = new OptionsForm( this );
+        optionsForm->chartTypeComboBox->setCurrentItem( m_chartType );
+        optionsForm->setFont( m_font );
+
        if ( optionsForm->exec() ) {
+            setChartType( ChartType(
+                    optionsForm->chartTypeComboBox->currentItem()) );
+            m_font = optionsForm->font();
+
            drawElements();
+        }
+        delete optionsForm;
+    }
+
+

The form for setting options is provided by our custom OptionsForm +covered in Setting Options. 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. +

+ +

    void ChartForm::optionsSetData()
+    {
+        SetDataForm *setDataForm = new SetDataForm( &m_elements, m_decimalPlaces, this );
+        if ( setDataForm->exec() ) {
+            m_changed = TRUE;
+            drawElements();
+        }
+        delete setDataForm;
+    }
+
+

The form for creating and editing chart data is provided by our custom +SetDataForm covered in Taking Data. +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 » +

+

+ +


+ +
Copyright © 2007 +TrolltechTrademarks +
TQt 3.3.8
+
+ -- cgit v1.2.1