/* * - Check that SaveFile puts correct initial name in. * - Make sure that filename gets initialized properly in all cases. */ /* * TransEdit.c version 3.05 - TransSkel plug-in module supporting an * arbitrary number of generic edit windows. Each window may be * bound to a file. * * *** Requires FakeAlert.c for proper linking! *** * * TransSkel and TransEdit are public domain. For more information, * contact: * * Paul DuBois * Wisconsin Regional Primate Research Center * 1220 Capitol Court * Madison, WI 53715-1299 USA * * Internet: dubois@primate.wisc.edu * * * This version of TransEdit is written for THINK C 6.0. * THINK C is a trademark of: * * Symantec Corporation * 10201 Torre Avenue * Cupertino, CA 95014 USA * * History * 08/25/86 Genesis. Beta version. * 09/15/86 * - Changed to allow arbitrary number of windows. * * 11/04/86 Release 1.0 * - Added conditional stuff to allow compilation in single- or * multiple-window mode. * * 01/17/87 Release 1.01 * - The window type when a new window is created is documentProc+8 * now, so that the window will have a zoom box on a machine with * 128K ROMS. Default file name in SaveFile() is initialized after * SyncAllGlobals() - fixing bug found by Owen Hartnett. * * 01/29/89 Release 2.0 * - Converted to work with TransSkel 2.0. 2-byte and * 4-byte integer types are typedef'ed to Integer and LongInt to * ease porting. Added SystemEdit() check to Edit menu handler. * * 16 Jun 92 Release 3.01 * - Ported for TransSkel 3.00. Basically this amounted to adding * function prototypes. * - Use real curly quotes in FakeAlert() messages. * * 05 Jun 93 Release 3.02 * - Conversion for THINK C 6.0. * * 08 Jun 93 Release 3.03 * - Took out all the stuff to allow compiling to handle only a single * edit window. The savings in bytes of object code is no longer worth * the extra source code complexity. It's also unnecessary because I no longer * maintain a window list, since I ... * - Reimplemented linked list holding edit window data using TransSkel's * window property functions (new in TS 3.00). The property type is * skelWPropEditWind, and the data value is a handle to the window data * structure. * - Took out all the "register" declarations. The compiler's smart enough * now that they don't make any difference, so they're just clutter. * 05 Jul 93 * - Redid FakeAlert() calls to correspond better to new button positioning. * (See FakeAlert.c remarks.) * - Fixed Activate() and Update() so that when a window goes inactive, * the scroll bar is hidden (and only the frame drawn). Previously, the * scroll bar was just made inactive, which doesn't quite conform to the user * interface guidelines. * 18 Dec 93 * - Untitled windows have "untitled" rather than "Untitled" in title bar, * and first untitled window isn't given a number, in accordance with Apple * guidelines. * - Replaced SFGetFile()/SFPutFile() with SFPGetFil()/SFPPutFile() so can * use SkelDlogFilter(). * 21 Dec 93 * - Use color grafports when available. * 04 Jan 94 * - Undid Integer/LongInt type stuff back to short/long. * * 18 Jan 94 Release 3.04 * - FakeAlert() code revamped. See FakeAlert.c. * 19 Jan 94 * - Window creation routines do better checks for allocation failures. * - Much rewriting to eliminate many global variables. No more SyncGlobals(). * 20 Jan 94 * - Fixed bug with missing SkelRmveDlogFilter() after SFPGetFile() call. * 23 Jan 94 * - Fixed bug in EWindowEditOp(); edit window doesn't need to be marked dirty * for Copy operation. * * 21 Feb 94 Release 3.05 * - Updated for TransSkel 3.11. * - Converted interface to be Pascal-compatible. */ # include "TransSkel.h" # include "TransEdit.h" # define normalHilite 0 # define dimHilite 255 /* * Maximum size of file we'll attempt to read. The value leaves * a little cushion against the TextEdit limit of 32767. */ # define maxFileSize 32000 # define WindowIsActive(w) ((WindowPeek) (w))->hilited /* * New(TypeName) returns handle to new object, for any TypeName. * If there is insufficient memory, the result is nil. */ # define New(type) (type **) NewHandle ((Size) sizeof (type)) # define enter 3 # define cr 13 typedef enum /* Edit menu item numbers */ { undo = 1, /* --- */ cut = 3, copy, paste, clear /* (it's ok if the host doesn't have this item) */ }; /* * Default values for edit window text display characteristics * and event notification procedures. Used when new edit windows * are created. */ static short e_font = monaco; /* default font */ static short e_size = 9; /* default pointsize */ static short e_wrap = 0; /* default word wrap (on) */ static short e_just = teJustLeft; /* default justification */ static TEditKeyProcPtr e_key = nil; /* default key procedure */ static TEditActivateProcPtr e_activate = nil; /* default activation procedure */ static TEditCloseProcPtr e_close = nil; /* default close procedure */ /* * Edit window document record. A handle to a window's document * record is stored in the window's property list. */ typedef struct DocRecord DocRecord, *DocPtr, **DocHandle; struct DocRecord { WindowPtr editWind; /* the edit window */ Boolean bound; /* whether window is bound to file */ SFReply editFile; /* file it's bound to, if bound true */ TEHandle editTE; /* window text */ Boolean dirty; /* whether text modified since save */ ControlHandle eScroll; /* scroll bar */ short visLines; /* # lines visible in window, max */ TEditKeyProcPtr eKey; /* key click notifier */ TEditActivateProcPtr eActivate; /* activate event notifier */ TEditCloseProcPtr eClose; /* close notifier */ }; /* * Macros for accessing parts of a document record, given a document * handle. */ # define DocWind(doc) ((**doc).editWind) # define DocBound(doc) ((**doc).bound) # define DocFile(doc) ((**doc).editFile) # define DocTE(doc) ((**doc).editTE) # define DocDirty(doc) ((**doc).dirty) # define DocScroll(doc) ((**doc).eScroll) # define DocVisLines(doc) ((**doc).visLines) # define DocKeyProc(doc) ((**doc).eKey) # define DocActivateProc(doc) ((**doc).eActivate) # define DocCloseProc(doc) ((**doc).eClose) static Point dlogWhere = { 70, 100 }; /* GetFile/PutFile location */ static OSType creator = 'TEDT'; /* default file creator */ static RgnHandle clipRgn = (RgnHandle) nil; /* -------------------------------------------------------------------- */ /* Miscellaneous Internal (private) Routines */ /* -------------------------------------------------------------------- */ static void ErrMesg (StringPtr s) { (void) FakeAlert (s, "\p", "\p", "\p", 1, 1, 0, "\pOK", "\p", "\p"); } /* * Save and restore the current window's clip region */ static void SaveClipRgn (void) { clipRgn = NewRgn (); GetClip (clipRgn); } static void RestoreClipRgn (void) { SetClip (clipRgn); DisposeRgn (clipRgn); } /* * Draw grow box in lower right hand corner of window. */ static void DrawGrowBox (WindowPtr w) { Rect r; SaveClipRgn (); r = w->portRect; r.left = r.right - 15; /* draw only in corner */ r.top = r.bottom - 15; ClipRect (&r); DrawGrowIcon (w); RestoreClipRgn (); } /* * Generate title for new untitled document. First one is * unnumbered, others are numbered. */ static void Untitled (StringPtr name) { static long num = 0; Str255 numBuf; BlockMove ("\puntitled", name, 9L); if (++num > 1) { name[++name[0]] = ' '; NumToString (num, numBuf); BlockMove (&numBuf[1], &name[name[0]+1], (long) numBuf[0]); name[0] += numBuf[0]; } } /* -------------------------------------------------------------------- */ /* Lowest-level Internal (Private) Edit Window Routines */ /* -------------------------------------------------------------------- */ /* * Get document handle associated with edit window. * Return nil if window isn't a known edit window. */ static DocHandle WindDocHandle (WindowPtr w) { SkelWindPropHandle ph; DocHandle doc = (DocHandle) nil; if (w != (WindowPtr) nil) { ph = SkelGetWindProp (w, skelWPropEditWind); if (ph != (SkelWindPropHandle) nil) doc = (DocHandle) (**ph).skelWPropData; } return (doc); } /* -------------------------------------------------------------------- */ /* Internal (private) Display Routines */ /* -------------------------------------------------------------------- */ /* * Calculate the dimensions of the editing rectangle for a window * (which is assumed to be the current port). (The viewRect and * destRect are the same size.) Assumes the port, text font and * text size are all set properly. The viewRect is sized so that * an integral number of lines can be displayed in it, i.e., so that * a partial line never shows at the bottom. If that's not done, * funny things can happen to the caret. */ static void CalcEditRect (WindowPtr w, Rect *r) { FontInfo f; short lineHeight; GetFontInfo (&f); lineHeight = f.ascent + f.descent + f.leading; *r = w->portRect; r->left += 4; r->right -= 17; /* leave room for scroll bar + 2 */ r->top += 2; r->bottom = r->top + ((r->bottom - r->top - 2) / lineHeight) * lineHeight; } /* * Calculate the dimensions of the scroll bar rectangle for a * window. Make sure that the edges overlap the window frame and * the grow box. */ static void CalcScrollRect (WindowPtr w, Rect *r) { *r = w->portRect; ++r->right; --r->top; r->left = r->right - 16; r->bottom -= 14; } /* * Return true if the mouse is in the non-scrollbar part of an * edit window. */ static Boolean PtInText (WindowPtr w, Point pt) { Rect r; r = w->portRect; r.right -= 15; return (PtInRect (pt, &r)); } /* * Set the cursor appropriately. If theCursor == iBeamCursor, check * that it's really in the text area of an edit window (and if not * set the cursor to an arrow instead). Otherwise, set the cursor * to the given type (usually a watch). * * Pass -1 for theCursor to set the cursor to the arrow. */ static void FixCursor (short theCursor) { WindowPtr w; GrafPtr savePort; Point pt; if (theCursor == iBeamCursor) /* check whether there's an edit */ { /* window in front and if so, */ theCursor = -1; /* whether the cursor's in its */ w = FrontWindow (); /* text area */ if (IsEWindow (w)) { GetPort (&savePort); SetPort (w); GetMouse (&pt); if (PtInText (w, pt)) theCursor = iBeamCursor; SetPort (savePort); } } SetCursor (theCursor == -1 ? &arrow : *GetCursor (theCursor)); } /* * Calculate the number of lines currently scrolled off * the top of an edit record. */ static short LinesOffTop (TEHandle hTE) { return (((**hTE).viewRect.top - (**hTE).destRect.top) / (**hTE).lineHeight); } /* * Return the line number that the caret (or the beginning of * the currently selected text) is in. Value returned is in * the range 0..(**editTE).nLines. If = (**editTE).nLines, the * caret is past the last line. The only special case to watch out * for is when the caret is at the very end of the text. If the * last character is not a carriage return, then the caret is on * the (nLines-1)th line, not the (nLines)th line. * * (This really should do a binary search for speed.) */ static short LineWithCaret (TEHandle hTE) { short i; short nLines; short teLength; short selStart; short lineStart; selStart = (**hTE).selStart; nLines = (**hTE).nLines; teLength = (**hTE).teLength; if (selStart == teLength) { if (teLength != 0 && (*((**hTE).hText))[teLength-1] != cr) return (nLines - 1); return (nLines); } for (i = 0; /* empty */; ++i) { if ((lineStart = (**hTE).lineStarts[i]) >= selStart) { if (lineStart != selStart) --i; return (i); } } } /* * Return the number of the last displayable line. That's one * more than nLines if the text is empty or the last character * is a carriage return. */ static short LastLine (TEHandle hTE) { short nLines; short teLength; nLines = (**hTE).nLines; teLength = (**hTE).teLength; if (teLength == 0 || (*((**hTE).hText))[teLength-1] == cr) nLines++; return (nLines); } /* * Highlight the scroll bar properly. This means that it's not * made active if the window itself isn't active, even if * there's enough text to fill the window. */ static void HiliteScroll (DocHandle doc) { WindowPtr w; ControlHandle scroll; short hilite; w = DocWind (doc); scroll = DocScroll (doc); hilite = (WindowIsActive (w) && GetCtlMax (scroll) > 0 ? normalHilite : dimHilite); HiliteControl (scroll, hilite); } /* * Set scroll bar current value (but only if it's different than * the current value, to avoid needless flashing). */ static void SetScrollValue (ControlHandle scroll, short value) { if (GetCtlValue (scroll) != value) SetCtlValue (scroll, value); } /* * Set the maximum value of the scroll bar. It's set so that if * there's more text than fits in the window, the bottom line can * be scrolled up at least a little below the bottom of the window. * * The shenanigans with topLines and scrollableLines have to do with * the case where there may be less text than fills the window, but * most of it's scrolled off the top. This can happen when you * scroll a bunch of stuff up, then delete everything visible in * the window. */ static void SetScrollMax (DocHandle doc) { ControlHandle scroll; TEHandle hTE; short topLines; short scrollableLines; short max; scroll = DocScroll (doc); hTE = DocTE (doc); topLines = LinesOffTop (hTE); scrollableLines = LastLine (hTE) - DocVisLines (doc);; max = (topLines > scrollableLines ? topLines : scrollableLines); if (max < 0) max = 0; if (max != GetCtlMax (scroll)) { SetCtlMax (scroll, max); HiliteScroll (doc); } } /* * Scroll to the correct position. lDelta is the * amount to CHANGE the current scroll setting by. * Positive scrolls the text up, negative down. */ static void ScrollText (DocHandle doc, short lDelta) { ControlHandle scroll; TEHandle hTE; short topVisLine; short newTopVisLine; scroll = DocScroll (doc); hTE = DocTE (doc); topVisLine = LinesOffTop (hTE); newTopVisLine = topVisLine + lDelta; if (newTopVisLine < 0) /* clip to range */ newTopVisLine = 0; if (newTopVisLine > GetCtlMax (scroll)) newTopVisLine = GetCtlMax (scroll); SetScrollValue (scroll, newTopVisLine); TEScroll (0, (topVisLine - newTopVisLine ) * (**hTE).lineHeight, hTE); } /* * Scroll to home position without redrawing. */ static void ScrollToHome (TEHandle hTE) { Rect r; r = (**hTE).destRect; OffsetRect (&r, 0, 2 - r.top); (**hTE).destRect = r; } /* * ClikLoop proc for autoscrolling text when the mouse is dragged out * of the text view rectangle. * * The clipping region has to be set to include the scroll bar, * because whenever this proc is called, TextEdit has the region set * to the view rectangle - if it's not reset, changes to the scroll * bar won't show up! * * AutoScroll() uses scrollDoc, which must be set in Mouse() before * calling TEClick(), which calls AutoScroll(). */ static DocHandle scrollDoc; static short scrollPart; static pascal Boolean AutoScroll (void) { WindowPtr w; TEHandle hTE; Point p; Rect r; w = DocWind (scrollDoc); hTE = DocTE (scrollDoc); SaveClipRgn (); ClipRect (&w->portRect); GetMouse (&p); r = (**hTE).viewRect; if (p.v < r.top) ScrollText (scrollDoc, -1); /* back one line */ else if (p.v > r.bottom) ScrollText (scrollDoc, 1); /* forward one line */ RestoreClipRgn (); return (true); /* true = 'keep tracking mouse' */ } /* * Filter proc for tracking mousedown in scroll bar. * * I suspect odd scrolling may occur for hits in paging regions if * the window is allowed to size such that less than two lines show. * * TrackScroll() uses scrollDoc and scrollPart, which must be set in * Mouse() before calling TrackControl(), which calls TrackScroll(). * scrollPart is the original part code in which the mousedown occurred. */ static pascal void TrackScroll (ControlHandle theScroll, short partCode) { short lDelta; short visLines = DocVisLines (scrollDoc); if (partCode == scrollPart) /* still in same part? */ { switch (partCode) { case inUpButton: lDelta = -1; break; case inDownButton: lDelta = 1; break; case inPageUp: lDelta = -(visLines - 1); break; case inPageDown: lDelta = visLines - 1; break; } ScrollText (scrollDoc, lDelta); } } /* * Set the scroll bar properly and adjust the text in the * window so that the line containing the caret is visible. * If the line with the caret is more than a line outside of * the viewRect, try to place it in the middle of the window. * * Yes, it is necessary to call SetScrollMax() at the end. */ static void AdjustDisplay (DocHandle doc) { ControlHandle scroll; TEHandle hTE; short visLines; short caretLine; short topVisLine; short d; scroll = DocScroll (doc); hTE = DocTE (doc); visLines = DocVisLines (doc); SetScrollMax (doc); caretLine = LineWithCaret (hTE); topVisLine = LinesOffTop (hTE); if ((d = caretLine - topVisLine) < 0) ScrollText (doc, d == -1 ? -1 : d - visLines / 2); else if (( d = caretLine - (topVisLine + visLines - 1)) > 0) ScrollText (doc, d == 1 ? 1 : d + visLines / 2); else SetScrollValue (scroll, topVisLine); SetScrollMax (doc); /* might have changed from scrolling */ } /* * Overhaul the entire display. This is called after catastrophic * events, such as resizing the window, or changes to the word * wrap style. It makes sure the view and destination rectangles * are sized properly, and that the bottom line of text never * scrolls up past the bottom line of the window (if there's * enough to fill the window), and that the scroll bar max and * current values are set properly. * * Resizing the dest rect just means resetting the right edge * (the top is NOT reset), since text might be scrolled off the * top (i.e., destRect.top != 0). * * Doesn't redraw the control, though! */ static void OverhaulDisplay (DocHandle doc, Boolean showCaret, Boolean recalc) { TEHandle hTE; Rect r; hTE = DocTE (doc); r = (**hTE).viewRect; /* erase current viewRect */ EraseRect (&r); CalcEditRect (DocWind (doc), &r); /* calculate new viewRect */ (**hTE).destRect.right = r.right; (**hTE).viewRect = r; if (recalc) TECalText (hTE); /* recalculate line starts */ DocVisLines (doc) = (r.bottom - r.top) / (**hTE).lineHeight; /* * If there is text, but none of it is visible in the window * (it's all scrolled off the top), pull some down. */ if (showCaret) AdjustDisplay (doc); else SetScrollMax (doc); TEUpdate (&r, hTE); } /* ---------------------------------------------------------------- */ /* Window Handler Routines */ /* ---------------------------------------------------------------- */ /* * Handle mouse clicks in window. The viewRect is never tested * directly, because if it were, clicks along the top, left and * bottom edges of the window wouldn't register. */ static pascal void Mouse (Point thePt, long t, short mods) { WindowPtr w; DocHandle doc; ControlHandle scroll; short thePart; short oldCtlValue; GetPort (&w); doc = WindDocHandle (w); scroll = DocScroll (doc); if ((thePart = TestControl (scroll, thePt)) == inThumb) { oldCtlValue = GetCtlValue (scroll); if (TrackControl (scroll, thePt, nil) == inThumb) ScrollText (doc, GetCtlValue (scroll) - oldCtlValue); } else if (thePart != 0) { scrollDoc = doc; /* set globals for TrackScroll */ scrollPart = thePart; (void) TrackControl (scroll, thePt, &TrackScroll); } else if (PtInText (w, thePt)) { scrollDoc = doc; /* set global for AutoScroll */ TEClick (thePt, (mods & shiftKey) != 0, DocTE (doc)); } SetScrollMax (doc); } /* * Handle key clicks in window */ static pascal void Key (short c, short code, short mods) { WindowPtr w; DocHandle doc; GetPort (&w); doc = WindDocHandle (w); if (c != enter) TEKey (c, DocTE (doc)); AdjustDisplay (doc); DocDirty (doc) = true; if (DocKeyProc (doc) != nil) /* report event to the host */ (*DocKeyProc (doc)) (); } /* * Update window. The update event might be in response to a * window resizing. If so, move and resize the scroll bar, * and recalculate the text display. * * The ValidRect call is done because the HideControl adds the * control bounds box to the update region - which would generate * another update event! Since everything is redrawn below anyway, * the ValidRect is used to cancel the update. */ static pascal void Update (Boolean resized) { WindowPtr w; DocHandle doc; ControlHandle scroll; TEHandle hTE; Rect r; GetPort (&w); doc = WindDocHandle (w); scroll = DocScroll (doc); hTE = DocTE (doc); if (resized) { r = w->portRect; EraseRect (&r); HideControl (scroll); r = (**scroll).contrlRect; ValidRect (&r); CalcScrollRect (w, &r); SizeControl (scroll, 16, r.bottom - r.top); MoveControl (scroll, r.left, r.top); OverhaulDisplay (doc, false, (**hTE).crOnly >= 0); ShowControl (scroll); } else { OverhaulDisplay (doc, false, false); if (WindowIsActive (w)) DrawControls (w); /* redraw scroll bar */ else { /* draw outline of scroll, erase interior */ r = (**scroll).contrlRect; FrameRect (&r); InsetRect (&r, 1, 1); EraseRect (&r); } } DrawGrowBox (w); } /* * When the window comes active, highlight the scroll bar appropriately. * When the window is deactivated, hide the scroll bar (this is drawn * immediately rather than invalidating the rectangle and waiting for * Update(), because that just seems too slow). * * Redraw the grow box in any case. Set the cursor (FixCursor avoids * changing it from an ibeam to an arrow back to an ibeam, in the case * where one edit window is going inactive and another is coming * active). * * Report the event to the host. */ static pascal void Activate (Boolean active) { WindowPtr w; DocHandle doc; ControlHandle scroll; TEHandle hTE; RgnHandle oldClip; Rect r; GetPort (&w); doc = WindDocHandle (w); scroll = DocScroll (doc); hTE = DocTE (doc); DrawGrowBox (w); if (active) { TEActivate (hTE); HiliteScroll (doc); ShowControl (scroll); } else { TEDeactivate (hTE); /* hide scroll but don't show it being hidden */ oldClip = NewRgn (); GetClip (oldClip); SetRect (&r, 0, 0, 0, 0); ClipRect (&r); HideControl (scroll); SetClip (oldClip); DisposeRgn (oldClip); /* now erase inside of scroll (but not outline, to avoid flicker) */ r = (**scroll).contrlRect; /* erase scroll */ InsetRect (&r, 1, 1); /* but not outline */ EraseRect (&r); } FixCursor (iBeamCursor); if (DocActivateProc (doc) != nil) /* report event to the host */ (*DocActivateProc (doc)) (active); } /* * Close box was clicked. If user specified notify proc, call it. * Otherwise do default close operation (ask about saving if dirty, * etc.). */ static pascal void Close (void) { WindowPtr w; DocHandle doc; GetPort (&w); doc = WindDocHandle (w); if (DocCloseProc (doc) != nil) (*DocCloseProc (doc)) (); else (void) EWindowClose (w); } /* * Clobber an edit window. This routine is written defensively on the * assumption that not all pieces of a complete edit window are present. * This allows it to be called by SkelRmveWind() during window creation * attempts if allocations fail. * * The window's skelWPropEditWind property structure will be disposed * of by TransSkel, but the data associated with it (returned by * WindDocHandle()) must be disposed of here. * * At this point it's too late to back out if any changes have been * made to the text. */ static pascal void Clobber (void) { WindowPtr w; DocHandle doc; TEHandle hTE; GetPort (&w); doc = WindDocHandle (w); /* * Toss document record and any pieces that exist */ if (doc != (DocHandle) nil) { if ((hTE = DocTE (doc)) != (TEHandle) nil) TEDispose (hTE); /* toss text record */ DisposeHandle ((Handle) doc); } DisposeWindow (w); /* toss window (scroll bar, too) */ FixCursor (iBeamCursor); } /* * Blink the caret and make sure the cursor's an i-beam when it's * in the non-scrollbar part of the window. */ static pascal void Idle (void) { WindowPtr w; DocHandle doc; GetPort (&w); doc = WindDocHandle (w); TEIdle (DocTE (doc)); /* blink that caret! */ FixCursor (iBeamCursor); } /* ---------------------------------------------------------------- */ /* Internal File Routines */ /* ---------------------------------------------------------------- */ /* * Save the contents of the edit window. If there is no file bound * to the window, ask for a file name. If askForFile is true, ask * for a name even if the window is currently bound to a file. If * bindToFile is true, bind the window to the file written to (if * that's different than the currently bound file), and clear the * window's dirty flag. * * Return true if the file was written without error. Return false * if (a) user was asked for name and clicked Cancel (b) there was * some error writing the file. In the latter case, the window is * not bound to any new name given by user. * * Always returns false if the window isn't an edit window. This * simplifies EWindowSave, EWindowSaveAs, EWindowSaveCopy. (They * don't do the test.) */ static Boolean SaveFile (WindowPtr w, Boolean askForFile, Boolean bindToFile) { DocHandle doc; TEHandle hTE; short f; FInfo fndrInfo; /* finder info */ SFReply tmpFile; Handle hText; long count; OSErr result; Boolean haveNewFile = false; if (!IsEWindow (w)) return (false); doc = WindDocHandle (w); hTE = DocTE (doc); tmpFile = DocFile (doc); /* initialize default name */ if (DocBound (doc) == false || askForFile) { /*SFPPutFile (dlogWhere, "\pSave file as:", editFile.fName, nil, &tmpFile, putDlgID, SkelDlogFilter (nil, false));*/ SFPPutFile (dlogWhere, "\pSave file as:", tmpFile.fName, nil, &tmpFile, putDlgID, SkelDlogFilter (nil, false)); SkelRmveDlogFilter (); SkelDoUpdates (); if (!tmpFile.good) return (false); else { haveNewFile = true; if (GetFInfo (tmpFile.fName, tmpFile.vRefNum, &fndrInfo) == noErr) /* exists */ { if (fndrInfo.fdType != 'TEXT') { ErrMesg ("\pNot a TEXT File"); return (false); } } else /* doesn't exist. create it. */ { if (Create (tmpFile.fName, tmpFile.vRefNum, creator, 'TEXT') != noErr) { ErrMesg ("\pCan't Create"); return (false); } } } } if (FSOpen (tmpFile.fName, tmpFile.vRefNum, &f) != noErr) ErrMesg ("\pCan't Open"); else { FixCursor (watchCursor); (void) SetFPos (f, fsFromStart, 0L); hText = (**hTE).hText; HLock (hText); count = (**hTE).teLength; result = FSWrite (f, &count, *hText); (void) GetFPos (f, &count); (void) SetEOF (f, count); (void) FSClose (f); (void) FlushVol (nil, tmpFile.vRefNum); HUnlock (hText); FixCursor (iBeamCursor); if (result == noErr) { if (bindToFile) { DocDirty (doc) = false; if (haveNewFile) /* name different than current */ { SetWTitle (w, tmpFile.fName); DocBound (doc) = true; DocFile (doc) = tmpFile; } } return (true); } ErrMesg ("\pWrite error!"); } return (false); } /* * Read file from disk. Return value indicates whether or not the * file was read in successfully. */ static Boolean ReadFile (DocHandle doc) { TEHandle hTE; SFReply sfReply; Boolean result = false; short f; long len; Handle h; hTE = DocTE (doc); sfReply = DocFile (doc); FixCursor (watchCursor); if (FSOpen (sfReply.fName, sfReply.vRefNum, &f) != noErr) ErrMesg ("\pCouldn't open file"); else { (void) GetEOF (f, &len); if (len >= maxFileSize) ErrMesg ("\pFile is too big"); else { h = (Handle) TEGetText (hTE); SetHandleSize (h, len); HLock (h); (void) FSRead (f, &len, *h); HUnlock (h); (**hTE).teLength = len; TESetSelect (0L, 0L, hTE); /* set caret at start */ DocDirty (doc) = false; result = true; } (void) FSClose (f); } FixCursor (iBeamCursor); return (result); } /* ------------------------------------------------------------ */ /* Interface (Public) Lowest-level Routines */ /* ------------------------------------------------------------ */ /* * Return true/false to indicate whether the window is really an * edit window. */ pascal Boolean IsEWindow (WindowPtr w) { return ((Boolean) (WindDocHandle (w) != nil)); } /* * Return true/false to indicate whether the text associated with * the window has been changed since the last save/revert (or since * created, if not bound to file). */ pascal Boolean IsEWindowDirty (WindowPtr w) { DocHandle doc; if ((doc = WindDocHandle (w)) != nil) return (DocDirty (doc)); return (false); } /* * Return a handle to the TextEdit record associated with the edit * window, or nil if it's not an edit window */ pascal TEHandle GetEWindowTE (WindowPtr w) { DocHandle doc; return ((doc = WindDocHandle (w)) == nil ? nil : DocTE (doc)); } /* * Return true/false depending on whether or not the editor is * bound to a file, and a copy of the file info in the second * argument. Pass nil for fileInfo if only want the return status. * Returns false if it's not an edit window. */ pascal Boolean GetEWindowFile (WindowPtr w, SFReply *fileInfo) { DocHandle doc; if ((doc = WindDocHandle (w)) != nil) { if (fileInfo != nil) *fileInfo = DocFile (doc); return (DocBound (doc)); } return (false); } /* ---------------------------------------------------------------- */ /* Interface Display Routines */ /* ---------------------------------------------------------------- */ /* * Install event notification procedures for an edit window. */ pascal void SetEWindowProcs (WindowPtr w, TEditKeyProcPtr pKey, TEditActivateProcPtr pActivate, TEditCloseProcPtr pClose) { DocHandle doc; if (w == nil) /* reset window creation defaults */ { e_key = pKey; e_activate = pActivate; e_close = pClose; } else if ((doc = WindDocHandle (w)) != (DocHandle) nil) { DocKeyProc (doc) = pKey; DocActivateProc (doc) = pActivate; DocCloseProc (doc) = pClose; } } /* * Change the text display characteristics of an edit window * and redisplay it. * * Scroll to home position before overhauling, because although * the overhaul sets the viewRect to display an integral number * of lines, there's no guarantee that the destRect offset will * also be integral except at home position. Clipping is set to * an empty rect so the scroll doesn't show. */ pascal void SetEWindowStyle (WindowPtr w, short font, short size, short wrap, short just) { DocHandle doc; GrafPtr savePort; FontInfo f; TEHandle hTE; Rect r; short oldWrap; if (w == nil) /* reset window creation defaults */ { e_font = font; e_size = size; e_wrap = wrap; e_just = just; return; } if ((doc = WindDocHandle (w)) != (DocHandle) nil) { GetPort (&savePort); SetPort (w); hTE = DocTE (doc); GetPort (&savePort); ScrollToHome (hTE); oldWrap = (**hTE).crOnly; (**hTE).crOnly = wrap; /* set word wrap */ TESetJust (just, hTE); /* set justification */ TextFont (font); /* set the font and point size */ TextSize (size); /* of text record */ GetFontInfo (&f); (**hTE).lineHeight = f.ascent + f.descent + f.leading; (**hTE).fontAscent = f.ascent; (**hTE).txFont = font; (**hTE).txSize = size; OverhaulDisplay (doc, true, (oldWrap >= 0 || wrap >= 0)); SetPort (savePort); } } /* * Redo display. Does not save current port. This is used by hosts * that mess with the text externally to TransEdit. The arguments * determine whether the text is scrolled to show the line with the * caret, whether the lineStarts are recalculated, and whether or not * the text should be marked dirty. */ pascal void EWindowOverhaul (WindowPtr w, Boolean showCaret, Boolean recalc, Boolean dirty) { DocHandle doc; if ((doc = WindDocHandle (w)) != (DocHandle) nil) { OverhaulDisplay (doc, showCaret, recalc); DrawControls (w); DocDirty (doc) = dirty; } } /* ---------------------------------------------------------------- */ /* Interface Menu Routine */ /* ---------------------------------------------------------------- */ /* * Do Edit menu selection. This is only valid if an edit * window is frontmost. Cut, Paste, and Clear cause the window * to be marked dirty. Copy does not. Undo is unimplemented. */ pascal void EWindowEditOp (short item) { DocHandle doc; TEHandle hTE; Boolean modified = false; if (SystemEdit (item - 1)) return; if ((doc = WindDocHandle (FrontWindow ())) == (DocHandle) nil) return; /* not an edit window */ hTE = DocTE (doc); switch (item) { /* * cut selection, put in TE Scrap, clear clipboard and put * TE scrap in it */ case cut: TECut (hTE); (void) ZeroScrap (); (void) TEToScrap (); modified = true; break; /* * copy selection to TE Scrap, clear clipboard and put * TE scrap in it */ case copy: TECopy (hTE); (void) ZeroScrap (); (void) TEToScrap (); break; /* * get clipboard into TE scrap, put TE scrap into edit record */ case paste: (void) TEFromScrap (); TEPaste (hTE); modified = true; break; /* * delete selection without putting into TE scrap or clipboard */ case clear: (void) TEDelete (hTE); modified = true; break; } AdjustDisplay (doc); if (modified) DocDirty (doc) = true; } /* ---------------------------------------------------------------- */ /* Interface File Routines */ /* ---------------------------------------------------------------- */ /* * Set file creator for any files created by TransEdit */ pascal void SetEWindowCreator (OSType creat) { creator = creat; } /* * Save the contents of the given window */ pascal Boolean EWindowSave (WindowPtr w) { return (SaveFile (w, /* window to save */ false, /* don't ask for file if have one */ true)); /* bind to new file if one given */ } /* * Save the contents of the given window under a new name * and bind to that name. */ pascal Boolean EWindowSaveAs (WindowPtr w) { return (SaveFile (w, /* window to save */ true, /* ask for file even if have one */ true)); /* bind to new file if one given */ } /* * Save the contents of the given window under a new name, but * don't bind to the name. */ pascal Boolean EWindowSaveCopy (WindowPtr w) { return (SaveFile (w, /* window to save */ true, /* ask for file even if have one */ false)); /* don't bind to file */ } /* * Close the window. If it's dirty and is either bound to a file * or (if not bound) has some text in it, ask about saving it first, * giving user option of saving changes, tossing them, or * cancelling altogether. * * Return true if the file was saved and the window closed, false if * user cancelled or there was an error. */ pascal Boolean EWindowClose (WindowPtr w) { DocHandle doc; TEHandle hTE; SFReply sfReply; if ((doc = WindDocHandle (w)) == (DocHandle) nil) return (false); hTE = DocTE (doc); sfReply = DocFile (doc); if ( (DocBound (doc) || (**hTE).teLength > 0) && DocDirty (doc)) { switch (FakeAlert ("\pSave changes to \322", sfReply.fName, "\p\323?", "\p", 3, 1, 2, "\pSave", "\pCancel", "\pDon\325t Save")) /* ask whether to save */ { case 1: if (SaveFile (w, /* window to save */ false, /* don't ask for name */ false) /* don't bind to name */ == false) return (false); /* canceled or error - cancel Close */ break; case 2: /* cancel Close */ return (false); case 3: /* toss changes */ break; } } SkelRmveWind (w); return (true); } /* * Revert to saved version of file on disk. The window must be an edit * window, and must be bound to a file. Returns false if one of these * conditions is not met, or if they are met but there was an error * reading the file. * * The window need not be dirty; if it is, the user is asked * whether to really revert. */ pascal Boolean EWindowRevert (WindowPtr w) { DocHandle doc; TEHandle hTE; SFReply sfReply; if ((doc = WindDocHandle (w)) == (DocHandle) nil) return (false); if (!DocBound (doc)) return (false); /* no file to revert to */ if (DocDirty (doc)) { sfReply = DocFile (doc); if (FakeAlert ("\p\322", sfReply.fName, "\p\323 has been changed. Really revert?", "\p", 2, 1, 1, "\pCancel", "\pRevert", "\p") == 1) return (false); } if (ReadFile (doc) == false) return (false); ScrollToHome (DocTE (doc)); OverhaulDisplay (doc, true, true); DrawControls (w); ValidRect (&w->portRect); return (true); } /* ---------------------------------------------------------------- */ /* Interface Initialization/Termination Routines */ /* ---------------------------------------------------------------- */ /* * Set up the SFReply record for a document. Ask for a file and use * the file selected if bindToFile is true. Otherwise use title for * the name if it's non-nil, or make the document untitled if title is * nil or the empty string. * * Copy the name into the file info structure even if the window is * unbound, because the Save operations expect to find it there as the * most likely name to use. */ static Boolean SetupFile (StringPtr title, Boolean bindToFile, SFReply *sfReply) { Str255 s; OSType type = 'TEXT'; sfReply->vRefNum = 0; if (bindToFile) { SFPGetFile (dlogWhere, "\p", nil, 1, &type, nil, sfReply, getDlgID, SkelDlogFilter (nil, false)); SkelRmveDlogFilter (); return (sfReply->good); } if (title == nil || title[0] == 0) { Untitled (s); title = s; } BlockMove (title, sfReply->fName, (long) (title[0] + 1)); return (true); } /* * Create and initialize an edit window and the associated data * structures. If the window and data cannot be allocated, destroy * the window and return nil. Otherwise return the window. * * The caller should set and restore the port before and after calling * SetupDocWind(). */ static WindowPtr SetupDocWind (WindowPtr w, Boolean visible, Boolean bindToFile, SFReply *sfReply) { DocHandle doc; SkelWindPropHandle prop; ControlHandle scroll; TEHandle hTE; Rect r; if (!SkelWindow (w, /* the window */ Mouse, /* mouse click handler */ Key, /* key click handler */ Update, /* window updating procedure */ Activate, /* window activate/deactivate procedure */ Close, /* window close procedure */ Clobber, /* window disposal procedure */ Idle, /* idle proc */ true)) /* idle only when frontmost */ { DisposeWindow (w); return (nil); } /* * After this point SkelRmveWind() can be called to remove the window * if any allocations fail. */ /* * Get new document record, attach to window property list. * Also make document record point to window. */ if (!SkelAddWindProp (w, skelWPropEditWind, (long) 0L)) { SkelRmveWind (w); return (nil); } doc = New (DocRecord); if (doc == (DocHandle) nil) { SkelRmveWind (w); return (nil); } prop = SkelGetWindProp (w, skelWPropEditWind); (**prop).skelWPropData = (long) doc; DocWind (doc) = w; /* * Build the scroll bar. Make sure the borders overlap the * window frame and the frame of the grow box. */ CalcScrollRect (w, &r); scroll = NewControl (w, &r, "\p", true, 0, 0, 0, scrollBarProc, 0L); DocScroll (doc) = scroll; /* * Create the TE record used for text display. Use default * characteristics. */ CalcEditRect (w, &r); hTE = TENew (&r, &r); DocTE (doc) = hTE; SetClikLoop (AutoScroll, hTE); /* set autoscroll proc */ if (scroll == (ControlHandle) nil || hTE == (TEHandle) nil) { SkelRmveWind (w); return (nil); } /* * Install default event notification procedures, font characteristics. */ SetEWindowProcs (w, e_key, e_activate, e_close); SetEWindowStyle (w, e_font, e_size, e_wrap, e_just); DocFile (doc) = *sfReply; DocBound (doc) = bindToFile; if (bindToFile && !ReadFile (doc)) { SkelRmveWind (w); return (nil); } DocDirty (doc) = false; /* * Show window if specified as visible, and return a pointer to it. */ OverhaulDisplay (doc, true, true); if (visible) ShowWindow (w); return (w); } /* * Initialize the window and associated data structures. * Return window pointer or nil if some sort of error. * * Preserves the current port. If the window is visible, * an activate event will follow, at which point the port * will be set to the window. * * If a file is to be solicited, ask for it before creating the * window to avoid the following problem: If you create a window * to be frontmost but invisible, putting up the getfile dialog * after creating the window causes the window to go behind the * first visible window when the file dialog goes away. */ pascal WindowPtr NewEWindow (Rect *bounds, StringPtr title, Boolean visible, WindowPtr behind, Boolean goAway, long refNum, Boolean bindToFile) { WindowPtr w; GrafPtr savePort; SFReply sfReply; if (!SetupFile (title, bindToFile, &sfReply)) /* user cancelled */ return (nil); if (SkelQuery (skelQHasColorQD)) { w = NewCWindow (nil, bounds, sfReply.fName, false, documentProc + 8, behind, goAway, refNum); } else { w = NewWindow (nil, bounds, sfReply.fName, false, documentProc + 8, behind, goAway, refNum); } if (w != (WindowPtr) nil) { GetPort (&savePort); w = SetupDocWind (w, visible, bindToFile, &sfReply); /* nil if allocation failed */ SetPort (savePort); } return (w); } /* * GetNewEWindow() is the resource equivalent of NewEWindow(). It reads in the * 'WIND' template and yanks window creation values out of it rather than using * GetWindow() since the latter would create the window visible right away. * NewEWindow() doesn't show the window until after the file has been read in, if * a file is to be read. procID value from resource is ignored. */ /* 'WIND' resource structure */ typedef struct WTmpl WTmpl, **WTHandle; struct WTmpl { Rect bounds; short procId; short visible; short goAway; long refCon; Str255 title; }; pascal WindowPtr GetNewEWindow (short resourceNum, WindowPtr behind, Boolean bindToFile) { WTHandle h; WindowPtr w; h = (WTHandle) GetResource ('WIND', resourceNum); if (h == (WTHandle) nil) return ((WindowPtr) nil); MoveHHi ((Handle) h); HLock ((Handle) h); w = NewEWindow (&((**h).bounds), (**h).title, (Boolean) ((**h).visible != 0), behind, (Boolean) ((**h).goAway != 0), (**h).refCon, bindToFile); HUnlock ((Handle) h); return (w); } /* * Look through the list of windows, shutting down all the edit * windows. If any window is dirty, ask user about saving it first. * If the user cancels on any such request, ClobberEWindows returns * false. If all edit windows are shut down, return true. It is * then safe for the host to exit. * * When a window *is* shut down, have to start looking through the * window list again, since w no longer points anywhere * meaningful. */ pascal Boolean ClobberEWindows (void) { WindowPtr w; for (;;) { for (w = FrontWindow (); w != nil; w = (WindowPtr) ((WindowPeek) w)->nextWindow) { if (IsEWindow (w)) break; } if (w == nil) return (true); /* all edit windows are shut down */ if (w != FrontWindow ()) { SelectWindow (w); ShowWindow (w); EWindowOverhaul (w, false, false, IsEWindowDirty (w)); SetPort (w); ValidRect (&w->portRect); } if (EWindowClose (w) == false) return (false); /* cancel or error */ } }