This document covers the following topics:
Both clipboard and drag/drop data transfer make use of a logical
clipboard at the Natural language level, allowing a single set of methods to
handle both requirements. The PROCESS GUI
actions
for handling the logical clipboard are as follows:
OPEN-CLIPBOARD
,
SET-CLIPBOARD-DATA
,
CLOSE-CLIPBOARD
,
GET-CLIPBOARD-DATA
and INQ-FORMAT-AVAILABLE
.
Each Natural process has exactly one logical clipboard, which is why it is
referred to in the product documentation as the "local"
clipboard.
OPEN-CLIPBOARD
is the first step in building up the logical clipboard data. It takes an
optional parameter (owner window), which is typically the handle of the control
sourcing the data. If anything was previously on the logical clipboard, this
action empties it. Note that you don't need to call this for drag and drop,
because Natural does this implicitly before raising the
BEGIN-DRAG
event (see below).
SET-CLIPBOARD-DATA
puts the actual data on the logical clipboard. The first parameter is the
clipboard format, specified as a string. There are two pre-defined formats
(defined in NGULKEY1 as CF-TEXT and CF-FILELIST), which are used for standard
text transfer, and lists of files (suitable for data exchange with the Windows
Explorer and many other applications) respectively. In addition, an arbitrary
string (which should not begin with a dgit) should be used to indicate a
private clipboard format that only Natural applications can understand (they
just need to know the format string so they can pass it to
GET-CLIPBOARD-DATA
to retrieve the data). The second and subsequent arguments are an arbitrary
number of data operands. These can be any combination of arrays (incl. index
ranges) or scalars (incl. dynamic and large alpha variables). Arrays are
internally expanded into their individual elements, which are then handled
individually as for scalars.
For example, the following code:
DEFINE DATA LOCAL 1 #ARR(A1/2,3) INIT (1,1)<'A'> (1,2)<'B'> (1,3)<'C'> (2,1)<'X'> (2,2)<'Y'> (2,3)<'Z'> END-DEFINE PROCESS GUI ACTION SET-CLIPBOARD-DATA WITH CF-TEXT #ARR(*,*)
is equivalent to:
PROCESS GUI ACTION SET-CLIPBOARD-DATA WITH CF-TEXT 'A' 'B' 'C' 'X' 'Y' 'Z'
For the pre-defined formats, the operands must be alphanumeric (format
A). For private formats, the data arguments can be of almost any type.
Exception: handle variables (incl. HANDLE OF OBJECT
) are not
supported, because they are process-specific. The data for private formats is
stored "as-is" (i.e., no conversion).
Note that multiple data formats can be placed on the clipboard by
performing a SET-CLIPBOARD-DATA
action for each required format. However, any call to
SET-CLIPBOARD-DATA
for a particular format replaces any data that may already exist in that
format.
Note also that the data is not placed on the Windows clipboard. This is done when the logical clipboard is closed (see below).
CLOSE-CLIPBOARD
closes the logical clipboard, and places the data on the Windows clipboard, so
that it becomes available for pasting into other applications. The data cannot
be modified by SET-CLIPBOARD-DATA
after this call. Note that this call is not necessary for drag and drop because
you usually don't need to also make the dragged data available for pasting.
Drag and drop can work directly with the logical clipboard.
GET-CLIPBOARD-DATA
is used by the application performing the paste or acting as the drop target to
retrieves the data from the drag-drop clipboard (if a drag and drop operation
is in progress) or from the Windows clipboard otherwise. The drag-drop
clipboard is a synonym for the logical clipboard belonging to the source
Natural process. SET-CLIPBOARD-DATA
,
a clipboard format is specified, followed by an arbitrary list of data operands
(with the same format type restrictions). For private formats, the operands
need not have the same format type as used in the
GET-CLIPBOARD-DATA
action. For example, you can place an integer on the clipboard and read it back
into a packed numeric (P) variable. Internally, a
MOVE
conversion is done.
Therefore, if different format types are used for setting and getting the data,
they must be MOVE
-compatible.
For pre-defined formats, where the individual data items are either
delimited by CR/LF (for CF-TEXT format) or by null-terminators (for CF-FILELIST
format), only one item is usually read into each receiving field. Exception: If
the last receiving field is a dynamic alpha variable, it receives all remaining
data items, including the delimiters. This exception allows the application to
use (for example) a single dynamic alpha variable to set and get multiple lines
of data or multiple file/directory names. Regardless of the format used, if too
many receiving operands are specified, the excess fields are reset (see the
RESET
statement). Note
that individual data fields may be skipped by using the
nX
notation. For example,
5x
skips 5 data items (where a "data item" is a
single line for CF-TEXT format).
INQ-FORMAT-AVAILABLE
is used for querying whether data is available in a given format (see
specification for syntax). It is typically used to determine whether to enable
or disable the command, or whether to display
the "no drop" cursor for drag/drop operations.
The actual clipboard data transfer has been covered above. However,
Natural allows you to define signals, menu items and toolbar items of the
special types CLICK
events. For input fields, edit areas, selection boxes and table controls, it's
obvious what Natural should do, and Natural does this implicitly. For Natural,
these commands now support list boxes and ActiveX controls. However, the
mechanism is different in this case, because it is ambiguous as to how Natural
should respond to these commands. Therefore, Natural needs some assistance from
the application. This assistance comes in the form of six new events:
CUT
,
COPY
,
PASTE
,
DELETE
,
UNDO
and CLIPBOARD-STATUS
,
all of which are suppressible (via the new
SUPPRESS-CUT-EVENT
,
SUPPRESS-COPY-EVENT
,
SUPPRESS-PASTE-EVENT
,
SUPPRESS-DELETE-EVENT
,
SUPPRESS-UNDO-EVENT
and SUPPRESS-CLIPBOARD-STATUS-EVENT
attributes). All six events are suppressed by default. The
CUT
,
COPY
,
PASTE
,
DELETE
and
UNDO
events are raised whenever the respective command is triggered. The
corresponding event suppression flags are used by Natural to decide whether to
enable or disable the corresponding command(s) in the user interface.
The CLIPBOARD-STATUS
event is sent to the focus control during idle processing to give the
application a chance to set these event suppression flags dynamically according
to the context (e.g., whether or not there is an active selection). Natural
raises this event before it queries the event suppression flags for the purpose
of clipboard command status updating). Note that these new events are
(currently) only sent to list boxes and ActiveX controls (and, of course, only
if they currently have the focus). Input fields, selection boxes, etc., are
still handled implicitly.
The CLIPBOARD-STATUS
event is only raised if there is at least one clipboard command in the user
interface that needs to be updated.
The following example shows a typical
CLIPBOARD-STATUS
event for a list box control:
DEFINE DATA LOCAL 1 #CONTROL HANDLE OF GUI 1 #FMT (A10) CONST<'MYDATAFMT'> 1 #AVAIL (L) END-DEFINE ... #CONTROL := *CONTROL /* /* Cut, Copy & Delete are enabled if an item is selected, /*or disabled otherwise /* IF #CONTROL.SELECTED-SUCCESSOR <> NULL-HANDLE #CONTROL.SUPPRESS-CUT-EVENT := NOT-SUPPRESSED #CONTROL.SUPPRESS-COPY-EVENT := NOT-SUPPRESSED #CONTROL.SUPPRESS-DELETE-EVENT := NOT-SUPPRESSEDELSE #CONTROL.SUPPRESS-CUT-EVENT := SUPPRESSED #CONTROL.SUPPRESS-COPY-EVENT := SUPPRESSED #CONTROL.SUPPRESS-DELETE-EVENT := SUPPRESSED END-IF /* /* Paste command is enabled if data is available in a /* recognized format, or disabled otherwise /* PROCESS GUI ACTION INQ-FORMAT-AVAILABLE WITH #FMT #AVAIL GIVING *ERROR /* IF #BOOL #CONTROL.SUPPRESS-PASTE-EVENT := NOT-SUPPRESSED ELSE #CONTROL.SUPPRESS-PASTE-EVENT := SUPPRESSED END-IF
Drag and drop operations can be triggered automatically (for list boxes
and bitmap controls) or manually, via the new
PERFORM-DRAG-DROP
action (typically for ActiveX controls in response to control-specific mouse
click or drag start events). For automatic drag/drop, the mouse cursor must be
over the active selection (if any). For manual drag/drop, the parameters for
PERFORM-DRAG-DROP
include the handle of the control that should receive the drag/drop events (the
drag source), and an optional flag indicating whether drag and drop should
begin immediately, or only after the user moves the mouse a system-defined
minimum number of pixels. Both automatic and manual drag/drop use the same code
internally, so the same events are received in both cases.
Drag/drop is controlled by by two new I4 attributes,
DRAG-MODE
(for drag sources) and DROP-MODE
(for drop targets). These attributes can be set to one of 8 values (defined in
NGULKEY1): DM-NONE
(no drag/drop allowed), DM-COPY
(copy allowed), DM-MOVE
(move allowed), DM-COPYMOVE
(copy and move allowed), DM-LINK
(link allowed),
DM-COPYLINK
(copy and link allowed), DM-MOVELINK
(move and link allowed), DM-COPYMOVELINK
(copy, move and link
allowed). Link operations imply that the drop target should create a link to
the source data, rather than creating a copy of it. For file operations,
desktop shortcuts are typically used (not currently explicitly supported by
Natural). Drag operations are only initiated if the source's
DRAG-MODE
attribute is set to something other that the default DM-NONE
value. In addition, the application must respond to the
BEGIN-DRAG
event (see below).
Control types capable of acting as drop targets are: ActiveX controls,
bitmap controls, list boxes, control boxes, edit areas, and dialogs (tab
controls and table controls are planned for the future but are not currently
supported). These windows are, however, only registered with OLE as drop
targets if their DROP-MODE
attribute is set to something other that the default DM-NONE
value. During a drag/drop operation, OLE automatically searches up through the
window hierarchy, starting with the window immediately under the cursor, until
it finds a window that has been registered as a drop target. This is the window
that gets the OLE drop notifications and therefore is the window that receives
the Natural drag/drop events (see below).
The new drag/drop related events are:
BEGIN-DRAG
,
END-DRAG
,
DRAG-ENTER
,
DRAG-OVER
and DRAG-LEAVE
.
In addition, the existing DRAG-DROP
event (for the Mickey Mouse non-OLE drag/drop support for bitmap controls) is
also used. All events are suppressible via the appropriate event suppression
attributes (SUPPRESS-BEGIN-DRAG-EVENT
etc.), all of which are SUPPRESSED
by default.
The BEGIN-DRAG
event (if not suppressed) is sent to the drag source on initiation of a drag
operation. The application must use the
SET-CLIPBOARD-DATA
action to place some data on the drag/drop clipboard before returning from this
event, otherwise the drag/drop operation is implicitly cancelled without the
mouse cursor having changed. Note that there is no need to call either of the
OPEN-CLIPBOARD
or CLOSE-CLIPBOARD
actions.
The END-DRAG
event (if not suppressed) is sent to the drag source after a drag/drop
operation has completed (even if the drag operation was cancelled). The main
use of this event is to delete the source data if a Move operation occurred.
The application can find out whether a Move operation has occurred by calling
the existing INQ-DRAG-DROP
action, which has been extended with two new optional integer output
parameters. The first of these new parameters indicates which mouse buttons are
currently pressed (1 = Left button, 2 = Right button, 4 = Middle button, or a
combination thereof). The second new parameter is the one we need here, and
contains the drop effect resulting from a drag/drop operation
(DM-NONE
if no drop or if the operation was cancelled,
DM-COPY
if a Copy operation was performed, DM-MOVE
if
a Move operation was performed, and DM-LINK
if a link operation
was performed.
The DRAG-ENTER
event (if not suppressed) is sent to the drop target when the drag cursor
(re-)enters the region occupied by the drop target. The application typically
responds to this event by calling the
INQ-FORMAT-AVAILABLE
action to find out if a compatible data format is available on the clipboard,
and then setting the SUPPRESS-DRAG-DROP-EVENT
attribute accordingly. The SUPPRESS-DRAG-DROP-EVENT
is important because it not only determines whether the
DRAG-DROP
event should be raised, but also informs Natural as to whether a drop should be
allowed. After raising the
DRAG-ENTER
and DRAG-OVER
events, Natural inspects the SUPPRESS-DRAG-DROP-EVENT
attribute and displays a "no drop" symbol. Otherwise, the drop
effect is determined by the combination of the drag source's
DRAG-MODE
value,
the drop target's DROP-MODE
value, and the
augmentation keys (SHIFT and CTRL) that are currently
being pressed.
The DRAG-OVER
event (if not suppressed) is frequently sent to the drop target as the drag
cursor moves over the drop target. It can be used, for example, to update the
drop emphasis (if any) as the user traverses the items within the control
and/or to update the SUPPRESS-DRAG-DROP-EVENT
attribute if the feasibility of a drop operation depends on the position within
the drop target.
The DRAG-LEAVE
event (if not suppressed) is sent to the drop target when the drag cursor
leaves the region occupied by the drop target without a drop having occurred.
This is mainly used (if at all) to remove the drop emphasis (if any) applied in
the DRAG-OVER
event.
The DRAG-DROP
event (if not suppressed) is sent to the drop target when the user performs a
drop. drag cursor leaves the region occupied by the drop target without a drop
having occurred. The application should respond to this by effectively
performing a Paste operation, using the current relative position within the
control, if necessary. Both the relative position and the type of operation can
be retrieved via the INQ-DRAG-DROP
action. The latter is returned in the new (optional) "drop effect"
parameter (see the description of the
END-DRAG
event above for more information).
For list boxes, a new "insertion mark (i)"
style can be used to indicate that a dashed horizontal line be used to indicate
the current insert position when the drag cursor is moved over the control
(assuming it is a drop target). The application cannot query the insertion mark
position directly, but can find out where to insert the data by querying the
relative position within the control via the
INQ-DRAG-DROP
action, then passing these coordinates to the
INQ-ITEM-BY-POSITION
action, as in the following example:
DEFINE DATA LOCAL 1 #Y (I4) 1 #CONTROL HANDLE OF GUI 1 #ITEM HANDLE OF GUIEND-DEFINE ... /* DRAG-DROP event: PROCESS GUI ACTION INQ-DRAG-DROP WITH 4X #Y GIVING *ERROR * IF #Y < 0 #Y := 0 END-IF #CONTROL := *CONTROL PROCESS GUI ACTION INQ-ITEM-BY-POSITION WITH #CONTROL 0 #Y #ITEM GIVING *ERROR
After the above code has executed, the variable #ITEM
contains the handle of the item immediately following the insertion point. You
can then dynamically insert one or more list box items at this position by
calling the ADD
action with the
WITH PARAMETERS
clause, setting the
SUCCESSOR
attribute to #ITEM
.
Note that the correction for negative y-coordinate in the above example
is necessary to cover the situation where the drop position is on the list
boxes top border. If no correction would be made here, #ITEM
would
be set to NULL-HANDLE
and the new list box item(s) would be added
undesirably at the end of the list instead of at the beginning if we were to
directly use #ITEM
as the SUCCESSOR
attribute, as described above.
For convenience, here is a brief overview of the steps involved in implementing drag-drop in Natural applications:
Set the DRAG-MODE
for each drag
source. If the drag source is a bitmap control, its
DRAGGABLE
attribute must also be set to TRUE
.
Set SET-CLIPBOARD-DATA
in the BEGIN-DRAG
event for each drag source to provide the transfer data.
Set the DROP-MODE
for each drop
target.
In the DRAG-ENTER
event, use the INQ-FORMAT-AVAILABLE
action to set the SUPPRESS-DRAG-DROP-EVENT
attribute to NOT-SUPPRESSED (0)
if a supported clipboard format is
available, or SUPPRESSED (1)
otherwise. If the control can also
act as a drag source and you need to prohibit drag-drop operations within the
control, call INQ-DRAG-DROP
to get the source control handle and compare it to the current control
(*CONTROL
), suppressing the drag-drop event if
both are identical.
If the effect of the drop is position-sensitive within the target
control, use the INQ-DRAG-DROP
action within the DRAG-OVER
event to get the current position, determine the item under the drag cursor
(e.g. via the INQ-ITEM-BY-POSITION
action) and set the SUPPRESS-DRAG-DROP-EVENT
attribute appropriately. Highlight the current item if desired.
If the current item was highlighted in step 5 above, unhighlight it
(if necessary) in the DRAG-LEAVE
and (potentially) DRAG-DROP
events.
Use GET-CLIPBOARD-DATA
in the DRAG-DROP
event to retrieve the transfer data and process it accordingly.
In the END-DRAG
event for the drag source, delete the source data if the drop effect returned
by INQ-DRAG-DROP
is set to DM-MOVE
.
If the drag source is an ActiveX control, call the
PERFORM-DRAG-DROP
action to initiate the drag-drop operation in response to a
"MouseDown" event (for example) if a location within
the current selection is clicked.
One of the problems in setting or retrieving data that may need to be placed or already have been placed on the Windows or drag-drop clipboard in response to a user interaction is being able to cope with an arbitrary amount of data at run-time. For example, the user may select a single, a few, or possibly even hundreds or thousands of list box items before performing a clipboard or drag-drop operation on them. With fixed-size arrays, one would have to define huge arrays to cope with the worst-case scenario, even though typically only a small percentage would be used most of the time.
There are two possible solutions to this problem available in Natural.
The first way is to use a single dynamic alpha variable to contain all items to
be set or retrieved. The application is then responsible for building up the
items (including delimiters) in the dynamic variable before calling
SET-CLIPBOARD-DATA
,
and for extracting the items from the dynamic variable after calling
GET-CLIPBOARD-DATA
.
This approach is not possible for private formats, because these are not
delimited.
The second approach is to make use of X-Arrays. For setting clipboard data, these behave similarly to fixed-size arrays, except that their size can be modified to contain exactly the number of elements needed in a specific situation. For example, if there are 17 items that need to be written to the clipboard, then you can use:
DEFINE DATA LOCAL 1 #X-ARR(A80/1:*) 1 #UPB (I4) INIT <17> END-DEFINE RESIZE ARRAY #X-ARR TO (1:#UPB) PROCESS GUI ACTION SET-CLIPBOARD-DATA WITH CF-TEXT #X-ARR(*)
instead of having to use a wastefully large fixed array, of which only a small range is used:
DEFINE DATA LOCAL 1 #ARR(A80/10000) 1 #UPB (I4) INIT <17> END-DEFINE PROCESS GUI ACTION SET-CLIPBOARD-DATA WITH CF-TEXT ARR(1:#UPB)
When retrieving clipboard data, X-Arrays are even more useful, because the application does not know in advance how many items are on the clipboard. Passing all array elements (10,000 in the above example) would be relatively slow, because all unused elements need to be reset.
However, if an X-Array is used instead, Natural automatically resizes
the array to (1:N), where N is the minimum of the number of items (remaining)
on the clipboard and the array's maximum upper bound (as defined in
DEFINE-DATA
, where
"*" indicates the maximum possible value). Note that
there are three restrictions on the use of X-Arrays in conjunction with
GET-CLIPBOARD-DATA
:
The X-Array must be the last (or only) parameter.
Only 1-dimensional X-Arrays are supported.
The X-Arrays defined range must include the element 1.
Here is an example program illustrating the use of a dynamic X-Array for retrieving clipboard data, including the use of a second X-Array to store and display the data lengths:
DEFINE DATA LOCAL 1 #FMT (A10) CONST<'MYDATAFMT'> 1 #X-ARR (A/1:*) DYNAMIC 1 #X-LEN (I4/1:*) 1 #UPB (I4) 1 #I (I4) END-DEFINE PROCESS GUI ACTION OPEN-CLIPBOARD GIVING *ERROR PROCESS GUI ACTION SET-CLIPBOARD-DATA WITH #FMT 'MIKE' 'FRED' 'JIM' 'LULU' 'FRANK' 'JANA' 'ELIZABETH' 'TONY' GIVING *ERROR PROCESS GUI ACTION CLOSE-CLIPBOARD GIVING *ERROR PROCESS GUI ACTION GET-CLIPBOARD-DATA WITH #FMT #X-ARR(*) GIVING *ERROR #UPB := *UBOUND(#X-ARR) RESIZE ARRAY #X-LEN TO (1:#UPB) FOR #I 1 #UPB #X-LEN(#I) := *LENGTH(#X-ARR(#I)) END-FOR DISPLAY #X-ARR(*) (AL=10) #X-LEN(*) / '*** END OF DATA ***' END