[ First ]  [ Previous ]  [ Next ]  [ Last ]  [ Manuals ]

 

Chapter 9.

 

Drag and Drop in PowerPlant



This chapter discusses how to implement drag and drop features in a PowerPlant application.


Introduction to Drag and Drop in PowerPlant

PowerPlant provides wrappers for the Mac OS Drag Manager, and implements most of the required functionality at a default level. If you use the PowerPlant default behavior, implementing drag and drop can be fairly simple. You also have the option of extending PowerPlant for your own purposes.

You'll see how as we discuss:

The code exercise at the end of the chapter takes you through the process of implementing drag and drop in real code.

This chapter does not teach you the intricacies of the Drag Manager, or the drag and drop human interface. For more information, consult the official Drag Manager documentation: "Drag Manager Programmer's Guide" and "Drag and Drop H.I. Guidelines."

Both of these documents are available from Apple Computer, Inc:

http://developer.apple.com/techpubs/macos8/


Drag and Drop Strategy

The Drag Manager supports drag operations at the window level. A window has a drag tracking handler to handle the visual feedback required as a drag passes across a window's content area. A window also has a drag receive handler to accept the data in the drag and add it to the window's content. Both of these handlers are callback routines used by the Mac OS to implement drag and drop functionality.

Because it works at the window level, the Mac OS Drag Manager has a somewhat coarse resolution. An individual window may have several different sections that display different kinds of data. For example, a window may have a text area, a drawing area, some buttons or controls, a tool area, and so forth. You may want to handle drag tracking and receiving in different ways in different portions of a window.

You can do this with the Drag Manager only if you test the location of the drag at any moment, compare that against the geography of your window contents, and then act accordingly. This kind of complex, location-dependent system is anathema to the principles of object-oriented programming.

PowerPlant extends the Drag Manager so that it becomes an object-oriented tool. PowerPlant implements the concept of a "drop area." In PowerPlant, you add drag and drop functionality to individual panes. The pane could be a window (windows are views, and views are panes), a scrolling view, a text view, a button, or any other pane-based visual element in PowerPlant. Any pane that is drag and drop aware is a drop area. Because a drag always involves a visual item, all drag operations in PowerPlant are pane-related.

There are two important architectural features that determine the internal workings of a drag-aware PowerPlant application. A PowerPlant application has a single tracking and receive handler installed with the Drag Manager. Nevertheless, each drop area has its own internal tracking and receiving handlers.

Here's how it works. PowerPlant maintains a list of all drop areas. The tracking and receive handlers installed for the Drag Manager determine which drop area is involved in the operation, and dispatch control to the correct handler for the particular drop area. As a result, you are no longer limited to window-level resolution. You have object-level resolution for drag and drop functionality. You can define the behavior of each individual class of object with respect to drag and drop, not just each window.

In terms of implementation, a drag operation has a beginning, middle, and end. It begins when the user clicks and begins to drag an item. It continues while the user keeps the mouse button down. It ends when the user releases the mouse button.

The beginning occurs in a pane's Click() function when you write code to test for a drag. When you detect a drag, you create a drag task object. This may be an LDragTask object, or an object from a custom class derived from LDragTask. The drag task contains the data that represents the item or items being moved by the user. The drag task also contains a function for initiating the drag.

After creating the drag task object, you tell the drag task to begin tracking the drag. As the drag crosses various panes, you provide feedback to the user during the drag. The pane is responsible for providing the visual feedback during the drag.

A pane involved in a drag and drop operation should inherit from LDragAndDrop. LDragAndDrop is a mix-in class that implements the default behavior required to track a drag and receive a drop. LDragAndDrop is a concrete class that inherits from the abstract class LDropArea.

In a traditional implementation, you might have your window class inherit from LDragAndDrop. Then the user can drop items in your window. However, the PowerPlant strategy allows you to implement drag and drop for any individual pane as well.

When the user releases the mouse button at the end of a drag, the drop area receives the contents of the drop. Once again, it is LDragAndDrop that provides the functionality at the pane level. Any pane that inherits from LDragAndDrop may receive a drop.

PowerPlant provides functions that you override to put data into a drag, and to receive data from a drag. PowerPlant calls these functions at the appropriate moments. You provide the data and receive the data. PowerPlant does most of the rest of the background work.

Let's look at the features of the LDragTask, LDropArea, and LDragAndDrop classes to see how they work.


Drag and Drop Classes

There are three classes involved in PowerPlant's implementation of drag and drop functionality. They are:

Figure 9.1 illustrates the class hierarchy for these classes.

Drag and drop class hierarchy:

If you wish to support drag and drop in your code, you create custom pane classes. Remember that views are also panes. In a typical implementation, it is a view class that inherits from LDragAndDrop. For example, you might create a text view class that inherits from both LTextEdit and LDragAndDrop.


LDragTask

When the user begins a drag, you create an LDragTask object. LDragTask class has three data members, as shown in Table 9.1.

LDragTask data members:

 

Data member
Stores
mDragRef  
reference to this drag record  
mDragRegion  
region handle for the drag region  
mEventRecord  
reference to the event that started the drag  

The mDragRef member stores a DragReference value. The Drag Manager creates and uses a DragReference to access information about the drag.

The mDragRegion member stores a handle to the drag region. When tracking a drag, the Drag Manager draws the region that defines the bounds of objects being dragged.

The mEventRecord member stores a pointer to an EventRecord. The event record contains the location where the drag started, in global coordinates.

LDragTask is a simple class. The most useful functions are shown in Table 9.2.

LDragTask functions:

 

Function
Purpose
AddFlavors()  
add data to the drag in various flavors  
MakeDragRegion()  
create a drag region  
AddRectDragItem()  
add a rectangle to the drag region  
DoDrag()  
initiate the drag  

The AddFlavors() and MakeDragRegion() functions give you the opportunity to add data in a variety of flavors to a drag, and to specify the precise drag region you wish to use. These functions are empty in LDragTask. We'll discuss when and how to implement them in "Creating a Drag Task."

The AddRectDragItem() function is a utility routine that adds the bounds of the Rect you provide to an accumulating drag region.

The DoDrag() function is the routine you call to initiate the drag. The default implementation calls AddFlavors(), MakeDragRegion(), and the Drag Manager's TrackDrag() routine.


LDropArea

LDropArea is an abstract base class that provides the interface you use to implement drag tracking and receiving functionality.

LDropArea uses the LArray class. Other than that single exception, LDropArea-like many other independent modules in PowerPlant-does not requires any other PowerPlant classes. You can use LDropArea independently as a wrapper class for the Drag Manager. However, its most common use is as an integral part of a PowerPlant application through LDragAndDrop, its concrete descendant.

Most of the data members in LDropArea are used internally by PowerPlant. You won't have need to use them yourself. Table 9.3 lists the important data members.

LDropArea data members:

 

Data member
Stores
mDragWindow  
WindowPtr for the window containing drop area  
mCanAcceptCurrent
Drag
 
whether the drop area can receive this drag  
mIsHilited  
whether the drop area is currently highlighted  
sDragHasLeftSender  
whether the drag has left the sender window  
sCurrentDropArea  
current drop area  

Notice that the sDragHasLeftSender and sCurrentDropArea are static variables, so all instances of LDropArea share the same values.

LDropArea also defines a series of static class functions, some of which are shown inTable 9.4.

LDropArea static functions:

 

Function
Purpose
DragAndDropIs-Present()  
returns true if the Mac OS Drag Manager is available  
AddDropArea()  
add a drop area to the list  
RemoveDropArea()  
remove a drop area from the list  
FindDropArea()  
determine drop area involved in the drag  
InstallHandlers()  
install tracking and receive handlers  
HandleDragTracking()  
drag tracking callback routine  
HandleDragReceive()  
drag receive callback routine  
InTrackingWindow()  
track a drag while it is in a window  

These functions provide the core of PowerPlant's default implementation of drag and drop. These functions are used internally by PowerPlant. You should never have to call or modify these functions.

The DragAndDropIsPresent() function can be called to determine if the Drag Manager is present. PowerPlant checks at startup. This returns the result of that check.

The "drop area" functions manage PowerPlant's drop area list.

The two "handle" functions are Drag Manager callback routines. The InstallHandlers() function installs these tracking and receive handlers for the Drag Manager as the only handlers for the application. When tracking a drag inside a window, the default tracking handler calls InTrackingWindow(). The InTrackingWindow() code finds the correct drop area from the drop area list, and calls the correct handler.

When it is time to receive a drop, the default receive handler sends a DoDragReceive() message to the current drop area.

There are additional functions for custom drag behavior such as sending data in response to a promise, and custom drag drawing. We discuss those functions in "Providing Custom Drag Behavior." In addition to the static functions just discussed, LDropArea provides a series of functions that are the heart of PowerPlant's implementation of drag and drop. Table 9.5 lists these functions.

LDropArea functions:

 

Function
Purpose
PointInDropArea()  
return true if drag is inside the drop area (must be overridden)  
FocusDropArea()  
set up local coordinate system and clipping region for a drop area  
HiliteDropArea()  
highlight a drop area to indicate that it can accept the current Drag  
UnhiliteDropArea()  
remove highlight from a drop area  
EnterDropArea()  
called when drag enters a drop area  
LeaveDropArea()  
called when drag leaves a drop area  
DragIsAcceptable()  
return true when all items in the drag are acceptable  
ItemIsAcceptable()  
determine whether an individual item in the drag is acceptable  
InsideDropArea()  
called repeatedly while inside a drop area  
DoDragReceive()  
receive a drag  
ReceiveDragItem()  
receive an item in the drag  

PointInDropArea() is a pure virtual function, and must be overridden. In addition, several other functions are empty, including FocusDropArea(), InsideDropArea(), ItemIsAcceptable(), and ReceiveDragItem(). You must provide this functionality in classes that derive from LDropArea.

In a typical implementation of drag and drop in PowerPlant, you override several of these functions (including the empty functions). We discuss which functions you commonly override in "Implementing Drag and Drop in PowerPlant."


LDragAndDrop

LDragAndDrop is a mix-in class designed to add drag and drop features to a pane. It inherits from LDropArea and is a concrete implementation of that class. A pane that supports drag and drop should multiply inherit from LDragAndDrop, not LDropArea.


NOTE

A drag-savvy pane must override certain LDragAndDrop behaviors. We discuss which functions to override in "Implementing Drag and Drop in PowerPlant."


LDragAndDrop adds one data member, mPane, to store a pointer to the pane associated with this drop area.

The LDragAndDrop class overrides three LDropArea functions, as shown in Table 9.6.

LDragAndDrop functions:

 

Function
Purpose
PointInDropArea()  
determine if point is in the pane  
FocusDropArea()  
call FocusDraw() for the pane  
HiliteDropArea()  
highlight the pane frame  

LDragAndDrop does not declare or define any new functions.

Now that you are familiar with the classes involved in drag and drop, let's examine how to actually implement drag and drop in a PowerPlant application.


Implementing Drag and Drop in PowerPlant

This section walks you through the process of implementing drag and drop in a typical PowerPlant application. As you know, this process involves creating a drag task, and having panes that can track and receive a drag. The tasks involved are:


Looking for the Drag Manager

Before using any routine that requires the presence of the Drag Manager, you should ensure that the Drag Manager is present. Simply call LDropArea::DragAndDropIsPresent() before any call that depends on the Drag Manager.

How to respond to the absence of the Drag Manager is, of course, a function of your application. If it absolutely requires drag and drop functionality, you should use weak import DragLib. This allows the application to launch even if the Drag Manager is not present. Then, at startup time, check for drag and drop. If it is not present, quit the application gracefully after alerting the user.

If your code is not dependent on drag and drop, you must implement alternative mechanisms, one for the drag manager, and one for when the drag manager is not present. You can do this by subclassing LDragAndDrop, if you carefully test for the presence of drag and drop before calling any Drag Manager or LDropArea routines, and you provide alternative code.


Handling Clicks

Handling clicks in a window that supports drag and drop is a two-step operation. First, you set an attribute for the window involved in the PPob resource for that window. Second, you override the Click() function inherited from LPane. Let's look at how these two tasks relate to each other.

The drag and drop human interface guidelines are very specific about how a drag operation should appear to a user. The user should be able to click and drag data from an inactive window into another window, without the source window becoming activated.


NOTE

In the traditional human interface, selection highlighting disappears when a window becomes inactive. With drag and drop, the user still needs to see what objects are selected in an inactive window. This requires that you provide background highlighting as described in Apple's drag and drop documentation. The LTable and LTableView classes takes care of background highlighting automatically. TextEdit in the Toolbox provides background highlighting for both LTextEdit and LEditField.


PowerPlant provides support for clicking and dragging from a background window in the PPob resource through the Delay Select option illustrated in Figure 9.2. This option corresponds to the delay-Select attribute of the LWindow class.

Getting the select click:

When the delaySelect attribute is set, a click that would normally activate a window should be processed as if the window were already active. You accomplish that by overriding Click() for panes that support drag and drop.

As you know, LPane::Click() typically handles any click in a pane, view, or control. The default functionality usually provides everything you need. In the case of drag and drop, it does not.

The default code responds to clicks only when the delaySelect attribute is not set. Listing 9.1 shows the relevant code from LPane.

LPane::Click():


LPane::Click(SMouseDownEvent& inMouseDown)
{
	if (!inMouseDown.delaySelect) {
        // normal click handling code
		}
	}
}

You must override this function and replace the functionality. Otherwise, when you set the delaySelect attribute, your pane will not respond to clicks when it is in an inactive window. Replacement code for Click() might be organized like the code in Listing 9.2.

A Click() override:


CMyClass::Click( SMouseDownEvent& inMouseDown)
{
	if ( inMouseDown.delaySelect )
	{
		// process the click for selection, etc.
		if( ::WaitMouseMoved(
								inMouseDown.macEvent.where))
		{
			// drag begins
		}
	}
		
} else
	{
		// Call inherited Click() for default behavior.
	}
}

Of course, the actual structure and functionality of your Click() routine depends upon your pane's contents and behavior. For example, in a graphics application you might check the location of the click against an existing selection. If the click is not on an already-selected item, you should deselect any existing selection, and select the clicked item. This same approach doesn't work for text, because you cannot select text with a single click.

After performing any preprocessing on the click, you then test for a drag, as described in "Identifying a Drag." Notice you do this regardless of whether the click occurs in a foreground or background window, or changes a selection. This allows the user to perform a single-gesture selection and drag, as required in the drag and drop human interface. The process of handling a drag is described in the rest of this chapter.

If no drag begins, you simply exit the routine. In that case, control ultimately returns to the LWindow::ClickInContent() routine in PowerPlant, which activates the window for you.

For more information on topics relating to the drag and drop human interface, consult the drag and drop documentation.


Identifying a Drag

A drag begins when the user clicks and holds the mouse button down. Call the Mac OS routine WaitMouseMoved() to identify such an occurrence. The WaitMouseMoved() call returns a boolean value of true if a drag has begun. Listing 9.2 contains an example.


Creating a Drag Task

When a drag begins, you create an LDragTask object.

In PowerPlant, the LDragTask object manages the data necessary for the Drag Manager: the event that starts the drag, the drag reference, the data being dragged, and the drag region handle.


TIP

Before creating the LDragTask, call the window's ApplyForeAndBackColors() function to set the proper colors for the port and ensure that the drag highighting shows up correctly on non-white backgrounds, or for colored objects.


PowerPlant provides two alternative mechanisms for instantiating an LDragTask object. We will call them the simple approach and the flexible approach. Each approach has its own constructor function.


The simple approach

This technique is suitable for dragging a single item in a single flavor (data type). The constructor routine for the simple approach has several parameters. Here's the prototype for the constructor function.


LDragTask(const EventRecord &inEventRecord,
					const Rect &inItemRect,
					ItemReference inItemRef,
					FlavorType inFlavor,
					void *inDataPtr,
					Size inDataSize,
					FlavorFlags inFlags);

You provide the event record, the bounding rectangle, an item reference (typically the value 1), the flavor type, a pointer to the data, the size of the data, and any flavor flags.

When you call the constructor, PowerPlant takes over. The constructor function stores the event record, creates the necessary drag reference, adds the data to the drag in the flavor type you specify, and creates the drag region.

The drag region is always the bounds of the rectangle you provide in the second parameter. As a result, no matter what the underlying shape is, the drag outline that the user sees is rectangular.

The constructor also starts the drag by calling the Drag Manager's TrackDrag() function.

In the simple approach, the very act of instantiating the LDragTask object begins the drag operation automatically. However, the simple approach is suitable only for dragging one item in one flavor (data type).


The flexible approach

Use this approach if you want to drag multiple items, or if you want to provide multiple flavors for a single item. In practice, the flexible approach is much more useful. A well-designed application drags data in a variety of flavors to increase the likelihood that the destination can understand the data. Remember, the destination may be any other process, not just the source application.

In addition, the flexible approach lets you set flags before starting a drag. For example, you can restrict the drag to the sender only.

The flexible approach uses a very simple LDragTask constructor.


LDragTask(const EventRecord& inEventRecord);

You provide the event record that starts the drag. The constructor function stores the event record, and creates the drag reference.

After that, you start the drag by calling the drag task's DoDrag() function. Here's the code for LDragTask::DoDrag().




LDragTask::DoDrag()
{
	AddFlavors(mDragRef);
	MakeDragRegion(mDragRef, mDragRegion);
	::TrackDrag(mDragRef, &mEventRecord,
mDragRegion); }

 


Notice that it calls two LDragTask functions, AddFlavors() and MakeDragRegion(). These are empty functions in LDragTask. You must subclass the

LDragTask class and override these functions if you use the flexible

approach.

 

In your definition of AddFlavors(), you write code to add data for multiple objects in multiple

flavors. How you do that is dependent upon the data in your own

application. Read the Drag Manager documentation for the steps

to take and the calls to make to accomplish this task.

 

Similarly, in your definition of MakeDragRegion() you describe the outline of the various objects. Once again,

consult the Drag Manager documentation for suggestions. You should

also explore the LDragTask::AddRectDragItem() code to see how PowerPlant accomplishes the task for a simple

rectangle.

 

Having provided these two functions, your initial work setting

up the drag task and starting the drag is complete. The Drag Manager

begins tracking the drag. During the process it calls your tracking

handler and sends it messages.


Tracking a Drag

 

Every pane capable of receiving a drag should have a tracking

handler. When the drag is within the bounds of the pane, the pane

provides some visual feedback to the user indicating whether the

drag can be received. For this to work, the pane must inherit

from a class derived from LDragAndDrop.

 

In its simplest form, implementing drag tracking requires that

you override at least one function.

 

You must override ItemIsAcceptable(). In this function you use the Drag Manager to determine what

data is in the drag. If you can accept it, you return true. Otherwise, you return false.

 

The second function you may optionally override is HiliteDropArea(). The default behavior of LDragAndDrop highlights the drop area

which is the frame of its associated pane inset by one pixel to

account for the border which usually surrounds a Drop-capable

pane. Override HiliteDropArea() to provide custom highlighting.

 

If you want to get fancy, PowerPlant gives you the means to do

so. There are several functions you may override if you wish to

implement custom behavior. Table 9.7 lists the functions.

Override for special tracking behavior:

 

Function
Purpose
UnhiliteDropArea()  

remove highlight from a drop area  

EnterDropArea()  

called when drag enters a drop area  

LeaveDropArea()  

called when drag leaves a drop area  

InsideDropArea()  

called repeatedly while inside a drop area  

 

 

 

Override UnhiliteDropArea() if HiliteDropArea() does something other than call the Drag Manager's ShowDragHilite() routine.

 

Override EnterDropArea() and LeaveDropArea() if you want to implement special behavior when such an event

occurs. The default behavior of these functions calls HiliteDropArea() and UnhiliteDropArea() respectively.

 

Override InsideDropArea() if the drag location inside the drop area affects what happens.

For example, you may wish to indicate an insertion point in a

text area.


Receiving a Drop

 

The final task you must perform is to receive the data in a drop.

To accomplish this task, you must override the ReceiveDragItem() function.

 

In the standard implementation, the DoDragReceive() function walks through all the items in the drag, and calls ReceiveDragItem() for each one. You do not need to override DoDragReceive() in a typical application.

 

Once again, what data you can receive and how you receive it

are matters that are application dependent and beyond the scope

of PowerPlant. Read the Drag Manager documentation for the steps

to take and for the calls to make to accomplish this task.

 

When a drag begins and ends in the same inactive window, PowerPlant

activates the window automatically. This behavior is required

by the drag and drop human interface guidelines. With that one

exception, a successful drop does not activate the receiving window.


Providing Custom Drag Behavior

 

The Drag Manager gives you the option of replacing default Drag

Manager behavior with special callback functions. You can use

these advanced techniques to manage data delivery, drag feedback,

and mouse and keyboard modifiers.

 

For example, the user may want to drag an item that contains

a large amount of data. Rather than gathering up that data, duplicating

it, and placing it in the drag, you can provide a "promise" to

deliver the data to the destination. If the user aborts the drag

you haven't wasted time or memory on an uncompleted operation.

If the user completes the drag, the Drag Manager calls your custom

callback function. This function fulfills your promise to deliver

data.

 

Similarly, you may want to customize drag feedback. The Drag

Manager draws a simple grey outline of the drag region. You may

want to do something fancier, like drag an actual bitmap of the

item. To do so, you must provide a custom callback function to

handle drag drawing.

 

See the Drag Manager documentation for more information about

the circumstances under which you may wish to replace the default

Drag Manager behavior.

 

Notice that this custom behavior occurs on a per-drag basis.

It has nothing to do with windows, panes, or applications. You

provide the address of the callback function to the Drag Manager

along with the drag reference for which you wish the callback

to be used.

 

The LDropArea class has a variety of functions to assist you

with these custom callback functions, as listed in Table 9.8.

LDropArea custom callback support functions:

 

Function
Purpose
HandleDragSendData()  

send data callback routine  

HandleDragInput()  

user input callback routine  

HandleDragDrawing()  

drag drawing callback routine  

DoDragSendData()  

send data in response to a promise  

DoDragInput()  

modify mouse and modifier keys during a drag  

DoDragDrawing()  

do all drawing during a drag  

 

 

 

The "handle" functions are the Drag Manager callback routines.

They are static member functions. The code in each of the three

"handle" functions is complete. You would not typically override

that behavior. Each calls the corresponding "do" routine.

 

The code in each of the "do" routines is incomplete. In fact,

all three "do" functions are empty. You must override these functions

in a subclass and provide definitions.

 

Please note that the LDropArea::InstallHandlers() function only installs the tracking and receive handlers. The

handlers to send data, control input, and perform drag drawing

are not installed by PowerPlant.

 

If you want the Drag Manager to use these three PowerPlant handlers,

you must call the appropriate Drag Manager routine to install

the handler. The routines are: SetDragSendProc(), SetDragInputProc(), and SetDragDrawingProc().


WARNING!

When you set any of these three handlers for a particular drag,

you must provide a pointer to the appropriate drop area as the

refcon parameter. The PowerPlant handlers expect to find the LDropArea

pointer in the refcon parameter when called by the Mac OS.



Summary of Drag and Drop in PowerPlant

 

Drag and drop is a powerful tool. Users love the ease of use

and intuitive freedom it provides for copying and pasting data.

 

However, implementing drag and drop is not always simple. PowerPlant

eliminates much of the pain of basic implementation. It does not

eliminate all of the complexity. As you learned, in many cases

you must still prepare the data in various flavors, and add that

data to the drag. You must also handle receiving the data. This

can be a non-trivial task.

 

However, PowerPlant gives you a robust framework on which you

can hang your drag and drop code. PowerPlant takes care of dispatch

and control. You provide the functions to determine if a drag

can be received, to receive the items in the drag, and (typically)

to add data to the drag and create a drag region.

 

PowerPlant also provides a variety of functions you can use to

customize your drag and drop behavior. You can implement a full

range of features using descendants of the LDropArea class.


Code Exercise for Drag and Drop

 

In this exercise you implement simple drag and drop in an application

named "DragAndDrop." The purpose of this exercise is to give you

experience with drag and drop in PowerPlant-the functions you

override and the tasks you perform. This exercise is not intended

as a tutorial on the Drag Manager.

 

The vehicle for drag and drop is a small window containing an

instance of the PowerPlant LTable class. Figure 9.3 shows the window. It contains a single-column table that lists

a variety of fruits.

The DragAndDrop window:

The general tasks you must perform to support drag and drop in

a PowerPlant application are:

 

In this exercise you write code to accomplish each of these tasks.

 

1. Support drag and drop in an inactive window.

Drag Window resource DragAndDrop.ppob

 

In this step you modify the PPob resource to enable the delaySelect attribute of the window. Open the DragAndDrop.ppob file in Constructor,

open the Drag Window resource, and open the property inspector

for the drag window.

 

Click on the Delay Select check box to turn it on. With this option active, PowerPlant

will delay activating an inactive window. This gives you an opportunity

to handle the click for drag and drop.

Turning on Delay Select:

When you are through, save your changes and close the file. You

can quit Constructor is you wish.

 

2. Handle clicks.

Click() CDragAndDropTable.cp

 

In order to support drag and drop, when a click occurs you must

explicitly test for the delaySelect attribute, and the presence of drag and drop. If both conditions

exist, then you can process the click.

 

The existing code has an empty if test. Test the delaySelect field of the inMouseDown parameter, and call DragAndDropIsPresent(). The code controlled by the if statement is provided for you.

It does the required bookkeeping and calls ClickSelf().


  if ( inMouseDown.delaySelect

&& DragAndDropIsPresent() ) {

 

With these two steps you have accomplished the first principal

task, handling clicks properly. Next, you identify the beginning

of a drag.

 

3. Identify a drag operation.

ClickCell() CDragAndDropTable.cp

 

The LTable::ClickSelf() function identifies which cell in the table has been clicked,

and calls ClickCell(). CDragAndDrop-Table overrides ClickCell() to support drag and drop. This is where you detect an incipient

drag.

 

The existing code has an empty if test. You must test for the presence of the drag manager, and

whether a drag has commenced.

 

To test whether a drag has commenced, call the Mac OS Toolbox

function WaitMouseMoved(). Pass inMouseDown.macEvent.where as the parameter to WaitMouseMoved().


  if ( DragAndDropIsPresent()

   && ::WaitMouseMoved(
inMouseDown.macEvent.where ) ) {

The code inside this if statement executes if and only if the user is dragging and the

Drag Manager is present. You have successfully accomplished the

second principal task, detecting a drag. Next, you create a drag

task.

 

4. Create a drag task

ClickCell() CDragAndDropTable.cp

 

The code you write in this step goes inside the if statement you modified in the previous step. If the user is dragging,

you create a drag task.

 

The existing code manipulates table information and sets some

local variables. It gets the cell, the cell frame in a Rect, and the cell data in an Str255. After that, you create a drag task.

 

This code supports dragging a single item in a single flavor,

so you can implement the "simple" approach to a drag task. This

constructor requires that you pass the event record involved,

the bounds of the drag region, an item reference, the flavor of

data, the address of the data, the length of the data, and flavor

flags.


  GetCellData( theCell, theString );

  // Create the drag task.


  LDragTask theDragTask( inMouseDown.macEvent,


   theCellFrame, 1, 'TEXT',


   &theString[1],


StrLength(theString), 0 );

 

Remember that creating a drag task in this way also starts the

drag running automatically. There is no need to call DoDrag().

 

After this code, the existing code performs one more step required

to conform to the human interface guidelines for the Drag Manager.

The code checks to see if the drag ended up in the trash. Here's

the relevant code. You don't have to type this in, it already

exists.


if ( UDragAndDropUtils::DroppedInTrash(
						theDragTask.GetDragReference() ) ) {

 


If the drag ends up in the trash, the code removes the item from

the table. You have now completed the third principal task required

to support drag and drop. You have created the drag task and started

the drag running. In the next three steps you provide the support

required to track a drag.


TIP

You may find the UDragAndDropUtils class very useful in your own

coding. It has static functions to test for a drop in the trash,

whether the user has the option key down, and whether the drop

is in the same window it started in. These are very useful housekeeping

routines.


5. Determine if the drag is acceptable.

ItemIsAcceptable() CDragAndDropTable.cp

 

The ItemIsAcceptable() function is a utility function that PowerPlant calls when it

needs to know if a drop can be received by a particular drop area.

To properly support drag and drop, you must define this function.

 

The table is capable of receiving a text item. You should call

the Drag Manager's GetFlavorFlags() function and determine if the item has the TEXT flavor. You should

also test to ensure that the table is enabled (this is an LPane

function, IsEnabled()).


  FlavorFlags theFlags;

  return IsEnabled()


   && (::GetFlavorFlags( inDragRef,


inItemRef, 'TEXT', &theFlags ) == noErr);

 

6. Provide drag feedback.

HiliteDropArea() CDragAndDropTable.cp

 

PowerPlant calls this function when it is necessary to highlight

a drop area. This provides the user with visual feedback that

the drop is acceptable in the drop area.

 

In this case, the drop area is the window's content area. This

function should get the local frame bounds, convert it into a

region, call the Drag Manager's ShowDragHilite() function, and dispose of the region.


   // Get the frame rect.

   Rect theRect;


   CalcLocalFrameRect( theRect );


  
   // Show the drag hilite in the drop area.


   RgnHandle theRgnH = ::NewRgn();


   ::RectRgn( theRgnH, &theRect );


   ::ShowDragHilite( inDragRef, theRgnH, true );


::DisposeRgn( theRgnH );

 

Because you use the ShowDragHilite() function, you do not need to override UnhiliteDropArea().

 

7. Provide insertion point feedback.

InsideDropArea() CDragAndDropTable.cp

 

In the DragAndDrop application, the drop area is the table, and

the table occupies the content area of the window. To be very

friendly, there should also be an insertion point to clearly demonstrate

where the drop is going to end up in the list.

 

To make this work, you must track the drag while it moves around

inside the drop area. While a drag moves inside a drop area, PowerPlant

repeatedly calls InsideDropArea().

 

The existing code does all the setup work. This application uses

the mDropRow data member to track where in the table the drop should go. The

existing code calls the inherited InsideDropArea() function, gets the mouse location for the drag, and determines

what row is involved.

 

If it's a new row, it erases the previous dividing line (insertion

point). After that, you should draw a new dividing line.

 

Save theRow in the mDropRow data member. Then call DrawDividingLine().


  mDropRow = theRow;

DrawDividingLine( mDropRow );

 

The code for determining the row and drawing the dividing line

is provided for you. Feel free to examine it.


NOTE

The CDragAndDropTable class also overrides EnterDropArea() and LeaveDropArea(). These overrides simply invalidate the mDropRow value so that a new insertion point is drawn when the drag changes

cells.


With this step you have completed the support required for tracking

a drag. All that remains is receiving a drop.

 

8. Receive the drag item.

ReceiveDragItem() CDragAndDropTable.cp

 

As you know, the purpose of this function is to retrieve data

from the drop. The code in this function is provided for you.

It has more to do with manipulating data in an LTable than anything

else.

 

Examine the code, and understand the tasks it performs. First,

it gets the data in the drag item by calling the Drag Manager's

GetFlavorData() function. Then it gets the size of the data by calling GetFlavorDataSize().

 

After that, the code determines if the drag is a move or a copy

operation. It is a move operation if the drag is in the sender

window, and the option key is not down. Otherwise it is a copy

operation. Notice that the code uses the UDragAndDropUtils member

functions for both tests.

 

If it is a move operation, the code removes the data from one

location in the table and inserts it in another.

 

If it is a copy operation, the code inserts the data in the proper

location in the table.

 

9. Build and run the application.

When the application builds successfully and runs, the window

shown in Figure 9.3 appears with the list of fruits.

 

Click and drag an entry in the list to another location in the

list. Notice that you can select and drag an item with a single

gesture. This is an important feature of the human interface.

 

While a drag is underway, observe the insertion point feedback

(the black line between entries). The insertion point is the work

of the InsideDropArea() function. As the drag moves, the insertion point follows along.

 

Notice that there is no drop area highlighting (the border around

the content area of the window). Drop area highlighting only appears

after a drag has left the sender window. To see it, drag the item

out of the window, and then back into the window. When you reenter

the drop area, the drop area highlighting appears indicating that

this is an acceptable drop. The highlight is from the HiliteDropArea() function.

 

A drag within a single container can be a move or a copy. Hold

down the option key while you begin a drag, or when you drop an

item in the same table as you started. You don't have to hold

the option key down for the entire drag, just at the beginning

or the end. If the option key was down at either of those moments,

the original remains and a copy is placed at the drop location

when you drop the item.

 

Drag an item to the trash and observe what happens. The item

should disappear from the table. Look in the trash, and you'll

see a text clipping. You can drag that item back out of the trash

and into a table if you wish.

 

Now, make a new window and drag items back and forth between

windows. In particular, drag an item from the inactive window

into the active window. A drag from an inactive window or a drop

into an inactive window does not cause that window to become active.

This is another important feature of the human interface. Notice

also that the inactive window shows background highlighting of

the currently selected table cell in that window.

 

Finally, drag an item from a window and drop it on the Finder's

desktop. The item appears on the desktop as a text clipping. Drag

the text clipping into a table. The text appears as a new entry.

Drag and drop works across applications!

 

Launch a text processor that supports drag and drop, like SimpleText.

Type a phrase into SimpleText. Then drag it into the DragAndDrop

table. The text transfers from one application to another with

no difficulty. Drag something from the table into SimpleText.

It works both ways.

 

Play around with drag and drop. It makes data transfer so simple

that you might wonder why it hasn't always been this easy. When

you are through playing, quit the application.

 

PowerPlant gives you a lot of drag and drop functionality for

free. PowerPlant takes care of most of the housekeeping details,

dispatching control to the proper functions when necessary. You

override and define a few, relatively simple hook functions such

as ItemIsAcceptable(), HiliteDropArea(), and so forth.

 

As always, there is room for further exploration. You could implement

drag and drop for pictures. Follow the same steps as you did in

this exercise, but use PICT data instead of text.

 

For a greater challenge, you might add multiple flavors of the

same data to a drag (such as both TEXT and PICT). Or you might

add multiple items to a single drag. You might create a drag region

that outlines each individual item in a multiple-item drag, rather

than the encompassing rectangle. See "The flexible approach" for ideas on how to go about this.

 

Finally, for a real challenge, you could implement custom routines

for managing a drag operation. For example, you could drag around

a bitmap image of the item being moved, rather than a simple outline.

See "Providing Custom Drag Behavior" for hints and clues.

 

Good luck, and happy dragging.

 

 

 

 

 

 

 

 


[ First ]  [ Previous ]  [ Next ]  [ Last ]  [ Manuals ]

Visit the Metrowerks website at: http://www.metrowerks.com
For assistance contact Metrowerks Technical Support at: cw_support@metrowerks.com
Copyright © 2000, Metrowerks Corp. All rights reserved.

 

Last updated: July 21, 2000