Artifact 8ae004cfe503adfe333660b14dbfbed845b2ca64f27a4cd96c10c70e336b8e12:
- Executable file
r36/cslbase/c_text.cpp
— part of check-in
[f2fda60abd]
at
2011-09-02 18:13:33
on branch master
— Some historical releases purely for archival purposes
git-svn-id: https://svn.code.sf.net/p/reduce-algebra/code/trunk/historical@1375 2bfe0521-f11c-4a00-b80e-6202646ff360 (user: arthurcnorman@users.sourceforge.net, size: 150120) [annotate] [blame] [check-ins using] [more...]
// c_text.cpp // Support for a window that can support regular, bold, italic and symbol // fonts each in large & small, plus the ability to select the size of // characters to be used. // // // See accompanying file "cwin.tex" for an explanation of the policy // for scrolling, text selection and treatment of buffer over-full // conditions. // // Copyright (C)/©/¸ Codemist Ltd, 1995-99 /* Signature: 7f4001bb 07-Mar-2000 */ #include "cwin.hpp" // There are times when this code wants to indicate to the user's // application that it should terminate. If such a request has been // raised the macro exception_pending() given here will evaluate to true, // but the way that this happens is specific the the CSL/CCL Lisp system // and thus is not exactly generic. extern "C" { extern int C_nil; extern FILE *spool_file; extern void inject_randomness(int); } #define exception_pending() ((C_nil&1) != 0) static BOOL hasFinished = FALSE; CMainWindow::CMainWindow() { hasFinished = FALSE; complete = FALSE; char *menuName = "MainMenu"; HINSTANCE hInst = AfxFindResourceHandle(menuName, RT_MENU); HMENU hMenu = ::LoadMenu(hInst, menuName); this->CreateEx(WS_EX_ACCEPTFILES, mainWindowClass, "", WS_OVERLAPPEDWINDOW | WS_HSCROLL | WS_VSCROLL, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, NULL, hMenu); // Now I will add the dynamic menu items that I need... // I demand that the "help" be the rightmost one on the menu bar HMENU helpMenu = ::GetSubMenu(hMenu, ::GetMenuItemCount(hMenu)-1); int i; theApp.dynamicCount = 0; // Insert extra help menu items here. I expect that the registry will // contain a subkey called HelpItems which is an integer, and then // a bunch of strings takked as T000/P000, T001/P001 etc where the Tiii are // strings to place on the menu and the Piii are paths to relevant help // files that can be opened. int helpCount = theApp.GetProfileInt("HelpItems", "HowMany", 0); #ifdef MAYBE int helpCount = theApp.GetProfileInt("HelpItems", "HowMany", -1); if (helpCount < 0) { theApp.dynamic[0] = "&Contents" theApp.dynamic_files[0] = "???"; theApp.dynamic[1] = "&Search for Help" theApp.dynamic_files[1] = "???"; helpCount = 2; WriteProfileInt("HelpItems", "HowMany", 2); WriteProfileString("HelpItems", "T000", theApp.dynamic[0]); WriteProfileString("HelpItems", "P000", theApp.dynamic_files[0]); WriteProfileString("HelpItems", "T001", theApp.dynamic[1]); WriteProfileString("HelpItems", "P001", theApp.dynamic_files[1]); } else #endif { if (helpCount >= IDM_LAST_DYNAMIC-IDM_DYNAMIC_ITEMS) helpCount = IDM_LAST_DYNAMIC-IDM_DYNAMIC_ITEMS-1; for (i=0; i<helpCount; i++) { char tag[8]; sprintf(tag, "T%.3d", i); CString key(theApp.GetProfileString("HelpItems", tag, "")); sprintf(tag, "P%.3d", i); CString path(theApp.GetProfileString("HelpItems", tag, "")); if (key.GetLength() != 0 && path.GetLength() != 0) { char *m; m = (char *)malloc(1+key.GetLength()); strcpy(m, LPCTSTR(key)); theApp.dynamic[theApp.dynamicCount] = m; m = (char *)malloc(1+path.GetLength()); strcpy(m, LPCTSTR(path)); theApp.dynamic_files[theApp.dynamicCount++] = m; } } } // Now place them on the menu for (i=0; i<theApp.dynamicCount; i++) ::InsertMenu(helpMenu, IDM_HELP_ON_HELP, MF_BYCOMMAND, IDM_DYNAMIC_ITEMS+i, theApp.dynamic[i]); // typeAheadBuffer is a smallish buffer and when the user presses a key the // resulting character is put there. No echoing is performed. Use of DEL // can discard a previously-typed character and an attempt to over-fill the // buffer before other parts of the code have emptied it will cause a BEEP. // typeAheadP1 = typeAheadP2 = 0; // pointers into typeAheadBuffer // inputBuffer holds stuff that the user has accepted (by typing a newline) // but that the program has not yet read. inputP shows where the next // character should be read from it. The end of the buffer is marked with // a newline. If inputP<0 the buffer is empty. inputP = -1; // textBuffer is a large buffer just containing characters. textFirst // points to the first active character and textLast to one after the last // active character. Both pointers are kept masked with TEXT_MASK which // wraps them around to give a circular buffer. textFirst = textLast = 0; // main text buffer, now empty caretChar = 0; // char before which caret is shown caretLine = 0; caretX = 0; icaretChar = icaretLine = icaretX = 0; caretVisible = TRUE; caretFontWidths = windowFonts.HCourier.across; icaretFontWidths = windowFonts.HCourier.across; endFontWidths = windowFonts.HCourier.across; endX = 0; xOffset = 0; // Not horizontally scrolled. inputLineStart = -1; caretWidth = 2*GetSystemMetrics(SM_CXBORDER); clipboardInput = NULL; insertMode = TRUE; savedP1 = savedP2 = savedFirst = savedLast = 0; currentInputLine = -1; pageLine = 0; pageMode = pagePaused = FALSE; selFirstChar = selStartChar = selEndChar = 0; selFirstX = selStartX = selEndX = 0; selFirstLine = selStartLine = selEndLine = 0; trackingSelection = FALSE; selRootValid = FALSE; hasFocus = FALSE; // The characters in textBuffer are organised into lines. The start of each // line is recorded in lineBuffer, which is also a circular buffer so the // pointers into it are kept reduced using LINE_MASK. lineFirst identified // the first line stored. lineLast identifies the last line. Note that // the data making up a single line runs from where lineBuffer points either // to where a termination character is found in the text (this will be // a newline character) or until textLast. The latter case arises only for // a final non-terminated line of text. In general there will not be room // on the screen to display all the lines of text that are present. // lineVisible identifies the first line that can be displayed. // lineFirst = lineVisible = 0; // line buffer, contains one (empty) lineLast = 0; // line lineBuffer[0].position = 0; lineBuffer[0].up = 000; lineBuffer[0].height = 000; // get filled in when fonts are created lineBuffer[0].width = 000; // gets filled in when line painted. lineBuffer[0].address = textFirst; cLeft[0] = cRight[0] = 0; strncpy(cMid, programName, 31); cMid[31] = 0; GetSystemTime(&titleUpdateTime); inject_randomness((int)titleUpdateTime.wMilliseconds); cwin_display_date(); strcpy(cwin_prompt_string, "> "); // A default prompt string. windowColour = GetSysColor(COLOR_WINDOW); textColour = GetSysColor(COLOR_WINDOWTEXT); highlightColour = GetSysColor(COLOR_HIGHLIGHT); highlightTextColour = GetSysColor(COLOR_HIGHLIGHTTEXT); #ifdef GRAPHICS_WINDOW graphicsShown = FALSE; graphicsWindow = new CGraphicsWindow; graphicsWindow->ShowWindow(SW_HIDE); #endif helpShown = FALSE; helpWindow = new CHelpWindow; CClientDC dc(helpWindow); dc.SelectObject(&helpWindow->helpFont); TEXTMETRIC mm; dc.GetTextMetrics(&mm); helpWindow->height = mm.tmExternalLeading + mm.tmAscent + mm.tmDescent; int widths[4]; dc.GetCharWidth('X', 'X', widths); helpWindow->width = widths[0]; WINDOWPLACEMENT wp; helpWindow->GetWindowPlacement(&wp); int screenWidth = GetSystemMetrics(SM_CXSCREEN); RECT *wr = &wp.rcNormalPosition; int left = wr->left; int cwidth = 80*helpWindow->width + 3*GetSystemMetrics(SM_CXBORDER) + 2*GetSystemMetrics(SM_CXFRAME) + 5; // Try to get the whole window onto the screen. if (left + cwidth > screenWidth) { left = screenWidth - cwidth; if (left < 0) left = 0; } int screenHeight = GetSystemMetrics(SM_CYSCREEN); int top = wr->top; int cheight = 25*helpWindow->height + GetSystemMetrics(SM_CYCAPTION) + GetSystemMetrics(SM_CYMENU) + 3*GetSystemMetrics(SM_CYBORDER) + 2*GetSystemMetrics(SM_CYFRAME) + 5; if (top + cheight > screenHeight) { top = screenHeight - cheight; if (top < 0) top = 0; } helpWindow->SetWindowPos(NULL, top, left, cwidth, cheight, SWP_NOACTIVATE | SWP_NOREDRAW | SWP_NOZORDER); helpWindow->ShowWindow(SW_HIDE); myTimer = SetTimer(1, 50, NULL); // 20 ticks per second -> message queue cwin_interrupt_pending = 0; complete = TRUE; } CMainWindow::~CMainWindow() { } void CMainWindow::OnDestroy() { #ifdef GRAPHICS_WINDOW graphicsWindow->viewpointWindow.DestroyWindow(); graphicsWindow->DestroyWindow(); // delete graphicsWindow; #endif if (myTimer!=0) KillTimer(myTimer); windowFonts.DeleteFonts(); hasFinished = TRUE; } BEGIN_MESSAGE_MAP(CMainWindow, CFrameWnd) ON_WM_DESTROY() ON_WM_CHAR() ON_WM_HSCROLL() ON_WM_KEYDOWN() ON_WM_KILLFOCUS() ON_WM_LBUTTONDBLCLK() ON_WM_LBUTTONDOWN() ON_WM_LBUTTONUP() ON_WM_MBUTTONDBLCLK() ON_WM_MBUTTONDOWN() ON_WM_MBUTTONUP() ON_WM_MOUSEMOVE() // ON_WM_NCLBUTTONDOWN() // ON_WM_NCMBUTTONDOWN() // ON_WM_NCRBUTTONDOWN() ON_WM_PAINT() ON_WM_TIMER() ON_WM_RBUTTONDBLCLK() ON_WM_RBUTTONDOWN() ON_WM_RBUTTONUP() ON_WM_SETFOCUS() ON_WM_SIZE() ON_WM_MOVE() ON_WM_VSCROLL() // Now things on the menus... ON_COMMAND(IDM_READ, OnRead) ON_COMMAND(IDM_SAVEAS, OnSaveAs) ON_COMMAND(IDM_SAVESEL, OnSaveSel) ON_COMMAND(IDM_PRINT, OnPrint) ON_COMMAND(IDM_PRINTSEL, OnPrintSel) ON_COMMAND(IDM_TOFILE, OnToFile) ON_COMMAND(IDM_EXIT, OnExit) ON_COMMAND(IDM_CUT, OnCut) ON_COMMAND(IDM_COPY, OnCopy) ON_COMMAND(IDM_PASTE, OnPaste) ON_COMMAND(IDM_REINPUT, OnReInput) ON_COMMAND(IDM_SELECTALL, OnSelectAll) ON_COMMAND(IDM_CLEAR, OnClear) // ON_COMMAND(IDM_UNDO, OnUndo) ON_COMMAND(IDM_REDRAW, OnRedraw) ON_COMMAND(IDM_HOME, OnHome) ON_COMMAND(IDM_END, OnEnd) ON_COMMAND(IDM_FONT, OnFont) ON_COMMAND(IDM_RESET_FONT, OnResetFont) ON_COMMAND(IDM_RESET_WINDOW, OnResetWindow) ON_COMMAND(IDM_INTERRUPT, OnInterrupt) ON_COMMAND(IDM_BACKTRACE, OnBacktrace) ON_COMMAND(IDM_PAGEMODE, OnPageMode) #ifdef GRAPHICS_WINDOW ON_COMMAND(IDM_GRAPHICS, OnGraphics) ON_MESSAGE(WM_USER, OnGraphics1) #endif #ifndef COMMON ON_COMMAND_RANGE(IDM_FIRSTLOAD, IDM_FIRSTLOAD+199, OnLoadLibrary) ON_COMMAND_RANGE(IDS_FIRSTSWITCH, IDS_FIRSTSWITCH+199, OnSwitch) #endif END_MESSAGE_MAP() // Message handlers... #define caretY (lineBuffer[caretLine].position - \ lineBuffer[lineVisible].position) #define caretDY (lineBuffer[caretLine].height) #define caretY1 (caretY+caretDY) void CMainWindow::OnPaint() { RECT update; inject_randomness((int)clock()); if (!GetUpdateRect(&update, TRUE)) return; CPaintDC dc(this); dc.SetTextAlign(TA_BASELINE); dc.SetTextColor(RGB(0,0,0)); currentColour = CH_BLACK; dc.SetBkColor(windowColour); dc.SelectObject(windowFonts.Courier); currentFont = CH_COURIER; currentWidths = windowFonts.HCourier.across; int yBase = lineBuffer[lineVisible].position, line; HideCaret(); for (line=lineVisible;; line=(line+1)&LINE_MASK) { int y = lineBuffer[line].position - yBase; int y1 = y+lineBuffer[line].up; int y2 = y+lineBuffer[line].height; if (y2 >= update.top) lineBuffer[line].width = PaintTextLine(&dc, -xOffset, y, y1, y2, lineBuffer[line].address, clientWidth, &windowFonts, 0); if (y > update.bottom+lineBuffer[line].up) break; if (line==lineLast) break; } ShowCaret(); dc.SelectStockObject(SYSTEM_FONT); // Now I will worry about positioning the horizontal scroll thumb. int longestLine = 0; for (line=lineVisible;; line=(line+1)&LINE_MASK) { int y = lineBuffer[line].position - yBase; if (y > clientHeight) break; int w = lineBuffer[line].width; if (w > longestLine) longestLine = w; if (line==lineLast) break; } if (longestLine <= clientWidth) { if (xOffset != 0) { // Mess here - my response is to force the X offset to zero // and scroll the window to match - that will generally force a fresh // painting operation. This sort of thing can happen when there was a // long line in the buffer and the window had been scrolled right to view it, // and then by a sequence of vertical scrolling operations that line is // no longer on the screen and all the lines that are visible are short enough // to be shown in their entirity. It can also happen when somebody // increases the size of the window. ScrollWindow(xOffset, 0, NULL, NULL); xOffset = 0; SetScrollPos(SB_HORZ, 0, TRUE); } } else { int HScrollPos = (100*xOffset)/(longestLine-clientWidth/2); // If (for instance) the font had just cahnged or the windows size had been // enlarged you might have too little left visible on the screen - in such // cases I will forcibly scroll to correct things. if (HScrollPos > 100) { ScrollWindow(xOffset-longestLine+clientWidth/2, 0, NULL, NULL); xOffset = longestLine-clientWidth/2; HScrollPos = 100; } SetScrollPos(SB_HORZ, HScrollPos, TRUE); } CPoint caretPos(caretX-xOffset, caretY); SetCaretPos(caretPos); } // a, b and c are pointers into the circular text buffer, with a<=c // (logically). Test is a<=b<c. The mess here is because of wrap- // around with TEXT_MASK. BOOL betweenChar(int a, int b, int c) { if (a<=c) return (a<=b && b<c); else return (a<=b || b<c); } // Ditto but relative to the line buffer. Observe that the code I have is // identical, but I still like to keep the abstractions separate. BOOL betweenLine(int a, int b, int c) { if (a<=c) return (a<=b && b<c); else return (a<=b || b<c); } int CMainWindow::PaintTextLine(CDC *dc, int x, int topY, int y, int bottomY, int textChar, int width, FontArray *ff, int context) { // context = 0 painting to screen // = 1 printing whole of buffer // = 2 just printing selected region int nCount = 0; int needFont = (currentFont != CH_COURIER); int needColour = (currentColour != CH_BLACK); // I will buffer up to 80 characters of fixed-pitch output and display them // using a single call to TextOut. This is intended to reduce the cost of // calling the operating system for output. char buff[80]; int bufp = 0, bufx = 0, bufy = 0; for (;;textChar=(textChar+1)&TEXT_MASK) { // I reset the X coordinate relating to the root, start and end of // any selection. This is necessary (for instance) after a font change, // and also means I do not have to re-calculate the X offsets when I am // inserting characters into the middle of the buffer. if (context == 0) { if (textChar == selFirstChar) selFirstX = x+xOffset; if (textChar == selStartChar) selStartX = x+xOffset; if (textChar == selEndChar) selEndX = x+xOffset; // Just after I have deleted a character (or two) I can have repositioned // the character but I will have lost track of the font active at its new // position, and thus of the font metrics, and the X coordinate that the // caret will be at. So although when inserting characters into the buffer // I can keep track of font & position I reset them here so that a // Paint operation following a delection puts things back in a coherent // state. if (textChar==caretChar) { caretX = x+xOffset; caretFontWidths = currentWidths; } if (textChar==icaretChar) { icaretX = x+xOffset; icaretFontWidths = currentWidths; } // Ditto for the end of the text buffer. if (textChar==textLast) { endX = x+xOffset; endFontWidths = currentWidths; break; } } else if (textChar==textLast) break; // I will stop if I either find a newline character or I reach the end // of the buffer. In each case I will have sorted out how long the line // I was displaying was. int c = textBuffer[textChar]; // I permit ESC characters in the buffer, but I will display them as $ // following an ancient tradition. Other control characters are not really // permitted and could lead to mangled display. if (c == 0x1b) c = '$'; int w = currentWidths[c & 0xff]; BOOL inSelection = betweenChar(selStartChar, textChar, selEndChar); if (context==0 && inSelection && (currentColour==CH_BLACK || currentColour==CH_HIGHLIGHT)) { needColour = FALSE; currentColour = CH_HIGHLIGHT; if (bufp != 0) dc->TextOut(bufx, bufy, buff, bufp), bufp=0; dc->SetTextColor(highlightTextColour); dc->SetBkColor(highlightColour); } else if (!inSelection) { // If I am outside the selected region and context==2 (printing selection) // then I map characters onto blanks so thet they will not appear in the // output. if (context == 2) c = ' '; if (currentColour==CH_HIGHLIGHT) { needColour = FALSE; currentColour = CH_BLACK; if (bufp != 0) dc->TextOut(bufx, bufy, buff, bufp), bufp=0; dc->SetTextColor(RGB(0,0,0)); dc->SetBkColor(windowColour); } } switch (c & 0xff) { case '\n': break; case CH_RED: needColour = FALSE; if (currentColour == c) continue; currentColour = c; if (bufp != 0) dc->TextOut(bufx, bufy, buff, bufp), bufp=0; dc->SetTextColor(RGB(255,0,0)); dc->SetBkColor(windowColour); continue; case CH_BLUE: needColour = FALSE; if (currentColour == c) continue; currentColour = c; if (bufp != 0) dc->TextOut(bufx, bufy, buff, bufp), bufp=0; dc->SetTextColor(RGB(0,0,255)); dc->SetBkColor(windowColour); continue; case CH_BLACK: if (inSelection && context==0) { c = CH_HIGHLIGHT; needColour = FALSE; if (currentColour == c) continue; currentColour = c; if (bufp != 0) dc->TextOut(bufx, bufy, buff, bufp), bufp=0; dc->SetTextColor(highlightTextColour); dc->SetBkColor(highlightColour); } else { needColour = FALSE; if (currentColour == c) continue; currentColour = c; if (bufp != 0) dc->TextOut(bufx, bufy, buff, bufp), bufp=0; dc->SetTextColor(RGB(0,0,0)); dc->SetBkColor(windowColour); } continue; case CH_GRAY: needColour = FALSE; if (currentColour == c) continue; currentColour = c; if (bufp != 0) dc->TextOut(bufx, bufy, buff, bufp), bufp=0; dc->SetTextColor(RGB(128,128,128)); dc->SetBkColor(windowColour); continue; case CH_COURIER: needFont = FALSE; if (currentFont == c) continue; currentFont = c; currentWidths = ff->HCourier.across; dc->SelectObject(ff->Courier); continue; case CH_ROMAN: needFont = FALSE; if (currentFont == c) continue; currentFont = c; currentWidths = ff->HRoman.across; if (bufp != 0) dc->TextOut(bufx, bufy, buff, bufp), bufp=0; dc->SelectObject(ff->Roman); continue; case CH_BOLD: needFont = FALSE; if (currentFont == c) continue; currentFont = c; currentWidths = ff->HBold.across; if (bufp != 0) dc->TextOut(bufx, bufy, buff, bufp), bufp=0; dc->SelectObject(ff->Bold); continue; case CH_ITALIC: needFont = FALSE; if (currentFont == c) continue; currentFont = c; currentWidths = ff->HItalic.across; if (bufp != 0) dc->TextOut(bufx, bufy, buff, bufp), bufp=0; dc->SelectObject(ff->Italic); continue; case CH_SYMBOL: needFont = FALSE; if (currentFont == c) continue; currentFont = c; currentWidths = ff->HSymbol.across; if (bufp != 0) dc->TextOut(bufx, bufy, buff, bufp), bufp=0; dc->SelectObject(ff->Symbol); continue; case CH_Roman: needFont = FALSE; if (currentFont == c) continue; currentFont = c; currentWidths = ff->Hroman.across; if (bufp != 0) dc->TextOut(bufx, bufy, buff, bufp), bufp=0; dc->SelectObject(ff->roman); continue; case CH_Bold: needFont = FALSE; if (currentFont == c) continue; currentFont = c; currentWidths = ff->Hbold.across; if (bufp != 0) dc->TextOut(bufx, bufy, buff, bufp), bufp=0; dc->SelectObject(ff->bold); continue; case CH_Italic: needFont = FALSE; if (currentFont == c) continue; currentFont = c; currentWidths = ff->Hitalic.across; if (bufp != 0) dc->TextOut(bufx, bufy, buff, bufp), bufp=0; dc->SelectObject(ff->italic); continue; case CH_Symbol: needFont = FALSE; if (currentFont == c) continue; currentFont = c; currentWidths = ff->Hsymbol.across; if (bufp != 0) dc->TextOut(bufx, bufy, buff, bufp), bufp=0; dc->SelectObject(ff->symbol); continue; default: // The start of every line should be in regular-sized Courier and // in black. But rather than calling Windows to force that selection // on every line (I hypothesize that selecting a fond and a colour // into my device context may be costly) I only do so when I find I have // a character to display and then only in cases where what was left over // from whatever was last printed was somehow different. if (context==0 && needColour && inSelection) { if (bufp != 0) dc->TextOut(bufx, bufy, buff, bufp), bufp=0; dc->SetTextColor(highlightTextColour); dc->SetBkColor(highlightColour); currentColour = CH_BLUE; needColour = FALSE; } else if (needColour) { if (bufp != 0) dc->TextOut(bufx, bufy, buff, bufp), bufp=0; dc->SetTextColor(RGB(0,0,0)); dc->SetBkColor(windowColour); currentColour = CH_BLACK; needColour = FALSE; } if (needFont) { dc->SelectObject(ff->Courier); currentFont = CH_COURIER; currentWidths = ff->HCourier.across; needFont = FALSE; } if (c == '\t') { c = ' '; w = currentWidths[c]*(8-(nCount&7)); nCount = 7; } else if (c < 32) { w = currentWidths['^']; // There is a curiosity here, that for the moment I am going to leave alone. // If one has a control character in the buffer it is displayed as something // like ^I (with the '^' as a separate character and the 'I' a normal letter). // If this is within a selection the 'I' is shown in inverse video as usual // but the '^' is not. This could perhaps be seen as a way of making it // possible to distinguish between the control character and a regular pair // of characters spelt the same. if (x+w>=0 && x<width) { if (bufp>=sizeof(buff)) dc->TextOut(bufx, bufy, buff, bufp), bufp=0; if (bufp == 0) bufx = x, bufy = y; buff[bufp++] = '^'; } x += currentWidths['^']; c = c | 0x40; w = currentWidths[c & 0xff]; nCount++; } if (inSelection && context==0) { CBrush b(highlightColour); CBrush *oldbrush = dc->SelectObject(&b); if (bufp != 0) dc->TextOut(bufx, bufy, buff, bufp), bufp=0; dc->SelectStockObject(NULL_PEN); dc->Rectangle(x, topY, x+w+1, bottomY+1); dc->SelectObject(oldbrush); } if (x+w>=0 && x<width) { if (currentFont == CH_COURIER) { if (bufp>=sizeof(buff)) dc->TextOut(bufx, bufy, buff, bufp), bufp = 0; if (bufp == 0) bufx = x, bufy = y; buff[bufp++] = c; } else { if (bufp != 0) dc->TextOut(bufx, bufy, buff, bufp), bufp=0; buff[0] = c; dc->TextOut(x, y, buff, 1); } } x += w; nCount++; continue; } break; } if (bufp != 0) dc->TextOut(bufx, bufy, buff, bufp); // I am NOT happy about the next few lines -- they fill an area in background // colour out across to the end of the line. I do this because otherwise // despite lots of calls to HideCaret I seem to observe remains of the caret // displayed after I have made a line shorter. I do not understand why this // happens but hope that what follows will work around the problem. if (context==0) { CBrush b(windowColour); CBrush *oldbrush = dc->SelectObject(&b); dc->SelectStockObject(NULL_PEN); dc->Rectangle(x, topY, width, bottomY+1); dc->SelectObject(oldbrush); } return x + xOffset; } int cwin_linelength = 80; void CMainWindow::OnSize(UINT nType, int cx, int cy) { WINDOWPLACEMENT wp; GetWindowPlacement(&wp); RECT *wr = &wp.rcNormalPosition; clientWidth = cx; clientHeight = cy; if (nType != SIZE_MINIMIZED) cwin_linelength = (clientWidth-5) / windowFonts.HCourier.across['X']; theApp.WriteProfileInt("MainWindow", "ScreenWidth", wr->right-wr->left); theApp.WriteProfileInt("MainWindow", "ScreenHeight", wr->bottom-wr->top); theApp.WriteProfileInt("MainWindow", "LineLength", cwin_linelength); if (caretVisible) { OnVScroll(SB_FOR_CARET, 0, NULL); OnHScroll(SB_FOR_CARET, 0, NULL); } OnHScroll(SB_REFRESH_THUMB, 0, NULL); OnVScroll(SB_REFRESH_THUMB, 0, NULL); } void CMainWindow::OnMove(int x, int y) { WINDOWPLACEMENT wp; GetWindowPlacement(&wp); RECT *wr = &wp.rcNormalPosition; theApp.WriteProfileInt("MainWindow", "ScreenLeft", wr->left); theApp.WriteProfileInt("MainWindow", "ScreenTop", wr->top); } void CMainWindow::OnResetWindow() { int screenWidth = GetSystemMetrics(SM_CXSCREEN); WINDOWPLACEMENT wp; GetWindowPlacement(&wp); RECT *wr = &wp.rcNormalPosition; int left = wr->left; int cwidth = 80*windowFonts.HCourier.across['X'] + 3*GetSystemMetrics(SM_CXBORDER) + 2*GetSystemMetrics(SM_CXFRAME) + GetSystemMetrics(SM_CXVSCROLL) + 5; // Try to get the whole window onto the screen. if (left + cwidth > screenWidth) { left = screenWidth - cwidth; if (left < 0) left = 0; } SetWindowPos(NULL, left, wr->top, cwidth, (wr->bottom - wr->top), SWP_SHOWWINDOW | SWP_NOZORDER); UpdateWindow(); cwin_linelength = 80; theApp.WriteProfileInt("MainWindow", "ScreenLeft", left); theApp.WriteProfileInt("MainWindow", "ScreenWidth", cwidth); theApp.WriteProfileInt("MainWindow", "ScreenTop", wr->top); theApp.WriteProfileInt("MainWindow", "ScreenHeight", wr->bottom-wr->top); theApp.WriteProfileInt("MainWindow", "LineLength", cwin_linelength); } void CMainWindow::OnVScroll(UINT nSBCode, UINT nPos, CScrollBar *pNctl) { // I will start by computing the total height of all the stuff I have in // my text buffer. At the very worst I only save a couple of thousand lines, // so adding up their heights again will not be TOO painful, I hope. int VScrollPos, movedUp=0, i, w; int aboveTop = -LineY(lineFirst); int totalHeight = aboveTop + LineY(lineLast) + LineDY(lineLast); inject_randomness(totalHeight); // If I can make everything visible I will do that regardless of anything // the user tries to do. if (totalHeight <= clientHeight) { lineVisible = lineFirst; movedUp = aboveTop; VScrollPos = 0; } else { switch (nSBCode) { case SB_ENDSCROLL: return; case SB_REFRESH_THUMB: // In case the window has just been enlarged I check to see if I can scroll // up a bit until one more scroll up would make the last line of my text // drop off the (new) bottom of the window. while (lineVisible!=lineFirst) { int lv = (lineVisible-1)&LINE_MASK; int nAbove = lineBuffer[lv].position - lineBuffer[lineFirst].position; if (totalHeight > nAbove+clientHeight) break; lineVisible = lv; w = LineDY(lv); movedUp += w; aboveTop -= w; } break; case SB_THUMBPOSITION: case SB_THUMBTRACK: i = (nPos*(totalHeight-clientHeight))/100; if (i > aboveTop) { while (totalHeight > aboveTop+clientHeight && i > aboveTop) { w = LineDY(lineVisible); aboveTop += w; movedUp -= w; lineVisible = (lineVisible+1) & LINE_MASK; } } else { i += LineDY(lineVisible); while (lineVisible != lineFirst && i <= aboveTop) { lineVisible = (lineVisible-1) & LINE_MASK; w = LineDY(lineVisible); aboveTop -= w; movedUp += w; } } break; case SB_FOR_CARET: if (LineY(caretLine)+LineDY(caretLine) > clientHeight) { while (totalHeight > aboveTop+clientHeight && LineY(caretLine)+LineDY(caretLine) > clientHeight) { w = LineDY(lineVisible); aboveTop += w; movedUp -= w; lineVisible = (lineVisible+1) & LINE_MASK; } } else { while (lineVisible != lineFirst && LineY(caretLine) < 0) { lineVisible = (lineVisible-1) & LINE_MASK; w = LineDY(lineVisible); aboveTop -= w; movedUp += w; } } break; case SB_LINEDOWN: // If the bottom line of text is already visible I will not scroll down // any further. if (totalHeight > aboveTop+clientHeight) { lineVisible = (lineVisible+1) & LINE_MASK; w = lineBuffer[lineVisible].height; aboveTop += w; movedUp = -w; } break; case SB_LINEUP: if (lineVisible != lineFirst) { w = lineBuffer[lineVisible].height; aboveTop -= w; movedUp = w; lineVisible = (lineVisible-1) & LINE_MASK; } break; // When asked to scroll be a "page" I in fact jump by around 0.7 times // the number of lines visible on a page. case SB_PAGEDOWN: while (totalHeight > aboveTop+clientHeight && -10*movedUp < 7*clientHeight) { lineVisible = (lineVisible+1) & LINE_MASK; w = lineBuffer[lineVisible].height; aboveTop += w; movedUp -= w; } break; case SB_PAGEUP: while (lineVisible != lineFirst && 10*movedUp < 7*clientHeight) { w = lineBuffer[lineVisible].height; aboveTop -= w; movedUp += w; lineVisible = (lineVisible-1) & LINE_MASK; } break; } if (lineVisible == lineFirst) VScrollPos = 0; else if (totalHeight <= aboveTop+clientHeight) VScrollPos = 100; else VScrollPos = (100*aboveTop)/(totalHeight-clientHeight); } // If the user had just dragged the scroll bar I will tend to leave the // scroll bar thumb where the user put it. However I will make an exception // if the user's drag caused me to go to one or other extreme position, since // I think it would be confusing to have the window totally fully scrolled // and the thumb not extremal in its bar. But at all intermediate positions // it is kindest to let it settle where the user put it! if (nSBCode == SB_THUMBPOSITION || nSBCode == SB_THUMBTRACK) { if (VScrollPos == 0 || VScrollPos == 100) nPos = VScrollPos; SetScrollPos(SB_VERT, nPos, TRUE); } else SetScrollPos(SB_VERT, VScrollPos, TRUE); if (movedUp != 0) { HideCaret(); ScrollWindow(0, movedUp, NULL, NULL); ShowCaret(); } // If the window is minimized then I will not change the status of apparent // visibility of the caret. This is important because if the caret becomes // marked as invisible because (for instance) the window is of zero size then // it will remain invisible and the window will not be scrolled ever, leaving // the text buffer to over-fill and hang things up. if (IsIconic()) return; else if (caretY >= 0 && caretY1 <= clientHeight && caretX-xOffset >= 0 && caretX-xOffset < clientWidth) caretVisible = TRUE; else caretVisible = FALSE; } void CMainWindow::OnHScroll(UINT nSBCode, UINT nPos, CScrollBar *PCntl) { // Each time I hit the horizontal scroll-bar I will check to find how // long the longest (partially) visible line is. int yBase = lineBuffer[lineVisible].position; int line, longestLine = 0; for (line=lineVisible;; line=(line+1)&LINE_MASK) { int y = lineBuffer[line].position - yBase; if (y > clientHeight) break; int w = lineBuffer[line].width; if (w > longestLine) longestLine = w; if (line==lineLast) break; } // If everything fits I will just make sure I am scrolled fully left. Well as // a matter of caution I do not force things that way if the request was // to make the caret visible, since maybe if the screen is not quite up to // date it may seem that I could afford to scroll fully left but that would // in fact hide the caret. if (longestLine < clientWidth && nSBCode != SB_FOR_CARET) { if (xOffset != 0) { HideCaret(); ScrollWindow(xOffset, 0, NULL, NULL); ShowCaret(); } xOffset = 0; SetScrollPos(SB_HORZ, 0, TRUE); return; } int moveLeft = 0, w; switch (nSBCode) { case SB_ENDSCROLL: return; case SB_REFRESH_THUMB: break; case SB_THUMBPOSITION: case SB_THUMBTRACK: w = (nPos*(longestLine-clientWidth/2))/100; moveLeft = xOffset - w; xOffset = w; break; case SB_FOR_CARET: w = windowFonts.HCourier.across['X']; if (caretX-xOffset < 5*w) moveLeft = w*((clientWidth/2 - (caretX-xOffset))/w); else if (caretX-xOffset >= clientWidth) moveLeft = -w*((caretX - xOffset - clientWidth)/w + 4); xOffset -= moveLeft; break; // Small jumps are by the width of the letter X in the biggest fixed pitch // font that I have selected. Thus if you select a big font you will get // coarser contol over horizontal scrolling. case SB_LINERIGHT: w = windowFonts.HCourier.across['X']; xOffset += w; moveLeft = -w; break; case SB_LINELEFT: w = windowFonts.HCourier.across['X']; xOffset -= w; moveLeft = w; break; // Big jumps are by roughly (2/3) the width of the main Window case SB_PAGERIGHT: w = (2*clientWidth)/3; xOffset += w; moveLeft = -w; break; case SB_PAGELEFT: w = (2*clientWidth)/3; xOffset -= w; moveLeft = w; break; } if (xOffset < 0) { moveLeft += xOffset; xOffset = 0; } // I will permit scrolls to the right to a stage where the longest line // is (1/2) across the screen. But no further and especially I will not // permit scrolls right that cause the text on the screen to vanish // totally. The limit I use here is somewhat of an arbitrary choice. If a // "scroll page right" request looked as if it would move beyond this I will // clip the scroll. w = longestLine - clientWidth/2; if (w <= 0) { moveLeft += xOffset; xOffset = 0; w = 1; } else if (xOffset>w) { moveLeft += xOffset - w; xOffset = w; } int HScrollPos = (100*xOffset)/w; if (HScrollPos > 100) HScrollPos = 100; if ((nSBCode != SB_THUMBPOSITION && nSBCode != SB_THUMBTRACK) || HScrollPos == 0 || HScrollPos == 100) SetScrollPos(SB_HORZ, HScrollPos, TRUE); else SetScrollPos(SB_HORZ, nPos, TRUE); if (moveLeft != 0) { HideCaret(); ScrollWindow(moveLeft, 0, NULL, NULL); ShowCaret(); } } void CMainWindow::OnSetFocus(CWnd *pOldWnd) { CreateSolidCaret(caretWidth, windowFonts.HCourier.height); ShowCaret(); SetWindowPos(&wndTop, 0, 0, 0, 0, SWP_NOMOVE|SWP_NOSIZE|SWP_SHOWWINDOW); selectScrollSpeed = 0; hasFocus = TRUE; /* True just after focus has returned to this window */ } void CMainWindow::OnKillFocus(CWnd *pNewWnd) { ::DestroyCaret(); hasFocus = FALSE; } // Sometime I find reading documentation quite hard. I had really thought that // when one selected an application (eg using ALT+TAB) that would give the // input focus to that application's main window. But it appears // (experimentally) that this is not always true, and I found myself with // a window with hard blue title bar but no input focus. It seems (?) that // the application has been "activated", so here I ensure that when that // happens it grabs the focus. Elsewhere you will find that whenever I see a // mouse button depressed I grab the focus too (just to be on the safe side). void CMainWindow::OnActivate(UINT state, CWnd *pWnd, BOOL minim) { if (state != WA_INACTIVE) SetFocus(); } // When I put a character into the text buffer I need to worry about purging // old characters when the buffer wraps around. That may have an effect on // what is visible on the screen, or on a selected region. If I insert a // character and I am at the foot of a page I may need to scroll up. All // in all the funny cases are quite messy here! void CMainWindow::cwin_putchar(int ch) { if (hasFinished) return; BOOL caretAtEnd = (caretChar == textLast); if (ch == '\n') { // If the caret is at the end of the text and if "page mode" is enabled // then putting a newline into the text buffer will sometimes lead to // a pause.... BOOL usualTitle = TRUE; // The test here says pause if // (a) I am in page mode // (b) lineVisible==pageLine. Well lineFirst is the line that is now in // danger of being about to scroll off the top of the screen, and so // pageLine will identify the current line to be "protected" from that // fate. // (c) caretLine vs clientHeight: This tries to test if scrolling would be // called for if the caret moved down a line (hence the 2*LineDY()). // In the pause case I just loop polling the window manager until something // magic happens. In general I can do the correct magic by making a suitable // selection of events do "pageLine = caretLine;" // // Actually it is nastier than that, because lineVisible may not be up to // date - so I have to work out what lineVisible WOULD be in the relevant // situation by having code rather line that in OnVScroll/SB_FOR_CARET. while (pageMode) { int lv = lineVisible, w; int aboveTop = -LineY(lineFirst); int totalHeight = aboveTop + LineY(lineLast) + LineDY(lineLast); if (totalHeight <= clientHeight) lv = lineFirst; else while (totalHeight > aboveTop+clientHeight && LineY(caretLine)+LineDY(caretLine) > clientHeight) { w = LineDY(lv); aboveTop += w; lv = (lv+1) & LINE_MASK; } if (lv==pageLine && (LineY(caretLine)+2*LineDY(caretLine) > clientHeight)) { if (usualTitle) { usualTitle = FALSE; pagePaused = TRUE; // BeginWaitCursor(); // For reasons that I do not fully understand a "wait" cursor established here // gets turned back into the usual arrow one before I want it to. I suspect // maybe that whenever the system re-draws the cursor it does so using the // version that is set as default for the application? Anyway the effect is // that I get an apparantly inconsistent behaviour, and at present I think // that keeping the cursor consistently an arrow is better than letting it // flash incoherently between that and an hourglass. SetWindowText("(page-mode) Type any key to continue"); } // Here I will force the caret to be visible and poll the window manager if (!hasFinished) { OnVScroll(SB_FOR_CARET, 0, NULL); OnHScroll(SB_FOR_CARET, 0, NULL); cwin_poll_window_manager(); } if (hasFinished) return; continue; } else break; } if (!usualTitle) { SetWindowText(mainTitle); // EndWaitCursor(); } pagePaused = FALSE; textBuffer[textLast] = '\n'; int n = (textLast+1) & TEXT_MASK; if (n == textFirst) WrapTextBuffer(TRUE); textLast = n; // When I see a newline I may need to wrap the line buffer, discarding the // oldest saved line. int nextPos = lineBuffer[lineLast].position + lineBuffer[lineLast].height; n = (lineLast+1) & LINE_MASK; if (n == lineFirst) WrapTextBuffer(TRUE); lineLast = n; lineBuffer[lineLast].position = nextPos; lineBuffer[lineLast].up = windowFonts.HCourier.up; lineBuffer[lineLast].height = windowFonts.HCourier.up + windowFonts.HCourier.down; lineBuffer[lineLast].width = 0; lineBuffer[lineLast].address = textLast; endFontWidths = windowFonts.HCourier.across; endX = 0; if (caretAtEnd) { caretChar = icaretChar = textLast; caretLine = icaretLine = lineLast; caretX = icaretX = 0; caretFontWidths = icaretFontWidths = windowFonts.HCourier.across; } } else { textBuffer[textLast] = ch; int n = (textLast+1) & TEXT_MASK; if (n == textFirst) WrapTextBuffer(TRUE); // I invalidate all the way to the right of the line just to be on the // safe side. This is probably excessive... but equally the painting in of // the solid blank to the right of any real text will not be too expensive. CRect cr(lineBuffer[lineLast].width-xOffset, LineY(lineLast), clientWidth, LineY(lineLast)+LineDY(lineLast)); InvalidateRect(&cr); // Should I hide the caret here? textLast = n; endX += endFontWidths[ch]; lineBuffer[lineLast].width += endFontWidths[ch]; if (caretAtEnd) { caretChar = icaretChar = textLast; caretX += caretFontWidths[ch]; icaretX = caretX; } } } // This code inserts characters wherever the caret happens to be, typically // in the middle of the text buffer. This returns TRUE if it succeeds. The // case when it might fail is if the text buffer is full and the caret is on // the first line. Then to make room for the next stuff the first line would // need to be discarded, but I can not do that without loss of the caret. // // UnTypeAhead() is used to push characters into the type-ahead buffer if // a newline is inserted into the current line of input (ie between // inputLineStart and textLast). It returns FALSE if pushing characters // flushed out previously typed stuff. BOOL CMainWindow::UnTypeAhead(int ch) { int w = (typeAheadP1-1)&typeAheadBufferMask; typeAheadBuffer[w] = ch; typeAheadP1 = w; if (typeAheadP1 == typeAheadP2) { typeAheadP2 = (typeAheadP2-1)&typeAheadBufferMask; return FALSE; // Inserting pushes out something else } else return TRUE; } BOOL CMainWindow::InsertAtCaret(char *s, int n) { // I will do large inserts in 60-character chunks. That is so I can have // a limited size circular buffer (64 chars here) for use when copying // stuff down the buffer. Huge inserts would benefit from a larger chunk-size // here, at the cost of using more memory for the copy buffer. while (n > 60) { if (!InsertAtCaret(s, 60)) return FALSE; s += 60; n -= 60; } if (n == 0) return TRUE; int oldCaretLine = caretLine, oldCaretChar = caretChar; BOOL startEnd = (inputLineStart == textLast); int i, p = textLast; // Firstly I will make sure that there is room for the inserted text by // stepping on n characters from the current end of the text buffer and // wraping to free up the space I walk over. If this were to collide with // the caret position I would be in a MESS so in such cases I will not // perform the insert & will return a failure flag. for (i=0; i<n; i++) { p = (p+1)&TEXT_MASK; if (p == textFirst) { if (caretLine == lineFirst) return FALSE; // fail else WrapTextBuffer(FALSE); } } // Also I need to ensure that there is room for the number of new lines // that I will insert. It will be rare that this is a problem, I suspect. // Also beware here if the caret is positioned on the top line of the // buffer. int k = 0; for (i=0; i<n; i++) if (s[i] == '\n') k++; p = lineLast; for (i=0; i<k; i++) { p = (p+1)&LINE_MASK; if (p == lineFirst) { if (caretLine == lineFirst) return FALSE; // fail else WrapTextBuffer(FALSE); } } // Now I know that there is room for the insertion, I will "just" need to // copy characters up through the text buffer. char circle[64], circleFlags[64]; memcpy(circle, s, n); memset(circleFlags, 0, n); int inP = n, outP = 0; // (line,p) is where I read characters from the buffer, while (line1,p1) // is where I put them back. Certainly at the start, and quite often all // the way through these values will remain in step. They may drift apart if // previous deletions within lines lave left gaps in the buffer between the // end of one line and the start of the next. int line = caretLine; p = caretChar; int line1 = line, p1 = p; while (p != textLast) { int c = textBuffer[p]; circle[inP] = c; circleFlags[inP] = 0; if (p == caretChar) circleFlags[inP] |= 1; // I will leave inputLineStart where it is if it lies just before the // caret position at the start of the insert operation. if (p == inputLineStart && p != oldCaretChar) circleFlags[inP] |= 2; if (p == icaretChar) circleFlags[inP] |= 4; if (c == '\n') { line = (line+1)&LINE_MASK; p = lineBuffer[line].address; } else p = (p+1)&TEXT_MASK; inP = (inP+1)&63; int c1 = circle[outP]; textBuffer[p1] = c1; if (circleFlags[outP] & 1) caretChar = p1, caretLine = line1; if (circleFlags[outP] & 2) inputLineStart = p1; if (circleFlags[outP] & 4) icaretChar = p1, icaretLine = line1; p1 = (p1+1)&TEXT_MASK; outP = (outP+1)&63; // Now if I had just inserted a newline I need to fix up the pointers // from the line buffer into the text buffer. if (c1 == '\n') { if (line == line1) // Here I need to shuffle lines up { int a = p1, line2=line; lineLast = (lineLast+1)&LINE_MASK; for (;;) { line2 = (line2+1)&LINE_MASK; int w = lineBuffer[line2].address; lineBuffer[line2].address = a; if (line2==lineLast) break; a = w; } line = (line+1)&LINE_MASK; } line1 = (line1+1)&LINE_MASK; lineBuffer[line1].address = p1; } } while (inP != outP) { int c1 = circle[outP]; textBuffer[p1] = c1; if (circleFlags[outP] & 1) caretChar = p1, caretLine = line1; if (circleFlags[outP] & 2) inputLineStart = p1; if (circleFlags[outP] & 4) icaretChar = p1, icaretLine = line1; if (c1 == '\n') { line1 = (line1+1)&LINE_MASK; lineBuffer[line1].address = p1; } p1 = (p1+1)&TEXT_MASK; outP = (outP+1)&63; } textLast = p1; lineLast = line1; // Here it may be that a newline had been inserted within the line that // was being prepared for input. If so, stuff after that newline must be // pushed back into the type-ahead buffer. When this happens it will always // be the case that the text in the buffer after inputLineStart will form // a compact block so I can just copy chars backwards BOOL success = TRUE; if (startEnd) inputLineStart = textLast; else if (inputLineStart>=0 && k!=0) { int lastNewline = inputLineStart; while (lastNewline!=textLast && textBuffer[lastNewline]!='\n') lastNewline = (lastNewline+1)&TEXT_MASK; while (textLast!=lastNewline) { int nn = (textLast-1)&TEXT_MASK; if (textLast==caretChar) caretChar = nn; if (textLast==icaretChar) icaretChar = nn; if (textLast==selStartChar || textLast==selFirstChar || textLast==selEndChar) CancelSelection(); textLast = nn; int c = textBuffer[textLast]; if (!UnTypeAhead(c)) success = FALSE; if (c=='\n') lineLast = (lineLast-1)&LINE_MASK; } } LineSizes(); CRect cr(0, LineY(oldCaretLine), clientWidth, clientHeight); if (k==0) // No newlines inserted { cr.left = caretX; cr.bottom = LineY(caretLine)+LineDY(caretLine); } InvalidateRect(&cr); UpdateWindow(); cwin_poll_window_manager(); return success; } void CMainWindow::cwin_caret_putchar(int ch) { if (hasFinished) return; // If the caret happens to be at the end maybe I ought not to have got here // anyway, but I will chain to the code that inserts things at the end of the // text. if (caretChar == textLast) { cwin_putchar(ch); return; } char buff[4]; buff[0] = ch; // I beep if the insertion was impossible. if (!InsertAtCaret(buff, 1)) ::MessageBeep(0xffffffff); } void CMainWindow::cwin_caret_replacechar(int ch) { if (hasFinished) return; // If the caret happens to be at the end maybe I ought not to have got here // anyway, but I will chain to the code that inserts things at the end of the // text. if (caretChar == textLast || ch == '\n' || textBuffer[caretChar] == '\n') { cwin_putchar(ch); return; } // Gosh this OUGHT to be easy! textBuffer[caretChar] = ch; if (caretChar == icaretChar) { icaretX += caretFontWidths[ch]; icaretChar++; } CRect cr(caretX, LineY(caretLine), clientWidth, LineY(caretLine)+LineDY(caretLine)); InvalidateRect(&cr); caretX += caretFontWidths[ch]; caretChar++; UpdateWindow(); cwin_ensure_screen(FALSE); } // When I have put enough characters on the screen my text buffer may become // full. In which case I will discard the earliest stored line in it. Because // the line will always be stored with at least a newline character present // this guarantees to free up at least one byte in the buffer. I can also // call this routine to discard the top line of the buffer when I need to // recycle space in the record of lines (as distinct from characters). // // If I attempt to cancel part of the buffer by coming here but either the // top line of the buffer is visible on the screen or part of the top line // is included in a selection then I will pause until the user scrolls the // window or does something to the selection that will mean that throwing // away information will not discard anything still being looked at. // // Note that when I say here "Part of the first line is selected" I will // mean that a non-empty part of the line is selected. Not having a // selection in force is indicated by selStartChar==selEndChar, but the // selection pointers can otherwise point anywhere at all in the buffer // or indeed outside it. void CMainWindow::WrapTextBuffer(BOOL waitForSelection) { selRootValid = FALSE; BOOL usualTitle = TRUE; if (waitForSelection) { while ((lineVisible == lineFirst && caretChar != textLast && LineY(lineLast)+LineDY(lineLast)>=clientHeight) || (selStartLine == lineFirst && selStartChar != selEndChar)) { cwin_ensure_screen(FALSE); if (usualTitle) usualTitle = FALSE, SetWindowText( "Output pending: cancel selection/scroll down please"); cwin_poll_window_manager(); } } if (hasFinished) return; if (!usualTitle) SetWindowText(mainTitle); if (lineFirst==lineLast) OnClear(); // Drastic! else { lineFirst = (lineFirst+1)&LINE_MASK; textFirst = lineBuffer[lineFirst].address; } } void cwin_putchar(int c) { theApp.mainWindow->cwin_putchar(c); inject_randomness(c); inject_randomness((int)clock()); } void CMainWindow::cwin_puts(const char *s) { if (hasFinished) return; int ch; // Here I should be careful and do just ONE call to Invalidate for all the // changes that I make. But to start with it is MUCH easier to call putchar // lots of times, so that is what I will do. while ((ch=*s++)!=0) cwin_putchar(ch); } void cwin_puts(const char *s) { theApp.mainWindow->cwin_puts(s); } #ifndef MS_CDECL #ifdef _MSC_VER #define MS_CDECL __cdecl #else #define MS_CDECL #endif #endif void MS_CDECL CMainWindow::cwin_printf(const char *s, ...) { if (hasFinished) return; va_list a; va_start(a, s); cwin_vfprintf(s, a); va_end(a); } void MS_CDECL cwin_printf(const char *s, ...) { if (hasFinished) return; va_list a; va_start(a, s); theApp.mainWindow->cwin_vfprintf(s, a); va_end(a); } // The versions of printf() that work with the window system use an internal // buffer and print characters into that before sending them to the screen. // It appears to be hard to check for overflow of this buffer, so the user // will have to take care. A call to cwin_printf that generates more than // MAX_PRINTF_OUTPUT characters can crash the system. Sorry. Maybe I // should use the Watcom-specific _vbprintf() function here... #define MAX_PRINTF_OUTPUT 256 void CMainWindow::cwin_vfprintf(const char *s, va_list a) { if (hasFinished) return; char temp[MAX_PRINTF_OUTPUT]; #ifdef __WATCOMC__ _vbprintf(temp, MAX_PRINTF_OUTPUT, s, a); // I put a zero byte at the end of the buffer to ensure that the string // left there is properly terminated even if _vbprintf() wrote proper // characters right up to the end. temp[MAX_PRINTF_OUTPUT-1] = 0; #else vsprintf(temp, s, a); #endif cwin_puts(temp); } void cwin_vfprintf(const char *s, va_list a) { theApp.mainWindow->cwin_vfprintf(s, a); } void CMainWindow::cwin_ensure_screen(BOOL poll) { if (hasFinished) return; // Here I need to do Invalidate operations, re-position the caret // and adjust the position of scroll-bar thumbs. if (caretVisible) { OnVScroll(SB_FOR_CARET, 0, NULL); OnHScroll(SB_FOR_CARET, 0, NULL); } // OnVScroll(SB_REFRESH_THUMB, 0, NULL); HideCaret(); CPoint caretPos(caretX-xOffset, caretY); SetCaretPos(caretPos); ShowCaret(); if (poll) cwin_poll_window_manager(); } void cwin_ensure_screen() { theApp.mainWindow->cwin_ensure_screen(TRUE); } // This will remove a character from the buffer at a location defined // by the caret, which will in general not be at the end of the text. void CMainWindow::cwin_caret_unputchar() { // When I try to remove a character from the screen I know that there is // no selection active, since a DELETE when there was a selection deleted it // as a block, and so did not reach this bit of code. So perhaps I can // make this code work by creating a selected region containing the // character (or prompt string) that I to be discarded and then do a // DeleteSelection. if (hasFinished) return; // If it happens that I am at the end of the text I will call the other // bit of character deletion code, so that in the bulk of this function I // can know that I am in the general case (which tends to be an expensive one) if (caretChar==textLast) { cwin_unputchar(); return; } selRootValid = FALSE; // If the caret is right at the start of the buffer I do not have // anything to delete. This will, of course, include the case where the // buffer is totally empty. if (caretChar==textFirst) return; // Set up the start of the selection at the position of the caret. selFirstChar = selStartChar = selEndChar = caretChar; selFirstX = selStartX = selEndX = caretX; selFirstLine = selStartLine = selEndLine = caretLine; // Now I need to step back a "character", where that could be a newline // or a prompt string rather than any single simple character. I also // ought to worry here about deletion of items in the text buffer that // control formatting. All in all this is pretty dodgy, so I will cope with // just the simple cases to start with. int n; if (lineBuffer[caretLine].address == caretChar) // deleting a newline? { return; // For now I am going to prevent deletion of or through a prompt or newline n = (caretLine-1)&LINE_MASK; caretLine=n; caretX = lineBuffer[n].width; n = lineBuffer[n].address; while (textBuffer[n]!='\n') n = (n+1)&TEXT_MASK; } else { n = (caretChar-1) & TEXT_MASK; // I produce just an APPROXIMATION to the desired caretX here, but want that // so that I can limit the amount of re-painting that happens. int w = windowFonts.HCourier.across['X']; if ((textBuffer[n]&0xff)==CH_ENDPROMPT) // delete whole of a prompt { return; // do not delete prompt do { n = (n-1)&TEXT_MASK; caretX -= w; } while (n!=textFirst && (textBuffer[n]&0xff)!=CH_PROMPT); } else caretX -= w; } caretChar = n; if (caretLine == lineLast) { icaretChar = caretChar; icaretLine = caretLine; icaretX = caretX; } selStartChar = n; selStartLine = caretLine; selStartX = caretX; DeleteSelection(); } // Now a version of unputchar that deletes characters from the end of the // text buffer (and the caret may or may not be there). void CMainWindow::cwin_unputchar() { if (hasFinished) return; selRootValid = FALSE; int n; if (textFirst == textLast) return; // utterly empty buffer if (lineBuffer[lineLast].address == textLast) // deleting a newline? { return; n = (lineLast-1)&LINE_MASK; endX = lineBuffer[n].width; if (caretLine==lineLast) caretLine=n, caretX=endX; if (icaretLine==lineLast) icaretLine=n, icaretX=endX; lineLast=n; n = lineBuffer[n].address; while (textBuffer[n]!='\n') n = (n+1)&TEXT_MASK; if (textLast == caretChar) caretChar = n; if (textLast == icaretChar) icaretChar = n; if (textLast == inputLineStart) inputLineStart = n; textLast = n; } else { n = (textLast-1) & TEXT_MASK; int w = windowFonts.HCourier.across['X']; if ((textBuffer[n]&0xff)==CH_ENDPROMPT) // delete whole of a prompt { return; do { n = (n-1)&TEXT_MASK; endX -= w; } while (n!=textFirst && (textBuffer[n]&0xff)!=CH_PROMPT); } else endX -= w; if (textLast == caretChar) caretChar = n; if (textLast == icaretChar) icaretChar = n; if (textLast == inputLineStart) inputLineStart = n; textLast = n; // The amount of invalidation done here runs across to the end of the // last line, and that ought to get rid of any residues of the old // caret. I use an APPROXIMATION to the new value of endX, but hope that // that gets corrected during the re-painting. CRect cr(endX-xOffset, LineY(lineLast), clientWidth, LineY(lineLast)+LineDY(lineLast)); InvalidateRect(&cr); // Should I hide the caret here? } UpdateWindow(); HideCaret(); CPoint caretPos(caretX-xOffset, caretY); SetCaretPos(caretPos); ShowCaret(); } int CMainWindow::cwin_getchar() { if (hasFinished) return EOF; int ch; if (inputP >= 0) { ch = inputBuffer[inputP++]; if (ch == '\n') inputP = -1; // This buffer now all used up. return ch; } cwin_putchar(CH_PROMPT); cwin_puts(cwin_prompt_string); if (spool_file != NULL) fprintf(spool_file, "%s", cwin_prompt_string); cwin_putchar(CH_ENDPROMPT); if (hasFinished) return EOF; inputLineStart = textLast; // just beyond the prompt // Next I move characters across from the type-ahead buffer into the main // text buffer, echoing them as I go (and handling DELETE chars too). I // stop when I find a newline. Perhaps some people would like if if some // other characters would terminate an input line, but for the moment I am // sticking to just newline.... except that any keyboard or menu activity // that raises an "exception" will stop input for me rather abruptly. for (;;) { cwin_ensure_screen(FALSE); while (typeAheadP1 == typeAheadP2 && clipboardInput == NULL) { cwin_poll_window_manager(); if (hasFinished || exception_pending()) return EOF; } if (clipboardInput != NULL) { ch = *clipboardInputP++; // When input is coming from the clipboard to the very end of the input // text I will ignore copied prompt strings. Note that this is different // from the situation with PASTE inserts into the middle of the text. // A curious side-effect here is that if a regular TEXT clipboard file is // pasted in and it happens to contain these funny character codes... while ((ch&0xff) == CH_PROMPT) { while ((ch = *clipboardInputP++) != 0 && (ch&0xff) != CH_ENDPROMPT); if (ch == 0) break; ch = *clipboardInputP++; } if (ch == '\r') continue; if (ch == 0) { free(clipboardInput); clipboardInput = NULL; continue; } } else { ch = typeAheadBuffer[typeAheadP1]; typeAheadP1 = (typeAheadP1 + 1) & typeAheadBufferMask; } if (ch == 0x7f) cwin_unputchar(); else cwin_putchar(ch); if (ch == '\n' || hasFinished) break; } if (hasFinished) return EOF; // By the time I exit the above loop there must be at least one character // in the line that has just been read, even if it is only the terminating // '\n'. inputP = 0; // When I copy stuff from the screen into the input buffer I discard any // prompts. They might be there if the line of input was created in part by // pasting material into the middle of a partly-typed line. BOOL inPrompt = FALSE; for (;;) { ch = textBuffer[inputLineStart]; inputLineStart = (inputLineStart+1)&TEXT_MASK; // Here I quietly truncate very very long input lines. For my first attempt // I have inputBufferSize set at 2048. if ((ch&0xff) == CH_PROMPT) inPrompt = TRUE; if (inputP<inputBufferSize && !inPrompt) inputBuffer[inputP++] = ch; if ((ch&0xff) == CH_ENDPROMPT) inPrompt = FALSE; else if (ch == '\n') break; } inputBuffer[inputBufferSize-1] = '\n'; // Now I have the next line of user input in inputBuffer, running from 0 // up to the first '\n' (and I know there is a '\n' in there somewhere). // I want to copy it away into a "saved input lines" buffer, so that they // can be retrieved later by a DOSKEY-like protocol. Note that provided // I keep the size of the save buffer larger than that of the input buffer // the copying operation here will not cause embarassing overflow! currentInputLine = -1; savedLines[savedP2++] = savedLast; if (savedP2 == MAX_SAVED_LINES) savedP2 = 0; if (savedP2 == savedP1) { savedP1++; if (savedP1 == MAX_SAVED_LINES) savedP1 = 0; savedFirst = savedLines[savedP1]; } inputP = 0; for (;;) { ch = inputBuffer[inputP++]; savedChars[savedLast++] = ch; if (savedLast == MAX_SAVED_CHARS) savedLast = 0; if (savedLast == savedFirst) { savedP1++; if (savedP1 == MAX_SAVED_LINES) savedP1 = 0; savedFirst = savedLines[savedP1]; } if (ch == '\n') break; } inputP = 0; ch = inputBuffer[inputP++]; if (ch == '\n') inputP = -1; // This buffer now all used up. cwin_ensure_screen(FALSE); return ch; } int cwin_getchar() { return theApp.mainWindow->cwin_getchar(); } int CMainWindow::cwin_getchar_nowait() { if (hasFinished) return EOF; int ch; if (inputP >= 0) { ch = inputBuffer[inputP++]; if (ch == '\n') inputP = -1; // This buffer now all used up. return ch; } cwin_putchar(CH_PROMPT); cwin_puts(cwin_prompt_string); if (spool_file != NULL) fprintf(spool_file, "%s", cwin_prompt_string); cwin_putchar(CH_ENDPROMPT); if (hasFinished) return EOF; inputLineStart = textLast; // just beyond the prompt // Next I move characters across from the type-ahead buffer into the main // text buffer, echoing them as I go (and handling DELETE chars too). I // stop when I find a newline. for (;;) { cwin_ensure_screen(FALSE); if (typeAheadP1 == typeAheadP2 && clipboardInput == NULL) return EOF; if (clipboardInput != NULL) { ch = *clipboardInputP++; while ((ch&0xff) == CH_PROMPT) { while ((ch = *clipboardInputP++) != 0 && (ch&0xff) != CH_ENDPROMPT); if (ch == 0) break; ch = *clipboardInputP++; } if (ch == '\r') continue; if (ch == 0) { free(clipboardInput); clipboardInput = NULL; continue; } } else { ch = typeAheadBuffer[typeAheadP1]; typeAheadP1 = (typeAheadP1 + 1) & typeAheadBufferMask; } if (ch == 0x7f) cwin_unputchar(); else cwin_putchar(ch); if (ch == '\n' || hasFinished) break; } if (hasFinished) return EOF; // By the time I exit the above loop there must be at least one character // in the line that has just been read, even if it is only the terminating // '\n'. inputP = 0; BOOL inPrompt = FALSE; for (;;) { ch = textBuffer[inputLineStart]; inputLineStart = (inputLineStart+1)&TEXT_MASK; // Here I quietly truncate very very long input lines. For my first attempt // I have inputBufferSize set at 2048. if ((ch&0xff) == CH_PROMPT) inPrompt = TRUE; if (inputP<inputBufferSize && !inPrompt) inputBuffer[inputP++] = ch; if ((ch&0xff) == CH_ENDPROMPT) inPrompt = FALSE; else if (ch == '\n') break; } inputBuffer[inputBufferSize-1] = '\n'; inputP = 0; ch = inputBuffer[inputP++]; if (ch == '\n') inputP = -1; // This buffer now all used up. return ch; } int cwin_getchar_nowait() { return theApp.mainWindow->cwin_getchar_nowait(); } void CMainWindow::cwin_discard_input() { typeAheadP1 = typeAheadP2 = 0; if (clipboardInput) free(clipboardInput); clipboardInput = 0; } void cwin_discard_input() { theApp.mainWindow->cwin_discard_input(); } void CMainWindow::cwin_set_prompt(const char *s) { strncpy(cwin_prompt_string, s, 31); cwin_prompt_string[31] = 0; } void cwin_set_prompt(const char *s) { theApp.mainWindow->cwin_set_prompt(s); } // The READ command on the menu will work by forcing the caret to the // end of the input buffer and simulating some typed in text which // contains a file-name. I will make a start at pretending to make it // configurable here. But there seem to be LOTS of things that need to be // changed to match the system that is using the window manager code... // I hope that eventually I will provide configuration calls so that the // grim detail here gets set from "user" code. The basic idea is that // readFileCommand holds a string to be generated, with a marker (%) somewhere // where the file-name should go. I need options to control processing // of the file-name to get around reader escape conventions etc. I also need // to tell the file selection dialog what sorts of files to look for... #define VERBATIM_FILENAME 0 #define DOUBLE_BACKSLASH 1 #define DOUBLE_DOUBLEQUOTE 2 #ifdef COMMON static char *readFileCommand = "(load \"%\")"; static int fileOptions = DOUBLE_BACKSLASH; static char *defaultExtension = "lsp"; static char *defaultFile = "*.lsp"; static char *fileNameFilter = "Lisp Files (*.lsp)|*.lsp|All Files (*.*)|*.*||\0"; #else static char *readFileCommand = "in \"%\";"; static int fileOptions = DOUBLE_DOUBLEQUOTE; static char *defaultExtension = "red"; static char *defaultFile = "*.RED;*.TST"; static char *fileNameFilter = "Reduce (*.red/*.tst)|*.RED;*.TST|Lisp Files (*.lsp)|*.LSP|All Files (*.*)|*.*||\0"; #endif void CMainWindow::OnRead() { OnEnd(); pageLine = caretLine; CFileDialog FDialog(TRUE, defaultExtension, defaultFile, OFN_FILEMUSTEXIST | OFN_HIDEREADONLY, fileNameFilter, NULL); FDialog.m_ofn.lpstrTitle = "Read File"; if (FDialog.DoModal() != IDOK) { cwin_ensure_screen(FALSE); // needed to re-position caret, it seems. return; } CString p = FDialog.GetPathName(); int nameLength = p.GetLength(); char *q = readFileCommand; int i = strlen(q); while (i-- != 0) { int ch = q[i]; if (ch == '%') { int j = nameLength; while (j-- != 0) { int ch1 = p[j]; switch (fileOptions) { case DOUBLE_BACKSLASH: if (ch1 == '\\') UnTypeAhead(ch1); break; case DOUBLE_DOUBLEQUOTE: if (ch1 == '"') UnTypeAhead(ch1); break; default:break; } UnTypeAhead(ch1); } } else UnTypeAhead(ch); } cwin_ensure_screen(FALSE); } #ifndef COMMON static char **library_name_p = NULL; static int library_name_n = 0; static char *library_name[] = { "algint", "applysym", "arnum", "assist", "avector", "boolean", "cali", "camal", "changevr", "compact", "complex", "crack", "cvit", "decompos", "defint", "desir", "dfpart", "dummy", "elem", "excalc", "fide", "fps", "gentran", "gnuplot", "groebner", "ideals", "ineq", "invbase", "laplace", "lie", "linalg", "modsr", "ncpoly", "normform", "numeric", "odesolve", "orthovec", "physop", "pmrules", "randpoly", "reacteqn", "residue", "rlfi", "rsolve", "scope", "sets", "spde", "specfn", "symmetry", "taylor", "tps", "tri", "trigsimp", "wu", "xcolor", "xideal", "zeilberg", "ztrans" }; void CMainWindow::OnLoadLibrary(UINT a) { OnEnd(); pageLine = caretLine; char q[100]; int i; if (library_name_p != NULL) { if (a-IDM_FIRSTLOAD >= library_name_n) return; sprintf(q, "load_package %s;\n", library_name_p[a - IDM_FIRSTLOAD]); } else { sprintf(q, "load_package %s;\n", library_name[a - IDM_FIRSTLOAD]); } i = strlen(q); while (i-- != 0) { int ch = q[i]; UnTypeAhead(ch); } cwin_ensure_screen(FALSE); } static char **switch_name_p = NULL; static int switch_name_n = 0; static struct { char *name; int status; } switch_name[] = { {"algint", 0}, {"adjprec", 0}, {"allbranch", 0}, {"allfac", 1}, {"arbvars", 0}, {"asterisk", 1}, {"backtrace", 0}, {"balanced_mod", 0}, {"bfspace", 0}, {"combineexpt", 0}, {"combinelogs", 0}, {"comp", 0}, {"complex", 0}, {"compxroots", 0}, {"cramer", 0}, {"cref", 0}, {"defn", 0}, {"demo", 0}, {"dfprint", 0}, {"div", 0}, {"echo", 0}, {"errcont", 0}, {"evallhseqp", 0}, {"exp", 1}, {"expandexpt", 1}, {"expandlogs", 0}, {"ezgcd", 0}, {"factor", 0}, {"fastfor", 0}, {"force", 0}, {"fort", 0}, {"fortupper", 0}, {"fullprec", 0}, {"fullprecision", 0}, {"fullroots", 0}, {"gc", 0}, {"gcd", 0}, {"heugcd", 0}, {"horner", 0}, {"ifactor", 0}, {"int", 0}, {"intstr", 0}, {"lcm", 1}, {"lessspace", 0}, {"limitedfactors", 0}, {"list", 0}, {"listargs", 0}, {"lower", 1}, {"mcd", 1}, {"modular", 0}, {"msg", 1}, {"multiplicities", 0}, {"nat", 1}, {"nero", 0}, {"noarg", 1}, {"noconvert", 0}, {"nonlnr", 0}, {"nosplit", 1}, {"numval", 1}, {"output", 1}, {"period", 1}, {"pgwd", 0}, {"plap", 0}, {"precise", 1}, {"pret", 0}, {"pri", 1}, {"pwrds", 1}, {"quotenewnam", 1}, {"raise", 0}, {"rat", 0}, {"ratarg", 0}, {"rational", 0}, {"rationalize", 0}, {"ratpri", 1}, {"reduced", 0}, {"revpri", 0}, {"rlisp88", 0}, {"rootmsg", 0}, {"roundall", 1}, {"roundbf", 0}, {"rounded", 0}, {"savestructr", 0}, {"solvesingular", 0}, {"time", 0}, {"trallfac", 0}, {"trfac", 0}, {"trint", 0}, {"trroot", 0} }; void CMainWindow::OnSwitch(UINT a) { OnEnd(); pageLine = caretLine; char q[100]; int i; if (switch_name_p != NULL) { int n = a - IDS_FIRSTSWITCH; if (n >= switch_name_n) return; i = switch_name_p[n][0] == 'y'; switch_name_p[n][0] ^= ('y' ^ 'n'); // Note well - if the user types "on xxx" or "off xxx" directly rather than // by using this mechanism then the checked or otherwise status of the menu // items will get out of step. Tough luck. If you use the menu to set or clear // a switch the switch will certainly be brought into line with what the // menu information indicates. GetMenu()->CheckMenuItem(a, MF_BYCOMMAND | (i ? MF_UNCHECKED : MF_CHECKED)); DrawMenuBar(); sprintf(q, "%s %s;\n", i ? "off" : "on", 1+switch_name_p[n]); } else { i = switch_name[a - IDS_FIRSTSWITCH].status; switch_name[a - IDS_FIRSTSWITCH].status = !i; // Note well - if the user types "on xxx" or "off xxx" directly rather than // by using this mechanism then the checked or otherwise status of the menu // items will get out of step. Tough luck. If you use the menu to set or clear // a switch the switch will certainly be brought into line with what the // menu information indicates. GetMenu()->CheckMenuItem(a, MF_BYCOMMAND | (i ? MF_UNCHECKED : MF_CHECKED)); DrawMenuBar(); sprintf(q, "%s %s;\n", i ? "off" : "on", switch_name[a - IDS_FIRSTSWITCH].name); } i = strlen(q); while (i-- != 0) { int ch = q[i]; UnTypeAhead(ch); } cwin_ensure_screen(FALSE); } #endif void CMainWindow::OnExit() { pageLine = caretLine; DestroyWindow(); } int cwin_interrupt_pending = 0; void CMainWindow::OnInterrupt() { pageLine = caretLine; cwin_interrupt_pending = 1; cwin_ensure_screen(FALSE); } void CMainWindow::OnBacktrace() { pageLine = caretLine; cwin_interrupt_pending = 3; cwin_ensure_screen(FALSE); } void CMainWindow::OnPageMode() { pageLine = caretLine; pageMode = !pageMode; GetMenu()->CheckMenuItem(IDM_PAGEMODE, MF_BYCOMMAND | (pageMode ? MF_CHECKED : MF_UNCHECKED)); DrawMenuBar(); cwin_ensure_screen(FALSE); } #ifdef GRAPHICS_WINDOW void CMainWindow::OnGraphics() { graphicsWindow->ShowWindow(graphicsShown ? SW_HIDE : SW_SHOW); if (graphicsShown) graphicsWindow->viewpointWindow.ShowWindow(SW_HIDE); graphicsShown = !graphicsShown; if (graphicsShown) graphicsWindow->Invalidate(); GetMenu()->CheckMenuItem(IDM_GRAPHICS, MF_BYCOMMAND | (graphicsShown ? MF_CHECKED : MF_UNCHECKED)); DrawMenuBar(); cwin_ensure_screen(FALSE); } void CMainWindow::OnGraphics1(UINT a, LONG b) { OnGraphics(); } #endif void CMainWindow::ReWriteTitleText() { if (hasFinished) return; CWindowDC dc(this); // It is not (at present) clear to me how to decide how wide to make // the title. What follows is some sort of a guess. CRect clientArea; GetClientRect(&clientArea); int wTitle = clientArea.Width() - 3*::GetSystemMetrics(SM_CXVSCROLL); int wLeft = dc.GetTextExtent(cLeft, strlen(cLeft)).cx; int wMid = dc.GetTextExtent(cMid, strlen(cMid)).cx; int wRight = dc.GetTextExtent(cRight, strlen(cRight)).cx; // For the calculations about padding that I use here to work the font used // in the title bar had better not do any kerning and overhang-effects must // not interfere. I find I have all sorts of horrid problems if I try to // use regular blank characters for padding, but '\xa0' displays as blank // space and is better behaved. It also seems (???) that at least under // Windows 3.1 there is no great joy in trying to use caption strings that // are longer than 78 characters... #define PADDING_CHAR '\xa0' char strSp[4]; strSp[0] = PADDING_CHAR; int wSp = dc.GetTextExtent(strSp, 1).cx; int cw = wTitle / wSp; // I first measure things on the supposition that I would allow up to // 90 characters in the title. After balancing it a bit I cut that // down to the 78 that Windows 3.1 seems to be prepared to tolerate. if (cw > 90) wTitle = 90*wSp; int pad = (wTitle - wMid)/2; char *l = cLeft, *r = cRight; for (;;) { int padLeft = (pad - wLeft) / wSp; int padRight = (pad - wRight) / wSp; int excess = strlen(cLeft) + padLeft + strlen(cMid) + padRight + strlen(cRight) - 78; if (excess > 0) { if (excess & 1) { if (padLeft > padRight) padLeft--; else padRight--; excess--; } excess /= 2; padLeft -= excess; padRight -= excess; } #ifdef DISCARD_LEFT_IF_TITLE_OVERFULL if (padLeft <= 0 && padRight <= 0) { strcpy(mainTitle, cMid); // Abandon both right & left items break; } #else // Here even though there is no room to display the whole text I wanted // on the title bar (maybe the window had been iconized?) I will display // the left & mid parts with a single blank between. if (padLeft <= 0 && padRight <= 0) { sprintf(mainTitle, "%s%c%s", cLeft, PADDING_CHAR, cMid); break; } #endif else { #ifdef DISCARD_LEFT_IF_TITLE_OVERFULL if (padLeft <= 0 && wLeft != 0) { l = ""; // Abandon left item & re-try wLeft = 0; continue; } #endif if (padRight <= 0 && wRight != 0) // Abandon right item & re-try { r = ""; wRight = 0; continue; } char *p = mainTitle; while (*l != 0) *p++ = *l++; for (int i=0; i<padLeft; i++) *p++ = PADDING_CHAR; l = cMid; while (*l != 0) *p++ = *l++; for (i=0; i<padRight; i++) *p++ = PADDING_CHAR; while (*r != 0) *p++ = *r++; *p = 0; break; } } SetWindowText(mainTitle); } void CMainWindow::cwin_report_left(const char *msg) { // I will take this opportunity to make the "To-File" menu item track // whether there really is a spool file active. This only effects how // the menu gets displayed, not what it does... if (complete) { GetMenu()->ModifyMenu(IDM_TOFILE+MF_STRING, MF_BYCOMMAND, IDM_TOFILE, spool_file==NULL ? "&To File ..." : "&Terminate log"); DrawMenuBar(); } if (msg == NULL) { leftSetByUser = FALSE; return; // Date and time of day will appear } strncpy(cLeft, msg, 31); cLeft[31] = 0; ReWriteTitleText(); leftSetByUser = TRUE; } void cwin_report_left(const char *s) { theApp.mainWindow->cwin_report_left(s); } void CMainWindow::cwin_report_mid(const char *msg) { if (msg == NULL) msg = programName; strncpy(cMid, msg, 31); cLeft[31] = 0; ReWriteTitleText(); } void cwin_report_mid(const char *s) { theApp.mainWindow->cwin_report_mid(s); } void CMainWindow::cwin_report_right(const char *msg) { if (msg == NULL) msg = ""; strncpy(cRight, msg, 31); cRight[31] = 0; ReWriteTitleText(); } void cwin_report_right(const char *s) { theApp.mainWindow->cwin_report_right(s); } void CMainWindow::cwin_display_date() { char dateBuffer[20]; time_t t0 = time(NULL); char *m = ctime(&t0); #ifdef OLD_AND_USES_UNIVERSAL_TIME_WHICH_IS_SILLY sprintf(dateBuffer, "%02d-%.3s-%02d, %02d:%02d:%02d", titleUpdateTime.wDay, "xxxJanFebMarAprMayJunJulAugSepOctNovDec"+3*titleUpdateTime.wMonth, titleUpdateTime.wYear%100, titleUpdateTime.wHour, titleUpdateTime.wMinute, titleUpdateTime.wSecond); #else sprintf(dateBuffer, "%.2s-%.3s-%.2s, %.8s", m+8, m+4, m+22, m+11); #endif cwin_report_left(dateBuffer); leftSetByUser = FALSE; } void cwin_menus(char **packages, char **switches) { theApp.mainWindow->cwin_menus(packages, switches); } void CMainWindow::cwin_menus(char **packages, char **switches) { CMenu *main = GetMenu(); // main menu bar for this window if (packages != NULL && *packages != NULL) { library_name_p = packages; main->DeleteMenu(4, MF_BYPOSITION); int n = IDM_FIRSTLOAD; CMenu mload; mload.CreatePopupMenu(); int firstletter = 'a'; int lastletter = 'a', nextletter; int count = 0, nextcount; char **p = packages; library_name_n = 0; while (*p++ != NULL) library_name_n++; p = packages; while (*p && **p == lastletter) count++, p++; char **p1 = p; while (*packages) { for (;;) { nextcount = 0; nextletter = lastletter + 1; while (*p && **p == nextletter) nextcount++, p++; if (count + nextcount > 20) break; lastletter = nextletter; count += nextcount; p1 = p; if (lastletter == 'z') break; } char subname[8]; if (firstletter == lastletter) sprintf(subname, "%c", firstletter); else sprintf(subname, "%c-%c", firstletter, lastletter); CMenu sub1; sub1.CreatePopupMenu(); while (packages != p1) sub1.AppendMenu(MF_STRING, n++, *packages++); mload.AppendMenu(MF_STRING | MF_POPUP, (UINT)sub1.Detach(), subname); firstletter = lastletter = nextletter; count = nextcount; p1 = p; } main->InsertMenu(4, MF_BYPOSITION | MF_POPUP | MF_STRING, (UINT)mload.Detach(), "&Load"); } // Now do roughly the same with switches if (switches != NULL && *switches != NULL) { switch_name_p = switches; main->DeleteMenu(5, MF_BYPOSITION); int n = IDS_FIRSTSWITCH; CMenu mload; mload.CreatePopupMenu(); int firstletter = 'a'; int lastletter = 'a', nextletter; int count = 0, nextcount; char **p = switches; switch_name_n = 0; while (*p++ != NULL) switch_name_n++; p = switches; while (*p && (*p)[1] == lastletter) count++, p++; char **p1 = p; while (*switches) { for (;;) { nextcount = 0; nextletter = lastletter + 1; while (*p && (*p)[1] == nextletter) nextcount++, p++; if (count + nextcount > 20) break; lastletter = nextletter; count += nextcount; p1 = p; if (lastletter == 'z') break; } char subname[8]; if (firstletter == lastletter) sprintf(subname, "%c", firstletter); else sprintf(subname, "%c-%c", firstletter, lastletter); CMenu sub1; sub1.CreatePopupMenu(); while (switches != p1) { sub1.AppendMenu(MF_STRING, n, 1+*switches); sub1.CheckMenuItem(n, MF_BYCOMMAND | (**switches=='y' ? MF_CHECKED : MF_UNCHECKED)); n++; switches++; } mload.AppendMenu(MF_STRING | MF_POPUP, (UINT)sub1.Detach(), subname); firstletter = lastletter = nextletter; count = nextcount; p1 = p; } main->InsertMenu(5, MF_BYPOSITION | MF_POPUP | MF_STRING, (UINT)mload.Detach(), "&Switch"); } DrawMenuBar(); } // When the user types in a key all that happens (to start with) is that // I store it in a buffer. If the buffer gets to be full I beep and // ignore any further characters. #define Ctrl(x) ((x) & 0x1f) void CMainWindow::OnChar(UINT ch, UINT nRepCnt, UINT nFlags) { // I turn the ENTER key into a newline character (and consequently ^M will // also go that way). I also discard 0x7f (for the delete/erase key) and // anything in the range 0x80 to 0xa0 (which I reserve for my own private // use in the text buffer). I ignore the repetition count. // I also have to process some accelerators. pageLine = caretLine; // The following line is in effect following a request from F J Wright. He // pointed out that the character that gets pressed when one is in page mode // to un-pause the screen should be allowed to do just that and should not // also appear as an input character. So here if I find a character being // typed when things are paused I just lose it (as well as clearing the // flags that pause things. if (pagePaused) { pagePaused = FALSE; return; } inject_randomness(ch); inject_randomness((int)time(NULL)); switch (ch) { case Ctrl('C'): ToIcaret(); caretVisible = TRUE; cwin_ensure_screen(FALSE); // may jump screen to caret location OnInterrupt(); return; // I make Ctrl+D and Ctrl+Z exit, but note very well that they both // exit from this system super-promptly when the key is typed, and // they do NOT wait until the program gets around to reading them. case Ctrl('D'): // Some Unix users may be used to Ctrl+D as EOF? OnExit(); return; case Ctrl('G'): ToIcaret(); caretVisible = TRUE; cwin_ensure_screen(FALSE); // may jump screen to caret location OnBacktrace(); return; case Ctrl('I'): // TAB (I need to worry about the display of tabs!) break; case Ctrl('J'): // LINEFEED ch = '\n'; break; case Ctrl('L'): OnRedraw(); return; case Ctrl('M'): // CARRIAGE RETURN ch = '\n'; case Ctrl('N'): OnEnd(); // so that CR accepts the line always break; case Ctrl('O'): OnCopy(); return; case Ctrl('Q'): if (pageMode) OnPageMode(); return; case Ctrl('R'): OnReInput(); return; case Ctrl('S'): if (!pageMode) OnPageMode(); return; case Ctrl('V'): OnPaste(); return; case Ctrl('X'): OnCut(); return; case Ctrl('Z'): // Some PC users may be used to Ctrl+Z as EOF? OnExit(); return; case 0x1b: // ESC goes through to the user break; default: if ((ch & 0x7f) < 0x20) return; // Discard control chars. break; } CancelSelection(); ToIcaret(); caretVisible = TRUE; cwin_ensure_screen(FALSE); // If the caret is NOT at the end of the text then the insert operation I do // here goes directly into the text buffer. If the caret IS at the end of the // text I push the character into a type-ahead buffer, and beep if there // was no room for it there. if (caretChar != textLast) { if (insertMode) cwin_caret_putchar(ch); else cwin_caret_replacechar(ch); } else { int p2 = (typeAheadP2 + 1) & typeAheadBufferMask; if (p2 == typeAheadP1) { ::MessageBeep(0xffffffff); return; } typeAheadBuffer[typeAheadP2] = ch; typeAheadP2 = p2; } } void CMainWindow::ReplaceLastLine(unsigned char *s) { typeAheadP1 = typeAheadP2 = 0; // cancel type-ahead selRootValid = FALSE; OnEnd(); caretVisible = TRUE; // The following loop deletes characters from the last line of the input, // until what is left is (a) a totally empty buffer, (b) a buffer where the // last line is empty or (c) the final thing in the buffer is a prompt // item. While doing the deletion I will not update the screen at all. while (textFirst != textLast && lineBuffer[lineLast].address != textLast) { int n = (textLast-1) & TEXT_MASK; if ((textBuffer[n]&0xff)==CH_ENDPROMPT) break; if (textLast == caretChar) caretChar = n; if (textLast == icaretChar) icaretChar = n; if (textLast == inputLineStart) inputLineStart = n; textLast = n; } int i=savedLines[currentInputLine]; for (;;) { int ch = savedChars[i++]; if (ch == '\n') break; if (i == MAX_SAVED_CHARS) i = 0; cwin_putchar(ch); } CRect cr(0, LineY(lineLast), clientWidth, LineY(lineLast)+LineDY(lineLast)); InvalidateRect(&cr); LineSizes(); UpdateWindow(); cwin_ensure_screen(FALSE); } void CMainWindow::OnKeyDown(UINT ch, UINT nRepCnt, UINT nFlags) { CPoint cp; int saveX, saveY; pageLine = caretLine; switch (ch) { // The INS key flips the status with respect to insert mode. I make this // TRUE at the start of a run, and any time anybody does a PASTE operation. case VK_INSERT: insertMode = !insertMode; return; // I make the DEL key delete forwards by first simulating a right-arrow // cursor movement effect and then performing the sort of deletion that // I consider ordinary. case VK_DELETE: ToIcaret(); caretVisible = TRUE; cwin_ensure_screen(FALSE); if (selStartChar != selEndChar) { DeleteSelection(); return; } if (caretChar == textLast) return; OnKeyDown(VK_RIGHT, 1, nFlags); if (caretChar != textLast) { cwin_caret_unputchar(); return; } if (typeAheadP1 == typeAheadP2) { typeAheadBuffer[typeAheadP2] = 0x7f; typeAheadP2 = (typeAheadP2 + 1) & typeAheadBufferMask; return; } typeAheadP2 = (typeAheadP2 - 1) & typeAheadBufferMask; return; case VK_BACK: ToIcaret(); caretVisible = TRUE; cwin_ensure_screen(FALSE); // may jump screen to caret location // If there is a selection valid then DELETE will delete it, without any // regard for where the caret is (although the caret will generally be // at one or other end of the selected region). if (selStartChar != selEndChar) { DeleteSelection(); return; } // If the caret is NOT at the end of the text then DELETE has to work // inside the buffer. This will typically make it a fairly expensive // operation. if (caretChar != textLast) { cwin_caret_unputchar(); return; } // If there are no characters typed ahead I will put 0x7f into the // input buffer. This case can arise in two different circumstances. The // first is if a user types DELETE as the first character on a line. Then // I will want to discard the junk characters later on. Otherwise it may be // that the current line is partly displayed and is subject to interactive // editing and then the 0x7f put in the buffer will be found again very // soon and used to cause deletion of some prior character. Because of this // latter case I will not beep on initial DELETE characters on a line. if (typeAheadP1 == typeAheadP2) { typeAheadBuffer[typeAheadP2] = 0x7f; typeAheadP2 = (typeAheadP2 + 1) & typeAheadBufferMask; return; } // If there are typed-ahead characters I can just discard the most recent. At // this stage the character has not been displayed (eg a user typed XXX DEL // before the application was ready to look at it) so the removal is really // very easy. typeAheadP2 = (typeAheadP2 - 1) & typeAheadBufferMask; return; // The various keys like "PAGE UP" etc do just what the scroll bar can do. case VK_PRIOR: OnVScroll(SB_PAGEUP, 0, NULL); return; case VK_NEXT: OnVScroll(SB_PAGEDOWN, 0, NULL); return; case VK_HOME: OnHome(); return; case VK_END: OnEnd(); return; // The cursor arrow keys are funny here, with behaviour inspired by the // DOSKEY history program. Right and left movement moves the caret, but // up and down will bring back one of a number of stored input line. case VK_UP: // If my caret is on the last line I will do something DOSKEY-ish... if (caretLine == lineLast) { if (currentInputLine == savedP1) return; if (currentInputLine == -1) currentInputLine = savedP2; if (currentInputLine == 0) currentInputLine = MAX_SAVED_LINES; currentInputLine--; ReplaceLastLine(&savedChars[savedLines[currentInputLine]]); return; } if (caretLine == lineFirst) return; if (caretLine == lineVisible) OnVScroll(SB_LINEUP, 0, NULL); cp.x = caretX-xOffset; cp.y = caretY - 1; FindMouseChar(cp); caretVisible = TRUE; cwin_ensure_screen(FALSE); return; case VK_DOWN: // If my caret is on the last line I will do something DOSKEY-ish... if (caretLine == lineLast) { int n = currentInputLine + 1; if (n == MAX_SAVED_LINES) n = 0; if (currentInputLine == -1 || n == savedP2) return; currentInputLine = n; ReplaceLastLine(&savedChars[savedLines[currentInputLine]]); return; } cp.x = caretX-xOffset; saveY = caretY, saveX = caretX; cp.y = caretY + LineDY(caretLine) + 1; // The case when I am at the bottom of the screen and hit the "DOWN" key // is a bit of a mess. I call FindMouse Char as usual to try to re-position // the caret. If that would be outside the window I get a flag set to tell me // so. In that case I re-position the caret where it just came from, then // scroll the window, and finally repeat my attempt to re-position the // caret. FindMouseChar(cp); if (mouseOutside & 8) { cp.x = saveX-xOffset; cp.y = saveY + 1; FindMouseChar(cp); OnVScroll(SB_LINEDOWN, 0, NULL); cp.x = caretX-xOffset; cp.y = caretY + LineDY(caretLine) + 1; FindMouseChar(cp); } caretVisible = TRUE; cwin_ensure_screen(FALSE); return; case VK_LEFT: caretVisible = TRUE; if (textFirst == caretChar) return; // at start of buffer? if (lineBuffer[caretLine].address == caretChar) // back over a newline? { int n = (caretLine-1)&LINE_MASK; caretX = lineBuffer[n].width; caretLine=n; n = lineBuffer[n].address; while (textBuffer[n]!='\n') n = (n+1)&TEXT_MASK; caretChar = n; } else { int n = (caretChar-1) & TEXT_MASK; int w = windowFonts.HCourier.across['X']; if ((textBuffer[n]&0xff)==CH_ENDPROMPT) // move over whole of a prompt { do { n = (n-1)&TEXT_MASK; caretX -= w; } while (n!=textFirst && (textBuffer[n]&0xff)!=CH_PROMPT); caretChar = n; if (lineBuffer[caretLine].address == caretChar) // back over a newline? { n = (caretLine-1)&LINE_MASK; caretX = lineBuffer[n].width; caretLine=n; n = lineBuffer[n].address; while (textBuffer[n]!='\n') n = (n+1)&TEXT_MASK; caretChar = n; } } else caretX -= w; if (caretLine == lineLast) icaretChar = n, icaretLine = lineLast; caretChar = n; // The amount of invalidation done here runs across to the end of the // last line, and that ought to get rid of any residues of the old // caret. I use an APPROXIMATION to the new value of caretX, but hope that // that gets corrected during the re-painting. CRect cr(caretX-xOffset, LineY(caretLine), clientWidth, LineY(caretLine)+LineDY(caretLine)); InvalidateRect(&cr); // Should I hide the caret here? } UpdateWindow(); cwin_ensure_screen(FALSE); return; case VK_RIGHT: caretVisible = TRUE; if (textLast == caretChar) return; // at end of buffer? if (textBuffer[caretChar] == '\n') // at a newline? { caretX = 0; int w = windowFonts.HCourier.across['X']; caretLine = (caretLine+1)&LINE_MASK; caretChar = lineBuffer[caretLine].address; if (caretChar!=textLast && (textBuffer[caretChar]&0xff)==CH_PROMPT) // move over whole of a prompt { while (caretChar!=textLast && (textBuffer[caretChar]&0xff)!=CH_ENDPROMPT) { caretChar = (caretChar+1)&TEXT_MASK; caretX += w; } caretX -= w; if (caretChar!=textLast) caretChar = (caretChar+1)&TEXT_MASK; } CPoint caretPos(caretX-xOffset, caretY); SetCaretPos(caretPos); if (caretLine == lineLast) { icaretLine = caretLine; icaretChar = caretChar; icaretX = caretX; } } else { int n = caretChar; int oldCaretX = caretX; int w = windowFonts.HCourier.across['X']; n = (n+1)&TEXT_MASK; caretX += w; if (caretLine == lineLast) icaretChar = n, icaretLine = caretLine; caretChar = n; CRect cr(oldCaretX-xOffset, LineY(caretLine), caretX-xOffset+w, LineY(caretLine)+LineDY(caretLine)); InvalidateRect(&cr); // Should I hide the caret here? } UpdateWindow(); cwin_ensure_screen(FALSE); return; } } // The next function takes a mouse position and identifies the character in // the text buffer that it identifies. It must be kept well in step with // the corresponding code in PaintTextLine(). int CMainWindow::FindMouseChar(CPoint point) { // I may have captured the mouse, in which case the coordinates returned may // be outside my client area. Clip them to it before doing anything else. int x = point.x, y = point.y; int nCount = 0; mouseOutside = 0; if (x<0) x=0, mouseOutside |= 1; else if (x>clientWidth) x=clientWidth, mouseOutside |= 2; if (y<0) y=0, x=0, mouseOutside |= 4; else if (y>clientHeight) y=clientHeight, x=clientWidth, mouseOutside |= 8; if (mouseOutside == 0) selectScrollSpeed = 0; int yOffset = lineBuffer[lineVisible].position; int line; for (line=lineVisible;;line=(line+1)&LINE_MASK) { int y1 = lineBuffer[line].position - yOffset; int y2 = y1 + lineBuffer[line].height; if (y>=y1 && y<=y2) break; if (line==lineLast) break; } // Now I have identified the line that the mouse cursor is on. int textChar = lineBuffer[line].address; int activeFont = CH_COURIER; unsigned char *activeWidths = windowFonts.HCourier.across; int xc = -xOffset; int inPrompt = -1, inPromptX; for (;;textChar=(textChar+1)&TEXT_MASK) { if (textChar==textLast) break; int c = textBuffer[textChar]; int w = activeWidths[c & 0xff]; switch (c & 0xff) { case '\n': break; case CH_PROMPT: inPrompt = textChar, inPromptX = xc; continue; case CH_ENDPROMPT: inPrompt = -1; continue; case CH_RED: // case CH_BLUE: // case CH_BLACK: case CH_GRAY: continue; case CH_COURIER: activeFont = c; activeWidths = windowFonts.HCourier.across; continue; case CH_ROMAN: activeFont = c; activeWidths = windowFonts.HRoman.across; continue; case CH_BOLD: activeFont = c; activeWidths = windowFonts.HBold.across; continue; case CH_ITALIC: activeFont = c; activeWidths = windowFonts.HItalic.across; continue; case CH_SYMBOL: activeFont = c; activeWidths = windowFonts.HSymbol.across; continue; case CH_Roman: activeFont = c; activeWidths = windowFonts.Hroman.across; continue; case CH_Bold: activeFont = c; activeWidths = windowFonts.Hbold.across; continue; case CH_Italic: activeFont = c; activeWidths = windowFonts.Hitalic.across; continue; case CH_Symbol: activeFont = c; activeWidths = windowFonts.Hsymbol.across; continue; default: if (c == '\t') { c = ' '; w = currentWidths[' ']*(8-(nCount&7)); nCount = 7; } else if (c < 32) w = activeWidths['^'] + activeWidths[c & 0xff], nCount++; if (x <= xc+w/2) { // I will not allow the user to select a position within a prompt, since // if I did it would make it possible to CUT or DELete part of the prompt // string, including either the control character that marked its start or // its end. The result would be that the status of the text as "prompt" would // be lost with possible consequent confusion. if (inPrompt>=0) textChar=inPrompt, xc=inPromptX; break; // Bingo! Found it. } xc += w; nCount++; continue; } break; } // If the mouse click moves me to the last line then I place the // input caret there. Otherwise if a previous caret had been on the last line // I leave the input caret where the previous caret had been. if (line == lineLast) { icaretChar = textChar; icaretX = xc+xOffset; icaretFontWidths = activeWidths; icaretLine = line; } else if (caretLine == lineLast) { icaretChar = caretChar; icaretX = caretX; icaretFontWidths = caretFontWidths; icaretLine = caretLine; } caretChar = textChar; caretX = xc+xOffset; caretFontWidths = activeWidths; caretLine = line; inject_randomness(caretX); inject_randomness(caretLine); // In the case that mouse activity re-positions the caret I will move the // caret on the screen directly to where it needs to be shown. No benefit // would come from delaying until a future re-paint operation. HideCaret(); CPoint caretPos(caretX-xOffset, caretY); SetCaretPos(caretPos); ShowCaret(); return caretChar; } // StartSelection is slightly curious in that it sets the selection // to be empty but rooted at a specific point. The effect will be that // a subsequent MouseMove or shift-click can extend the selection to // be non-trivial. The ugly feature of this is that a selection that has // been "started" in this way could suffer if the contents of the text buffer // moved. To prevent trouble I will keep a flag that indicates when I have // a selection starting-point that I will count as valid. I will set this // flag here and clear it anywhere where I move or destroy bits of the // buffer. void CMainWindow::StartSelection() { CancelSelection(); selFirstChar = selStartChar = selEndChar = caretChar; selFirstX = selStartX = selEndX = caretX; selFirstLine = selStartLine = selEndLine = caretLine; selRootValid = TRUE; } void CMainWindow::InvalidateSelection(int l1, int x1, int l2, int x2) { int w; if (l1 > l2) { w=l1, l1=l2, l2=w, w=x1, x1=x2, x2=w; if (x2 == 0) l2=(l2-1)&LINE_MASK, x2=clientWidth+xOffset; } HideCaret(); if (l1==l2) { if (x1>x2) w=x1, x1=x2, x2=w; CRect r(x1-xOffset, LineY(l1), x2+caretWidth-xOffset, LineY(l1)+LineDY(l1)); if (x1 != x2) InvalidateRect(&r); } else { CRect r(0, LineY(l1), clientWidth, LineY(l2)+LineDY(l2)); InvalidateRect(&r); } ShowCaret(); } void CMainWindow::ExtendSelection() { int oldStart = selStartChar, oldEnd = selEndChar; if (selFirstChar == selStartChar) InvalidateSelection(selEndLine, selEndX, caretLine, caretX); else InvalidateSelection(selStartLine, selStartX, caretLine, caretX); if (betweenChar(textFirst, caretChar, selFirstChar)) { selStartChar = caretChar; selStartX = caretX; selStartLine = caretLine; selEndChar = selFirstChar; selEndX = selFirstX; selEndLine = selFirstLine; } else { selStartChar = selFirstChar; selStartX = selFirstX; selStartLine = selFirstLine; selEndChar = caretChar; selEndX = caretX; selEndLine = caretLine; } if (selStartChar==oldStart && selEndChar==oldEnd) return; } void CMainWindow::CancelSelection() { InvalidateSelection(selStartLine, selStartX, selEndLine, selEndX); selFirstChar = selStartChar = selEndChar = caretChar; selFirstX = selStartX = selEndX = caretX; selFirstLine = selStartLine = selEndLine = caretLine; } void CMainWindow::OnLButtonDblClk(UINT nFlags, CPoint point) { } void CMainWindow::OnLButtonDown(UINT nFlags, CPoint point) { /* * The idea here is that a first click within a window should NOT move * the caret or start a new selection, since it was probably (?) the * mouse event that just caused the window to become active again. */ if (pagePaused || hasFocus) return; FindMouseChar(point); trackingSelection = TRUE; SetCapture(); selectScrollSpeed = 0; if (nFlags & MK_SHIFT && selRootValid) ExtendSelection(); else StartSelection(); } void CMainWindow::OnLButtonUp(UINT nFlags, CPoint point) { if (hasFocus) { hasFocus = FALSE; return; } if (pagePaused) pageLine = caretLine; else { FindMouseChar(point); ::ReleaseCapture(); if (trackingSelection) ExtendSelection(); trackingSelection = FALSE; selectScrollSpeed = 0; } } void CMainWindow::OnMButtonDblClk(UINT nFlags, CPoint point) { } void CMainWindow::OnMButtonDown(UINT nFlags, CPoint point) { } void CMainWindow::OnMButtonUp(UINT nFlags, CPoint point) { if (hasFocus) { hasFocus = FALSE; return; } pageLine = caretLine; } void CMainWindow::OnRButtonDblClk(UINT nFlags, CPoint point) { } void CMainWindow::OnRButtonDown(UINT nFlags, CPoint point) { } void CMainWindow::OnRButtonUp(UINT nFlags, CPoint point) { if (hasFocus) { hasFocus = FALSE; return; } pageLine = caretLine; } void CMainWindow::OnMouseMove(UINT nFlags, CPoint point) { inject_randomness((int)clock()); if (trackingSelection && !pagePaused) { FindMouseChar(point); if (mouseOutside != 0) { if (selectScrollSpeed == 0) selectScrollSpeed = 7, selectScrollCount = 1; selectScrollDirection = mouseOutside; selectScrollPoint = point; OnTimer(1); } else ExtendSelection(); } } void CMainWindow::OnTimer(UINT timerId) { if (selectScrollSpeed == 0) return; selectScrollCount--; if (selectScrollCount > 0) return; if (selectScrollDirection == 0) return; if (selectScrollDirection & 8) OnVScroll(SB_LINEDOWN, 0, NULL); else if (selectScrollDirection & 4) OnVScroll(SB_LINEUP, 0, NULL); else if (selectScrollDirection & 1 && xOffset!=0) OnHScroll(SB_LINELEFT, 0, NULL); else if (selectScrollDirection & 2 && caretX < lineBuffer[caretLine].width) OnHScroll(SB_LINERIGHT, 0, NULL); selectScrollCount = selectScrollSpeed; if (selectScrollSpeed > 1) selectScrollSpeed--; FindMouseChar(selectScrollPoint); ExtendSelection(); } void CMainWindow::OnNcLButtonDown(UINT nFlags, CPoint point) { } void CMainWindow::OnNcMButtonDown(UINT nFlags, CPoint point) { } void CMainWindow::OnNcRButtonDown(UINT nFlags, CPoint point) { } // // Now the things provoked from the menus... // // SAVEAS will dump everything that is in the text buffer into a // log file. The status of prompts and any other funny stuff will not // concern me here - I will just dump out bytes as they appear in the // text buffer. But at least I will arrange that I do not dump any characters // that have a code >= 0x80 into the saved file. void CMainWindow::OnSaveAs() { CFileDialog FDialog(FALSE, "LOG", "savefile.log", OFN_HIDEREADONLY, "Log Files (*.LOG)|*.LOG|All Files (*.*)|*.*||", NULL); FDialog.m_ofn.lpstrTitle = "Save As"; if (FDialog.DoModal() != IDOK) return; FILE *ofile = fopen(FDialog.GetPathName(), "w"); if (ofile == NULL) { DisplayMsg("Could not write to file"); cwin_ensure_screen(FALSE); return; } for (int i=lineFirst; i!=lineLast; i=(i+1)&LINE_MASK) { int p = lineBuffer[i].address, c; while ((c = textBuffer[p]) != '\n') { if ((c & 0x80) == 0) putc(c, ofile); p = (p+1) & TEXT_MASK; } putc('\n', ofile); } int p = lineBuffer[lineLast].address; while (p != textLast) { int c = textBuffer[p]; if ((c & 0x80) == 0) putc(c, ofile); p = (p+1) & TEXT_MASK; } putc('\n', ofile); // extra newline at end to be tidy. fclose(ofile); cwin_ensure_screen(FALSE); } void CMainWindow::OnSaveSel() { CFileDialog FDialog(FALSE, "LOG", "savesel.log", OFN_HIDEREADONLY, "Log Files (*.LOG)|*.LOG|All Files (*.*)|*.*||", NULL); FDialog.m_ofn.lpstrTitle = "Save Selection"; if (FDialog.DoModal() != IDOK) return; FILE *ofile = fopen(FDialog.GetPathName(), "w"); if (ofile == NULL) { DisplayMsg("Could not write to file"); cwin_ensure_screen(FALSE); return; } // print selected region... int l1 = selStartLine; int cp = selStartChar; for (;;) { while (cp!=selEndChar && textBuffer[cp]!='\n') { int c = textBuffer[cp]; if ((c & 0x80) == 0) putc(c, ofile); cp = (cp+1)&TEXT_MASK; } if (cp==selEndChar) break; putc('\n', ofile); l1 = (l1+1)&LINE_MASK; cp = lineBuffer[l1].address; } fclose(ofile); cwin_ensure_screen(FALSE); } void CMainWindow::OnToFile() { // When I have a log file active I will make the menu entry into // one that closes the transcript, while if I do not have a file in // use the File/&T menu entry will start one off. // // NOTE that the "spool_file" mentioned here belongs somewhere in CSL, // and is not in the "cwin" code in any more direct way. if (spool_file != NULL) { fprintf(spool_file, "\n+++ End of transcript +++\n"); fclose(spool_file); spool_file = NULL; GetMenu()->ModifyMenu(IDM_TOFILE+MF_STRING, MF_BYCOMMAND, IDM_TOFILE, "&To File..."); DrawMenuBar(); cwin_ensure_screen(FALSE); return; } CFileDialog FDialog(FALSE, "LOG", "logfile.log", OFN_HIDEREADONLY, "Log Files (*.LOG)|*.LOG|All Files (*.*)|*.*||", NULL); FDialog.m_ofn.lpstrTitle = "Transcript File"; if (FDialog.DoModal() != IDOK) return; FILE *ofile = fopen(FDialog.GetPathName(), "w"); if (ofile == NULL) { DisplayMsg("Could not write to file"); cwin_ensure_screen(FALSE); return; } spool_file = ofile; time_t t0 = time(NULL); fprintf(spool_file, "+++ Transcript started at %.24s +++\n", ctime(&t0)); GetMenu()->ModifyMenu(IDM_TOFILE+MF_STRING, MF_BYCOMMAND, IDM_TOFILE, "&Terminate log"); DrawMenuBar(); cwin_ensure_screen(FALSE); } static CFont *MakeNewFont(CDC *dc, FontHeights *fh, DWORD charSet, const char *fontName, int weight, DWORD italic, int height) { CFont *newFont = new CFont; newFont->CreateFont( height, 0, // height, width 0, 0, // angle of escapement, base line orientation angle weight, italic, // weight, italic-flag 0, 0, charSet, // underline, strike-out, character-set OUT_DEFAULT_PRECIS, CLIP_DEFAULT_PRECIS, DEFAULT_QUALITY, DEFAULT_PITCH+FF_DONTCARE, fontName); dc->SelectObject(newFont); TEXTMETRIC mm; dc->GetTextMetrics(&mm); fh->up = mm.tmExternalLeading + mm.tmAscent; fh->down = mm.tmDescent; fh->height = mm.tmHeight; int widths[256], i; dc->GetCharWidth(0, 255, widths); for (i=0x80; i<0xa0; i++) widths[i] = 0; // my control characters // The width of a character in any sensible sized font will be less than // 256 pixels, so I save some space by stashing the information in a byte // array rather than a word array. If I really knew I was only ever going to // use TrueType fonts maybe I ought to use ABCWidths rather than just // widths. At present I think that would count as overkill. for (i=0; i<256; i++) fh->across[i] = (unsigned char)widths[i]; return newFont; } void CMainWindow::OnPrint() { CPrintDialog pd(FALSE); if (pd.DoModal() != IDOK) { cwin_ensure_screen(FALSE); return; } CDC PrintDC; if (PrintDC.CreateDC(pd.GetDriverName(), pd.GetDeviceName(), pd.GetPortName(), NULL)) { int printWidth = PrintDC.GetDeviceCaps(HORZRES), printHeight = PrintDC.GetDeviceCaps(VERTRES); // Also get the size of the print area in mm. int sizeX = PrintDC.GetDeviceCaps(HORZRES), // measured in pixels sizeY = PrintDC.GetDeviceCaps(VERTRES); int mmX = PrintDC.GetDeviceCaps(HORZSIZE), // measured in mm mmY = PrintDC.GetDeviceCaps(VERTSIZE); // I will try to allow a 10mm margin to right & left and a 15mm at top //and bottom of the printed page. int offsetX = (10*sizeX)/mmX; int offsetY = (15*sizeY)/mmY; sizeX -= 2*offsetX; sizeY -= 2*offsetY; // Create printer fonts, using the same type face as is present on the // screen. Scale it so that around 80 columns will fit across the page. // I scale for 81 not 80 to leave a margin for rounding etc etc. FontHeights fh; CFont *f = MakeNewFont(&PrintDC, &fh, ANSI_CHARSET, (char *)windowFonts.baseFace, windowFonts.boldFlag, 0, 100); PrintDC.SelectStockObject(SYSTEM_FONT); delete f; int X = fh.across['X']; int newH = (fh.height * sizeX)/(81 * X); printerFonts.ChangeFont(&PrintDC, newH, windowFonts.boldFlag, windowFonts.baseFace); DOCINFO DocInfo; DocInfo.cbSize = sizeof(DOCINFO); DocInfo.lpszDocName = "Screen Dump"; DocInfo.lpszOutput = NULL; if (PrintDC.StartDoc(&DocInfo) != -1) { PrintDC.StartPage(); PrintDC.SetTextAlign(TA_BASELINE); PrintDC.SetTextColor(RGB(0,0,0)); currentColour = CH_BLACK; PrintDC.SetBkColor(windowColour); PrintDC.SelectObject(printerFonts.Courier);currentFont = CH_COURIER; currentWidths = printerFonts.HCourier.across; int y = offsetY, line; for (line=lineFirst;; line=(line+1)&LINE_MASK) { int up, down; MeasureLine(lineBuffer[line].address, &up, &down, &printerFonts); int y1 = y+up; int y2 = y+up+down; if (y2 > sizeY) { PrintDC.EndPage(); PrintDC.StartPage(); PrintDC.SetTextAlign(TA_BASELINE); PrintDC.SetTextColor(RGB(0,0,0)); currentColour = CH_BLACK; PrintDC.SetBkColor(windowColour); PrintDC.SelectObject(printerFonts.Courier);currentFont = CH_COURIER; currentWidths = printerFonts.HCourier.across; y = offsetY; y1 = y+up; y2 = y+up+down; } PaintTextLine(&PrintDC, offsetX, y, y1, y2, lineBuffer[line].address, sizeX, &printerFonts, 1); y = y2; if (line==lineLast) break; } PrintDC.SelectStockObject(BLACK_PEN); PrintDC.EndPage(); PrintDC.EndDoc(); } printerFonts.DeleteFonts(); } GlobalFree(pd.m_pd.hDevMode); GlobalFree(pd.m_pd.hDevNames); cwin_ensure_screen(FALSE); } void CMainWindow::OnPrintSel() { if (selStartChar == selEndChar) return; // nothing selected! CPrintDialog pd(FALSE); if (pd.DoModal() != IDOK) { cwin_ensure_screen(FALSE); return; } CDC PrintDC; if (PrintDC.CreateDC(pd.GetDriverName(), pd.GetDeviceName(), pd.GetPortName(), NULL)) { int printWidth = PrintDC.GetDeviceCaps(HORZRES), printHeight = PrintDC.GetDeviceCaps(VERTRES); // Also get the size of the print area in mm. int sizeX = PrintDC.GetDeviceCaps(HORZRES), // measured in pixels sizeY = PrintDC.GetDeviceCaps(VERTRES); int mmX = PrintDC.GetDeviceCaps(HORZSIZE), // measured in mm mmY = PrintDC.GetDeviceCaps(VERTSIZE); // I will try to allow a 10mm margin to right & left and a 15mm at top //and bottom of the printed page. int offsetX = (10*sizeX)/mmX; int offsetY = (15*sizeY)/mmY; sizeX -= 2*offsetX; sizeY -= 2*offsetY; // Create printer fonts, using the same type face as is present on the // screen. Scale it so that around 80 columns will fit across the page. // I scale for 81 not 80 to leave a margin for rounding etc etc. FontHeights fh; CFont *f = MakeNewFont(&PrintDC, &fh, ANSI_CHARSET, (char *)windowFonts.baseFace, windowFonts.boldFlag, 0, 100); PrintDC.SelectStockObject(SYSTEM_FONT); delete f; int X = fh.across['X']; int newH = (fh.height * sizeX)/(81 * X); printerFonts.ChangeFont(&PrintDC, newH, windowFonts.boldFlag, windowFonts.baseFace); DOCINFO DocInfo; DocInfo.cbSize = sizeof(DOCINFO); DocInfo.lpszDocName = "Selection Dump"; DocInfo.lpszOutput = NULL; if (PrintDC.StartDoc(&DocInfo) != -1) { PrintDC.StartPage(); PrintDC.SetTextAlign(TA_BASELINE); PrintDC.SetTextColor(RGB(0,0,0)); currentColour = CH_BLACK; PrintDC.SetBkColor(windowColour); PrintDC.SelectObject(printerFonts.Courier);currentFont = CH_COURIER; currentWidths = printerFonts.HCourier.across; int y = offsetY, line; for (line=selStartLine;; line=(line+1)&LINE_MASK) { int up, down; MeasureLine(lineBuffer[line].address, &up, &down, &printerFonts); int y1 = y+up; int y2 = y+up+down; if (y2 > sizeY) { PrintDC.EndPage(); PrintDC.StartPage(); PrintDC.SetTextAlign(TA_BASELINE); PrintDC.SetTextColor(RGB(0,0,0)); currentColour = CH_BLACK; PrintDC.SetBkColor(windowColour); PrintDC.SelectObject(printerFonts.Courier);currentFont = CH_COURIER; currentWidths = printerFonts.HCourier.across; y = offsetY; y1 = y+up; y2 = y+up+down; } PaintTextLine(&PrintDC, offsetX, y, y1, y2, lineBuffer[line].address, sizeX, &printerFonts, 2); y = y2; if (line==selEndLine) break; } PrintDC.SelectStockObject(BLACK_PEN); PrintDC.EndPage(); PrintDC.EndDoc(); } printerFonts.DeleteFonts(); } GlobalFree(pd.m_pd.hDevMode); GlobalFree(pd.m_pd.hDevNames); cwin_ensure_screen(FALSE); } void CMainWindow::OnCut() { OnCopy(); DeleteSelection(); cwin_ensure_screen(FALSE); } void CMainWindow::DeleteSelection() { int p = selStartChar, q = selEndChar; if (betweenChar(p, caretChar, q)) { caretChar = p; caretLine = selStartLine; // In general after a delete operation caretX will not be valid, but the // paint operation puts that right soon enough. //-- caretX = selStartX; } if (betweenChar(p, icaretChar, q)) { icaretChar = p; icaretLine = selStartLine; // In general after a delete operation icaretX will not be valid, but the // paint operation puts that right soon enough. //-- icaretX = selStartX; } if (selStartLine==selEndLine) { while (q!=textLast) { int c = textBuffer[q]; textBuffer[p] = c; if (caretChar==q) caretChar = p; if (icaretChar==q) icaretChar = p; if (inputLineStart==q) inputLineStart=p; if (c == '\n') break; p = (p+1)&TEXT_MASK; q = (q+1)&TEXT_MASK; } if (textLast==q) textLast = p; if (inputLineStart==q) inputLineStart=p; if (caretChar==q) caretChar=p; if (icaretChar==q) icaretChar=p; // The caret will have its position re-calculated during the re-draw, and also // the line-length record will be adjusted then. InvalidateSelection(selStartLine, selStartX, selStartLine, clientWidth+xOffset); } else { int l = selStartLine, l1 = selEndLine; lineLast = l; for (;;) { while (q!=textLast) { int c = textBuffer[q]; textBuffer[p] = c; if (caretChar==q) caretChar=q, caretLine = l; if (inputLineStart==q) inputLineStart=p; if (icaretChar==q) icaretChar=p, icaretLine = l; p = (p+1)&TEXT_MASK; q = (q+1)&TEXT_MASK; if (c=='\n') break; } if (q==textLast) break; l1 = (l1+1)&LINE_MASK; q = lineBuffer[l1].address; l = (l+1)&LINE_MASK; lineBuffer[l].address = p; lineLast = l; } if (inputLineStart==q) inputLineStart=p; if (caretChar==q) caretChar=p; if (icaretChar==q) caretChar=p; textLast = p; LineSizes(); // I invalidate down to the bottom of the screen. In some cases maybe I // count do a ScrollWindow to good effect, and the plain invalidation // here may cause the screen to flicker more than would be ideal. CRect cr(0, LineY(selStartLine), clientWidth, clientHeight); InvalidateRect(&cr); } selFirstChar = selEndChar = selStartChar; selFirstX = selEndX = selStartX; selFirstLine = selEndLine = selStartLine; // The following sequence of operations is intended to scroll the window // (if necessary) to make the caret visible, to get scroll bar thumbs // up to date (eg after a multi-line delete) etc. UpdateWindow(); OnSize(SIZE_RESTORED, clientWidth, clientHeight); HideCaret(); CPoint caretPos(caretX-xOffset, caretY); SetCaretPos(caretPos); ShowCaret(); } void CMainWindow::OnCopy() { // Here I have to allocate some global memory and copy text out of my buffer // into it, making sure I insert CR/LF combinations between lines and a // zero byte on the end as a terminator. If the text buffer contains funny // characters that are used to indicate font changes etc I will just leave // them in there for the moment. if (!OpenClipboard()) return; if (!::EmptyClipboard()) return; // Take control of clipboard int l1 = selStartLine; int cp = selStartChar, size=1; for (;;) { while (cp!=selEndChar && textBuffer[cp]!='\n') { cp = (cp+1)&TEXT_MASK; size++; } if (cp==selEndChar) break; size=size+2; l1 = (l1+1)&LINE_MASK; cp = lineBuffer[l1].address; } HGLOBAL h = ::GlobalAlloc(GMEM_MOVEABLE+GMEM_DDESHARE, size); HGLOBAL h1 = 0; if (clipboardformat != 0) h1 = ::GlobalAlloc(GMEM_MOVEABLE+GMEM_DDESHARE, size); char *p = NULL, *p1 = NULL; if (h != NULL) p = (char *)::GlobalLock(h); if (h1 != NULL) p1 = (char *)::GlobalLock(h1); if (p != NULL) { l1 = selStartLine; cp = selStartChar; for (;;) { while (cp!=selEndChar && textBuffer[cp]!='\n') { int c = textBuffer[cp]; if ((c&0xff) != CH_PROMPT && (c&0xff) != CH_ENDPROMPT) *p++ = c; if (p1 != NULL) *p1++ = c; cp = (cp+1)&TEXT_MASK; size++; } if (cp==selEndChar) break; *p++ = '\r'; *p++ = '\n'; // CR/LF combination at end of line if (p1 != NULL) { *p1++ = '\r'; *p1++ = '\n'; } l1 = (l1+1)&LINE_MASK; cp = lineBuffer[l1].address; } *p = 0; ::GlobalUnlock(h); if (p1 != NULL) { *p1 = 0; ::GlobalUnlock(h1); ::SetClipboardData(clipboardformat, h1); } ::SetClipboardData(CF_TEXT, h); } ::CloseClipboard(); cwin_ensure_screen(FALSE); } void CMainWindow::OnPaste() { HGLOBAL clipboardInputHandle; char *w; ToIcaret(); caretVisible = TRUE; cwin_ensure_screen(FALSE); // may jump screen to caret location pageLine = caretLine; insertMode = TRUE; if (!OpenClipboard()) return; // If I get a new PASTE operation before an earlier one has quite finished // I will just abandon the end of the old one without further fuss. if (clipboardInput != NULL) free(clipboardInput); // If I had managed to register my own format I will use data in that format // in preference to text. Otherwise I will just grab text. if (clipboardformat != 0) clipboardInputHandle = ::GetClipboardData(clipboardformat); else clipboardInputHandle = 0; if (clipboardInputHandle == 0) clipboardInputHandle = ::GetClipboardData(CF_TEXT); clipboardInput = NULL; if (clipboardInputHandle != 0) { w = (char *)::GlobalLock(clipboardInputHandle); if (w != NULL) { int n = strlen(w); // amount of data found clipboardInput = clipboardInputP = (char *)malloc(n+1); if (clipboardInput != NULL) strcpy(clipboardInput, w); ::GlobalUnlock(clipboardInputHandle); } } ::CloseClipboard(); if (caretChar != textLast) { char oneLine[128]; for (;;) { int i=0, c; while (i<120 && clipboardInput!=NULL && (c=*clipboardInputP++)!=0) { if (c == '\r') continue; oneLine[i++] = c; if (c == '\n') break; // Do pastes one line at a time break; // do it one CHARACTER at a time... } InsertAtCaret(oneLine, i); if (c == 0) break; } if (clipboardInput != NULL) free(clipboardInput); clipboardInput = NULL; } cwin_ensure_screen(FALSE); } // The REINPUT request just combines a COPY and PASTE. It seems clumsy to // have to do both as separate actions. void CMainWindow::OnReInput() { OnCopy(); OnPaste(); } void CMainWindow::OnSelectAll() { selFirstChar = selStartChar = textFirst; selEndChar = textLast; selFirstX = selStartX = 0; selEndX = lineBuffer[lineLast].width; selFirstLine = selStartLine = lineFirst; selEndLine = lineLast; HideCaret(); Invalidate(); ShowCaret(); } void CMainWindow::OnClear() { CancelSelection(); caretChar = icaretChar = textFirst = textLast; caretLine = icaretLine = lineFirst = lineLast; caretX = icaretX = endX = 0; xOffset = 0; caretVisible = TRUE; caretFontWidths = windowFonts.HCourier.across; endFontWidths = windowFonts.HCourier.across; if (inputLineStart>=0) inputLineStart = textLast; pageLine = caretLine; cwin_ensure_screen(FALSE); } // void CMainWindow::OnUndo() // { // DisplayMsg("OnUndo Text"); // } void CMainWindow::OnRedraw() { HideCaret(); Invalidate(); ShowCaret(); } // I make END reset the caret to the end of the text buffer, since that // is an important state to be in, both with regard to input and to output. // HOME just scrolls the window to make the top of the text visible, but does // not re-position the caret. That is done because I do not generally expect // people to want to insert characters at the very top of the buffer. void CMainWindow::OnHome() { OnVScroll(SB_THUMBPOSITION, 0, NULL); OnHScroll(SB_THUMBPOSITION, 0, NULL); } void CMainWindow::OnEnd() { icaretLine = caretLine = lineLast; icaretChar = caretChar = textLast; icaretX = caretX = endX; caretFontWidths = endFontWidths; selRootValid = TRUE; OnVScroll(SB_FOR_CARET, 0, NULL); OnHScroll(SB_FOR_CARET, 0, NULL); caretVisible = TRUE; cwin_ensure_screen(FALSE); // makes sure caret is properly on screen } void CMainWindow::ToIcaret() { caretLine = icaretLine; caretChar = icaretChar; caretX = icaretX; caretFontWidths = icaretFontWidths; OnVScroll(SB_FOR_CARET, 0, NULL); OnHScroll(SB_FOR_CARET, 0, NULL); caretVisible = TRUE; cwin_ensure_screen(FALSE); // makes sure caret is properly on screen } // // The font arrangements are as follows - at each stage I will have a // master fixed-pitch font (typically "Courier New"). Whenever I change // that font I locate a collection of slave fonts - Roman, Roman Italic // and Symbol each in two sizes (one about the same as the master font and // another about (10/12)^2 of that size, for use in sub- and super-scripts). // // When I change fonts I will only change the ones used for display in // my window. The ones used when printing will be left as their original size. // I only permit font selection to alter the font (name) for the simple // fixed-pitch font that I use. But I still need to reconstruct printer // fonts to allow for changes there. void CMainWindow::OnResetFont() { HideCaret(); CClientDC dc(this); // I need to find a suitable size for my default font, so I create one // asking for "size=0" and see what windows hands back to me. FontHeights fh; CFont *f = MakeNewFont(&dc, &fh, ANSI_CHARSET, "Courier New", FW_BOLD, 0, 0); dc.SelectStockObject(SYSTEM_FONT); delete f; // If there is a font already set up somebody must discard it... windowFonts.DeleteFonts(); windowFonts.ChangeFont(&dc, fh.height, FW_BOLD, "Courier New"); theApp.WriteProfileInt("MainWindow", "FontSize", fh.height); theApp.WriteProfileString("MainWindow", "FontName", "Courier New"); theApp.WriteProfileInt("MainWindow", "FontWeight", 0); // scroll activity may be needed here just as it is // after some changes of window size. OnSize(SIZE_RESTORED, clientWidth, clientHeight); Invalidate(); // I think a total re-draw after a font change // makes sense, and will not worry evan if the new // font was identical to the old one, since after all // the user prodded the OK not the CANCEL button. ShowCaret(); } void CMainWindow::OnFont() { HideCaret(); CClientDC dc(this); LOGFONT logFont; windowFonts.Courier->GetObject(sizeof(logFont), &logFont); CFontDialog fontDialog(&logFont, CF_SCREENFONTS | CF_FIXEDPITCHONLY | CF_TTONLY | CF_ANSIONLY | CF_INITTOLOGFONTSTRUCT); // I will keep trying to obtain fonts from the user until I find one that // can be properly installed. Maybe it is unkind, but if a font as selected // from the dialog box fails to be created I just restart the dialog without // further explanation. for (;;) { int w = fontDialog.DoModal(); if (w == IDCANCEL) break; else if (w != IDOK) continue; LOGFONT *lf = fontDialog.m_cf.lpLogFont; // If there is a font already set up somebody must discard it... windowFonts.DeleteFonts(); // Whatever the user does I do not want either very very small fonts or // ones that would be so huge that their metrics could exceed 256. I apply // rather arbitrary cut-offs here. if (lf->lfHeight < -150) lf->lfHeight = -150; if (lf->lfHeight <= -6); else if (lf->lfHeight < 6) lf->lfHeight = -6; else if (lf->lfHeight > 150) lf->lfHeight = -150; windowFonts.ChangeFont(&dc, lf->lfHeight, lf->lfWeight, lf->lfFaceName); theApp.WriteProfileInt("MainWindow", "FontSize", lf->lfHeight); theApp.WriteProfileString("MainWindow", "FontName", lf->lfFaceName); theApp.WriteProfileInt("MainWindow", "FontWeight", lf->lfWeight==FW_BOLD ? 0 : 1); break; } // scroll activity may be needed here just as it is // after some changes of window size. OnSize(SIZE_RESTORED, clientWidth, clientHeight); Invalidate(); // I think a total re-draw after a font change // makes sense, and will not worry evan if the new // font was identical to the old one, since after all // the user prodded the OK not the CANCEL button. ShowCaret(); } void FontArray::InitFont(CDC *dc, const char *name, int weight, int size) { // Create and install a suitable initial font - I use "Courier New" // and ask for a default size the very first time, and this is // achieved by passing down "Courier New" and "0". // Because Courier tends to look rather light on // the screen I will use a bold weight by default. // I really hope that the default size will be such that 80 columns will fit // neatly across the screen... FontHeights fh; weight = weight ? FW_NORMAL : FW_BOLD; CFont *f = MakeNewFont(dc, &fh, ANSI_CHARSET, name, weight, 0, size); dc->SelectStockObject(SYSTEM_FONT); delete f; ChangeFont(dc, size == 0 ? fh.height : size, weight, name); } void FontArray::DeleteFonts() { delete Courier; Courier = NULL; delete Roman; Roman = NULL; delete Bold; Bold = NULL; delete Italic; Italic = NULL; delete Symbol; Symbol = NULL; delete roman; roman = NULL; delete bold; bold = NULL; delete italic; italic = NULL; delete symbol; symbol = NULL; } // The next function is a place-holder, I guess. void CMainWindow::MeasureLine(int address, int *up, int *down, FontArray *fa) { *up = fa->HCourier.up; *down = fa->HCourier.down; } void FontArray::ChangeFont(CDC *dc, int size, int weight, const char *facename) { strcpy(baseFace, facename); boldFlag = weight; Courier = MakeNewFont(dc, &HCourier, ANSI_CHARSET, facename, weight, 0, size); // I choose the scale factor 1.2^2 between my main and my script font mainly // because that is what TeX seems to have adopted - Windows will generally // round the size I ask for somewhat.... I also reload the size of my // basic fixed-pitch font in case an inconvenient value had been handed in // to start with and creating the font led to rounding. size = HCourier.height; int small = (100*size+72)/144; // These fonts have my predefined font-names and weights. They are not in fact // really (?) used at present. Roman = MakeNewFont(dc, &HRoman, ANSI_CHARSET, "Times New Roman", FW_NORMAL, 0, size); Bold = MakeNewFont(dc, &HBold, ANSI_CHARSET, "Times New Roman", FW_BOLD, 0, size); Italic = MakeNewFont(dc, &HItalic, ANSI_CHARSET, "Times New Roman", FW_NORMAL, 1, size); Symbol = MakeNewFont(dc, &HSymbol, SYMBOL_CHARSET, "Symbol", FW_NORMAL, 0, size); roman = MakeNewFont(dc, &Hroman, ANSI_CHARSET, "Times New Roman", FW_NORMAL, 0, small); bold = MakeNewFont(dc, &Hbold, ANSI_CHARSET, "Times New Roman", FW_BOLD, 0, small); italic = MakeNewFont(dc, &Hitalic, ANSI_CHARSET, "Times New Roman", FW_NORMAL, 1, small); symbol = MakeNewFont(dc, &Hsymbol, SYMBOL_CHARSET, "Symbol", FW_NORMAL, 0, small); theApp.mainWindow->LineSizes(); ::DestroyCaret(); theApp.mainWindow->CreateSolidCaret( 2*GetSystemMetrics(SM_CXBORDER), HCourier.height); } void CMainWindow::LineSizes() { int up, down, pos; int lineFirst = theApp.mainWindow->lineFirst; int lineLast = theApp.mainWindow->lineLast; TextLine *lineBuffer = &theApp.mainWindow->lineBuffer[0]; MeasureLine(lineBuffer[lineFirst].address, &up, &down, &windowFonts); lineBuffer[lineFirst].position = 0; lineBuffer[lineFirst].up = up; lineBuffer[lineFirst].height = up+down; lineBuffer[lineFirst].width = 000; pos = up + down; for (int i=(lineFirst+1)&LINE_MASK;;i=(i+1)&LINE_MASK) { MeasureLine(lineBuffer[i].address, &up, &down, &windowFonts); lineBuffer[i].position = pos; lineBuffer[i].up = up; lineBuffer[i].height = up+down; lineBuffer[i].width = 000; pos += up + down; if (i==lineLast) break; } } // Now some support for a really crude and simple text-based help window. // The interface I provide here is intended to be such that I can have // alternative implementations (eg using Unix) that use a "curses" style // screen manager. // For Windows I will only support an 80 by 25 window. I guess it // would be easy enough to permit other sizes, except that I do not have // an easy answer to what should happen if the user re-sizes the window // while other things are going on. Hence my conservative caution - at // least for now! extern "C" { int LINES = 25, COLS = 80; // initscr() must be called once at the start of a run extern void initscr(); // initkb() and resetkb() delimit regions in the code where keyboard // input is treated as requests to the curses window but is accepted // with no delay and no echo. Also mouse events can be posted during // this time. extern void initkb(); extern void resetkb(); extern int mouse_button; // set non-zero when user presses a button extern int mouse_cx; // 0 <= mouse_cx < COLS extern int mouse_cy; // 0 <= mouse_cy < LINES // refresh() is called to force the screen to be up to date extern void refresh(); // endwin() hides the curses window, restoring simple text handling extern void endwin(); // Move text insertion point. Origin (0,0) is top left of screen extern void move(int y, int x); // standout() and standend() delimit inverse video (or whatever) text extern void standout(); extern void standend(); // erase() clears the whole screen extern void erase(); // // addch() and addstr() add text to the screen, advancing the cursor. I // view it as illegal to write beyond either right or bottom margin of the // screen. // extern void addch(int ch); extern void addstr(char *s); // // getch() reads a character from the keyboard. It does not wait for // a newline, and does not echo anything. Because the name getch() may be // in use in some C libraries in a way that could conflict I use some // re-naming here. If there has been a mouse-click recently then getch() // should return a value (0x100 + bits) where the odd bits may indicate which // button was pressed. In that case (mouse_cx,mouse_cy) will be the // character-position coordinates at which the hit was taken. Systems // that can not support a mouse do not have to worry about this and can always // return a value in the range 0..255, or EOF. On some systems getch() will // return 0 with no delay if there is no character available (so that // the application will busy-wait). On others it is entitled to wait until // the user presses a key. But (once again) it should not do line editing or // wait for an ENTER. // extern int my_getch(); #undef getch #define getch() my_getch() } static BOOL helpShown = FALSE; static int helpChar = -1; void initscr() { helpShown = FALSE; helpChar = -1; } void initkb() { if (!helpShown) { erase(); theApp.mainWindow->helpWindow->ShowWindow(SW_SHOW); helpShown = TRUE; } helpChar = -1; } void resetkb() { if (helpShown) { theApp.mainWindow->helpWindow->ShowWindow(SW_HIDE); helpShown = FALSE; } } void endwin() { resetkb(); } static int helpY, helpX; void move(int y, int x) { helpY = y; helpX = x; } void erase() { helpX = helpY = 0; for (int y=0; y<LINES; y++) for (int x=0; x<COLS; x++) theApp.mainWindow->helpWindow->contents[y][x] = ' '; theApp.mainWindow->helpWindow->highline = theApp.mainWindow->helpWindow->highstart = theApp.mainWindow->helpWindow->highend = -1; } void standout() { theApp.mainWindow->helpWindow->highline = helpY; theApp.mainWindow->helpWindow->highstart = theApp.mainWindow->helpWindow->highend = helpX; } void standend() { if (theApp.mainWindow->helpWindow->highline != helpY) { theApp.mainWindow->helpWindow->highline = helpY; theApp.mainWindow->helpWindow->highstart = 0; } theApp.mainWindow->helpWindow->highend = helpX; } void addch(int ch) { if (helpX < COLS && helpY < LINES) { theApp.mainWindow->helpWindow->contents[helpY][helpX] = ch; helpX++; } } void addstr(char *s) { while (*s != 0) addch(*s++); } int mouse_button = 0, mouse_cx = 0, mouse_cy = 0; void refresh() { if (!helpShown) initkb(); theApp.mainWindow->helpWindow->Invalidate(); } int my_getch() { while (helpChar < 0) cwin_poll_window_manager(); int ch = helpChar; helpChar = -1; return ch; } CHelpWindow::CHelpWindow() { char *menuName = "HelpMenu"; HINSTANCE hInst = AfxFindResourceHandle(menuName, RT_MENU); HMENU hMenu = ::LoadMenu(hInst, menuName); this->CreateEx(0, mainWindowClass, "Help Window", WS_OVERLAPPED | WS_CAPTION, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, NULL, hMenu); helpFont.CreateFont( 0, 0, // height, width 0, 0, // angle of escapement, base line orientation angle FW_BOLD, 0, // weight, italic-flag 0, 0, ANSI_CHARSET, // underline, strike-out, character-set OUT_DEFAULT_PRECIS, CLIP_DEFAULT_PRECIS, DEFAULT_QUALITY, DEFAULT_PITCH+FF_DONTCARE, "Courier New"); for (int y=0;y<25;y++) for (int x=0;x<80;x++) contents[y][x] = ' '; highline = 5; highstart = 7; highend = 11; } BEGIN_MESSAGE_MAP(CHelpWindow, CFrameWnd) ON_WM_PAINT() ON_WM_CHAR() ON_WM_KEYDOWN() ON_WM_LBUTTONDOWN() ON_COMMAND(IDM_REDRAW, OnRedraw) // ON_COMMAND(IDM_COPY, OnCopy) ON_COMMAND(IDM_CLOSE, OnClose) END_MESSAGE_MAP() void CHelpWindow::OnPaint() { CPaintDC dc(this); dc.SelectObject(&helpFont); for (int i = 0; i<25; i++) { if (i == highline) { dc.TextOut(0, i*height, contents[i], highstart); dc.SetTextColor(theApp.mainWindow->highlightTextColour); dc.SetBkColor(theApp.mainWindow->highlightColour); dc.TextOut(highstart*width, i*height, &contents[i][highstart], highend-highstart); dc.SetTextColor(theApp.mainWindow->textColour); dc.SetBkColor(theApp.mainWindow->windowColour); dc.TextOut(highend*width, i*height, &contents[i][highend], 80-highend); } else dc.TextOut(0, i*height, contents[i], 80); } dc.SelectStockObject(SYSTEM_FONT); } void CHelpWindow::OnChar(UINT ch, UINT nRepCnt, UINT nFlags) { switch (ch) { //case ('O' & 0x1f): // OnCopy(); // return; case ('L' & 0x1f): OnRedraw(); return; default: helpChar = ch; return; } } void CHelpWindow::OnKeyDown(UINT ch, UINT nRepCnt, UINT nFlags) { } void CHelpWindow::OnLButtonDown(UINT nFlags, CPoint point) { } //void CHelpWindow::OnCopy() //{ // HGLOBAL hpbmi = 0; // @@@@@@@@@ // if (this->OpenClipboard()) // { ::EmptyClipboard(); // ::SetClipboardData(CF_DIB, hpbmi); // ::CloseClipboard(); // } //} void CHelpWindow::OnRedraw() { Invalidate(); } void CHelpWindow::OnClose() { helpChar = 'q'; } // end of c_text.cpp