Download full source and demo (part 7, 104Kb)
This will probably be quite a short tutorial as the subject of margins is really quite simple to implement. This time around we will look at implementing a selection margin (complete with full-line selection), line numbers, line-indicator icons (e.g. like the breakpoint bitmaps in Visual Studio), and lastly the problem of long-line display.

The main screenshot shows Neatpad with a selection-margin, line-numbers, bitmapped bookmarks and long-line highlights. Although it looks quite complicated drawing a margin is really easy, as long as you understand that it is just a rectangular area to the left of the display that is drawn and scrolled differently.

I've highlighted the picture above to illustrate that even though we've added
a vertical margin, lines are still drawn horizontally one-by-one. However
the line-drawing process (the TextView::PaintLine function) has
changed to allow for the margins.
First of all the margin is drawn using the new function TextView::PaintMargin
int TextView::PaintMargin(HDC hdc, ULONG nLineNo, RECT *margin)
The area taken by the margin is then clipped to prevent anything drawing over it. Next, the text is drawn as normal - but offset to the right of the margin. The clipping is important because it ensures that when we scroll horizontally the lines of text don't overdraw the margin and instead "disappear" behind the margin. Basically the process looks like this:
void TextView::PaintLine(HDC hdc, ULONG nLineNo)
{
RECT rect;
// work out where to draw the line
// handle the margins
if(LeftMarginWidth() > 0)
{
RECT margin;
// work out the margin coordinates
// paint the margin
PaintMargin(hdc, nLineNo, &margin);
// clip the margin so the text doesn't draw over it
ExcludeClipRect(hdc, margin.left, margin.top, margin.right, margin.bottom);
// offset the text placement
rect.left += LeftMarginWidth();
}
// paint the text as normal
PaintText(hdc, nLineNo, &rect);
}
The key point to understand is that the normal line-text must be offset to the right to take into account the margin. It's really very simple so just take a look at the sourcecode download to see it in action.
Two more important things to mention: I have also had to modify the scrolling
routine so that the margin is not scrolled when the main
text is scrolled. This was very simple as the clipping rectangle we specify
in ScrollWindowEx is simply adjusted so that the margin does
not get included.
HRGN TextView::ScrollRgn(int dx, int dy, bool fReturnUpdateRgn)
{
...
// take margin into account
clip.left += LeftMarginWidth();
...
}
The other modification was the mouse-input (selection) code. The cursor-position must be adjusted so that the margin is again taken into account. I really don't want to include any code for this as you should be getting the idea by now. Basically, any x-coordinate is shifted to the right by the size of the margin before any input or drawing takes place.
Just a quick word about the line-indicator bitmaps you can see. I've added a new ImageList member variable to the TextView class which can hold a user-specified collection of bitmaps:
HIMAGELIST m_hImageList;
The image-list can be set using the new TextView TXM_SETIMAGELIST
message (or the TextView_SetImageList macro).
HIMAGELIST hImageList = ImageList_LoadImage(...); TextView_SetImageList(hwndTextView, hImageList);
Using an image-list is the simplest way to work with images as it manages the bitmap memory, and also provides drawing support as well - so storing/drawing bitmaps is really easy.
The difficult part comes when we draw each line. How do we know what image to place in the margin-area? For a text-editors which use an array or linked-list of lines this is really easy - because additional information can be easily stored for each line entry in the editor. However for our Neatpad design this is not possible because we have to support large-file editing at some point - and having a line-buffer for a 4gb file would not be a good idea.
For this reason I have used a separate array which holds only those lines that have bitmaps associated with them. The array is always sorted so that we can use a binary-search to quickly determine if a specific line has any bitmap assocated with it.
typedef struct
{
ULONG nLineNo;
ULONG nImageIdx;
} LINEINFO;
LINEINFO m_LineInfo[MAX_LINE_INFO];
Each time a line is drawn the LINEINFO array is searched using the TextView::GetLineInfo
function:
LINEINFO* TextView::GetLineInfo(ULONG nLineNo)
{
LINEINFO key = { nLineNo, 0 };
// perform the binary search
return (LINEINFO *) bsearch(
&key,
m_LineInfo,
m_nLineInfoCount,
sizeof(LINEINFO),
(COMPAREPROC)CompareLineInfo
);
}
This function returns a pointer to the appropriate LINEINFO
structure if successful, or NULL if there is no stored information for the
specified line.
I anticipate that more information could be stored about lines such as a whole-line highlight colour, bookmarks, annotations etc. However for now I have just included support for an image-index. The images for each line can be set using another new TextView message, TXM_SETLINEIMAGE:
TextView_SetLineImage(hwndTextView, nLineNo, nImageIdx);
Now that we have a margin to the left of the main text display we can use this area to initiate line-based selections.

To allow for this line-based selection method, we need to keep track of more
than just the fact that we are making a selection. The previous boolean m_fSelection
(which was just used to indicate if a selection was in progress or not) has
been replaced by a new variable:
SELMODE m_nSelectionMode;
m_nSelectionMode is a SELMODE enumeration with the following
values:
enum SELMODE
{
SELMODE_NONE,
SELMODE_NORMAL,
SELMODE_MARGIN
};
So at the moment we support two types of selection - "normal" and "margin". At some point in the future this could be extended to support other types of selection such as column and block selections. Of course the mouse-routines have been modified to understand the new types of selection.
One feature which I find particularly useful is the highlighting of long lines. I can still remember the Borland C++ 4.0 IDE for Windows 3.1 which featured a single vertical-line margin to indicate where column-80 was. I actually found this a little tacky but I wanted to create a similar effect.
The reason for highlighting longs lines is provide a way to indicate to the user when a line of text becomes too long. This is most useful for programmers who don't want their text to "wrap" when it is printed out - so the aim is to keep all lines of text under a certain limit.
There are basically two ways of implementing long-line highlights:
In order to support this new long-line display I've had to extend the TextView::ApplyTextAttributes
member-function with a new parameter - &nColumn :
int TextView::ApplyTextAttributes(ULONG nLineNo, ULONG nOffset, ULONG &nColumn, TCHAR *szText, int nTextLen, ATTR *attr)
Notice that nColumn is a C++ reference. It is continually
updated by ApplyTextAttributes to keep track of the current column-position
(we need a C++ reference so that the value is preserved through successive
calls). Once nColumn reaches a certain value, ApplyTextAttributes
will use a different default background colour for the text.
A new message (TXM_SETLONGLINE) has been added to the TextView
to allow the long-line limit to be programmatically altered. The TextView_SetLongLine
can be used to set this value:
TextView_SetLongLine(hwndTextView, 80);
This probably a little late in the day (it should have happened from day#1) but I've started to make the Neatpad and TextView projects 64bit compatible. You will therefore need to use a recent Platform SDK when compiling Neatpad in order to get the new mixed 32bit/64bit definitions. This has also resulted so far in the following change:
To test that these changes actually worked I compiled the project using the Microsoft 64bit compiler, targetted for the IA64 architecture. Follow the steps below to duplicate this:
Project -> Export MakefileMicrosoft Platform SDK -> Windows XP 64bit Build Environmentnmake Neatpad.makThere are still alot of changes to make (ULONG vs ULONG64 issues) and as I don't have access to a 64bit machine currently I won't be able to make any more modifications until I can test properly. However I hope to have Neatpad fully 64bit compatible before the end of the series!
This tutorial was a little off-track as margins and long-line highlights are not really that important for a text-editor's design. Anyhow I wanted to get it out of the way so I could concentrate on the core functionality.
Coming up in Part 8 will be support for UTF-8 and Unicode!
Please send any comments or suggestions to:
Last modified: 01 August 2008 12:48:45