TextSections.c

/*------------------------------------------------------------------------------
 *
 *  Apple Developer Technical Support
 *
 *  Text section handling routines
 *
 *  Program:    AEObject-Edition Sample
 *  File:       TextSections.c -    C Source
 *
 *  by:         C.K. Haun <TR>
 *
 *  Copyright © 1990-1992 Apple Computer, Inc.
 *  All rights reserved.
 *
 *------------------------------------------------------------------------------
 * This file contains the functions for manipulating and bookkeeping
 * for published and subscribed text sections in this sample.
 *----------------------------------------------------------------------------*/
 
#define __TEXTSECTIONS__
 
#pragma segment TextHandler
#include "Sampdefines.h"
 
 
static Boolean Touching(mySectionDataHandle checkSection, TEHandle teText);
extern void MyHiHook(void);
 
Rect nulRect={0,0,0,0};
/* the following few functions are for text, and will be expanded as this is fully implemented */
Boolean HasTESelection(windowCHandle inWind)
{
    TEHandle temp;
    if (inWind == nil)
        return(false);
    temp = (*inWind)->boxHandle;
    if (temp != nil)
        return((*temp)->selStart != (*temp)->selEnd);
    else
        return(false);
}
 
Boolean PasteText(void)
{
    windowCHandle tempWC;
    tempWC = (windowCHandle)GetWRefCon(FrontWindow() );
    if ((*tempWC)->boxHandle != nil && gClipHasContents == kClipHasText) {
        TEFromScrap();
        CheckTextSections(tempWC, kAdding);
        TEPaste((*tempWC)->boxHandle);
        return(true);
    }
    return(false);
}
 
Boolean CutText(void)
{
    windowCHandle tempWC;
    tempWC = (windowCHandle)GetWRefCon(FrontWindow() );
    if (HasTESelection(tempWC)) {
        CheckTextSections(tempWC, kRemoving);               /* adjusts the end points on text sections */
        TECut((*tempWC)->boxHandle);
 
        ZeroScrap();
        TEToScrap();
        return(true);
    }
    return(false);
}
 
Boolean CopyText(void)
{
    windowCHandle tempWC;
    tempWC = (windowCHandle)GetWRefCon(FrontWindow() );
    if (HasTESelection(tempWC)) {
        TECopy((*tempWC)->boxHandle);
        ZeroScrap();
        TEToScrap();
        return(true);
    }
    return(false);
}
 
Boolean ClearText(void)
{
    windowCHandle tempWC;
    tempWC = (windowCHandle)GetWRefCon(FrontWindow() );
    if (HasTESelection(tempWC)) {
        CheckTextSections(tempWC, kRemoving);
        TEDelete((*tempWC)->boxHandle);
 
        return(true);
    }
    return(false);
}
 
/* This checks all the publishers and subscribers in the current TE record, and sees */
/* if their data needs to be modified based on the TE action pending */
void CheckTextSections(windowCHandle inWindow, short Action)
{
    TEHandle theTEHandle = (*inWindow)->boxHandle;
    mySectionDataHandle startSection = (*inWindow)->textSections;
    Boolean testFlag = true;                                /* a gen purp flag please */
    Boolean whapBorder = false;
    mySectionDataHandle currentSection = startSection;
    short counter = 0;
    /*  see if the insertion point or selection range is after all our sections.  */
    /* if it is (a forlorn hope) we can exit without adjusting anything. */
    if (startSection == nil)
        return;                                             /* no sections, go away */
    do {
        if ((*currentSection)->endChar > (*theTEHandle)->selStart) {
            testFlag = false;
            counter++;                                      /* a minimal aid to exiting this stuff later */
        if((*currentSection)->bordered)whapBorder=true;
        }
        currentSection = (*currentSection)->nextSection;
    } while (currentSection);
    if (testFlag)
        return;                                             /* no section is affected by this action, go away */
    
    /* OK, someone (or two, or twenty) will be affected by this action */
    /* so we have to see who, and how much, and if it's like adding in the middle, */
    /* or like shifting start and end points down, or like whatever, y'know? */
    
    currentSection = (*inWindow)->textSections;
    switch (Action) {
        unsigned long actionLen;
        case kKeyStroke:
            /* so we bump the start and end positions of all the sections after this point by 1 */
            while (counter) {                               /* minimal aid in exiting this thing */
                
                if ((*theTEHandle)->selStart <= (*currentSection)->startChar) {
                    (*currentSection)->startChar++;
                    (*currentSection)->endChar++;
                    counter--;
                } else {
                    if ((*theTEHandle)->selStart > (*currentSection)->startChar &&
                        (*theTEHandle)->selStart < (*currentSection)->endChar) {
                        /* this should mean that the input char is between the start and the end */
                        /* so the publisher expands by 1 */
                        (*currentSection)->endChar++;
                        counter--;
                    }
                }
                currentSection = (*currentSection)->nextSection;
                if (currentSection == nil)
                    break;                                  /* hey, y'know? */
            }
            break;
        case kAdding:
            /* increase start and finished positions by the size of the clip being added */
            actionLen = TEGetScrapLength();
            /* so we bump the start and end positions of all the sections after this point by 1 */
            while (counter) {                               /* minimal aid in exiting this thing */
                if ((*currentSection)->startChar > (*theTEHandle)->selStart) {
                    (*currentSection)->startChar += actionLen;
                    (*currentSection)->endChar += actionLen;
                    counter--;
                } else {Boolean temp=false;
                    /* expand the published section */
                  temp=(*currentSection)->bordered;
                    (*currentSection)->endChar += actionLen;
                    
                }
                currentSection = (*currentSection)->nextSection;
                if (currentSection == nil)
                    break;                                  /* hey, y'know? */
            }
            break;
        case kRemoving:
            /* decrease start and finished positions by the size of the current selection */
            /* You have another worry in this area, of course.  If the user has highlighted */
            /* the WHOLE publisher or subscriber, that means that you will be deleting */
            /* it completely */
            actionLen = (*currentSection)->endChar - (*currentSection)->startChar;
            /* so we bump the start and end positions of all the sections after this point by 1 */
            while (counter) {                               /* minimal aid in exiting this thing */
                if ((*currentSection)->startChar > (*theTEHandle)->selStart) {
                    (*currentSection)->startChar -= actionLen;
                    (*currentSection)->endChar -= actionLen;
                    counter--;
                }
                currentSection = (*currentSection)->nextSection;
                if (currentSection == nil)
                    break;                              
            }
            break;
    }
    /* if anything changed and borders are on in any section, we need to */
    /* erase and redraw the borders */
    /* This will cause a lot of screen flicker, you can do it neater in your app */
    if(whapBorder)InvalRect(&(*theTEHandle)->viewRect);
}
/* KillTextBorders does about what you'd expect, it turns off all the  */
/* text borders */
void KillTextBorders(void)
{windowCHandle tempWC = (windowCHandle)GetWRefCon(FrontWindow() );
mySectionDataHandle startSection = (*tempWC)->textSections;
while(startSection){
(*startSection)->bordered = false;
startSection = (*startSection)->nextSection;
}
}
 
void ShowAllTextBorders(void)
{windowCHandle tempWC = (windowCHandle)GetWRefCon(FrontWindow() );
mySectionDataHandle startSection = (*tempWC)->textSections;
while(startSection){
(*startSection)->bordered = true;
startSection = (*startSection)->nextSection;
}
 
 
}
/* SkipOverSubscriber is used to move the insertion point around any subscriptions */
/* in response to a cursor or delete key action */
/* This is, of course, because you cannot edit the contents of a subscription */
Boolean SkipOverSubscriber(windowCHandle inWindow, unsigned short theKey)
{
    long oldStart, oldEnd;
    Boolean flagBack = false;
    short newInsertionPoint;                                /* where the insertion point will be moving to as */
    /*  a result of this key */
    /* first, as usually, see if this action impinges on the target */
    /* of course, this only counts for subscriptions */
    TEHandle theTEHandle = (*inWindow)->boxHandle;
    mySectionDataHandle startSection = (*inWindow)->textSections;
    if (!startSection)
        return(flagBack);                                   /* scat if no sections */
    do {
        /* first see if it's a publisher, if it is, skip it.  */
        if (!(*startSection)->publishing) {
            switch (theKey) {                               /* actions based on what direction this key is moving us */
                case kLeftArrow:
                    newInsertionPoint = ((*theTEHandle)->selStart) - 1;
                    if (newInsertionPoint <= (*startSection)->endChar && newInsertionPoint >= (*startSection)->startChar) {
                        flagBack = true;
                        TESetSelect(((*startSection)->startChar), ((*startSection)->startChar), theTEHandle);
                    }
                    break;
                    
                case kRightArrow:
                    newInsertionPoint = ((*theTEHandle)->selEnd) + 1;
                    if (newInsertionPoint >= (*startSection)->startChar && newInsertionPoint <= (*startSection)->endChar) {
                        flagBack = true;
                        TESetSelect(((*startSection)->endChar), ((*startSection)->endChar), theTEHandle);
                    }
                    break;
                case kUpArrow:
                case kDownArrow:
                    /* for up and down arrows, we actually have to do the TEKey thing here, then evaluate the */
                    /* result and see if it landed in a subscription.  This is of course because we're using */
                    /* TextEdit, if you're doing your own text processing you will know where any keystroke lands */
                    /* Of course, you gotta save the current position of the insertion point first */
                    oldStart = (*theTEHandle)->selStart;
                    oldEnd = (*theTEHandle)->selEnd;
                    TEKey(theKey, theTEHandle);
                    /* see where it ended up */
                    newInsertionPoint = (*theTEHandle)->selEnd;
                    if (newInsertionPoint >= (*startSection)->startChar && newInsertionPoint <= (*startSection)->endChar) {
                        flagBack = true;
                        /* now move before or after the section depending on the arrow */
                        if (theKey == kUpArrow)
                            TESetSelect(((*startSection)->startChar), ((*startSection)->startChar), theTEHandle);
                        else
                            TESetSelect(((*startSection)->endChar), ((*startSection)->endChar), theTEHandle);
                    } else {
                        TESetSelect(oldStart, oldEnd, theTEHandle);
                    }
                    break;
                    
            }
        }
        startSection = (*startSection)->nextSection;        /* go to next in list */
    } while (startSection);
    
    return(flagBack);
}
 
/* RePackText makes sure we have the proper text in our lil publisher before writing the */
/* thing out.  */
/* Actually, since this function exists, we don't really need to keep a copy of the text attached */
/* to the publisher.  */
void RePackText(mySectionDataHandle currentSection, TEHandle theTEHandle)
{
    short theLength;
    CharsHandle theRawText;
    Ptr src;
    theLength = (*currentSection)->endChar - (*currentSection)->startChar;
    SetHandleSize((*currentSection)->additionalData, theLength);
    HLock((*currentSection)->additionalData);
    theRawText = TEGetText(theTEHandle);
    HLock((Handle)theRawText);
    src = (Ptr)*theRawText;
    src = src + (*currentSection)->startChar;
    BlockMove((Ptr)((Ptr)*theRawText)+(*currentSection)->startChar, (Ptr)*((*currentSection)->additionalData), theLength);
    HUnlock((Handle)theRawText);
    HUnlock((*currentSection)->additionalData);
}
 
/* InTextBox tells us if the rectangle passed intersects the text rectangle */
/* in the current window.  We use this for; */
/* 1) Seeing if the mouse click was in the TERect, so we can call TEClick */
/* 2) See if one of our drawing commands will overdraw the text, which is BaD */
Boolean InTextBox(windowCHandle inName, Rect *where)
{
    Rect temp;
    if ((*inName)->boxHandle == nil) {
        return(false);
    } else {
        return(SectRect(where, &(*inName)->textBox, &temp));
    }
}
 
/* CheckSubscriberHit sees if the user person clicked inside a subscription.  If they did, then */
/* we'll either highlight the whole subscriber and <sometime> draw a border around it, or */
/* extend the selection range to include the whole subscription if they dragged into just part of it */
/* for bording, things get a little more complicated.  If this is a publisher, and it's already */
/* showing a border, we want to do a normal TEClick since the user can edit inside a publisher */
void CheckSectionHit()
{
    windowCHandle shortName = (windowCHandle)GetWRefCon(FrontWindow());
    TEHandle theTEHandle = (*shortName)->boxHandle;
    long mySelStart = (*theTEHandle)->selStart;             /* added these variables just to make the code a */
    long mySelEnd = (*theTEHandle)->selEnd;                 /* little easier to read */
    long newStart = mySelStart;                             /* if any adjustments need to be made */
    long newEnd = mySelEnd;
    Boolean startedBordered;
    mySectionDataHandle startSection = (*shortName)->textSections;
    mySectionDataHandle currentSection = startSection;
    /* and of course, the affected window is the front window, since they clicked there, y'know */
    /* what we do here is see if the current mouse click lands in a publisher or */
    /* subscriber.  If it's a publisher, check for a double click and highlite. */
    /* if the selection range covers ANY part of a subscriber, extend the selection to */
    /* encompass the whole subscriber.  Then border the subscriber also. */
    /* This means that we cannot exit after finding one pub or sub, we must */
    /* check all since the user could have selected all the text in the record */
    while (currentSection) {
        startedBordered = (*currentSection)->bordered;
        if (Touching(currentSection, theTEHandle)) {
            /* see if this is a subscriber.  If so, extend start and end positions to include it all */
            if (!(*currentSection)->publishing) {
                /* it is a subscriber */
                if ((*currentSection)->startChar < mySelStart)
                    newStart = (*currentSection)->startChar;
                if ((*currentSection)->endChar > mySelEnd)
                    newEnd = (*currentSection)->endChar;
        TESetSelect(newStart, newEnd, theTEHandle);        
                
            } else {
            /* for a publisher, highlith the whole thing if it's the first click.  If it's the */
            /* second (or subsequent, o' course) they want to edit, so let them */
     if (startedBordered)
        TESetSelect(newStart, newEnd, theTEHandle);     
    else
        TESetSelect((*currentSection)->startChar, (*currentSection)->endChar, theTEHandle);
            }
        } else {
        /* if this section was not hit, deborder it */
        if(!gShowingAll && (*currentSection)->bordered){(*currentSection)->bordered=false;
            InvalRect(&(*theTEHandle)->viewRect);
        }}
        currentSection = (*currentSection)->nextSection;
    }
    
   
    
}
 
/*  Touching was created just so I wouldn't have to have a huge if statement in */
/* CheckSectionHit */
/* All it does is see if the current selection range of the TEHandle touches or */
/* fully encloses any text sections */
static Boolean Touching(mySectionDataHandle checkSection, TEHandle teText)
{
    Boolean ouch = false;                                   /* will turn true if a touch actually happened */
    
    long tStart = (*teText)->selStart;
    long tEnd = (*teText)->selEnd;
    short ourStart = (*checkSection)->startChar;
    short ourEnd = (*checkSection)->endChar;
    /* first see if the start hits inside the section */
    if (tStart > ourStart && tStart < ourEnd)
        ouch = true;
    else if (tStart < ourStart && tEnd > ourStart)
        ouch = true;                                        /* now see if we got completely enclosed */
    else if (tEnd > ourStart && tEnd < ourEnd)
        ouch = true;
    /* and finally see if we got touched by the end of the thing */
    if (ouch){
        BorderTextSection(checkSection);
        /* put this section in our gloabl section handle holder so we can do options on it */
        gShowingSecHandle=(*checkSection)->theSection;
        if((*checkSection)->publishing) {
        gShowPub = true;
        gShowPubRect=nulRect;
        } else {
         gShowSub = true;
        gShowSubRect=nulRect;
        }}
    return(ouch);
}
 
/* ExcludeSubscriber removes subscriptions from the range of selected text, so when a keystroke */
/* replaces a selection, the subscriber does not also get replaced. */
void ExcludeSubscriber(windowCHandle tempCH)
{
#pragma unused (tempCH)    
}
 
/* this borders the selected section.  It's a pain.  */
/* BUT, it's a pain because I'm using TextEdit.  If you're writing a word processor */
/* or another application which does it's own text processing, this will be a lot easier */
/* ¥¥¥¥ NOTE:  this doesn't look very good when a subscriber is embedded in a publisher, since */
/* the lines of course overlap.  I don't know how to deal with this yet. */
void BorderTextSection(mySectionDataHandle theText)
{
    TEHandle theTEHandle;
    WindowPtr theWindow;
    windowCHandle shortName;
    PolyHandle borderPoly;
    long origIStart;
    long origIEnd;
    long newStart;
    long newEnd;
    Point startPoint;
    Point endPoint;
    RgnHandle oldClip = NewRgn();
    WindowPtr oldPort;
    GetPort(&oldPort);
    GetClip(oldClip);
    
    theWindow = FindSection((*theText)->theSection);
    SetPort(theWindow);
    shortName = (windowCHandle)GetWRefCon(theWindow);
    HLock((Handle)shortName);
    theTEHandle = (*shortName)->boxHandle;
    
    origIStart = (*theTEHandle)->selStart;
    origIEnd = (*theTEHandle)->selEnd;
    TESetSelect((*theText)->startChar, (*theText)->endChar, theTEHandle);
    newStart = (*theTEHandle)->selStart;
    newEnd = (*theTEHandle)->selEnd;
    ClipRect(&(*theTEHandle)->viewRect);
    /* so our poly doesn't exceed the bounds of our rect */
    /* now what we do is create a Polygon that's the shape of the text section */
    startPoint = TEGetPoint((short)newStart, theTEHandle);
    endPoint = TEGetPoint((short)newEnd, theTEHandle);
    /* ¥¥¥¥ NOTE:  This is a really simplistic check, since this is a simple sample.  */
    /* In a more complex text document you'd be worrying about text scrolled out of the view rect, */
    /* weird regions, and other things I'm not going to deal with here.  */
    /* see if the verts are the same.  If they are, then all we need is a simple rect */
    
    if (startPoint.v == endPoint.v) {
        borderPoly = OpenPoly();
        startPoint.v -= (*theTEHandle)->lineHeight;         /* top o' the line to you! */
        
        MoveTo(startPoint.h, startPoint.v);
        LineTo(endPoint.h, startPoint.v);
        LineTo(endPoint.h, endPoint.v);
        LineTo(startPoint.h, endPoint.v);
        LineTo(startPoint.h, startPoint.v);
        ClosePoly();
        PenSize(3, 3);
        if ((*theText)->publishing)
            PenPat(&qd.gray);
        else
            PenPat(&qd.dkGray);
        FramePoly(borderPoly);
        PenPat(&qd.black);
        PenSize(1, 1);
        
    } else {
        short endTop = endPoint.v -(*theTEHandle)->lineHeight;
        /* need to build a slightly more complex poly */
        borderPoly = OpenPoly();
        startPoint.v -= (*theTEHandle)->lineHeight;         /* top o' the line to you! */
        
        MoveTo(startPoint.h, startPoint.v);
        LineTo((*theTEHandle)->destRect.right, startPoint.v);
        LineTo((*theTEHandle)->destRect.right, endTop);
        LineTo(endPoint.h, endTop);                         /* may not be needed, but doesn't hurt anything */
        LineTo(endPoint.h, endPoint.v);
        LineTo((*theTEHandle)->destRect.left, endPoint.v);
        LineTo((*theTEHandle)->destRect.left, (startPoint.v + ((*theTEHandle)->lineHeight)));
        LineTo(startPoint.h, (startPoint.v + ((*theTEHandle)->lineHeight)));
        LineTo(startPoint.h, startPoint.v);
        ClosePoly();
        PenSize(3, 3);
        if ((*theText)->publishing)
            PenPat(&qd.gray);
        else
            PenPat(&qd.dkGray);
        FramePoly(borderPoly);
        PenPat(&qd.black);
        PenSize(1, 1);
        
    }
    if((*theText)->bordered)
    /* if it started bordered, and it's a publisher, then when set the selection range to */
    /* what it was when we entered */
        TESetSelect(origIStart,origIEnd, theTEHandle);  
     else 
        (*theText)->bordered = true;    /* log it bordered */
    KillPoly(borderPoly);
    SetClip(oldClip);
    DisposeRgn(oldClip);
    SetPort(oldPort);
    HUnlock((Handle)shortName);
}
 
 
#undef __TEXTSECTIONS__