Home | All Classes | Main Classes | Annotated | Grouped Classes | Functions |

[Previous: Using Qt Main Window Classes] [Home] [Next: Replacing the View Widget]

# Refactoring Existing Code

In the author's view, the existing code is slightly disorganized. Even though the code does work, some cleanups and reorganization can only help with readability and maintainability. The steps described below are not necessary during the migration process but are included for completeness.

## Migrating Data Structures to C++

The `Page` data structure is an opaque data type. The real data structure is called `PageRec`; `Page` defined to be a pointer to a `PageRec`. In addition, we have the `AllocPage()` function that allocates and initializes memory for a `PageRec` struct.

With C++, we can do this in the constructor. We can also write a destructor which automatically frees all resources in the `PageRec`, instead of having to do it in several different places.

The `PageRec` struct declaration is removed from `page.h`. We declare a `Page` struct with the same data members as `PageRec`, a constructor and a destructor.

        struct Page {
            Page()
                : page( 0 ), majorTab( 0 ), minorTab( 0 ), label( 0 ),
                  minorPB( (Widget) 0 ), majorPB( (Widget) 0 ),
                  lasttoppos( 0 ), lastcursorpos( 0 )
            {
            }

            ~Page()
            {
                if ( page )     XtFree( page );
                if ( majorTab ) XtFree( majorTab );
                if ( minorTab ) XtFree( minorTab );
                if ( label )    XtFree( label );

                if ( minorPB ) XtDestroyWidget( minorPB );
                if ( majorPB ) XtDestroyWidget( majorPB );
            }

            char *page;
            char *majorTab;
            char *minorTab;
            char *label;
            Widget minorPB;
            Widget majorPB;
            int lasttoppos;
            int lastcursorpos;

The existing `pages`, `currentPage` and `maxpages` global variables are removed from the source files. We replace them with `extern` declarations in `page.h`.

        extern Page *pages[];
        extern int currentPage;
        extern int maxpages;

The global variable instantiations are placed in `todo.cpp`.

Each source file contains function declarations that deal with the global `Page` related variables. We remove these declarations from the source files and declare them once in the `page.h` header file.

        void SetPage(int);
        void AdjustPages(int, int);
        void FixPages();

Now that `Page` has a constructor, we remove the `AllocPage()` function. It is no longer needed. The calls to `AllocPage()` are replaced with `'new Page()'` in the `NewPage()`, `DeletePage()` and `ReadDB()` functions. We also replace the code to deallocate pages with `delete pages[X]`, where X is the appropriate index value. This is done in the `ReadDB` and `DeletePage()` functions.

Code that accesses the global `pages` variable does not need to be modified since the data members of the `Page` struct did not change. The existing code will continue to work.

The `OptionsRec` struct declared in `page.h` is also updated, following the same pattern as the `Page` struct above.

        struct Options {
            Options() : todoFile( 0 ) { }
            ~Options()
            {
                if ( todoFile ) XtFree( todoFile );
            }

            String todoFile;
        };

        extern Options options;

The global variable instantiation is also placed in `todo.cpp`.

Code that accesses the global `options` variable does not need to be modified since the data members of the `Options` struct did not change. The existing code will continue to work.

## Using new and delete

The destructors of the `Page` and `Options` structs use delete instead of `XtFree()` to deallocate all `char*` members. This is a necessary change since we are migrating away from Xt/Motif. We need to fix existing code that modifies the `Page` struct members to use new and delete ( instead of `XtMalloc()`, `XtNewString()` and `XtFree()` ).

The `PageChange()` function in `todo.cpp` simply saves the contents and cursor position of current page before calling `SetPage()`. We use new and delete when modifying members of the `Page` struct.

        PageChange(Widget, XtPointer, XmNotebookCallbackStruct *cs)
        {
          if (modified && pages[currentPage] != NULL) {
            delete [] pages[currentPage] -> page;

When storing the context of the `XmText` widget, we use `qstrdup()` to make a copy of the string returned by the `XmTextGetString()` function.

            char *p = XmTextGetString(textw);
            pages[currentPage] -> page = qstrdup( p );
            XtFree( p );

            pages[currentPage] -> lasttoppos = XmTextGetTopCharacter(textw);
            pages[currentPage] -> lastcursorpos = XmTextGetInsertionPosition(textw);
          }

          SetPage(cs -> page_number - 1);

The `ReadDB()` function in `io.cpp` needs similar changes. We replace all use of `XtMalloc()` and `XtNewString()` with new and `qstrdup()`, respectively.

This needs to be done just after opening the file.

          if (input != NULL) {
            max = MAXINIT;
            buffer = new char[max];
            buffer[0] = 0; /* Reset page buffer */
            current = 0;
            pages[0] = new Page();

... when starting a new page ...

            ...
                        pages[number] -> page = buffer;
                        current = 0;
                        max = MAXINIT;
                        buffer = new char[max];
                        buffer[0] = 0; /* Reset page buffer */
                        number++;
                        pages[number] = new Page();

... and when reading in the label and tab texts/

            ...
            ...
            ...

The `ReadDB()` function uses `XtRealloc()` to expand the data storage buffer. Unfortunately, C++ does not provide a way to reallocate an existing block of data, so we have to do this ourselves.

                    // C++ doesn't have 'renew', so we need to create a new
                    // buffer, copy the existing data from old to new and then
                    // delete the old

                    int newmax = 2 * max;
                    char *b = new char[newmax];
                    memcpy( b, buffer, max );

                    delete [] buffer;
                    buffer = b;
                    max = newmax;

There is also one occurence in `ReadDB()` where we call `XtMalloc()` with an argument of 2. This was done when a file could not be read. Creating an empty string is not necessary, so we remove this code instead of using new.

          if (input == NULL) {
            number = 0;
            pages[0] = new Page();
          } else {
            pages[number] -> page = buffer;
          }

The `SaveDB()` function in `io.cpp` also needs these changes. We change one occurence of `XtFree()` to delete.

          /* Make sure to grab current page */
          if (modified) {
            delete [] pages[currentPage] -> page;

Finally, We need to replace two occurences of `XtNewString()` in the `main()` function in `todo.cpp`.

          if (options.todoFile == NULL) {
            strcpy(temppath, user -> pw_dir);
            strcat(temppath, "/.todo");
            options.todoFile = qstrdup( temppath );
          } else {
            /* Copy the string for consistency */
            options.todoFile = qstrdup( options.todoFile );

## Moving Existing Code

The rest of the refactoring process involves moving existing code into new places. Currently, each function in the `mainwindow.ui.h` file simply calls the old callback handlers present in the other files. Instead of calling the old callback functions, the implementations are moved accordingly.

Function | Original File | Moved to Function
--- | --- | ---
`New()` | `todo.cpp` | `MainWindow::fileNew()`
`Open()` | `todo.cpp` | `MainWindow::fileOpen()`
`SaveIt()` | `actions.cpp` | `MainWindow::fileSave()`
`Save()` | `todo.cpp` | `MainWindow::fileSaveAs()`
`ShowPrintDialog()` | `todo.cpp` | `MainWindow::filePrint()`
`EditPage()` | `actions.cpp` | `MainWindow::selProperties()`
`NewPage()` | `actions.cpp` | `MainWindow::selNewPage()`
`DeletePage()` | `actions.cpp` | `MainWindow::selDeletePage()`

The `Print()` callback function is still used by the Print dialog, so we move it into `mainwindow.ui.h` and make it `static`.

Previously, the `Open()`, `Save()`, `EditPage()` and `DeletePage()` functions created dialogs with client_data as the parent argument. Since we have moved the code directly into the Main Window implementation, we create these dialogs with this as the parent argument.

The `PageChange()` callback function is moved from `actions.cpp` to `todo.cpp` and made `static` since it is not used anywhere else.

Earlier modifications to `actions.cpp` caused the `Trim()` function to become redundant, so we remove it.

The `MIN()` and `MAX()` macros in `todo.cpp` are redundant. Qt provides the `qMin()` and `qMax()` functions which we will use.

Earlier modifications caused the `fallback_resources` array to become redundant, so we remove it.

In the near future, our program will not use Motif any more, and we will no longer need to use QMotif. To prepare for this, we remove the `resources` and `optionDesc` arrays and create the QMotif instance with just the APP_CLASS argument.

The `#include` statements in the source files are mostly incorrect due to the refactoring changes. Many of the `#include` statements are no longer needed. The `#include` statements from each file are listed below, instead of describing which includes are removed and added to each file.

Includes for `actions.cpp`:

        #include <Xm/Xm.h>
        #include <Xm/Notebook.h>
        #include <Xm/Text.h>

        #include "page.h"

Includes for `io.cpp`:

        #include <qmessagebox.h>

        #include <unistd.h>

        // Motif includes
        #include <Xm/Xm.h>
        #include <Xm/Text.h>

        #include "page.h"

        // Demo includes
        extern "C" {
        #include <Exm/TabB.h>
        }

Includes for `todo.cpp`:

        #include <qapplication.h>
        #include <qmotif.h>
        #include <qmotifwidget.h>

        #include <pwd.h>
        #include <unistd.h>
        #include <stdlib.h>

        #include <Xm/Xm.h>
        #include <Xm/Label.h>
        #include <Xm/Notebook.h>
        #include <Xm/Text.h>

        #include "page.h"

Includes for `mainwindow.ui.h`:

        #include <qapplication.h>
        #include <qfiledialog.h>
        #include <qlineedit.h>
        #include <qmessagebox.h>
        #include <qstatusbar.h>

        #include <qmotifwidget.h>
        #include <qmotifdialog.h>

        #include <unistd.h>

        #include <X11/Intrinsic.h>
        #include <X11/StringDefs.h>
        #include <X11/Xatom.h>

        #include <Xm/Xm.h>
        #include <Xm/Text.h>

        #include "page-refactor.h"

        extern "C" {
        #include <Exm/TabB.h>
        #include <Xmd/Print.h>
        } // extern "C"

[Previous: Using Qt Main Window Classes] [Home] [Next: Replacing the View Widget]