_C PROGRAMMING COLUMN_ by Al Stevens [Listing One] // -------- editor.h #ifndef EDITOR_H #define EDITOR_H #include "editbox.h" const unsigned char Ptab = '\t'+0x80; // pseudo tab expansion #define MaxTab 12 // maximum tab width class Editor : public EditBox { int tabs; // tab expansion value Bool wordwrapmode; // True = wrap words int row; // Current row void OpenWindow(); void ScrollCursor(); void AdjustCursorTabs(int key = 0); void ExtendBlock(int x, int y); void InsertTab(); void AdjustTabInsert(); void AdjustTabDelete(); void WordWrap(); Bool AtBufferStart() { return (Bool) (column == 0 && row == 0); } protected: virtual void Upward(); virtual void Downward(); virtual void Forward(); virtual void Backward(); virtual void DeleteCharacter(); virtual void BeginDocument(); virtual void EndDocument(); virtual Bool PageUp(); virtual Bool PageDown(); virtual void InsertCharacter(int key); virtual void PaintCurrentLine() { WriteTextLine(row); } virtual Bool ResetCursor(); virtual void WriteString(String &ln, int x, int y, int fg, int bg); virtual void LeftButton(int mx, int my); public: Editor(const char *ttl, int lf, int tp, int ht, int wd, DFWindow *par=0) : EditBox(ttl, lf, tp, ht, wd, par) { OpenWindow(); } Editor(const char *ttl, int ht, int wd, DFWindow *par=0) : EditBox(ttl, ht, wd, par) { OpenWindow(); } Editor(int lf, int tp, int ht, int wd, DFWindow *par=0) : EditBox(lf, tp, ht, wd, par) { OpenWindow(); } Editor(int ht, int wd, DFWindow *par=0) : EditBox(ht, wd, par) { OpenWindow(); } Editor(const char *ttl) : EditBox(ttl) { OpenWindow(); } virtual void Keyboard(int key); virtual unsigned char CurrentChar() { return *(TextLine(row) + column); } virtual unsigned CurrentCharPosition() { return (unsigned) ((const char *) (TextLine(row)+column) - (const char *) *text); } virtual void FormParagraph(); virtual void AddText(const String& txt); virtual const String GetText(); virtual void ClearText(); virtual int GetRow() const { return row; } int Tabs() { return tabs; } void SetTabs(int t); Bool WordWrapMode() { return wordwrapmode; } void SetWordWrapMode(Bool wmode) { wordwrapmode = wmode; } virtual void DeleteSelectedText(); virtual void InsertText(const String& txt); }; #endif [Listing Two] // ----- editor.cpp #include "editor.h" #include "desktop.h" // ----------- common constructor code void Editor::OpenWindow() { windowtype = EditorWindow; row = 0; tabs = 4; insertmode = desktop.keyboard().InsertMode(); wordwrapmode = True; DblBorder = False; } // ---- keep the cursor out of tabbed space void Editor::AdjustCursorTabs(int key) { while (CurrentChar() == Ptab) key == FWD ? column++ : --column; ResetCursor(); } // -------- process keystrokes void Editor::Keyboard(int key) { int svwtop = wtop; int svwleft = wleft; switch (key) { case '\t': InsertTab(); BuildTextPointers(); PaintCurrentLine(); ResetCursor(); break; case ALT_P: FormParagraph(); break; case UP: Upward(); TestMarking(); break; case DN: Downward(); TestMarking(); break; case CTRL_HOME: BeginDocument(); TestMarking(); break; case CTRL_END: EndDocument(); TestMarking(); break; case '\r': InsertCharacter('\n'); BuildTextPointers(); ResetCursor(); Paint(); break; case DEL: case RUBOUT: visible = False; EditBox::Keyboard(key); visible = True; BuildTextPointers(); PaintCurrentLine(); ResetCursor(); break; default: EditBox::Keyboard(key); break; } if (svwtop != wtop || svwleft != wleft) Paint(); } // --- move the cursor forward one character void Editor::Forward() { if (CurrentChar()) { if (CurrentChar() == '\n') { Home(); Downward(); } else EditBox::Forward(); AdjustCursorTabs(FWD); } } // --- move the cursor back one character void Editor::Backward() { if (column) EditBox::Backward(); else if (row) { Upward(); End(); } AdjustCursorTabs(); } // ---- if cursor moves out of the window, scroll void Editor::ScrollCursor() { if (column < wleft || column >= wleft + ClientWidth()) { wleft = column; Paint(); } } // --- move the cursor up one line void Editor::Upward() { if (row) { if (row == wtop) ScrollDown(); --row; AdjustCursorTabs(); ScrollCursor(); } } // --- move the cursor down one line void Editor::Downward() { if (row < wlines) { if (row == wtop + ClientHeight() - 1) ScrollUp(); row++; AdjustCursorTabs(); ScrollCursor(); } } // --- move the cursor to the beginning of the document void Editor::BeginDocument() { row = 0; wtop = 0; EditBox::Home(); AdjustCursorTabs(); } // --- move the cursor to the end of the document void Editor::EndDocument() { TextBox::End(); row = wlines-1; End(); AdjustCursorTabs(); } // --- keep cursor in the window, in text and out of empty space Bool Editor::ResetCursor() { KeepInText(column, row); if (EditBox::ResetCursor()) { if (!(row >= wtop && row < wtop+ClientHeight())) { desktop.cursor().Hide(); return False; } } return True; } // ------- page up one screenfull Bool Editor::PageUp() { if (wlines) { row -= ClientHeight(); if (row < 0) row = 0; EditBox::PageUp(); AdjustCursorTabs(); return True; } return False; } // ------- page down one screenfull Bool Editor::PageDown() { if (wlines) { row += ClientHeight(); if (row >= wlines) row = wlines-1; EditBox::PageDown(); AdjustCursorTabs(); return True; } return False; } // --- insert a tab into the edit buffer void Editor::InsertTab() { visible = False; if (insertmode) { EditBox::InsertCharacter('\t'); while ((column % tabs) != 0) EditBox::InsertCharacter(Ptab); } else do Forward(); while ((column % tabs) != 0); visible = True; } // --- When inserting char, adjust next following tab, same line void Editor::AdjustTabInsert() { visible = False; // ---- test if there is a tab beyond this character int savecol = column; while (CurrentChar() && CurrentChar() != '\n') { if (CurrentChar() == '\t') { column++; if (CurrentChar() == Ptab) EditBox::DeleteCharacter(); else for (int i = 0; i < tabs-1; i++) EditBox::InsertCharacter(Ptab); break; } column++; } column = savecol; visible = True; } // --- test for wrappable word and wrap it void Editor::WordWrap() { // --- test for word wrap int len = LineLength(row); int wd = ClientWidth()-1; if (len >= wd) { const char *cp = TextLine(row); char ch = *(cp + wd); // --- test words beyond right margin if (len > wd || (ch && ch != ' ' && ch != '\n')) { // --- test typing in last word in window's line const char *cw = cp + wd; cp += column; while (cw > cp) { if (*cw == ' ') break; --cw; } int newcol = 0; if (cw <= cp) { // --- user was typing last word on line // --- find beginning of the word const char *cp1 = TextLine(row); const char *cw1 = cw; while (*cw1 != ' ' && cw1 > cp1) --cw1, newcol++; wleft = 0; } FormParagraph(); if (cw <= cp) { // --- user was typing last word on line column = newcol; if (cw == cp) --column; row++; if (row - wtop >= ClientHeight()) ScrollUp(); ResetCursor(); } } } } // --- insert a character at the current cursor position void Editor::InsertCharacter(int key) { if (insertmode) { if (key != '\n') AdjustTabInsert(); } else if (CurrentChar() == '\t') { // --- overtyping a tab visible = False; column++; while (CurrentChar() == Ptab) EditBox::DeleteCharacter(); --column; } visible = False; EditBox::InsertCharacter(key); visible = True; ResetCursor(); if (wordwrapmode) WordWrap(); } // --- When deleting char, adjust next following tab, same line void Editor::AdjustTabDelete() { visible = False; // ---- test if there is a tab beyond this character int savecol = column; while (CurrentChar() && CurrentChar() != '\n') { if (CurrentChar() == '\t') { column++; // --- count pseudo tabs int pct = 0; while (CurrentChar() == Ptab) pct++, column++; if (pct == tabs-1) { column -= tabs-1; for (int i = 0; i < tabs-1; i++) EditBox::DeleteCharacter(); } else EditBox::InsertCharacter(Ptab); break; } column++; } column = savecol; visible = True; } // --- delete the character at the current cursor position void Editor::DeleteCharacter() { if (CurrentChar() == '\0') return; if (insertmode) AdjustTabDelete(); if (CurrentChar() == '\t') { // --- deleting a tab EditBox::DeleteCharacter(); while (CurrentChar() == Ptab) EditBox::DeleteCharacter(); return; } const char *cp = TextLine(row); const char *cw = cp + column; const Bool delnewline = (Bool) (*cw == '\n'); const Bool reform = (Bool) (delnewline && *(cw+1) != '\n'); const Bool lastnewline = (Bool) (delnewline && *(cw+1) == '\0'); int newcol = 0; if (reform && !lastnewline) { // --- user is deleting /n, find beginning of last word while (*--cw != ' ' && cw > cp) newcol++; } EditBox::DeleteCharacter(); if (lastnewline) return; if (delnewline && !reform) { // --- user deleted a blank line visible = True; BuildTextPointers(); Paint(); return; } if (wordwrapmode && reform) { // --- user deleted /n wleft = 0; FormParagraph(); if (CurrentChar() == '\n') { // ---- joined the last word with next line's // first word and then wrapped the result column = newcol; row++; } } } // --- form a paragraph from the current cursor position // through one line before the next blank line or end of text void Editor::FormParagraph() { int BegCol, FirstLine; const char *blkBegLine, *blkEndLine, *blkBeg; // ---- forming paragraph from cursor position FirstLine = wtop + row; blkBegLine = blkEndLine = TextLine(row); if ((BegCol = column) >= ClientWidth()) BegCol = 0; // ---- locate the end of the paragraph while (*blkEndLine) { Bool blank = True; const char *BlankLine = blkEndLine; // --- blank line marks end of paragraph while (*blkEndLine && *blkEndLine != '\n') { if (*blkEndLine != ' ') blank = False; blkEndLine++; } if (blank) { blkEndLine = BlankLine; break; } if (*blkEndLine) blkEndLine++; } if (blkEndLine == blkBegLine) { visible = True; Downward(); return; } if (*blkEndLine == '\0') --blkEndLine; if (*blkEndLine == '\n') --blkEndLine; // --- change newlines, tabs, and tab expansions to spaces blkBeg = blkBegLine; while (blkBeg < blkEndLine) { if (*blkBeg == '\n' || ((*blkBeg) & 0x7f) == '\t') { int off = blkBeg - (const char *)*text; (*text)[off] = ' '; } blkBeg++; } // ---- insert newlines at new margin boundaries blkBeg = blkBegLine; while (blkBegLine < blkEndLine) { blkBegLine++; if ((int)(blkBegLine - blkBeg) == ClientWidth()-1) { while (*blkBegLine != ' ' && blkBegLine > blkBeg) --blkBegLine; if (*blkBegLine != ' ') { blkBegLine = strchr(blkBegLine, ' '); if (blkBegLine == NULL || blkBegLine >= blkEndLine) break; } int off = blkBegLine - (const char *)*text; (*text)[off] = '\n'; blkBeg = blkBegLine+1; } } BuildTextPointers(); changed = True; // --- put cursor back at beginning column = BegCol; if (FirstLine < wtop) wtop = FirstLine; row = FirstLine - wtop; visible = True; Paint(); ResetCursor(); } // --------- add a line of text to the editor textbox void Editor::AddText(const String& txt) { // --- compute the buffer size based on tabs in the text const char *tp = txt; int x = 0; int sz = 0; while (*tp) { if (*tp == '\t') { // --- tab, adjust the buffer length int sps = Tabs() - (x % Tabs()); sz += sps; x += sps; } else { // --- not a tab, count the character sz++; x++; } if (*tp == '\n') x = 0; // newline, reset x tp++; } // --- allocate a buffer char *ep = new char[sz]; // --- detab the input file tp = txt; char *ttp = ep; x = 0; while (*tp) { // --- put the character (\t, too) into the buffer *ttp++ = *tp; x++; // --- expand tab into \t and expansions (\t + 0x80) if (*tp == '\t') while ((x % Tabs()) != 0) *ttp++ = Ptab, x++; else if (*tp == '\n') x = 0; tp++; } *ttp = '\0'; // ---- add the text to the editor window EditBox::AddText(String(ep)); } // ------- retrieve editor text collapsing tabs const String Editor::GetText() { char *tx = new char[text->Strlen()+1]; const char *tp = (const char *) *text; char *nt = tx; while (*tp) { if (*(const unsigned char *)tp != Ptab) *tx++ = *tp; tp++; } *tx = '\0'; String temp(nt); return nt; } // --- write a string to the editor window void Editor::WriteString(String &ln,int x,int y,int fg,int bg) { String nln(ln.Strlen()); int ch; for (int i = 0; i < ln.Strlen(); i++) { ch = ln[i]; nln[i] = (ch & 0x7f) == '\t' ? ' ' : ch; } EditBox::WriteString(nln, x, y, fg, bg); } // ---- left mouse button pressed void Editor::LeftButton(int mx, int my) { if (ClientRect().Inside(mx, my)) { column = mx-ClientLeft()+wleft; row = my-ClientTop()+wtop; ResetCursor(); } TextBox::LeftButton(mx, my); } // --------- clear the text from the editor window void Editor::ClearText() { row = 0; EditBox::ClearText(); } // ------ extend the marked block void Editor::ExtendBlock(int x, int y) { row = y; EditBox::ExtendBlock(x, y); } // ---- delete a marked block void Editor::DeleteSelectedText() { if (TextBlockMarked()) { row = BlkBegLine; column = BlkBegCol; if (row < wtop || row >= wtop + ClientHeight()) wtop = row; if (column < wleft || column >= wleft + ClientWidth()) wleft = column; EditBox::DeleteSelectedText(); FormParagraph(); } } // ---- insert a string into the text void Editor::InsertText(const String& txt) { EditBox::InsertText(txt); FormParagraph(); } // ---- set tab width void Editor::SetTabs(int t) { if (t && tabs != t && t <= MaxTab) { tabs = t; if (text != 0) { String ln; // ------- retab the text for (int lno = 0; lno < wlines; lno++) { // --- retrieve a line at a time ln = ExtractTextLine(lno); int len = ln.Strlen(); String newln(len * t); // --- copy string, collapsing old tabs // and expanding new tabs unsigned int ch; for (int x2 = 0, x1 = 0; x2 < len; x2++) { ch = ln[x2] & 0xff; if (ch == Ptab) // --- collapse old tab expansion continue; // --- copy text newln[x1++] = ch; if (ch == '\t') // --- expand new tabs while ((x1 % t) != 0) newln[x1++] = Ptab; } newln[x1] = '\0'; newln += "\n"; // --- compute left segment length unsigned seg1 = (unsigned) (TextLine(lno) - (const char *) *text); // --- compute right segment length unsigned seg2 = 0; if (lno < wlines+1) seg2 = (unsigned) text->Strlen() - (TextLine(lno+1) - (const char *) *text); // --- rebuild the text from the three parts String lft = text->left(seg1); String rht = text->right(seg2); *text = lft+newln+rht; BuildTextPointers(); } Paint(); } } }