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);
}

 

Community Search:
MacTech Search:

Software Updates via MacUpdate

Latest Forum Discussions

See All

Aether Gazer unveils Chapter 16 of its m...
After a bit of maintenance, Aether Gazer has released Chapter 16 of its main storyline, titled Night Parade of the Beasts. This big update brings a new character, a special outfit, some special limited-time events, and, of course, an engaging... | Read more »
Challenge those pesky wyverns to a dance...
After recently having you do battle against your foes by wildly flailing Hello Kitty and friends at them, GungHo Online has whipped out another surprising collaboration for Puzzle & Dragons. It is now time to beat your opponents by cha-cha... | Read more »
Pack a magnifying glass and practice you...
Somehow it has already been a year since Torchlight: Infinite launched, and XD Games is celebrating by blending in what sounds like a truly fantastic new update. Fans of Cthulhu rejoice, as Whispering Mist brings some horror elements, and tests... | Read more »
Summon your guild and prepare for war in...
Netmarble is making some pretty big moves with their latest update for Seven Knights Idle Adventure, with a bunch of interesting additions. Two new heroes enter the battle, there are events and bosses abound, and perhaps most interesting, a huge... | Read more »
Make the passage of time your plaything...
While some of us are still waiting for a chance to get our hands on Ash Prime - yes, don’t remind me I could currently buy him this month I’m barely hanging on - Digital Extremes has announced its next anticipated Prime Form for Warframe. Starting... | Read more »
If you can find it and fit through the d...
The holy trinity of amazing company names have come together, to release their equally amazing and adorable mobile game, Hamster Inn. Published by HyperBeard Games, and co-developed by Mum Not Proud and Little Sasquatch Studios, it's time to... | Read more »
Amikin Survival opens for pre-orders on...
Join me on the wonderful trip down the inspiration rabbit hole; much as Palworld seemingly “borrowed” many aspects from the hit Pokemon franchise, it is time for the heavily armed animal survival to also spawn some illegitimate children as Helio... | Read more »
PUBG Mobile teams up with global phenome...
Since launching in 2019, SpyxFamily has exploded to damn near catastrophic popularity, so it was only a matter of time before a mobile game snapped up a collaboration. Enter PUBG Mobile. Until May 12th, players will be able to collect a host of... | Read more »
Embark into the frozen tundra of certain...
Chucklefish, developers of hit action-adventure sandbox game Starbound and owner of one of the cutest logos in gaming, has released their roguelike deck-builder Wildfrost. Created alongside developers Gaziter and Deadpan Games, Wildfrost will... | Read more »
MoreFun Studios has announced Season 4,...
Tension has escalated in the ever-volatile world of Arena Breakout, as your old pal Randall Fisher and bosses Fred and Perrero continue to lob insults and explosives at each other, bringing us to a new phase of warfare. Season 4, Into The Fog of... | Read more »

Price Scanner via MacPrices.net

Free iPhone 15 plus Unlimited service for $60...
Boost Infinite, part of MVNO Boost Mobile using AT&T and T-Mobile’s networks, is offering a free 128GB iPhone 15 for $60 per month including their Unlimited service plan (30GB of premium data).... Read more
$300 off any new iPhone with service at Red P...
Red Pocket Mobile has new Apple iPhones on sale for $300 off MSRP when you switch and open up a new line of service. Red Pocket Mobile is a nationwide MVNO using all the major wireless carrier... Read more
Clearance 13-inch M1 MacBook Airs available a...
Apple has clearance 13″ M1 MacBook Airs, Certified Refurbished, available for $759 for 8-Core CPU/7-Core GPU/256GB models and $929 for 8-Core CPU/8-Core GPU/512GB models. Apple’s one-year warranty is... Read more
Updated Apple MacBook Price Trackers
Our Apple award-winning MacBook Price Trackers are continually updated with the latest information on prices, bundles, and availability for 16″ and 14″ MacBook Pros along with 13″ and 15″ MacBook... Read more
Every model of Apple’s 13-inch M3 MacBook Air...
Best Buy has Apple 13″ MacBook Airs with M3 CPUs in stock and on sale today for $100 off MSRP. Prices start at $999. Their prices are the lowest currently available for new 13″ M3 MacBook Airs among... Read more
Sunday Sale: Apple iPad Magic Keyboards for 1...
Walmart has Apple Magic Keyboards for 12.9″ iPad Pros, in Black, on sale for $150 off MSRP on their online store. Sale price for online orders only, in-store price may vary. Order online and choose... Read more
Apple Watch Ultra 2 now available at Apple fo...
Apple has, for the first time, begun offering Certified Refurbished Apple Watch Ultra 2 models in their online store for $679, or $120 off MSRP. Each Watch includes Apple’s standard one-year warranty... Read more
AT&T has the iPhone 14 on sale for only $...
AT&T has the 128GB Apple iPhone 14 available for only $5.99 per month for new and existing customers when you activate unlimited service and use AT&T’s 36 month installment plan. The fine... Read more
Amazon is offering a $100 discount on every M...
Amazon is offering a $100 instant discount on each configuration of Apple’s new 13″ M3 MacBook Air, in Midnight, this weekend. These are the lowest prices currently available for new 13″ M3 MacBook... Read more
You can save $300-$480 on a 14-inch M3 Pro/Ma...
Apple has 14″ M3 Pro and M3 Max MacBook Pros in stock today and available, Certified Refurbished, starting at $1699 and ranging up to $480 off MSRP. Each model features a new outer case, shipping is... Read more

Jobs Board

Omnichannel Associate - *Apple* Blossom Mal...
Omnichannel Associate - Apple Blossom Mall Location:Winchester, VA, United States (https://jobs.jcp.com/jobs/location/191170/winchester-va-united-states) - Apple Read more
Operations Associate - *Apple* Blossom Mall...
Operations Associate - Apple Blossom Mall Location:Winchester, VA, United States (https://jobs.jcp.com/jobs/location/191170/winchester-va-united-states) - Apple Read more
Cashier - *Apple* Blossom Mall - JCPenney (...
Cashier - Apple Blossom Mall Location:Winchester, VA, United States (https://jobs.jcp.com/jobs/location/191170/winchester-va-united-states) - Apple Blossom Mall Read more
IT Systems Engineer ( *Apple* Platforms) - S...
IT Systems Engineer ( Apple Platforms) at SpaceX Hawthorne, CA SpaceX was founded under the belief that a future where humanity is out exploring the stars is Read more
*Apple* Systems Administrator - JAMF - Activ...
…**Public Trust/Other Required:** None **Job Family:** Systems Administration **Skills:** Apple Platforms,Computer Servers,Jamf Pro **Experience:** 3 + years of Read more
All contents are Copyright 1984-2011 by Xplain Corporation. All rights reserved. Theme designed by Icreon.