From: deveau@oceanroutes.ns.ca ("Terry J. Deveau")
Newsgroups: comp.windows.x.motif
Subject: popdown menu
Date: 1 Sep 92 20:32:40 GMT
Distribution: inet
Organization: Oceanroutes Canada Inc.

I'm looking for people who are willing to give me some feedback
regarding the style and desireability of a new Motif menu type.

At the end of this message is a short demonstration program that 
includes the three traditional Motif menus (pulldown, popup,
and option), plus an ordinary pushbutton, along with a new
type of menu that I call "popdown".  You can run the program
and compare the behaviour of these five selection mechanisms.

Am I violating the Motif Style Guidelines here? (I don't think so, but
I'm not really sure).  Has anyone else found a need to resort to
this kind of thing?  Any other comments?

The table below summarizes the behaviour I would like to have for the
selection mechanisms; a + indicates non-default behaviour that I know
how to achieve via extra translations, etc.; a - indicates behaviour 
that I want, but can't figure out how to get.  Hints on how to obtain
the -'ed behaviours would be appreciated.

All I have (and know much about) is X11R3 and Motif 1.0; if some of 
this is different in later releases I'd like to know that too.


Event              pushbutton  popup    pulldown      option        popdown
================   ==========  =====    ===========   ===========   =========
btn1 down & up      arm &       --      post menu     activate      post menu
   on rectangle     activate            key mode      selection     drag mode
   (w or w/o motion)                                               -(key mode)

btn1 down           arm         --      post menu     post menu     post menu
   on rectangle                         drag mode     drag mode     drag mode
                                                      & select

btn1 down on rect.  disarm      --      post menu     post menu     post menu
   then move off                        drag mode     drag mode     drag mode

btn3 down             --    post menu      --            --            --
                            drag mode

btn up on menu        --    activate    activate      select &      activate
   in drag mode             & unpost    & unpost      activate      & unpost
                                                      & unpost

btn up off menu       --    unpost      unpost        unpost       -unpost
   in drag mode                                                    (see note 1)
   (and off rect.)

btn down on menu      --    select &    select &      select &     -(select &)
   in key mode              change to   change to     change to     change to
                            drag mode   drag mode     drag mode     drag mode
                                                                   (see note 2)

btn down off app.     --    change to   unpost        change to     change to
   in key mode              drag mode                 drag mode     drag mode

btn down off menu     --    change to   change to     change to     change to
   but on app. in           drag mode   drag mode     drag mode     drag mode
   key mode

Meta-mnemonic         --        --      post menu    -(post menu   -(post menu
                                        key mode      key mode      key mode)
                                                      & select)

menu mnemonic         --        --         --         post menu        --      
                                                      key mode     
                                                      & select

menu accelerator      --    post menu   highlight &      --        -(post menu
   from anywhere            key mode    accept focus                key mode)
   in application                                                  (see note 3)

item accelerator      --    activate    activate      select &      activate
                            & unpost    & unpost      activate      & unpost
                                                      & unpost

ESC when menu         --    unpost      unpost        unpost        unpost
   posted any mode

tab to the         highlight &  --     +highlight &   highlight &   highlight &
   rectangle       accept focus        +accept focus  accept focus  accept focus

tab from the       unhighlight& --     -unhighlight&  unhighlight&  unhighlight &
   rectangle       loose focus         -loose focus   loose focus   loose focus

Return when rect. +arm &        --      post menu     activate      post menu
   in focus       +activate             key mode      selection     drag mode
                                                                   -(key mode)

Space when rect.   arm &        --     +(post menu    post menu     post menu
   in focus        activate             key mode)     key mode      key mode    
                                                      & select

Return when in        --    activate    activate      activate      activate
   menu key mode            & unpost    & unpost      & unpost      & unpost

Space when in         --   +(activate  +(activate    +(activate     activate
   menu key mode            & unpost)   & unpost)     & unpost)     & unpost

Up/Down arrow in      --    select      select        select        select
   menu key mode

Left/Right arrow      --   +unpost      unpost &     +unpost        unpost
   in menu key mode                     post next    
                                        on menu bar

tab in menu key mode  --   +unpost     +unpost       +unpost        unpost

item mnemonic in      --    activate    activate      select &      activate
   menu key mode            & unpost    & unpost      activate      & unpost
                                                      & unpost     (see note 4)

Notes:
  1.  Sometimes this works, sometimes it doesn't.  It always works if the btn
      is released off the APPLICATION.  It usually works if the menu is first
      crossed with the mouse.

  2.  Nothing is selected until the mouse moves to a different entry.

  3.  Only works when the rectangle already has focus.

  4.  Also works in drag mode as long as the mouse button has been released.
      I would rather have the menu in key mode in that case, however.

      
-------------- CUT HERE FOR DEMO PROGRAM ------------------------ 

#include <stdio.h>
#include <X11/StringDefs.h>
#include <X11/Intrinsic.h>
#include <X11/keysym.h>
#include <X11/Shell.h>
#include <Xm/Xm.h>
#include <Xm/DrawingA.h>
#include <Xm/Label.h>
#include <Xm/RowColumn.h>
#include <Xm/PushBG.h>
#include <Xm/PushB.h>
#include <Xm/CascadeB.h>

#define popup    0
#define pulldown 1
#define option   2
#define popdown  3

#define MySetArg(argList, count, n, d) \
    ( (argList)[count].name = (n), (argList)[count++].value = (XtArgVal)(d) )

static char *menus[] = { "popup", "pulldown", "option", "popdown" };

static void MyCallback (w,client_data,call_data)
Widget w;
int client_data;
XmAnyCallbackStruct *call_data;
{
  if (client_data) 
    fprintf(stderr,"callback from %s menu, pushbutton %c\n",
    menus[client_data>>8],client_data & 0xFF);
  else
    fprintf(stderr,"callback from pushbutton\n");
}

static void PostPopup (w, menu, event)
Widget w;
Widget menu;
XButtonEvent *event;
{
  if (event->button == Button3) {
    XmMenuPosition(menu,event);
    XtManageChild(menu); }
}

static XtCallbackRec cb[2];
static Arg args[10];
static int Nargs;

void populateMenu (menu, buttonCodes, menuType)
Widget menu;
String buttonCodes;
int menuType;
{
  Widget w;
  XmString xmstr;
  char *p_txt, *p_name, *p_acc, *loop,
    *buttonName = "pushbutton x", *acc = "Ctrl <Key>x", *txt = "(Ctrl-x)";

  p_name = strchr(buttonName,'\0') - 1;
  p_acc = strchr(acc,'\0') - 1;
  p_txt = strchr(txt,'\0') - 2;
  for (loop=buttonCodes; *loop; loop++) {
    *p_name = *p_acc = *p_txt = *loop;
    cb[0].closure = (caddr_t) (menuType<<8 | *loop);
    Nargs = 0;
    MySetArg (args, Nargs, XmNactivateCallback, cb);
    MySetArg (args, Nargs, XmNmnemonic, *loop);
    MySetArg (args, Nargs, XmNaccelerator, acc);
    xmstr = XmStringCreateLtoR (txt, XmSTRING_DEFAULT_CHARSET);
    MySetArg (args, Nargs, XmNacceleratorText, xmstr);
    w = XmCreatePushButtonGadget(menu,buttonName, args, Nargs);
    XmStringFree(xmstr);
    XtManageChild(w); }
}

/**************** begin special section to support popdown menu behaviour *********/

static void MyNoOp (w, event, params, num_params)
Widget w;
XEvent *event;
String *params;
Cardinal *num_params;
{
}

static void PostPopdown (w, menu, event)
Widget w;
Widget menu;
XEvent *event;
{
  int n, height, x, y;
  Arg args[5];
  KeySym ks;
  Widget nxt;

  switch (event->type) {
    case ButtonPress : if (event->xbutton.button != Button1) return;
      break;
    case KeyPress : ks = XLookupKeysym(&(event->xkey),0);
      if (ks!=XK_Return && ks!=XK_space) return;
      break;
    default : return; }
  n = 0;
  XtSetArg (args[n], XmNx, &x); n++;
  XtSetArg (args[n], XmNy, &y); n++;
  XtSetArg (args[n], XmNheight, &height); n++;
  event->xkey.x_root = event->xkey.y_root = 0;   /* term. cond. won't be right */
  for (nxt = w; nxt; nxt = XtParent(nxt)) {      /* if called from a DialogShell */ 
    XtGetValues (nxt, args, n);                  /* in that case, stop there */
    if (nxt==w) event->xkey.y_root += height;
    event->xkey.x_root += x; event->xkey.y_root += y; }
  XmMenuPosition(menu, event);
  XtManageChild(menu);
}

static XtActionsRec MyActions[] = { {"MyNoOp", MyNoOp} };

static String PDbuttonBehaviour = "<Key>Return: ArmAndActivate()\n\
                                       <Enter>: MyNoOp()\n\
                                     <BtnDown>: MyNoOp()\n\
                                       <BtnUp>: MyNoOp()";
static XtTranslations PDbuttonTranslation = NULL;

static String PDmenuBehaviour = "<Key>space: MenuGadgetReturn()\n\
                  <Enter>,<BtnDown>,<BtnUp>: PopupBtnDown() PopupBtnUp()\n\
                            <Enter>,<BtnUp>: PopupBtnUp()\n\
                          <BtnDown>,<BtnUp>: MenuGadgetEscape()\n\
                            <Leave>,<BtnUp>: MenuGadgetEscape()\n\
                              Shift<Key>Tab: MenuGadgetEscape()\n\
                               Ctrl<Key>Tab: MenuGadgetEscape()\n\
                                   <Key>Tab: MenuGadgetEscape()\n\
                                 <Key>Right: MenuGadgetEscape()\n\
                                  <Key>Left: MenuGadgetEscape()";
static XtTranslations PDmenuTranslation = NULL;

static String PDshellBehaviour = "<Key>Escape: MenuShellPopdownDone()\n\
                                      <BtnUp>: MyNoOp()\n\
                                    <BtnDown>: ClearTraversal()";
static XtTranslations PDshellTranslation = NULL;

/************** end special section to support popdown menu behaviour *************/

main(argc, argv)
int argc;
char *argv[];
{
  XmString xmstr;
  Widget shell, container, menubar, menu, w;
  XtAppContext app;

  cb[0].callback = MyCallback;                          /* initialize constants */
  cb[1].callback = NULL;
  cb[1].closure = NULL;

                                                          /* top-level widgets */
  shell = XtInitialize ("main",argv[0], NULL, 0, &argc, argv); 
  app = XtWidgetToApplicationContext(shell);
  Nargs = 0;
  MySetArg (args, Nargs, XmNtraversalOn, True);
  container = XmCreateDrawingArea (shell, "area", args, Nargs);
  XtManageChild (container); 
                                                   /* build translations, etc. */
  PDbuttonTranslation = XtParseTranslationTable (PDbuttonBehaviour);
  PDmenuTranslation = XtParseTranslationTable (PDmenuBehaviour);
  PDshellTranslation = XtParseTranslationTable (PDshellBehaviour);
  XtAppAddActions (app, MyActions, sizeof MyActions / sizeof (XtActionsRec));

  Nargs = 0;                                                   /* pushbutton */
  MySetArg (args, Nargs, XmNactivateCallback, cb);
  MySetArg (args, Nargs, XmNtraversalOn, True);
  MySetArg (args, Nargs, XmNhighlightThickness, 2);
  cb[0].closure = 0;
  w = XmCreatePushButton(container,"pushbutton", args, Nargs);
  XtManageChild (w); 
  XmAddTabGroup(w);

  Nargs = 0;                                                 /* reminder label */
  xmstr = XmStringCreateLtoR("Press mouse button 3, or Ctrl-m, for a Popup Menu",
    XmSTRING_DEFAULT_CHARSET);
  MySetArg (args, Nargs, XmNy, 60);
  MySetArg (args, Nargs, XmNlabelString, xmstr);
  w = XmCreateLabel(container,"label", args, Nargs);
  XmStringFree(xmstr);
  XtManageChild (w); 

  Nargs = 0;                                                     /* popup menu */
  MySetArg (args, Nargs, XmNmenuAccelerator, "Ctrl <Key>m"); 
  menu = XmCreatePopupMenu (container,menus[popup],args,Nargs);
  populateMenu (menu, "abc", popup);
  XtAddEventHandler(container,ButtonPressMask,False,PostPopup,menu);

  Nargs = 0;                                                  /* pulldown menu */
  MySetArg (args, Nargs, XmNy, 100);
  MySetArg (args, Nargs, XmNmenuAccelerator, "Ctrl <Key>p"); 
  menubar = XmCreateMenuBar (container,"menubar",args,Nargs);
  XtManageChild (menubar);
  menu = XmCreatePulldownMenu (menubar,menus[pulldown],NULL,0);
  populateMenu (menu, "def", pulldown);
  Nargs = 0;
  MySetArg (args, Nargs, XmNsubMenuId, menu);
  MySetArg (args, Nargs, XmNmnemonic, 'p');
  w = XmCreateCascadeButton (menubar, "pulldown menu (Ctrl-p)", args, Nargs);
  XtManageChild (w);

                                                               /* option menu */
  menu = XmCreatePulldownMenu (container,menus[option],NULL,0);
  populateMenu (menu, "ghi", option);
  Nargs = 0;
  xmstr = XmStringCreateLtoR("Option Menu:", XmSTRING_DEFAULT_CHARSET);
  MySetArg (args, Nargs, XmNlabelString, xmstr);
  MySetArg (args, Nargs, XmNsubMenuId, menu);
  MySetArg (args, Nargs, XmNmnemonic, 'o');
  MySetArg (args, Nargs, XmNx, 160);
  w = XmCreateOptionMenu(container,"option menu", args, Nargs);
  XmStringFree(xmstr);
  XtManageChild (w); 
  w = XmOptionButtonGadget(w);
  Nargs = 0;
  MySetArg (args, Nargs, XmNtraversalOn, True);
  MySetArg (args, Nargs, XmNhighlightThickness, 2);
  XtSetValues (w, args, Nargs);
  XmAddTabGroup(w);

  Nargs = 0;                                                   /* popdown menu */
  MySetArg (args, Nargs, XmNy, 100);
  MySetArg (args, Nargs, XmNx, 260);
  MySetArg (args, Nargs, XmNtraversalOn, True);
  MySetArg (args, Nargs, XmNhighlightThickness, 2);
  w = XmCreatePushButton(container,"popdown menu", args, Nargs);
  XtManageChild (w); 
  XmAddTabGroup(w);
  Nargs = 0;
  MySetArg (args, Nargs, XmNwhichButton, 1); 
  MySetArg (args, Nargs, XmNdialogStyle, XmDIALOG_MODELESS); 
  MySetArg (args, Nargs, XmNtraversalOn, True); 
  MySetArg (args, Nargs, XmNmenuAccelerator, "<Key>space"); 
  menu = XmCreatePopupMenu (w, menus[popdown], args, Nargs);
  XtAddEventHandler (w, KeyPressMask | ButtonPressMask, False, PostPopdown, menu);
  XtOverrideTranslations (w,PDbuttonTranslation);
  XtOverrideTranslations (menu,PDmenuTranslation);
  XtOverrideTranslations (XtParent(menu),PDshellTranslation);
  populateMenu (menu, "jkl", popdown);

  XtRealizeWidget(shell);
  XtAppMainLoop(app);

}