Drag and drop lets you drag arbitrary data within an application or between applications.
This chapter discusses:
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:
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.
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.
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:
The source widget can also cancel the operation if it wishes.
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.
The steps to follow are:
The PtTransportCtrl_t structure has a list of response data, which is automatically sent if the destination requests it. The source widget can add data to this queue by calling PtAddResponseType().
If the source widget doesn't want to pack the requestable data at this point, it must provide a callback when calling PtTransportRequestable().
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.
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; }
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).
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.
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().
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:
If the operation is canceled in this way, the library cleans up the data structures automatically.
The subtypes of a drag-and-drop event that are of interest to the destination of the operation are:
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.
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.
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 ); }
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.)
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:
The source and destination applications must both define the data type in their transport registries before the data can be successfully transported. |
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:
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:
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.
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;
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 */ };
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:
Tr_ENDIAN( type, member )
Tr_ENDIAN( type, member )
Tr_ENDIAN_ARRAY( type, member )
Tr_ENDIAN_ARRAY( type, member )
Tr_ENDIAN_REF( type, member )
Tr_ENDIAN_REF( type, member )
Tr_ENDIAN_REF( type, member )
Tr_ENDIAN_REF( type, member )
For example, for a Sample_t structure:
Tr_ENDIAN( Sample_t, i )
Tr_ENDIAN_ARRAY( Sample_t, array )
Tr_ENDIAN_REF( Sample_t, short_nums )
Tr_ENDIAN_REF( Sample_t, long_nums )
Tr_ENDIAN( Sample_t, ms.width ), Tr_ENDIAN( Sample_t, ms.height )
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:
Here's the full list of fixup manifests:
Tr_STRING( type, member )
Tr_REF_ARRAY( type, member, number_of_elements )
Tr_TYPE( type, member, type_name )
Tr_TYPE_ARRAY( type, member, type_name )
Tr_REF_TYPE( type, member, type_name )
Tr_REF_TYPE_ARRAY( type, member, num_elements, \ type_name )
Tr_REF_TYPE_REF_ARRAY( type, member, \ num_elements, type_name )
Here are some sample members and their fixup manifests:
Tr_STRING( Sample_t, name )
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 )
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" )
Tr_TYPE_ARRAY( Sample_t, simple_array, "simp1" )
Tr_REF_TYPE( Sample_t, simp1_ref, "simp1" )
Tr_REF_TYPE_ARRAY( Sample_t, simp1_ref, \ Tr_FETCH( Sample_t, num_simp1s ), "simp1" )
Tr_REF_TYPE_REF_ARRAY( Sample_t, simp1_ref, \ Tr_FETCH( Sample_t, num_simp1s ), "simp1" )
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 };
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 use these:
The source application uses these, in roughly this order:
These are low-level functions that you'll probably never need to call directly:
The destination application uses these, in roughly this order: