TweetFollow Us on Twitter

Track, Field 1
Volume Number:6
Issue Number:2
Column Tag:Intermediate Mac'ing

Related Info: List Manager Script Manager TextEdit

Mouse Track and Field, Part I

By Martin Minow, Arlington, VA

Note: Source code files accompanying article are located on MacTech CD-ROM or source code disks.

[Martin Minow is a Principal Engineer at Digital Equipment Corporation. He started programming on Illiac-1 in 1962 and still misses its simplicity (but not the turn-around times). In his spare time, he runs with the Hash House Harriers, runs one marathon a year (slowly and painfully), and orienteers (even more slowly).]

Introduction

There’s an old vaudeville joke that goes something like this:

“The Thermos bottle is the greatest invention of the century.”

“Why?”

“Because it keeps hot food hot and cold food cold.”

“What’s so special about that?”

“How does it know?”

You could ask the same question about the mouse: a few minutes after you start to use the Macintosh, your hand understands how to use it to select text or graphic objects. But the application only sees the location of the mouse on the screen: how does it connect the horizontal and vertical coordinates of a pixel to the program’s data? This article shows you how the mouse works: how it tracks text objects and fields button presses.

Unfortunately, the “official” documentation isn’t much help: Inside Macintosh has only a few paragraphs describing the way selection is supposed to work -- in volume 1, page 63 -- and this information isn’t elaborated on in the Human Interface Guidelines.

For small amounts of text, such as in dialogs, you can use the TextEdit library and its TEClick routine (described in a number of MacTutor articles). For simple lists and spreadsheets, you can use the List Manager’s LClick function (also described in a number of MacTutor articles). However, if your data doesn’t fit into these two categories, or you agree with Apple that “TextEdit is not a text editor,” you have to roll your own.

For the last year or so, I’ve been “midnite-hacking” on a terminal emulator that has the features I want and doesn’t have the options I don’t need. I wanted to add mouse selection and cut/paste, so I could download a file by listing the directory on the screen, then click on a file name and use cut and paste to transmit the file name to the host computer. Since my emulator can’t use TextEdit, I was forced to confront the central question: how does the mouse actually work?

There were two alternatives, neither of them pleasant:

• Decompile the ROMs and trace through TextEdit. (Boring work: to be avoided at all costs.)

• Stare at the way the mouse works in a word-processor and dope out the algorithm, possibly with some improvements. (Real work: to be avoided at all costs.)

Extracting the mouse-track algorithm from the ROMs seemed at first like a good choice, especially since I could call this a trackeotomy. However, decompiling the ROMs also appeared to require more effort than figuring out the method from scratch, so I sat down with a block of paper and started scribbling before and after selections to work out the algorithm.

Then I worked it out again. And again: always missing some small detail. Finally, believe it or not, I fell asleep over the code and woke the next morning with the missing insight. As far as I can tell, my algorithm works identically to Apple’s. Of course, I’ve been wrong before and I’m certain that MacTutor readers will enjoy finding the lingering bugs in my code.

After I showed a sample program running to a reviewer, he suggested that I add enough additional code to replace TextEdit. This seemed like a good idea, but turned out to add a rather large amount of code, large enough to require splitting the article into two parts. In this article, I’ll describe how mouse selection works, and present the demo program, header file, and the mouse selection routine itself: this is all you need if you want use the mouse in your own programs.

Part II has little text, but the code section contains the entire TextEdit replacement library.

The Selection Algorithm

The TrackClick subroutine processes mouse clicks, handles shift and double-click options and, eventually, sets the selStart and selEnd structure elements to values that delimit the selection. TrackClick has a beginning, middle, and end:

• an initialization section checks for shift- and double-clicks,

• a middle phase tracks the mouse until the user releases the button,

• a final phase records the information needed by the rest of the program.

TrackClick is called when the main event-loop receives a mouse down event in the content portion of a document window. Its parameters are identical to the Toolbox TEClick function. (Of course, the TrackRecord structure replaces TextEdit’s TEHandle.) Initialization proceeds as follows:

1. The program converts the mouse value from global to window-local coordinates and then to a value, dot, that uniquely identifies a character in the text. (dot is a term from TECO, one of the first text editors.) If you are adapting this algorithm to a drawing program, you would reduce the coordinates to a drawing object specifier. The important point is that every selectable entity must have a unique and distinct dot value.

Also, it must be possible to proceed from the start to the end of the document and back again by manipulating dot. For example, in a drawing program, dot might be a pointer to structure with forward and backward links.

2. Next, it checks for a shift-click that extends the current selection. If so, the program uses selStart from the previous call, inverts what has to be inverted, and sets selEnd to dot. Note that the algorithm has to deal with three cases: dot can be to the left of the selection, to its right, or inside. The first two are straightforward, and the inside case is treated as a continuation of the previous selection as shown in Fig. 1.

Figure 1. Text Selection

3. If this isn’t a shift-click, the algorithm throws away any current selection and checks for a double-click (which causes the selection algorithm to operate on a word-by-word basis). Double-clicks are defined as two clicks within a defined interval (returned by the Toolbox GetDblTime function) and within a defined space, which the program defines as “the same character.” When TrackClick completes, it will remember the time and mouse position so it can detect a subsequent double-click.

4. If it’s a double-click, the selection is extended to the boundaries of the word, inverted, and a flag set TRUE to trigger word-selection code in the mouse tracking procedure. Also, it sets the last_word variable to the dot value of the first character in order to uniquely identify this word. This will be described further.

The algorithm follows the mouse until the user releases the button. It consists of two loops: an outer loop that follows the mouse as it moves through the text and an inner loop that just stalls while the mouse is on one character.

Hiliting, or the miracle of InvertRect

Once the mouse moves off the current character, the program must extend the selection and communicate that extension to the user. Normal (single-click) selection is simple: invert the text between the mouse’s last position and its current position. This works no matter how the mouse moves. If it’s extending a selection, it hilites the new text. If, on the other hand, the mouse is sweeping back over text that had already been hilited, the second invert restores the text’s original form. It’s that easy. (The subroutine that actually manages selection display will be described in Part II.)

The Mouse Selection Algorithm

The selection algorithm (ignoring word-by-word complications) is actually quite simple:

/* 1 */

/*
 * Initialize: find the character under the
 * mouse and set the selection boundaries
 * to that character.
 */
dot = mouse_to_dot(mouse_position);
start = end = dot;
while (WaitMouseUp()) {
  /*
   * Vamp while the mouse is on
   * the same character.
   */
  temp = dot;
  do {
    GetMouse(&mouse_position);
    dot = mouse_to_dot(mouse_position);
  } while (temp == dot && StillDown());
  /*
   * The mouse moved: invert the selection
   * between end and dot (the old and new
   * ends of the selection).
   */
  invert_selection(dot, end);
  end = dot;
}
clickTime = TickCount();
clickLoc = dot;

When the user releases the mouse button, the subroutine records the time and mouse position so it can detect a subsequent double-click. Also, it adjusts the start and end so selStart precedes selEnd.

Word-selection is not quite as simple

If the program is working on a word-by-word basis, the program locates the start of the current word. If the mouse is on the same word, nothing happens. But if the start of the current word differs from last_word, the selection is adjusted. Note that the actual adjustment depends on whether the mouse is before (to the left in English) or after the selection start. This turned out to be a difficult problem with a simple, but not obvious solution that appeared after several evenings of fiddling with the code.

Not seeing the obvious, I wrote out all possible word-selection cases and found that they collapsed into two main groups, with a few unexpected special tests, as shown in Figure 2. There, the “anchor” word designates where the user first double-clicked; “last” is the word under the mouse (if not “anchor”) before it moved to “new,” its current position. The at_anchor flag is set true when the process starts and whenever the mouse is on the anchor word, and a variable, last, records the last hilite. Note that anchor.start and anchor.end refer to the left and right ends of the word.

/* 2 */

(New > Anchor && at_anchor == TRUE)
last = Anchor.end;
end = New.end;
hilite(last, end)
last = end;
at_anchor = FALSE;

/* 3 */

(New > Anchor && at_anchor == FALSE)
end = New.end;
hilite(last, end);
last = end;
at_anchor = FALSE;

/* 4 */

(New >= Anchor && last >= Anchor.start)
end = New.end;
hilite(last, end);
last = end;
at_anchor = (Anchor == New);
                                                                     
                                       

/* 5 */

(New > Anchor && last < Anchor.start)    J
hilite(last, Anchor.start);
last = Anchor.end
end = New.end;
hilite(last, end);
last = end;
at_anchor = FALSE;

/* 6 */

(New < Anchor  && at_anchor == TRUE)
last = Anchor.start;
end = New.start;
hilite(last, end)
last = end;
at_anchor = FALSE;

/* 7 */

(New < Anchor && at_anchor == FALSE)
end = New.start;
hilite(last, end);
last = end;
at_anchor = FALSE;

/* 8 */

(New <= Anchor && last <= Anchor.start)
end = New.start;
hilite(last, end);
last = end;
at_anchor = (Anchor == New);
                                                                     
                                       

/* 8 */

(New < Anchor && last > Anchor.start)    J
hilite(last, Anchor.end);
last = Anchor.start;
end = New.start;
hilite(last, end);
last = end;
at_anchor = FALSE;

Figure 2. Word-selection conditions

By diagramming the conditions in such obsessive detail, the algorithm became clear, and all I had left to do was to optimize to combine common code segments. (It should be pointed out, however, that I had been drawing similar diagrams for several months: I had to put all of the cases on a single sheet of paper before the algorithm appeared.) The two cases marked with a J show that it is possible for the new selection point to jump directly to the other side of the original word. This happens -- assuming English -- when the mouse moves left-to-right in the anchor line, then directly up one line (and similarly for “right-to-left, then down” movement). In these cases, the code must make sure that the anchor word stays selected. When the mouse moves, the selection goes between the word under the mouse and the far end of the anchor word.

Part of the problem with word-selection is that, unlike “normal” selection, the anchor word is always hilited. This prevented the simple “invert from last to current” algorithm from working. Also, the program must invert to the edge of the word furthest from the anchor.

The word-by-word selection algorithm -- after optimization -- is sketched as follows:

/* 9 */

typedef struct {
  DOT      start;
  DOT      end;
} _Track_Loc;
_Track_Loc  anchor;
_Track_Loc  new;

/*
 * Check for a double-click.
 */
dot = mouse_to_dot(mouse_position);
if ((TickCount() - clickTime) <= GetDblTime()
 && dot == clickLoc) {
  /*
   * Initialize for word-selection: find the
   * word under the mouse, set the anchor
   * boundaries to that word, and hilite it.
   * The extend_selection subroutine
   * determines the start and end of the
   * word under the mouse.
   */
  extend_selection(mouse_position, &anchor);
  hilite_selection(anchor.start,anchor.end);
  new = anchor;
  last_word_start = anchor.start;
  at_anchor = TRUE;
}
while (WaitMouseUp()) {
  /*
   * Vamp while the mouse is on the same word.
   */
  do {
    GetMouse(&mouse_position);
    extend_selection(mouse_position, &new);
  } while (new.start == last_word_start
      && StillDown());
  /*
   * The mouse moved to a different word.
   */
  last_word_start = new.start;
  if (new.start < anchor.start) {
    new_end = new.start;
    if (at_anchor)
     last_end = anchor.start;
    else if (last_end > anchor.start) {      J
     hilite_selection(last_end, anchor.end);
     last_end = anchor.start;
    }
  }
  else /* new.start >= anchor.start */ {
    new_end = new.end;
    if (at_anchor)
 last_end = anchor.end;
    else if (last_end < anchor.start) {      J
            hilite_selection(last_end, anchor.start);
     last_end = anchor.end;
    }
  }
  hilite_selection(last_end, new_end);
  last_end = new_end;
  at_anchor = (new.start == anchor.start);
}    /* While the mouse is still down */

The two statements marked J implement the similarly marked special cases in Figure 2.

Support Subroutines

The mouse selection algorithm needs a few subroutines to make things work:

mouse_to_dot converts “raw” (window-local) mouse coordinates to the dot specifier. The code is fairly straightforward for a text editor, but might require some thinking if you’re writing a graphics program (especially since the mouse might not be on any graphics object during part of the selection process).

hilite_selection inverts the display between the start and end (expressed in dot units). It must understand that selections may extend over more than a single row. My implementation (see Part II), differs from the standard Macintosh routine in that it doesn’t invert the display out to the window edge.

extend_selection is used for word-selection to convert mouse coordinates to the dot delimiters of an entire word. My implementation uses some of the capabilities of the Macintosh FindWord routine, and would need some work to exploit its multi-language capabilities.

As you will see from the sample code, the actual routines use slightly different names and calling sequences.

The Sample Program

The library was developed using a trivial application that displays a paragraph of text in a window and lets you use the mouse to select, cut, copy, and paste as you wish. You can also import and export text using the clipboard and launch desk accessories. You can’t print, read, or write files.

The program provides menu options to select whether to use the Script Manager and intelligent cut and paste. It works slightly differently depending on which options are selected. After all, to misquote Chaucer’s Wife of Bath, “it’s a poor mouse that has but one algorithm.” (“I hold a mouses herte not worth a leek, That hath but oon hole for to sterten to.” Of course, Chaucer was in turn misquoting Plautus.)

The sample program isn’t very interesting; read the code to see how it works. If you’re just starting to program the Macintosh, you might find that the code offers a useful, if undocumented, view of a minimal application skeleton. Although I haven’t cut too many corners, a real application needs quite a bit more code in order to work well on the Macintosh. For example, all error handling is done by calling the Debugger. (I wrote and debugged the program using Think C version 3.)

In the sample program, a pointer to the TrackRecord is stored in the window’s refCon. Although this is overkill for this sample application, keeping all of the active data in a structure allowed the program to be written without global variables. This makes it very simple to generalize the program to multiple windows. Note that the TrackRecord structure contains a reference variable that your program is free to use as it wishes.

Real Life

You should be able to incorporate the mouse selection algorithm into your applications without too much difficulty. You will, of course, have to adapt the data storage and display routines to your specific needs. If you use a structure to identify your objects, you’ll have to write a comparison function that replaces my simple if statements. Also, if you are writing a text editor, you should add the TextEdit capabilities, such as tabs, styles, and multi-direction.

Adapting the routines for multi-directional text is not trivial. Apple has published some documentation on the ScriptManager subroutines that you should consult in addition to the description in Inside Macintosh. (Remember this paragraph: it’s genuine computerese for “I couldn’t figure it out.”)

When you do adapt the routines to your own needs, don’t forget to put some effort into error recovery and making them faster. Also, a “real” application should handle display updating better (i..e., faster).

References and Acknowledgements

Inside Macintosh, Volumes I-V, and the Apple Human Interface Guidelines are published by Addison-Wesley Publishing Company.

Small parts of the demo application are based on code published in Stephen Chernicoff’s Macintosh Revealed, Volume Two, published by Hayden Books.

Thanks to Mike Morton for the suggestion to rewrite the subroutines as a coherent unit and to Shirley Marotta for the mouse drawing.

Copyright and Ownership

These routines are Copyright © 1989, Martin Minow and MacTutor. You may copy, modify, and use this software in your applications without restriction as long as all copyright notices remain intact and the applications are not sold for profit. You may not redistribute the source for profit without permission. Please contact me c/o Mactutor if you wish to use these routines in a commercial or shareware product.

Listing:  Mouse Header File
/*   MouseTrack.h   */
/*
 *
 * Copyright © 1989 Martin Minow and MacTutor. All rights reserved.
 *
 * You may incorporate portions of this program in your
 * applications without restriction as long as this
 * copyright notice remains intact and the applications
 * are not sold for profit and this source is not
 * redistributed for profit.
 *
 * Private macros and functions are prefixed by _Track_
 */
#ifndef _MouseTrackHdr_
#define _MouseTrackHdr_
#include <ScriptMgr.h>
/*
 * Compiler-independent datatype definitions.
 */
typedef short           INTEGER;
typedef unsigned short  CHAR;
typedef long            LONGINT;
typedef unsigned long   DOT;

/*
 * All of the information the program needs to process
 * text is contained in the TrackRecord structure.
 * Note that all callback procedures should be declared
 * pascal <type> function.
 */
 typedef struct {
  LONGINT   topPixel;     /* Top line in viewRect       */
  LONGINT   leftPixel;    /* Leftmost pixel in ViewRect */
  INTEGER   lineWidth;    /* Document’s pixel width     */
  Rect      viewRect;     /* View (clip) rectangle      */
  INTEGER   lineHeight;   /* For line spacing           */
  INTEGER   fontAscent;   /* Caret/highlight position   */
  INTEGER   fontDescent;  /* For caret positioning      */
  LONGINT   selRow;       /* For caret positioning      */
  DOT       selStart;     /* Start of selection range   */
  DOT       selEnd;       /* End of selection range     */
  ProcPtr   wordBreak;    /* At a word boundary?        */
  ProcPtr   clikLoop;     /* Called from TrackClick     */
  LONGINT   clickTime;    /* Mouse-up time              */
  DOT       clickLoc;     /* Mouse-up position          */
  LONGINT   caretTime;    /* Used to flash the caret    */
  INTEGER   just;         /* Justification flags        */
  LONGINT   textLength;   /* Total text length          */
  Handle    hText;        /* Text to be selected        */
  INTEGER   crOnly;       /* If < 0, newline at Return  */
  INTEGER   txFont;       /* Text font number           */
  Style     txFace;       /* Character style            */
  INTEGER   txMode;       /* Pen mode                   */
  INTEGER   txSize;       /* Font size                  */
  GrafPtr   inPort;       /* Output grafPort            */
  INTEGER   flags;        /* Internal flags             */
  ProcPtr   highHook;     /* User hilite  procedure     */
  ProcPtr   caretHook;    /* User draw caret procedure  */
  LONGINT   refCon;       /* For user program use       */
  LONGINT   nLines;       /* Number of lines of text    */
  DOT       lineStarts[1];/* Start of each line         */
} TrackRecord, *TrackPtr, **TrackHandle;

/*
 * just contains the following values (identical to
 * TextEdit).
 */
enum {
  trackJustLeft = 0,
  trackJustCenter = 1,
  trackJustRight = -1
};

/*
 * flags contains the following bit-encoded values.
 */
#define _Track_has_script_manager   0x0001
#define _Track_use_script_manager   0x0002
#define _Track_use_smart_cut_paste  0x0004
#define _Track_is_active            0x0008
#define _Track_caret_visible        0x0010
#define _Track_do_autoscroll        0x0020

/*
 * When any Track routine is called, it calls _Track_lock
 * to record the current state of the TrackRecord in this
 * structure.  On exit, _Track_unlock restores the
 * original state.
 */
typedef struct {
  TrackHandle track_handle; /* The track handle itself  */
  INTEGER   oldHState;    /* Handle locked flag         */
  GrafPtr   oldPort;      /* Caller’s GrafPort          */
  RgnHandle oldClip;      /* Previous clip region       */
  INTEGER   oldFont;      /* Previous font              */
  Style     oldFace;      /* Previous text face         */
  INTEGER   oldMode;      /* Previous drawing mode      */
  INTEGER   oldSize;      /* Previous text size         */
} _Track_state;

/*
 * These macros access the private flags in TrackRecord.
 */
#define _Track_set(p, x)        ((p)->flags |= (x))
#define _Track_clear(p, x)      ((p)->flags &= ~(x))
#define _Track_flip(p, x)       ((p)->flags ^= (x))
#define _Track_is_set(p, x)     (((p)->flags & (x)) != 0)

/*
 * “desired state” parameter for _Track_caret()
 */
#define _Track_caret_on     0
#define _Track_caret_off    1
#define _Track_caret_invert 2

/*
 * This structure records both ends of a word.
 */
typedef struct {
  DOT     start;            /* Start of word            */
  DOT     end;              /* End of word              */
} _Track_Loc;

/*
 * User-callable prototypes
 */
void      TrackActivate(TrackHandle);
void      TrackAutoView(Boolean, TrackHandle);
void      TrackCalText(TrackHandle);
void      TrackClick(Point, Boolean, TrackHandle);
void      TrackCopy(TrackHandle);
void      TrackCut(TrackHandle);
void      TrackDeactivate(TrackHandle);
void      TrackDelete(TrackHandle);
void      TrackDispose(TrackHandle);
OSErr     TrackFromScrap(void);
LONGINT   TrackGetHeight(LONGINT, LONGINT, TrackHandle);
DOT       TrackGetOffset(Point, TrackHandle);
void      TrackGetPoint(
            DOT, TrackHandle, LONGINT *, LONGINT *);
LONGINT   TrackGetScrapLen(void);
#define   TrackGetSelectionLength(track_handle) \
  ((*track_handle)->selEnd - (*track_handle)->selStart)
Handle    TrackGetText(TrackHandle);
void      TrackIdle(TrackHandle);
void      TrackInit(void);
void      TrackInsert(Ptr, LONGINT, TrackHandle);
void      TrackKey(CHAR, TrackHandle);
#define   TrackLength(track_handle) \
  ((*track_handle)->textLength)
TrackHandle TrackNew(INTEGER, Rect *);
void      TrackPaste(TrackHandle);
void      TrackPinScroll(LONGINT, LONGINT, TrackHandle);
Handle    TrackScrapHandle(void);
void      TrackScroll(LONGINT, LONGINT, TrackHandle);
void      TrackSelView(TrackHandle);
#define   TrackSetClikLoop(func, track_handle) \
  ((*track_handle)->clikLoop = func)
void      TrackSetJust(INTEGER, TrackHandle);
void      TrackSetScrapLen(LONGINT);
void      TrackSetSelect(DOT, DOT, TrackHandle);
void      TrackSetText(Ptr, LONGINT, TrackHandle);
#define   TrackSetWordBreak(func, h) \
  ((*track_handle)->wordBreak = func)
OSErr     TrackToScrap(void);
void      TrackUpdate(Rect *, TrackHandle);

/*
 * These functions are called internally by the Track
 * library.  They are not be called by user programs.
 * Note: the TrackRecord is locked when these functions
 * are called.  A few functions temporarily unlock the
 * TrackRecord: they return a new TrackPtr value.
 */
void      _Track_autoscroll(TrackPtr, Point *);
void      _Track_caret(TrackPtr, INTEGER);
void      _Track_do_clear(TrackHandle, Boolean, Boolean);
void      _Track_do_insert(TrackPtr, long, Ptr, long);
void      _Track_do_paste(TrackHandle);
void      _Track_do_scroll(TrackPtr, LONGINT, LONGINT);
void      _Track_do_update(TrackPtr, Rect *);
LONGINT   _Track_dot_to_bol(TrackPtr, LONGINT);
LONGINT   _Track_dot_to_col(TrackPtr, DOT);
LONGINT   _Track_dot_to_eol(TrackPtr, LONGINT);
LONGINT   _Track_h_origin(TrackPtr, LONGINT);
void      _Track_hilite(TrackPtr, DOT, DOT);
void      _Track_invert_caret(TrackPtr);
Boolean   _Track_is_white(TrackPtr, char *, DOT);
TrackPtr  _Track_lock(TrackHandle, _Track_state *);
DOT       _Track_mouse_to_dot(TrackPtr, Point);
LONGINT   _Track_pixel_row(TrackPtr, INTEGER);
TrackPtr  _Track_rebuild(TrackHandle, DOT);
LONGINT   _Track_row(TrackPtr, DOT);
LONGINT   _Track_row_pixel(TrackPtr, LONGINT);
void      _Track_unlock(_Track_state *);
void      _Track_word(TrackPtr, Point, _Track_Loc *);

/*
 * Track library private data.
 */
extern Handle   TrackScrpHandle;    /* Track Scrap      */
extern LONGINT  TrackScrpLength;    /* Scrap length     */

#endif
Listing:  Main Program
/*                        MouseDemo.c                   */
/*
 * Mouse selection mainline.
 *
 * Copyright © 1989 Martin Minow and MacTutor. All rights reserved.
 *
 * You may incorporate portions of this program in your
 * applications without restriction as long as this
 * copyright notice remains intact and the applications
 * are not sold for profit and this source is not
 * redistributed for profit.
 */
#include “TrackEdit.h”
/*
 * Set DEBUG 1 to make a very small window so you can
 * use the Think C debugger without getting unwanted
 * window update events.
 */
#define DEBUG           0
#define NIL             (0L)
#define TOP_MARGIN      4
#define SIDE_MARGIN     4
#define sBarWidth       15    /* Scroll bar width       */
#define THE_FONT        “\pHelvetica”
#define FONT_SIZE       18
/*
 * We always store the current TrackHandle in a local
 * variable named track_handle.
 */
#define TR              (**track_handle)
/*
 * height and width define the extants of a rectangle.
 */
#define height(r)       ((r).bottom - (r).top)
#define width(r)        ((r).right - (r).left)

/*
 * Menu organization
 */
enum Menus {
  MENU_Apple      = 1,
  MENU_File       = 256,
  MENU_Edit       = 257,
  MENU_Ctrl       = 258
};

enum Apple_Menu {
  Apple_About = 1
};

enum File_Menu {
  File_Debug = 1,
  Unused1,
  File_Reinitialize,
  Unused2,
  File_Quit
};

enum Edit_Menu {
  Edit_Undo = 1,
  Edit_Skip,
  Edit_Cut,
  Edit_Copy,
  Edit_Paste,
  Edit_Clear
};

enum Control_Menu {
  Ctrl_Script = 1,
  Ctrl_Smart,
  Ctrl_Unused1,
  Ctrl_Hilite,
  Ctrl_Caret,
  Ctrl_ObeyCR,
  Ctrl_AutoScroll,
  Ctrl_Unused2,
  Ctrl_JustLeft,    /* Keep in      */
  Ctrl_JustCenter,  /*  this        */
  Ctrl_JustRight    /*   order.     */
};

#define keyCodeShift  8 
enum Arrow_Keys {
  /*
   * ADB keyboard arrow keys and keypad keys
   */
  Left_Arrow  = (0x7B << keyCodeShift),
  Right_Arrow = (0x7C << keyCodeShift),
  Down_Arrow  = (0x7D << keyCodeShift),
  Up_Arrow    = (0x7E << keyCodeShift),
  Home_Key    = (0x73 << keyCodeShift),
  End_Key     = (0x77 << keyCodeShift),
  Pg_Down_Key = (0x74 << keyCodeShift),
  Pg_Up_Key   = (0x79 << keyCodeShift),
  Help_Key    = (0x72 << keyCodeShift),
  /*
   * Mac-Plus arrow keys.
   */
  Right_Plus  = (0x42 << keyCodeShift),
  Left_Plus   = (0x46 << keyCodeShift),
  Down_Plus   = (0x48 << keyCodeShift),
  Up_Plus     = (0x4D << keyCodeShift)
};

/*
 * This is the data shown in the display.
 */
char        fubar[] =
“The term FUBAR actually first appeared during the reign”
“ of Queen Anne of England (1702-1714), the last ruling”
“ sovereign of the Stuart Dynasty (1603-1714). The Duke”
“ of Marlborough (1650-1722), John Churchill, Sir”
“ Winston’s great great  grandfather, after his great”
“ victory at the battle of Blenhiem (August 13, 1704)”
“ against the French, in Austria, had some captured”
“ French dispatches translated.  The translator,”
“ unfortunately unknown, but believed to be a Lance”
“ Corporal in the Royal Guards, having some difficulty”
“ translating a slang French expression used by Marshall”
“ Tallard, the defeated French general, gave up in”
“ despair and wrote in FUBAR; which, although not”
“ literally translating the dispatch, expressed the”
“ French general’s analysis of the situation.\r”
“Smith-Huxley, J.P., “The Augustan Age of Good Queen”
“ Anne,” pp 386-387, R. Clay Ltd, London, (1903)”
“ SBN 384-82210-2.”;

#define FUBAR_SIZE  (sizeof fubar - 1)  /* No NUL trail */

WindowPtr   the_window;
TrackHandle the_track_handle;
MenuHandle  apple_menu;
MenuHandle  file_menu;
MenuHandle  edit_menu;
MenuHandle  ctrl_menu;

/*
 * Justification flags.  Keep in this order.
 */
INTEGER justify[] = {
  trackJustLeft, trackJustCenter, trackJustRight
};
Boolean     do_autoscroll;

/*
 * These variables manage transfers to/from the desk
 * scrap.  See Chernicoff, Macintosh Revealed, Vol 2.
 */
Boolean     scrap_changed;      /* TRUE after Cut/Copy  */
INTEGER     scrap_count;        /* ZeroScrap counter    */
/*
 * Prototypes
 */
void        do_command(long);
void        do_mouse(EventRecord);
void        enable_edit_menu(TrackHandle, Boolean);
void        get_desk_scrap(void);
void        keyin(TrackHandle, EventRecord);
void        main(void);
pascal void my_caret(Rect *, TrackPtr);
pascal void my_hilite(Rect *, TrackPtr);
void        put_desk_scrap(void);
void        setup(void);
void        get_text_box(Rect *, WindowPtr);

/*
 * main()
 * Mainline: initialize the application, then process
 * mouse and menu events.
 */
void main()
{
    Boolean         is_event;
    WindowPtr       window;
    EventRecord     event;
    GrafPtr         save_port;
    TrackHandle     track_handle;
    Rect            box;

    setup();
    for (;;) {
      SystemTask();
      TrackIdle(the_track_handle);
      is_event = GetNextEvent(everyEvent, &event);
      if (is_event) {
        if (event.what == activateEvt
         || event.what == updateEvt)
          window = (WindowPtr) event.message;
        else {
          window = FrontWindow();
        }
        if (window != the_window)
          track_handle = NIL;
        else {
          track_handle = (TrackHandle) GetWRefCon(window);
          if (track_handle != NIL
           && track_handle != the_track_handle)
            DebugStr(“\pBogus track_handle”);
        }
        switch (event.what) {
        case mouseDown:
          do_mouse(event);
          break;
        case keyDown:
        case autoKey:
          keyin(track_handle, event);
          break;
        case activateEvt:
          /*
           * Activate or deactivate the Track record
           * as needed.  Also, if we are going to/from
           * a desk accessory (or another application),
           * copy the desk scrap and enable/disable
           * appropriate edit menu options.
           */
          if (track_handle != NIL
           && window == the_window) {
            if ((event.modifiers & activeFlag) != 0) {
              TrackActivate(track_handle);
              get_desk_scrap();
              enable_edit_menu(track_handle, FALSE);
            }
            else /* deactivating */ {
              TrackDeactivate(track_handle);
              put_desk_scrap();
              enable_edit_menu(track_handle, TRUE);
            }
          }
          DrawGrowIcon(window);
          break;
        case updateEvt:
          if (window == the_window && track_handle != NIL) {
            GetPort(&save_port);
            SetPort(window);
            BeginUpdate(window);
            EraseRect(&window->portRect);
            /*
             * Use a local copy of the viewRect in case
             * the (unlocked) TrackRecord moves.
             */
            box = TR.viewRect;
            TrackUpdate(&box, track_handle);
#if TOP_MARGIN != 0 && SIDE_MARGIN != 0
            InsetRect(&box, -1, -1);
            FrameRect(&box);
#endif
            /*
             * Must follow TrackUpdate
             */
            DrawGrowIcon(window);
            EndUpdate(window);
            SetPort(save_port);
          }
          break;
        default:
          break;
        }
      }
    }
}

/*
 * Handle keystrokes.  Most of this is needed for
 * arrow-key scrolling.
 */
static void keyin(track_handle, event)
TrackHandle track_handle;
EventRecord event;
{
    INTEGER   c;
    LONGINT   choice;
    LONGINT   hdelta, hscroll, hmax;
    LONGINT   vdelta, vscroll, vmax;

    /*
     * The arrow keys scroll the window.
     * <Option><Arrow> scrolls single pixels.
     * [h,v]max     The largest amount we will
     *              scroll the rectangle.
     * [h,v]delta   The amount the arrow key scrolls.
     * [h,v]scroll  The amount we actually scroll.
     */
    hmax = TR.lineWidth - width(TR.viewRect);
    vmax = (TR.nLines * TR.lineHeight)
         + TR.fontDescent - height(TR.viewRect);
    hdelta = vdelta = TR.lineHeight;
    if ((event.modifiers & optionKey) != 0)
      hdelta = vdelta = 1;
    hscroll = vscroll = 0;
    switch (event.message & keyCodeMask) {
    case Left_Arrow:  case Left_Plus:
      hscroll = -hdelta;                goto do_scroll;
    case Right_Arrow: case Right_Plus:
      hscroll = hdelta;                 goto do_scroll;
    case Up_Arrow:    case Up_Plus:
      vscroll = -vdelta;                goto do_scroll;
    case Down_Arrow:  case Down_Plus:
      vscroll = vdelta;                 goto do_scroll;
    case Pg_Up_Key:           /* Up one screen          */
      vscroll = (-height(TR.viewRect)); goto do_scroll;
    case Pg_Down_Key:         /* Down one screen        */
      vscroll = height(TR.viewRect);    goto do_scroll;
    case Home_Key:          /* Scroll to start of text  */
      hscroll = -TR.leftPixel;
      vscroll = -TR.topPixel;           goto do_scroll;
    case End_Key:           /* Scroll to end of text    */
      hscroll = -TR.leftPixel + hmax;
      vscroll = -TR.topPixel + vmax;
      /*
       * Pin the scroll amount against the
       * boundaries of the document and scroll
       * if anything changes.  Note that we can’t
       * determine the maximum width of the document
       * if lines are only delimited by <return>.
       */
do_scroll:
      if (hscroll != 0 || vscroll != 0)
        TrackPinScroll(hscroll, vscroll, track_handle);
      break;
    case Help_Key:
      TrackSelView(track_handle);
      break;
    default:
      c = event.message & charCodeMask;
      if ((event.modifiers & cmdKey) != 0) {
        if (event.what == keyDown) {
          choice = MenuKey(c);
          if (HiWord(choice) != 0)
            do_command(choice);
          else {
            SysBeep(10);    /* Bogus <cmd>  */
          }
        }
      }
      else if (track_handle != NIL)
        TrackKey(c, track_handle);
    }
}
/* do_mouse(event)
 * Process a mouse button press, calling handlers as
 * needed.  */
static void do_mouse(event)
EventRecord   event;
{
    WindowPtr     window;
    register int  which_part;
    Rect          box;
    int           kind;
    TrackHandle   track_handle;
    long          new;
    
    which_part = FindWindow(event.where, &window);
    switch (which_part) {
    case inDesk:
      SysBeep(2);
      break;
    case inMenuBar:
      do_command(MenuSelect(event.where));
      break;
    case inSysWindow:
      SystemClick(&event, window);
      break;
    case inGoAway:
      if (window == the_window
       && TrackGoAway(window, event.where))
          ExitToShell();
      else {
        kind = ((WindowPeek) window)->windowKind;
        if (kind < 0)           /* Desk accessory?      */
          CloseDeskAcc(kind);
      }
      break;
    case inDrag:
      box = screenBits.bounds;
      box.top += GetMBarHeight();
      InsetRect(&box, 2, 4);
      DragWindow(window, event.where, &box);
      break;
    case inGrow:
      box = screenBits.bounds;
      box.left = 64 + sBarWidth;
      box.top = 64 + sBarWidth;
      box.bottom -= GetMBarHeight();
      new = GrowWindow(window, event.where, &box);
      if (new != 0) {
        track_handle = (TrackHandle) GetWRefCon(window);
        EraseRect(&window->portRect);
        SizeWindow(window, LoWord(new), HiWord(new), TRUE);
        InvalRect(&window->portRect);
        get_text_box(&TR.viewRect, window);
      }
      break;
    case inContent:
      if (FrontWindow() != window)
        SelectWindow(window);
      else {
        SetPort(window);
        track_handle = (TrackHandle) GetWRefCon(window);
        GlobalToLocal(&event.where);
        TrackClick(event.where,
          (event.modifiers & shiftKey) != 0,
          track_handle);        
        enable_edit_menu(track_handle, FALSE);
      }
      break;
    }
}

/*
 * Private hilite function (see Inside Mac I-379).
 * Hilite the selection by underlining it.
 */
pascal void my_hilite(boxp, tr)
Rect      *boxp;
TrackPtr  tr;
{
    Rect      box;
    
    box = *boxp;
    box.top = box.bottom;
    ---box.top;
    InvertRect(&box);
}

/*
 * Private caret function (see Inside Mac I-379).
 * Draw the selection as a nice TextEdit vertical line.
 */
pascal void my_caret(boxp, tr)
Rect      *boxp;
TrackPtr  tr;
{
    InvertRect(boxp);
}
    
/*
 * do_command(which_item)
 * Process menu bar commands.
 */
void do_command(choice)
long        choice;
{
    int           item;
    register int  i;
    Str255        name;
    GrafPtr       save_port;
    int           old_option;
    int           the_class;
    TrackHandle   track_handle;
    WindowPtr     window;
    
    window = FrontWindow();
    track_handle = (TrackHandle) GetWRefCon(window);
    if (track_handle != the_track_handle)
      DebugStr(“\pStrange front window”);
    item = LoWord(choice);
    switch (HiWord(choice)) {
    case MENU_Apple:
      GetItem(apple_menu, item, &name);
      if (item == Apple_About)
        SysBeep(10);        /* No Mouse About           */
      else {
        /*
         * Launch a desk accessory: enable the entire Edit
         * menu, save the port, call the accessory, then
         * restore our state.
         */
        enable_edit_menu(track_handle, TRUE);
        GetPort(&save_port);
        OpenDeskAcc(name);
        SetPort(save_port);
        enable_edit_menu(track_handle, FALSE);
      }
      break;
    case MENU_File:
      switch (item) {
      case File_Debug:
        Debugger();
        break;
      case File_Reinitialize:
        TrackSetText(fubar, FUBAR_SIZE, track_handle);
        break;
      case File_Quit:
        ExitToShell();
      }
      break;
    case MENU_Edit:
      if (!SystemEdit(item - 1)) {
        switch (item) {
        case Edit_Undo:
          break;
        case Edit_Cut:
          TrackCut(track_handle);
          scrap_changed = TRUE;
          break;
        case Edit_Copy:
          TrackCopy(track_handle);
          scrap_changed = TRUE;
          break;
        case Edit_Paste:
          TrackPaste(track_handle);
          break;
        case Edit_Clear:
          TrackDelete(track_handle);
          break;
        }
      }
      enable_edit_menu(track_handle, FALSE);
      break;
    case MENU_Ctrl:
      switch (item) {
      case Ctrl_Script:
        _Track_flip(
          *track_handle, _Track_use_script_manager);
        CheckItem(
          ctrl_menu,
          Ctrl_Script,
          _Track_is_set(
            *track_handle, _Track_use_script_manager));
        break;
      case Ctrl_Smart:
        _Track_flip(
          *track_handle, _Track_use_smart_cut_paste);
        CheckItem(ctrl_menu, Ctrl_Smart, _Track_is_set(
            *track_handle, _Track_use_smart_cut_paste));
        break;
      case Ctrl_Hilite:
        TrackDeactivate(track_handle);
        if (TR.highHook == NIL)
          TR.highHook = (ProcPtr) my_hilite;
        else {
          TR.highHook = NIL;
        }
        TrackActivate(track_handle);
        CheckItem(
          ctrl_menu, Ctrl_Hilite, (TR.highHook != NIL));
        break;
      case Ctrl_Caret:
        TrackDeactivate(track_handle);
        if (TR.caretHook == NIL)
          TR.caretHook = (ProcPtr) my_caret;
        else {
          TR.caretHook = NIL;
        }
        TrackActivate(track_handle);
        CheckItem(
          ctrl_menu, Ctrl_Caret, (TR.caretHook != NIL));
        break;
      case Ctrl_ObeyCR:
        TR.crOnly = (TR.crOnly == 0) ? -1 : 0;
        TrackCalText(track_handle);
        CheckItem(
          ctrl_menu, Ctrl_ObeyCR, (TR.crOnly < 0));
        break;
      case Ctrl_AutoScroll:
        do_autoscroll = !do_autoscroll;
        TrackAutoView(do_autoscroll, track_handle);
        CheckItem(
          ctrl_menu, Ctrl_AutoScroll, do_autoscroll);
        break;
      case Ctrl_JustLeft:
      case Ctrl_JustCenter:
      case Ctrl_JustRight:
        TrackSetJust(
          justify[item - Ctrl_JustLeft], track_handle);
        for (i = Ctrl_JustLeft; i <= Ctrl_JustRight; i++)
          CheckItem(ctrl_menu, i, (item == i));
      }
      break;
    }   
    HiliteMenu(0);
}

/*
 * enable_edit_menu(track_handle, is_desk_accessory)
 * Enable/disable edit menu items.  All are enabled if
 * we’re starting a desk accessory. If not, Enable the
 * Cut, Copy, and Clear options if there’s a selection,
 * and Paste if there’s something in the track scrap.
 * Note that we don’t support Undo.
 */
void enable_edit_menu(track_handle, starting_desk)
TrackHandle   track_handle;
Boolean       starting_desk;
{ 
    if (starting_desk) {
      EnableItem(edit_menu, Edit_Undo);
      EnableItem(edit_menu, Edit_Cut);
      EnableItem(edit_menu, Edit_Copy);
      EnableItem(edit_menu, Edit_Paste);
      EnableItem(edit_menu, Edit_Clear);
    }
    else {
      DisableItem(edit_menu, Edit_Undo);
      if (TrackGetSelectionLength(track_handle) > 0) {
        EnableItem(edit_menu, Edit_Cut);
        EnableItem(edit_menu, Edit_Copy);
        EnableItem(edit_menu, Edit_Clear);
      }
      else {
        DisableItem(edit_menu, Edit_Cut);
        DisableItem(edit_menu, Edit_Copy);
        DisableItem(edit_menu, Edit_Clear);
      }
      if (TrackGetScrapLen() != 0)
        EnableItem(edit_menu, Edit_Paste);
      else {
        DisableItem(edit_menu, Edit_Paste);
      }
    }
}

/*
 * get_desk_scrap()
 * If there is a TEXT item in the desk scrap, read it in
 * and load it into the Track scrap. The algorithm is
 * adapted from Chernicoff, Macintosh Revealed, vol. 2.
 */
void get_desk_scrap()
{
    long        offset;
    long        length;   /* Scrap non-empty?     */

    /*
     * If the desk scrap contents have changed or
     * the scrap hasn’t been initialized, read it
     * and load the Track private scrap.
     */
    if (scrap_count != InfoScrap()->scrapCount
     || InfoScrap()->scrapState < 0) {
      /*
       * The scrap has changed, check for a text item.
       */ 
      length = GetScrap(NIL, ‘TEXT’, &offset);
      if (length < 0 || TrackFromScrap() != noErr)
          length = -1;            /* signal error       */
      if (length > 0)             /* non-empty scrap?   */
        EnableItem(edit_menu, Edit_Paste);
      else {
        TrackSetScrapLen(0);      /* Make it empty      */
        DisableItem(edit_menu, Edit_Paste);
      }
    }
    scrap_count = InfoScrap()->scrapCount;
}

/*
 * put_desk_scrap()
 * Copy the Track scrap to the desk scrap.  Algorithm
 * adapted from Chernicoff, Macintosh Revealed, Vol 2.
 */
void put_desk_scrap()
{
    OSErr         status;
    
    if (scrap_changed) {
      scrap_count = ZeroScrap();
      status = TrackToScrap();
      if (status != noErr)
        DebugStr(“\pCan’t write scrap.”);
      scrap_changed = FALSE;
    }
}

/*
 * setup()
 * One-time initialization.
 */
static void setup()
{
    int             font_number;
    TrackHandle     track_handle;
    Rect            box;

    /* Normal Macintosh initialization.*/
    InitGraf(&thePort);
    InitFonts();
    FlushEvents(everyEvent, 0);
    InitWindows();
    InitMenus();
    TEInit();
    InitDialogs(NIL);
    InitCursor();
    MaxApplZone();
    /* Initialize mouse track library.*/
    TrackInit();
    /*
     * Create the menus and check off the
     * TrackHandle flags.
     */
    apple_menu = NewMenu(MENU_Apple, “\p\024”);
    file_menu = NewMenu(MENU_File, “\pFile”);
    edit_menu = NewMenu(MENU_Edit, “\pEdit”);
    ctrl_menu = NewMenu(MENU_Ctrl, “\pOptions”);
    AppendMenu(apple_menu, “\p(No Mouse About;(-”);
    AddResMenu(apple_menu, ‘DRVR’);
    AppendMenu(file_menu,
      “\pDebug/.;(-;Reinitialize;(-;Quit/Q”
    );
    AppendMenu(edit_menu,
      “\pUndo/Z;(-;Cut/X;Copy/C;Paste/V;Clear”
    );
    AppendMenu(ctrl_menu,
      “\pScriptManager;Intelligence;(-;”
      “My Hilite;My Caret;No word wrap;Auto-scroll;(-;”
      “Left Justify;Center Justify;Right Justify”
    );
    InsertMenu(apple_menu, 0);
    InsertMenu(file_menu, 0);
    InsertMenu(edit_menu, 0);
    InsertMenu(ctrl_menu, 0);
    DrawMenuBar();
    /*
     * Create the display window, set the proper font,
     * and set the TrackHandle in the window’s refCon.
     */
    box = screenBits.bounds;
    box.top += GetMBarHeight() * 2;
#if DEBUG
    box.bottom = 160;
    box.left += 20;
    box.right -= 20;
#endif
    the_window = NewWindow(
          NIL,                  /* Allocate storage     */
          &box,                 /* Display Rect         */
          “\pMouse Demo”,       /* Title                */
          TRUE,                 /* Visible on creation  */
          documentProc,         /* Window type          */
          -1L,                  /* Show in front        */
          FALSE,                /* no GoAway box        */
          NIL                   /* RefCon               */
        );
    SetPort(the_window);
    GetFNum(THE_FONT, &font_number);
    TextFont(font_number);
    TextSize(FONT_SIZE);
    get_text_box(&box, the_window);
    track_handle = TrackNew(box.right - box.left, &box);
    the_track_handle = track_handle;
    SetWRefCon(the_window, (LONGINT) track_handle);
    TrackSetText(fubar, FUBAR_SIZE, track_handle);
    CheckItem(ctrl_menu, Ctrl_Script, _Track_is_set(
        *track_handle, _Track_use_script_manager));
    CheckItem(ctrl_menu, Ctrl_Smart, _Track_is_set(
        *track_handle, _Track_use_smart_cut_paste));
    CheckItem(ctrl_menu, Ctrl_JustLeft, TRUE);
    CheckItem(ctrl_menu, Ctrl_AutoScroll, TRUE);
    do_autoscroll = TRUE;
    TrackAutoView(do_autoscroll, track_handle);
}

/*
 * get_text_box()
 * Locate the display text in the window.
 */
void get_text_box(boxp, window)
register Rect *boxp;
WindowPtr     window;
{
    *boxp = window->portRect;
    boxp->right -= sBarWidth;
    boxp->bottom -= sBarWidth;
    InsetRect(boxp, TOP_MARGIN, SIDE_MARGIN);
}

Listing:  Mouse Click Subroutine
/*                      TrackClick.c                    */
/*
 * Copyright © 1989 Martin Minow and MacTutor. All rights reserved.
 * You may incorporate portions of this program in your
 * applications without restriction as long as this
 * copyright notice remains intact and the applications
 * are not sold for profit and the source is not
 * redistributed for profit.
 *
 * TrackClick(mouse, extend, track_handle)
 * mouse        The mouse location in local coordinates.
 * extend       TRUE to extend the selection.
 * track_handle Everything we need to know about the data.
 *
 * Process a mouse button click in the document window.
 * If clikProc is non-NIL, the function will be called
 * repeatedly as follows:
 *  (*clikProc)(mouse, TrackPtr)
 * Your function should be declared
 *  pascal Boolean
 *  myClikLoop(Point mouse, TrackPtr trackptr)
 * where
 *  mouse     mouse location in window-local coordinates.
 *  trackptr  pointer to a locked TrackRecord.
 * If the myClikLoop returns FALSE, TrackClick will assume
 * that the mouse button has been released.
 * If clikProc is NIL, TrackClick will autoscroll if the
 * mouse moves outside of the viewRect.
 */
#include  “TrackEdit.h”
#define TR  (*tr)
#define NIL     0
/* save_select is used for double-clicks to ensure that
 * the TrackRecord has the current selection. */
#define save_select(tr, a, c) do {    \
      if (a.start <= c.end) {         \
        (tr)->selStart = a.start;     \
        (tr)->selEnd = c.end;         \
      }                               \
      else {                          \
        (tr)->selStart = c.start;     \
        (tr)->selEnd = a.end;         \
      }                               \
    } while (0)

void TrackClick(mouse, shift_click, track_handle)
Point         mouse;
Boolean       shift_click;
TrackHandle   track_handle;
{
    register TrackPtr     tr;
    _Track_state          state;
    register DOT          dot;
    register Boolean      is_word;
    register DOT          temp;
    register DOT          last_word;
    auto _Track_Loc       anchor;
    auto _Track_Loc       current;
    DOT                   last_end, new_end;
    Boolean               at_anchor;
    
    tr = _Track_lock(track_handle, &state);
    dot = _Track_mouse_to_dot(tr, mouse);
    is_word = FALSE;
    _Track_caret(tr, _Track_caret_off);
    if (shift_click && TR.selStart != TR.selEnd) {
      /*
       * Extend the selection.  If the mouse is outside
       * the current selection, extend from the mouse
       * to the nearest end (the selection always gets
       * bigger).  Otherwise, truncate from the start
       * (left).  Note: some applications just continue
       * from the current invocation, but I don’t think
       * this looks good.  Shift-click doesn’t do
       * word-by-word (double-click) extension.
       */
      if (dot > TR.selStart)
        _Track_hilite(tr, dot, TR.selEnd);
      else {
        _Track_hilite(tr, dot, TR.selStart);
        TR.selStart = TR.selEnd;
      }
      TR.selEnd = dot;
    }
    else {
      /*
       * Not shift-click with a selection.  Dump any
       * previous selection. (_Track_Hilite does nothing
       * if it’s called with no selection.)
       */
      _Track_hilite(tr, TR.selStart, TR.selEnd);
      TR.selStart = TR.selEnd = dot;
      /*
       * Check for a double-click.  If so, extend the
       * selection to encompass the entire word.
       */
      if ((TickCount() - TR.clickTime) <= GetDblTime()
       && dot == TR.clickLoc) {
        is_word = TRUE;
        _Track_word(tr, mouse, &anchor);
        _Track_hilite(tr, anchor.start, anchor.end);
        current = anchor;
        last_word = anchor.start;
        at_anchor = TRUE;
      }
    }
    /*  Track the mouse, extending the visual selection
     * whenever it moves off-character.
     */
    while (WaitMouseUp()) {
      temp = dot;
      do {
        GetMouse(&mouse);
        if (is_word)
          save_select(tr, anchor, current);
        if (TR.clikLoop != NIL
         && !CallPascalB(mouse, tr, TR.clikLoop)) {
          goto done;
        }
        else {
          _Track_autoscroll(tr, &mouse);
        }
        dot = _Track_mouse_to_dot(tr, mouse);
      } while (dot == temp && StillDown());
      if (is_word) {
        /*
         * Word-by-word selection: the mouse moved.
         * Check to see if the mouse is in the same word.
         * If so, do nothing.  Else, extend the selection
         * to the new word.
         */
        _Track_word(tr, mouse, &current);
        if (current.start != last_word) {
          /*
           * The mouse moved off the current word. The
           * word is always identified by the location of
           * its left end.  Invert the selection.  If the
           * mouse is before (left of) the current point,
           * invert from the start of the word, else from
           * the end of the word.  Note that the word
           * that the user clicked on is not un-hilited.
           */
          last_word = current.start;
          if (current.start < anchor.start) {
            new_end = current.start;
            if (at_anchor)
              last_end = anchor.start;
            else if (last_end > anchor.start) {
              _Track_hilite(tr, last_end, anchor.end);
              last_end = anchor.start;
            }
          }
          else /* if (current.start >= anchor.start) */ {
            new_end = current.end;
            if (at_anchor)
              last_end = anchor.end;
            else if (last_end < anchor.start) {
              _Track_hilite(tr, last_end, anchor.start);
              last_end = anchor.end;
            }
          }
          _Track_hilite(tr, last_end, new_end);
          last_end = new_end;
          at_anchor = (current.start == anchor.start);
        }
      }
      else {
        /*
         * Normal (not word) selection. Just invert from
         * the info->select.end point to the mouse postion.
         */
        _Track_hilite(tr, dot, TR.selEnd);
        TR.selEnd = dot;
      }
    }
    /*
     * Done! remember the mouse location for a subsequent
     * double-click, and normalize the selection (so that
     * info->select.start < info->select.end).  If this
     * was a zero-width selection, the user was setting
     * the insertion point, which should be marked.
     */
done:
    TR.clickTime = TickCount();
    TR.clickLoc = dot;
    if (is_word)
      save_select(tr, anchor, current);
    else if (TR.selStart > TR.selEnd) {
      dot = TR.selStart;
      TR.selStart = TR.selEnd;
      TR.selEnd = dot;
    }
    TR.selRow = _Track_pixel_row(tr, mouse.v);
    _Track_caret(tr, _Track_caret_on);
    _Track_unlock(&state);
}

 
AAPL
$102.99
Apple Inc.
+0.00
MSFT
$44.38
Microsoft Corpora
+0.00
GOOG
$532.71
Google Inc.
+0.00

MacTech Search:
Community Search:

Software Updates via MacUpdate

jAlbum Pro 12.2.4 - Organize your digita...
jAlbum Pro has all the features you love in jAlbum, but comes with a commercial license. With jAlbum, you can create gorgeous custom photo galleries for the Web without writing a line of code!... Read more
jAlbum 12.2.4 - Create custom photo gall...
With jAlbum, you can create gorgeous custom photo galleries for the Web without writing a line of code! Beginner-friendly, with pro results Simply drag and drop photos into groups, choose a design... Read more
ExpanDrive 4.1.7 - Access remote files o...
ExpanDrive builds cloud storage in every application, acts just like a USB drive plugged into your Mac. With ExpanDrive, you can securely access any remote file server directly from the Finder or... Read more
Evernote 5.6.2 - Create searchable notes...
Evernote allows you to easily capture information in any environment using whatever device or platform you find most convenient, and makes this information accessible and searchable at anytime, from... Read more
OmniOutliner 4.1.3 - Organize your ideas...
OmniOutliner is a flexible program for creating, collecting, and organizing information. Give your creativity a kick start by using an application that's actually designed to help you think. It's... Read more
BBEdit 11.0 - Powerful text and HTML edi...
BBEdit is the leading professional HTML and text editor for the Mac. Specifically crafted in response to the needs of Web authors and software developers, this award-winning product provides a... Read more
Apple Security Update 2014-005 - For OS...
Apple Security Update is recommended for all users and improves the security of Mac OS X. For information on the security content of this update, please visit this website: http://support.apple.com/... Read more
EyeTV 3.6.6 - Watch and record TV on you...
EyeTV brings a rich TV experience to your Mac. Watch live TV on your Mac. Pause, rewind, and record whenever you want. EyeTV gives you powerful control over what you watch and how you watch it. Put... Read more
RapidWeaver 6.0 - Create template-based...
RapidWeaver is a next-generation Web design application to help you easily create professional-looking Web sites in minutes. No knowledge of complex code is required, RapidWeaver will take care of... Read more
NTFS 12.0.39 - Provides full read and wr...
Paragon NTFS breaks down the barriers between Windows and OS X. Paragon NTFS effectively solves the communication problems between the Mac system and NTFS, providing full read and write access to... Read more

Latest Forum Discussions

See All

Jam Messenger Review
Jam Messenger Review By Jennifer Allen on October 23rd, 2014 Our Rating: :: SIMPLE MESSAGINGiPhone App - Designed for the iPhone, compatible with the iPad Want a very quick way to send push-based messages? Jam Messenger is basic... | Read more »
Felllice (Games)
Felllice 1.0 Device: iOS Universal Category: Games Price: $.99, Version: 1.0 (iTunes) Description: EXCLUSIVE PRICE DROP! 50% OFF FOR A LIMITED TIME! EAT EAT EAT AND GROW ! | Read more »
The Arrow Game: by Grazie Media (Games)
The Arrow Game: by Grazie Media 1.0 Device: iOS Universal Category: Games Price: $.99, Version: 1.0 (iTunes) Description: Guide a flying arrow through skyscrapers and city streets to hit a distant target. Experience an endless... | Read more »
Worldly (Games)
Worldly 1.2 Device: iOS Universal Category: Games Price: $2.99, Version: 1.2 (iTunes) Description: | Read more »
Money Pro - EASY! Bills, Budgets and Acc...
Money Pro - EASY! Bills, Budgets and Accounts w/ Sync 1.0 Device: iOS Universal Category: Finance Price: $4.99, Version: 1.0 (iTunes) Description: Manage money like a pro. Money Pro is the next generation of Money app (over 2 million... | Read more »
Pro Strategy Football 2014 (Games)
Pro Strategy Football 2014 2014.141001 Device: iOS Universal Category: Games Price: $4.99, Version: 2014.141001 (iTunes) Description: Take the proven strategy of the PSF franchise and add in Casual Play, improved graphics and... | Read more »
Super Glyph Quest (Games)
Super Glyph Quest 1.01 Device: iOS Universal Category: Games Price: $2.99, Version: 1.01 (iTunes) Description: Adventure is back Questers! Combine elemental glyphs together to cast powerful spells and vanquish adorable monsters in... | Read more »
Fighting Fantasy: Caverns of the Snow Wi...
Fighting Fantasy: Caverns of the Snow Witch 1.0 Device: iOS Universal Category: Games Price: $5.99, Version: 1.0 (iTunes) Description: Travel to Northern Allansia’s perilous Icefinger Mountains to defeat the wicked Snow Witch in this... | Read more »
Star Warfare 2: Payback Review
Star Warfare 2: Payback Review By Blake Grundman on October 22nd, 2014 Our Rating: :: ONE-TRICK PONYUniversal App - Designed for iPhone and iPad Unfortunately, it doesn’t take long for Star Warfare 2’s free-firing fun to turn into... | Read more »
TinType by Hipstamatic (Photography)
TinType by Hipstamatic 1.0 Device: iOS iPhone Category: Photography Price: $.99, Version: 1.0 (iTunes) Description: Create hauntingly beautiful, soul capturing portraits with TinType by Hipstamatic. Inspired by daguerreotypes,... | Read more »

Price Scanner via MacPrices.net

Save with Best Buy’s College Student Deals
Take an additional $50 off all MacBooks and iMacs at Best Buy Online with their College Students Deals Savings, valid through November 1st. Anyone with a valid .EDU email address can take advantage... Read more
iPad Air 2 & iPad mini 3 Best Tablets Yet...
The new iPads turned out to be pretty much everything I’d been hoping for and more than I’d expected.”More” particularly in terms of a drinking-from-a-firehose choice of models and configurations,... Read more
Drafts 4 Reinvents iOS Productivity App
N Richland Hills, Texas based Agile Tortoise has announced the release of Drafts 4 for iPhone and iPad. Drafts is a quick capture note taking app with flexible output actions. Drafts 4 scales from... Read more
AT&T accepting preorders for new iPads fo...
AT&T Wireless is accepting preorders for the new iPad Air 2 and iPad mini 3, cellular models, for $100 off MSRP with a 2-year service agreement: - 16GB iPad Air 2 WiFi + Cellular: $529.99 - 64GB... Read more
Apple offering refurbished Mac Pros for up to...
The Apple Store is offering Apple Certified Refurbished 2013 Mac Pros for up to $600 off the cost of new models. An Apple one-year warranty is included with each Mac Pro, and shipping is free. The... Read more
Select MacBook Airs $100 off MSRP, free shipp...
B&H Photo has 2014 a couple of MacBook Airs on sale for $100 off MSRP. Shipping is free, and B&H charges NY sales tax only. They also include free copies of Parallels Desktop and LoJack for... Read more
13-inch 2.5GHz MacBook Pro on sale for $100 o...
B&H Photo has the 13″ 2.5GHz MacBook Pro on sale for $999.99 including free shipping plus NY sales tax only. Their price is $100 off MSRP. Read more
Strong iPhone, Mac And App Store Sales Drive...
Apple on Monday announced financial results for its fiscal 2014 fourth quarter ended September 27, 2014. The Company posted quarterly revenue of $42.1 billion and quarterly net profit of $8.5 billion... Read more
Apple Posts How-To For OS X Recovery
OS X 10.7 Lion and later include OS X Recovery. This feature includes all of the tools you need to reinstall OS X, repair your disk, and even restore from a Time Machine backup. OS X Recovery... Read more
Mac OS X Versions (Builds) Supported By Vario...
Apple Support has posted a handy resource explaining which Mac OS X versions (builds) originally shipped with or are available for your computer via retail discs, downloads, or Software Update. Apple... Read more

Jobs Board

Senior Event Manager, *Apple* Retail Market...
…This senior level position is responsible for leading and imagining the Apple Retail Team's global event strategy. Delivering an overarching brand story; in-store, Read more
*Apple* Solutions Consultant (ASC) - Apple (...
**Job Summary** The ASC is an Apple employee who serves as an Apple brand ambassador and influencer in a Reseller's store. The ASC's role is to grow Apple Read more
Project Manager / Business Analyst, WW *Appl...
…a senior project manager / business analyst to work within our Worldwide Apple Fulfillment Operations and the Business Process Re-engineering team. This role will work Read more
*Apple* Retail - Multiple Positions (US) - A...
Job Description: Sales Specialist - Retail Customer Service and Sales Transform Apple Store visitors into loyal Apple customers. When customers enter the store, Read more
Position Opening at *Apple* - Apple (United...
…customers purchase our products, you're the one who helps them get more out of their new Apple technology. Your day in the Apple Store is filled with a range of Read more
All contents are Copyright 1984-2011 by Xplain Corporation. All rights reserved. Theme designed by Icreon.