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
$119.00
Apple Inc.
+1.40
MSFT
$47.75
Microsoft Corpora
+0.28
GOOG
$540.37
Google Inc.
-0.71

MacTech Search:
Community Search:

Software Updates via MacUpdate

HoudahSpot 3.9.6 - Advanced file search...
HoudahSpot is a powerful file search tool built upon MacOS X Spotlight. Spotlight unleashed Create detailed queries to locate the exact file you need Narrow down searches. Zero in on files Save... Read more
RapidWeaver 6.0.3 - Create template-base...
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
iPhoto Library Manager 4.1.10 - Manage m...
iPhoto Library Manager lets you organize your photos into multiple iPhoto libraries. Separate your high school and college photos from your latest summer vacation pictures. Or keep some photo... Read more
iExplorer 3.5.1.9 - View and transfer al...
iExplorer is an iPhone browser for Mac lets you view the files on your iOS device. By using a drag and drop interface, you can quickly copy files and folders between your Mac and your iPhone or... Read more
MacUpdate Desktop 6.0.3 - Discover and i...
MacUpdate Desktop 6 brings seamless 1-click installs and version updates to your Mac. With a free MacUpdate account and MacUpdate Desktop 6, Mac users can now install almost any Mac app on macupdate.... Read more
SteerMouse 4.2.2 - Powerful third-party...
SteerMouse is an advanced driver for USB and Bluetooth mice. It also supports Apple Mighty Mouse very well. SteerMouse can assign various functions to buttons that Apple's software does not allow,... Read more
iMazing 1.1 - Complete iOS device manage...
iMazing (was DiskAid) is the ultimate iOS device manager with capabilities far beyond what iTunes offers. With iMazing and your iOS device (iPhone, iPad, or iPod), you can: Copy music to and from... Read more
PopChar X 7.0 - Floating window shows av...
PopChar X helps you get the most out of your font collection. With its crystal-clear interface, PopChar X provides a frustration-free way to access any font's special characters. Expanded... Read more
Carbon Copy Cloner 4.0.3 - Easy-to-use b...
Carbon Copy Cloner backups are better than ordinary backups. Suppose the unthinkable happens while you're under deadline to finish a project: your Mac is unresponsive and all you hear is an ominous,... Read more
ForeverSave 2.1.3 - Universal auto-save...
ForeverSave auto-saves all documents you're working on while simultaneously doing backup versioning in the background. Lost data can be quickly restored at any time. Losing data, caused by... Read more

Latest Forum Discussions

See All

Make Way for Fat Chicken, from the Maker...
Make Way for Fat Chicken, from the Makers of Scrap Squad Posted by Jessica Fisher on November 26th, 2014 [ permalink ] Relevant Games has announced they will be releasing their reverse tower defense game, | Read more »
Tripnary Review
Tripnary Review By Jennifer Allen on November 26th, 2014 Our Rating: :: TRAVEL BUCKET LISTiPhone App - Designed for the iPhone, compatible with the iPad Want to create a travel bucket list? Tripnary is a fun way to do exactly that... | Read more »
Ossian Studios’ RPG, The Shadow Sun, is...
Ossian Studios’ RPG, The Shadow Sun, is Now Available for $4.99 Posted by Jessica Fisher on November 26th, 2014 [ permalink ] Universal App - Designed for iPhone and iPad | Read more »
Mmmm, Tasty – Having the Angry Birds for...
The very first Angry Birds debuted on iOS back in 2009. When you sit back and tally up the number of Angry Birds games out there and the impact they’ve had on pop culture as a whole, you just need to ask yourself: “How would the birds taste... | Read more »
Rescue Quest Review
Rescue Quest Review By Jennifer Allen on November 26th, 2014 Our Rating: :: PATH BASED MATCH-3Universal App - Designed for iPhone and iPad Guide a wizard to safety by matching gems. Rescue Quest might not be an entirely original... | Read more »
You Can Play the Final Chapter of Lone W...
You Can Play the Final Chapter of Lone Wolf: Dawn Over V’taag Right Now Posted by Jessica Fisher on November 26th, 2014 [ permalink ] Universal App - Designed for iPhone and iPad | Read more »
Swords of Anima (Games)
Swords of Anima 1.0 Device: iOS Universal Category: Games Price: $2.99, Version: 1.0 (iTunes) Description: A new tactical turn-based RPG experience. Command the Savior Rex Squad in an epic journey of courage and deception. Can you... | Read more »
Audio Defence: Zombie Arena
Audio Defence: Zombie Arena By Lee Hamlet on November 26th, 2014 Our Rating: :: DRAGS ITS FEETUniversal App - Designed for iPhone and iPad From the makers of Papa Sangre comes a defense game that forces players to listen carefully... | Read more »
Tales from the Borderland​s Will be Comi...
Tales from the Borderland​s Will be Coming to iOS by the End of the Year Posted by Jessica Fisher on November 26th, 2014 [ permalink ] Telltale Games has announced | Read more »
Sunburn! Review
Sunburn! Review By Campbell Bird on November 26th, 2014 Our Rating: :: DON'T DIE ALONEUniversal App - Designed for iPhone and iPad Platform through the depths of space to make sure your entire crew dies together in this satisfying... | Read more »

Price Scanner via MacPrices.net

2014 1.4GHz Mac mini on sale for $449, save $...
 B&H Photo has the new 1.4GHz Mac mini on sale for $449.99 including free shipping plus NY tax only. Their price is $50 off MSRP, and it’s the lowest price available for this new model. Adorama... Read more
Early Black Friday pricing on 27-inch 5K iMac...
 B&H Photo continues to offer Black Friday sale prices on the 27″ 3.5GHz 5K iMac, in stock today and on sale for $2299 including free shipping plus NY sales tax only. Their price is $200 off MSRP... Read more
Early Black Friday sale prices on iPad Air 2,...
 MacMall is discounting iPad Air 2s by up to $75 off MSRP as part of their Black Friday sale. Shipping is free: - 16GB iPad Air WiFi: $459 $40 off - 64GB iPad Air WiFi: $559 $40 off - 128GB iPad Air... Read more
Early Black Friday MacBook Air sale prices, $...
 MacMall has posted early Black Friday MacBook Air sale prices. Save $101 on all models for a limited time: - 11″ 1.4GHz/128GB MacBook Air: $798 - 11″ 1.4GHz/256GB MacBook Air: $998 - 13″ 1.4GHz/... Read more
Why iPhone 6 Tablet/Laptop Cannibalization Is...
247wallst.com blogger Douglas A. McIntyre noted last week that according to research posted on the Applovin blog site the iPhone 6 is outselling the iPhone 6 Plus by a wide margin . Hardly a surprise... Read more
Worldwide Tablet Growth Expected to Slow to 7...
The global tablet market is expected to record massive deceleration in 2014 with year-over-year growth slowing to 7.2%, down from 52.5% in 2013, according to a new forecast from International Data... Read more
Touchscreen Glove Company Announces New Produ...
Surrey, United Kingdom based TouchAbility specializes in design and manufacture of a wide variety of products compatible with touchscreen devices including smartphones, tablets and computers. Their... Read more
OtterBox Alpha Glass Screen Protectors for iP...
To complement the bigger, sharper displays on the latest Apple devices, OtterBox has introduced Alpha Glass screen protectors to the iPhone 6 and iPhone 6 Plus. The fortified glass screen protectors... Read more
Early Black Friday Mac Pro sale, 6-Core 3.5GH...
 B&H Photo has the 6-Core 3.5GHz Mac Pro on sale today for $3499 including free shipping plus NY sales tax. Their price is $500 off MSRP, and it’s the lowest price available for this model from... Read more
Early Black Friday sale price: 15-inch 2.2GHz...
 B&H Photo has the 2014 15″ 2.2GHz Retina MacBook Pro on sale today for $1699.99. Shipping is free, and B&H charges NY sales tax only. Their price is $300 off MSRP, equalling Best Buy’s price... Read more

Jobs Board

*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
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* 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* 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
*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
All contents are Copyright 1984-2011 by Xplain Corporation. All rights reserved. Theme designed by Icreon.