Events

The interactions between applications, users and the Photon server are represented by data structures called events.

This chapter discusses:

Event information is stored in structures of type PhEvent_t; see the Photon Library Reference.

Pointer events

Most of the time, you can use a widget's callbacks to handle what the user does while pointing to it. If you're working with event handlers, you'll need to know what events Photon emits.

Pressing a button

When you press the pointer button, Photon emits a Ph_EV_BUT_PRESS event to the widget that currently has focus.

Releasing a button

When you release the button, Photon emits two Ph_EV_BUT_RELEASE events:

The real release hits whatever the mouse points to when you release the button. The phantom release always goes to the same region (and position) that received the press.

In other words, if your widget saw the press, it also sees the phantom release. And depending on where the mouse was pointing to, you may or may not get the real release. If your widget gets both the real and phantom releases, the real one always comes first.

Multiple clicks

Whenever you press or release the mouse button, the event includes the click count. How can your application determine that you clicked, instead of double clicked?

There's a click counter in the event data that's associated with Ph_EV_BUT_PRESS and Ph_EV_BUT_RELEASE events; to get this data, call PhGetData(). The data for these events is a structure of type PhPointerEvent_t (see the Photon Library Reference for details); its click_count member gives the number of times that you clicked the mouse button.

If you keep clicking quickly enough without moving the mouse, the counter keeps incrementing. If you move the mouse or stop clicking for a while, the counter resets and Photon emits a Ph_EV_BUT_RELEASE event with a subtype of Ph_EV_RELEASE_ENDCLICK.

In other words, the first click generates a Ph_EV_BUT_PRESS event and a pair of Ph_EV_BUT_RELEASE events (one REAL and one PHANTOM) with click_count set to 1. Then, depending on whether the user clicks again soon enough or not, you get either:

After the second click, you either get a third one or an ENDCLICK, and so on. But eventually you get an ENDCLICK — and the next time the person clicks, the click count is 1 again.

Modifier keys

If you need to determine what keys were pressed in a pointer event, call PhGetData() to get the event data that's included for Ph_EV_BUT_PRESS and Ph_EV_BUT_RELEASE events. The data for these events is a structure of type PhPointerEvent_t (described in the Photon Library Reference); check its key_mods member to determine the modifier keys that were pressed.

For example, this Pt_CB_ACTIVATE callback lists the modifier keys that were pressed when the pointer button was released:

/* 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"


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

{

   PhPointerEvent_t *event_data;

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

   if (cbinfo->event->type != Ph_EV_BUT_RELEASE) {
     printf ("Not a Ph_EV_BUT_RELEASE event\n");
   } else {
     printf ("It's a Ph_EV_BUT_RELEASE event\n");

     event_data = (PhPointerEvent_t *)
                     PhGetData (cbinfo->event);

     if (event_data->key_mods & Pk_KM_Shift )
        printf ("   Shift\n");

     if (event_data->key_mods & Pk_KM_Ctrl )
        printf ("   Ctrl\n");

     if (event_data->key_mods & Pk_KM_Alt )
        printf ("   Alt\n");

   }
   return( Pt_CONTINUE );
}

Emitting events

This section describes how to use PhEmit(), as well as:

The most general way for your application to emit an event is to call PhEmit():

int PhEmit( PhEvent_t *event,
            PhRect_t *rects,
            void *data );

The arguments are:

event
A pointer to a PhEvent_t structure. The application emitting the event needs to set the following members:

The Photon Manager sets the following members of the event structure after it has enqueued a copy of the event to an application:

rects
An array of PhRect_t structures (see the Photon Library Reference) indicating the event's initial rectangle set. If this argument is NULL, the set consists of a single rectangle corresponding to the emitting region.
data
Valid data for the type of event being emitted. Each type of event has its own type of data, as described for the PhEvent_t structure in the Photon Library Reference.

If the event-specific data isn't in contiguous memory, you may find PhEmitmx() more useful than PhEmit():

int PhEmitmx( PhEvent_t *event,
              PhRect_t *rects,
              int mxparts,
              struct _mxfer_entry *mx );
    

The return codes for PhEmit() and PhEmitmx() are:

A nonnegative value
Successful completion.
-1
An error occurred; check the value of errno.

Targeting specific regions

Sometimes an application needs to target an event directly at a specific region, without making the event travel through the event space before arriving at that region. You can use an inclusive event or a direct event to ensure that the targeted region sees the event.

Inclusive event

For an inclusive event, do the following:

If you don't want an inclusive targeted event to continue through the event space, you must make the emitting region opaque to that type of event, or use a direct event instead.

Direct event

For a direct event, do the following:

Targeting specific widgets

If you want to send an event to a specific widget, you could call PhEmit() as described above, but you'll need to look after a lot of details, including making sure:

It's easier to call PtSendEventToWidget(). This function gives the event to the specified widget directly and without delay. It's much more deterministic and efficient than PhEmit().

The prototype is:

int PtSendEventToWidget( PtWidget_t *widget, 
                         PhEvent_t *event );

Emitting key events

Sometimes you might need to simulate a key press in your application. Depending on what exactly you want to achieve, you can choose from several ways of generating key events:

In both of these cases, use a PhKeyEvent_t structure as the event data. For more information, see the Photon Library Reference.

Here's an example:

static void send_key( long key )
{
    struct{
    PhEvent_t event;
    PhRect_t  rect;
    PhKeyEvent_t pevent;
    } new_event;

    PhEvent_t        event;
    PhKeyEvent_t  key_event;

    PhRect_t rect;


    rect.ul.x = rect.ul.y = 0;
    rect.lr.x = rect.lr.y = 0;

    memset( &event    , 0, sizeof(event)     );
    memset( &key_event, 0, sizeof(key_event) );

    event.type          = Ph_EV_KEY;
    event.emitter.rid   = Ph_DEV_RID;
    event.num_rects = 1;
    event.data_len      = sizeof(key_event);
    event.input_group   = 1;

    key_event.key_cap   = key;
    key_event.key_sym   = key;

    if ( isascii( key ) && isupper( key ) )
    {
        key_event.key_mods = Pk_KM_Shift;
    }

    /* Emit the key press. */

    key_event.key_flags = Pk_KF_Sym_Valid | Pk_KF_Cap_Valid |
                          Pk_KF_Key_Down;
    PhEmit( &event, &rect, &key_event );

    /* Emit the key release. */

    key_event.key_flags &= ~(Pk_KF_Key_Down | Pk_KF_Sym_Valid) ;
    PhEmit( &event ,&rect, &key_event );

    return;
}

Event coordinates

When an event is emitted, the coordinates of its rectangle set are relative to the emitting region's origin. But when the event is collected, its coordinates become relative to the collecting region's origin.

The Photon Manager ensures this happens by translating coordinates accordingly. The translation member of the PhEvent_t specifies the translation between the emitting region's origin and the collecting region's origin.

Event handlers — raw and filter callbacks

The PtWidget widget class provides these callbacks for processing events:

Pt_CB_FILTER
Invoked before the event is processed by the widget. They let you perform actions based on the event before the widget sees it. They also give you the opportunity to decide if the event should be ignored, discarded, or allowed to be processed by the widget.
Pt_CB_RAW
These callbacks are invoked after the widget has processed the event, even if the widget's class methods consume it.

These callbacks are called every time a Photon event that matches an event mask (provided by the application) is received. Since all the widget classes in the Photon widget library are descended from the PtWidget, these callbacks can be used with any widget from the Photon widget library.


Note: When you attach a raw or filter callback to a widget, the widget library creates a region, if necessary, that will pick up specific events for the widget. This increases the number of regions the Photon Manager must manage and, as a result, may reduce performance.

For this reason, use event handlers only when you have to do something that can't be done using standard widget callbacks. If you do use event handlers, consider using them only on window widgets, which already have regions.


Whenever a Photon event is received, it's passed down the widget family hierarchy until a widget consumes it. (When a widget has processed an event and prevents another widget from interacting with the event, the first widget is said to have consumed the event.)

In general, the Pt_CB_FILTER callbacks are invoked on the way down the hierarchy, and the Pt_CB_RAW callbacks are invoked on the way back up. Each widget processes the event like this:

  1. The widget's Pt_CB_FILTER callbacks are invoked if the event type matches the callback's mask. The callback's return code indicates what's to be done with the event:
    Pt_CONSUME
    The event is consumed, without being processed by the widget's class methods.
    Pt_PROCESS
    The widget's class methods are allowed to process the event.
    Pt_IGNORE
    The event bypasses the widget and all its descendants, as if they didn't exist.
  2. If the widget is sensitive to the event, and the Pt_CB_FILTER callback permits, the widget's class method processes the event. The class method might consume the event.
  3. If the widget consumes the event, the widget's Pt_CB_RAW callbacks are invoked if the event type matches the callback's mask. The raw callbacks of the widget's parents aren't called.
  4. If the widget doesn't consume the event, the event is passed to the widget's children, if any.
  5. If no widget consumes the event, the event is passed back up the family hierarchy, and each widget's Pt_CB_RAW callbacks are invoked if the event type matches the callback's mask.

    The value returned by a widget's Pt_CB_RAW callback indicates what's to be done with the event:

    Pt_CONSUME
    The event is consumed and no other raw callbacks are invoked as the event is passed up to the widget's parent.
    Pt_CONTINUE
    The event is passed up to the widget's parent.

Note: If a widget is disabled (i.e. Pt_BLOCKED is set in its Pt_ARG_FLAGS), the raw and filter callbacks aren't invoked. Instead, the widget's Pt_CB_BLOCKED callbacks (if any) are invoked.

Let's look at a simple widget family to see how this works. Let's suppose you have a window that contains a pane that contains a button. Here's what normally happens when you click on the button:

  1. The window's Pt_CB_FILTER callbacks are invoked, but don't consume the event. The window's class methods don't consume the event either.
  2. The event is passed to the pane. Neither its Pt_CB_FILTER callbacks nor its class methods consume the event.
  3. The event is passed to the button. Its Pt_CB_FILTER callbacks don't consume the event, but the class methods do; the appropriate callback (e.g. Pt_CB_ACTIVATE) is invoked.
  4. The button's Pt_CB_RAW callbacks are invoked for the event.
  5. The pane's and window's Pt_CB_RAW callbacks aren't invoked because the button consumed the event.

If the pane's Pt_CB_FILTER callback says to ignore the event:

  1. The window processes the event as before.
  2. The pane's Pt_CB_FILTER callbacks say to ignore the event, so the pane and all its descendants are skipped.
  3. There are no more widgets in the family, so the window's Pt_CB_RAW callbacks are invoked.

For information on adding event handlers, see:

Collecting events

Most applications collect events by calling PtMainLoop(). This routine processes Photon events and supports work procedures and input handling.

If your application doesn't use widgets, you can collect events:

However, writing your own mainloop function isn't a trivial matter; it's easier to create at least a disjoint widget (such as PtRegion or PtWindow) and then use PtMainLoop().

PhGetRects() extracts the rectangle set, and PhGetData() extracts the event's data portion.


Note: A region can collect a given event only if portions of the region intersect the event, and the region is sensitive to that type of event.

Event compression

The Photon Manager compresses drag, boundary, and pointer events. That is, if an event of that type is pending when another event arrives, the new event will be merged with the unprocessed events. As a result, an application sees only the latest values for these events and is saved from collecting too many unnecessary events.

Dragging

If you need to capture mouse coordinates, for example to drag graphical objects in your application, you'll need to work with events.


Note: If you want to transfer arbitrary data within your application or between applications, see the Drag and Drop chapter.

There are two types of dragging:

outline dragging
The user sees an outline while dragging. When the dragging is complete, the application repositions the widget.
opaque dragging
The application moves the widget as the dragging progresses.

Dragging is done in two steps:

  1. Initiating the dragging, usually when the user clicks on something.
  2. Handling drag (Ph_EV_DRAG) events.

These steps are discussed in the sections that follow.

Initiating dragging

Where you initiate the dragging depends on how the user is meant to drag widgets. For example, if the user holds down the left mouse button on a widget to drag it, initiate dragging in the widget's Arm (Pt_CB_ARM) or Outbound (Pt_CB_OUTBOUND) callback. Make sure that Pt_SELECTABLE is set in the widget's Pt_ARG_FLAGS resource.

Dragging is started by calling the PhInitDrag() function:

int PhInitDrag( PhRid_t rid,
                unsigned flags,
                PhRect_t *rect,
                PhRect_t *boundary,
                unsigned int input_group,
                PhDim_t *min,
                PhDim_t *max,
                const PhDim_t *step,
                const PhPoint_t *ptrpos,
                const PhCursorDescription_t *cursor );

The arguments are used as follows:

rid
The ID of the region that rect and boundary are relative to. You can get this by calling PtWidgetRid().
flags
Indicate whether outline or opaque dragging is to be used, and which edge(s) of the dragging rectangle track the pointer, as described below.
rect
A PhRect_t structure (see the Photon Library Reference) that defines the area to drag.
boundary
Rectangular area that limits the dragging
input_group
Get this from the event in the callback's cbinfo parameter by calling PhInputGroup().
min, max
Pointers to PhDim_t structures (see the Photon Library Reference) that define the minimum and maximum sizes of the drag rectangle.
step
Dragging granularity.
ptrpos
If not NULL, it's a pointer to a PhPoint_t structure (see the Photon Library Reference) that defines the initial cursor position for the drag. Applications should take it from the event that makes them decide to start a drag. If the cursor moves from that position by the time your PhInitDrag() reaches Photon, your drag is updated accordingly. In other words, Photon makes the drag behave as if it started from where you thought the cursor was rather than from where it actually was a few moments later.
cursor
If not NULL, defines how the cursor should look while dragging.

If Ph_DRAG_TRACK is included in flags, then opaque dragging is used; if Ph_DRAG_TRACK isn't included, outline dragging is used.

The following flags indicate which edge(s) of the dragging rectangle track the pointer:

Outline dragging

The following example shows an Arm (Pt_CB_ARM) callback that initiates outline dragging:

/* Start dragging a widget                               */

/* 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 "globals.h"
#include "abimport.h"
#include "proto.h"

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

{
    PhDim_t *dimension;
    PhRect_t rect;
    PhRect_t boundary;

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

    /* Set the dragging rectangle to the position and size of
       the widget being dragged. */

    PtWidgetExtent (widget, &rect);

    /* Set the boundary for dragging to the boundary of the
       window. */

    PtGetResource (ABW_base, Pt_ARG_DIM, &dimension, 0); 
    boundary.ul.x = 0;
    boundary.ul.y = 0;
    boundary.lr.x = dimension->w - 1;
    boundary.lr.y = dimension->h - 1;

    /* Initiate outline dragging (Ph_DRAG_TRACK isn't 
       specified). */

    PhInitDrag (PtWidgetRid (ABW_base), 
                Ph_TRACK_DRAG,
                &rect, &boundary,
                PhInputGroup( cbinfo->event ),
                NULL, NULL, NULL, NULL, NULL );

    /* Save a pointer to the widget being dragged. */

    dragged_widget = widget;

    return( Pt_CONTINUE );
}

The above callback is added to the Arm (Pt_CB_ARM) callback of the widget to be dragged. It can be used for dragging any widget, so a pointer to the widget is saved in the global variable dragged_widget.

Opaque dragging

If you want to use opaque dragging, add the Ph_DRAG_TRACK flag to the call to PhInitDrag():

PhInitDrag( PtWidgetRid (ABW_base), 
            Ph_TRACK_DRAG | Ph_DRAG_TRACK,
            &rect, &boundary,
            PhInputGroup( cbinfo->event ),
            NULL, NULL, NULL, NULL, NULL );

Handling drag events

To handle drag (Ph_EV_DRAG) events, you need to define a Raw (Pt_CB_RAW) or Filter (Pt_CB_FILTER) callback.


Note: The raw or filter callback must be defined for the widget whose region was passed to PhInitDrag(), not for the widget being dragged. For the example given, the Raw callback is defined for the base window.

As described in Event handlers — raw and filter callbacks in the Editing Resources and Callbacks in PhAB chapter, you use an event mask to indicate which events your callback is to be called for. For dragging, the event is Ph_EV_DRAG. The most commonly used subtypes for this event are:

Ph_EV_DRAG_START
The user has started to drag.
Ph_EV_DRAG_MOVE
The dragging is in progress (opaque dragging only).
Ph_EV_DRAG_COMPLETE
The user has released the mouse button.

Outline dragging

If you're doing outline dragging, the event subtype you're interested in is Ph_EV_DRAG_COMPLETE. When this event occurs, your callback should:

  1. Get the data associated with the event. This is a PhDragEvent_t structure that includes the location of the dragging rectangle, in absolute coordinates. For more information, see the Photon Library Reference.
  2. Calculate the new position of the widget, relative to the dragging region. This is the position of the upper left corner of the dragging rectangle, translated by the amount given in the event's translation field.
  3. Set the widget's Pt_ARG_POS resource to the new position.

    Note: Remember, the callback's widget parameter is a pointer to the container (the base window in the example), not to the widget being dragged. Make sure you pass the correct widget to PtSetResources() or PtSetResource() when setting the Pt_ARG_POS resource.

For example, here's the Raw callback for the outline dragging initiated above:

/* Raw callback to handle drag events; define this 
   for the base window. */

/* 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 "globals.h"
#include "abimport.h"
#include "proto.h"

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

{
    PhDragEvent_t *dragData;
    PhPoint_t new_pos;

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

    /* Ignore all events until dragging is done. */

    if (cbinfo->event->subtype != Ph_EV_DRAG_COMPLETE)
    {
      return (Pt_CONTINUE);
    }

    /* Get the data associated with the event. */

    dragData = PhGetData (cbinfo->event);
    
    /* The rectangle in this data is the absolute 
       coordinates of the dragging rectangle. We want to 
       calculate the new position of the widget, relative to 
       the dragging region. */

    new_pos.x = dragData->rect.ul.x 
                + cbinfo->event->translation.x;
    new_pos.y = dragData->rect.ul.y 
                + cbinfo->event->translation.y;
    printf ("New position: (%d, %d)\n", new_pos.x, new_pos.y);

    /* Move the widget. */

    PtSetResource (dragged_widget, Pt_ARG_POS, &new_pos, 0);

    return( Pt_CONTINUE );

}

Opaque dragging

The callback for opaque dragging is similar to that for outline dragging—the only difference is the subtype of event handled:

if (cbinfo->event->subtype != Ph_EV_DRAG_MOVE)
{
  return (Pt_CONTINUE);
}