Drag and Drop

Drag and drop lets you drag arbitrary data within an application or between applications.


Note: If you simply need to drag graphical objects around, see Dragging in the Events chapter.

This chapter discusses:

Transport mechanism

Photon's transport mechanism lets you transfer arbitrary data from one application to another, even if the applications are on different platforms with different endian-ness. This mechanism is used as the basis for drag and drop, but could be used for other purposes such as configuration files.

There are two ways to transport data:

Inline
The data is packed into a stream and sent to the destination.
By request
Descriptions of the data are packed into a stream and sent. The destination decides which type(s) of data it wants and sends the request back to the source, which then packs only the data required.

In order to transport data, the transport mechanism must pack data at the source application or widget and unpack it at the destination. It has to have a means of recognizing the type of data to determine what packing and unpacking must be done. This is accomplished via the transport registry.

There are a number of system-registered types that exist after PtInit() or PtAppInit() initializes the Photon library — this is done automatically for PhAB applications. The system-registered types are:

You can add other data types to the registry, as described in Registering new transport types,” later in this chapter.

The transport mechanism works by building a linked list of data to be transported, packing up the data into a stream, with each block preceded by a header that describes the data.


Packed data and headers


Packed data and headers.

When the data arrives at the destination, the headers are extracted to get the unpacking instructions for the data. The transport mechanism automatically unpacks the data; the application gets the data in its original form.

Using drag and drop

This section includes:

You can use drag and drop to move data from one widget to another, using Photon's transport mechanism. You can transport several types of data at once, giving the destination the choice of which ones to receive. All of the communication between the source and destination is nonblocking.

The basic steps (described in more detail in the sections that follow) are:

  1. The user holds down the pointer button on the widget that's to be the source of the drag-and-drop operation.
  2. In its Pt_CB_OUTBOUND callback, the source widget packs up the data to be dragged, and starts a drag-and-drop operation.
  3. The user drags the data and decides to drop it on a widget.
  4. In its Pt_CB_DND callback, the destination widget decides which pieces of dragged data (if any) it will accept. Any data that's accepted is unpacked automatically. The data is stored in allocated memory; the destination should free the memory when it doesn't need the data any more.

The source widget can also cancel the operation if it wishes.

Starting drag and drop

To start a drag-and-drop operation, the source widget must pack up the data to be dragged, and then initiate drag-and-drop. This is typically done in one of the widget's Pt_CB_OUTBOUND callbacks.


Note: Pt_CB_OUTBOUND callbacks are invoked only if the widget has Pt_SELECTABLE set in its Pt_ARG_FLAGS.

The steps to follow are:

  1. If the data isn't of one of the system-defined types of transport data, create a transport registry entry for it. For more information, see Registering new transport types,” below.
  2. Create a transport control structure (of type PtTransportCtrl_t) for use with drag and drop, by calling PtCreateTransportCtrl(). The transport control structure is freed automatically when the drag-and-drop operation is done.
  3. The data to be dragged can be packed inline (i.e. included directly in the structure passed to the destination) or it can be requestable (i.e. the data isn't packed up until the destination asks for it).
  4. When all the data is packed, call PtInitDnd() to initiate the drag-and-drop operation.

Example

Here's an example of a callback that initiates a drag-and-drop operation for a PtLabel widget. You can use this callback for the label widget's Pt_CB_OUTBOUND callback.


Note: Be sure to set Pt_SELECTABLE in the label's Pt_ARG_FLAGS.

This callback sets up a drag-and-drop operation involving these pieces of data:

The callback assigns the same grouping number to the image and the alternate text, to indicate that they're different forms of the same data.

/* Standard headers */
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>

/* Toolkit headers */
#include <Ph.h>
#include <Pt.h>
#include <Ap.h>

/* Local headers */
#include "abimport.h"
#include "proto.h"

#define TEXT_GROUP   0
#define IMAGE_GROUP  1

PtTransportReqDataCB_t request_callback;

int start_dnd( PtWidget_t *widget,
               ApInfo_t *apinfo,
               PtCallbackInfo_t *cbinfo )
{

    char *widget_text = NULL;
    char *label_type;
    PhImage_t * image = NULL;
    PtRequestables_t *req;
    PtTransportCtrl_t *tctrl =
        PtCreateTransportCtrl();
    int ret;

    /* eliminate 'unreferenced' warnings */
    widget = widget, apinfo = apinfo;
    cbinfo = cbinfo;

    /* Get the type of label so we can determine
       what data to pack. */
    PtGetResource( widget, Pt_ARG_LABEL_TYPE,
                   &label_type, 0);

    if ((*label_type == Pt_Z_STRING) ||
        (*label_type == Pt_TEXT_IMAGE))
    {
      /* Get the widget's text and pack it inline. */
      PtGetResource( widget, Pt_ARG_TEXT_STRING,
                     &widget_text, 0);
      PtTransportType( tctrl, "text", "plain",
         TEXT_GROUP, Ph_TRANSPORT_INLINE, "string",
         widget_text, 0, 0);
    }

    /* If there's an image, add it as requestable
       data. Prepare the response data (to allow an
       automatic response). */

    if ((*label_type == Pt_IMAGE) ||
        (*label_type == Pt_TEXT_IMAGE))
    {
        PtGetResource( widget, Pt_ARG_LABEL_IMAGE,
                       &image, 0);
        if (image)
        {
           req = PtTransportRequestable ( tctrl,
                   "image", "an image", IMAGE_GROUP,
                   Ph_TRANSPORT_INLINE, "PhImage",
                   NULL, NULL );
           PtAddResponseType( tctrl, req, "image",
              "an image", Ph_TRANSPORT_INLINE,
              "PhImage", image, 0, 0);
        }
    }

    /* Add a requestable string that will be
       provided by a callback. */

    PtTransportRequestable( tctrl, "text",
       "image description", IMAGE_GROUP,
       Ph_TRANSPORT_INLINE, "string",
       (PtTransportReqDataCB_t *) &request_callback,
       "This was requested");

    /* Initiate the drag and drop. */

    ret = PtInitDnd( tctrl, widget, cbinfo->event,
                     NULL, 0);
    return( Pt_CONTINUE );
}

int unsigned request_callback( int unsigned type,
                 PtReqResponseHdr_t *req_hdr,
                 PtRequestables_t *req)
{
   if (type == Pt_DND_REQUEST_DATA)
   {
      /* Respond to the request with the string in
         req->rq_callback_data, the last argument
         to PtTransportRequestable(). */

      PtAddResponseType( req->ctrl, req, "text",
         "request", Ph_TRANSPORT_INLINE, "string",
         req->rq_callback_data, 0, 0);
      return Pt_CONTINUE;
   }

   /* Deny the request. */
   return Pt_END;
}

Receiving drag-and-drop events

To make a widget able to receive drag-and-drop events, attach a Pt_CB_DND callback to the widget (see PtWidget in the Photon Widget Reference).


Note: A widget doesn't have to have Pt_SELECTABLE set in its Pt_ARG_FLAGS for its Pt_CB_DND callbacks to be invoked.

Whenever the widget is involved in a drag-and-drop event in some fashion, its Pt_CB_DND callback is invoked. In the callback, the cbinfo->reason_subtype indicates the type of drag-and-drop action that's occurring.

The sections below describe the drag-and-drop events that are of interest to the source and destination widgets. Of course, if a widget can be the source and destination of (separate) drag-and-drop operations, its Pt_CB_DND callbacks need to have both sets of events.

For more information about events, see PhEvent_t in the Photon Library Reference.

Source widget

The source widget of a drag-and-drop operation can receive events that describe the status of the operation. If you don't want these events, set Pt_DND_SILENT in the flags argument to PtInitDnd().


Note: Don't set Pt_DND_SILENT if you've included requestable data in the control structure.

The subtypes of a drag-and-drop event that are of interest to the source of the operation are:

Ph_EV_DND_INIT
The operation has started successfully.
Ph_EV_DND_CANCEL
The operation was canceled (for example, if the drop occurred when not over a drop zone, or the destination terminated the operation before receiving the drop or before it finished fetching requestable data).

If the operation is canceled in this way, the library cleans up the data structures automatically.

Ph_EV_DND_COMPLETE
The drag-and-drop event is enqueued at the destination (the destination hasn't seen it yet).
Ph_EV_DND_DELIVERED
The destination has dequeued the drag-and-drop event.

Destination widget

The subtypes of a drag-and-drop event that are of interest to the destination of the operation are:

Ph_EV_DND_ENTER
Someone has dragged some data into the widget's region but hasn't yet released it. This is the reason_subtype the first time that the drag-and-drop callback is called.

At this time, your application decides if it will accept the data to be dropped. It must build an array of PtDndFetch_t structures and pass it to PtDndSelect(). This array describes the acceptable types, descriptions, and transport methods for drag-and-drop data to be accepted by a widget. PtDndSelect() returned the number of selected items from the array. If the event contains data, or references to data, in an acceptable format, those pieces of the event are selected.

If none of the data is acceptable, this widget isn't notified of any other events for the current drag-and-drop operation.

Ph_EV_DND_MOTION
The pointer is moving inside the widget's region. This type of event is emitted only if the Pt_DND_SELECT_MOTION bit is set in the select_flags member of the PtDndFetch_t structure for a piece of selected data.
Ph_EV_DND_DROP
The user has dropped the data.

For this reason_subtype, the callback should retrieve the selected data from the event. This might involve some automatic, nonblocking communication with the source of the data — to prevent any communication with the source, specify Ph_TRANSPORT_INLINE as the only acceptable transport protocol.

If the drop is successful, the memory used by the transport mechanism is automatically freed.

Ph_EV_DND_LEAVE
The pointer has moved out of the widget's region, but the user didn't drop the data.

Here's an example that works with the callback given above for a PtLabel widget. This callback accepts the following from the drag-and-drop data:

The source widget packed the image and the alternate text as requestable data, but the destination doesn't have to do anything to request it; the transport mechanism does it automatically.

/* Standard headers */
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>

/* Toolkit headers */
#include <Ph.h>
#include <Pt.h>
#include <Ap.h>

/* Local headers */
#include "abimport.h"
#include "proto.h"

static PtDndFetch_t stuff_i_want[] =
{
   {"text", "plain", Ph_TRANSPORT_INLINE, },
   {"image", NULL, Ph_TRANSPORT_INLINE, },
   {"text", "image description",
    Ph_TRANSPORT_INLINE,
    Pt_DND_SELECT_DUP_DATA, },
};

enum {
     PLAIN_TEXT = 0,
     IMAGE,
     IMAGE_TEXT,
};


int dnd_callback( PtWidget_t *widget,
                  ApInfo_t *apinfo,
                  PtCallbackInfo_t *cbinfo )

{
  PtDndCallbackInfo_t *dndcb = cbinfo->cbdata;
  int deep_free = 1, num_matches;

  /* eliminate 'unreferenced' warnings */
  widget = widget, apinfo = apinfo;
  cbinfo = cbinfo;

  switch (cbinfo->reason_subtype)
  {
    case Ph_EV_DND_ENTER:
      num_matches = PtDndSelect (widget,
         stuff_i_want,
         sizeof( stuff_i_want ) / sizeof( stuff_i_want[0] ),
         NULL, NULL, cbinfo );
      break;

    case Ph_EV_DND_DROP:
      switch (dndcb->fetch_index)
      {
        case PLAIN_TEXT:
          PtSetResource (widget, Pt_ARG_TEXT_STRING,
            dndcb->data, strlen(dndcb->data));
          break;

        case IMAGE:
          PtSetResource (widget, Pt_ARG_LABEL_IMAGE,
            dndcb->data, 0);
            free (dndcb->data);
          deep_free = 0;
          break;

        case IMAGE_TEXT:
          printf (
             "No image; the alternate text is: %s\n",
             (char *)dndcb->data);
          break;
      }

      if (deep_free) {
         PhFreeTransportType (dndcb->data,
            dndcb->trans_hdr->packing_type);
      }
      break;

  }

  return( Pt_CONTINUE );
}

Canceling drag and drop

The source widget can cancel the drag-and-drop operation by calling PtCancelDnd().

The widget must then clean up the transport-control structures and the packed data by calling PtReleaseTransportCtrl(). (If the drop is successfully done, the control structures are cleaned up automatically. The destination decides when to free the dropped data.)

Registering new transport types

This section includes:

To transport data other than the types automatically defined by Photon, you must define the type and register it with the transport registry, a collection of type descriptions, each of which includes:


Note: The source and destination applications must both define the data type in their transport registries before the data can be successfully transported.

A simple data structure

Let's consider a simple data structure:

typedef struct simp1 {
    int num;
    int nums_10[10];
    char name[10];
    short vals_5[5];
} Simp1_t;

This structure could easily be packed using the raw type because it doesn't make any external references (i.e. it has no pointer members). But that doesn't protect the transported data from endian differences between the source and destination. So even for this simple structure, a type description detailing its endian sensitivity is beneficial.

The type definition starts with an array of int unsigned entries that described the endian-sensitivity for each member:

static const int unsigned Simp1Endians[] = {
    Tr_ENDIAN( Simp1_t, num ),
    Tr_ENDIAN_ARRAY( Simp1_t, nums_10 ),
    Tr_ENDIAN_ARRAY( Simp1_t, vals_5 ),
    0 /* End of the endian list */
};

Note that this list must end with an entry of 0. The name member isn't endian-sensitive, so it isn't included in the list.

All types or references to types correct the endian-ness of their members based on the endian array defined for the type. The classifications of endian-sensitive members are:

Tr_ENDIAN( typedef_name, member )
int, long, short, (signed or unsigned). For example, unsigned int my_scalar.
Tr_ENDIAN_ARRAY( typedef_name, member )
Array of short or int entries. For example, short short_nums[10].
Tr_ENDIAN_REF( typedef_name, member, num )
A reference to endian scalars. For example, int *nums.

Having defined the endian list for our simple data type, let's create the definition to go into the transport registry:

static const PhTransportRegEntry_t Simp1TransDef = {
    "simp1",
    Ph_PACK_STRUCT,
    sizeof( Simp1_t ),
    0,
    NULL,
    &Simp1Endians,
    NULL
};

The PhTransportRegEntry_t structure includes the following members:

char *type
The name of the type being registered.
int unsigned packing
The packing method to be used (one of Ph_PACK_RAW, Ph_PACK_STRING, or Ph_PACK_STRUCT).
int unsigned size
The size, in bytes, of the data type.
int unsigned num_fixups
The number of entries in the fixups arrays.
PhTransportFixupRec_t const *fixups
A list of instructions for dealing with references to data outside the type being defined. We'll discuss this later.
int unsigned const *endians
The zero-terminated array of endian information described above.
int unsigned const *clear_refs
The zero-terminated array of members that should be cleared (i.e. set to NULL) when this type is unpacked.

To register this newly defined type, call PhRegisterTransportType():

PhRegisterTransportType( &Simp1TransDef );

This new type, simp1, can now be used with any of the transport functions to pack or unpack data.

The destination application doesn't need to concern itself with the endian orientation of the source. When the destination unpacks this type, the transport mechanism automatically corrects the endian-ness using the endian definition in the registered transport type. This is particularly beneficial in a multiplatform, networked environment. If the transport mechanism is used to write binary configuration files, the same files can be used by applications regardless of the endian orientation of the machine they are running on.

A more complicated structure

You'll frequently need to transport more complex data types that have references to data external to themselves (pointer members). These members need special handling when performing packing and unpacking operations. In order for these members to get the treatment they deserve, they must be described in the fixup member of the entry in the transport registry.

Here's a more complicated structure:

typedef struct simp2 {

    /* Scalar and reference to a scalar array */
    int num_ref_vals;
    int *ref_vals;

    /* Scalar array */
    int nums_10[10];

    /* Scalar array (not endian sensitive) */
    char first_name[10];

    /* Reference to a string */
    char *last_name2;

    /* Scalar array */
    short vals_5[5];

    /* Registered type member */
    Simp1_t simp1_instance;

    /* Registered type member array */
    Simp1_t simp1_array[4];

    /* Reference to a registered type */
    Simp1_t *simp1_reference;

    /* Scalar and reference to a registered
       type array */
    int     num_simps;
    Simp1_t *ref_simp1_array;

    /* Scalar and reference to a registered
       type reference array */
    int     num_simp_refs;
    Simp1_t **ref_simp1_ref_array;

    /* Two scalars and a reference to arbitrarily
       sized data */
    short bm_height;
    int bm_bpl;
    char *bitmap;

    /* Something we don't want packed, but want
       cleared when unpacking */
    char *dont_pack_this;

} Simp2_t;

Clear-references list

Here's the clear_refs list for this structure:

static const int unsigned Simp2ClearRefs[] = {
    offsetof( Simp2_t, dont_pack_this ),
    0 /* End of the clear-refs list */
    };

Endian list

Here's the endian list for this structure:

static const int unsigned Simp2Endians[] = {
    Tr_ENDIAN( Simp2_t, num_ref_vals ),
    Tr_ENDIAN_REF( Simp2_t, ref_vals ),
    Tr_ENDIAN_ARRAY( Simp2_t, nums_10 ),
    Tr_ENDIAN_ARRAY( Simp2_t, vals_5 ),
    0 /* End of the endian list */
    };

Here's the full list of endian manifests for each type of member:

Scalar (char)
None
Scalar (short)
Tr_ENDIAN( type, member )
  
Scalar (int)
Tr_ENDIAN( type, member )
  
Scalar Array (char)
None
Scalar Array (short)
Tr_ENDIAN_ARRAY( type, member )
  
Scalar Array (int)
Tr_ENDIAN_ARRAY( type, member )
  
Reference (char)
None
Reference (short)
Tr_ENDIAN_REF( type, member )
  
Reference (int)
Tr_ENDIAN_REF( type, member )
  
Reference (char array)
None
Reference (short array)
Tr_ENDIAN_REF( type, member )
  
Reference (int array)
Tr_ENDIAN_REF( type, member )
  
Simple structure
List each endian-sensitive member of the member structure
Registered type
None
Reference to registered type
None

For example, for a Sample_t structure:

int i;
Tr_ENDIAN( Sample_t, i )
int array[7];
Tr_ENDIAN_ARRAY( Sample_t, array )
short *short_nums;
Tr_ENDIAN_REF( Sample_t, short_nums )
int *long_nums;
Tr_ENDIAN_REF( Sample_t, long_nums )
struct my_simp ms;
Tr_ENDIAN( Sample_t, ms.width ),
Tr_ENDIAN( Sample_t, ms.height )

Fixup list

The Simp2_t structure given earlier includes some entries that reference data outside the structure. These elements need PhTransportFixupRec_t entries in the fixup list to tell the transport mechanism how to get the data:

static const PhTransportFixupRec_t
Simp2Fixups[] = {
    Tr_REF_ARRAY( Simp2_t, ref_vals,
                  Tr_FETCH( Simp2_t, num_ref_vals ) ),
    Tr_STRING( Simp2_t, name2 ),
    Tr_TYPE( Simp2_t, simp1_instance ),
    Tr_TYPE_ARRAY( Simp2_t, simp1_array ),
    Tr_REF_TYPE( Simp2_t, simp1_reference ),
    Tr_REF_TYPE_ARRAY(
        Simp2_t, ref_simp1_array,
        Tr_FETCH( Simp2_t, num_simps ) ),
    Tr_REF_TYPE_REF_ARRAY(
        Simp2_t, ref_simp1_ref_array,
        Tr_FETCH( Simp2_t, num_simp_refs ) ),
    Tr_ALLOC( Simp2_t, bitmap,
              Tr_FETCH( Simp2_t, bm_bpl ), '*',
              Tr_FETCH( Simp2_t, bm_height ) )
    };

When defining a fixup entry, you might need to use information within the structure that the entry is defining. In these circumstances, use this manifest:

Tr_FETCH( type, member )
This manifest causes the value of the specified member to be used at runtime when data is being packed or unpacked. Also, any members that are defined via other registered types are automatically endian corrected using the endian definition from that type's transport registry entry.

Here's the full list of fixup manifests:

Scalar
None.
Scalar Array
None.
Reference (string)
Tr_STRING( type, member )
  
Reference (scalar array)
Tr_REF_ARRAY( type, member, number_of_elements )
  
Registered type
Tr_TYPE( type, member, type_name )
  
Registered type array
Tr_TYPE_ARRAY( type, member, type_name )
  
Reference (registered type)
Tr_REF_TYPE( type, member, type_name )
  
Reference (registered type array)
Tr_REF_TYPE_ARRAY( type, member, num_elements, \
                   type_name )
  
Reference( registered type reference array )
Tr_REF_TYPE_REF_ARRAY( type, member, \
                       num_elements, type_name )
  

Here are some sample members and their fixup manifests:

char *name;
Tr_STRING( Sample_t, name )
  
int num_nums;
int *int_array;
Tr_REF_ARRAY( Sample_t, int_array, \
              Tr_FETCH( Sample_t, num_nums) )
  

or if the number is known:

Tr_REF_ARRAY( Sample_t, int_array, 7 )
  
Simp1_t simple_instance
Tr_TYPE( Sample_t, simple_instance, "simp1" )
  

or, as a single instance if it's just an array of one:

Tr_TYPE_ARRAY( Sample_t, simple_instance, \
               "simp1" )
  
Simp1_t simple_array[5]
Tr_TYPE_ARRAY( Sample_t, simple_array, "simp1" )
  
Simp1_t *simp1_ref
Tr_REF_TYPE( Sample_t, simp1_ref, "simp1" )
  
short num_simp1s;
Simp1_t *simp1_ref
Tr_REF_TYPE_ARRAY( Sample_t, simp1_ref, \
    Tr_FETCH( Sample_t, num_simp1s ), "simp1" )
  
short num_simp1s;
Simp1_t **simp1_ref;
Tr_REF_TYPE_REF_ARRAY( Sample_t, simp1_ref, \
    Tr_FETCH( Sample_t, num_simp1s ), "simp1" )
  

Registry entry

Finally, here's the registry entry for Simp2_t:

static const PhTransportRegEntry_t
Simp2TransDef = {
    "simp2",
    Ph_PACK_STRUCT,
    sizeof( Simp2_t ),
    sizeof(Simp2Fixups)/sizeof(Simp2Fixups[0]),
    &Simp2Fixups,
    &Simp2Endians,
    &Simp2ClearRefs
    };

Transport functions

This section describes the low-level functions and data types that deal with the transport mechanism. Some functions are called by the application that's the source of the data, some are called by the destination, and some are called by both.

Both applications

Both applications use these:

PhTransportRegEntry_t
Data structure that describes data to be transported
PhRegisterTransportType()
Add a new transport type to the transport registry
PhFindTransportType()
Find a transport type in the transport registry

Source application

The source application uses these, in roughly this order:

PhTransportCtrl_t
Control structure for the Photon transport mechanism
PhCreateTransportCtrl()
Allocate a PhCreateTransportCtrl() structure
PhTransportType()
Pack data into a PhTransportCtrl_t structure
PhTransportFindLink()
Search a linked list of transport data for some specific data
PhTransportLink_t
Entry in a linked list of transport data
PhLinkTransportData()
Add transport data to a linked list
PhGetNextInlineData()
Get the data for the next entry in a linked list of transport data
PhGetTransportVectors()
Build an I/O vector of data to be transported
PhFreeTransportType()
Free data associated with a transport registry entry
PhReleaseTransportCtrl()
Free a PhTransportCtrl_t structure

These are low-level functions that you'll probably never need to call directly:

PhAllocPackType()
Allocate a buffer and pack transport data into it
PhPackEntry()
Pack transport data, given a transport registry entry
PhPackType()
Pack transport data, given the type of data

Destination application

The destination application uses these, in roughly this order:

PhGetAllTransportHdrs()
Extract all the headers from a buffer of packed transport data
PhGetTransportHdr()
Extract the header from a buffer of packed transport data
PhGetNextTransportHdr()
Get the next header from a buffer of packed transport data
PhLocateTransHdr()
Look for specific data in a linked list of transport headers
PhMallocUnpack()
Unpack transport data, using a custom memory-allocation function
PhUnpack()
Unpack transport data
PhUnlinkTransportHdr()
Remove an entry from a linked list of transport headers
PhReleaseTransportHdrs()
Free a linked list of headers for packed transport data