TweetFollow Us on Twitter

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

Related Info: Script Manager TextEdit Scrap Manager
Control Manager

Mouse Track and Field, Part II

By Martin Minow, Arlington, VA

Introduction

About a year ago, I wrote a terminal emulator that used the mouse to select text (such as the name of a file I wanted to download). Thinking that other MacTutor readers would be interested, I reimplemented the algorithm as a separate subroutine (fixing numerous bugs along the way) and added a small sample program. When I showed the draft article to a colleague for review, he suggested I add a few additional routines to create a TextEdit replacement library. Naively thinking this wouldn’t be too much additional work, I went off and started typing. And typing. And typing. When I came up for air, I had most of TextEdit (I didn’t add styles or multi-directional text). Perhaps more importantly, the library is written in C so it could be adapted to other uses.

Part I described the central mouse selection algorithm in somewhat obsessive detail. It also includes a sample program listing. This article describes the library itself in rather general terms, and includes the complete TrackEdit source library listing, except the library routines that were printed in Part I.

The TrackEdit Library

The TrackEdit library can be used as a direct replacement for TextEdit (only TextBox was left out). If I did my work well, you should be able to adapt these routines to any “editing” application that can’t use TextEdit. The routines take essentially the same arguments as their TextEdit counterparts.

Table 1 shows the correspondence between TextEdit and TrackEdit:

TextEdit Track

TEAutoView TrackAutoView

TEInit TrackInit

TENew TrackNew

TEDispose TrackDispose

TESetText TrackSetText

TEGetText TrackGetText

TEIdle TrackIdle

TEClick TrackClick

TESetSelect TrackSetSelect

TEActivate TrackActivate

TEDeactivate TrackDeactivate

TEKey TrackKey

TECut TrackCut

TECopy TrackCopy

TEDelete TrackDelete

TEGetHeight TrackGetHeight

TEGetOffset TrackGetOffset

TEGetPoint TrackGetPoint

TEInsert TrackInsert

TEPinScroll TrackPinScroll

TESelView TrackSelView

TESetJust TrackSetJust

TEUpdate TrackUpdate

TextBox not implemented

TEScroll TrackScroll

TEFromScrap TrackFromScrap

TEToScrap TrackToScrap

TEScrapHandle TrackScrapHandle

TEGetScrapLen TrackGetScrapLen

TESetScrapLen TrackSetScrapLen

SetWordBreak TrackSetWordBreak

SetClikLoop TrackSetClikLoop

TECalText TrackCalText

not implemented TrackGetSelectLength

Table 1: Comparison of TextEdit and Track Routines

If you have a basic understanding of TextEdit, you should be able to understand how to call the Track library routines.

The Track library stores all information needed to track the mouse in a data structure whose elements are described in Table 2.

Element Usage

topPixel offset of top line (part of the destination rectangle in TextEdit)

leftPixel offset of top line (part of the destination rectangle in TextEdit)

lineWidth width of text line (computed from the destination rectangle in TextEdit)

viewRect view (clip) rectangle

lineHeight line spacing for display and mouse tracking

fontAscent position text for display

fontDescent position caret for display

selPoint used to display insertion point

selStart selection start

selEnd selection end

clikLoop click loop routine pointer

clickTime mouse-up time to detect double-clicks

clickLoc mouse position to detect double-clicks

caretTime when to invert the caret

just justification flags

textLength number of bytes of text

hText text handle

crOnly break lines at carriage-returns only

txFont text font

txFace character style

txMode pen mode

txSize font size

inPort grafPort

flags used internally

highHook hilight routine pointer

caretHook insertion mark routine

refCon structure reference value

nLines number of lines of text

lineStarts[] positions of line starts

Table 2: TrackRecord Elements

Most of this information is identical to the identically-named fields in the TextRecord used by TextEdit. There are a few differences worth noting:

• topPixel, leftPixel, and lineWidth replace TextEdit’s destRect. This works around a TextEdit text size limitation. topPixel and leftPixel are affected by scrolling, while lineWidth is used to compute line wrapping. topPixel and leftPixel are long integers to work around a limitation of TextEdit. If you are replacing TextEdit in your editor, you may have to adjust your scrolling routines to use these variables.

• The Track routines store line pointers in long integer arrays. This means that they can deal with text longer than 32,000 bytes. Also nLines is a long integer. A “real” text editor should use a more efficient data structure (with paragraphs residing in a disk scratch file until needed) -- TrackEdit is just enough to get you going.

In addition to the externally-visible functions, the library has a number of private functions and two global variables (used to manage the desk scrap). These begin with the character sequence _Track_.

The Track library functions provide a scaffold for the mouse selection algorithm which, in turn, assigns values to two elements of the TrackRecord structure, selStart and selEnd. These delimit the selection range: selStart indicates the first character in the selection, and selEnd indicates the first character after the selection. In a real application, your program would use a more complex object to access its data: for example, my terminal emulator uses a structure containing a handle to the start of a line and an integer giving the offset to a particular character. This more elegant organization simplifies the storage allocation algorithms at the cost of making selection slightly more complex.

The program assumes that data are organized in a linear first-to-last arrangement that would naturally be top-to-bottom and left-to-right for English text, but is really dependent on the data you are presenting. (It gets more complicated for multi-lingual text, such as mixed Hebrew and English.)

Locating the Mouse

_Track_mouse_to_dot takes a “raw” mouse coordinate and converts it to a selection. The code is fairly straightforward. First, it uses lineHeight to compute the row. Then, starting from the character at the left edge of this row, the sum of the display width of each character determines the location of dot. If the mouse is past the middle of the character, dot is set to the next character. I use the term dot to refer to a value that uniquely identifies each character in the document: the term comes from TECO, one of the first text editors. (Note that dot is actually between two characters.)

_Track_word takes the actual location of the mouse in window coordinates and determines the dot value for the start and end of the word. It was not clear to me (for the first few months) that I needed the mouse location in order to compute the word start: the problem is due to the behavior at the end of a line, when the mouse may be at the end of one line or at the beginning of the next line: in both cases, the dot value is the same, but word start and word end are quite different.

When the mouse was in the right half of the last character on a line, dot, when converted back to window coordinates, referenced the start of the next line. This inadvertently extended the selection over the first word in the following line when the program scanned forward for white-space. The problem was solved by blocking the scan if mouse is in row r and dot is in row r+1. While this sounds like a simple insight, I had to fall asleep over the code one night to discover it..

Instead of sleeping, I would have been better off reading Inside Mac, volume V, where the FindWord toolbox procedure is described. This locates the start and end of a word in a language-independent manner. FindWord can also handle selection for multi-directional text, such as mixed English and Hebrew, however this library does not take advantage of this capability. (Any volunteers?)

Autoscrolling

When you examine the mouse selection routine in TrackClick.c, note that the inner “follow-the-mouse” loop calls an automatic scrolling routine that moves the display if the mouse goes outside of the visual display (the viewRect). This should be overridden by your application if the text window has scroll bars. I.e., the functions in TrackScroll.c and TrackAutoScroll.c do only part of the work.

Displaying the Selection

There are two functions used to display mouse selections: _Track_hilite and _Track_caret.

_Track_hilite takes two selection delimiters and inverts the screen image that encloses them. The routine is straightforward, but note that it must understand that a selection may extend over more than one row. This implementation differs slightly from other Macintosh selection routines in that it doesn’t invert the entire screen out to the window edges.

If the user clicked the mouse to set an insertion point, selStart will equal selEnd. In this case, _Track_caret will draw (or erase) the selection marker. As with _TrackWord, it needs the actual mouse location to distinguish between “end of line” and “beginning of next line,” since these have the same dot value.

Intelligent Cut and Paste

The cut and paste routines in TrackEdit.c implement Apple’s “Intelligent Cut and Paste” algorithm described in Inside Macintosh Volume I and the Apple Human Interface Guidelines. The routines are straightforward, though you should read Apple’s description to understand why they work as they do. They can be turned off by a menu option in the demo program (and are turned off if your system doesn’t support the Script Manager). Note that TrackKey suppresses “intelligent cut.” You might want to extend TrackKey to support arrow-key selection as discussed in Inside Mac, Vol IV, Chapter 1.

Real Life

If you need to replace TextEdit, you should be able to use these routines with a minimum of fuss. Of course, they are not needed for many TextEdit uses, such as entering data in dialogs.

The way you decide to store your data will affect the low-level routines in TrackLocate.c and TrackPosition.c (these translate between the data-specific dot value and the corresponding position in window-local coordinates). You will also have to make small changes in several other modules, notably TrackEdit.c and TrackUpdate.c as these must access the actual data.

Also, if you use a structure to identify text (perhaps containing a line handle and offset), you’ll have to write a comparison function that replaces my simple if statements. Also, you ought to add the remaining TextEdit capabilities, such as styles, tabs, and multi-language support.

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.

When you do adapt the routines to your own needs, don’t forget to put some effort into error recovery and filling in the missing pieces. 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.

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:  Document Activation
/*                      TrackActivate.c                 */
/*
 * Copyright © 1989 Martin Minow and MacTutor. All rights reserved.
 *
 * void
 * TrackActivate(track_handle)
 * TrackHandle  track_handle;
 *
 * void
 * TrackDeactivate(track_handle)
 * TrackHandle  track_handle;
 *
 * These routines are called when the application program
 * receives an activate or deactivate event.  They may
 * also be called temporarily to erase any current
 * selection if, for example, the application program
 * needs to change the hiliting characteristics.
 */  
#include “TrackEdit.h”
#define TR  (*tr)

void TrackActivate(track_handle)
TrackHandle track_handle;
{
    register TrackPtr tr;
    _Track_state      state;
    
    tr = _Track_lock(track_handle, &state);
    SetPort(TR.inPort);
    if (!_Track_is_set(tr, _Track_is_active)) {
      /*
       * Activating: call the hiliters to invert any
       * selection.
       */
      _Track_set(tr, _Track_is_active);
      _Track_hilite(tr, TR.selStart, TR.selEnd);
      _Track_caret(tr, _Track_caret_on);
    }
    _Track_unlock(&state);
}

void TrackDeactivate(track_handle)
TrackHandle track_handle;
{
    register TrackPtr tr;
    _Track_state      state;

    tr = _Track_lock(track_handle, &state);
    if (_Track_is_set(tr, _Track_is_active)) {
      /*
       * Deactivating: invert any selection to
       * erase it from the window.
       */
      _Track_hilite(tr, TR.selStart, TR.selEnd);
      _Track_caret(tr, _Track_caret_off);
      _Track_clear(tr, _Track_is_active);
    }
    _Track_unlock(&state);
}
Listing:  Automatic Scrolling
/*                    TrackAutoScroll.c                 */
/*
 * Copyright © 1989 Martin Minow and MacTutor. All rights reserved.
 *
 * Manage automatic scrolling.
 *
 * void
 * TrackSelView(track_handle)
 * TrackHandle  track_handle;
 *
 * If automatic scrolling has been enabled, make sure the
 * start of the selection range is visible, scrolling
 * it into view if necessary.  If automatic scrolling
 * is disabled, TrackSelView does nothing.
 *
 * void
 * TrackAutoView(auto, track_handle)
 * Boolean      auto;
 * TrackHandle  track_handle;
 *
 * Enable or disable automatic scrolling.
 */
#include  “TrackEdit.h”
#define TR  (*tr)
/*
 * Calculate the width or height of a rect.
 */
#define width(r) ((r).right - (r).left)
#define height(r) ((r).bottom - (r).top)

/* Scroll the selection start into view.*/
void TrackSelView(track_handle)
TrackHandle track_handle;
{
    register TrackPtr tr;
    _Track_state      state;
    LONGINT           hpixel, vpixel;
    LONGINT           hdelta, vdelta;
    
    tr = _Track_lock(track_handle, &state);
    if (!_Track_is_set(tr, _Track_do_autoscroll))
      return;
    TrackGetPoint(
      TR.selStart, track_handle, &hpixel, &vpixel);
    vpixel -= TR.lineHeight;
    hdelta = vdelta = 0;
    if (vpixel < (LONGINT) TR.viewRect.top
     || vpixel > (LONGINT) TR.viewRect.bottom)
      vdelta = vpixel - TR.viewRect.top;
    if (hpixel < (LONGINT) TR.viewRect.left
     || hpixel > (LONGINT) TR.viewRect.right)
      hdelta = hpixel - TR.viewRect.left;
    if (hdelta != 0 || vdelta != 0)
      _Track_do_scroll(tr, hdelta, vdelta);
    _Track_unlock(&state);
}

/* Turn on/off automatic scrolling. */
void TrackAutoView(enable, track_handle)
Boolean     enable;
TrackHandle track_handle;
{
    if (enable)
      _Track_set((*track_handle), _Track_do_autoscroll);
    else {
      _Track_clear((*track_handle), _Track_do_autoscroll);
    }
}

/* _Track_autoscroll()
 * Make sure the mouse is within the viewRect, scrolling
 * the text if necessary.  This should “pin” the text
 * so it doesn’t scroll out of the window.*/
void _Track_autoscroll(tr, mousep)
register TrackPtr tr;
register Point    *mousep;
{
    LONGINT       deltah, deltav;
    LONGINT       max_horiz, max_vert;
    
    if (!_Track_is_set(tr, _Track_do_autoscroll))
      return;
    deltah = 0;
    deltav = 0;
    if (mousep->v < TR.viewRect.top
     && TR.topPixel > 0) {
      --deltav;
      mousep->v = TR.viewRect.top;
    }
    else if (mousep->v > TR.viewRect.bottom) {
      max_vert = (TR.nLines * TR.lineHeight)
               - height(TR.viewRect);
      if (TR.topPixel < max_vert) {
        ++deltav;
        mousep->v = TR.viewRect.bottom;
      }
    }
    if (mousep->h < TR.viewRect.left
     && TR.leftPixel > 0) {
      --deltah;
      mousep->h = TR.viewRect.left;
    }
    else if (mousep->h > TR.viewRect.right) {
      max_horiz = TR.lineWidth - width(TR.viewRect);
      if (TR.crOnly < 0 || TR.leftPixel < max_horiz) {
        ++deltah;
        mousep->h = TR.viewRect.right;
      }
    }
    /* Each pass through autoscroll moves the window
     * a few pixels at a time -- this slows things down
     * so the user can stop before the text disappears.*/
    if (deltav != 0 || deltah != 0) {
      _Track_do_scroll(tr, deltah * TR.lineHeight,
        deltav * TR.lineHeight);
    }
}
Listing:  Recalculate Line Widths
/*                    TrackCalText.c
 * Copyright © 1989 Martin Minow and MacTutor. All rights reserved.
 * void TrackCalText(track_handle)
 * TrackHandle  track_handle;
 * Recalculate line starts.  Call this after you change
 * the font characteristics. */
#include “TrackEdit.h”
#define TR    (*tr)

void TrackCalText(track_handle)
TrackHandle track_handle;
{
    register TrackPtr tr;
    _Track_state      state;
    
    tr = _Track_lock(track_handle, &state);
    _Track_rebuild(track_handle, 0);
    _Track_unlock(&state);
}
Listing:  Edit Menu Subroutines
/*                      TrackEdit.c 
 * Copyright © 1989 Martin Minow and MacTutor. All rights reserved.
 * TrackCut(track_handle)
 * track_handle The TrackRecord handle
 * Cut the selection to the Track private scrap.
 * Anything currently in the scrap is deleted.  If there
 * is no selection, the scrap is emptied.
 *
 * TrackCopy(track_handle)
 * track_handle The TrackRecord handle
 * Copy the selection to the Track private scrap.
 * Anything currently in the scrap is deleted.  If there
 * is no selection, the scrap is emptied.
 *
 * TrackPaste(track_handle)
 * track_handle The TrackRecord handle
 * Replace the selection range with the contents of the
 * Track private scrap.  If the scrap is empty, the
 * selection range is deleted.  The private scrap is
 * not changed.
 *
 * TrackDelete(track_handle)
 * track_handle The TrackRecord handle
 * Delete the selection range, but do not copy it to
 * the Track private scrap. */

#include  “TrackEdit.h”
#define TR  (*tr)
#define LEFT    FALSE
#define RIGHT   TRUE
#define NIL     0L
#define NOT_NIL 1L

static void       do_paste(TrackHandle);
static void       do_clear(TrackHandle, Boolean);
static Boolean    is_at_space(TrackPtr, Boolean);
static Boolean    is_at_word(TrackPtr, DOT, DOT, Boolean);

/* TrackCut(track_handle)
 * Copy the selection to the scrap, then remove it. */
void TrackCut(track_handle)
TrackHandle track_handle;
{
    TrackCopy(track_handle);
    TrackDelete(track_handle);
}

/* TrackCopy(track_handle)
 * Copy the selection to the scrap.
 * This is the only routine to change TrackScrpHandle.*/
void TrackCopy(track_handle)
TrackHandle track_handle;
{
    register TrackPtr tr;
    _Track_state      state;
    register long     size;
    
    tr = _Track_lock(track_handle, &state);
    size = TR.selEnd - TR.selStart;
    SetHandleSize(TrackScrpHandle, size);
    if (MemErr == noErr) {
      BlockMove(&(*TR.hText)[TR.selStart],
        *TrackScrpHandle, size);
      TrackScrpLength = size;
    }
    _Track_unlock(&state);
}

/* TrackPaste(track_handle)
 * Replace the selection with the scrap*/
void TrackPaste(track_handle)
TrackHandle track_handle;
{
    _Track_state  state;

    _Track_lock(track_handle, &state);
    _Track_do_paste(track_handle);
    _Track_unlock(&state);
}

/* TrackDelete(track_handle)
 * Remove the selection.*/
void TrackDelete(track_handle)
TrackHandle track_handle;
{
    _Track_state  state;
    
    _Track_lock(track_handle, &state);
    _Track_do_clear(track_handle, TRUE, TRUE);
    _Track_unlock(&state);
}

/* _Track_do_paste(track_handle)
 * If a selection exists, remove it; then insert the
 * current scrap.  Select the newly-inserted data.
 * Use “intelligent paste” if available.  Note that
 * _Track_do_paste() may unlock the TrackRecord
 * temporarily. */
void _Track_do_paste(track_handle)
TrackHandle track_handle;
{
    register TrackPtr tr;
    _Track_state      state;
    DOT               start;
    
    tr = *track_handle;
    _Track_caret(tr, _Track_caret_off);
    _Track_do_clear(track_handle, FALSE, TRUE);
    tr = *track_handle;
    start = TR.selStart;
    /* This is needed only for “intelligent paste.”
     * Insert a space before the selection if there
     * is a word before the selection.*/
    if (is_at_word(tr, 0, TR.selStart, RIGHT)) {
      _Track_do_insert(tr, TR.selStart, “ “, 1L);
      TR.selStart++;
    }
    HLock(TrackScrpHandle);
    _Track_do_insert(
      tr, TR.selStart, *TrackScrpHandle, TrackScrpLength);
    HUnlock(TrackScrpHandle);
    TR.selEnd = TR.selStart + TrackScrpLength;
    /* This is needed only for “intelligent paste.”
     * Insert a space after the selection if there
     * is a word after the selection.*/
    if (is_at_word(tr, TR.selEnd, TR.textLength, LEFT))
      _Track_do_insert(tr, TR.selEnd, “ “, 1L);
    _Track_rebuild(track_handle, start);
}

/* _Track_do_insert(track_ptr, offset, src, size)
 * Stuff something into the text. */
void _Track_do_insert(tr, offset, src, size)
TrackPtr  tr;
long      offset;
Ptr       src;
long      size;
{
    Munger(TR.hText, offset, NIL, 0L, src, size);
    if (MemErr == noErr)
      TR.textLength = GetHandleSize(TR.hText);
}

/* _Track_do_clear(track_handle, rebuild, smart)
 * If a selection exists, delete it; then rebuild the
 * lineStarts vector if requested. Use “intelligent cut”
 * if available and smart is TRUE.  _Track_do_clear() may
 * temporarily unlock the TrackRecord.*/
void _Track_do_clear(track_handle, rebuild, smart)
TrackHandle track_handle;
Boolean     rebuild;
Boolean     smart;
{
    register TrackPtr   tr;
    long                size;
    
    tr = (*track_handle);
    if (TR.selEnd != TR.selStart) {
      /* There is a selection.  If the character to the
       * left or right of the current selection is a
       * space, cut it along with the selection.
       * is_at_space() fails if “intelligent cut and
       * paste” is disabled.*/
      if (smart) {
        if (is_at_space(tr, LEFT))
          --TR.selStart;
        else if (is_at_space(tr, RIGHT))
          ++TR.selEnd;
      }
      size = TR.selEnd - TR.selStart;
      Munger(TR.hText, TR.selStart, NIL, size, NOT_NIL, 0);
      TR.textLength -= size;
      SetHandleSize(TR.hText, TR.textLength);
      TR.selEnd = TR.selStart;
      if (rebuild)
        _Track_rebuild(track_handle, TR.selStart);
    }
}

/* is_at_space(direction)
 * Use the ScriptMgr FindWord procedure to determine if
 * the designated end of the selection is a word with an
 * adjacent space. It is needed only for “intelligent
 * cut and paste.”*/
static Boolean is_at_space(tr, direction)
register TrackPtr tr;
Boolean         direction;
{
    if (direction == LEFT) {
      if (TR.selStart > 0
       && is_at_word(tr, TR.selStart, TR.selEnd, LEFT)
       && _Track_is_white(tr, *(TR.hText), TR.selStart-1))
        return (TRUE);
    }
    else /* RIGHT */ {
      if (TR.selEnd < TR.textLength
       && is_at_word(tr, TR.selStart, TR.selEnd, RIGHT)
       && _Track_is_white(tr, *(TR.hText), TR.selEnd))
        return (TRUE);
    }
    return (FALSE);
}

/* is_at_word(track_ptr, start, end, direction)
 * Use FindWord to decide whether start..end is a word at
 * the designated end.  This routine is needed only if
 * you want “intelligent cut and paste.” */
static Boolean is_at_word(tr, start, end, direction)
register TrackPtr tr;
DOT               start;
DOT               end;
Boolean           direction;
{
    OffsetTable       offsets;
    
    /* This table has a bit set for characters that form
     * words. The bits are “numbered” from the left.
     * We need it to check on one-character selections
     * as they might be punctuation.*/
    static long       word_break[] = {
      0x00000000, 0x0000FFC0, /* 00..3F (allow digits)  */     
      0x7FFFFFE1, 0x7FFFFFE0, /* 40..7F (allow letters) */
      0xFFFFFFFF, 0x01030003, /* 80..BF (int’l letters) */
      0x001F0080, 0x00000000  /* C0..FF (a few more)    */
    };
    
    if (TR.wordBreak != NIL) {
      if (direction == LEFT)
        return (_Track_is_white(tr, *TR.hText, start));
      else {
        return (_Track_is_white(tr, *TR.hText, end));
      }
    }
    if (!_Track_is_set(tr, _Track_use_script_manager)
     || !_Track_is_set(tr, _Track_use_smart_cut_paste))
      return (FALSE);     
    if (direction == LEFT)
      FindWord(*TR.hText, end, start, TRUE, NIL, offsets);
    else {
      FindWord(*TR.hText, end, end, FALSE, NIL, offsets);
    }
#if smgrVers >= 0x0210  /* See ScriptMgr.h            */
    /* In Think C version 4, the offset table is defined
     * as a 3-element structure.*/
#define START offsets[0].offFirst
#define END   offsets[0].offSecond
#else
    /* This is specific to Think C version 3, where the
     * offset table is defined as a 6-element short vector.
     * Warning: no longer tested.*/
#define START offsets[0]
#define END   offsets[1]
#endif
    if ((END - START) == 1) {
      return (BitTst(word_break, (*TR.hText)[START]));
    }
    else {
      return ((END > START) ? TRUE : FALSE);
    }
}
Listing:  Hilite Selection Subroutines
/*                      TrackHilite.c 
 * Copyright © 1989 Martin Minow and MacTutor. All rights reserved.
 * These routines invert the selection or draw insertion
 * point.  When called, _TrackPtr points to the locked
 * TrackRecord.  These routines are not called directly
 * by the application program. */  
#include “TrackEdit.h”
#include “Color.h”
#define TR  (*tr)
#define NIL   0

static void   invert_rect(
                TrackPtr, LONGINT, LONGINT, LONGINT);

/* _Track_hilite(track_ptr, start, end)
 * Invert the selection between start and end, both
 * given as indices into *(_TrackPtr->string). Note:
 * start is not necessarily “earlier” than end.*/
void _Track_hilite(tr, dot_start, dot_end)
register TrackPtr tr;
DOT               dot_start;
DOT               dot_end;
{
    LONGINT           row_start, row_end;
    INTEGER           col_start, col_end, start, end;
    DOT               temp;

    /* If there is no selection, there’s nothing to
     * invert.  Note that we’re only called if the
     * window is active.*/
    if (dot_start == dot_end)
      return;           /* No hiliting needed           */
    /* It’s a real selection: normalize the selection
     * so start is north-east of end and convert to
     * pixel coordinates.*/
    if (dot_start > dot_end) {
      temp = dot_start;
      dot_start = dot_end;
      dot_end = temp;
    }
    row_start = _Track_row(tr, dot_start);
    row_end   = _Track_row(tr, dot_end);
    col_start = _Track_dot_to_col(tr, dot_start);
    col_end   = _Track_dot_to_col(tr, dot_end); 
    /* If everything is on one row, do it.*/
    if (row_start == row_end)
      invert_rect(tr, row_start, col_start, col_end);
    else {
      /* We have several lines to invert. Start with the
       * right end of the first line.*/
      end = _Track_dot_to_eol(tr, row_start);
      invert_rect(tr, row_start, col_start, end);
      /* Do any complete rows.  This can be optimized
       * for many applications.*/
      while (++row_start < row_end) {
        start = _Track_dot_to_bol(tr, row_start);
        end   = _Track_dot_to_eol(tr, row_start);
        invert_rect(tr, row_start, start, end);
      }
      /* Finally, do the last row.*/
      start = _Track_dot_to_bol(tr, row_end);
      invert_rect(tr, row_end, start, col_end);
    }
}

/* _Track_caret(track_ptr, desired_state)
 * Draw (or erase) the selection marker at selStart.
 * (Make sure it’s in the mouse row.)
 * Call the user’s caret routine if requested.*/
void _Track_caret(tr, state)
register TrackPtr tr;
INTEGER           state;
{
    /* If there is a real selection, or the text is not
     * active, just return.  Likewise if the caret state
     * shouldn’t change. Otherwise, invert the caret.*/
    if (!_Track_is_set(tr, _Track_is_active)
     || TR.selStart != TR.selEnd)
      ;               /* No caret wanted              */
    else if (state == _Track_caret_on
     && _Track_is_set(tr, _Track_caret_visible))
      ;               /* Want on and it’s still on    */
    else if (state == _Track_caret_off
     && !_Track_is_set(tr, _Track_caret_visible))
      ;               /* Want off and it’s still off  */
    else {
      /* Invert the caret on screen.*/
      _Track_flip(tr, _Track_caret_visible);
      _Track_invert_caret(tr);
    }
}

void _Track_invert_caret(tr)
register TrackPtr tr;
{
    PenState          pen_state;
    Rect              caret;
    LONGINT           row;
    LONGINT           col;
    DOT               dot;
    
    if (TR.selStart != TR.selEnd)
      return;
    row = _Track_row(tr, TR.selStart);
    col = _Track_dot_to_col(tr, TR.selStart);
    if (col == TR.viewRect.left && TR.selRow < row) {
      /* Hack: if dot is at the start of a line, the true
       * mouse point might be at the end of the previous
       * line.  In this case, the mouse row won’t equal
       * dot’s row.  Force the caret point so it’s to
       * the right of the last character on the previous
       * row.
       * Further hack, if selStart is at the end of the
       * text, look at the last character.  If it’s
       * a <return>, caret is really on the next line.
       * Grumble. */
      if (TR.selStart != (DOT) TR.textLength
       || TR.textLength == 0
       || (*TR.hText)[TR.selStart - 1] != ‘\r’) { 
        row = TR.selRow;
        col = _Track_dot_to_col(tr, TR.selStart - 1)  
            + CharWidth((*TR.hText)[TR.selStart - 1]);
      }
    }
    /* Convert to the equivalent pixel coordinate and
     * draw it if it might be visible. */
    row = _Track_row_pixel(tr, row);
    if (row < (LONGINT) TR.viewRect.top - TR.lineHeight
     || row > (LONGINT) TR.viewRect.bottom + TR.lineHeight
     || col < (LONGINT) TR.viewRect.left
     || col > (LONGINT) TR.viewRect.right)
      return;                     /* Invisible          */
    if (TR.caretHook != NIL) {
      SetRect(&caret, (int) col, (int) row - TR.fontAscent,
        (int) col + 1, (int) row + TR.fontDescent);
      CallPascal(&caret, tr, TR.caretHook);
    }
    else {
      /* Fancy “caret.”*/
      GetPenState(&pen_state);
      PenNormal();
      PenMode(patXor);
      MoveTo(col, (int) row - TR.fontAscent);
      Line(0, TR.fontAscent);
      Line(-TR.fontDescent, TR.fontDescent);
      Move(TR.fontDescent * 2, 0);
      Line(-TR.fontDescent, -TR.fontDescent);
      SetPenState(&pen_state);
    }
}

/* invert_rect(tr, row, start, end)
 * Invert the screen rectangle on the specified row
 * between the specified horizontal pixels.
 * Note: end points just to the right of the last pixel
 * to invert.  Also, note that this routine understands
 * that points are normalized, extending them in the
 * appropriate direction to cover the character.*/
static void invert_rect(tr, row, start, end)
register TrackPtr tr;
LONGINT               row;
LONGINT               start;
LONGINT               end;
{
    Rect              box;

    /* Convert row to the equivalent pixel coordinate.*/
    row = _Track_row_pixel(tr, row);
    if (start == end
     || row < (LONGINT) TR.viewRect.top - TR.lineHeight
     || row > (LONGINT) TR.viewRect.bottom + TR.lineHeight)
      return;
    if (start < (LONGINT) TR.viewRect.left)
      start = (LONGINT) TR.viewRect.left;
    if (end > (LONGINT) TR.viewRect.right)
      end = (LONGINT) TR.viewRect.right;
    if (start >= end)
      return;
    box.left = (int) start;
    box.right = (int) end;
    box.top = (int) row + TR.fontDescent - TR.lineHeight;
    box.bottom = (int) row + TR.fontDescent;
    if (SectRect(&box, &TR.viewRect, &box)) {
      if (TR.highHook != NIL)             /* Use user’s */
        CallPascal(&box, tr, TR.highHook);  /*  hiliter */
      else {
        /* Properly hilite color screens (ok for b/w, too)
         * See Inside Mac V, p. 62.*/ 
        HiliteMode &= ~(1 << hiliteBit);
        InvertRect(&box);
      }
    }
}
Listing:  Idle Subroutine
/*                          TrackIdle.c
 * Copyright © 1989 Martin Minow and MacTutor. All rights reserved.
 * void TrackIdle(track_handle)
 * TrackHandle  track_handle;
 * Flash the cursor.*/
#include  “TrackEdit.h”
#define TR  (*tr)

void TrackIdle(track_handle)
TrackHandle track_handle;
{
    register TrackPtr tr;
    _Track_state      state;
    register LONGINT  now;
    
    tr = _Track_lock(track_handle, &state);
    if (TR.selStart == TR.selEnd) {
      now = TickCount();
      if (now > TR.caretTime) {
        _Track_caret(tr, _Track_caret_invert);
        TR.caretTime = now + CaretTime;
      }
    }
    _Track_unlock(&state);
}
Listing:  Initialization Subroutines
/*                      TrackInit.c
 * Copyright © 1989 Martin Minow and MacTutor. All rights reserved.
 * void TrackInit()
 *
 * TrackHandle TrackNew(dest, view)
 * Rect         *dest;
 * Rect         *view;
 *
 * void TrackDispose(track_handle)
 * TrackHandle  track_handle;
 * Initialize, create, and destroy TrackRecords. */
#include  “TrackEdit.h”
#define TR  (*tr)

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

void TrackInit()
{
    TrackScrpHandle = NewHandle(0);
    TrackScrpLength = 0;
}

TrackHandle TrackNew(line_width, viewRectp)
INTEGER line_width;
Rect    *viewRectp;
{
    TrackHandle       track_handle;
    TrackPtr          tr;
    register char     *ptr;
    register int      i;
    FontInfo          info;
    Handle            temp;
    
    track_handle =
      (TrackHandle) NewHandle(sizeof (TrackRecord));
    /* Initialize the record by clearing out all fields.
     * Note that we can’t call _Track_lock here.*/
    HLock(track_handle);
    tr = (*track_handle);
    ptr = (char *) tr;
    for (i = 0; i < sizeof (TrackRecord); i++)
      *ptr++ = 0;
    TR.inPort = thePort;
    TR.hText = NewHandle(0L);
    /* Test whether the ScriptManager is installed,
     * Note that the ROMS must not change while the
     * program is running.*/
#define Unimplemented 0x9F
#define ScriptUtil    0xB5
    if ((GetTrapAddress(Unimplemented)
      != GetTrapAddress(ScriptUtil))
     && GetEnvirons(smEnabled)) {
      _Track_set(tr, _Track_has_script_manager);
      _Track_set(tr, _Track_use_script_manager);
      _Track_set(tr, _Track_use_smart_cut_paste);
    }
    TR.lineWidth = line_width;
    TR.viewRect = *viewRectp;
    TR.txFont = thePort->txFont;
    TR.txFace = thePort->txFace;
    TR.txMode = thePort->txMode;
    TR.txSize = thePort->txSize;
    GetFontInfo(&info);
    TR.fontAscent = info.ascent;
    TR.fontDescent = info.descent;
    TR.lineHeight =
      info.ascent + info.descent + info.leading;
    HUnlock(track_handle);
    return (track_handle);
}

void TrackDispose(track_handle)
TrackHandle track_handle;
{
    DisposHandle((*track_handle)->hText);
    DisposHandle(track_handle);
}
Listing:  Insert Text Subroutine
/*                      TrackInsert.c
 * Copyright © 1989 Martin Minow and MacTutor. All rights reserved.
 * TrackInsert(text, length, track_handle)
 * Ptr          text;
 * LONGINT      length;
 * TrackHandle  track_handle;
 * Insert the specified text just before the selection
 * range.  Do not change either the selection range or
 * the Track private scrap.*/
#include “TrackEdit.h”
#define TR  (*tr)

void TrackInsert(text, length, track_handle)
Ptr         text;
LONGINT     length;
TrackHandle track_handle;
{
    register TrackPtr tr;
    _Track_state      state;
    DOT               start;
    
    tr = _Track_lock(track_handle, &state);
    start = TR.selStart;
    _Track_do_insert(tr, TR.selStart, text, length);
    TR.selStart += length;
    TR.selEnd += length;
    _Track_rebuild(track_handle, start);
    _Track_unlock(&state);
}
Listing:  Insert Character Subroutine
/*                      TrackKey.c
 * Copyright © 1989 Martin Minow and MacTutor. All rights reserved.
 *
 * TrackKey(key, track_handle)
 * key          The character to insert
 * track_handle The TrackRecord handle.
 * Replace the selection character, leaving an insertion
 * point just past the inserted charcter.  If the key
 * is backspace (hex 08), the selection or character
 * before the insertion point is deleted.
 *
 * Note: we don’t do “arrow-key” selection as described
 * in Inside Mac, volume IV.*/
#include “TrackEdit.h”
#define TR  (*tr)

void TrackKey(c, track_handle)
CHAR        c;
TrackHandle track_handle;
{
    register TrackPtr tr;
    _Track_state      state;
    DOT               start;
    char              text[1];
    
    tr = _Track_lock(track_handle, &state);
    start = TR.selStart;
    if (c != ‘\b’) {
      _Track_do_clear(track_handle, FALSE, FALSE);
      tr = (*track_handle);
      text[0] = c;
      _Track_do_insert(tr, TR.selStart, text, 1L);
      TR.selStart++;
      TR.selEnd = TR.selStart;
    }
    else {
      /* Backspace cuts the selection, or, if there is
       * none, the preceeding character. */
      if (TR.selEnd == TR.selStart
       && TR.selStart > 0)
        --TR.selStart;
      start = TR.selStart;
      _Track_do_clear(track_handle, FALSE, FALSE);
    }
    _Track_rebuild(track_handle, start);
    _Track_unlock(&state);
}
Listing:  Locate Selection Subroutines
/*                    TrackLocation.c    
 * Copyright © 1989 Martin Minow and MacTutor. All rights reserved.
 * These routines convert from display (mouse) coordinates
 * to the internal DOT value that designates data.
 *
 * DOT TrackGetOffset(point, track_handle)
 * Point        point;
 * TrackHandle  track_handle;
 *  TrackGetOffset returns the index of the character
 *  corresponding to the given point (expressed in
 *  window-local coordinates).
 *
 * _Track_mouse_to_dot()    Convert window-local mouse
 *        coordinates to a character designator.
 * _Track_word()            Extend window-local coodinates
 *        to designate the start and end of a word.
 * _Track_is_white()        Determine if a character is
 *        “whitespace”.
 * _Track_pixel_row()       Locate the row (line of text)
 *        that is designated by the mouse vertical loc. */
#include “TrackEdit.h”
#define TR    (*tr)
#define NIL   0L

DOT TrackGetOffset(point, track_handle)
Point       point;
TrackHandle track_handle;
{
    register TrackPtr tr;
    _Track_state      state;
    DOT               result;
    
    tr = _Track_lock(track_handle, &state);
    result =_Track_mouse_to_dot(tr, point);
    _Track_unlock(&state);
    return (result);
}

/* _Track_mouse_to_dot(mouse)
 * Convert the mouse position (given in window-local
 * coordinates) to an index to the specified character in
 * the string.  If the mouse is in the left half of the
 * character, the index refers to the selected character;
 * otherwise it refers to the following character.*/
DOT _Track_mouse_to_dot(tr, mouse)
register TrackPtr tr;
Point             mouse;
{
    register LONGINT  row;
    register INTEGER  col;
    register int      i;
    register DOT      dot;
    register DOT      next_row;
    register int      width;
    
    row = _Track_pixel_row(tr, mouse.v);
    if (row < 0)
      return (0);
    else if (row >= TR.nLines)
      return (TR.textLength);
    else {
      dot = TR.lineStarts[row];
      next_row = TR.lineStarts[row + 1];
      col = mouse.h - _Track_h_origin(tr, row);
      width = 0;
      while (dot < next_row
          && (*TR.hText)[dot] != ‘\r’) {
        width = CharWidth((*TR.hText)[dot]);
        if (col < width)
          break;
        col -= width;
        dot++;
      }
      /* If the mouse is in the right-half of the
       * character, (and it’s not at the end of the row),
       * move it forward: note that the DOT value is
       * between two characters.*/
      if (dot < next_row && col >= (width / 2))
        dot++;
    }
    return (dot);
}

/* _Track_word(tr, mouse, DOT *, DOT *)
 * Extend dot in both directions to the nearest word
 * boundary.  Note that the last character on the line is
 * treated specially. Bug alert: FindWord uses a 16 bit
 * integer for textLength and offset. Thus, as our indices
 * are longs (so we can have a lot of text), we should
 * really fiddle with *hText so FindWord only sees one
 * line of text.*/
void _Track_word(tr, mouse, word)
register TrackPtr tr;
Point             mouse;
_Track_Loc        *word;
{
    register DOT      dot;
    register LONGINT  row;
    register DOT      end;
    OffsetTable       offsets;
    
    dot = _Track_mouse_to_dot(tr, mouse);
    if (_Track_is_set(tr, _Track_use_script_manager)) {
      FindWord(*TR.hText, (INTEGER) TR.textLength,
        (INTEGER) dot, TRUE,  NIL, offsets);
#if smgrVers >= 0x0210  /* See ScriptMgr.h            */
    /* In Think C version 4, the offset table is defined
     * as a 3-element structure.*/
#define START offsets[0].offFirst
#define END   offsets[0].offSecond
#else
    /* This is specific to Think C version 3, where the
     * offset table is defined as a 6-element short vector.
     * Warning: no longer tested.*/
#define START offsets[0]
#define END   offsets[1]
#endif
      word->start = (DOT) START;
      word->end = (DOT) END;
    }
    else {    
      row = _Track_row(tr, dot);
      end = TR.lineStarts[row];
      word->start = dot;
      while (word->start > end
         && !_Track_is_white(
                tr, *(TR.hText), word->start - 1)) {
        --(word->start);
      }
      word->end = dot;
      if (_Track_pixel_row(tr, mouse.v) == row) {
        /* Scan for the whitespace that follows this word.
         * Note that we don’t scan if dot has crept into
         * the next line. */
        end = TR.lineStarts[row + 1];
        while (word->end < end
           && !_Track_is_white(tr, (*TR.hText), word->end))
          (word->end)++;
      }
    }
}

/* _Track_is_white(track_pointer, text_pointer, index)
 * Return TRUE if the character at this location is
 * whitespace, calling the application’s wordbreak
 * routine if one is present.*/
Boolean _Track_is_white(tr, ptr, index)
register TrackPtr tr;
char              *ptr;
DOT               index;
{
    if (TR.wordBreak == 0)
      return (((unsigned) ptr[index]) <= ‘ ‘);
    else {
      return (CallPascalB(ptr, index, TR.wordBreak));
    }
}

/* Determine the row that contains this mouse location
 * (in window-local coordinates). */
LONGINT _Track_pixel_row(tr, vpixel)
register TrackPtr tr;
INTEGER           vpixel;
{
    return (
        ((LONGINT) vpixel - TR.viewRect.top + TR.topPixel)
      / TR.lineHeight);
}
Listing:  Lock TrackRecord Subroutine
/*                      TrackLock.c
 * Copyright © 1989 Martin Minow and MacTutor. All rights reserved.
 * _Track_lock(track_handle, &state)
 * TrackHandle  track_handle;
 * _Track_unlock(&state)
 * _Track_lock() is called at the beginning of all
 * functions to lock the TrackHandle in memory, save
 * the old port, clip region and text parameters; and
 * and set our port, clipRect, and text parameters.
 * _Track_unlock() is called at the end to restore
 * the original parameters.*/
 
#include “TrackEdit.h”    
#define TR  (*tr)

/* _Track_lock is called at the start of all
 * user-callable functions to lock the TrackRecord
 * in memory.  It returns a pointer to the record.*/
TrackPtr _Track_lock(track_handle, sp)
TrackHandle           track_handle;
register _Track_state *sp;
{
    register TrackPtr   tr;
    
    sp->track_handle = track_handle;
    sp->oldHState = HGetState(track_handle);
    MoveHHi(track_handle);
    HLock(track_handle);
    tr = (*track_handle);
    GetPort(&sp->oldPort);
    SetPort(TR.inPort);
    /* Save the old clip region and set the clip region to
     * the intersection of the old region and the viewRect.*/
    sp->oldClip = NewRgn();
    GetClip(sp->oldClip);
    ClipRect(&TR.viewRect);
    SectRgn(
      sp->oldClip, thePort->clipRgn, thePort->clipRgn);
    /*Save the old drawing parameters. (Perhaps even color?)*/
    sp->oldFont = thePort->txFont;  TextFont(TR.txFont);
    sp->oldFace = thePort->txFace;  TextFace(TR.txFace);
    sp->oldMode = thePort->txMode;  TextMode(TR.txMode);
    sp->oldSize = thePort->txSize; TextSize(TR.txSize);
    return (tr);
}

/* _Track_unlock()
 * Restore the track handle to the state it had on entrance.*/
void _Track_unlock(sp)
register _Track_state *sp;
{
    TextSize(sp->oldSize);
    TextMode(sp->oldMode);
    TextFace(sp->oldFace);
    TextFont(sp->oldFont);
    SetClip(sp->oldClip);
    DisposeRgn(sp->oldClip);
    SetPort(sp->oldPort);
    HSetState(sp->track_handle, sp->oldHState);
}

Listing:  Position Cursor Subroutines
/*                    TrackPosition.c
 * Copyright © 1989 Martin Minow and MacTutor. All rights reserved.
 * Convert from textual to positional coordinates.
 * void TrackGetPosition(dot, track_handle, hpixel, vpixel)
 * DOT          dot;
 * TrackHandle  track_handle;
 * LONGINT      *hpixel;
 * LONGINT      *vpixel;
 * TrackGetPosition returns the point (in window-local
 * coordinates) of the character at the indicated
 * position.  Note that the results are long integers.
 * You should not assume that the point is visible.
 *
 * LONGINT TrackGetHeight(endline, startline, track_handle)
 * LONGINT      endline;
 * LONGINT      startline;
 * TrackHandle  track_handle;
 * TrackGetHeight returns the total height of all of
 * the lines in the text between and including startline
 * and endline.
 * _Track_dot_to_col() determines the horizontal pixel
 *        coordinate for the specified DOT.
 * _Track_dot_to_bol() locates the pixel coordinates at
 *        the beginning of the line containing DOT.
 * _Track_dot_to_eol() locates the pixel coordinates at
 *        the end of the line containing DOT.
 * _Track_row() determines the row (index into lineStarts)
 *        containing DOT.
 * _Track_row_pixel() determines the vertical location
 *        in window-local coordinates for the given row. */
#include “TrackEdit.h”
#define TR (*tr)
static LONGINT  h_pixel(TrackPtr, LONGINT, INTEGER);
static INTEGER  line_length(TrackPtr, LONGINT);

/*  Return the window-position of dot. */
void TrackGetPoint(dot, track_handle, hpixel, vpixel)
DOT               dot;
TrackHandle       track_handle;
LONGINT           *hpixel;
LONGINT           *vpixel;
{
    register TrackPtr tr;
    _Track_state      state;
    LONGINT           row;
    INTEGER           col;
  
    tr = _Track_lock(track_handle, &state);
    row = _Track_row(tr, dot);
    col = (INTEGER) (dot - TR.lineStarts[row]);
    *hpixel = h_pixel(tr, row, col);
    *vpixel = _Track_row_pixel(tr, row);
    _Track_unlock(&state);
}

/* Return the height of the selection (in pixels). */
LONGINT TrackGetHeight(end, start, track_handle)
LONGINT           end;
LONGINT           start;
TrackHandle       track_handle;
{
    register TrackPtr tr;
    _Track_state      state;
    LONGINT           result;
  
    tr = _Track_lock(track_handle, &state);
    result = _Track_row_pixel(tr, _Track_row(tr, end))
           - _Track_row_pixel(tr, _Track_row(tr, start));
    _Track_unlock(&state);
    return (result);
}

/* _Track_dot_to_col()
 * Compute the horizontal position of the DOT in the window.*/
LONGINT _Track_dot_to_col(tr, dot)
register TrackPtr tr;
DOT               dot;
{
    LONGINT       row;
    INTEGER       col;
    
    row = _Track_row(tr, dot);
    col = (INTEGER) (dot - TR.lineStarts[row]);
    return (h_pixel(tr, row, col));
}

/* _Track_dot_to_bol()
 * Compute the horizontal position of the beginning of
 * the line.*/
LONGINT _Track_dot_to_bol(tr, row)
register TrackPtr tr;
LONGINT           row;
{
    return (_Track_h_origin(tr, row));
}

/* _Track_dot_to_eol()
 * Compute the horizontal position of the end of the line.*/
LONGINT _Track_dot_to_eol(tr, row)
register TrackPtr tr;
LONGINT           row;
{
    INTEGER       col;
    
    col = line_length(tr, row);
    return (h_pixel(tr, row, col));
}

/* _Track_row(tr, dot)
 * Return the row that contains dot.  The ancient and
 * honorable binary-chop table lookup algorithm.*/
LONGINT _Track_row(tr, dot)
register TrackPtr tr;
DOT               dot;
{
    register LONGINT  mid;
    register LONGINT  low;
    register LONGINT  high;
    
    low = 0;
    high = TR.nLines - 1;
    while (low <= high) {
      mid = low + (high - low) / 2;
      if (dot < TR.lineStarts[mid])
        high = mid - 1;
      else if (dot >= TR.lineStarts[mid + 1])
        low = mid + 1;
      else {
        return (mid);
      }
    }
    /* Don’t return beyond the last value. */
    if (low >= TR.nLines && TR.nLines > 0)
      low = TR.nLines - 1;
    return (low);
}

LONGINT _Track_row_pixel(tr, row)
register TrackPtr tr;
LONGINT           row;
{
    return (
        (row * TR.lineHeight)
      + TR.viewRect.top       /* To window-local space  */
      + TR.fontAscent         /* To character origin    */
      - TR.topPixel           /* Offset by scrolling    */
    );
}

/* h_pixel()
 * Compute the horizontal pixel position in window-local
 * coordinates of the specified [row, col].  */
LONGINT h_pixel(tr, row, col)
register TrackPtr tr;
LONGINT           row;
INTEGER           col;
{
    LONGINT       pixel;
    Ptr           line_start;
    INTEGER       state;

    state = HGetState(TR.hText);
    HLock(TR.hText);
    line_start = (*TR.hText) + TR.lineStarts[row];
    pixel = TextWidth(line_start, 0, col)
          + _Track_h_origin(tr, row);
    HSetState(TR.hText, state);
    return (pixel);
}

/* _Track_h_origin()
 * Compute the horizontal pixel position in window-local
 * coordinates of the first (i.e., leftmost) character
 * in this row.  This is the only function that
 * knows about text justification.*/
LONGINT _Track_h_origin(tr, row)
register TrackPtr tr;
LONGINT           row;
{
    LONGINT       pixel;
    INTEGER       window_width;
    INTEGER       text_width;
    INTEGER       state;
    
    pixel = TR.viewRect.left - TR.leftPixel;
    if (TR.just != 0) {         /* Not left justified?  */
      window_width = TR.viewRect.right - TR.viewRect.left;
      state = HGetState(TR.hText);
      HLock(TR.hText);
      text_width = TextWidth((*TR.hText) + TR.lineStarts[row],
                    0, line_length(tr, row));
      HSetState(TR.hText, state);
      if (TR.just < 0)          /* Right justified      */
        pixel += (window_width - text_width);
      else {                    /* Center justified     */
        pixel += ((window_width - text_width) / 2);
      }
    }
    return (pixel);
}

/* line_length(tr, row)
 * Return the number of characters in the specified row. */
static int line_length(tr, row)
register TrackPtr tr;
LONGINT           row;
{
    if (row >= TR.nLines)
      return (0);
    return (TR.lineStarts[row + 1] - TR.lineStarts[row]);
}
Listing:  Rebuild Line Pointers Subroutine
/*                    TrackRebuild.c   
 * Copyright © 1989 Martin Minow and MacTutor. All rights reserved.
 * This routine is called whenever the contents of
 * the TrackRecord text changes. It rebuilds the
 * line start vector and repaints the display.*/  
#include “TrackEdit.h”
#define TR    (*tr)
#define NIL   0L
#define TR_SIZE(tr) \
  sizeof (TrackRecord) + (sizeof (DOT) * (*tr).nLines);

static TrackPtr newline(TrackHandle, DOT);

/* _Track_rebuild(track_handle, start)
 *
 * _Track_rebuild() is called whenever the content of
 * the TrackRecord text string changes.  It rebuilds
 * the lineStarts vector and forces the text to be
 * redrawn on the screen.  _Track_rebuild is the only
 * routine that understands line breaks and such.
 * Walk through the string to build a vector that locates
 * the start of each line. There is always one extra entry
 * so the number of characters in the last line can be
 * computed.  The display window is the current port.
 * Note that the word/line algorithm only notices
 * whitespace.  The start parameter identifies the first
 * character that has changed.
 *
 * Since _Track_rebuild temporarily unlocks the
 * TrackRecord, it returns the current value of the
 * relocked record.*/
TrackPtr _Track_rebuild(track_handle, start)
TrackHandle track_handle;
DOT         start;
{
    register TrackPtr   tr;
    register unsigned   c;
    register DOT        index;
    register DOT        line_break; /* space here       */
    register DOT        line_start; /* -> start of line */
    register INTEGER    line_width; /* Current width    */
    LONGINT             size;
    LONGINT             row;
    LONGINT             old_row, new_row;
    Rect                box;
    
    tr = *track_handle;
    MoveHHi(TR.hText);              /* Lock the text    */
    HLock(TR.hText);                /* record.          */
    if (start == 0) {
      TR.nLines = 0;
      index = 0;
    }
    else {
      /* Start at the previous line since, if we delete
       * the end of the first word on a line, it might
       * fit on the previous line. */
      TR.nLines = _Track_row(tr, start);
      if (TR.nLines > 0)
        --TR.nLines;
      index = TR.lineStarts[TR.nLines];
    }
    /* Get the screen position *before* munging the lines.*/
    old_row = _Track_row(tr, start);
    tr = newline(track_handle, index);
    line_start = index;
    line_width = 0;
    line_break = 0;
    /* Each pass through this loop eats one character.*/
    while (index < TR.textLength) {
      c = (*TR.hText)[index++];     /* Grab the byte    */
      if (c == ‘\r’)
        goto do_newline;
      line_width += CharWidth(c);
      if (TR.crOnly < 0 || line_width <= TR.lineWidth) {
        /*
         * This byte fits.  If it’s a word break, remember
         * its location for the end of line test.
         */
        if (_Track_is_white(tr, *TR.hText, index - 1))
          line_break = index;
      }
      else {
        /* We’re at the end of the line.  If we’ve seen
         * a word, break the line there.  Else, break
         * at the previous byte (if there is one on this
         * line,  if the first byte on the line doesn’t
         * fit, stuff it in so we don’t loop forever.*/
        if (line_break != 0)
          index = line_break;     /* Break at word      */
        else if (index > (line_start + 1)) {
          --index;                /* Rescan this one    */
        }
do_newline:
        tr = newline(track_handle, index);
        line_start = index;
        line_width = 0;
        line_break = 0;
      }
    }                             /* Loop on characters */
    TR.lineStarts[TR.nLines] = TR.textLength;
    HUnlock(TR.hText);
    /* All of the line lengths have been set.  Adjust
     * the TrackRecord size in case it’s shrunk.*/
    size = TR_SIZE(tr)
    if (size > GetHandleSize(track_handle)) {
      HUnlock(track_handle);
      SetHandleSize(track_handle, size);
      MoveHHi(track_handle);
      HLock(track_handle);
      tr = *track_handle;
    }
    /* Locate this row on the screen.  If it (or anything
     * later on) will be visible, repaint as little
     * as possible.  Note that we may have to repaint
     * the row that the selection *was* on before we
     * started messing with the lines.*/
    new_row = _Track_row(tr, start);
    if (new_row < old_row)
      old_row = new_row;
    row = _Track_row_pixel(tr, old_row) - TR.fontAscent;
    box = TR.viewRect;
    if (row <= (LONGINT) box.bottom) {
      if (row > (LONGINT) box.top)
        box.top = row;
      EraseRect(&box);
      _Track_do_update(tr, &box);
      ValidRect(&box);
    }
    return (tr);
}

/* newline()
 * Called when a new line is ready: this sets the
 * handle size and stores the index to the first
 * character in this line.*/
static TrackPtr newline(track_handle, index)
TrackHandle track_handle;
DOT         index;
{
    register TrackPtr tr;
    _Track_state      state;
    LONGINT           size;
    
    tr = *track_handle;
    ++TR.nLines;
    size = TR_SIZE(tr);
    if (size > GetHandleSize(track_handle)) {
      /*
       * Reallocate the track handle to its proper size.
       * You can allocate a chunk of lines here without
       * problems.
       */
      HUnlock(track_handle);
      SetHandleSize(track_handle, size);
      MoveHHi(track_handle);
      HLock(track_handle);
      tr = *track_handle;
    }
    TR.lineStarts[TR.nLines - 1] = index;
    return (tr);
}
Listing:  Scrap Management Subroutines
/*                      TrackScrap.c
 * Copyright © 1989 Martin Minow and MacTutor. All rights reserved.
 *
 * OSErr TrackFromScrap()
 * Copy the desk scrap to the Track private scrap.
 * Return noErr if successful, else an error code.
 *
 * OSErr TrackToScrap()
 * Copy the Track private scrap to the desk scrap.
 * Return noErr if successful, else an error code.
 *
 * Handle TrackScrapHandle()
 * Return a handle to the Track private scrap.
 *
 * LONGINT TrackGetScrapLen()
 * Return the length of the Track private scrap in bytes.
 *
 * TrackSetScrapLen(length)
 * LONGINT    length;
 * Set the size of the Track private scrap.  This will
 * call SetHandleSize() on the track handle. */

#include “TrackEdit.h”

/* TrackFromScrap()
 * If there is a TEXT item in the desk scrap, read it in
 * and store it in the Track private scrap.  Return
 * the error status (noErr is normal, noTypErr means
 * there wasn’t any TEXT in the scrap, anything is
 * is trouble.*/
OSErr TrackFromScrap()
{
    long        offset;
    Handle      scrap;
    long        status;
    
    SetHandleSize(TrackScrpHandle, 0);
    status = GetScrap(TrackScrpHandle, ‘TEXT’, &offset);
    if (status >= 0) {
      TrackScrpLength = status;
      status = noErr;
    }
    return (status);
}

/* TrackToScrap()
 * Copy the current selection to the Desk scrap.  Return
 * noErr if ok, else an error code.*/
OSErr TrackToScrap()
{
    OSErr             status;
    
    MoveHHi(TrackScrpHandle);
    HLock(TrackScrpHandle);
    status = PutScrap(
              TrackScrpLength, ‘TEXT’, *TrackScrpHandle);
    HUnlock(TrackScrpHandle);
    return (status);
}

/* Handle
 * TrackScrapHandle()
 * Return a handle to the Track private scrap.  Note:
 * this is the *real* handle, not a copy.*/
Handle TrackScrapHandle()
{
    return (TrackScrpHandle);
}

/* LONGINT TrackGetScrapLen()
 * Return the length of the Track private scrap in bytes. */
LONGINT TrackGetScrapLen()
{
    return (TrackScrpLength);
}

/* TrackSetScrapLen(length)
 * LONGINT    length;
 * Set the size of the Track private scrap.  This will
 * call SetHandleSize() on the track handle.*/
void TrackSetScrapLen(length)
LONGINT     length;
{
    SetHandleSize(TrackScrpHandle, length);
    TrackScrpLength = length;
}
Listing:  Scroll Text Subroutine
/*                      TrackScroll.c
 * Copyright © 1989 Martin Minow and MacTutor. All rights reserved.
 * void TrackScroll(horiz, vert,  track_handle)
 * LONGINT      horiz;
 * LONGINT      vert;
 * TrackHandle  track_handle;
 * Scroll the TrackRecord text.  Both values are in
 * pixels.
 *
 * void TrackPinScroll(horiz, vert, track_handle)
 * LONGINT      horiz;
 * LONGINT      vert;
 * TrackHandle  track_handle;
 * Like TrackScroll, but stop scrolling when the last line
 * is visible.
 */

#include  “TrackEdit.h”
#define TR  (*tr)
#define abs(x)  ((x) < 0 ? (-(x)) : (x))
#define height(r) ((r).bottom - (r).top)
#define width(r) ((r).right - (r).left)
#define pin(delta, current, max) (                \
    (delta < 0) ? ((current <= 0)  ? 0 : delta)   \
                : ((current >= max) ? 0 : delta)  \
  )

void TrackScroll(hscroll, vscroll, track_handle)
LONGINT     hscroll;
LONGINT     vscroll;
TrackHandle track_handle;
{
    register TrackPtr tr;
    _Track_state      state;
    
    tr = _Track_lock(track_handle, &state);
    _Track_do_scroll(tr, hscroll, vscroll);
    _Track_unlock(&state);
}

void TrackPinScroll(hscroll, vscroll, track_handle)
LONGINT     hscroll;
LONGINT     vscroll;
TrackHandle track_handle;
{
    register TrackPtr tr;
    _Track_state      state;
    LONGINT           hmax, vmax;
    
    tr = _Track_lock(track_handle, &state);
    hmax = TR.lineWidth - width(TR.viewRect);
    vmax = (TR.nLines * TR.lineHeight)
         + TR.fontDescent - height(TR.viewRect);
    vscroll = pin(vscroll, TR.topPixel, vmax);
    if (TR.crOnly >= 0)
      hscroll = pin(hscroll, TR.leftPixel, hmax);
    else if (hscroll < 0 && TR.leftPixel <= 0)
      hscroll = 0;
    _Track_do_scroll(tr, hscroll, vscroll);
    _Track_unlock(&state);
}

void _Track_do_scroll(tr, hscroll, vscroll)
register TrackPtr tr;
LONGINT           hscroll;
LONGINT           vscroll;
{
    RgnHandle         old_clip;
    RgnHandle         scroll_region;
    Rect              scroll_rect;

    /* Save the old clip region so we can be called
     * multiple times between update events.  This
     * is needed for autoscrolling.*/
    old_clip = NewRgn();
    GetClip(old_clip);
    scroll_region = NewRgn();
    TR.topPixel += vscroll;
    TR.leftPixel += hscroll;
    if (abs(vscroll) > height(TR.viewRect)
     || abs(hscroll) > width(TR.viewRect)) {
      EraseRect(&TR.viewRect);
      scroll_rect = TR.viewRect;
      RectRgn(scroll_region, &scroll_rect);
    }
    else {
      ScrollRect(&TR.viewRect, (INTEGER) -hscroll,
        (INTEGER) -vscroll, scroll_region);
      scroll_rect = (*scroll_region)->rgnBBox;
      SetClip(scroll_region);
    }
    _Track_do_update(tr, &scroll_rect);
    ValidRgn(scroll_region);
    DisposeRgn(scroll_region);
    SetClip(old_clip);
    DisposeRgn(old_clip);
}
Listing:  Set Justification Subroutine
/*                      TrackSetJust.c       
 * Copyright © 1989 Martin Minow and MacTutor. All rights reserved.
 * void
 * TrackSetJust(just, track_handle)
 * INTEGER      just;
 * TrackHandle  track_handle;
 * Flash the cursor.
 */
#include  “TrackEdit.h”
#define TR  (*tr)

void TrackSetJust(just, track_handle)
INTEGER     just;
TrackHandle track_handle;
{
    register TrackPtr tr;
    _Track_state      state;
    
    tr = _Track_lock(track_handle, &state);
    TR.just = just;
    InvalRect(&TR.viewRect);
    _Track_unlock(&state);
}
Listing:  Set Selection Subroutine
/*                      TrackSetSelect.c
 * Copyright © 1989 Martin Minow and MacTutor. All rights reserved.
 * void TrackSetSelect(start, end,  track_handle)
 * DOT          start;
 * DOT          end;
 * TrackHandle  track_handle;
 * Set the selection range.
 */

#include  “TrackEdit.h”
#define TR  (*tr)

void TrackSetSelect(sel_start, sel_end,  track_handle)
DOT         sel_start;
DOT         sel_end;
TrackHandle track_handle;
{
    register TrackPtr tr;
    _Track_state      state;
    static Point      zero_point;
    
    TrackDeactivate(track_handle);  /* Undraw selection */
    tr = (*track_handle);           /* Lock unnecessary */
    if ((TR.selStart = sel_start) < 0)
      TR.selStart = 0;
    if ((TR.selEnd = sel_end) >  TR.textLength)
      TR.selEnd = TR.textLength;
    TR.selRow = _Track_row(tr, TR.selStart);
    TrackActivate(track_handle);    /* Redraw selection */
}
Listing:  Set Text Subroutine
/*                      TrackSetText.c
 * Copyright © 1989 Martin Minow and MacTutor. All rights reserved.
 * TrackSetText(text, length, track_handle)
 * text         Data to insert
 * length       The number of bytes to insert
 * track_handle The TrackRecord handle
 * Copy the specified text into the TrackRecord.  Any
 * text currently in the TrackRecord is lost.  The
 * selection range is set to an insertion point at the
 * end of the text.  (Note: TextEdit TESetText “doesn’t
 * dispose of any text currently in the edit record.”)
 * The text origin is reset to “upper-left” corner.
*/
#include  “TrackEdit.h”
#define TR  (*tr)

void TrackSetText(text, length, track_handle)
Ptr         text;
LONGINT     length;
TrackHandle track_handle;
{
    register TrackPtr tr;
    _Track_state      state;
    Boolean           track_was_active;
    
    /* Dump the selection, stuff the text, then
     * recreate the selection.*/
    track_was_active =
      _Track_is_set((*track_handle), _Track_is_active);
    if (track_was_active)
      TrackDeactivate(track_handle);
    tr = _Track_lock(track_handle, &state);
    SetHandleSize(TR.hText, length);
    BlockMove(text, *TR.hText, length);
    TR.selStart = TR.selEnd = length;
    TR.textLength = length;
    TR.topPixel = 0;
    TR.leftPixel = 0;
    _Track_rebuild(track_handle, 0L);
    _Track_unlock(&state);
    if (track_was_active)
      TrackActivate(track_handle);
}
Listing:  Update Event Subroutines
/*                      TrackUpdate.c  
 * Copyright © 1989 Martin Minow and MacTutor. All rights reserved.
 * Update the track data.  User programs call TrackUpdate,
 * internally, _Track_do_update is called.
 * void TrackUpdate(rectp, track_handle)
 * Rect         *rectp;
 * TrackHandle  track_handle;
 *
 * void _Track_do_update(tr, rectp)
 * TrackPtr     tr;
 * Rect         *rectp;
 */
 
#include “TrackEdit.h”
#define TR  (*tr)

static void     drawtext(TrackPtr, Rect *);

void TrackUpdate(update_rect_ptr, track_handle)
Rect        *update_rect_ptr;
TrackHandle track_handle;
{
    register TrackPtr tr;
    _Track_state      state;
    
    tr = _Track_lock(track_handle, &state);
    _Track_do_update(tr, update_rect_ptr);
    _Track_unlock(&state);
}

void _Track_do_update(tr, update_rect_ptr)
register TrackPtr tr;
Rect              *update_rect_ptr;
{
    RgnHandle         clip;
    Rect              view;

    /* Get a copy of the current grafPort's clipping
     * region (TrackLock has already clipped the window
     * to the TrackRecord's viewRect) and intersect that
     * with the update_rect.  Don't bother restoring
     * the clipRgn: TrackUnlock will do that.*/
    clip = NewRgn();
    RectRgn(clip, update_rect_ptr);
    SectRgn(clip, thePort->clipRgn, thePort->clipRgn);
    /* Draw any text inside the update rectangle, then.
     * if the window is active, invert any hilited text.*/
    drawtext(tr, update_rect_ptr);
    if (_Track_is_set(tr, _Track_is_active)) {
      _Track_hilite(tr, TR.selStart, TR.selEnd);
      if (_Track_is_set(tr, _Track_caret_visible))
        _Track_invert_caret(tr);
    }
    DisposeRgn(clip);
}

/* draw_text(track_ptr, draw_rect)
 * Draw text onto the screen.  The caller has set
 * the grafPort's clip rect.  The draw_rect defines
 * the top and bottom of the data to be drawn.*/
static void drawtext(tr, view)
register TrackPtr tr;
Rect              *view;
{
    register int      vpixel;
    register long     first, last;
    register int      t_width, w_width;
    register int      hpixel;

    /* Locate the first and last rows to be drawn.
     * Note that we may have a partial last line,
     * so we try to draw one extra.*/
    first = _Track_pixel_row(tr, view->top);
    last = _Track_pixel_row(tr, view->bottom) + 1;
    if (last >= TR.nLines)
      last = TR.nLines;
    /* Since the view rectange we are presented may not
     * be aligned with our line grid, we compute our
     * pen position so it's aligned with the top row.*/
    vpixel = _Track_row_pixel(tr, first);
    MoveHHi(TR.hText);
    HLock(TR.hText);
    while (first < last) {
      if (first >= 0) {
        MoveTo((INTEGER) _Track_h_origin(tr, first), vpixel);
        DrawText(*TR.hText,TR.lineStarts[first],
          TR.lineStarts[first + 1] - TR.lineStarts[first]);
      }
      vpixel += TR.lineHeight;
      first++;
    }
    HUnlock(TR.hText);
}

 
AAPL
$100.86
Apple Inc.
-0.77
MSFT
$46.76
Microsoft Corpora
+0.52
GOOG
$579.95
Google Inc.
+6.85

MacTech Search:
Community Search:

Software Updates via MacUpdate

Transmit 4.4.7 - Excellent FTP/SFTP clie...
Transmit is an excellent FTP (file transfer protocol), SFTP, S3 (Amazon.com file hosting) and iDisk/WebDAV client that allows you to upload, download, and delete files over the internet. With the... Read more
Macgo Blu-ray Player 2.10.8.1715 - Blu-r...
Macgo Mac Blu-ray Player can bring you the most unforgettable Blu-ray experience on your Mac. Overview Macgo Mac Blu-ray Player can satisfy just about every need you could possibly have in a Blu-ray... Read more
Capture One Pro 8.0.0.433 - RAW workflow...
Capture One Pro 8 is a professional RAW converter offering you ultimate image quality with accurate colors and incredible detail from more than 300 high-end cameras -- straight out of the box. It... Read more
Adobe Acrobat Pro 11.0.09 - Powerful PDF...
Adobe Acrobat allows users to communicate and collaborate more effectively and securely. Unify a wide range of content in a single organized PDF Portfolio. Collaborate through electronic document... Read more
Adobe Reader 11.0.09 - View PDF document...
Adobe Reader allows users to view PDF documents. You may not know what a PDF file is, but you've probably come across one at some point. PDF files are used by companies and even the IRS to... Read more
iFFmpeg 4.6.1 - Convert multimedia files...
iFFmpeg is a graphical front-end for FFmpeg, a command-line tool used to convert multimedia files between formats. The command line instructions can be very hard to master/understand, so iFFmpeg does... Read more
NTFS 11.3.62 - 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
OS X Yosemite 10.10 DP8 - Developer Prev...
Note: This is a Developer Preview. You must be a registered Apple Mac Developer to download this update. You can also sign up for the free OS X Beta Program to download and preview public beta... Read more
FotoMagico 4.5 - Powerful slideshow crea...
FotoMagico lets you create professional slideshows from your photos and music with just a few, simple mouse clicks. It sports a very clean and intuitive yet powerful user interface. High image... Read more
Screenshot Path 1.2.1 - Change the defau...
Screenshot Path lets you change the folder where OS X saves screenshots. Screenshots are saved by default to the user’s desktop. This is handy for the occasional screenshot but those looking to take... Read more

Latest Forum Discussions

See All

Zynga Unveils First Look at Looney Tunes...
Zynga Unveils First Look at Looney Tunes Dash! Posted by Ellis Spice on September 16th, 2014 [ permalink ] Be vewy, vewy qwiet. I’m hunting wunners featuwing wabbits, and luckily, it’s duck season wunner season. | Read more »
Guardians on the Go – How to Connect to...
At this point I’m relatively certain most of the country is familiar with Destiny. And for those looking to augment their game a bit, Bungie has changed their Bungie Mobile Companion App to Destiny Companion. | Read more »
Kunin Review
Kunin Review By Jordan Minor on September 16th, 2014 Our Rating: :: SHURIKEN DO ITUniversal App - Designed for iPhone and iPad Kunin is a nervous test of ninja reflexes.   Developer: Dodreams Price: FREE Version Reviewed: 1.0.1... | Read more »
4 KEMCO Titles are Just $0.99 for a Limi...
4 KEMCO Titles are Just $0.99 for a Limited Time Posted by Jessica Fisher on September 16th, 2014 [ permalink ] KEMCO RPGs Destiny Fantasia, Infinite Dunamis, Bonds of the Sk | Read more »
Introducing Flash, the Latest Wearable F...
Introducing Flash, the Latest Wearable Fitness Monitor from Misfit Posted by Jessica Fisher on September 16th, 2014 [ permalink ] The Misfit Flash is the newly-released fitness and sleep monitor from | Read more »
Hyper Trip Review
Hyper Trip Review By Jennifer Allen on September 16th, 2014 Our Rating: :: HYPER TWITCHYUniversal App - Designed for iPhone and iPad Tough and unforgiving, Hyper Trip is a bit like Snake – if Snake was really harsh.   | Read more »
Collectible Card Game Earthcore: Shatter...
Collectible Card Game Earthcore: Shattered Elements is Set to Arrive on iOS in 2015 Posted by Ellis Spice on September 16th, 2014 [ permalink ] Polish developers | Read more »
Boogey Boy Review
Boogey Boy Review By Jennifer Allen on September 16th, 2014 Our Rating: :: PRETTY BUT BASICUniversal App - Designed for iPhone and iPad It looks delightful but lack of Game Center support and more variety really affects the fun... | Read more »
Vizzywig 4K (Photography)
Vizzywig 4K 1.0 Device: iOS iPhone Category: Photography Price: $999.99, Version: 1.0 (iTunes) Description: REQUIRES: iOS 7 on iPhone 5S with 32GB or 64GB. (Do not use iOS 8)The world's FIRST mobile 4K video capture, editing and... | Read more »
The Sleeping Prince Review
The Sleeping Prince Review By Jennifer Allen on September 15th, 2014 Our Rating: :: RESTRICTIVE KINGDOM SAVINGUniversal App - Designed for iPhone and iPad The Sleeping Prince looks and feels great to play, but its lack of peril and... | Read more »

Price Scanner via MacPrices.net

Sprint offers 16GB iPad mini for $199.99 with...
Sprint is offering 1st generation 16GB iPad minis for $199.99 with a 2-year service agreement. Standard MSRP for this iPad is $429. Their price is the lowest available for this model. Read more
2.5GHz Mac mini remains on sale for $549, sav...
B&H Photo has the 2.5GHz Mac mini on sale for $549.99 including free shipping. That’s $50 off MSRP, and B&H will also include a free copy of Parallels Desktop software. NY sales tax only. Read more
Apple refurbished iMacs available for up to $...
The Apple Store has Apple Certified Refurbished iMacs available for up to $300 off the cost of new models. Apple’s one-year warranty is standard, and shipping is free. These are the best prices on... Read more
13″ 2.5GHz MacBook Pro offered for $100 off M...
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
Free GIMP Professional Grade Graphics App Ver...
The latest 2.8.14 version of the oddly-named GIMP (acronym for: GNU Image Manipulation Program) open source, high-end image editing and creation alternative to Adobe’s Photoshop and refuge from... Read more
Apple Announces Record Pre-orders for iPhone...
Apple has released metrics showing a record number of first day pre-orders of iPhone 6 and iPhone 6 Plus, with over four million sold in the first 24 hours. Demand for the new iPhones exceeds the... Read more
10% off iPhone 6 and 6 Plus Otterbox cases
Get 10% off on popular Otterbox iPhone 6 and iPhone 6 Plus cases at MacMall through September 19th. Use code OTTERBOX10 to see the discount. Read more
15-inch MacBook Pros on sale for up to $125 o...
Amazon has the new 2014 15″ Retina MacBook Pros on sale for up to $125 off MSRP including free shipping: - 15″ 2.2GHz Retina MacBook Pro: $1899.99 save $100 - 15″ 2.5GHz Retina MacBook Pro: $2374... Read more
27-inch 3.2GHz iMac on sale for $1698, $101 o...
Abt has the 27″ 3.2GHz iMac on sale for $1698 including free shipping. Their price is $101 off MSRP. Read more
More To Making A Larger iPad Than Expanded Sc...
CNET’s Ross Rubin has posted a thoughtful analysis of prospects for a larger display iPad Pro, noting that Microsoft and Samsung currently have the large-display touchscreen tablet category to... Read more

Jobs Board

*Apple* Retail - Multiple Positions (US) - A...
Sales Specialist - Retail Customer Service and Sales Transform Apple Store visitors into loyal Apple customers. When customers enter the store, you're also the Read more
*Apple* Retail - Multiple Positions (US) - A...
Sales Specialist - Retail Customer Service and Sales Transform Apple Store visitors into loyal Apple customers. When customers enter the store, you're also the Read more
*Apple* Retail - Multiple Positions (US) - A...
Sales Specialist - Retail Customer Service and Sales Transform Apple Store visitors into loyal Apple customers. When customers enter the store, you're also the Read more
*Apple* Retail - Multiple Positions (US) - A...
Sales Specialist - Retail Customer Service and Sales Transform Apple Store visitors into loyal Apple customers. When customers enter the store, you're also the Read more
*Apple* Retail - Multiple Positions (US) - A...
Sales Specialist - Retail Customer Service and Sales Transform Apple Store visitors into loyal Apple customers. When customers enter the store, you're also the Read more
All contents are Copyright 1984-2011 by Xplain Corporation. All rights reserved. Theme designed by Icreon.