From owner-yacl-list Mon Jan 29 03:42:00 1996
Received: (from majordom@localhost) by usceast.cs.sc.edu (8.6.12/8.6.9) id DAA25144 for yacl-list-outgoing; Mon, 29 Jan 1996 03:34:46 -0500
Received: from gprhp.insa-lyon.fr (gprhp.insa-lyon.fr [134.214.76.158]) by usceast.cs.sc.edu (8.6.12/8.6.9) with SMTP id DAA25139 for <yacl-list@cs.sc.edu>; Mon, 29 Jan 1996 03:34:26 -0500
Message-Id: <199601290834.DAA25139@usceast.cs.sc.edu>
Received: by gprhp.insa-lyon.fr
	(1.37.109.4/16.2) id AA25012; Mon, 29 Jan 96 09:25:36 +0100
From: Ludovic Brenta <brenta@gprhp.insa-lyon.fr>
Subject: ANNOUNCE: accelerator keys for YACL
To: yacl-list@cs.sc.edu
Date: Mon, 29 Jan 96 9:25:35 MET
Cc: brenta@gprhp.insa-lyon.fr
Mailer: Elm [revision: 70.85]
Sender: owner-yacl-list@usceast.cs.sc.edu
Precedence: bulk
Status: OR


Hello all YACLers,

I have been working at home on support for accelerator keys since around
Christmas. Due to illness and lack of free time, I only finished this
week-end.  Here is what I came up with; you are free to use it any way
you see fit, and Sridhar is specifically encouraged to include it in the
next beta of YACL.

The accelerator classes, UI_ControllerWithAccel and UI_ApplicationWith-
Accel, only work in OS/2 currently (EMX-GCC), but are written in the
YACL spirit of portability; see below.

Here goes...



   In this  paper  I  present  my  implementation  of  accelerator   in  a
YACL application.  Although the implementation only supports OS /2, it  is
designed to facilitate porting to other platforms.  Ease of use  has  also
been accounted for, resulting in a sheme not very different from that used
in menus.


I- THE PROBLEM

   Accelerators are a quick and efficient means to interact with an appli-
cation without having to reach for the mouse and click on menu items. They
are extensively used throughout every GUI I've come across, and are  espe-
cially handy in applications involving a lot of typing such as  word  pro-
cessors or source code editors.	This was the one feature missing  in  YACL
that I wanted to have, and I thought I could implement them myself  as	an
exercise to learn more about YACL.
   I based all my programming on the <uidemo/menu0>  sample  program. Spe-
cifically, I set up a menu and a MenuDriver class whose constructor regis-
tered various methods as event dependents on MenuItems in the menu.
   The first thing I did then was to just  override  HandleEvent()  in	my
main window class and access the platform-dependent  message  structure in
each Event_KeyTyped I received to determine if it was an  accelerator key,
then directly call the MenuDriver method  I  knew  would  be in  charge of
handling that accelerator.  Then I looked upon what I had done, and I  saw
that it was bad.
   Conceptually, an accelerator table can be seen as a mapping  from  "key
combinations" to "events to be sent" to "MenuItems".  Here the  term  "key
combinations" is used loosely; it refers both to single keys  (like  func-
tion keys) and combinations (like Ctrl+O).  It  should	be  specified	as
easily as in the DispatcherStruct used to setup event dependents  on  menu
events (see page 277 of the book).
   But in my first implementation, the accelerator table was changed  into
a switch statement in the HandleEvent() method of my main window; further-
more, the HandleEvent() method accessed directly the native event structu-
re to perform its task. Clearly, this was not as portable and general as I
wanted.	So I devised a  much better  means  of  implementing   accelerator
tables.



II- HOW YACL DISPATCHES EVENTS TO MenuItems

   I first had to gain insight of how the events were translated and  dis-
pached by the YACL framework, somehow resulting  in  a  MenuDriver  method
being called.   Most of this job is done by  the  Controller.  Its methods
include the following, in the calling order:

{ Begin Portable code, in <ui/cntroler.cxx> }

o  EventLoop(), a generalized event loop;

{ End Portable Code }

{ Begin platform-dependent code, in <ui/os2evt.cxx> for OS/2 }

o  ProcessNativeEvents(); this method contains the actual event loop.

o  _DoOneEvent() (private method); called upon receiving every platform e-
   vent. Basically, this method just  calls   TranslateNativeEvent(), then
   DispatchNativeEvent().

o  TranslateNativeEvent() (private method). This is where all the work is.
   This method first translates the  platform  event  into  a  YACL event,
   using a table (a static array  that  is  defined  in  <ui/os2evt.cxx>).
   Then, it does some event-specific stuff. (This is  where  the  switch()
   with WM_ messages takes place).   One of the main things to  do is   to
   identify the recipient VisualObject that is to receive  the  event  and
   put it into the event's Destination().  After that, all events are YACL
   events only.

o  DispatchNativeEvent() basically calls DispatchEvent(),  except  in  the
   case of Event_MouseMove where it does some additional stuff.

{ End platform-dependent code }

{ Back to portable code }

o  DispatchEvent() basically calls HandleEvent() on the VisualObject whose
   address is in the event's Destination(), and on all its  parents  until
   one of them returns TRUE (see page 213 of the book).

{ End. }


III- THE SOLUTION

   III- Hooking new code to the library

   What I wanted to do was send an   event  to  the desired  MenuItem upon
receipt of a keyboard event that turned out   to  be an   accelerator key.
Clearly, all this work would have to be done in TranslateNativeEvent(). In
order not to intrude into the library itself, I chose to derive a class of
my own from UI_Controller and  provide  an  override   to TranslateNative-
Event().  The new class is simply called  UI_ControllerWithAccel,  and  is
declared in "ControllerWithAccel.h" and implemented in  "ControllerWithAc-
cel.c++".
   Several steps had to be taken for this to work:

o  Make UI_Controller::TranslateNativeEvent() virtual, so that the overri-
   ding mechanism worked;

o  Make UI_ControllerWithAccel a friend to UI_Event (I was  surprised that
   friendliness wasn't inherited);

o  Derive the UI_Application class so that its Initialize() method creates
   an instance of UI_ControllerWithAccel rather  than  UI_Controller;  the
   new class is called UI_ApplicationWithAccel (files  "ApplicationWithAc-
   cel.h" and "ApplicationWithAccel.c++").

o  Explicitly create the instance of UI_ApplicationWithAccel at  the  very
   beginning of the program by writing:

   static UI_Application* _TheApplication = new UI_ApplicationWithAccel;

   With all this done, let's get to the heart of it.



   III-2. The accelerator table

   An efficient means of specifying the correspondence between  key combi-
nations, MenuItems and event types had  to  be   devised.  Hopefully, YACL
already includes the CL_Map template class to do such  things;  I  used  a
predefined instantiation of it, CL_IntPtrMap, in a way  that  allows  much
flexibility.
   The key combination is coded as a long integer.  The lower 16 bits  re-
present the key itself, or the virtual key code  for   special  keys (e.g.
function keys, or INSERT or DELETE keys). The higher 16 bits contain flags
defined as follows :

      enum Key_Flag
      {
         Key_Virtual = 0x00010000,
         Key_Shift   = 0x00020000,
         Key_Ctrl    = 0x00040000,
         Key_Alt     = 0x00080000
      };

   These flags are simply OR-ed with the key  code  when  the   appropriate
flags are  found in the platform-dependent message structure. The resulting
"extended key code" serves as the key to the aforementioned Map.
   What serves as the value in the Map is a   private   class  derived from
CL_Object (since the CL_IntPtrMap maps long integers   to  CL_Object  poin-
ters).  The new class is defined inside UI_ControllerWithAccel as follows:

      // This class is for internal use only, so it is made private to
      // UI_ControllerWithAccel
      private:
      class UI_AcceleratorTarget : public CL_Object
      {
      public:
         UI_VisualObject* target;   // recipient of the event to be sent
         UI_EventType eventType;    // type of event to be sent to the
                                    // recipient
      };

   Finally, a new data member was added to UI_ControllerWithAccel:

      protected:
      CL_IntPtrMap _accelTable;




   III-2.Specifying accelerator tables

   The UI_ControllerWithAccel class includes new methods to add  or remove
accelerators to its _accelTable member; let's see them in turn.

bool AddAccelerator(long, UI_VisualObject*, UI_EventType);
   Adds a single accelerator to the controller.

bool RemoveAccelerator(long);
   Removes the accelerator from the controller.

bool AddAcceleratorTable(UI_AcceleratorTable*, UI_Menu&);
   Adds several accelerators for the specified menu in one operation.  The
   first parameter is an array of UI_AcceleratorTable  structures, defined
   as follows in "ControllerWithAccel.c++":

      struct UI_AcceleratorTable
      {
         long key;
         UI_ViewID targetID;
         UI_EventType eventType;
      };

   The last element in the array must have a null key.

void RemoveAllAccelerators();
   Calls DestroyContents() on the accelerator table; this  also   delete's
   all pointed-to objects in the Map values. (This is  also  done  by  the
   destructor of the class, so you don't normally need to call this method
   explicitly).

   All these methods return TRUE on success   and  FALSE   on error; also,
since the Map stores only pointers to the AcceleratorTargets, they  manage
the creation and destruction of these objects on the heap.
   In the sample program I provide at the end of  this  paper,  the  Menu-
Driver calls UI_ControllerWithAccel::AddAcceleratorTable just after it has
registered the event dependent methods on the menu items.




   III-3. Translation between accelerators and events

   The new   version   of  TranslateNativeEvent()  first  calls its parent
(UI_Controller::TranslateNativeEvent()), then traps  the  Event_KeyTyped's
to do the actual translation: it first computes the  "extended  key  code"
associated with the key being typed  (this  is   platform-dependent), then
just sets the event type and destination to the ones specified in the  as-
sociated AcceleratorTarget object:

      UI_AcceleratorTarget* target;
      target = (UI_AcceleratorTarget*) _accelTable[vKey];

      if(target && target->target->IsEnabled())
      {
         e._type = target->eventType;
         e._dest = e._origin = target->target;
      }

   Since this is done just before  DispatchNativeEvent()  is  called,  the
event is dispatched to the MenuItem just as if it were clicked on with the
mouse.




IV- OTHER CONSIDERATIONS


   IV-1. Additional features

   The accelerator keys may be used even if there is  no menu, or if there
are several menus (both MenuBars and PopupMenus) in the application.  This
is because the UI_ControllerWithAccel::AddAccelerator() method accepts any
valid pointer to a VisualObject; the ControllerWithAccel simply routes ac-
celerator keys to the proper VisualObject.
   A VisualObject usually doesn't  need  to   know   whether  the event it
receives comes from an accelerator or not;  however,   it  may access  the
native event structure and find that it contains a WM_CHAR message, rather
than a  WM_MENUSELECT message (in OS/2).



   IV-2. Porting to other platforms

   The non-portable code has been carefully isolated and is located in the
"ControllerWithAccel.h" and "ControllerWithAccel.c++" files.
   The .h file #defines virtual key  codes  for  use   in  the accelerator
tables; these must map to the corresponding key codes on the desired plat-
form.
   The .c++ accesses the platform-dependent event structure in its  Trans-
lateNativeEvent() method.   This is not different from the way YACL itself
is implemented.

