Dr. Dobb's is part of the Informa Tech Division of Informa PLC

This site is operated by a business or businesses owned by Informa PLC and all copyright resides with them. Informa PLC's registered office is 5 Howick Place, London SW1P 1WG. Registered in England and Wales. Number 8860726.


Channels ▼
RSS

C/C++

C Programming


Jan99: C Programming

Al is a DDJ contributing editor. He can be contacted at [email protected].


This month's project, a programmer's editor, is another chapter in the continuing saga of Quincy 99 and D-Flat 2000, which I introduced last month. Some of what I'll discuss this month goes back two months. What follows will be in future columns over the months to come. You can't just pass through here. You might need to get some back issues, and you certainly need to continue with this series if you want to see how it turns out.

Why another programmer's editor? Why indeed. I've almost finished writing one, and you can download it from DDJ (see "Resource Center," page 5) or my web site (http://www.midifitz.com/alstevens/editor/ and http://www.midifitz.com/alstevens/ quincy99/). The sites include binaries, source code, and installation instructions. It has the unimaginative name, Editor, which I hope is generic enough to keep it from infringing on someone's trademark and waking up their lawyers. I have several reasons for writing yet another programmer's editor. The Quincy 99 project I launched last month uses the MFC CEditView class that uses the Win32 CEdit control as its underlying editor. That editor control has a few drawbacks. First, its undo feature is minimal and it has no redo feature. Second, the control will not handle files larger than something less than 32 KB. That was okay for Quincies 96 and 97, which existed to support small exercise programs from a book and a CD-ROM. Quincy 99, however, has a bigger purpose as I explained last month, and it needs to support larger files. Some of the header files for the Win32 API and the C++ library are themselves too big for the CEdit control, and programmers need to look at those files from time to time. Third, in the CEditView class architecture, the view class encapsulates the document data -- there is no corresponding CEditDoc class -- which is a departure from the more intuitive document/view architecture that MFC applications typically use.

I considered using the Win32 rich-edit control document and view classes but rejected that idea for two reasons. First, the control carries some overhead baggage because of its larger purpose, which is to support rich-text format data files. Second, Quincy and the D-Flat 2000 application framework, which I began discussing last month, might someday wind up on a platform that does not support a rich-edit control.

Furthermore, having written text editors in the past and remembering all the convoluted programming idioms involving text row tables and such, I wanted to experiment with a programming model that uses standard C++ library containers to represent the document data values in memory. Specifically, I wanted to represent the lines of text as a std::vector of std::string objects.

Finally, I wanted to build an editor control configured as view and document base classes that are themselves derived from the MFC CDocument and CView classes (or the equivalent classes in another framework someday). By using this approach, I can insert the new classes into Quincy 99 without a lot of porting complexity, or so I hope. To test the editor classes, I built a simple multiple-document-interface MFC application, which is the Editor project and which turns out to be a handy standalone programmers' editor. As I write this column, I have not yet installed the editor classes into Quincy 99. Perhaps I will have by the time you read this.

Much Undo About Nothing

Two months ago I described an undo/ redo template class library that implements undo and redo operations for interactive applications. The library is parameterized on the document that gets updated and the types of elements that get inserted, deleted, and replaced in the document. I used the library in several applications and was confident of its generic nature. The Editor project proved, however, that the undo/redo library needed to be raised one more level of abstraction. The library used the parameterized element type to instantiate pointers to that type, inserting, deleting, and replacing elements in the document by using those pointers. That technique worked well because all my applications, until now, stored the document as a contiguous array of elements whose address does not change. Pointers to those elements within the array sufficed to represent their document position for the undo and redo operations.

Standard C++ containers work with iterator objects that can, in some cases, also be pointers. The iterator values, however, cannot be considered valid after the document has changed, even when the change merely appends an object to the container. The standard containers' template classes dynamically reallocate memory and move objects around to suit the purposes of containment and to effect the efficient allocation of memory. Consequently, the undo/redo logic that stored object addresses does not work when the document resides in a standard container. The undo/redo library has to templatize instead on a user-defined class that represents an object's position, independent of any memory or container architecture and independent of the type of the document element. In the case of Editor's vector of strings, the position object contains the logical row and column within the document of the data being inserted, deleted, or replaced.

Undo.h (available electronically; see "Resource Center," page 5) is a replacement for the file of the same name that I provided two months ago. The requirements for applications that use this library are slightly different than those for the earlier library. An application must provide the type that represents a document element's position, which could be a class or could be as simple as a pointer. The application must provide functions that use an object of that type to insert, delete, and replace document elements in the document. The undo/redo library calls those functions. These functions are member functions in the templatized document class provided by the application. Finally, the application must provide a function that retrieves an element object from the document given an object of the templatized position type. The previous undo/redo library stored and dereferenced pointers to the element.

You can see the new undo/redo library in use and how it differs from the one I described two months ago by downloading the source code of the Editor project.

The Selection Template Library

Editor uses another template library that I developed some time ago for a musical notation program. Interactive GUI applications usually allow users to mark selections in the document, move and copy those selections to the clipboard or to other places in the document, and delete the data within the selection. GUI applications support these operations with mouse and keyboard actions.

Marking a selection typically involves reversing the foreground and background colors of the video display where the selected data elements are shown. The mechanics of mouse and keyboard operations are usually specific to the application and its operating platform and not necessarily something that you would abstract any higher. But the mechanics of accounting for whether something is selected, where the selection is, and communicating to the application about when and where to display the selection are likely candidates for a template class. When I wrote the musical notation program, I realized that I was about to rewrite code that I had written before in other applications to support marked selection blocks. The earlier code was not reusable because it deals with rows and columns of text, whereas the musical notation program deals with staffs, measures, notes, and rests. I decided then to build the musical selection code as a generic template class in case I ever had to write selection marking code again. Sure enough, when I undertook this programmer's editor, such a need surfaced, and guess what? Whereas the music program's generic solution works with text without modification, the earlier text-marking solution would not have worked for Editor. Just like the old undo library, the old pretemplate selection code assumes a document representation of a contiguous character array rather than a vector of strings. I keep learning the same lesson over and over.

Listing One is selection.h, the header file that defines the Selection template class. You can see it in use by downloading the Editor project. I'll briefly describe how to use it here.

Whereas the undo/redo library is associated with an application's document class, the selection library is associated with the application's view class. The application's view class instantiates an object of type Selection<P,V> where P is a position type that defines where in the document a selection can begin or end, and V is the type of the application's view class. P can be a pointer to an object in the document. Editor has a more complex P because of its text row/column architecture. P must support initialization with a zero constant (or null pointer value), assignment, and a less-than operator. Pointers have these properties.

V, the application's view class, must provide one function:

void V::InvertDisplayItems(P begin, P end);

Selection<P,V> calls V::InvertDisplayItems to tell the view class to mark or unmark a range of data elements in the view of the document. This range is not inclusive; the function should not mark or unmark the data element that the end argument references. By using inverted video, one function serves to mark and unmark data elements. InvertDisplayItems does not have to determine whether an element in a range is marked; it can swap the video display's foreground and background colors for the element. The Selection template keeps track of what is marked and what is not, calling InvertDisplayItems to mark unmarked elements and unmark marked elements.

An application would not have to invert the video in the InvertDisplayItems function. It could ignore the range and simply invalidate the client window so that the draw function displays the marked selection. I chose to do the inverting as it occurs to avoid unnecessary full window refreshes.

Marking a selection works like this: The user clicks an item in the document or moves the caret with the Shift key pressed. The application's view class calls Selection<P,V>::SetMarking(P) passing a P object that specifies where the marked selection begins. When users move the mouse with the button down or presses one of the keyboard's caret movement keys with the Shift key pressed, the application calls Selection<P,V>::ExtendSelection(P) passing a P object that specifies the new caret or mouse position. The ExtendSelection function keeps track of the original position where marking started and the current terminal position, which can be ahead of or following the original position. ExtendSelection calls the view class's InvertDisplayItems function to tell it to modify its view to represent the selection's configuration.

When users release the mouse button, the application calls Selection<P,V>::StopMarking(). Keyboard marking continues until users do something else that would terminate the marking operation. The Selection template supports the application with the functions in Table 1.

The Editor Program

The Editor project demonstrates the power of truly reusable code. In just over a week, I wrote a program that has most of the features of all the programmer's editors I ever used. That achievement, which surprised me, was possible because I already had an application framework, a standard library of containers, and undo/redo and marked selection template classes. The difficult parts were finished before I started.

Editor supports multiple documents, full undo/redo, clipboard operations, find and replace, printing, and two kinds of macros. The only features that I did not add are a word-wrap feature that lets the editor become a miniature word processor, and the ability to launch a compiler from within the editor. I don't need word wrapping. This editor is for writing code; it is not for prose. Compiler launching comes later when Editor becomes an integral part of Quincy 99, which already integrates compiler support.

Macros

Many programmers' editors support macros, a feature that lets the user extend the editor's functionality. The Editor project has two kinds of macro support drawn from my experience with other editors. The first kind of macro is the quick and dirty macro that you program on the fly. You start recording, and all subsequent keystrokes and some menu commands, such as find and replace, are recorded. When the macro is fully programmed, you stop recording. Later you can replay the macro with a menu or keyboard command. Only one such macro is ever programmed at a time. A new macro program replaces the previous one. The macro is not persistent. When you exit the program, the macro is lost.

The other kind of macro that Editor supports resembles the WordBasic macros and the C-like macro script languages that other programmers' editors use. Programmers use these script languages to extend their editors with fancy features like smart indenting, brace insertion, and so on. Editor's script language is not some wimpy C or Basic look alike, however. Editor's script language is full-blown Standard C++. Amazing, you say? How did I do that, you ask? Easy. You have Editor's source code. The CEditorView class has a public member function named bool ExtendEditor(UINT nChar). As distributed, all the function does is return False. The program calls that function for every keystroke. To extend Editor, replace that inline function with one that does something. Return True if you don't want Editor to process the keystroke. Look at CEditorView and CEditorDoc to see how their internal APIs work with one another to change the document's content and move the caret. Use those APIs to extend Editor. Someday I might add a script language, particularly since I have a C subset script interpreter named "S" in the can from a C column of many years ago. Until then, I guess C++ will have to suffice.

Printing

Printing is a daunting task in any Windows application. MFC makes it a little easier, but there is a lot about Windows printing that few books fully cover. I was able to get all of what I needed from Programming Windows 95 with MFC, by Jeff Prosise (Microsoft Press, 1996, ISBN 1-55615-902-1). The MFC application framework automatically adds a print preview feature to applications. Given that you have properly written code that displays a document in a view window, the code needed to print and print preview the document is minimal, particularly when you don't worry about things like user-adjustable margins and other layout considerations. The complexity that lurks under the surface, however, is something else. Take a look at the CEditorView class's functions related to printing to get an idea of what the code is. Then read Chapter 10 in Prosise's book to find out what the code does.

Windows in a Minute

You've seen books with silly titles like, Learn C++ in 24 Hours, which lure buyers with the promise of a quick and easy path to mastering a complex subject such as a programming language. Now there's a book called Windows In A Minute. Don't rush out to get it for your technically challenged friends, however. It turns out to be about how to do window treatments -- draperies, curtains, and such -- for your house. I saw it on one of Judy's favorite daytime arts and crafts TV shows. Despite the book's title, it took the author about 10 minutes to make one of her simpler one-minute window treatments and a lot of the preparation had been finished offline. I guess the sleaze element in book titles is not restricted to the computer world.

DDJ

Listing One

// ---- Selection.h#ifndef SELECTION_H
#define SELECTION_H
namespace DDJCProgrammingColumnSelection    {
//////////////////////////////////////////////////////////////////////
// P is a user-defined iterator type that indexes the item 
//     (char, e.g.) in the view
// P must support assignment and operator<
// P must support p(0) to initialize a P object to a null value 
//   which is the lowest relational value that a P object can have
// V is the view class, which must provide this function:
//   void InvertDisplayItems(P begin, P end);
//
template <class P, class V>
class Selection {
    V& view;        // reference to the document's view
    P anchor;       // the first event of a marked selection
    P terminal;     // the last event of a marked selection
    P saveanchor;   // for saving first event of marked selection
    P saveterminal; // for saving last event of marked selection
    bool marking;   // true when marking with mouse or keyboard
public:
    Selection(V& rV);
    bool IsSelectionMarked() const


</p>
       { return !(anchor == terminal); }
    bool IsMarking() const
        { return marking; }
    bool IsItemInSelection(P pos);
    void RestoreSelection();
    void SaveSelection();
    void GetSelectionMarkers(P& begin, P& end) const;
    void ExtendSelection(P pos);
    void UnmarkSelection();
    void SetMarking(P pos);
    void StopMarking();
};
template <class P, class V>
Selection<P, V>::Selection(V& rV) : 
    view(rV), anchor(0), terminal(0), marking(false)
{
}
template <class P, class V>
bool Selection<P, V>::IsItemInSelection(P pos)
{
    P begin, end;
    GetSelectionMarkers(begin, end);
    return !(pos < begin) && (pos < end);
}
template <class P, class V>
void Selection<P,V>::SaveSelection()
{
    saveanchor = anchor;
    saveterminal = terminal;
}
template <class P, class V>
void Selection<P,V>::RestoreSelection()
{
    anchor = saveanchor;
    terminal = saveterminal;
    P begin, end;
    GetSelectionMarkers(begin, end);
    view.InvertDisplayItems(begin, end);
}
template <class P, class V>
void Selection<P,V>::GetSelectionMarkers(P& begin, P& end) const
{
    begin = anchor;
    end = terminal;
    if (end < begin)    {
        P temp = begin;
        begin = end;
        end = temp;
    }
}
template <class P, class V>
void Selection<P,V>::ExtendSelection(P pos)
{
    if (pos < terminal) {
        view.InvertDisplayItems(pos, terminal);
    }
    else
        view.InvertDisplayItems(terminal, pos);
    terminal = pos;
}
template <class P, class V>
void Selection<P,V>::UnmarkSelection()
{
    if (IsSelectionMarked())    {
        P begin, end;
        GetSelectionMarkers(begin, end);
        view.InvertDisplayItems(begin, end);
        marking = false;
        anchor = terminal = 0;
    }
}
template <class P, class V>
void Selection<P,V>::SetMarking(P pos)
{
    if (!marking)   {
        UnmarkSelection();
        marking = true;
        terminal = anchor = pos;
    }
}
template <class P, class V>
void Selection<P,V>::StopMarking()
{
    marking = false;
}
}       // namespace DDJCProgrammingColumnSelection
#endif  // SELECTION_H

Back to Article


Copyright © 1999, Dr. Dobb's Journal

Related Reading


More Insights






Currently we allow the following HTML tags in comments:

Single tags

These tags can be used alone and don't need an ending tag.

<br> Defines a single line break

<hr> Defines a horizontal line

Matching tags

These require an ending tag - e.g. <i>italic text</i>

<a> Defines an anchor

<b> Defines bold text

<big> Defines big text

<blockquote> Defines a long quotation

<caption> Defines a table caption

<cite> Defines a citation

<code> Defines computer code text

<em> Defines emphasized text

<fieldset> Defines a border around elements in a form

<h1> This is heading 1

<h2> This is heading 2

<h3> This is heading 3

<h4> This is heading 4

<h5> This is heading 5

<h6> This is heading 6

<i> Defines italic text

<p> Defines a paragraph

<pre> Defines preformatted text

<q> Defines a short quotation

<samp> Defines sample computer code text

<small> Defines small text

<span> Defines a section in a document

<s> Defines strikethrough text

<strike> Defines strikethrough text

<strong> Defines strong text

<sub> Defines subscripted text

<sup> Defines superscripted text

<u> Defines underlined text

Dr. Dobb's encourages readers to engage in spirited, healthy debate, including taking us to task. However, Dr. Dobb's moderates all comments posted to our site, and reserves the right to modify or remove any content that it determines to be derogatory, offensive, inflammatory, vulgar, irrelevant/off-topic, racist or obvious marketing or spam. Dr. Dobb's further reserves the right to disable the profile of any commenter participating in said activities.

 
Disqus Tips To upload an avatar photo, first complete your Disqus profile. | View the list of supported HTML tags you can use to style comments. | Please read our commenting policy.