TweetFollow Us on Twitter

MACINTOSH C CARBON

Demonstration Program Drag

Goto Contents

// *******************************************************************************************
// Drag.h                                                                   CARBON EVENT MODEL
// *******************************************************************************************
//
// This program demonstrates drag and drop utilising the Drag Manager.  Support for Undo and
// Redo of drag and drop within the source window is included.
//
// The bulk of the code in the source code file Drag.c is identical to the code in the file
// Text1.c in the demonstration program MonoTextEdit (Chapter 21).
//
// The program utilises the following resources:
//
// o  A 'plst' resource.
//
// o  An 'MBAR' resource, and 'MENU' resources for Apple, File, and Edit menus.
//
// o  A 'WIND' resource (purgeable) (initially visible).
//
// o  'CNTL' resources (purgeable) for the vertical scroll bars in the text editor window and
//    Help dialog, and for the pop-up menu in the Help Dialog.
//
// o  A 'STR#' resource  (purgeable) containing error text strings.
//
// o  A 'SIZE' resource with the acceptSuspendResumeEvents, canBackground, 
//    doesActivateOnFGSwitch, and isHighLevelEventAware flags set.
//
// *******************************************************************************************

// .................................................................................. includes

#include <Carbon.h>

// ................................................................................... defines

#define rMenubar          128
#define mAppleApplication 128
#define  iAbout           1
#define mFile             129
#define  iNew             1
#define  iOpen            2
#define  iClose           4
#define  iSaveAs          6
#define  iQuit            12
#define mEdit             130
#define  iUndo            1
#define  iCut             3
#define  iCopy            4
#define  iPaste           5
#define  iClear           6
#define  iSelectAll       7
#define rWindow           128
#define rVScrollbar       128
#define rErrorStrings     128
#define  eMenuBar         1
#define  eWindow          2
#define  eDocStructure    3
#define  eTextEdit        4
#define  eExceedChara     5
#define  eNoSpaceCut      6
#define  eNoSpacePaste    7
#define  eDragHandler     8
#define  eDrag            9
#define  eDragUndo        10
#define kMaxTELength      32767
#define kTab              0x09
#define kBackSpace        0x08
#define kForwardDelete    0x7F
#define kReturn           0x0D
#define kEscape           0x1B
#define kReturn           0x0D
#define kFileCreator      'KJbb'
#define topLeft(r)        (((Point *) &(r))[0])
#define botRight(r)       (((Point *) &(r))[1])

// .................................................................................. typedefs

typedef struct
{
  TEHandle   textEditStrucHdl;
  ControlRef vScrollbarRef;
  WindowRef  windowRef;
  Boolean    windowTouched;
  Handle     preDragText;
  SInt16     preDragSelStart;
  SInt16     preDragSelEnd;
  SInt16     postDropSelStart;
  SInt16     postDropSelEnd;
} docStructure, *docStructurePointer;

// ....................................................................... function prototypes

void            main                   (void);
void            doPreliminaries        (void);
OSStatus        appEventHandler        (EventHandlerCallRef,EventRef,void *);
OSStatus        windowEventHandler     (EventHandlerCallRef,EventRef,void *);
void            doIdle                 (void);
void            doKeyEvent             (SInt8);
void            scrollActionFunction   (ControlRef,SInt16);
void            doInContent            (Point,EventRecord *,Boolean);
void            doDrawContent          (WindowRef);
void            doActivateDeactivate   (WindowRef,Boolean);
void            doOSEvent              (EventRecord *);
WindowRef       doNewDocWindow         (void);
EventHandlerUPP doGetHandlerUPP        (void);
Boolean         customClickLoop        (void);
void            doSetScrollBarValue    (ControlRef,SInt16 *);
void            doAdjustMenus          (void);
void            doMenuChoice           (MenuID,MenuItemIndex);
void            doFileMenu             (MenuItemIndex);
void            doEditMenu             (MenuItemIndex);
SInt16          doGetSelectLength      (TEHandle);
void            doAdjustScrollbar      (WindowRef);
void            doAdjustCursor         (WindowRef);
void            doCloseWindow          (WindowRef);
void            doSaveAsFile           (TEHandle);
void            doOpenCommand          (void);
void            doOpenFile             (FSSpec);
void            doErrorAlert           (SInt16);
void            navEventFunction       (NavEventCallbackMessage,NavCBRecPtr,
                                        NavCallBackUserData);

OSErr           doStartDrag            (EventRecord *,RgnHandle,TEHandle);
OSErr           dragTrackingHandler    (DragTrackingMessage,WindowRef,void *,DragRef);
SInt16          doGetOffset            (Point,TEHandle);
SInt16          doIsOffsetAtLineStart  (SInt16,TEHandle);
void            doDrawCaret            (SInt16,TEHandle);
SInt16          doGetLine              (SInt16,TEHandle);

OSErr           dragReceiveHandler     (WindowRef,void *,DragRef);
Boolean         doIsWhiteSpaceAtOffset (SInt16,TEHandle);
Boolean         doIsWhiteSpace         (char);
char            doGetCharAtOffset      (SInt16,TEHandle);
SInt16          doInsertTextAtOffset   (SInt16,Ptr,SInt32,TEHandle);
Boolean         doSavePreInsertionText (docStructurePointer);
void            doUndoRedoDrag         (WindowRef);

// *******************************************************************************************
// Drag.c
// *******************************************************************************************

// .................................................................................. includes

#include "Drag.h"

// .......................................................................... global variables

ControlActionUPP       gScrollActionFunctionUPP;
TEClickLoopUPP         gCustomClickLoopUPP;
DragTrackingHandlerUPP gDragTrackingHandlerUPP;
DragReceiveHandlerUPP  gDragReceiveHandlerUPP;
Boolean                gRunningOnX             = false;
SInt16                 gNumberOfWindows        = 0;
SInt16                 gOldControlValue;
Boolean                gEnableDragUndoRedoItem = false;
Boolean                gUndoFlag;

// ************************************************************************************** main

void  main(void)
{
  MenuBarHandle menubarHdl;
  SInt32        response;
  MenuRef       menuRef;
  EventTypeSpec applicationEvents[] = { { kEventClassApplication, kEventAppActivated    },
                                        { kEventClassApplication, kEventAppDeactivated  },
                                        { kEventClassCommand,     kEventProcessCommand  },
                                        { kEventClassMenu,        kEventMenuEnableItems },
                                        { kEventClassMouse,       kEventMouseMoved      } };

  // ........................................................................ do preliminaries

  doPreliminaries();

  // ..................................................... create universal procedure pointers

  gScrollActionFunctionUPP = NewControlActionUPP((ControlActionProcPtr) scrollActionFunction);
  gCustomClickLoopUPP      = NewTEClickLoopUPP((TEClickLoopProcPtr) customClickLoop);

  gDragTrackingHandlerUPP  = NewDragTrackingHandlerUPP((DragTrackingHandlerProcPtr)
                                                       dragTrackingHandler);
  gDragReceiveHandlerUPP   = NewDragReceiveHandlerUPP((DragReceiveHandlerProcPtr)
                                                      dragReceiveHandler);

  // ............................................................... set up menu bar and menus

  menubarHdl = GetNewMBar(rMenubar);
  if(menubarHdl == NULL)
    doErrorAlert(eMenuBar);
  SetMenuBar(menubarHdl);
  DrawMenuBar();

  Gestalt(gestaltMenuMgrAttr,&response);
  if(response & gestaltMenuMgrAquaLayoutMask)
  {
    menuRef = GetMenuRef(mFile);
    if(menuRef != NULL)
    {
      DeleteMenuItem(menuRef,iQuit);
      DeleteMenuItem(menuRef,iQuit - 1);
    }

    gRunningOnX = true;
  }
  else
  {
    menuRef = GetMenuRef(mFile);
    if(menuRef != NULL)
      SetMenuItemCommandID(menuRef,iQuit,kHICommandQuit);
  }

  // ....................................................... install application event handler
  
  InstallApplicationEventHandler(NewEventHandlerUPP((EventHandlerProcPtr) appEventHandler),
                                 GetEventTypeCount(applicationEvents),applicationEvents,
                                 0,NULL);

  // ......................................................................... install a timer

  InstallEventLoopTimer(GetCurrentEventLoop(),0,TicksToEventTime(GetCaretTime()),
                        NewEventLoopTimerUPP((EventLoopTimerProcPtr) doIdle),NULL,
                        NULL);
  // ............................................................................. open window

  doNewDocWindow();

  // .............................................................. run application event loop

  RunApplicationEventLoop();
}

// *************************************************************************** doPreliminaries

void  doPreliminaries(void)
{
  MoreMasterPointers(192);
  InitCursor();
}

// *************************************************************************** appEventHandler

OSStatus  appEventHandler(EventHandlerCallRef eventHandlerCallRef,EventRef eventRef,
                          void * userData)
{
  OSStatus      result = eventNotHandledErr;
  UInt32        eventClass;
  UInt32        eventKind;
  HICommand     hiCommand;
  MenuID        menuID;
  MenuItemIndex menuItem;
  WindowClass   windowClass;

  eventClass = GetEventClass(eventRef);
  eventKind  = GetEventKind(eventRef);

  switch(eventClass)
  {
    case kEventClassApplication:
      if(eventKind == kEventAppActivated)
        SetThemeCursor(kThemeArrowCursor);
      break;

    case kEventClassCommand:
      if(eventKind == kEventProcessCommand)
      {
        GetEventParameter(eventRef,kEventParamDirectObject,typeHICommand,NULL,
                          sizeof(HICommand),NULL,&hiCommand);
        menuID = GetMenuID(hiCommand.menu.menuRef);
        menuItem = hiCommand.menu.menuItemIndex;
        if((hiCommand.commandID != kHICommandQuit) && 
           (menuID >= mAppleApplication && menuID <= mEdit))
        {
          doMenuChoice(menuID,menuItem);
          result = noErr;
        }
      }
      break;

    case kEventClassMenu:
      if(eventKind == kEventMenuEnableItems)
      {
        GetWindowClass(FrontWindow(),&windowClass);
        if(windowClass == kDocumentWindowClass)
          doAdjustMenus();
        result = noErr;
      }
      break;

    case kEventClassMouse:
      if(eventKind == kEventMouseMoved)
      {
        GetWindowClass(FrontWindow(),&windowClass);
        if(windowClass == kDocumentWindowClass)
          doAdjustCursor(FrontWindow());
        result = noErr;
      }
      break;
  }

  return result;
}

// ************************************************************************ windowEventHandler

OSStatus  windowEventHandler(EventHandlerCallRef eventHandlerCallRef,EventRef eventRef,
                             void* userData)
{
  OSStatus        result = eventNotHandledErr;
  UInt32          eventClass;
  UInt32          eventKind;
  WindowRef       windowRef;
  UInt32          modifiers; 
  Point           mouseLocation;
  Boolean         shiftKeyDown = false;
  EventRecord     eventRecord;
  ControlRef      controlRef;
  ControlPartCode controlPartCode;
  SInt8           charCode;

  eventClass = GetEventClass(eventRef);
  eventKind  = GetEventKind(eventRef);

  switch(eventClass)
  {
    case kEventClassWindow:                                               // event class window
      GetEventParameter(eventRef,kEventParamDirectObject,typeWindowRef,NULL,sizeof(windowRef),
                        NULL,&windowRef);
      switch(eventKind)
      {
        case kEventWindowDrawContent:
          doDrawContent(windowRef);
          result = noErr;
          break;

        case kEventWindowActivated:
          doActivateDeactivate(windowRef,true);
          result = noErr;
          break;

        case kEventWindowDeactivated:
          doActivateDeactivate(windowRef,false);
          result = noErr;
          break;

        case kEventWindowClickContentRgn:
          SetPortWindowPort(FrontWindow());
          GetMouse(&mouseLocation);
          GetEventParameter(eventRef,kEventParamKeyModifiers,typeUInt32,NULL,
                            sizeof(modifiers),NULL,&modifiers);
          if(modifiers & shiftKey)
            shiftKeyDown = true;
          ConvertEventRefToEventRecord(eventRef,&eventRecord);
          doInContent(mouseLocation,&eventRecord,shiftKeyDown);
          result = noErr;
          break;

        case kEventWindowClose:
          doCloseWindow(windowRef);
          result = noErr;
          break;
      }
      break;

    case kEventClassMouse:                                                // event class mouse
      switch(eventKind)
      {
        case kEventMouseDown:
          GetEventParameter(eventRef,kEventParamMouseLocation,typeQDPoint,NULL,
                            sizeof(mouseLocation),NULL,&mouseLocation);
          SetPortWindowPort(FrontWindow());
          GlobalToLocal(&mouseLocation);
          controlRef = FindControlUnderMouse(mouseLocation,FrontWindow(),&controlPartCode);
          if(controlRef)
          {
            gOldControlValue = GetControlValue(controlRef);
            TrackControl(controlRef,mouseLocation,gScrollActionFunctionUPP);
            result = noErr;
          }
        break;
      }
      break;

    case kEventClassKeyboard:                                           // event class keyboard
      switch(eventKind)
      {
        case kEventRawKeyDown:
        case kEventRawKeyRepeat: 
          GetEventParameter(eventRef,kEventParamKeyMacCharCodes,typeChar,NULL,
                            sizeof(charCode),NULL,&charCode);
          GetEventParameter(eventRef,kEventParamKeyModifiers,typeUInt32,NULL,
                            sizeof(modifiers),NULL,&modifiers);
          if((modifiers & cmdKey) == 0)
            doKeyEvent(charCode);
          result = noErr;
          break;
      }
      break;
  }

  return result;
}

// ************************************************************************************ doIdle

void  doIdle(void)
{
  WindowRef           windowRef;
  docStructurePointer docStrucPtr;

  windowRef = FrontWindow();
  if(GetWindowKind(windowRef) == kApplicationWindowKind)
  {
    docStrucPtr = (docStructurePointer) (GetWRefCon(windowRef));
    if(docStrucPtr != NULL)
      TEIdle(docStrucPtr->textEditStrucHdl);
  }
}

// ******************************************************************************** doKeyEvent

void  doKeyEvent(SInt8 charCode)
{
  WindowRef           windowRef;
  docStructurePointer docStrucPtr;
  TEHandle            textEditStrucHdl;
  SInt16              selectionLength;

  if(charCode <= kEscape && charCode != kBackSpace && charCode != kReturn)
    return;

  windowRef = FrontWindow();
  docStrucPtr = (docStructurePointer) (GetWRefCon(windowRef));
  textEditStrucHdl = docStrucPtr->textEditStrucHdl;

  gEnableDragUndoRedoItem = false;

  if(charCode == kTab)
  {
    // Do tab key handling here if required.
  }
  else if(charCode == kForwardDelete)
  {
    selectionLength = doGetSelectLength(textEditStrucHdl);
    if(selectionLength == 0)
      (*textEditStrucHdl)->selEnd += 1;
    TEDelete(textEditStrucHdl);
    doAdjustScrollbar(windowRef);
  }
  else
  {
    selectionLength = doGetSelectLength(textEditStrucHdl);
    if(((*textEditStrucHdl)->teLength - selectionLength + 1) < kMaxTELength)
    {
      TEKey(charCode,textEditStrucHdl);
      doAdjustScrollbar(windowRef);
    }
    else
      doErrorAlert(eExceedChara);
  }
}

// ********************************************************************** scrollActionFunction

void  scrollActionFunction(ControlRef controlRef,SInt16 partCode)
{
  WindowRef           windowRef;
  docStructurePointer docStrucPtr;
  TEHandle            textEditStrucHdl;
  SInt16              linesToScroll;
  SInt16              controlValue, controlMax;

  windowRef = GetControlOwner(controlRef);
  docStrucPtr = (docStructurePointer) (GetWRefCon(windowRef));;
  textEditStrucHdl = docStrucPtr->textEditStrucHdl;

  controlValue = GetControlValue(controlRef);
  controlMax = GetControlMaximum(controlRef);

  if(partCode)
  {
    if(partCode != kControlIndicatorPart)
    {
      switch(partCode)
      {
        case kControlUpButtonPart:
        case kControlDownButtonPart:
          linesToScroll = 1;
          break;
        
        case kControlPageUpPart:
        case kControlPageDownPart:
          linesToScroll = (((*textEditStrucHdl)->viewRect.bottom - 
                          (*textEditStrucHdl)->viewRect.top) /
                          (*textEditStrucHdl)->lineHeight) - 1;
          break;
      }

      if((partCode == kControlDownButtonPart) || (partCode == kControlPageDownPart))
        linesToScroll = -linesToScroll;

      linesToScroll = controlValue - linesToScroll;
      if(linesToScroll < 0)
        linesToScroll = 0;
      else if(linesToScroll > controlMax)
        linesToScroll = controlMax;

      SetControlValue(controlRef,linesToScroll);

      linesToScroll = controlValue - linesToScroll;
    }
    else
    {
      linesToScroll = gOldControlValue - controlValue;
      gOldControlValue = controlValue;
    }

    if(linesToScroll != 0)
      TEScroll(0,linesToScroll * (*textEditStrucHdl)->lineHeight,textEditStrucHdl);
  }
}

// ******************************************************************************* doInContent

void  doInContent(Point mouseLocation,EventRecord * eventStrucPtr,Boolean shiftKeyDown)
{
  WindowRef           windowRef;
  docStructurePointer docStrucPtr;
  TEHandle            textEditStrucHdl;
  RgnHandle           hiliteRgn;
  OSErr               osError;

  windowRef = FrontWindow();
  docStrucPtr = (docStructurePointer) GetWRefCon(windowRef);
  textEditStrucHdl = docStrucPtr->textEditStrucHdl;

  if(PtInRect(mouseLocation,&(*textEditStrucHdl)->viewRect))
  {
    hiliteRgn = NewRgn();
    
    TEGetHiliteRgn(hiliteRgn,textEditStrucHdl);

    if(!EmptyRgn(hiliteRgn) && PtInRgn(mouseLocation,hiliteRgn))
    {
      if(WaitMouseMoved(mouseLocation))
      {
        osError = doStartDrag(eventStrucPtr,hiliteRgn,textEditStrucHdl);
        if(osError != noErr)
          doErrorAlert(eDrag);
      }  
    }
    else
    {
      TEClick(mouseLocation,shiftKeyDown,textEditStrucHdl);
      gEnableDragUndoRedoItem = false;    
    } 

    DisposeRgn(hiliteRgn);
  }
}

// ***************************************************************************** doDrawContent

void  doDrawContent(WindowRef windowRef)
{
  docStructurePointer docStrucPtr;
  TEHandle            textEditStrucHdl;
  GrafPtr             oldPort;
  RgnHandle           visibleRegionHdl = NewRgn();
  Rect                portRect;

  docStrucPtr = (docStructurePointer) (GetWRefCon(windowRef));
  textEditStrucHdl = docStrucPtr->textEditStrucHdl;
  
  GetPort(&oldPort);
  SetPortWindowPort(windowRef);

  GetPortVisibleRegion(GetWindowPort(windowRef),visibleRegionHdl);
  EraseRgn(visibleRegionHdl);

  UpdateControls(windowRef,visibleRegionHdl);

  GetWindowPortBounds(windowRef,&portRect);
  TEUpdate(&(*textEditStrucHdl)->viewRect,textEditStrucHdl);

  DisposeRgn(visibleRegionHdl);
  SetPort(oldPort);
}

// *********************************************************************** doActivateDocWindow

void  doActivateDeactivate(WindowRef windowRef,Boolean becomingActive)
{
  docStructurePointer docStrucPtr;
  TEHandle            textEditStrucHdl;

  docStrucPtr = (docStructurePointer) (GetWRefCon(windowRef));
  textEditStrucHdl = docStrucPtr->textEditStrucHdl;

  if(becomingActive)
  {
    SetPortWindowPort(windowRef);

    (*textEditStrucHdl)->viewRect.bottom = ((((*textEditStrucHdl)->viewRect.bottom -
                                            (*textEditStrucHdl)->viewRect.top) / 
                                            (*textEditStrucHdl)->lineHeight) * 
                                            (*textEditStrucHdl)->lineHeight) + 
                                            (*textEditStrucHdl)->viewRect.top;
    (*textEditStrucHdl)->destRect.bottom = (*textEditStrucHdl)->viewRect.bottom;

    TEActivate(textEditStrucHdl);
    ActivateControl(docStrucPtr->vScrollbarRef);
    doAdjustScrollbar(windowRef);
    doAdjustCursor(windowRef);
  }
  else
  {
    TEDeactivate(textEditStrucHdl);
    DeactivateControl(docStrucPtr->vScrollbarRef);
  }
}

// **************************************************************************** doNewDocWindow

WindowRef  doNewDocWindow(void)
{
  WindowRef           windowRef;
  OSStatus            osError;
  Rect                contentRect = { 100,100,400,595 };
  WindowAttributes    attributes  = kWindowStandardHandlerAttribute |
                                    kWindowStandardDocumentAttributes;
  docStructurePointer docStrucPtr;
  Rect                portRect, destAndViewRect;
  EventTypeSpec   windowEvents[] = { { kEventClassWindow,   kEventWindowDrawContent     },
                                     { kEventClassWindow,   kEventWindowActivated       },
                                     { kEventClassWindow,   kEventWindowDeactivated     },
                                     { kEventClassWindow,   kEventWindowClickContentRgn },
                                     { kEventClassWindow,   kEventWindowClose           },
                                     { kEventClassMouse,    kEventMouseDown             },
                                     { kEventClassKeyboard, kEventRawKeyDown            },
                                     { kEventClassKeyboard, kEventRawKeyRepeat          } };

  osError = CreateNewWindow(kDocumentWindowClass,attributes,&contentRect,&windowRef);
  if(osError != noErr)
  {
    doErrorAlert(eWindow);
    return NULL;
  }

  ChangeWindowAttributes(windowRef,0,kWindowResizableAttribute);
  RepositionWindow(windowRef,NULL,kWindowCascadeOnMainScreen);
  SetWTitle(windowRef,"\puntitled");
  SetPortWindowPort(windowRef);
  TextSize(10);
  if(!(docStrucPtr = (docStructurePointer) NewPtr(sizeof(docStructure))))
  {
    doErrorAlert(eDocStructure);
    return NULL;
  }

  SetWRefCon(windowRef,(SInt32) docStrucPtr);
  SetWindowProxyCreatorAndType(windowRef,0,'TEXT',kUserDomain);

  InstallWindowEventHandler(windowRef,doGetHandlerUPP(),GetEventTypeCount(windowEvents),
                            windowEvents,0,NULL);

  gNumberOfWindows ++;

  docStrucPtr->windowRef     = windowRef;
  docStrucPtr->windowTouched = false;
  docStrucPtr->preDragText   = NULL;
  docStrucPtr->vScrollbarRef = GetNewControl(rVScrollbar,windowRef);
  
  GetWindowPortBounds(windowRef,&portRect);
  destAndViewRect = portRect;
  destAndViewRect.right -= 15;
  InsetRect(&destAndViewRect,2,2);

  if(!(docStrucPtr->textEditStrucHdl = TENew(&destAndViewRect,&destAndViewRect)))
  {
    DisposeWindow(windowRef);
    gNumberOfWindows --;
    DisposePtr((Ptr) docStrucPtr);
    doErrorAlert(eTextEdit);
    return NULL;
  }

  TESetClickLoop(gCustomClickLoopUPP,docStrucPtr->textEditStrucHdl);
  TEAutoView(true,docStrucPtr->textEditStrucHdl);
  TEFeatureFlag(teFOutlineHilite,teBitSet,docStrucPtr->textEditStrucHdl);

  if(osError = InstallTrackingHandler(gDragTrackingHandlerUPP,windowRef,docStrucPtr))
  {
    DisposeWindow(windowRef);
    gNumberOfWindows --;
    DisposePtr((Ptr) docStrucPtr);
    doErrorAlert(eDragHandler);
    return NULL;
  }

  if(osError = InstallReceiveHandler(gDragReceiveHandlerUPP,windowRef,docStrucPtr))
  {
    RemoveTrackingHandler(gDragTrackingHandlerUPP,windowRef);
    DisposeWindow(windowRef);
    gNumberOfWindows --;
    DisposePtr((Ptr) docStrucPtr);
    doErrorAlert(eDragHandler);
    return NULL;
  }


  ShowWindow(windowRef);

  return windowRef;
}

// *************************************************************************** doGetHandlerUPP

EventHandlerUPP  doGetHandlerUPP(void)
{
  static EventHandlerUPP windowEventHandlerUPP;

  if(windowEventHandlerUPP == NULL)
    windowEventHandlerUPP = NewEventHandlerUPP((EventHandlerProcPtr) windowEventHandler);

  return windowEventHandlerUPP;
}

// *************************************************************************** customClickLoop

Boolean  customClickLoop(void)
{
  WindowRef           windowRef;
  docStructurePointer docStrucPtr;
  TEHandle            textEditStrucHdl;
  GrafPtr             oldPort;
  RgnHandle           oldClip;
  Rect                tempRect, portRect;
  Point               mouseXY;
  SInt16              linesToScroll = 0;

  windowRef = FrontWindow();
  docStrucPtr = (docStructurePointer) (GetWRefCon(windowRef));
  textEditStrucHdl = docStrucPtr->textEditStrucHdl;

  GetPort(&oldPort);
  SetPortWindowPort(windowRef);
  oldClip = NewRgn();
  GetClip(oldClip);
  SetRect(&tempRect,-32767,-32767,32767,32767);
  ClipRect(&tempRect);

  GetMouse(&mouseXY);
  GetWindowPortBounds(windowRef,&portRect);

  if(mouseXY.v < portRect.top)
  {
    linesToScroll = 1;
    doSetScrollBarValue(docStrucPtr->vScrollbarRef,&linesToScroll);
    if(linesToScroll != 0)
      TEScroll(0,linesToScroll * ((*textEditStrucHdl)->lineHeight),textEditStrucHdl);
  }
  else if(mouseXY.v > portRect.bottom)
  {
    linesToScroll = -1;
    doSetScrollBarValue(docStrucPtr->vScrollbarRef,&linesToScroll);
    if(linesToScroll != 0)
      TEScroll(0,linesToScroll * ((*textEditStrucHdl)->lineHeight),textEditStrucHdl);
  }

  SetClip(oldClip);
  DisposeRgn(oldClip);
  SetPort(oldPort);

  return true;
}

// *********************************************************************** doSetScrollBarValue

void  doSetScrollBarValue(ControlRef controlRef,SInt16 *linesToScroll)
{
  SInt16 controlValue, controlMax;

  controlValue = GetControlValue(controlRef);
  controlMax = GetControlMaximum(controlRef);

  *linesToScroll = controlValue - *linesToScroll;
  if(*linesToScroll < 0)
    *linesToScroll = 0;
  else if(*linesToScroll > controlMax)
    *linesToScroll = controlMax;

  SetControlValue(controlRef,*linesToScroll);
  *linesToScroll = controlValue - *linesToScroll;
}

// ***************************************************************************** doAdjustMenus

void  doAdjustMenus(void)
{
  MenuRef             fileMenuRef, editMenuRef;
  WindowRef           windowRef;
  docStructurePointer docStrucPtr;
  TEHandle            textEditStrucHdl;
  ScrapRef            scrapRef;
  OSStatus            osError;
  ScrapFlavorFlags    scrapFlavorFlags;

  fileMenuRef = GetMenuRef(mFile);
  editMenuRef = GetMenuRef(mEdit);

  if(gNumberOfWindows > 0)
  {
    windowRef = FrontWindow();
    docStrucPtr = (docStructurePointer) (GetWRefCon(windowRef));;
    textEditStrucHdl = docStrucPtr->textEditStrucHdl;

    EnableMenuItem(fileMenuRef,iClose);

    if(gEnableDragUndoRedoItem)
    {
      EnableMenuItem(editMenuRef,iUndo);
      if(gUndoFlag)
        SetMenuItemText(editMenuRef,iUndo,"\pUndo Drag & Drop");
      else
        SetMenuItemText(editMenuRef,iUndo,"\pRedo Drag & Drop");
    }
    else
    {
      DisableMenuItem(editMenuRef,iUndo);
      SetMenuItemText(editMenuRef,iUndo,"\pRedo Drag & Drop");
    }
      
    if((*textEditStrucHdl)->selStart < (*textEditStrucHdl)->selEnd)
    {
      EnableMenuItem(editMenuRef,iCut);
      EnableMenuItem(editMenuRef,iCopy);
      EnableMenuItem(editMenuRef,iClear);
    }
    else
    {
      DisableMenuItem(editMenuRef,iCut);
      DisableMenuItem(editMenuRef,iCopy);
      DisableMenuItem(editMenuRef,iClear);
    }

    GetCurrentScrap(&scrapRef);

    osError = GetScrapFlavorFlags(scrapRef,kScrapFlavorTypeText,&scrapFlavorFlags);
    if(osError == noErr)
      EnableMenuItem(editMenuRef,iPaste);
    else
      DisableMenuItem(editMenuRef,iPaste);

    if((*textEditStrucHdl)->teLength > 0)
    {
      EnableMenuItem(fileMenuRef,iSaveAs);
      EnableMenuItem(editMenuRef,iSelectAll);
    }
    else
    {
      DisableMenuItem(fileMenuRef,iSaveAs);
      DisableMenuItem(editMenuRef,iSelectAll);
    }
  }
  else
  {
    DisableMenuItem(fileMenuRef,iClose);
    DisableMenuItem(fileMenuRef,iSaveAs);
    DisableMenuItem(editMenuRef,iClear);
    DisableMenuItem(editMenuRef,iSelectAll);
  }

  DrawMenuBar();
}

// ****************************************************************************** doMenuChoice

void  doMenuChoice(MenuID menuID,MenuItemIndex menuItem)
{
  if(menuID == 0)
    return;

  switch(menuID)
  {
    case mAppleApplication:
      if(menuItem == iAbout)
        SysBeep(10);
      break;

    case mFile:
      doFileMenu(menuItem);
      break;

    case mEdit:
      doEditMenu(menuItem);
      break;
  }
}

// ******************************************************************************** doFileMenu

void  doFileMenu(MenuItemIndex menuItem)
{
  docStructurePointer docStrucPtr;
  TEHandle            textEditStrucHdl;

  switch(menuItem)
  {
    case iNew:
      doNewDocWindow();
      break;

    case iOpen:
      doOpenCommand();
      doAdjustScrollbar(FrontWindow());
      break;

    case iClose:
      doCloseWindow(FrontWindow());
      break;

    case iSaveAs:
      docStrucPtr = (docStructurePointer) (GetWRefCon(FrontWindow()));
      textEditStrucHdl = docStrucPtr->textEditStrucHdl;
      doSaveAsFile(textEditStrucHdl);
      break;
  }
}

// ******************************************************************************** doEditMenu

void  doEditMenu(MenuItemIndex menuItem)
{
  WindowRef           windowRef;
  docStructurePointer docStrucPtr;
  TEHandle            textEditStrucHdl;
  SInt32              totalSize, contigSize, newSize;
  SInt16              selectionLength;
  ScrapRef            scrapRef;
  Size                sizeOfTextData;

  windowRef = FrontWindow();
  docStrucPtr = (docStructurePointer) (GetWRefCon(windowRef));
  textEditStrucHdl = docStrucPtr->textEditStrucHdl;

  switch(menuItem)
  {
    case iUndo:
      doUndoRedoDrag(windowRef);
      break;

    case iCut:
      if(ClearCurrentScrap() == noErr)
      {
        PurgeSpace(&totalSize,&contigSize);
        selectionLength = doGetSelectLength(textEditStrucHdl);
        if(selectionLength > contigSize)
          doErrorAlert(eNoSpaceCut);
        else
        {
          TECut(textEditStrucHdl);
          doAdjustScrollbar(windowRef);
          if(TEToScrap() != noErr)
            ClearCurrentScrap();
        }
      }
      break;

    case iCopy:
      if(ClearCurrentScrap() == noErr)
      {
        TECopy(textEditStrucHdl);
        if(TEToScrap() != noErr)
          ClearCurrentScrap();
      }
      break;

    case iPaste:
      GetCurrentScrap(&scrapRef);;
      GetScrapFlavorSize(scrapRef,kScrapFlavorTypeText,&sizeOfTextData);
      newSize = (*textEditStrucHdl)->teLength + sizeOfTextData;
      if(newSize > kMaxTELength)
        doErrorAlert(eNoSpacePaste);
      else
      {
        if(TEFromScrap() == noErr)
        {
          TEPaste(textEditStrucHdl);
          doAdjustScrollbar(windowRef);
        }
      }
      break;

    case iClear:
      TEDelete(textEditStrucHdl);
      doAdjustScrollbar(windowRef);
      break;

    case iSelectAll:
      TESetSelect(0,(*textEditStrucHdl)->teLength,textEditStrucHdl);
      break;
  }
}

// ************************************************************************* doGetSelectLength

SInt16  doGetSelectLength(TEHandle textEditStrucHdl)
{
  SInt16 selectionLength;

  selectionLength = (*textEditStrucHdl)->selEnd - (*textEditStrucHdl)->selStart;
  return selectionLength;
}

// ************************************************************************* doAdjustScrollbar

void  doAdjustScrollbar(WindowRef windowRef)
{
  docStructurePointer docStrucPtr;
  TEHandle            textEditStrucHdl;
  SInt16              numberOfLines, controlMax, controlValue;

  docStrucPtr = (docStructurePointer) (GetWRefCon(windowRef));;
  textEditStrucHdl = docStrucPtr->textEditStrucHdl;

  numberOfLines = (*textEditStrucHdl)->nLines;
  if(*(*(*textEditStrucHdl)->hText + (*textEditStrucHdl)->teLength - 1) == kReturn)
    numberOfLines += 1;

  controlMax = numberOfLines - (((*textEditStrucHdl)->viewRect.bottom - 
               (*textEditStrucHdl)->viewRect.top) /
               (*textEditStrucHdl)->lineHeight);
  if(controlMax < 0)
    controlMax = 0;
  SetControlMaximum(docStrucPtr->vScrollbarRef,controlMax);

  controlValue = ((*textEditStrucHdl)->viewRect.top - (*textEditStrucHdl)->destRect.top) / 
                  (*textEditStrucHdl)->lineHeight;
  if(controlValue < 0)
    controlValue = 0;
  else if(controlValue > controlMax)
    controlValue = controlMax;

  SetControlValue(docStrucPtr->vScrollbarRef,controlValue);

  SetControlViewSize(docStrucPtr->vScrollbarRef,(*textEditStrucHdl)->viewRect.bottom - 
                     (*textEditStrucHdl)->viewRect.top);
  
  TEScroll(0,((*textEditStrucHdl)->viewRect.top - (*textEditStrucHdl)->destRect.top) - 
               (GetControlValue(docStrucPtr->vScrollbarRef) *
              (*textEditStrucHdl)->lineHeight),textEditStrucHdl);
}

// **************************************************************************** doAdjustCursor

void  doAdjustCursor(WindowRef windowRef)
{
  GrafPtr             oldPort;
  RgnHandle           arrowRegion, iBeamRegion, hiliteRgn;
  Rect                portRect, cursorRect;
  docStructurePointer docStrucPtr;
  Point               offset, mouseXY;

  GetPort(&oldPort);
  SetPortWindowPort(windowRef);

  arrowRegion = NewRgn();
  iBeamRegion = NewRgn();
  hiliteRgn    = NewRgn();  
  SetRectRgn(arrowRegion,-32768,-32768,32766,32766);

  GetWindowPortBounds(windowRef,&portRect);
  cursorRect = portRect;
  cursorRect.right  -= 15;
  LocalToGlobal(&topLeft(cursorRect));
  LocalToGlobal(&botRight(cursorRect));  

  RectRgn(iBeamRegion,&cursorRect);
  DiffRgn(arrowRegion,iBeamRegion,arrowRegion);

  docStrucPtr = (docStructurePointer) (GetWRefCon(windowRef));
  TEGetHiliteRgn(hiliteRgn,docStrucPtr->textEditStrucHdl);
  LocalToGlobal(&topLeft(portRect));  
  offset = topLeft(portRect);
  OffsetRgn(hiliteRgn,offset.h,offset.v);
  DiffRgn(iBeamRegion,hiliteRgn,iBeamRegion);
  
  GetGlobalMouse(&mouseXY);

  if(PtInRgn(mouseXY,iBeamRegion))
    SetThemeCursor(kThemeIBeamCursor);
  else if(PtInRgn(mouseXY,hiliteRgn))
    SetThemeCursor(kThemeArrowCursor);
  else
    SetThemeCursor(kThemeArrowCursor);

  DisposeRgn(arrowRegion);
  DisposeRgn(iBeamRegion);
  DisposeRgn(hiliteRgn);

  SetPort(oldPort);
}

// ***************************************************************************** doCloseWindow

void  doCloseWindow(WindowRef windowRef)
{
  docStructurePointer docStrucPtr;

  docStrucPtr = (docStructurePointer) (GetWRefCon(windowRef));;

  DisposeControl(docStrucPtr->vScrollbarRef);
  TEDispose(docStrucPtr->textEditStrucHdl);
  DisposePtr((Ptr) docStrucPtr);

  if(docStrucPtr->preDragText == NULL)
    DisposeHandle(docStrucPtr->preDragText);

  RemoveTrackingHandler(gDragTrackingHandlerUPP,windowRef);
  RemoveReceiveHandler(gDragReceiveHandlerUPP,windowRef);
  
  DisposeWindow(windowRef);

  gNumberOfWindows --;
}

// ****************************************************************************** doSaveAsFile

void  doSaveAsFile(TEHandle textEditStrucHdl)
{
  OSErr            osError = noErr;
  NavDialogOptions dialogOptions;
  NavEventUPP      navEventFunctionUPP;
  WindowRef        windowRef;
  OSType           fileType;
  NavReplyRecord   navReplyStruc;
  AEKeyword        theKeyword;
  DescType         actualType;
  FSSpec           fileSpec;  
  SInt16           fileRefNum;
  Size             actualSize;
  SInt32           dataLength;
  Handle           editTextHdl;

  osError = NavGetDefaultDialogOptions(&dialogOptions);

  if(osError == noErr)
  {
    windowRef = FrontWindow();

    fileType = 'TEXT';

    navEventFunctionUPP = NewNavEventUPP((NavEventProcPtr) navEventFunction);
    osError = NavPutFile(NULL,&navReplyStruc,&dialogOptions,navEventFunctionUPP,fileType,
                         kFileCreator,NULL);
    DisposeNavEventUPP(navEventFunctionUPP);

    if(navReplyStruc.validRecord && osError == noErr)
    {
      if((osError = AEGetNthPtr(&(navReplyStruc.selection),1,typeFSS,&theKeyword,
                                &actualType,&fileSpec,sizeof(fileSpec),&actualSize)) == noErr)

      {
        if(!navReplyStruc.replacing)
        {
          osError = FSpCreate(&fileSpec,kFileCreator,fileType,navReplyStruc.keyScript);
          if(osError != noErr)
          {
            NavDisposeReply(&navReplyStruc);
          }
        }
        
        if(osError == noErr)
          osError = FSpOpenDF(&fileSpec,fsRdWrPerm,&fileRefNum);

        if(osError == noErr)
        {
          SetWTitle(windowRef,fileSpec.name);
          dataLength = (*textEditStrucHdl)->teLength;
          editTextHdl = (*textEditStrucHdl)->hText;
          FSWrite(fileRefNum,&dataLength,*editTextHdl);
        }

        NavCompleteSave(&navReplyStruc,kNavTranslateInPlace);
      }

      NavDisposeReply(&navReplyStruc);
    }
  }
}

// ***************************************************************************** doOpenCommand

void  doOpenCommand(void)
{  
  OSErr            osError  = noErr;
  NavDialogOptions dialogOptions;
  NavEventUPP      navEventFunctionUPP;
  NavReplyRecord   navReplyStruc;
  SInt32           index, count;
  AEKeyword        theKeyword;
  DescType         actualType;
  FSSpec           fileSpec;  
  Size             actualSize;
  FInfo            fileInfo;

  osError = NavGetDefaultDialogOptions(&dialogOptions);

  if(osError == noErr)
  {
    navEventFunctionUPP = NewNavEventUPP((NavEventProcPtr) navEventFunction);
    osError = NavGetFile(NULL,&navReplyStruc,&dialogOptions,navEventFunctionUPP,NULL,NULL,
                         NULL,NULL);
    DisposeNavEventUPP(navEventFunctionUPP);

    if(osError == noErr && navReplyStruc.validRecord)
    {
      if(osError == noErr)
      {
        if(osError == noErr)
        {
          osError = AECountItems(&(navReplyStruc.selection),&count);

          for(index=1;index<=count;index++)
          {
            osError = AEGetNthPtr(&(navReplyStruc.selection),index,typeFSS,&theKeyword,
                                &actualType,&fileSpec,sizeof(fileSpec),&actualSize);
            {
              if((osError = FSpGetFInfo(&fileSpec,&fileInfo)) == noErr)
                doOpenFile(fileSpec);
            }
          }
        }
      }

      NavDisposeReply(&navReplyStruc);  
    }
  }
}

// ******************************************************************************** doOpenFile

void  doOpenFile(FSSpec fileSpec)
{
  WindowRef           windowRef;
  docStructurePointer docStrucPtr;
  TEHandle            textEditStrucHdl;
  SInt16              fileRefNum;
  SInt32              textLength;
  Handle              textBuffer;

  if((windowRef = doNewDocWindow()) == NULL)
    return;

  docStrucPtr = (docStructurePointer) (GetWRefCon(windowRef));
  textEditStrucHdl = docStrucPtr->textEditStrucHdl;

  SetWTitle(windowRef,fileSpec.name);

  FSpOpenDF(&fileSpec,fsCurPerm,&fileRefNum);

  SetFPos(fileRefNum,fsFromStart,0);
  GetEOF(fileRefNum,&textLength);

  if(textLength > 32767)
    textLength = 32767;

  textBuffer = NewHandle((Size) textLength);

  FSRead(fileRefNum,&textLength,*textBuffer);

  MoveHHi(textBuffer);
  HLock(textBuffer);

  TESetText(*textBuffer,textLength,textEditStrucHdl);

  HUnlock(textBuffer);
  DisposeHandle(textBuffer);

  FSClose(fileRefNum);

  (*textEditStrucHdl)->selStart = 0;
  (*textEditStrucHdl)->selEnd = 0;

  doDrawContent(windowRef);
}

// ****************************************************************************** doErrorAlert

void  doErrorAlert(SInt16 errorCode)
{
  Str255 errorString;
  SInt16 itemHit;

  GetIndString(errorString,rErrorStrings,errorCode);

  if(errorCode < eWindow)
  {
    StandardAlert(kAlertStopAlert,errorString,NULL,NULL,&itemHit);
    ExitToShell();
  }
  else
  {
    StandardAlert(kAlertCautionAlert,errorString,NULL,NULL,&itemHit);
  }
}

// ************************************************************************** navEventFunction

void  navEventFunction(NavEventCallbackMessage callBackSelector,NavCBRecPtr callBackParms,
                       NavCallBackUserData callBackUD)
{
}

// *******************************************************************************************
// StartAndTrackDrag.c
// *******************************************************************************************

// .................................................................................. includes

#include "Drag.h"

// .......................................................................... global variables

Boolean gCursorInContent, gCanAcceptItems, gCaretShowFlag;
SInt16  gInsertPosition, gLastOffset, gCaretOffset;
UInt32  gSystemCaretTime, gCaretStartTime;

extern Boolean gRunningOnX;

// ******************************************************************************* doStartDrag

OSErr  doStartDrag(EventRecord *eventStrucPtr,RgnHandle hiliteRgnHdl,TEHandle textEditStrucHdl)
{
  OSErr         osError;
  DragReference dragRef;
  Rect          originalHiliteRect, zeroedHiliteRect;
  RgnHandle     maskRgnHdl;
  Point         offsetPoint;
  QDErr         qdError;
  CGrafPtr      savedPortPtr;
  GDHandle      saveDeviceHdl;
  GWorldPtr     dragGWorldPtr = NULL;
  PixMapHandle  dragPixMapHdl, windPixMapHdl;
  RgnHandle     dragRgnHdl, tempRgnHdl;

  // ......................................................................... create new drag

  if(osError = NewDrag(&dragRef))
    return osError;

  // ...................................................................... add 'TEXT' flavour

  osError = AddDragItemFlavor(dragRef,1,'TEXT',
                              (*(*textEditStrucHdl)->hText) + (*textEditStrucHdl)->selStart,
                              (*textEditStrucHdl)->selEnd - (*textEditStrucHdl)->selStart,0);


  // .................................... get and set drag image for translucent drag and drop

  if(!gRunningOnX)
  {
    maskRgnHdl =  dragRgnHdl = tempRgnHdl = NULL;

    GetRegionBounds(hiliteRgnHdl,&originalHiliteRect);
    zeroedHiliteRect = originalHiliteRect;
    OffsetRect(&zeroedHiliteRect,-originalHiliteRect.left,-originalHiliteRect.top);

    GetGWorld(&savedPortPtr,&saveDeviceHdl);

    qdError = NewGWorld(&dragGWorldPtr,8,&zeroedHiliteRect,NULL,NULL,0);
    if(dragGWorldPtr != NULL && qdError == noErr)
    {
      SetGWorld(dragGWorldPtr,NULL);
      EraseRect(&zeroedHiliteRect);
    
      dragPixMapHdl = GetGWorldPixMap(dragGWorldPtr);
      LockPixels(dragPixMapHdl);
      windPixMapHdl = GetGWorldPixMap(savedPortPtr);

      CopyBits((BitMap *) *windPixMapHdl,(BitMap *) *dragPixMapHdl,
               &originalHiliteRect,&zeroedHiliteRect,srcCopy,NULL);

      UnlockPixels(dragPixMapHdl);
      SetGWorld(savedPortPtr,saveDeviceHdl);

      maskRgnHdl = NewRgn();
      if(maskRgnHdl != NULL)
      {
        CopyRgn(hiliteRgnHdl,maskRgnHdl);
        OffsetRgn(maskRgnHdl,-originalHiliteRect.left,-originalHiliteRect.top);

        SetPt(&offsetPoint,originalHiliteRect.left,originalHiliteRect.top);
        LocalToGlobal(&offsetPoint);

        SetDragImage(dragRef,dragPixMapHdl,maskRgnHdl,offsetPoint,kDragStandardTranslucency);
      }
    }
  }

  // ......................................................................... get drag region

  dragRgnHdl = NewRgn();
  if(dragRgnHdl == NULL)
    return MemError();

  CopyRgn(hiliteRgnHdl,dragRgnHdl);
  SetPt(&offsetPoint,0,0);
  LocalToGlobal(&offsetPoint);
  OffsetRgn(dragRgnHdl,offsetPoint.h,offsetPoint.v);
  
  tempRgnHdl = NewRgn();
  if(tempRgnHdl == NULL)
    return MemError();

  CopyRgn(dragRgnHdl,tempRgnHdl);
  InsetRgn(tempRgnHdl,1,1);
  DiffRgn(dragRgnHdl,tempRgnHdl,dragRgnHdl);
  DisposeRgn(tempRgnHdl);

  // ........................................................................ perform the drag

  osError = TrackDrag(dragRef,eventStrucPtr,dragRgnHdl);
  if(osError != noErr && osError != userCanceledErr)
    return osError;

  if(dragRef)       DisposeDrag(dragRef);
  if(maskRgnHdl)    DisposeRgn(maskRgnHdl);
  if(dragGWorldPtr) DisposeGWorld(dragGWorldPtr);
  if(dragRgnHdl)    DisposeRgn(dragRgnHdl);
  if(tempRgnHdl)    DisposeRgn(tempRgnHdl);

  return noErr;
}

// *********************************************************************** dragTrackingHandler

OSErr dragTrackingHandler(DragTrackingMessage trackingMessage,WindowRef windowRef,
                          void *handlerRefCon,DragRef dragRef)
{
  docStructurePointer docStrucPtr;
  DragAttributes      dragAttributes;
  UInt32              theTime;
  UInt16              numberOfDragItems, index;
  ItemReference       itemRef;
  OSErr               result;
  FlavorFlags         flavorFlags;
  Point               mousePt, localMousePt;  
  RgnHandle           windowHiliteRgn;
  Rect                correctedViewRect;
  SInt16              theOffset;

  if((trackingMessage != kDragTrackingEnterHandler) && !gCanAcceptItems)
    return noErr;

  docStrucPtr = (docStructurePointer) handlerRefCon;    

  GetDragAttributes(dragRef,&dragAttributes);
  gSystemCaretTime = GetCaretTime();
  theTime = TickCount();
  
  switch(trackingMessage)
  {
    // ......................................................................... enter handler

    case kDragTrackingEnterHandler:
      gCanAcceptItems = true;

      CountDragItems(dragRef,&numberOfDragItems);

      for(index=1;index <= numberOfDragItems;index++)
      {
        GetDragItemReferenceNumber(dragRef,index,&itemRef);
        result = GetFlavorFlags(dragRef,itemRef,'TEXT',&flavorFlags);
        if(result != noErr)
        {
          gCanAcceptItems = false;
          break;
        }
      }
      break;

      // ........................................................................ enter window

    case kDragTrackingEnterWindow:
      gCaretStartTime  = theTime;
      gCaretOffset     = gLastOffset = -1;
      gCaretShowFlag   = true;
      gCursorInContent = false;
      break;

    // ............................................................................. in window

    case kDragTrackingInWindow:

      GetDragMouse(dragRef,&mousePt,NULL);
      localMousePt = mousePt;
      GlobalToLocal(&localMousePt);

      if(dragAttributes & kDragHasLeftSenderWindow)
      {
        if(PtInRect(localMousePt,&(**(docStrucPtr->textEditStrucHdl)).viewRect))
        {
          if(!gCursorInContent)
          {
            windowHiliteRgn = NewRgn();
            correctedViewRect = (**(docStrucPtr->textEditStrucHdl)).viewRect;
            InsetRect(&correctedViewRect,-2,-2);
            RectRgn(windowHiliteRgn,&correctedViewRect);
            ShowDragHilite(dragRef,windowHiliteRgn,true);
            DisposeRgn(windowHiliteRgn);
          }
          gCursorInContent = true;
        }
        else
        {
          if(gCursorInContent)
            HideDragHilite(dragRef);
          gCursorInContent = false;
        }
      }

      // . . . . . . . . . . . . start caret drawing stuff, first get the offset into the text

      theOffset = doGetOffset(mousePt,docStrucPtr->textEditStrucHdl);

      // . . . . . . . . . . . . . . .  if in sender window, defeat caret drawing in selection

      if(dragAttributes & kDragInsideSenderWindow)
      {
        if((theOffset >= (*(docStrucPtr->textEditStrucHdl))->selStart) &&
           (theOffset <= (*(docStrucPtr->textEditStrucHdl))->selEnd))
        {
          theOffset = -1;
        }
      }

      // . . . . . . . . . . . . . . save the offset to a global for use by dragReceiveHandler

      gInsertPosition = theOffset;

      // . . . . . . . . . . . . . . . . if offset has changed, reset the caret flashing timer

      if(theOffset != gLastOffset)
      {
        gCaretStartTime = theTime;
        gCaretShowFlag = true;
      }

      gLastOffset = theOffset;

      // . . . . if caret-flashing interval has elapsed, toggle caret "show" flag, reset timer

      if(theTime - gCaretStartTime > gSystemCaretTime)
      {
        gCaretShowFlag = !gCaretShowFlag;
        gCaretStartTime = theTime;
      }

      // . . . . . . . . . . if caret "show" flag is off, set variable to defeat caret drawing

      if(!gCaretShowFlag)
        theOffset = -1;

      // . . . . if offset has changed, erase previous caret, draw new caret at current offset

      if(theOffset != gCaretOffset)
      {
        // . . . . . . . if first pass this window, don't erase, otherwise erase at old offset

        if(gCaretOffset != -1)
          doDrawCaret(gCaretOffset,docStrucPtr->textEditStrucHdl);

        // . . . . . . .  . . .  . . .  if "show" flag says show, draw caret at current offset

        if(theOffset != -1)
          doDrawCaret(theOffset,docStrucPtr->textEditStrucHdl);
      }

      gCaretOffset = theOffset;

      break;

    // .......................................................................... leave window

    case kDragTrackingLeaveWindow:

      if(gCaretOffset != -1)
      {
        doDrawCaret(gCaretOffset,docStrucPtr->textEditStrucHdl);
        gCaretOffset = -1;
      }

      if(gCursorInContent && dragAttributes & kDragHasLeftSenderWindow)
        HideDragHilite(dragRef);

      break;

    // ......................................................................... leave handler

    case kDragTrackingLeaveHandler:
      break;
  }

  return noErr;
}

// ******************************************************************************* doGetOffset

SInt16  doGetOffset(Point mousePt,TEHandle textEditStrucHdl)
{
  WindowRef windowRef;
  SInt16    theOffset;
  Point     thePoint;

  theOffset = -1;

  if(FindWindow(mousePt,&windowRef) == inContent)
  {
    SetPortWindowPort(windowRef);
    GlobalToLocal(&mousePt);

    if(PtInRect(mousePt,&((*textEditStrucHdl)->viewRect)))
    {
      theOffset = TEGetOffset(mousePt,textEditStrucHdl);
      thePoint  = TEGetPoint(theOffset - 1,textEditStrucHdl);

      if((theOffset) &&
         (doIsOffsetAtLineStart(theOffset,textEditStrucHdl)) &&
         ((*(*textEditStrucHdl)->hText)[theOffset - 1] != 0x0D) &&
         (thePoint.h < mousePt.h))
      {
        theOffset--;
      }
    }
  }

  return theOffset;
}

// ********************************************************************* doIsOffsetAtLineStart

SInt16  doIsOffsetAtLineStart(SInt16 offset,TEHandle textEditStrucHdl)
{
  SInt16 line = 0;

  if((*textEditStrucHdl)->teLength == 0)
    return(true);

  if(offset >= (*textEditStrucHdl)->teLength)
    return((*((*textEditStrucHdl)->hText))[(*textEditStrucHdl)->teLength - 1] == 0x0D);

  while((*textEditStrucHdl)->lineStarts[line] < offset)
    line++;

  return ((*textEditStrucHdl)->lineStarts[line] == offset);
}

// ******************************************************************************* doDrawCaret

void  doDrawCaret(SInt16 theOffset,TEHandle textEditStrucHdl)
{
  Point  thePoint;
  SInt16 theLine, lineHeight;

  thePoint = TEGetPoint(theOffset,textEditStrucHdl);
  theLine  = doGetLine(theOffset,textEditStrucHdl);

  if((theOffset == (*textEditStrucHdl)->teLength) &&
      (*((*textEditStrucHdl)->hText))[(*textEditStrucHdl)->teLength - 1] == 0x0D)
  {
    thePoint.v += TEGetHeight(theLine,theLine,textEditStrucHdl);
  }

  PenMode(patXor);
  lineHeight = TEGetHeight(theLine,theLine,textEditStrucHdl);
  MoveTo(thePoint.h - 1,thePoint.v - 1);
  Line(0,1 - lineHeight);

  PenNormal();
}

// ********************************************************************************* doGetLine

SInt16  doGetLine(SInt16 theOffset,TEHandle textEditStrucHdl)
{
  SInt16 theLine = 0;

  if(theOffset > (*textEditStrucHdl)->teLength)
    return ((*textEditStrucHdl)->nLines);

  while((*textEditStrucHdl)->lineStarts[theLine] < theOffset)
    theLine++;

  return theLine;
}

// *******************************************************************************************
// ReceiveAndUndoDrag.c
// *******************************************************************************************

// .................................................................................. includes

#include "Drag.h"

// .......................................................................... global variables

extern Boolean gEnableDragUndoRedoItem;
extern Boolean gUndoFlag;
extern Boolean gCanAcceptItems;
extern SInt16  gInsertPosition, gCaretOffset;

// ************************************************************************ dragReceiveHandler

OSErr  dragReceiveHandler(WindowRef windowRef,void *handlerRefCon,DragRef dragRef)
{
  docStructurePointer docStrucPtr;
  TEHandle            textEditStrucHdl;
  SInt32              totalTextStart;
  Size                totalTextSize;
  Boolean             wasActive, moveText, gotUndoMemory = false;
  DragAttributes      dragAttributes;
  SInt16              mouseDownModifiers, mouseUpModifiers, selStart, selEnd;
  UInt16              numberOfDragItems, index;
  ItemReference       itemReference;
  OSErr               osError;
  Size                textSize;
  Ptr                 textDataPtr;
  SInt32              additionalChars;
  
  if((!gCanAcceptItems) || (gInsertPosition == -1))
    return dragNotAcceptedErr;

  docStrucPtr = (docStructurePointer) handlerRefCon;    
  textEditStrucHdl = docStrucPtr->textEditStrucHdl;

  // . set graphics port to this window's port and, if necessary, activate text edit structure

  SetPortWindowPort(windowRef);

  wasActive = (*textEditStrucHdl)->active != 0;
  if(!wasActive)
    TEActivate(textEditStrucHdl);

  // .............................................. get drag attributes and keyboard modifiers

  GetDragAttributes(dragRef,&dragAttributes);
  GetDragModifiers(dragRef,0L,&mouseDownModifiers,&mouseUpModifiers);

  // . . .  in case their are multiple items, save first insertion point for later TESetSelect

  totalTextStart = gInsertPosition;
  totalTextSize = 0;

  // . . for all items in drag, get 'TEXT' data, insert into this window's text edit structure

  CountDragItems(dragRef,&numberOfDragItems);
  
  for(index=1;index <= numberOfDragItems;index++)
  {
    GetDragItemReferenceNumber(dragRef,index,&itemReference);

    osError = GetFlavorDataSize(dragRef,itemReference,'TEXT',&textSize);
    if(osError == noErr)
    {
      // .  if addition of drag to the text edit structure would exceed TextEdit limit, return

      if(((*textEditStrucHdl)->teLength + textSize) > kMaxTELength)
        return dragNotAcceptedErr;

      // . . . . . . . . . . . . . create nonrelocatable block and get the 'TEXT' data into it

      textDataPtr = NewPtr(textSize);
      if(textDataPtr == NULL)
        return dragNotAcceptedErr;

      GetFlavorData(dragRef,itemReference,'TEXT',textDataPtr,&textSize,0);

      // . . . . . . . . . . . . . . . . . .  if caret or highlighting is on screen, remove it

      if(gCaretOffset != -1)
      {
        doDrawCaret(gCaretOffset,textEditStrucHdl);
        gCaretOffset = -1;
      }

      if(dragAttributes & kDragHasLeftSenderWindow)
        HideDragHilite(dragRef);

      // save current text and selection start/end for Undo, and set Redo/Undo menu item flags

      if(dragAttributes & kDragInsideSenderWindow)
      {
        gotUndoMemory = doSavePreInsertionText(docStrucPtr);
        if(gotUndoMemory)
        {
          gEnableDragUndoRedoItem = true;
          gUndoFlag = true;
        }
      }
      else
        gEnableDragUndoRedoItem = false;

      // . . . . . if in sender window, ensure selected text is deleted if option key not down

      moveText = (dragAttributes & kDragInsideSenderWindow) &&
                 (!((mouseDownModifiers & optionKey) | (mouseUpModifiers & optionKey)));

      if(moveText)
      {
        selStart = (*textEditStrucHdl)->selStart;
        selEnd   = (*textEditStrucHdl)->selEnd;

        // . . . . .  extend selection by one chara if space charas just before and just after
        
        if(doIsWhiteSpaceAtOffset(selStart - 1,textEditStrucHdl) &&
          !doIsWhiteSpaceAtOffset(selStart,textEditStrucHdl) &&
          !doIsWhiteSpaceAtOffset(selEnd - 1,textEditStrucHdl) &&
           doIsWhiteSpaceAtOffset(selEnd,textEditStrucHdl))
        {
          if(doGetCharAtOffset(selEnd,textEditStrucHdl) == ' ')
            (*textEditStrucHdl)->selEnd++;
        }

        // if insertion is after selected text, move insertion point back by size of selection
        
        if(gInsertPosition > selStart)
        {
          selEnd = (*textEditStrucHdl)->selEnd;
          gInsertPosition -= (selEnd - selStart);
          totalTextStart -= (selEnd - selStart);
        }

        // . . . .  . . . . . . . . . . . . . . . . . . . . . . . . . . . delete the selection
        
        TEDelete(textEditStrucHdl);  
      }

      // . . . . . . . . . . . . . . . . . . . . insert the 'TEXT' data at the insertion point

      additionalChars = doInsertTextAtOffset(gInsertPosition,textDataPtr,textSize,
                                             textEditStrucHdl);

      // . . . . . if inserting multiple blocks of text, update insertion point for next block

      gInsertPosition += textSize + additionalChars;
      totalTextSize += textSize + additionalChars;

      // . . . . . . . . . . . . . . . . . . . . . . . . . . . dispose of nonrelocatable block

      DisposePtr(textDataPtr);
    }
  }

  // ......................................... select total inserted text and adjust scrollbar

  TESetSelect(totalTextStart,totalTextStart + totalTextSize,textEditStrucHdl);
  doAdjustScrollbar(windowRef);

  // ...... set window's "touched" flag, and save post-insert selection start and end for Redo

  docStrucPtr->windowTouched = true;

  if(dragAttributes & kDragInsideSenderWindow)
  {
    docStrucPtr->postDropSelStart = totalTextStart;
    docStrucPtr->postDropSelEnd = totalTextStart + totalTextSize;
  }

  // ....................... if text edit structure had to be activated earlier, deactivate it

  if(!wasActive)
    TEDeactivate(textEditStrucHdl);

  return noErr;
}

// ******************************************************************** doIsWhiteSpaceAtOffset

Boolean  doIsWhiteSpaceAtOffset(SInt16 offset,TEHandle textEditStrucHdl)
{
  char theChar;

  if((offset < 0) || (offset > (*textEditStrucHdl)->teLength - 1))
    return true;

  theChar = ((char *) *((*textEditStrucHdl)->hText))[offset];

  return (doIsWhiteSpace(theChar));
}

// **************************************************************************** doIsWhiteSpace

Boolean  doIsWhiteSpace(char theChar)
{
  return ((theChar == ' ') || (theChar == 0x0D));
}

// ************************************************************************* doGetCharAtOffset

char  doGetCharAtOffset(SInt16 offset,TEHandle textEditStrucHdl)
{
  if(offset < 0)
    return 0x0D;

  return (((char *) *((*textEditStrucHdl)->hText))[offset]);
}

// ********************************************************************** doInsertTextAtOffset

SInt16  doInsertTextAtOffset(SInt16 textOffset,Ptr textDataPtr,SInt32 textSize,
                             TEHandle textEditStrucHdl)
{
  SInt16 charactersAdded = 0;

  if(textSize == 0)
    return charactersAdded;

  // .. if inserting at end of word, and selection does not begin with a space, insert a space

  if(!doIsWhiteSpaceAtOffset(textOffset - 1,textEditStrucHdl) &&
     doIsWhiteSpaceAtOffset(textOffset,textEditStrucHdl) &&
     !doIsWhiteSpace(textDataPtr[0]))
  {
    TESetSelect(textOffset,textOffset,textEditStrucHdl);
    TEKey(' ',textEditStrucHdl);
    ++textOffset;
    ++charactersAdded;
  }

  // . if inserting at beginning of word and selection does not end with a space, insert space

  if(doIsWhiteSpaceAtOffset(textOffset - 1,textEditStrucHdl) &&
    !doIsWhiteSpaceAtOffset(textOffset,textEditStrucHdl) &&
    !doIsWhiteSpace(textDataPtr[textSize - 1]))
  {
    TESetSelect(textOffset,textOffset,textEditStrucHdl);
    TEKey(' ',textEditStrucHdl);
    ++charactersAdded;
  }

  // ......................................... before inserting, set selection range to a zero

  TESetSelect(textOffset,textOffset,textEditStrucHdl);
  TEInsert(textDataPtr,textSize,textEditStrucHdl);

  return charactersAdded;
}

// ******************************************************************** doSavePreInsertionText

Boolean  doSavePreInsertionText(docStructurePointer  docStrucPtr)
{
  OSErr  osError;
  Size   tempSize;
  Handle tempTextHdl;

  if(docStrucPtr->preDragText == NULL)
    docStrucPtr->preDragText = NewHandle(0);

  tempTextHdl = (*(docStrucPtr->textEditStrucHdl))->hText;
  tempSize = GetHandleSize(tempTextHdl);
  SetHandleSize(docStrucPtr->preDragText,tempSize);
  osError = MemError();
  if(osError != noErr)
  {
    doErrorAlert(eDragUndo);
    return false;
  }

  BlockMove(*tempTextHdl,*(docStrucPtr->preDragText),tempSize);

  docStrucPtr->preDragSelStart = (*((docStrucPtr)->textEditStrucHdl))->selStart;
  docStrucPtr->preDragSelEnd   = (*((docStrucPtr)->textEditStrucHdl))->selEnd;

  return true;
}

// **************************************************************************** doUndoRedoDrag

void  doUndoRedoDrag(WindowRef windowRef)
{
  docStructurePointer docStrucPtr;
  Handle              tempTextHdl;
  Rect                portRect;
  
  docStrucPtr = (docStructurePointer) (GetWRefCon(windowRef));

  tempTextHdl = (*(docStrucPtr->textEditStrucHdl))->hText;
  (*(docStrucPtr->textEditStrucHdl))->hText = docStrucPtr->preDragText;
  docStrucPtr->preDragText = tempTextHdl;

  if(gUndoFlag)
  {
    (*((docStrucPtr)->textEditStrucHdl))->selStart = docStrucPtr->preDragSelStart;
    (*((docStrucPtr)->textEditStrucHdl))->selEnd = docStrucPtr->preDragSelEnd;
  }
  else
  {
    (*((docStrucPtr)->textEditStrucHdl))->selStart = docStrucPtr->postDropSelStart;
    (*((docStrucPtr)->textEditStrucHdl))->selEnd = docStrucPtr->postDropSelEnd;
  }

  gUndoFlag = !gUndoFlag;

  TECalText(docStrucPtr->textEditStrucHdl);
  GetWindowPortBounds(windowRef,&portRect);
  InvalWindowRect(windowRef,&portRect);
}

// *******************************************************************************************

Demonstration Program Drag Comments

When this program is run, the user should open the included document "Drag Document" and drag
selections to other locations within the document, to other demonstration program windows, to
the windows of other applications that accept 'TEXT' format data, and to the desktop and Finder
windows (to create text clippings).  The user should also drag text from the windows of other
applications to the demonstration program's windows.

The user should note the following:

o The highlighting of the demonstration program's windows, and of the proxy icon, when items
  containing data of the 'TEXT' flavour are dragged over those windows.

o The movement of the insertion point caret with the cursor when items are dragged within the
  demonstration program's windows, and the "hiding" of the caret when the drag originates in a
  demonstration program window and the cursor is moved over the selection.

o When dragging and dropping within the demonstration program's windows:

  o The program's implementation of "smart drag and drop" (For example, if there is a space
    character immediately to the left and right of the selection, the deletion is extended to
    include the second space character, thus leaving a single space character between the two
    words which previously bracketed the selection.)

  o The availability and effect of the Undo/Redo item in the Edit menu.

The non-drag and drop aspects of this program are based on the demonstration program
MonoTextEdit (Chapter 21), and the contents of Drag.h and Drag.c are very similar to the
contents of MonoTextEdit.c.  Accordingly, comments on the content of Drag.h and Drag.c are
restricted to those areas where modifications have been made to the code contained in
MonoTextEdit.c.

Drag.h

defines

Three additional constants are established for drag and drop errors.

typedefs

The docStructure data type has been extended to include fields to store the owning window's
window reference, a Boolean which is set to true when the contents of the window have been
modified, and five fields to support drag and drop undo/redo.

Drag.c

Global Variables

gDragTrackingHandlerUPP and gDragTrackingReceiverUPP will be assigned universal procedure
pointers to the tracking and receive handlers.  gEnableDragUndoRedoItem and gUndoFlag will be
used to control enabling/disabling of the Drag and Drop Undo/Redo item in the Edit menu.

main

Universal procedure pointers are created for the drag tracking and receive handlers
(dragTrackingHandler and dragReceiveHandler).

windowEventHandler

When the kWindowEventClickContentRgn event type is received, ConvertEventReftoEventRecord is
called and the address of the event record is passed in the call to doInContent, in addition to
the mouse location and Shift key position.  The address of an event record will be required by
the call to doStartDrag in doInContent, and ultimately by the Drag Manager function TrackDrag.

doKeyEvent

The global variable gEnableDragUndoRedoItem is set to false.  This causes the Drag and Drop
Undo/Redo item in the Edit menu to be disabled.

doInContent

If the mouse-down was within the TextEdit view rectangle, TEGetHiliteRgn is called to attempt
to get the highlight region.  If there is a highlight region (that is, a selection), and if the
mouse-down was within that region, WaitMouseMoved is called.  WaitMouseMoved waits for either
the mouse to move from the given initial mouse location or for the mouse button to be released. 
If the mouse moves away from the initial mouse location before the mouse button is released,
WaitMouseMoved returns true, in which case the function doStartDrag is called.

doNewDocWindow

A nonrelocatable block is created for the window's document structure.

SetWindowProxyCreatorAndType is called with 0 passed in the fileCreator parameter and 'TEXT'
passed in the fileType parameter to cause the system's default icon for a document file to be
displayed as the proxy icon.  In this program, the proxy icon is used solely for the purpose of
demonstrating proxy icon highlighting when ShowDragHilite tis called to indicate that the
window is a valid drag-and-drop target.

After gNumberOfWindows is incremented, four of the fields of the document structure are
initialised.

Following the call to TEFeatureFlag, the drag tracking and receive handlers are installed on
the window.  (Note that the pointer to the window's document structure is passed in the
handlerRefCon parameter of the installer functions.)  If either installation is unsuccessful,
the window and document structure are disposed of, the tracking handler also being removed in
the case of a failure to install the receive handler.

doAdjustMenus

When the global variable gEnableDragUndoRedoItem is set to true, the Drag and Drop Undo/Redo
item is enabled, otherwise it is disabled.  If the item is enabled, the global variable
gUndoFlag controls the item text, setting it to either Undo or Redo.

doEditMenu

If the Drag and Drop Undo/Redo item is chosen from the Edit menu, the function doUndoRedoDrag
is called.

doAdjustCursor

After the first call to DiffRgn (which establishes the arrow and IBeam regions), a pointer to
the window's document structure is retrieved.  This allows the handle to the window's TextEdit
structure to be passed in a call to TEGetHiliteRgn.  The region returned by TEGetHiliteRgn is
in local coordinates, so the next three lines change it to global coordinates preparatory to a
call to DiffRgn.  The DiffRgn call, in effect, cuts the equivalent of the highlight region out
of the IBeam region.

If the location of the mouse (returned by the call to GetGlobalMouse) is within the highlight
region, the cursor is set to the arrow shape.

doCloseWindow

If the preDragText field of the window's document structure does not contain NULL,
DisposeHandle is called to release memory assigned in support of drag and drop redo/undo.

The calls to RemoveTrackingHandler and RemoveReceiveHandler remove the tracking and receive
handlers before the window is disposed of.

StartAndTrackDrag.c

doStartDrag

The call to NewDrag allocates a new drag object.

The call to AddDragItemFlavor creates a drag item and adds a data flavour (specifically 'TEXT')
to that item.  Note that the item reference number passed is 1.  If additional flavours were to
be added to the item, this same item reference number would be passed in the additional calls
to AddDragItemFlavor.

The next block gets and sets the drag image for translucent dragging, but executes only if the
program is running on Mac OS 8/9.  GetRegionBounds gets the bounding rectangle of the highlight
region and OffsetRect adjusts the coordinates so that top left is 0,0.  The call to NewGWorld
creates an 8-bit deep offscreen graphics world the same size as this rectangle.  CopyBits is
then called to copy the highlight region area to the offscreen graphics world.  The calls to
CopyRgn and OffsetRgn create a region the same shape as the highlight region and adjusted so
that the top left of the bounding rectangle is 0,0.  The next two lines establish the offset
point required by the following call to SetDragImage.  This offset is required to move the
pixel map in the offscreen graphics world to the global coordinates where the drag image is to
initially appear.  Finally, the handles to the offscreen graphics world and mask, the offset,
and a constant specifying the required level of translucency are passed in the call to
SetDragImage, which associates the image with the drag reference.

Any errors which might occur in setting up the translucent drag are not critical on Mac OS 8/9
because the next block creates the drag region for the alternative visual representation (a
gray outline).  On Mac OS X, this is the block that creates the gray outline.  The received
highlight region (which is in local coordinates) is copied to dragRgnHdl, which is then
converted to global coordinates.  This region, in turn, is copied to tempRgnHdl, which is then
inset by one pixel.  The call to DiffRgn subtracts the inset region from dragRgnHdl, leaving
the latter with the same outline as the highlight region but only one pixel thick.  The newly
defined drag region is passed in the theRegion parameter of the call to TrackDrag.

TrackDrag performs the drag.  During the drag, the Drag Manager follows the cursor on the
screen with the translucent image (Mac OS 8/9) or gray outline (Mac OS X) and sends tracking
messages to applications that have registered drag tracking handlers.  When the user releases
the mouse button, the Drag Manager calls any receive drop handlers that have been registered on
the destination window.

The TrackDrag function returns noErr in situations where the user selected a destination for
the drag and the destination received data from the Drag Manager.  If the user drops over a
non-aware application or the receiver does not accept any data from the Drag Manager, the Drag
Manager automatically provides a "zoom back" animation and returns userCanceledErr.  Thus the
first return will execute only if an error other than userCanceledErr was returned.

dragTrackingHandler

dragTrackingHandler is the drag tracking handler.  It is a callback function.

Firstly, if the message received is not the enter handler message, and if it was determined at
the time of receipt of the enter handler message that the drop cannot be accepted, the function
returns immediately.

The pointer received in the handlerRefCon formal parameter is cast to a pointer to the window's
document structure so that certain fields in this structure can later be accessed.

GetDragAttributes gets the current set of drag attribute flags.  GetCaretTime gets the
insertion point caret blink interval.  TickCount gets the current system tick count.  These
latter two values will be used by the caret drawing code.
enter handler
Within the switch, the "enter handler" message is processed at the first case.

CountDragItems returns the number of items in the drag.  Then, for each of the items in the
drag, GetDragItemReferenceNumber is called to retrieve the item reference number, allowing the
call to GetFlavorFlags to determine whether the item contains data of the 'TEXT' flavour. 
(GetFlavorFlags will return an error if the flavour does not exist.)  gCanAcceptItems is
assigned false if any item does not contain 'TEXT' data, meaning that the drag will only be
accepted if all items contain data of the 'TEXT' flavour.
enter window
If the message is the "enter window" message, several global variables are initialised.  These
globals will be used when the "in window" message is received to control insertion point caret
drawing and window highlighting.
in window
Each time the "in window" message is received, GetDragMouse is called to get the current mouse
location in global coordinates.  The location is copied to a local Point variable, which is
then converted from global to local coordinates.

The first if block executes if the drag has left the sender window.  If the mouse cursor is
within the window's TextEdit view rectangle, and if gCursorInContent has previously been set to
false (recall that it is set to false at the "enter window" message), ShowDragHilite is called
to draw the standard drag and drop highlight around the view rectangle.  gCursorInContent is
then set to true to defeat further ShowDragHilite calls while the drag remains in this window.

If the mouse cursor is not within the window's TextEdit view rectangle, and if gCursorContent
has previously been set to true (causing a highlight draw), HideDragHilite is called to remove
the highlighting, and gCursorInContent is set to false again so that ShowDragHilite is called
if the drag moves back inside a view rectangle again.

With window highlighting attended to, the next task is to perform insertion point caret
drawing.

The function doGetOffset takes the mouse location in global coordinates and the window's
TextEdit structure handle and returns the offset into the text corresponding to the mouse
location.  (Note that doGetOffset will return -1 if the cursor is not within the content region
and the view rectangle of the window under the cursor.  Note also that, if the cursor is within
the content region of the window under the cursor, doGetOffset sets that window's graphics port
as the current port.)

if the drag is currently inside the sender window, and the offset returned by doGetOffset
indicates that the cursor is within the TextEdit selection, theOffset is set to -1.  As will be
seen, this defeats the drawing of the caret when the cursor is within the selection.

The offset returned by doGetOffset is then saved to a global variable.  As will be seen, the
value assigned to this variable the last time the "in window" case executes (when the user
releases the mouse button) will be used by the receive handler dragReceiveHandler.

If the offset has changed since the last time the "in window" case executed, the caret flashing
timer is reset to the time the handler was entered this time around and the caret show/hide
flag is set to "show".  The current offset is then assigned to the global gLastOffset
preparatory to the execution of the "in window" case.

If the caret flashing interval has elapsed, the show/hide flag is toggled and the caret
flashing timer is reset.

If the caret show/hide flag indicates that the caret should be "hidden", theOffset will be set
to -1 to defeat caret drawing.

If the offset has changed since the last execution of the "in window" case, the function for
drawing/erasing the caret is called in certain circumstances.  Firstly, if this is not the
first execution of the "in window" case since entering the window, doDrawCaret is called to
erase the caret previously drawn at the old offset.  Secondly, if the show/hide flag indicates
that the caret should be drawn, doDrawCaret is called to draw the caret at the current offset.

The global which stores the old offset is assigned the current offset before the case exits.
leave window
When the "leave window" message is received, if the caret is on the screen, it is erased.  If
the window highlighting has previously been drawn, HideDragHilite is called to remove the
highlighting.

doGetOffset

doGetOffset is called by dragTrackingHandler to return the offset into the text corresponding
to the specified mouse location.  doGetOffset also sets the graphics port to the port
associated with the window under the cursor.

If the part code returned by FindWindow indicates that the cursor is within the content region
of a window, that window's graphics port is set as the current port and the cursor location is
converted to local coordinates preparatory to the call to PtInRect.

If the cursor is within the view rectangle of the TextEdit structure associated with the
window, TEGetOffset is called to get the offset into the text corresponding to the cursor
location and TEGetPoint is called to get the local coordinates of the character immediately
before the offset.

If the offset is at a TextEdit line start, and the character immediately before the offset is
not a carriage return, and the horizontal coordinate of the character immediately before the
offset is less than the horizontal coordinate of the cursor, theOffset is decremented by one. 
In the situation where the cursor is dragged to the right of the rightmost character of a line,
this will cause the caret to continue to be drawn immediately to the right of that character
instead of "jumping" to the beginning of the next line.

isOffsetAtLineStart

isOffsetAtLineStart is called by doGetOffset.  It returns true if the specified offset is at a
TextEdit line start.

doDrawCaret

doDrawCaret is called by dragTrackingHandler to draw and erase the caret.  The transfer mode is
set to patXor so that two successive calls will, in effect, draw and erase the image.

The call to TEGetPoint gets the coordinates of the bottom left of the character at the
specified offset.  The call to the function doGetLine returns the TextEdit line number that
contains the specified offset.

The next block accommodates a quirk of TextEdit.  For some reason, TextEdit does not return the
proper coordinates of the last offset in the field if the last character in the record is a
carriage return.  TEGetPoint returns a point that is one line higher than expected.  This block
fixes the problem.

Following the call to PenMode, TEGetHeight is called to get the height of a single line of
text.  A line is then drawn from a point one pixel to the left and above the bottom left of the
character at the offset to a point vertically above the starting point.  The length of the line
is one pixel less that the line height.

doGetLine

doGetLine is called by doDrawCaret.  It returns the TextEdit line number that contains the
specified offset.

ReceiveAndUndoDrag.c

dragReceiveHandler

dragReceiveHandler is the drag receive handler.  It is a callback function.

Recall that dragTrackingHandler sets gCanAcceptItems to true if all items in the drag contained
data of the 'TEXT' flavour.  Recall also that dragTrackingHandler sets gInsertPosition to -1 if
the cursor is over the selection.  Thus, if at least one item does not contain data of the
'TEXT' flavour, or if the cursor is over the selection at the time the mouse button is
released, the function exits and dragNotAcceptedErr is returned to the Drag Manager. 
dragNotAcceptedErr causes the Drag Manager to execute a "zoomback" animation of the drag region
to the source location.

The pointer received in the handlerRefCon formal parameter is cast to a pointer to the window's
document structure.  The handle to the TextEdit structure associated with the window is then
retrieved from the document structure.

SetPortWindowPort sets the window's graphics port as the current port.  The next line saves
whether the TextEdit structure is currently active or inactive so that that state can later be
restored.  If the TextEdit structure is currently not active, it is made active.

GetDragAttributes and GetDragModifiers are called to get the drag's attributes and the modifier
keys that were pressed at mouse-down and mouse-up time.

If there are multiple items in the drag, the initial insertion point, which is set in the drag
tracking handler, is saved to a local variable for later use by TESetSelect.  (If there are
multiple items to insert, the offset in gInsertPosition will be updated each time the main if
block executes.)

CountDragItems returns the number of items in the drag and the main for loop executes for each
item.  Within the loop, the first call (to GetDragItemReferenceNumber) retrieves the item's
item reference number, which is passed in the call to GetFlavorDataSize to get the size of the
'TEXT' data.  If GetFlavorDataSize does not return an error, the following occurs:

o The data size returned by GetFlavorDataSize is added to the current size of the text in the
  TextEdit structure.  If adding the 'TEXT' data to the current text would exceed the TextEdit
  limit, the handler exits, returning dragNotAcceptedErr to the Drag Manager.

o A nonrelocatable block the size of the 'TEXT' data is created and GetFlavorData is called to
  get the 'TEXT' data into that block.

o If the insertion point caret is on the screen, it is removed. (Recall that the global
  variables used here are set in the drag tracking handler.)  If the window is currently
  highlighted, the highlighting is removed.  

o If the drop is within the sender window, the function doSavePreInsertionText is called to
  save the current TextEdit text and the current selection start and end.  This is to support
  drag undo/redo.  If doSavePreInsertionText returns true, flags are set to cause the Undo/Redo
  item in the Edit menu to be enabled and to cause the initial item text to be set to "Undo".

o If there are multiple items in the drag, the initial insertion point is saved for later use
  by TESetSelect.  If there are multiple items to insert, the offset in gInsertPosition will be
  updated each time around. 

o The variable moveText is assigned true if the drop is inside the sender window and the option
  key was not down when the mouse button went down or was released.  If moveText is true, the
  current selection must be deleted, so the if block executes.

  o Firstly, the current selection start and end are saved to two local variables.

  o The next block implements "smart drag and drop"  If the character just before selection
    start is a space or CR character, and if the first character in the selection is not such a
    character, and if the last character in the selection is not such a character, and if the
    character just after the selection is such a character, then, if the character just after
    the selection is a space character, the current end of the selection is extended to include
    that space character.  This means that if, for example, just the characters of a word are 
    currently selected, the space character immediately after the selection is added to the 
    selection so that only one space character will remain between the words which bracket the
    selection when the selection is deleted by TEDelete.

  o If the current drop insertion offset is after the selection start offset, the local
    variable holding the selection end offset is updated (it may have been increased by the 
    "smart drag and drop" code), and gInsertPosition and totalTextStart offsets are moved back
    by the length of the selection.

  o TEDelete then deletes the selected text (perhaps extended by one by the "smart drag and
    drop" code) from the TextEdit structure and redraws the text.

o The doInsertTextAtOffset function is called to insert the 'TEXT' data at the current
  insertion point.  This function implements another aspect of "smart drag and drop" which can
  possibly add additional characters to the TextEdit structure.  The number of additional
  characters added (if any) is returned by the function.

o If the main if block executes again (meaning that there are multiple items in the drag),
  gInsertPosition must be updated to the offset for the next insertion.  This is achieved by
  adding the size of the last insertion, plus the number of any additional characters added by
  doInsertTextAtOffset to the current value in gInsertPosition.  The value in totalTextSize is
  increased by the same amount.

When all items have been inserted, the main if block exits.  TESetSelect is then called to set
the selection range to the total inserted text.  This call unhighlights the previous selection
and highlights the new selection range.  doAdjustScrollbar is called to adjust the scroll bars.

The windowTouched field in the document structure is set to true to record the fact that the
contents of the window have been modified.  The post-insertion selection start and selection
end offsets are saved to the relevant fields of the document structure for possible use in the
application's Undo/Redo function.

Finally, if the TextEdit structure was not active when dragReceiveHandler was entered, it is
restored to that state.

isWhiteSpaceAtOffset

isWhiteSpaceAtOffset is called by dragReceiveHandler and doInsertTextAtOffset.  Given an offset
into a TextEdit structure, it determines if the character at that offset is a "white space"
character, that is, a space character or a carriage return character.

isWhiteSpace

isWhiteSpace is called by isWhiteSpaceAtOffset and doInsertTextAtOffset.  Given a character, it
returns true if that character is either a space character or a carriage return character.

doGetCharAtOffset

doGetCharAtOffset is called by dragReceiveHandler.  Given an offset and a handle to a TextEdit
structure, it returns the character at that offset.

doInsertTextAtOffset

doInsertTextAtOffset is called by dragReceiveHandler to insert the drag text at the specified
offset.  In addition to inserting the text, dragReceiveHandler implements additional aspects of
"smart drag and drop", returning the number of space characters, if any, added to the TextEdit
structure text by this process.

If there is no text in the buffer, the function simply returns.

If the character to the left of the insertion offset is not a space, and if the character at
the offset is a space, the insertion is at the end of a word.  If, in addition, the first
character in the drag is not a space, TESetSelect is called to collapse the selection range to
an insertion point at the received offset, TEKey is called to insert a space character in the
TextEdit structure at that offset, the offset is incremented by one to accommodate that added
character, and the variable which keeps track of the number of added characters is incremented.

The next block is similar, except that it inserts a space character if the insertion is at the
beginning of a word and the text to be inserted does not end with a space character.  Also, in
this block, the offset is not incremented.

With the "smart drag and drop" segment completed, TESetSelect is called to ensure that the
selection range is collapsed to an insertion point at the (possibly incremented) offset, and
TEInsert is called to insert the text into the TextEdit structure at that insertion point.

doSavePreInsertionText

doSavePreInsertionText is called by dragReceiveHandler to save the document's pre-drop text and
selection in support of drag and drop undo/redo.

If the preDragText field of the window's document structure currently contains NULL, a new
empty relocatable block is created and its handle assigned to the preDragText field.

The next block copies the handle to the TextEdit structure's text to a local variable, gets the
size of the TextEdit structure's text, and expands the newly-created block to that size.

BlockMove is then called to copy the TextEdit structure's text to the newly-created block.  In
addition, the current (pre-drop) selection start and end are saved to the window's document
structure.

doUndoRedoDrag

doUndoRedoDrag is called by doEditMenu in the event of the user choosing the Drag and Drop
Undo/Redo item in the Edit menu.  That item will only be enabled when the user has released the
mouse button and the drop is within the sender window.

The first block means that, each time this function is called, the text block whose handle is
assigned to the TextEdit structure will toggle between the pre-drop text and the post-drop
text.  The first time this function is called, the item text in the Edit Menu will be "Drag and
Drop Undo" and the TextEdit structure will be assigned the handle to pre-drop text saved in the 
document structure's preDragText field.  The next time the function is called, the item text in
the Edit menu will be "Drag and Drop Redo" and the TextEdit structure will be assigned the
handle to post-drop text, and so on.

The global variable gUndoFlag is toggled by this function.  At the next block, and depending on
the value in gUndoFlag, either the pre-drop or post-drop selection start and end offsets are
assigned to the TextEdit structure, as appropriate.  (gUndoFlag also controls the text in the
Drag and Drop Undo/Redo item in the Edit menu.  See doAdjustMenus.)

With the appropriate text and selection assigned to the window's TextEdit structure, TECalText
is called to wrap the text to the width of the view rectangle and re-calculate the line-starts. 
Finally, InvalWindowRect is called to force a call to TEUpdate (in the doDrawContent function)
to redraw the text.
 
AAPL
$524.94
Apple Inc.
+5.93
MSFT
$40.01
Microsoft Corpora
-0.39
GOOG
$536.10
Google Inc.
-20.44

MacTech Search:
Community Search:

Software Updates via MacUpdate

VMware Fusion 6.0.3 - Run Windows apps a...
VMware Fusion allows you to create a Virtual Machine on your Mac and run Windows (including Windows 8.1) and Windows software on your Mac. Run your favorite Windows applications alongside Mac... Read more
Tweetbot 1.5.1 - Popular iOS twitter cli...
Tweetbot is a full-featured OS X Twitter client with a lot of personality. Whether it's the meticulously-crafted interface, sounds and animation, or features like multiple timelines and column views... Read more
Mac DVDRipper Pro 4.1.7 - Copy, backup,...
Mac DVDRipper Pro is the DVD backup solution that lets you protect your DVDs from scratches, save your batteries by reading your movies from your hard disk, manage your collection with just a few... Read more
PDFpenPro 6.2 - Advanced PDF toolkit for...
PDFpenPro allows users to edit PDF's easily. Add text, images and signatures. Fill out PDF forms. Merge or split PDF documents. Reorder and delete pages. Even correct text and edit graphics! Create... Read more
PDFpen 6.2 - Edit and annotate PDFs with...
PDFpen allows users to easily edit PDF's. Add text, images and signatures. Fill out PDF forms. Merge or split PDF documents. Reorder and delete pages. Even correct text and edit graphics! Features... Read more
Monolingual 1.5.9 - Remove unwanted OS X...
Monolingual is a program for removing unnecesary language resources from OS X, in order to reclaim several hundred megabytes of disk space. It requires a 64-bit capable Intel-based Mac and at least... Read more
Maya 2015 - Professional 3D modeling and...
Maya is an award-winning software and powerful, integrated 3D modeling, animation, visual effects, and rendering solution. Because Maya is based on an open architecture, all your work can be scripted... Read more
Starcraft II: Wings of Liberty 1.1.1.180...
Download the patch by launching the Starcraft II game and downloading it through the Battle.net connection within the app. Starcraft II: Wings of Liberty is a strategy game played in real-time. You... Read more
Sibelius 7.5.0 - Music notation solution...
Sibelius is the world's best-selling music notation software for Mac. It is as intuitive to use as a pen, yet so powerful that it does most things in less than the blink of an eye. The demo includes... Read more
Typinator 5.9 - Speedy and reliable text...
Typinator turbo-charges your typing productivity. Type a little. Typinator does the rest. We've all faced projects that require repetitive typing tasks. With Typinator, you can store commonly used... Read more

Latest Forum Discussions

See All

Have a Special Dead Trigger 2 Easter Bas...
Have a Special Dead Trigger 2 Easter Basket Full of Goodies, Courtesy of Madfinger Games Posted by Rob Rich on April 18th, 2014 [ permalink ] Dead Trigger 2 | Read more »
Almost All of Playdek’s Library is on Sa...
Almost All of Playdek’s Library is on Sale Right Now, and You Should Check it Out Posted by Rob Rich on April 18th, 2014 [ permalink ] Playdek has released quite a few great iOS ports of board and card games over the years, and now most of them... | Read more »
Zynga Launches Brand New Farmville Exper...
Zynga Launches Brand New Farmville Experience with Farmville 2: Country Escape Posted by Tre Lawrence on April 18th, 2014 [ permalink ] | Read more »
David. Review
David. Review By Cata Modorcea on April 18th, 2014 Our Rating: :: MINIMALISTIC IN A DIFFERENT WAYUniversal App - Designed for iPhone and iPad David is a minimalistic game wrapped inside of a soothing atmosphere in which the hero... | Read more »
Eyefi Unveils New Eyefi Cloud Service Th...
Eyefi Unveils New Eyefi Cloud Service That Allows Users to Share Media Across Personal Devices Posted by Tre Lawrence on April 18th, 2014 [ permalink ] | Read more »
Tales from the Dragon Mountain: The Lair...
Tales from the Dragon Mountain: The Lair Review By Jennifer Allen on April 18th, 2014 Our Rating: :: STEADY ADVENTURINGiPad Only App - Designed for the iPad Treading a safe path, Tales from the Dragon Mountain: The Lair is a... | Read more »
Yahoo Updates Flickr App with Advanced E...
Yahoo Updates Flickr App with Advanced Editing Features and More Posted by Tre Lawrence on April 18th, 2014 [ permalink ] | Read more »
My Incredible Body - A Kid's App to...
My Incredible Body - A Kid's App to Learn about the Human Body 1.1.00 Device: iOS Universal Category: Education Price: $2.99, Version: 1.1.00 (iTunes) Description: Wouldn’t it be cool to look inside yourself and see what was going on... | Read more »
Trials Frontier Review
Trials Frontier Review By Carter Dotson on April 18th, 2014 Our Rating: :: A ROUGH LANDINGUniversal App - Designed for iPhone and iPad Trials Frontier finally brings the famed stunt racing franchise to mobile, but how much does its... | Read more »
Evernote Business Notebook by Moleskin I...
Evernote Business Notebook by Moleskin Introduced – Support Available in Evernote for iOS Posted by Tre Lawrence on April 18th, 2014 [ permalink ] | Read more »

Price Scanner via MacPrices.net

Free HopTo 2.2 Helps Enhance Your Productivit...
The HopTo app helps you do more on your iPad by providing more and easier adaccess to files and documents. Version 2.2 adds Egnyte and HopTo’s Mac OSX File Connector. If you already have the hopTo... Read more
National Distracted Driving Awareness Month:...
As the country recognizes National Distracted Driving Awareness Month, Sprint is reminding wireless consumers to focus on driving while behind the wheel, to not text or email while driving, and to... Read more
13-inch 2.4GHz Retina MacBook Pro available f...
Abt has the 13″ 2.4GHz 128GB Retina MacBook Pro available for $1229 including free shipping. Their price is $70 off MSRP. Read more
iMacs on sale for up to $160 off MSRP this we...
Best Buy has iMacs on sale for up to $160 off MSRP for a limited time. Choose free home shipping or free instant local store pickup (if available). Prices are valid for online orders only, in-store... Read more
iPad Airs on sale this weekend for up to $100...
Best Buy has WiFi iPad Airs on sale for $50 off MSRP and WiFi + Cellular iPad Airs on sale for $100 off MSRP on their online store for a limited time, with prices now starting at $449. Choose free... Read more
Apple restocks refurbished Mac minis starting...
The Apple Store has restocked Apple Certified Refurbished Mac minis for up to $150 off the cost of new models. Apple’s one-year warranty is included with each mini, and shipping is free: - 2.5GHz Mac... Read more
Hyundai Brings Apple CarPlay To The 2015 Sona...
Hyundai Motor America has announced it will bring Apple CarPlay functionality to the 2015 Sonata. CarPlay is pitched as a smarter, safer and easier way to use iPhone in the car and gives iPhone users... Read more
Updated iPads Coming Sooner Than We Had Thoug...
MacRumors, cites KGI securities analyst Ming Chi Kuo, well-respected as an Apple product prognisticator, saying that Apple will introduce an upgraded iPad Air and iPad mini in 2014/Q3, meaning the... Read more
Toshiba Unveils New High And Low End Laptop M...
Toshiba has announced new laptop models covering both the high-end and low-end of the notebook computer spectrum. Toshiba 4K Ultra HD Laptop Toshiba’s new Satellite P55t features one of the world’s... Read more
Save up to $270 with Apple refurbished 13-inc...
The Apple Store has Apple Certified Refurbished October 2013 13″ Retina MacBook Pros available starting at $1099, with models up to $270 off MSRP. Apple’s one-year warranty is standard, and shipping... Read more

Jobs Board

*Apple* Automotive Parts Department position...
Apple Automotive is one of the fastest growing dealer…and it shows. Consider making the switch to the Apple Automotive Group today! At Apple Automotive, we Read more
*Apple* Solutions Consultant (ASC) - Apple (...
**Job Summary** The ASC is an Apple employee who serves as an Apple brand ambassador and influencer in a Reseller's store. The ASC's role is to grow Apple Read more
*Apple* Retail - Manager - Holyoke - Apple I...
Job Summary Keeping an Apple Store thriving requires a diverse set of leadership skills, and as a Manager, you’re a master of them all. In the store’s fast-paced, Read more
*Apple* Retail - Manager - Apple (United Sta...
Job SummaryKeeping an Apple Store thriving requires a diverse set of leadership skills, and as a Manager, you're a master of them all. In the store's fast-paced, dynamic Read more
*Apple* Solutions Consultant (ASC) - Apple (...
**Job Summary** The ASC is an Apple employee who serves as an Apple brand ambassador and influencer in a Reseller's store. The ASC's role is to grow Apple Read more
All contents are Copyright 1984-2011 by Xplain Corporation. All rights reserved. Theme designed by Icreon.