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 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