// ====================================================================== // File: maclaf.cpp // Author: originally written by Timothy P. Justice // modifications by Thomas G. Ditterich and Timothy A. Budd // Created: March 22, 1994 // Last Modified: July 31, 1995 // Description: This file contains the implementation of the // classes for the Little Application Framework. // for the Macintosh // has been tested with symantic 6 and 7 // and metrowerks codewarrior 6 // ---------------------------------------------------------------------- // Copyright (c) 1992 by Timothy P. Justice., justict@cs.orst.edu // Copyright (c) 1994 by Thomas G. Dietterich, tgd@cs.orst.edu // Copyright (c) 1995 by Timothy A. Budd, budd@cs.orst.edu // // This file is part of LAF, the Little Application Framework. // // Permission is hereby granted to copy and distribute // without fee verbatim copies of this file. This copyright notice // must be retained on all copies. No warranty expressed or implied // is made concerning this software. // // We have written this without using any application resource file, // so that the program can be easily transported. // // ====================================================================== #include #include #include #include "laf.h" // Little Application Framework // --- Window Class ---------------------------------------------------- // constructor ------------------------------------------------ window::window(char * apTitle, int width, int height) : gridSize(0), gridColor(blackColor), focussedEditText(0), editableTexts(0), staticTexts(0), buttons(0), menus(0) { // Initialize the mac interface // All of these are Mac Toolbox functions InitGraf(&qd.thePort); // thePort is a global variable pointing // to the desktop QD port InitFonts(); FlushEvents(everyEvent, 0); InitWindows(); InitMenus(); TEInit(); // Init TexeditableText in case we create a TE InitDialogs(0L); InitCursor(); MaxApplZone(); // We create one root window with the application title on it. unsigned char * ptitle = CtoPstr(apTitle); // initialize our instance variables describing rectangles boundRect.top = 40; boundRect.left = 40; boundRect.bottom = 40+height; boundRect.right = 40+width; dragRect.top = 0; dragRect.bottom = 1024; dragRect.left = 0; dragRect.right = 1024; // allocate our root window macWindow = NewWindow(&macWindowRecord, &boundRect, ptitle, 1, // visible 0/*DocumentProc*/, // titled window, no grow box (WindowPtr) -1L, // in front 1, // with go-away box 0L); } // size information functions ------------------------------ int window::bottom() // return the global coordinates of the bottom of the content region window { Region rgn = **(macWindowRecord.contRgn); return (rgn.rgnBBox).bottom; } int window::top() { Region rgn = **(macWindowRecord.contRgn); return (rgn.rgnBBox).top; } int window::height() // return the height of the window in pixels { return bottom() - top(); } int window::left() { Region rgn = **(macWindowRecord.contRgn); return (rgn.rgnBBox).left; } int window::right() { Region rgn = **(macWindowRecord.contRgn); return (rgn.rgnBBox).right; } int window::width() { return right() - left(); } // screen update operations on the window ----------------- void window::clearAndUpdate() { // erase the current screen, then refresh SetPort(macWindow); EraseRect(&macWindow->portRect); InvalRect(&macWindow->portRect); } void window::update() { // invalidate the entire screen, and OS will schedule refresh SetPort(macWindow); InvalRect(&(macWindow->portRect)); } // graphics operations --------------------------------------- void window::circle(const int x, const int y, const int radius) { GrafPtr savePort; Rect r = {y - radius, x - radius, y + radius, x + radius}; GetPort(&savePort); // remember current QD port SetPort(macWindow); FrameOval(&r); SetPort(savePort); // restore QD port } void window::line(const int fromX, const int fromY, const int toX, const int toY) { GrafPtr savePort; GetPort(&savePort); // remember current QD port SetPort(macWindow); MoveTo(fromX, fromY); LineTo(toX, toY); SetPort(savePort); // restore QD port } void window::point(const int x, const int y) { this->circle(x, y, 1); } void window::rectangle(const int x, const int y, const int wdth, const int hght) { GrafPtr savePort; Rect r = {y , x, y+hght, x+wdth}; GetPort(&savePort); // remember current QD port SetPort(macWindow); FrameRect(&r); SetPort(savePort); // restore QD port } void window::print(int x, int y, const char * str) // print text honoring newlines. { GrafPtr savePort; Point pen; FontInfo fontinfo; int lineheight; GetPort(&savePort); // remember current QD port SetPort(macWindow); GetFontInfo(&fontinfo); GetPen(&pen); // figure out where we are now lineheight = fontinfo.ascent + fontinfo.descent + 1; MoveTo(x, y); while (*str) { if (*str == '\n') { y += lineheight; MoveTo(x, y); str++; } else { DrawText(str, 0, 1); str++; } } SetPort(savePort); // restore QD port } void window::setPenColor(long int color) { GrafPtr savePort; GetPort(&savePort); // remember current QD port SetPort(macWindow); ForeColor(color); SetPort(savePort); // restore QD port } void window::setPenSize(int size) { GrafPtr savePort; GetPort(&savePort); // remember current QD port SetPort(macWindow); PenSize(size,size); SetPort(savePort); // restore QD port } void window::setPen(long int color, Pattern pat, int size) { GrafPtr savePort; GetPort(&savePort); // remember current QD port SetPort(macWindow); #if __MWERKS__ PenPat(&pat); # else PenPat(pat); # endif PenSize(size,size); ForeColor(color); SetPort(savePort); // restore QD port } // ---- grid controls ------------------------------- void window::gridOn(int color, int size) // { assert(size>1); gridSize= size; gridColor= color; } void window::gridOff() { gridSize = 0; } static int idNumbers = 177; button::button (window * win, char * title, int x, int y, int width, int height) { idNumber = idNumbers++; Rect r = {y, x, y+height, x+width}; unsigned char * ptitle = CtoPstr(title); control = NewControl(win->macWindow, &r, ptitle, 1, // visible 0, // current value is 0 0, // min value 0 0, // max value 0 pushButProc, // simple button 0L); // now attach to windows list of buttons nextButton = win->buttons; win->buttons = this; } // private and protected methods -------------------- void window::paintGrid() // paint the grid onto the screen // always called from within update(), so the grafport is already set // properly. { int h = height(); int w = width(); setPen(gridColor, qd.black, 1); for (int row = 0; row < w; row += gridSize) { for (int col = 0; col < w; col += gridSize) { line(col,row,col,row); // draw point as trivial line } } } void window::snapCoord(int & coord) { coord = ((coord+(gridSize/2))/gridSize)*gridSize; } void window::findAndHitButton(ControlHandle h) { button * b = buttons; while (b && (b->control != h)) b = b->nextButton; if (b) b->pressed(this); } editableText * window::editableTextUnderPoint(Point & pt) // return pointer to editableText if any or 0 otherwise { editableText * et = editableTexts; while (et) { if (et->pointInTEdit(pt)) return et; et = et->nexteditableText; } return 0; } editableText * window::findeditableTextFromScrollControl(ControlHandle theControl) // given a control handle, find its owner editableText. { editableText * et = editableTexts; while (et) { if (et->vScroll == theControl) return et; et = et->nexteditableText; } return 0; } // --- Application ----------------------------------------------------- // constructor --------------------------------------- application::application(char * apTitle, int width, int height) : window(apTitle, width, height), // window initialization appleMenu(this), // create the apple menu fileMenu (this, "File"), // create the file menu #if __MWERKS__ quitItem (fileMenu) # else quitItem (fileMenu, "Quit/Q", quit) // attach the quit menu item # endif { } // the user should specialize these methods. In "application", they // do nothing at all. void application::paint() {}; void application::mouseButtonDown(int x, int y) {}; void application::keyPressed(char c) { } void application::eventTick() {}; // // application::run is the main event handler. // It creates an initial window, sets up the menu bar and cursor, and // enters the main event loop. void application::run() { // loop forever while (MainEvent()) {}; } void application::quit() { ExitToShell(); } // private and protected methods ----------------------- // standard menus application::appleMenuClass::appleMenuClass(window * w) : menu(w, "\024") { AddResMenu(menuPtr, 'DRVR'); } void application::appleMenuClass::select(window *, int itemNumber) { int accNumber; Str255 accName; // should handle ``about'' stuff, but we don't // get a desk accessory if (itemNumber > 1) { GetItem(menuPtr, itemNumber, accName); accNumber = OpenDeskAcc(accName); } } int application::MainEvent() // This is the main event loop. { EventRecord myEvent; short windowPart; WindowPtr whichWindow; MaintainCursor(); SystemTask(); if (focussedEditText) { TEIdle(focussedEditText->handle); } eventTick(); if (GetNextEvent(everyEvent, &myEvent)) { switch (myEvent.what) { case mouseDown: windowPart = FindWindow(myEvent.where, &whichWindow); DoMouseDown(windowPart, whichWindow, &myEvent); break; case keyDown: case autoKey: { register char theChar; theChar = myEvent.message & charCodeMask; if ((myEvent.modifiers & cmdKey) != 0) return (DoCommand(MenuKey(theChar))); else { if (focussedEditText) { TEKey(theChar, focussedEditText->handle); focussedEditText->showSelect(); } else // nothing else, give user a crack at it keyPressed(theChar); } break; } case activateEvt: if (focussedEditText && (myEvent.modifiers & activeFlag)) { TEActivate(focussedEditText->handle); ShowControl(focussedEditText->vScroll); TEFromScrap(); } break; case updateEvt: if (macWindow== (WindowPtr) myEvent.message) { Update(); } } } return (1); } void application::MaintainCursor() // periodically check the cursor location and set appropriately { Point pt; GrafPtr savePort; ControlHandle theControl; GetPort(&savePort); GetMouse(&pt); if (PtInRect(pt, &((**(macWindowRecord.contRgn)).rgnBBox))) { SetPort(macWindow); GlobalToLocal(&pt); if (editableTextUnderPoint(pt)) SetCursor(* GetCursor(iBeamCursor)); else if (FindControl(pt, macWindow, &theControl)) SetCursor(&qd.arrow); else SetCursor(* GetCursor(crossCursor)); } else SetCursor(&qd.arrow); SetPort(savePort); } void application::Update() // Time to re-draw our window { GrafPtr savePort; GetPort(&savePort); SetPort(macWindow); BeginUpdate(macWindow); DrawControls(macWindow); DrawMenuBar(); if (gridSize>0) paintGrid(); // update all of the editableTexts. { for(editableText * et = editableTexts; et; et = et->nexteditableText) { et->update(); } } // update all staticTexts. { for(staticText * st = staticTexts; st; st = st->nextStaticText) { st->update(); } } paint(); // user's painting routine DrawGrowIcon(macWindow); EndUpdate(macWindow); SetPort(savePort); } void application::DoMouseDown(int windowPart, WindowPtr whichWindow, EventRecord *myEvent) // A nasty aspect of the Mac is that we will get mouse clicks from anywhere on the screen // so sometimes we must pass the click off elsewhere { int ours = (whichWindow == macWindow); switch(windowPart) { case inGoAway: if (ours) { if (TrackGoAway(macWindow, myEvent->where)) { quit(); } } break; case inMenuBar: DoCommand(MenuSelect(myEvent->where)); break; case inSysWindow: SystemClick(myEvent, whichWindow); break; case inDrag: if (ours) { DragWindow(whichWindow, myEvent->where, &dragRect); } break; case inGrow: // handle window grow DoGrow(whichWindow, myEvent); break; case inContent: if (whichWindow != FrontWindow()) { SelectWindow(whichWindow); } else if (ours) { DoContent(myEvent); } break; } } void application::DoGrow(WindowPtr whichWindow, EventRecord * myEvent) { if (whichWindow != FrontWindow()) SelectWindow(whichWindow); else { Rect sizeRect; long newSize; SetRect(&sizeRect, 80, 80, 512, 342); newSize = GrowWindow(whichWindow, myEvent->where, &sizeRect); if (newSize != 0) { SizeWindow(whichWindow, LoWord(newSize), HiWord(newSize), TRUE); clearAndUpdate(); } } } void application::DoContent(EventRecord *myEvent) { int cntlCode; ControlHandle theControl; GrafPtr savePort; editableText * et; GetPort(&savePort); SetPort(macWindow); GlobalToLocal(&myEvent->where); // convert to window coordinates cntlCode = FindControl(myEvent->where, macWindow, &theControl); if (cntlCode) { // mouse went down in a control. // was it a button? if (cntlCode==inButton) { // track it: does highlighting, etc. if(TrackControl(theControl, myEvent->where, 0L)) { // mouse has gone UP still over the button findAndHitButton(theControl); } } else if ((cntlCode==inThumb) || (cntlCode==inUpButton) || (cntlCode==inDownButton) || (cntlCode==inPageUp) || (cntlCode==inPageDown)) { // in the scroll-bar of a editableText findAndScrolleditableText(cntlCode, theControl, myEvent->where); } } else if (0 != (et = (editableText *) editableTextUnderPoint(myEvent->where))) { // mouse has gone down over an editableText. // If that edit text is in focus, then pass it on if (et == focussedEditText) { TEClick(myEvent->where, (myEvent->modifiers & shiftKey) != 0, focussedEditText->handle); } else if (focussedEditText) { // defocus the current editableText focussedEditText->unSetFocus(); et->setFocus(); } else { et->setFocus(); } } else { // mouse went down in the content region outside of a editableText. defocus any editableText if (focussedEditText) { focussedEditText->unSetFocus(); } // if the grid is currently on, then we need to "snap" the mouse to the nearest grid // points. int x = myEvent->where.h; int y = myEvent->where.v; if (gridSize>0) { snapCoord(x); snapCoord(y); } mouseButtonDown(x, y); } SetPort(savePort); } int application::DoCommand(long int mResult) { int theMenu = HiWord(mResult); int theItem = LoWord(mResult); // walk through menus, see if any match for (menu * m = menus; m; m = m->nextMenu) if (m->idNumber == theMenu) { m->select(this, theItem); HiliteMenu(0); // remove highlighting break; } return(1); } // --- Scrolling Support ------------------------------------------------------------ // this is the current editableText to be scrolled by ScrollProc static editableText * currentScroller = 0; pascal void ScrollProc (ControlHandle theControl, short theCode) // this is a "pascal" call-back routine. TrackControl will invoke this at each event tick // to have us scroll the window { int scrollAmt; int oldCtl; if ((theCode == 0) || (currentScroller == 0)) return; switch (theCode) { case inUpButton: scrollAmt = -1; break; case inDownButton: scrollAmt = 1; break; case inPageUp: scrollAmt = -currentScroller->linesInFolder; break; case inPageDown: scrollAmt = currentScroller->linesInFolder; break; } oldCtl = GetCtlValue(theControl); SetCtlValue(theControl, oldCtl+scrollAmt); currentScroller->adjustText(); } void application::findAndScrolleditableText(int cntlCode, ControlHandle theControl, Point where) // The mouse button is currently down in the vertical scroll bar of the editableText. { editableText * te = findeditableTextFromScrollControl(theControl); assert(te!=0); if (cntlCode==inThumb) { TrackControl(theControl, where, 0L); te->adjustText(); } else { currentScroller = te; // can't seem to find the right types for this..... //TrackControl(theControl, where, (ControlActionUPP) ScrollProc); currentScroller = 0; } } // --- TEditCommon -------------------------------------------------- TEditCommon::TEditCommon(window * w, int x, int y, int width, int height, justification just, int borderP, char * text) : win(w), handle(0), topValue(y), leftValue(x), heightValue(height), widthValue(width), drawWithBorder(borderP) { Rect viewRect = {y+4, x+4, y+height-4, x+width-4}; int textLength = 0; if (text != 0) { textLength = ::strlen(text); for (int i = 0; text[i]; i++) if (text[i] == '\n') text[i] = '\r'; } GrafPtr savePort; GetPort(&savePort); SetPort(win->macWindow); TextFont(monaco); TextSize(9); // create the TEdit handle = TENew(&viewRect, &viewRect); TESetText(text, (long) textLength, handle); TESetJust(just, handle); SetPort(savePort); } void TEditCommon::update() { if (drawWithBorder) { win->rectangle(leftValue,topValue,widthValue,heightValue); } TEUpdate(&((win->macWindow)->portRect), handle); } int TEditCommon::size () { return (**handle).teLength; } char * TEditCommon::text () // copy the text out of the TEdit and into a string. // we allocate a heap string of the required size. { int len = size(); char * buffer = new char[len+1]; CharsHandle ch = TEGetText(handle); char thisChar; assert(buffer!=0); // copy into buffer, converting \r to \n int i; for (i = 0; (imacWindow); // create the scroll bar vScroll = NewControl(win->macWindow, &vScrollRect, "\p", 1,0,0,0,scrollBarProc, 0L); // number of lines that are visible linesInFolder = ((**handle).viewRect.bottom - (**handle).viewRect.top) / (**handle).lineHeight; SetPort(savePort); // finally, attach to window nexteditableText = w->editableTexts; w->editableTexts = this; } // if there are multiple editableTexts's, only one can be in focus at a time // This function sets the current focus. application::run will then // ensure that the caret will blink in that TextEdit. void editableText::setFocus() { win->focussedEditText = this; TEActivate(handle); HiliteControl(vScroll, 0); ShowControl(vScroll); TEFromScrap(); } void editableText::unSetFocus() { win->focussedEditText = 0; TEDeactivate(handle); // mark the control as inactive HiliteControl(vScroll, 255); TEToScrap(); } void TEditCommon::setText(char text[]) { for (int i = 0; text[i]; i++) if (text[i] == '\n') text[i] = '\r'; TESetText(text, ::strlen(text), handle); showSelect(); } void editableText::adjustText() // This routine actually scrolls the text in the editableText! { int oldScroll, newScroll, delta; oldScroll = (**handle).viewRect.top - (**handle).destRect.top; newScroll = GetCtlValue(vScroll) * (**handle).lineHeight; delta = oldScroll - newScroll; if (delta != 0) TEScroll(0, delta, handle); } void editableText::setVScroll() // set the control value of the vertical scroll bar { register int n = (**handle).nLines-linesInFolder; if (((**handle).teLength > 0) && ((*((**handle).hText))[(**handle).teLength-1]=='\r')) n++; SetCtlMax(vScroll, n > 0 ? n : 0); } void editableText::showSelect() // Update the vertical scroll bar as the user types. // And scroll the text if the user types off the bottom. { setVScroll(); adjustText(); register int topLine = GetCtlValue(vScroll); register int bottomLine = topLine + linesInFolder; int theLine; int selStart = (**handle).selStart; if ((selStart < (**handle).lineStarts[topLine]) || (selStart >= (**handle).lineStarts[bottomLine])) { for (theLine = 0; (**handle).selStart >= (**handle).lineStarts[theLine]; theLine++) { }; SetCtlValue(vScroll, theLine - linesInFolder / 2); adjustText(); } } // --- staticText -------------------------------------------------------- // In implementing the static text, we // faced a design decision. One option would be to make // Each staticText object a TextEdit to which we never gave focus (and which // didn't have a scroll bar). The advantage of this is that the object // would automatically repaint itself. // The other option was to use the TextBox() facility of the Mac. // This just paints a text box on the screen with justification and // line wrapping, but it does not persist. I decided to use the // TextEdit approach. - tgd staticText::staticText(window * win, int x, int y, int width, int height, char * t, int border, justification j) : TEditCommon(win, x, y, width, height, j, border, t) { // then add to windows lists of static texts nextStaticText = win->staticTexts; win->staticTexts = this; } //-------menus and menu items ----------------------------- menu::menu(window * parent, char * title) { // first, get our identification number idNumber = idNumbers++; menuPtr = NewMenu(idNumber, CtoPstr(title)); nextMenu = 0; firstItem = 0; // then, put us on the parent's list nextMenu = parent->menus; parent->menus = this; // finally, give the toolbox routines to draw menu InsertMenu(menuPtr, 0); DrawMenuBar(); } void menu::addMenuItem(menuItem * it, char * title) { // convert title to pascal string, and insert into menu AppendMenu(menuPtr, CtoPstr(title)); if (firstItem) it->positionNumber = firstItem->positionNumber + 1; else it->positionNumber = 1; it->nextItem = firstItem; firstItem = it; } void menu::select(window * win, int theItem) { for (menuItem * m = firstItem; m; m = m->nextItem) if (m->positionNumber == theItem) { // found it, then select it m->selected(win); } } menuItem::menuItem(menu & parent, char * t) { // give us a new unique id number positionNumber = 0; parent.addMenuItem(this, t); } void menuItem::selected(window * w) { /* default -- do nothing */ }