Building a Simple Editor Part 2

From EDM2
Jump to: navigation, search

Written by Gordon Zeglinski

Preamble

Recently, I was playing around with the source to maplay (a freeware mpeg layer III audio decoder). For fun, I tweaked the code and tried compiling it under VisualAge C++ for Windows and Watcom C/C++ 10.6. According to the NT performance monitor, the VAC++ executable took roughly 20% less cpu (~45% for VAC++ executable and ~65% for Watcom executable on a 133 MHz pentinum).

IBM has two new books out on using VisualAge C++. I haven't read them yet, but a preliminary scan through them indicates focus on using the Visual Builder. I won't do a comprehensive review of them but will give a general opinion of them in the future.

They are: "VisualAge for C++ Visual, Programmers Handbook" ISBN 0-13-614322-9 and "Visual Modeling Technique, Object Technology using Visual Programming" ISBN 0-8053-2574-3

Introduction

In this issue, we revisit the editor to add search capabilities. In particular, we will see how to use canvas controls. Canvas controls position other control windows within themselves. This allows us to create dialog box like windows without using non-portable ".rc" files. In addition, canvas controls are somewhat analogous to layout managers in Java.

VisualAge C++ includes 3 types of canvas controls. They are implemented by the classes IMultiCellCanvas, ISetCanvas and ISplitCanvas. We will use the first two canvas types in this example. Without further delay let's get to it.

Canvas Controls

The IMultiCellCanvas control is divided into an arbitrary number of rows and columns. The width of a column is equal to the largest minimum width of a control in that column. The height of a row is determined in the same manner. All controls in a given column are sized to the same width. Similarly, all controls in a given row have the same height. A row and column can be marked as expandable. This allows the row or column to expand when the canvas expands. For example, the canvas is the client area for a resizeable frame window. When the frame is resized, the expandable rows and columns would increase in size so the canvas fills the entire client. Naturally, the controls in the expanded rows and columns would also resize.

One drawback to making all controls in a given row or column the same size is that controls that are supposed to be narrow become expanded to match the largest control. A practical example would be placing an "OK" button under an MLE. The button would become expanded to be the same width as the MLE. The way to prevent this, is to use an ISetCanvas. An ISetCanvas organizes its child control windows into "decks". Each deck contains controls separated by a definable amount of space. Decks can be oriented either vertically or horizontally. Typically, the ISetCanvas is used to group buttons together. Now that we've covered the basics of these two types of canvas controls, let's look at an example.

Searching the MLE

To search the MLE, we create a simple generic search class as shown below. MLESearch is derived from IFrameWindow and ICommandHandler. Its constructor expects to be given the parent and owner window in addition it needs a pointer to IMultiLineEdit instance it's working on. When an instance of MLESearch is created, a modal "dialog box" appears.

   class MLESearch:public IFrameWindow,public
   ICommandHandler{

   public:
     enum Buttons{FindID=10,FindNextID,DoneID};

     MLESearch(IWindow *Par, IWindow *Own,IMultiLineEdit *_edit,
               int ID=9000);
     ~MLESearch();

   protected:
                    //the search function
     int            FindFnc();

                    //handle button presses
     Boolean        command(ICommandEvent &event);

                    //pointer to the MLE we're working with
     IMultiLineEdit *edit;

                    //the main canvas and client window
     IMultiCellCanvas
                    Canvas;

                    //entry field to accept the search for text
     IEntryField    FindTxt;

                    //static text field to label the entry field
     IStaticText    label;

                    //second canvas to group the buttons
     ISetCanvas     Canvas2;
                    //buttons for user interaction
     IPushButton    Find,
                    FindNext,
                    Done;

     IString        fndText,    //text we are looking for
                    srchText;   //text in the MLE

     int            StartPos;   //position to start search at
     int            CursorPos,  //initial cursor position
                    top;        //initial top line
   };

It's important to note the parent owner windows in the constructor. Canvas windows can only position control windows that are their children. The code for the constructor follows:

   MLESearch::MLESearch(IWindow *Par, IWindow *Own,
                        IMultiLineEdit *_edit,int ID):
                 IFrameWindow(
                    IResourceId(ID),
                    Par,
                    Own,
                    IRectangle(),
                    dialogBackground|dialogBorder|titleBar,
                    "Search"
                 ),
                 srchText(
                    _edit->text()
                 ),
                 edit(
                    _edit
                 ),
                 Canvas(
                    1,
                    this,        //Frame window is parent
                    this         //Frame window is owner
                 ),
                 Canvas2(
                    2,
                    &Canvas,     //main canvas is parent and owner
                    &Canvas
                 ),
                 Find(
                    FindID,
                    &Canvas2,    //Set canvas is parent and owner
                    &Canvas2
                 ),
                 FindNext(
                    FindNextID,
                    &Canvas2,    //Set canvas is parent and owner
                    &Canvas2
                 ),
                 Done(
                    DoneID,
                    &Canvas2,    //Set canvas is parent and owner
                    &Canvas2
                 ),
                 FindTxt(
                    20,
                    &Canvas,     //main canvas is parent and owner
                    &Canvas
                 ),
                 label(
                    100,
                    &Canvas,     //main canvas is parent and owner
                    &Canvas
                 )
   {
     //save initial MLE settings
     CursorPos=StartPos=edit->cursorPosition();
     top=edit->top();

     //label the buttons
     Find.setText("Find");
     FindNext.setText("Find Next");
     Done.setText("Done");

     FindNext.enable(false);

     //set the label text
     label.setText("Find Text:");

     //put the children of "Canvas" into cells
     Canvas.addToCell(&label,2,2);
     Canvas.addToCell(&FindTxt,2,4);
     Canvas.addToCell(&Canvas2,2,6);

     //center the Find window over the owner
     IRectangle orect(Own->rect());
     IRectangle rect(orect.minX(),
                     orect.minY(),
                     orect.minX()+Canvas.minimumSize(true).width()+
                       IMultiCellCanvas::defaultCell().width(),
                     orect.minY()+Canvas.minimumSize().height()
                     );

     rect=frameRectFor(rect);

     IPair      c( (orect.width()-rect.width())/2 ,
                   (orect.height()-rect.height())/2 );
     orect.shrinkBy(c);

     ISize size( rect.width(),
                 rect.height() );

     moveSizeTo(IRectangle(orect.minXMinY(),size ) );

     setClient(&Canvas);
     handleEventsFor(this);

     show();
     showModally();
   }

Note: The order in which the control classes are created effects the tab order. In particular, the order in which the control windows appear in the class definition is VERY important. The parent and owner of a control MUST appear in the class definition before the control's definition. For example, see the order in which the canvas and button controls are defined.

The code for the destructor simply resets the cursor and top line position of the MLE. We have seen command handlers before. Therefor, the curious can look at the source code for details on the function "command". The resulting window is shown below.

Srchwin.gif

The actual searching is done in the following function. This function performs a case sensitive forward search.

   int MLESearch::FindFnc(){
     //do a case sensitive forward search from StartPos
     int Idx=srchText.indexOf(fndText,StartPos);

     if(Idx){
        //select the found text
        edit->selectRange(IRange(Idx-1,Idx-1+fndText.length()));
        StartPos=Idx+fndText.length();
        //move cursor to the start of the found text
        edit->setCursorPosition(Idx-1);
        //make found text the top line
        edit->setTop(edit->cursorLinePosition());
     }

   return Idx!=0; //Idx is not 0 is we have a match
   }

That's it for the find function. Nothing fancy but functional.

Wrapping Things Up

We have seen how to use the IMultiCellCanvas and ISetCanvas classes to create operating system independent dialog box type windows. These canvas controls only manipulate child windows. The order in which these child windows are created determines their tab order.