#include "cTextDragAndDropAttachment.h" #include #include #include #include #include #include #include #include #include // =========================================================================== // cTextDragAndDropAttachment.cpp Version 1.3.3 ©1998 Joakim Braun All rights reserved. // =========================================================================== // // CONTENTS: Various LAttachment subclasses that add drag and drop behavior to LTextEditViews, // LEditFields and LEditTexts. Supports drag and drop of text as well as of text files. // NOTE: The changes since version 1.3.1 include changes to the CTYPs, which means you'll garbage data // in your old ppobs. Also the parametrized constructors have changed. My apologies, // but this is still evolving, and I don't think there's a user base of thousands out there. // NOTE: This has been written with PowerPlant 1.9.1 and CodeWarrior Pro 2. There are bugs in PP 1.9.1 // concerning drop hiliting in grayscale windows. You can bypass them by using custom hiliting (see below). // HOW TO USE: There are several ways to work with these classes. // // Ä Add drag and drop to individual TextEdit objects. // Ä LTextEditViews: Add a cTextEditViewDDattachment programmatically or in Constructor. // Ä LEditFields: Add a cEditFieldDDattachment programmatically or in Constructor. // Ä LEditTexts: Add a cEditTextDDattachment programmatically or in Constructor. // Ä Any of the above: Add a cSmartTextDDAttachment programmatically or in Constructor. // cSmartTextDDAttachment will automatically add the correct attachment depending on // whether the host is an LTextEditView, LEditField or LEditText, after which it deletes itself. // Convenient to use as "one size fits all"-drag and drop-attachment in Constructor. // Ä Add drag and drop to LViews. // All LTextEditViews, LEditFields and LEditTexts contained by an LView may have drag and drop // automatically added by attaching a cViewTextDDAttachment to the LView. // The cViewTextDDAttachment will walk through all subpanes and subviews and add // the relevant cTextDragAndDropAttachment subclasses, after which it deletes itself. // // So, you can add a single cViewTextDDAttachment to an LWindow and get drag and drop // for all LTextEditViews, LEditFields and LEditTexts contained by it. // NOTE: Combining "individual" drag and drop attachments with cViewTextDDAttachments // in the same LView is asking for trouble, since it will result in several drag and drop attachments // for the same TextEdit object. // NOTE: cViewTextDDAttachment likes to be at the top of any given LView hierarchy // in Constructor. If you put a LTabGroup before it in Constructor's hierarchy window, // you'll get an error in AddDragAndDropToView() when running the program, // though I haven't figured out why yet. // You may at any time enable/disable drag and drop globally (for all these attachments) // by using the SetDragEnable() function (see header). // You may choose another pane than the TextEdit pane to act as a LDropArea. // In fact, any pane in the entire window. Useful if, for instance, you wish to place an LTextEditView // inside a LScrollerView, with a few pixel's margin around, but don't want the margin to show // when drag hiliting. In that case, put the LTextEditView inside a slightly larger LView, // and specify that LView's pane ID as drop hilite pane. // LIMITATIONS: No support yet for "delay select" (dragging from inactive windows). // The windows will be activated when clicked in. Sorry. This is a limitation of // Power Plant, which does not easily support add-on of delay select behavior // through attachments. For a solution, look to upcoming versions of this class. // We should autoscroll while trying to drop but don't. // We allow drops of any kind of text on items that have key filters. But filtering // the drops to see if they're really all digits or whatever, and disallow them if they're not, // seems kinda messy to me, and confusing to the user. // cTextDragAndDropAttachment is free for any and all use, // though I wouldn't mind receiving a free, enabled copy of any app that uses it. // Do not distribute modified source code under my name. // No support promised, no liability accepted. Provided "as is". // That said, I can be reached at braun@swipnet.se. // Latest version of this and more at http://home4.swipnet.se/~w-41308/ // Change history: // 1.0 October 3, 1998 First release // 1.1 October 3, 1998 Forgot to include CTYP in 1.0 (duh) // 1.2 October 18, 1998 Rewritten and restructured. Now includes support for LEditFields and LEditTexts. // Drag caret does now blink. Fixed a few bugs. // 1.3 October 18, 1998 Fixed STUPID last-minute-change crashing bug. // 1.3.1 October 19, 1998 Fixed bug where drop area might be initialized to wrong window. // 1.3.2 November 2, 1998 Fixed bugs and cosmetic glitches. Changed things to annoy users. Added demo application. // 1.3.3 November 3, 1998 In the interest of customizability, let cSmartDDattachment store custom hilite insets. // Fixed a cosmetic bug or two in demo application. Boolean cTextDragAndDropAttachment::sAllowDragAndDrop = true; // =========================================================================== // Ä cTextDragAndDropAttachment() // =========================================================================== // Programmatical constructor: Initialize stuff from args. // Ä inAllowTextFileDropping: Set to true to allow drop of files of type 'TEXT'. // Ä inAcceptStyleData: Set to true to make attachment try to use 'styl' (styled text) data in drops. // Ä inCopyDragCursorID: Resource ID of cursor that indicates a drag copy operation. // Ä inDropHilitePaneID: Pane ID of a pane other that host pane that we should use for drag hiliting. // May be any pane in window. Pass zero or PaneIDT_Unspecified to use host pane. // Ä inDoCustomHiliting: Set to true to use hilite region returned by virtual GetDropHiliteRgn() function. // May look better under some circumstances. // Ä inCustomHiliteInset: Default behavior of GetDropHiliteRgn() is to get the drop pane's local frame rect, // inset it by mCustomHiliteInset and make a hollow region out of that. LEditTexts' frame rects include // 3 pixels space for 3D effects and focus borders, so an inCustomHiliteInset value of 3 makes for a nice // drop hilite that doesn't clobber anything. cTextDragAndDropAttachment::cTextDragAndDropAttachment( Boolean inAllowTextFileDropping, Boolean inAcceptStyleData, ResIDT inCopyDragCursorID, PaneIDT inDropHilitePaneID, Boolean inDoCustomHiliting, Int16 inCustomHiliteInset) : LAttachment(msg_AnyMessage, false), LDragAndDrop(UQDGlobals::GetCurrentPort(), NULL){ mAllowTextFileDropping = inAllowTextFileDropping; mAcceptStyleData = inAcceptStyleData, mCopyDragCursorID = inCopyDragCursorID; mDropHilitePaneID = inDropHilitePaneID; mDoCustomHiliting = inDoCustomHiliting; mCustomHiliteInset = inCustomHiliteInset; mLastDragCaretOffset = 0; mLastDragCaretTime = 0; mDragCaretState = eDragCaretNotDrawn; mCurrDragTask = NULL; mTextEditPane = NULL; } // =========================================================================== // Ä cTextDragAndDropAttachment() // =========================================================================== // Stream constructor. cTextDragAndDropAttachment::cTextDragAndDropAttachment(LStream *inStream) : LAttachment(inStream), LDragAndDrop(UQDGlobals::GetCurrentPort(), NULL){ *inStream >> mAllowTextFileDropping >> mAcceptStyleData >> mCopyDragCursorID >> mDropHilitePaneID >> mDoCustomHiliting >> mCustomHiliteInset; mLastDragCaretOffset = 0; mLastDragCaretTime = 0; mDragCaretState = eDragCaretNotDrawn; mCurrDragTask = NULL; mTextEditPane = dynamic_cast(mOwnerHost); ThrowIfNil_(mTextEditPane); SetMessage(msg_AnyMessage); mPane = GetDropPane(); // Don't throw an exception, since view hierarchy may not be up yet. // We'll get a couple more chances. } // =========================================================================== // Ä SetOwnerHost() // =========================================================================== // Save a pointer to whatever sort of LPane object mOwnerHost is. // This only gets called if AddAttachment() is used. void cTextDragAndDropAttachment::SetOwnerHost(LAttachable* inHost){ LAttachment::SetOwnerHost(inHost); mTextEditPane = dynamic_cast(mOwnerHost); ThrowIfNil_(mTextEditPane); mPane = GetDropPane(); ThrowIfNil_(mPane); } // =========================================================================== // Ä ExecuteSelf() // =========================================================================== // We trap msg_Click and msg_AdjustCursor. Note that because of the way Power Plant works, // there's no way to do "delay select" dragging (dragging from a pane in an inactive window) via an attachment. // (Well, actually we could hook up another attachment to the LEventDispatcher and do a lot of double processing, // but that'll have to wait for now). void cTextDragAndDropAttachment::ExecuteSelf( MessageT inMessage, void *ioParam){ // Since there's no such thing as FinishCreateSelf() for attachments, // this is the only place where we can be reasonably sure of the view hierarchy's being established. // So this is the last chance to initialize the LDragAndDrop mPane. if(!mPane){ mPane = GetDropPane(); ThrowIfNil_(mPane); } switch(inMessage){ case msg_AdjustCursor: // If we drag'n'drop, call AdjustCursor() to get an arrow cursor if mouse passes over selected text. if(sAllowDragAndDrop && DragAndDropIsPresent()) SetExecuteHost(AdjustCursor(*(EventRecord*)ioParam) == false); else SetExecuteHost(true); break; case msg_Click: SMouseDownEvent mouseEvent = *(SMouseDownEvent*)ioParam; // If we drag'n'drop... if(sAllowDragAndDrop && DragAndDropIsPresent()) { LCommander* TECommander = dynamic_cast(mTextEditPane); ThrowIfNil_(TECommander); LCommander::SwitchTarget(TECommander); // If it's a click in selection... if(PointInTEHiliteRgn(mouseEvent.whereLocal) && ::WaitMouseMoved(mouseEvent.macEvent.where)) { mLastDragCaretOffset = eDragCaretBadOffset; mDragCaretState = eDragCaretNotDrawn; mLastDragCaretTime = ::TickCount(); // Set up a drag task and do our dragging cTextEditDragTask theDragTask(this, mouseEvent); // mCurrDragTask is used to later indicate whether drag originated within "our" pane mCurrDragTask = &theDragTask; theDragTask.DoDrag(); mCurrDragTask = NULL; // Delete text that's dragged to trash if(theDragTask.DropLocationIsFinderTrash()) DeleteDraggedSelection(); SetExecuteHost(false); } else SetExecuteHost(true); } // Execute host if we don't drag and drop else SetExecuteHost(true); break; default: SetExecuteHost(true); } } // =========================================================================== // Ä HiliteDropArea() // =========================================================================== // Override to provide custom hilighting behavior, meaning in that case we call ShowDragHilite() // on the region we get from GetDropHiliteRgn(), which may be customized to suit. void cTextDragAndDropAttachment::HiliteDropArea( DragReference inDragRef){ if(mDoCustomHiliting){ StRegion hiliteRgn(GetDropHiliteRgn(inDragRef)); StColorPenState::Normalize(); ::ShowDragHilite(inDragRef, hiliteRgn, true); } else LDragAndDrop::HiliteDropArea(inDragRef); } // =========================================================================== // Ä UnhiliteDropArea() // =========================================================================== // Override to remove drag caret before unhiliting void cTextDragAndDropAttachment::UnhiliteDropArea( DragReference inDragRef){ if(mDragCaretState == eDragCaretDrawn) DrawDragCaret(mLastDragCaretOffset); mDragCaretState = eDragCaretNotDrawn; mPane->FocusDraw(); if(mDoCustomHiliting){ StColorPenState::Normalize(); ::HideDragHilite(inDragRef); } else LDragAndDrop::UnhiliteDropArea(inDragRef); } // =========================================================================== // Ä EnterDropArea() // =========================================================================== // Set up our caret drawing flag to indicate we haven't drawn a caret yet. void cTextDragAndDropAttachment::EnterDropArea( DragReference inDragRef, Boolean inDragHasLeftSender){ LDragAndDrop::EnterDropArea(inDragRef, inDragHasLeftSender); mDragCaretState = eDragCaretNotDrawn; mLastDragCaretTime = 0; } // =========================================================================== // Ä InsideDropArea() // =========================================================================== // Update caret position and adjust drag cursor. void cTextDragAndDropAttachment::InsideDropArea( DragReference inDragRef){ LDragAndDrop::InsideDropArea(inDragRef); UpdateDragCaret(inDragRef); AdjustDragCursor(inDragRef); } // =========================================================================== // Ä LeaveDropArea() // =========================================================================== // Erase caret, if still visible. void cTextDragAndDropAttachment::LeaveDropArea( DragReference inDragRef){ if(mDragCaretState == eDragCaretDrawn) DrawDragCaret(mLastDragCaretOffset); mDragCaretState = eDragCaretNotDrawn; LDragAndDrop::LeaveDropArea(inDragRef); } // =========================================================================== // Ä ItemIsAcceptable() // =========================================================================== // If we drag'n'drop, accept text drags, // and accept text file drags if mAllowTextFileDropping is true. Boolean cTextDragAndDropAttachment::ItemIsAcceptable( DragReference inDragRef, ItemReference inItemRef){ Boolean result = false; FlavorFlags theFlags; if(sAllowDragAndDrop){ // It's a 'TEXT' drag if(::GetFlavorFlags(inDragRef, inItemRef, eTextDragItemFlavor, &theFlags) == noErr) result = true; else if(mAllowTextFileDropping && (::GetFlavorFlags(inDragRef, inItemRef, flavorTypeHFS, &theFlags) == noErr)){ // It's a file drag: See if it's a text file. Size theDataSize = 0; HFSFlavor theFileData; ThrowIfOSErr_( ::GetFlavorDataSize( inDragRef, inItemRef, flavorTypeHFS, &theDataSize)); ThrowIfOSErr_( ::GetFlavorData( inDragRef, inItemRef, flavorTypeHFS, &theFileData, &theDataSize, 0 )); result = (theFileData.fileType == eTextDragItemFlavor); } } return result; } // =========================================================================== // Ä DoDragReceive() // =========================================================================== // We override to check whether drag came from another pane or was dropped outside of selection. // We also post an action. This will throw an exception if mOwnerHost isn't an LCommander. void cTextDragAndDropAttachment::DoDragReceive( DragReference inDragRef){ Point thePoint; ::GetDragMouse( inDragRef, &thePoint, nil ); // This line shouldn't be necessary but seems to work wonders UnhiliteDropArea(inDragRef); ThrowIfNil_(mTextEditPane); mTextEditPane->FocusDraw(); mTextEditPane->GlobalToPortPoint(thePoint); mTextEditPane->PortToLocalPoint(thePoint); // Receive drag always if it came from outside, but if it came from our pane, // only if it landed outside selection region. if(!mCurrDragTask || !PointInTEHiliteRgn(thePoint)){ LCommander* TECommander = dynamic_cast(mTextEditPane); ThrowIfNil_(TECommander); TECommander->PostAction(new cTextEditDropAction(this, inDragRef)); } } // =========================================================================== // Ä ReceiveDragItem() // =========================================================================== // Process a drop. void cTextDragAndDropAttachment::ReceiveDragItem( DragReference inDragRef, DragAttributes inDragAttrs, ItemReference inItemRef, Rect &inItemBounds){ TEHandle theTEH = GetMacTEH(); ThrowIfNil_(theTEH); Handle textData = NULL; StScrpHandle stylData = NULL; Int16 currSelStart = (*theTEH)->selStart, currSelEnd = (*theTEH)->selEnd; Size hSize = 0; Boolean deleteSelection = mCurrDragTask && !CheckForOptionKey(inDragRef); GetTextDropData(inDragRef, inItemRef, textData, stylData); hSize = ::GetHandleSize(textData); // Delete selection unless it's a copy operation or a drag from outside if(deleteSelection){ // ::TEDelete() will flash. Put it outside the screen for a while. ::OffsetRect(&(*theTEH)->viewRect, -5000, -5000); ::TEDelete(theTEH); ::OffsetRect(&(*theTEH)->viewRect, 5000, 5000); // If the drop was after now deleted selection, adjust mLastDragCaretOffset if(mLastDragCaretOffset > currSelStart) mLastDragCaretOffset -= (currSelEnd - currSelStart); } // Set insertion point to where drag landed (*theTEH)->selStart = mLastDragCaretOffset, (*theTEH)->selEnd = mLastDragCaretOffset; ReceiveDroppedText(&**textData, hSize, stylData); // Cleanup if(textData) ::DisposeHandle(textData); if(stylData) ::DisposeHandle((Handle)stylData); } // =========================================================================== // Ä MakeDragRegion() // =========================================================================== // Get region we should drag. outValidRgn must be an allocated region. void cTextDragAndDropAttachment::MakeDragRegion(RgnHandle outValidRgn){ StRegion hiliteRgn; GetTEHiliteRgn(hiliteRgn); // Make region hollow ::CopyRgn(hiliteRgn, outValidRgn); ::InsetRgn(hiliteRgn, 1, 1); ::DiffRgn(outValidRgn, hiliteRgn, outValidRgn); Point pt1 = *(Point*)&(*outValidRgn)->rgnBBox, pt2 = pt1; // Convert region to global coordinates ThrowIfNil_(mTextEditPane); mTextEditPane->LocalToPortPoint(pt2); mTextEditPane->PortToGlobalPoint(pt2); ::OffsetRgn (outValidRgn, pt2.h - pt1.h, pt2.v - pt1.v); } // =========================================================================== // Ä GetDropPane() // =========================================================================== // Get pointer to the pane that should act as drop area. // If mDropHilitePane is a valid ID, we try to find pane in window. // Fall back on mOwnerHost. // Note that when attachments are created, the view hierarchy will often be incomplete. // If you call this without making sure the view hierarchy IS complete, you'll get funny results. LPane* cTextDragAndDropAttachment::GetDropPane(void){ LPane *thePane = dynamic_cast(mOwnerHost); LView *superView = NULL, *nextSuper = NULL; // If we're not attached to a pane, something is very wrong. ThrowIfNil_(thePane); if(mDropHilitePaneID != PaneIDT_Unspecified && mDropHilitePaneID != 0){ for(;;){ superView = thePane->GetSuperView(); if(superView) thePane = superView; else break; } thePane = thePane->FindPaneByID(mDropHilitePaneID); } return thePane; } // =========================================================================== // Ä CheckForOptionKey() // =========================================================================== // Return true if option key was pressed at beginning or end of drag Boolean cTextDragAndDropAttachment::CheckForOptionKey(DragReference inDragRef){ Int16 theModifiersNow; Int16 theModifiersAtMouseDown; Int16 theModifiersAtMouseUp; ::GetDragModifiers( inDragRef, &theModifiersNow, &theModifiersAtMouseDown, &theModifiersAtMouseUp ); return ( (theModifiersAtMouseDown & optionKey) != 0 ) || ( (theModifiersAtMouseUp & optionKey) != 0 ); } // =========================================================================== // Ä GetTextDropData() // =========================================================================== // Copy text and 'styl' data into provided, unallocated handles. // Caller is responsible for disposing of handles. Handles may be NULL. void cTextDragAndDropAttachment::GetTextDropData( DragReference inDragRef, ItemReference inItemRef, Handle& outNewText, StScrpHandle& outNewStyles){ FlavorFlags theFlags; outNewText = NULL; outNewStyles = NULL; // It's a text drop if( ::GetFlavorFlags( inDragRef, inItemRef, eTextDragItemFlavor, &theFlags ) == noErr){ Size textDataSize = 0, stylDataSize = 0; // Ho¥w much text is there? ThrowIfOSErr_( ::GetFlavorDataSize( inDragRef, inItemRef, eTextDragItemFlavor, &textDataSize ) ); // Allocate a handle outNewText = ::NewHandleClear(textDataSize); ThrowIfNil_(outNewText); // Copy dropped text into handle ThrowIfOSErr_( ::GetFlavorData( inDragRef, inItemRef, eTextDragItemFlavor, &**outNewText, &textDataSize, 0 ) ); // Get style data, if we should and if any if(mAcceptStyleData && ::GetFlavorDataSize( inDragRef, inItemRef, eStyleDragItemFlavor, &stylDataSize ) == noErr){ // Allocate a StScrpHandle outNewStyles = (StScrpHandle)::NewHandleClear(stylDataSize); ThrowIfNil_(outNewStyles); ThrowIfOSErr_( ::GetFlavorData( inDragRef, inItemRef, eStyleDragItemFlavor, &**outNewStyles, &stylDataSize, 0 ) ); } } // It's a text file drop else if(::GetFlavorFlags( inDragRef, inItemRef, flavorTypeHFS, &theFlags ) == noErr){ Size theDataSize = 0; HFSFlavor theFileData; ThrowIfOSErr_( ::GetFlavorDataSize( inDragRef, inItemRef, flavorTypeHFS, &theDataSize)); ThrowIfOSErr_( ::GetFlavorData( inDragRef, inItemRef, flavorTypeHFS, &theFileData, &theDataSize, 0 )); LFile theFile(theFileData.fileSpec); // Get the text from the file ThrowIf_(theFile.OpenDataFork(fsRdPerm) == -1); outNewText = theFile.ReadDataFork(); theFile.CloseDataFork(); try{ // Get ¥styl' 128 resource, if any ThrowIf_(theFile.OpenResourceFork(fsRdPerm) == -1); outNewStyles = (StScrpHandle)::Get1Resource(eStyleDragItemFlavor, 128); if(outNewStyles) ::DetachResource((Handle)outNewStyles); theFile.CloseResourceFork(); } catch(...){ // We probably get here because file doesn't have any resource fork } } } // =========================================================================== // Ä GetDropHiliteRgn() // =========================================================================== // Called by HiliteDropArea() if mDoCustomHiliting is true. // Default behavior is to create a 3 pixel hollow frame, which works a lot better // with ShowDragHilite() than LDragAndDrop's default behavior. RgnHandle cTextDragAndDropAttachment::GetDropHiliteRgn( DragReference inDragRef){ RgnHandle hiliteRgn = ::NewRgn(); Rect frameRect; mPane->CalcLocalFrameRect(frameRect); ::InsetRect(&frameRect, mCustomHiliteInset, mCustomHiliteInset); ::OpenRgn(); ::FrameRect(&frameRect); ::InsetRect(&frameRect, 3, 3); ::FrameRect(&frameRect); ::CloseRgn(hiliteRgn); return hiliteRgn; } // =========================================================================== // Ä AdjustCursor() // =========================================================================== // If cursor passes over selection region, change it to an arrow. // Return whether or not we adjusted the cursor. Boolean cTextDragAndDropAttachment::AdjustCursor(EventRecord& inMacEvent){ Boolean result = false; Point thePt = inMacEvent.where; ThrowIfNil_(mTextEditPane); mTextEditPane->GlobalToPortPoint(thePt); mTextEditPane->PortToLocalPoint(thePt); if(PointInTEHiliteRgn(thePt)){ UCursor::SetArrow(); result = true; } return result; } // =========================================================================== // Ä AdjustDragCursor() // =========================================================================== // Set up a "drag copy" cursor if drag is a copy operation. void cTextDragAndDropAttachment:: AdjustDragCursor(DragReference inDragRef){ Int16 modsNow, originalMods; ::GetDragModifiers( inDragRef, &modsNow, &originalMods, NULL); // Set cursor for either "copy drag" cursor or arrow if(!mCurrDragTask || modsNow & optionKey || originalMods & optionKey) UCursor::SetTheCursor(mCopyDragCursorID); else UCursor::SetArrow(); } // =========================================================================== // Ä UpdateDragCaret() // =========================================================================== // Draw a caret to indicate where drop would take place. void cTextDragAndDropAttachment::UpdateDragCaret(DragReference inDragRef){ Int16 currOffset = 0; Point thePoint; TEHandle theTEH = GetMacTEH(); ThrowIfNil_(theTEH); ThrowIfNil_(mTextEditPane); if ( mTextEditPane->FocusDraw()) { // Get the mouse location and convert to local coordinates. ::GetDragMouse( inDragRef, &thePoint, nil ); mTextEditPane->GlobalToPortPoint(thePoint); mTextEditPane->PortToLocalPoint(thePoint); // If drag is from outside or we're outside of hilite rgn... if(!mCurrDragTask || !PointInTEHiliteRgn(thePoint)){ currOffset = ::TEGetOffset(thePoint, theTEH); // If caret should move to new position... if(currOffset != mLastDragCaretOffset){ // Erase old drag caret, if any if(mDragCaretState == eDragCaretDrawn) DrawDragCaret(mLastDragCaretOffset); // Draw the new caret and remember when we did it mLastDragCaretOffset = currOffset; DrawDragCaret(mLastDragCaretOffset); mDragCaretState = eDragCaretDrawn; mLastDragCaretTime = ::TickCount(); } // If caret shouldn't move, but it's blink time... else if(::TickCount() > (mLastDragCaretTime + ::LMGetCaretTime())){ DrawDragCaret(mLastDragCaretOffset); if(mDragCaretState == eDragCaretDrawn) mDragCaretState = eDragCaretNotDrawn; else mDragCaretState = eDragCaretDrawn; mLastDragCaretTime = ::TickCount(); } } // Erase last drag caret if we're inside selection region else if (mDragCaretState == eDragCaretDrawn){ DrawDragCaret(mLastDragCaretOffset); mDragCaretState = eDragCaretNotDrawn; mLastDragCaretOffset = eDragCaretBadOffset; } } } // =========================================================================== // Ä DrawDragCaret() // =========================================================================== // Draw a caret at position indicated. This code semi-swiped from somewhere. void cTextDragAndDropAttachment::DrawDragCaret(Int16 inOffset){ TEHandle theTEH = GetMacTEH(); Rect clipRect; ThrowIfNil_(mTextEditPane); mTextEditPane->FocusDraw(); mTextEditPane->ApplyForeAndBackColors(); mTextEditPane->CalcLocalFrameRect(clipRect); StClipRgnState clipState(clipRect); StColorPenState savedPen; Point theLoc = ::TEGetPoint(inOffset, theTEH); Int16 theLine = GetLineFromOffset(inOffset, theTEH), lineHeight; if((inOffset == (*theTEH)->teLength) && (*((*theTEH)->hText))[(*theTEH)->teLength - 1] == char_Return) theLoc.v += ::TEGetHeight(theLine, theLine, theTEH); lineHeight = ::TEGetHeight(theLine, theLine, theTEH); ::PenMode(srcXor); ::MoveTo(theLoc.h - 1, theLoc.v - 1); ::LineTo(theLoc.h - 1, (theLoc.v - 1) - lineHeight); } // =========================================================================== // Ä PointInTEHiliteRgn() // =========================================================================== // Return true if inWhereLocal is in TextEdit highlight region. Boolean cTextDragAndDropAttachment::PointInTEHiliteRgn(Point inWhereLocal){ StRegion hiliteRgn; GetTEHiliteRgn(hiliteRgn); return ::PtInRgn(inWhereLocal, hiliteRgn); } // =========================================================================== // Ä GetTEHiliteRgn() // =========================================================================== // Pass in a valid RgnHandle Allocated by ::NewRgn(). Caller remains responsible for disposing of region. void cTextDragAndDropAttachment::GetTEHiliteRgn(RgnHandle outValidRgn){ TEHandle theTEH = GetMacTEH(); ThrowIfNil_(theTEH); ThrowIfNil_(mTextEditPane); mTextEditPane->FocusDraw(); // If ::TEGetHiliteRgn() exists, call it if(UEnvironment::HasGestaltAttribute( gestaltTEAttr, gestaltTEHasGetHiliteRgn)) ::TEGetHiliteRgn (outValidRgn, theTEH); // Otherwise, manufacture our own hilite region, if there is a selection else if((*theTEH)->selStart != (*theTEH)->selEnd){ StRegion rectRgn; Point startPt, endPt; Rect frameRect, lineRect = {0, 0, 0, 0}; mTextEditPane->CalcLocalFrameRect(frameRect); for(int i = GetLineFromOffset((*theTEH)->selStart, theTEH); i <= GetLineFromOffset((*theTEH)->selEnd, theTEH); i++){ // GetLineFromOffset() returns a one-based result. // Decrement it for use as lineStarts array index. if((*theTEH)->selStart > (*theTEH)->lineStarts[i - 1]) // This line's selection starts somewhere into line startPt = ::TEGetPoint((*theTEH)->selStart, theTEH); else // This line's selection starts where line starts startPt = ::TEGetPoint((*theTEH)->lineStarts[i - 1], theTEH); if((*theTEH)->selEnd < (*theTEH)->lineStarts[i]) // This line's selection ends somewhere before line ends endPt = ::TEGetPoint((*theTEH)->selEnd, theTEH); else{ // This line's selection ends where line ends endPt = ::TEGetPoint((*theTEH)->lineStarts[i] - 1, theTEH); endPt.h = frameRect.right; } lineRect.left = startPt.h; lineRect.right = endPt.h; lineRect.bottom = frameRect.top + ::TEGetHeight(1, i, theTEH); lineRect.top = lineRect.bottom - ::TEGetHeight(i, i, theTEH); ::RectRgn(rectRgn, &lineRect); ::UnionRgn(rectRgn, outValidRgn, outValidRgn); } ::OffsetRgn(outValidRgn, -frameRect.left, -frameRect.top); // Clip to mTextEditPane frame ::RectRgn(rectRgn, &frameRect); ::SectRgn (rectRgn, outValidRgn, outValidRgn); } } // =========================================================================== // Ä GetLineFromOffset() // =========================================================================== // Calculate which text line inOffset is in. Result is one-based. Int16 cTextDragAndDropAttachment::GetLineFromOffset(Int16 inOffset, TEHandle inTEhandle){ Int16 result = 0; ThrowIfNil_(inTEhandle); if(inOffset > (*inTEhandle)->teLength) result = (*inTEhandle)->nLines; else{ while((*inTEhandle)->lineStarts[result] < inOffset) result++; } return result; } // =========================================================================== // Ä AddDragFlavors() // =========================================================================== // Put text and styles of current selection into inDragRef. // If TEHandle returned by GetMacTEH() is monostyled, make a bogus StScrpHandle with at least some information. void cTextDragAndDropAttachment::AddDragFlavors( DragReference inDragRef){ TEHandle theTEH = GetMacTEH(); ThrowIfNil_(theTEH); StScrpHandle styleHandle = NULL; char* ptr = &**(*theTEH)->hText; ::AddDragItemFlavor(inDragRef, 1, eTextDragItemFlavor, &ptr[(*theTEH)->selStart], (*theTEH)->selEnd - (*theTEH)->selStart, 0L); styleHandle = ::TEGetStyleScrapHandle(theTEH); // If we're not multistyled, make a phony style handle // approximating whatever we show the text in if(!styleHandle && (*theTEH)->txSize != -1) // txSize is -1 if TEHandle is multistyled styleHandle = MakePhonyStScrpHandle(theTEH); if(styleHandle){ ptr = (char*) &**styleHandle; ::AddDragItemFlavor(inDragRef, 1, eStyleDragItemFlavor, ptr, ::GetHandleSize((Handle)styleHandle), 0L); ::DisposeHandle((Handle)styleHandle); } } // =========================================================================== // Ä MakePhonyStScrpHandle() // =========================================================================== // Make up a StScrpHandle for monostyled text from values in TEHandle, // which must be monostyled (allocated by ::TENew(), not ::TEStyleNew()), or we get garbage. // Caller is responsible for deallocating StScrpHandle. StScrpHandle cTextDragAndDropAttachment::MakePhonyStScrpHandle(TEHandle inMonostyledTEhandle){ StScrpHandle styleHandle = (StScrpHandle)::NewHandleClear(sizeof(short) + sizeof(ScrpSTElement)); ThrowIfNil_(styleHandle); (*styleHandle)->scrpNStyles = 1; (*styleHandle)->scrpStyleTab[0].scrpHeight = (*inMonostyledTEhandle)->lineHeight, (*styleHandle)->scrpStyleTab[0].scrpAscent = (*inMonostyledTEhandle)->fontAscent, (*styleHandle)->scrpStyleTab[0].scrpFont = (*inMonostyledTEhandle)->txFont, (*styleHandle)->scrpStyleTab[0].scrpFace = (*inMonostyledTEhandle)->txFace, (*styleHandle)->scrpStyleTab[0].scrpSize = (*inMonostyledTEhandle)->txSize; // (The other fields are set to zero by ::NewHandleClear()) return styleHandle; } #pragma mark - // =========================================================================== // Ä cTextEditViewDDattachment() // =========================================================================== // Pass args to cTextDragAndDropAttachment, NULL mTEview ptr cTextEditViewDDattachment::cTextEditViewDDattachment( Boolean inAllowTextFileDropping, Boolean inAcceptStyleData, ResIDT inCopyDragCursorID, PaneIDT inDropHilitePaneID, Boolean inDoCustomHiliting, Int16 inCustomHiliteInset) : cTextDragAndDropAttachment( inAllowTextFileDropping, inAcceptStyleData, inCopyDragCursorID, inDropHilitePaneID, inDoCustomHiliting, inCustomHiliteInset){ mTEview = NULL; } // =========================================================================== // Ä cTextEditViewDDattachment() // =========================================================================== // Stream constructor. Throw an exception if host is not an LTextEditView. cTextEditViewDDattachment::cTextEditViewDDattachment(LStream *inStream) : cTextDragAndDropAttachment(inStream){ mTEview = dynamic_cast(mOwnerHost); ThrowIfNil_(mTEview); } // =========================================================================== // Ä SetOwnerHost() // =========================================================================== // Throw an exception if new host is not an LTextEditView. // This only gets called if AddAttachment() is used. void cTextEditViewDDattachment::SetOwnerHost(LAttachable* inHost){ cTextDragAndDropAttachment::SetOwnerHost(inHost); mTEview = dynamic_cast(mOwnerHost); ThrowIfNil_(mTEview); } // =========================================================================== // Ä ItemIsAcceptable() // =========================================================================== // Drops are only acceptable if LTextEditView is editable. Boolean cTextEditViewDDattachment::ItemIsAcceptable( DragReference inDragRef, ItemReference inItemRef){ ThrowIfNil_(mTEview); return (mTEview->HasAttribute(textAttr_Editable) && cTextDragAndDropAttachment::ItemIsAcceptable(inDragRef, inItemRef)); } // =========================================================================== // Ä ReceiveDroppedText() // =========================================================================== // Process text dropped at insertion point already set up by caller. // We customize this func according to what kind of TextEdit object we have. void cTextEditViewDDattachment::ReceiveDroppedText(char* inText, Int16 inTextLength, StScrpHandle inStyles){ Rect oldDestRect; TEHandle theTEH = GetMacTEH(); ThrowIfNil_(theTEH); ThrowIfNil_(mTEview); mTEview->FocusDraw(); mTEview->AlignTextEditRects(); oldDestRect = (*theTEH)->destRect; mTEview->Insert(inText, inTextLength, inStyles, false); mTEview->UserChangedText(); // Select text dropped. ::TESetSelect(mLastDragCaretOffset, mLastDragCaretOffset + inTextLength, theTEH); mTEview->ForceAutoScroll( oldDestRect ); mTEview->Draw(nil); mTEview->DontRefresh(); } // =========================================================================== // Ä MakeDragRegion() // =========================================================================== // Copy region we should drag into outValidRgn, which must be already allocated. // Overridden to provide drag behavior for nonselectable LTextEditViews. void cTextEditViewDDattachment::MakeDragRegion(RgnHandle outValidRgn){ TEHandle theTEH = GetMacTEH(); ThrowIfNil_(theTEH); Int16 oldSelStart = (*theTEH)->selStart, oldSelEnd = (*theTEH)->selEnd; // Enable dragging from nonselectable LTextEditView // by pretending user wants to drag all the text if(!mTEview->HasAttribute(textAttr_Selectable)){ (*theTEH)->selStart = 0, (*theTEH)->selEnd = (*theTEH)->teLength; } cTextDragAndDropAttachment:: MakeDragRegion(outValidRgn); // If we selected all in nonselectable LTextEditView, restore old selection if(!mTEview->HasAttribute(textAttr_Selectable)){ (*theTEH)->selStart = oldSelStart, (*theTEH)->selEnd = oldSelEnd; } } // =========================================================================== // Ä AddDragFlavors() // =========================================================================== // If LTextEditView is not selectable, temporarily fake-select the whole thing to enable dragging void cTextEditViewDDattachment::AddDragFlavors( DragReference inDragRef){ ThrowIfNil_(mTEview); TEHandle theTEH = GetMacTEH(); ThrowIfNil_(theTEH); Int16 oldSelStart = (*theTEH)->selStart, oldSelEnd = (*theTEH)->selEnd; // Enable dragging from nonselectable LTextEditView // by pretending user wants to drag all the text if(!mTEview->HasAttribute(textAttr_Selectable)){ (*theTEH)->selStart = 0, (*theTEH)->selEnd = (*theTEH)->teLength; } cTextDragAndDropAttachment::AddDragFlavors(inDragRef); // Restore old selection if(!mTEview->HasAttribute(textAttr_Selectable)){ (*theTEH)->selStart = oldSelStart, (*theTEH)->selEnd = oldSelEnd; } } // =========================================================================== // Ä DeleteDraggedSelection() // =========================================================================== // Have LTextEditView post a LTEViewClearAction. void cTextEditViewDDattachment::DeleteDraggedSelection(void){ mTEview->PostAnAction(new LTEViewClearAction(mTEview->GetMacTEH(), mTEview, mTEview)); mTEview->UserChangedText(); } // =========================================================================== // Ä GetMacTEH() // =========================================================================== // Return LTextEditView's TEHandle TEHandle cTextEditViewDDattachment::GetMacTEH(void){ ThrowIfNil_(mTEview); return mTEview->GetMacTEH(); } // =========================================================================== // Ä PointInTEHiliteRgn() // =========================================================================== // Always returns true if LTextEditView is not selectable, enabling convenient dragging of nonselectable text. Boolean cTextEditViewDDattachment::PointInTEHiliteRgn(Point inWhereLocal){ Boolean result = false; ThrowIfNil_(mTEview); // If not selectable, pretend user always clicks in selection if(!mTEview->HasAttribute(textAttr_Selectable)) result = true; else result = cTextDragAndDropAttachment::PointInTEHiliteRgn(inWhereLocal); return result; } #pragma mark - // =========================================================================== // Ä cEditFieldDDattachment() // =========================================================================== // Pass args to cTextDragAndDropAttachment, NULL mEditField ptr cEditFieldDDattachment::cEditFieldDDattachment( Boolean inAllowTextFileDropping, Boolean inAcceptStyleData, ResIDT inCopyDragCursorID, PaneIDT inDropHilitePaneID, Boolean inDoCustomHiliting, Int16 inCustomHiliteInset) : cTextDragAndDropAttachment( inAllowTextFileDropping, inAcceptStyleData, inCopyDragCursorID, inDropHilitePaneID, inDoCustomHiliting, inCustomHiliteInset){ mEditField = NULL; } // =========================================================================== // Ä cEditFieldDDattachment() // =========================================================================== // Stream constructor. Throw an exception if host is not an LEditField. cEditFieldDDattachment::cEditFieldDDattachment(LStream *inStream) : cTextDragAndDropAttachment(inStream){ mEditField = dynamic_cast(mOwnerHost); ThrowIfNil_(mEditField); } // =========================================================================== // Ä SetOwnerHost() // =========================================================================== // Throw an exception if new host is not an LEditField. // This only gets called if AddAttachment() is used. void cEditFieldDDattachment::SetOwnerHost(LAttachable* inHost){ cTextDragAndDropAttachment::SetOwnerHost(inHost); mEditField = dynamic_cast(mOwnerHost); ThrowIfNil_(mEditField); } // =========================================================================== // Ä ReceiveDroppedText() // =========================================================================== void cEditFieldDDattachment::ReceiveDroppedText(char* inText, Int16 inTextLength, StScrpHandle inStyles){ TEHandle theTEH = GetMacTEH(); ThrowIfNil_(theTEH); ThrowIfNil_(mEditField); if((*theTEH)->teLength + inTextLength < max_Int16){ ::TEInsert(inText, inTextLength, theTEH); // Select text dropped. ::TESetSelect(mLastDragCaretOffset, mLastDragCaretOffset + inTextLength, theTEH); ::TESelView(theTEH); //mEditField->Draw(nil); mEditField->UserChangedText(); } else{ SignalPStr_("\pText dropped > max_Int16!"); ::SysBeep(30); } } // =========================================================================== // Ä DeleteDraggedSelection() // =========================================================================== // Have LEditField post a LTEClearAction. void cEditFieldDDattachment::DeleteDraggedSelection(void){ ThrowIfNil_(mEditField); mEditField->PostAnAction(new LTEClearAction(mEditField->GetMacTEH(), mEditField, mEditField)); mEditField->UserChangedText(); } // =========================================================================== // Ä GetMacTEH() // =========================================================================== // Return LEditField's TEHandle TEHandle cEditFieldDDattachment::GetMacTEH(void){ ThrowIfNil_(mEditField); return mEditField->GetMacTEH(); } #pragma mark - // =========================================================================== // Ä cEditTextDDattachment() // =========================================================================== // Pass args to cTextDragAndDropAttachment, NULL mEditText ptr cEditTextDDattachment::cEditTextDDattachment( Boolean inAllowTextFileDropping, Boolean inAcceptStyleData, ResIDT inCopyDragCursorID, PaneIDT inDropHilitePaneID, Boolean inDoCustomHiliting, Int16 inCustomHiliteInset) : cTextDragAndDropAttachment( inAllowTextFileDropping, inAcceptStyleData, inCopyDragCursorID, inDropHilitePaneID, inDoCustomHiliting, inCustomHiliteInset){ mEditText = NULL; } // =========================================================================== // Ä cEditTextDDattachment() // =========================================================================== // Stream constructor. Throw an exception if host is not an LEditText. cEditTextDDattachment::cEditTextDDattachment(LStream *inStream) : cTextDragAndDropAttachment(inStream){ mEditText = dynamic_cast(mOwnerHost); ThrowIfNil_(mEditText); } // =========================================================================== // Ä SetOwnerHost() // =========================================================================== // Throw an exception if new host is not an LEditText. // This only gets called if AddAttachment() is used. void cEditTextDDattachment::SetOwnerHost(LAttachable* inHost){ cTextDragAndDropAttachment::SetOwnerHost(inHost); mEditText = dynamic_cast(mOwnerHost); ThrowIfNil_(mEditText); } // =========================================================================== // Ä ReceiveDroppedText() // =========================================================================== void cEditTextDDattachment::ReceiveDroppedText(char* inText, Int16 inTextLength, StScrpHandle inStyles){ TEHandle theTEH = GetMacTEH(); ThrowIfNil_(theTEH); ThrowIfNil_(mEditText); if((*theTEH)->teLength + inTextLength < max_Int16){ ::TEInsert(inText, inTextLength, theTEH); // Select text dropped. ::TESetSelect(mLastDragCaretOffset, mLastDragCaretOffset + inTextLength, theTEH); ::TESelView(theTEH); //mEditText->Refresh(); mEditText->UserChangedText(); } else{ SignalPStr_("\pText dropped > max_Int16!"); ::SysBeep(30); } } // =========================================================================== // Ä DeleteDraggedSelection() // =========================================================================== // Have LEditText post a LTEClearAction. void cEditTextDDattachment::DeleteDraggedSelection(void){ ThrowIfNil_(mEditText); mEditText->PostAnAction(new LTEClearAction(mEditText->GetMacTEH(), mEditText, mEditText)); mEditText->UserChangedText(); } // =========================================================================== // Ä GetMacTEH() // =========================================================================== // Return LEditText's TEHandle TEHandle cEditTextDDattachment::GetMacTEH(void){ ThrowIfNil_(mEditText); return mEditText->GetMacTEH(); } #pragma mark - // =========================================================================== // Ä cSmartTextDDAttachment() // =========================================================================== // Pass args to cTextDragAndDropAttachment cSmartTextDDAttachment:: cSmartTextDDAttachment( Boolean inAllowTextFileDropping, Boolean inAcceptStyleData, ResIDT inCopyDragCursorID, PaneIDT inDropHilitePaneID, Boolean inDoCustomHiliting, Int16 inTextEditViewCustomHiliteInset, Int16 inEditFieldCustomHiliteInset, Int16 inEditTextCustomHiliteInset) : cTextDragAndDropAttachment(inAllowTextFileDropping, inAcceptStyleData, inCopyDragCursorID, inDropHilitePaneID, inDoCustomHiliting, 0){ mTextEditViewCustomHiliteInset = inTextEditViewCustomHiliteInset, mEditFieldCustomHiliteInset = inEditFieldCustomHiliteInset, mEditTextCustomHiliteInset = inEditTextCustomHiliteInset; } // =========================================================================== // Ä cSmartTextDDAttachment() // =========================================================================== // Default stream constructor. Pass args to cTextDragAndDropAttachment, add drag and drop to host, then delete ourselves. cSmartTextDDAttachment:: cSmartTextDDAttachment(LStream *inStream) : cTextDragAndDropAttachment(inStream){ *inStream >> mTextEditViewCustomHiliteInset >> mEditFieldCustomHiliteInset >> mEditTextCustomHiliteInset; AddDragAndDropAttachment(mOwnerHost); delete this; } // =========================================================================== // Ä cSmartTextDDAttachment() // =========================================================================== // Alternate stream constructor where we don't do anything. cSmartTextDDAttachment:: cSmartTextDDAttachment(LStream *inStream, Boolean inDummy) : cTextDragAndDropAttachment(inStream){ *inStream >> mTextEditViewCustomHiliteInset >> mEditFieldCustomHiliteInset >> mEditTextCustomHiliteInset; } // =========================================================================== // Ä SetOwnerHost() // =========================================================================== // Add drag and drop to host, then delete ourselves. // This only gets called if AddAttachment() is used. void cSmartTextDDAttachment:: SetOwnerHost(LAttachable* inHost){ cTextDragAndDropAttachment::SetOwnerHost(inHost); AddDragAndDropAttachment(mOwnerHost); delete this; } // =========================================================================== // Ä AddDragAndDropAttachment() // =========================================================================== // We check if inAttachable is an LTextEditView, an LEditField or an LEditText, and add the appropriate attachment. void cSmartTextDDAttachment::AddDragAndDropAttachment(LAttachable* inAttachable){ ThrowIfNil_(inAttachable); LTextEditView* theTEview = dynamic_cast(inAttachable); LEditField* theEditField = dynamic_cast(inAttachable); LEditText* theEditText = dynamic_cast(inAttachable); LPane* thePane = dynamic_cast(inAttachable); // Set the port to this window if(thePane) thePane->FocusDraw(); if(theTEview) theTEview->AddAttachment(new cTextEditViewDDattachment( mAllowTextFileDropping, mAcceptStyleData, mCopyDragCursorID, mDropHilitePaneID, mDoCustomHiliting, mTextEditViewCustomHiliteInset)); else if(theEditField) theEditField->AddAttachment(new cEditFieldDDattachment( mAllowTextFileDropping, mAcceptStyleData, mCopyDragCursorID, mDropHilitePaneID, mDoCustomHiliting, mEditFieldCustomHiliteInset)); else if(theEditText) theEditText->AddAttachment(new cEditTextDDattachment( mAllowTextFileDropping, mAcceptStyleData, mCopyDragCursorID, mDropHilitePaneID, mDoCustomHiliting, mEditTextCustomHiliteInset)); } #pragma mark - // =========================================================================== // Ä cViewTextDDAttachment() // =========================================================================== // Pass args to cSmartTextDDAttachment. cViewTextDDAttachment:: cViewTextDDAttachment( Boolean inAllowTextFileDropping, Boolean inAcceptStyleData, ResIDT inCopyDragCursorID, PaneIDT inDropHilitePaneID, Boolean inDoCustomHiliting) : cSmartTextDDAttachment(inAllowTextFileDropping, inAcceptStyleData, inCopyDragCursorID, inDropHilitePaneID, inDoCustomHiliting){} // =========================================================================== // Ä cViewTextDDAttachment() // =========================================================================== // Pass args to cSmartTextDDAttachment alternate stream constructor, so we don't get deleted cViewTextDDAttachment:: cViewTextDDAttachment(LStream *inStream) : cSmartTextDDAttachment(inStream, false){} // =========================================================================== // Ä SetOwnerHost() // =========================================================================== // Add drag and drop to host, then delete ourselves. // This only gets called if AddAttachment() is used. void cViewTextDDAttachment:: SetOwnerHost(LAttachable* inHost){ cTextDragAndDropAttachment::SetOwnerHost(inHost); ExecuteSelf(msg_Nothing, NULL); } // =========================================================================== // Ä ExecuteSelf() // =========================================================================== // Add drag and drop to host view, then delete ourselves. // This gets called if stream constructor was used. void cViewTextDDAttachment:: ExecuteSelf(MessageT inMessage, void* ioParam){ AddDragAndDropToView(dynamic_cast(mOwnerHost)); SetExecuteHost(true); delete this; } // =========================================================================== // Ä AddDragAndDropToView() // =========================================================================== // Walk through inView's subpanes, add cEditFieldDDattachments/cTextEditViewDDattachment/cEditTextDDattachments as appropriate. // Note that we don't (can't, due to how LAttachable is written) check whether any cTextDragAndDropAttachments already exist. void cViewTextDDAttachment::AddDragAndDropToView(LView* inView){ ThrowIfNil_(inView); TArray* subPanes = &inView->GetSubPanes(); TArrayIterator iterator(*subPanes); LPane *theSub; LView *nextView; while (iterator.Next(theSub)) { AddDragAndDropAttachment(theSub); // Recurse if we have another LView nextView = dynamic_cast(theSub); if(nextView) AddDragAndDropToView(nextView); } } #pragma mark - // =========================================================================== // Ä cTextEditViewDragTask() // =========================================================================== // Drag task used by cTextDragAndDropAttachments. cTextEditDragTask::cTextEditDragTask(cTextDragAndDropAttachment* inDragAndDrop, const SMouseDownEvent &inMouseDown) : LDragTask(inMouseDown.macEvent){ mDDAttachment = inDragAndDrop; } // =========================================================================== // Ä AddFlavors() // =========================================================================== // Put data into inDragRef by calling cTextDragAndDropAttachment::AddDragFlavors(). void cTextEditDragTask::AddFlavors( DragReference inDragRef){ mDDAttachment->AddDragFlavors(inDragRef); } // =========================================================================== // Ä MakeDragRegion() // =========================================================================== // Make drag region by calling cTextDragAndDropAttachment::MakeDragRegion(). void cTextEditDragTask::MakeDragRegion(DragReference inDragRef, RgnHandle inDragRegion){ mDDAttachment->MakeDragRegion(inDragRegion); } #pragma mark - // =========================================================================== // Ä cTextEditDropAction() // =========================================================================== // Action posted by cTextDragAndDropAttachment when drop has arrived. // Provides undo support for drag'n'drop. cTextEditDropAction::cTextEditDropAction(cTextDragAndDropAttachment* inNewOwner, DragReference inDragRef) : LAction(STRx_RedoDrag, str_DragDrop), mSavedUndroppedState(dynamic_cast(inNewOwner->GetOwnerHost()), false), mSavedDroppedState(dynamic_cast(inNewOwner->GetOwnerHost()), false){ mOwner = inNewOwner; mDragRef = inDragRef; mSavedUndroppedState.SaveState(); } // =========================================================================== // Ä RedoSelf() // =========================================================================== // Call LDragAndDrop::DoDragReceive() the first time around, after that simply toggle between saved TextEdit states. void cTextEditDropAction::RedoSelf(){ if(mDragRef){ mOwner->LDragAndDrop::DoDragReceive(mDragRef); mDragRef = NULL; mSavedDroppedState.SaveState(); } else mSavedDroppedState.RestoreState(); } // =========================================================================== // Ä UndoSelf() // =========================================================================== void cTextEditDropAction::UndoSelf(){ mSavedUndroppedState.RestoreState(); } #pragma mark - // =========================================================================== // Ä cSavedTextEditState() // =========================================================================== // Class for saving/restoring TextEdit states (text and selection) for LTextEditViews, LEditFields and LEditTexts. cSavedTextEditState::cSavedTextEditState(LPane* inNewOwner, Boolean inSaveState){ mTEview = dynamic_cast(inNewOwner); mEditField = dynamic_cast(inNewOwner); mEditText = dynamic_cast(inNewOwner); mSavedText = NULL; mSavedStyles = NULL; ThrowIf_(mTEview == NULL && mEditField == NULL && mEditText == NULL); if(inSaveState) SaveState(); } // =========================================================================== // Ä ~cSavedTextEditState() // =========================================================================== // Destructor deallocates handles. cSavedTextEditState::~cSavedTextEditState(void){ DisposeData(); } // =========================================================================== // Ä SaveState() // =========================================================================== // Saves current state of TextEdit object. void cSavedTextEditState::SaveState(void){ TEHandle theTEH = GetMacTEH(); Handle savedTextH = NULL; ThrowIfNil_(theTEH); savedTextH = (*theTEH)->hText; DisposeData(); if(::HandToHand(&savedTextH) == noErr) mSavedText = savedTextH; mSavedSelStart = (*theTEH)->selStart; mSavedSelEnd = (*theTEH)->selEnd; (*theTEH)->selStart = 0; (*theTEH)->selEnd = (*theTEH)->teLength; mSavedStyles = ::TEGetStyleScrapHandle(theTEH); (*theTEH)->selStart = mSavedSelStart; (*theTEH)->selEnd = mSavedSelEnd; } // =========================================================================== // Ä RestoreState() // =========================================================================== // Call appropriate function for different objects. void cSavedTextEditState::RestoreState(void){ if(mTEview) RestoreTEViewState(); else if(mEditField) RestoreEditFieldState(); else if(mEditText) RestoreEditTextState(); } // =========================================================================== // Ä RestoreTEViewState() // =========================================================================== // Restore state of LTextEditView void cSavedTextEditState::RestoreTEViewState(void){ TEHandle theTEH = GetMacTEH(); ThrowIfNil_(theTEH); mTEview->FocusDraw(); Rect oldDestRect = (*theTEH)->destRect; // Hide for a while ::OffsetRect(&(*theTEH)->viewRect, -5000, -5000); ::TESetText(&**mSavedText, ::GetHandleSize(mSavedText), theTEH); ::TEUseStyleScrap(0, 32000, mSavedStyles, false, theTEH); ::TESetSelect(mSavedSelStart, mSavedSelEnd, theTEH); ::OffsetRect(&(*theTEH)->viewRect, 5000, 5000); ::TECalText(theTEH); ::TESelView(theTEH); mTEview->UserChangedText(); mTEview->ForceAutoScroll( oldDestRect ); mTEview->AdjustImageToText(); mTEview->Refresh(); } // =========================================================================== // Ä RestoreEditFieldState() // =========================================================================== // Restore state of LEditField void cSavedTextEditState::RestoreEditFieldState(void){ TEHandle theTEH = GetMacTEH(); ThrowIfNil_(theTEH); char* ptr = &**mSavedText; ::TESetText(ptr, ::GetHandleSize(mSavedText), theTEH); ::TESetSelect(mSavedSelStart, mSavedSelEnd, theTEH); mEditField->Refresh(); mEditField->UserChangedText(); } // =========================================================================== // Ä RestoreEditTextState() // =========================================================================== // Restore state of LEditText void cSavedTextEditState::RestoreEditTextState(void){ TEHandle theTEH = GetMacTEH(); ThrowIfNil_(theTEH); char* ptr = &**mSavedText; ::TESetText(ptr, ::GetHandleSize(mSavedText), theTEH); ::TESetSelect(mSavedSelStart, mSavedSelEnd, theTEH); mEditText->Refresh(); mEditText->UserChangedText(); } // =========================================================================== // Ä GetMacTEH() // =========================================================================== // Get the TEHandle of either LTextEditView, LEditField or LEditText. Result may be NULL. TEHandle cSavedTextEditState::GetMacTEH(void){ TEHandle result = NULL; if(mTEview) result = mTEview->GetMacTEH(); else if(mEditField) result = mEditField->GetMacTEH(); else if(mEditText) result = mEditText->GetMacTEH(); return result; } // =========================================================================== // Ä DisposeData() // =========================================================================== // Get rid of handles, if any. void cSavedTextEditState::DisposeData(void){ if(mSavedText){ ::DisposeHandle(mSavedText); mSavedText = NULL; } if(mSavedStyles){ ::DisposeHandle((Handle)mSavedStyles); mSavedStyles = NULL; } }