[comp.windows.x] Need help in interactive selection of a motif widget/gadget

mayer@hplabsz.HPL.HP.COM (Niels Mayer) (10/26/89)

I have added a primitive to my WINTERP application (a Motif application
prototyping/develpment/delivery environment) which will allow users click
on a widget to get back the WIDGET-OBJECT corresponding to the widget. I am
using this to allow users to interactively modify the interfaces they build
by pointing and clicking on widgets. (It's the sort of primitive that'll
enable WINTERP to take on some of the features of hypercard (hope I don't
get sued for saying that...). For example, to interactively change a
callback on a button, you can send the following instruction to winterp's
xlisp server:
		(send (get_moused_widget) :set_callback :xmn_arm_callback
			'()
			'((format T "this is a test\n")))
Then after the cursor changes to a crossbar (just like in xwd or xwininfo)
you can the boink the mouse on the appropriate widget to set or change
its callback. It's also lots of fun to use this to interactively change
colors inside your winterp-based applications.

My GET_MOUSED_WIDGET primitive currently works by using a combination of
XGrabPointer() and XTranslateCoordinates() to find the window that got a
buttonpress, the uses XtWindowToWidget() to get back the widgetID. If the
desired widget is infact a gadget (a windowless widget), the designated
window will infact be the gadget's manager. To check for that case, I see
whether the widget is a composite (i.e. a gadget-manager), and if so, I
call _XmInputInGadget() with the composite widget's ID, and the (x,y)
location of the buttonpress.

For the most part, GET_MOUSED_WIDGET works fine. However, if the designated
gadget is inside a rowcolumn manager which is inside a scrolledwindow
manager, i sometimes get problems. If the scrolledwin's origin == the
rowcol windows's origin, get_moused_widget will work fine on a gadget.
However, if the rowcol window is scrolled by <offset>, boinking a gadget at
(x+offset,y+offset) will return the WIDGETOBJ at (x,y).

Am I doing something wrong? Is this a Motif or Xtoolkit bug?  Do I have to
add yet another special kludge for a gadget-inside-a-manager-
-inside-a-scrolled-window? Inquiring minds want to know.

Here's the C code that gets called when winterp's xlisp interpreter
evaluates the expression (get_moused_widget):

/******************************************************************************
 * (GET_MOUSED_WIDGET)
 * evaluating this function will change the cursor to a crossbar, indicating
 * that the user is to 'click' the mouse to designate an object on the screen.
 * If the user clicks on a visual item within WINTERP, this fucntion will
 * return the WIDGETOBJ associated with the visual item. 
 *
 * KNOWN-BUG: Given gadget inside a rowcol manager which is inside a
 * scrolled manager. If the scrolled window's top == rowcol windows's top,
 * get_moused_widgetwill work fine on a gadget. However, if the rowcol
 * window is scrolled by <offset>, boinking a gadget at (x+offset,y+offset)
 * will return the WIDGETOBJ at (x,y).
 ******************************************************************************/
LVAL Wut_UserClick_To_WidgetObj()
{
  extern Display* display;	/* global in winterp.c */
  extern LVAL     Wcls_WidgetID_To_WidgetObj();	/* from w_classes.c */
  Cursor	  cursor = XCreateFontCursor(display, XC_crosshair);
  Window          root = DefaultRootWindow(display);
  Window          parent_win, child_win, win;
  int             parent_win_x, parent_win_y, child_win_x, child_win_y;
  Widget          widget_id;
  XEvent	  event;
  
  xllastarg();

  if (GrabSuccess != XGrabPointer(display, root, 0, ButtonPressMask, GrabModeAsync,
				  GrabModeAsync, None, cursor, CurrentTime))
    xlfail("Wut_UserClick_To_WidgetObject() -- couldn't grab pointer (XGrabPointer() failed).");
  
  XWindowEvent(display, root, ButtonPressMask, &event);
  XUngrabPointer(display, CurrentTime);
  XFlush(display);

  if (event.type != ButtonPress) /* sanity check: ensure valid access to event.xbutton variant */
    xlfail("Wut_UserClick_To_WidgetObject() -- expected a button event.");
  if (!event.xbutton.subwindow)
    xlfail("Wut_UserClick_To_WidgetObject() -- User clicked on root window.");

  parent_win = root;
  child_win = event.xbutton.subwindow;
  parent_win_x = event.xbutton.x_root;
  parent_win_y = event.xbutton.y_root;
  child_win_x = event.xbutton.x;
  child_win_y = event.xbutton.y;
  while (XTranslateCoordinates(display,
			       parent_win, child_win,
			       parent_win_x, parent_win_y, /* give the x,y coords of event in parent_w */
			       &parent_win_x, &parent_win_y, /* return the x,y coords relative to child_w */
			       &win) /* returns child of parent_win if coords are in this win, else NIL  */
	 && win) {
#ifdef DEBUG_WINTERP_1
    fprintf(stderr, "parent_win=%lx, child_win=%lx, win=%lx\n", parent_win, child_win, win);
#endif
    parent_win = child_win;
    child_win = win;
    child_win_x = parent_win_x;
    child_win_y = parent_win_y;
  }

#ifdef DEBUG_WINTERP_1
  fprintf(stderr, "	Smallest window containing userclick is %lx\n", child_win);
#endif

  if (!(widget_id = XtWindowToWidget(display, child_win)))
    xlfail("Wut_UserClick_To_WidgetObject() -- Couldn't find widget associated with window (Is the selected widget/window inside a different application?).\n");
  else if (XtIsComposite(widget_id)) { /* if it's a composite, smallest window may be a gadget's manager */
    extern XmGadget _XmInputInGadget();	/* in Xm/GadetUtils.c extern'd in XmP.h */
    Widget gadget_id = (Widget) _XmInputInGadget(widget_id, child_win_x, child_win_y);
    if (gadget_id)		/* if gadget could be retrieved from looking up x,y coords in manager */
      return (Wcls_WidgetID_To_WidgetObj(gadget_id)); /* then return the WIDGETOBJ assoc'd with gadget */
  }
  return (Wcls_WidgetID_To_WidgetObj(widget_id)); /* otherwise, we return the WIDGETOBJ assoc'd with smallest window */
}

-------------------------------------------------------------------------------
	    Niels Mayer -- hplabs!mayer -- mayer@hplabs.hp.com
		  Human-Computer Interaction Department
		       Hewlett-Packard Laboratories
			      Palo Alto, CA.
				   *

ben@hpcvlx.cv.hp.com (Benjamin Ellsworth) (10/27/89)

(I don't understand why you cross posted to comp.windows.x.  Anyways...)

> ...Am I doing something wrong?

In a word, yes.

The easy answer is to set owner events true on the grab, and then the
data in the event are exactly what you want.

Looking at your code, I don't understand how it would ever work for the
general case.  Specifically:

>   while (XTranslateCoordinates(display,
> 			       parent_win, child_win,
> 			       parent_win_x, parent_win_y,
> 			       &parent_win_x, &parent_win_y,
> 			       &win)
> 	 && win) {
>     parent_win = child_win;
>     child_win = win;
>     child_win_x = parent_win_x;
>     child_win_y = parent_win_y;
>   }

When win is set to NULL by XTranslateCoordinates, the entry condition
for the while loop becomes false and the loop terminates.
Unfortunately it terminates before updating child_win_x and
child_win_y.  The rest is, as they say, history.  

As far as I can tell, this code will *only* work when the parent and 
child window have coincident origins (such as a rowcolumn in a 
scrolledwindow before any user scrolling).


-----------------------------------------------------------------------
Benjamin Ellsworth      | ben@cv.hp.com                | INTERNET
Hewlett-Packard Company | {backbone}!hplabs!hp-pcd!ben | UUCP
1000 N.E. Circle        | (USA) (503) 750-4980         | FAX
Corvallis, OR 97330     | (USA) (503) 757-2000         | VOICE
-----------------------------------------------------------------------
                     All relevant disclaimers apply.
-----------------------------------------------------------------------

mayer@hplabsz.HPL.HP.COM (Niels Mayer) (10/27/89)

In article <4219@hplabsz.HPL.HP.COM> mayer@hplabs.hp.com (Niels Mayer) writes:
>I have added a primitive to my WINTERP application (a Motif application
>prototyping/develpment/delivery environment) which will allow users click
>on a widget to get back the WIDGET-OBJECT corresponding to the widget.
> ... [description of bug deleted] ...
>Am I doing something wrong? Is this a Motif or Xtoolkit bug?  Do I have to
>add yet another special kludge for a gadget-inside-a-manager-
>-inside-a-scrolled-window? Inquiring minds want to know.

Please ignore my previous request. When I wrote my GET_MOUSED_WIDGET
routine, I misunderstood how XTranslateCoordinates() worked. After
rereading the manpage, I was able to find the (trivial) bug in my code.
Clearly a case of earthquake-induced braindamage on my part.

Here's the working version of GET_MOUSED_WIDGET. You may find it useful...

/******************************************************************************
 * (GET_MOUSED_WIDGET)
 * evaluating this function will change the cursor to a crossbar, indicating
 * that the user is to 'click' the mouse to designate an object on the screen.
 * If the user clicks on a visual item within WINTERP, this fucntion will
 * return the WIDGETOBJ associated with the visual item. 
 ******************************************************************************/
LVAL Wut_UserClick_To_WidgetObj()
{
  extern Display* display;	/* global in winterp.c */
  extern LVAL     Wcls_WidgetID_To_WidgetObj();	/* from w_classes.c */
  extern XmGadget _XmInputInGadget(); /* in Xm/GadetUtils.c extern'd in XmP.h */
  Cursor	  cursor = XCreateFontCursor(display, XC_crosshair);
  Window          root = DefaultRootWindow(display);
  Window          parent_win, cur_win, child_win;
  int             win_x, win_y;
  Widget          widget_id, gadget_id;
  XEvent	  event;
  Bool            xtc_ok;
  
  xllastarg();

  if (GrabSuccess != XGrabPointer(display, root, 0, ButtonPressMask, GrabModeAsync,
				  GrabModeAsync, None, cursor, CurrentTime))
    xlfail("GET_MOUSED_WIDGET -- couldn't grab pointer (XGrabPointer() failed).");
  
  XWindowEvent(display, root, ButtonPressMask, &event);
  XUngrabPointer(display, CurrentTime);
  XFlush(display);

  if (event.type != ButtonPress) /* sanity check: ensure valid access to event.xbutton variant */
    xlfail("Bug in GET_MOUSED_WIDGET -- expected a button event.");
  if (!event.xbutton.subwindow)
    xlfail("GET_MOUSED_WIDGET aborted -- you clicked on the root window.");

  parent_win = event.xbutton.window; /* ASSERT event.xbutton.window == root, due to using XWindowEvent(root) */
  win_x      = event.xbutton.x;
  win_y      = event.xbutton.y;
  cur_win    = event.xbutton.subwindow;
  while ((xtc_ok = XTranslateCoordinates(display,
					 parent_win, cur_win,
					 win_x, win_y, /* give the x,y coords of event in parent_w */
					 &win_x, &win_y, /* return the x,y coords relative to cur_win */
					 &child_win)) /* returns child window of cur_win if that contains coords, else nil */
	 && child_win) {
#ifdef DEBUG_WINTERP_1
    fprintf(stderr, "parent_win=%lx, cur_win=%lx, child_win=%lx\n", parent_win, cur_win, child_win);
#endif
    parent_win = cur_win;
    cur_win    = child_win;
  }

#ifdef DEBUG_WINTERP_1
  fprintf(stderr, "	Smallest window containing userclick is %lx\n", cur_win);
#endif

  if (!xtc_ok)
    xlerror("Bug in GET_MOUSED_WIDGET -- XTranslateCoordinates() failed.");

  if (!(widget_id = XtWindowToWidget(display, cur_win)))
    xlfail("GET_MOUSED_WIDGET -- Couldn't find widget associated with window.\n	(Is the selected widget/window inside a different application?).\n");

  /* if the widget is a composite it may be managing a gadget -- attempt to retrieve it by looking up x,y coords in manager */
  if (XtIsComposite(widget_id) &&
      (gadget_id = (Widget) _XmInputInGadget(widget_id, win_x, win_y)))
    return (Wcls_WidgetID_To_WidgetObj(gadget_id)); /* then return the WIDGETOBJ assoc'd with gadget */
  else
    return (Wcls_WidgetID_To_WidgetObj(widget_id)); /* otherwise, we return the WIDGETOBJ assoc'd with smallest window */
}

-------------------------------------------------------------------------------
	    Niels Mayer -- hplabs!mayer -- mayer@hplabs.hp.com
		  Human-Computer Interaction Department
		       Hewlett-Packard Laboratories
			      Palo Alto, CA.
				   *

marbru@auto-trol.UUCP (Martin Brunecky) (10/28/89)

In article <4219@hplabsz.HPL.HP.COM> mayer@hplabs.hp.com (Niels Mayer) writes:
>I have added a primitive to my WINTERP application (a Motif application
....
>Am I doing something wrong? Is this a Motif or Xtoolkit bug?  Do I have to
>add yet another special kludge for a gadget-inside-a-manager-
>-inside-a-scrolled-window? Inquiring minds want to know.

I don't offer too much help, just an observation gained through burning
night oil in an effort to integrate my widgets with Motif.

Motif is "OPEN" (or that's what we all assume - don we ?). But what that
means ? Opennes for use, or openness for further development ????

The current Motif code implements it's desired functionality by defeating
anything that I ever read about object oriented programming. The entire
toolkit is tied together by numerous dependencies and assumptions. There
are widgets which know exactly what to do with xxxbutton and yyybutton.
There are widgets reloading translation tables in another widgets, knowing
what their action routines are. There are.......
What I am trying to say, is: Motif is here, but - by God - don't even think
about tailoring it. It has not been designed for it. Is it OSF's fault ?
I don't think so. They wanted to deliver a product, and most users will be
happy with Motif as it is.

The code You are trying to implement, will have to do the same thing as
Motif code does. Know exactly which widget it's dealing with, include
it's private header files and go for widget's private data. Ugly ???
Maybe. Difficult ? Certainly. Difficult to maintain ? You bet !

-- 
###############################################################################
Martin Brunecky, Auto-trol Technology Corporation,
12500 North Washington Street, Denver, CO-80241-2404
(303) 252-2499                                        ncar!ico!auto-trol!marbru