siemens-rtl/track_menu.c
2023-07-11 14:40:13 +02:00

822 lines
21 KiB
C
Raw Permalink Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#ifndef lint
static char sccs_id[] = "@(#)track_menu.c 5.2 6/2/89";
#endif
/*
* Copyright 1988 by Siemens Research and Technology Laboratories, Princeton, NJ
*
* All Rights Reserved
*
* Permission to use, copy, modify, and distribute this software and its
* documentation for any purpose and without fee is hereby granted,
* provided that the above copyright notice appear in all copies and that
* both that copyright notice and this permission notice appear in
* supporting documentation, and that the name of Siemens Research and Technology
* Laboratories not be used in advertising or publicity pertaining to
* distribution of the software without specific, written prior permission.
*
*
* SIEMENS DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING
* ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL
* SIEMENS BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR
* ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
* WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
* ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
* SOFTWARE.
*/
#include "copyright.h"
/*
RTL Menu Package Version 1.2
by Joe Camaratta and Mike Berman, Siemens RTL, Princeton NJ, 1988
track_menu.c: bring up menus and track the mouse
*/
#include "arrow_icon.h"
#include "null_icon.h"
#include "rtlmenuP.h"
#include "evsaveX.h"
#define CLICK_TIME 360 /* in milliseconds */
#define CursorLockMask (ButtonReleaseMask)
/* Event macros */
#define EventGetXCoord(rep) ((rep).xmotion.x)
#define EventGetYCoord(rep) ((rep).xmotion.y)
#define EventType(rep) ((rep).type)
#define EventXWindow(rep) ((rep).xcrossing.window)
#define EventXTime(rep) ((rep).xcrossing.time)
#define EventXMode(rep) ((rep).xcrossing.mode)
#define EventXRootX(rep) ((rep).xcrossing.x_root)
#define EventXRootY(rep) ((rep).xcrossing.y_root)
#define EventXDetail(rep) ((rep).xcrossing.detail)
#define EventMWindow(rep) ((rep).xmotion.window)
#define EventMTime(rep) ((rep).xmotion.time)
#define EventButton(rep) ((rep).xbutton.button)
#define EventBWindow(rep) ((rep).xbutton.window)
#define EventBTime(rep) ((rep).xbutton.time)
#define EventEX(rep) ((rep).xexpose.x)
#define EventEY(rep) ((rep).xexpose.y)
#define EventEWidth(rep) ((rep).xexpose.width)
#define EventEHeight(rep) ((rep).xexpose.height)
#define PointerEvent(rep) \
((EventType(rep) == ButtonPress) || \
(EventType(rep) == ButtonRelease) || \
(EventType(rep) == MotionNotify) || \
(EventType(rep) == EnterNotify) || \
(EventType(rep) == LeaveNotify) || \
(EventType(rep) == FocusIn) || \
(EventType(rep) == FocusOut))
#define KeyEvent(rep) \
((EventType(rep) == KeyPress) || (EventType(rep) == KeyRelease))
#define SelectChildX(menu, item, rep) \
(TestOptionFlag(menu, fixedchild)? \
(MenuX(menu) + ItemGetArrowPosition(item)): \
MIN(MenuX(menu) + ItemGetArrowPosition(item),EventXRootX(rep)))
/* Possible states for the state machine */
typedef enum
{
Initial, /* Inside a submenu, but not any item */
CheckTrigger, /* Inside an item that has submenu, checking for pullright */
Leaf, /* Inside an item with no submenu */
Exit, /* Preparing to exit */
LevelControl /* Not in any submenu, waiting to enter something */
} State;
/* functions in the menu.c module */
extern RTLMenu NewMenu();
extern void DisposeMenu ();
extern void Draw_Menu();
extern void ClearInitialItem();
extern void PlacePointer();
extern void AdjustPointer();
extern void SetInitialItem();
extern void Undisplay_Menu();
extern void MenuInvert();
extern void Draw_Item();
extern RTLMenuItem MenuItemByName ();
extern RTLMenuItem Display_Menu();
extern RTLMenuItem MenuGetItem();
extern RTLMenuItem GetInitialItem();
extern RTLMenu MenuGetMenu();
void LockCursor();
void UnlockCursor();
void GetNextSignificantEvent();
void PopSubmenu();
void Highlight();
void Unhighlight();
void TossExtraMoves();
void ProcessExposeEvents();
bool GrabPointer();
State LevelControlState();
State GetItemState();
State InitialState();
State CheckTriggerState();
State LeafState();
static RTLMenu current_item;
static RTLMenu current_menu;
static Window root_window;
static Display *display;
static int level; /* submenu level */
static Time button_time; /* time button press invoked */
static Cursor wait_cursor = None; /* empty cursor for lock state */
static bool click_allowed;
static EventStore ev_save;
static bool locked = FALSE;
RTLMenuItem TrackMenu(root_menu, root_x, root_y, init_button, root, buttime)
RTLMenu root_menu; /* Pointer to root menu requested to pop up */
int root_x, root_y; /* Position to start menu */
int init_button; /* The # of button used to pop up menu */
Window root; /* Window label for parent of menu */
Time buttime; /* timestamp for button (or 0, if CLICK == 0) */
{
State CurrentState = LevelControl;
XEvent Event_Reply;
int open_x;
bool selected = FALSE;
RTLMenuItem selected_item;
/* Initialize globals */
button_time = buttime;
root_window = root;
level = 0;
current_menu = root_menu;
display = MenuDisplay(current_menu);
click_allowed = (TestOptionFlag(current_menu, clickokay))? TRUE : FALSE;
/* If not already done, set up the null cursor for lock state */
if (wait_cursor == None)
{
Pixmap wc_pixmap;
XColor fg, bg;
wc_pixmap = XCreateBitmapFromData (display, root_window,
null_icon_bits,
null_icon_width, null_icon_height);
wait_cursor = XCreatePixmapCursor (display, wc_pixmap, wc_pixmap,
&fg, &bg, 1, 1);
}
/* *** Put up initial menus, and get into correct state *** */
AdjustPointer(current_menu, root_x, root_y);
/* Get the present state, so it can be restored later */
/* Any events on the queue when we start get saved now, restored later */
SaveEvents (display, &ev_save, ~(unsigned long) ButtonReleaseMask);
if (!GrabPointer() ||
!(current_item = Display_Menu(current_menu, NULLMENU, root_x, root_y)))
{
CurrentState = Exit;
}
else
{
LockCursor(ItemWindow(current_item));
open_x = root_x;
}
/* Push to appropriate previous item, if any */
while (MenuHasInitialItem(current_menu) && (CurrentState != Exit))
{
ProcessExposeEvents();
current_item = GetInitialItem(current_menu);
ClearInitialItem(current_menu);
/* if the initial item can't be selected, take first in list */
if (ItemIsNull(current_item) || ItemIsDisabled(current_item))
{
current_item = MenuItems(current_menu);
break;
}
else if (ItemIsLeaf(current_item)) /* then we're done */
break;
else
{
open_x += ItemGetArrowPosition(current_item);
Highlight(current_item);
TossExtraMoves(ItemWindow(current_item));
(void)PushSubmenu(open_x);
}
}
ProcessExposeEvents();
if (CurrentState != Exit)
{
CurrentState = (ItemIsLeaf(current_item))? Leaf: CheckTrigger;
if (!TestItemFlag(current_item, itemDisabled))
Highlight(current_item);
}
LockCursor(ItemWindow(current_item));
PlacePointer(current_menu,current_item);
DisposeEvents(display, (Mask)(PointerMotionMask | EnterWindowMask |
LeaveWindowMask | ExposureMask));
UnlockCursor();
/* State Machine */
while (CurrentState != Exit)
{
GetNextSignificantEvent(display, &Event_Reply, init_button);
switch (CurrentState)
{
case LevelControl:
CurrentState = LevelControlState(Event_Reply);
break;
case Initial:
CurrentState = InitialState(Event_Reply);
break;
case CheckTrigger:
CurrentState = CheckTriggerState(Event_Reply);
break;
case Leaf:
CurrentState = LeafState(Event_Reply, &selected);
break;
default:
CurrentState = Exit;
break;
}
}
/* Clean up and exit */
selected_item = (selected)? current_item : NULLITEM;
while (level)
{
if (selected && !TestOptionFlag(current_menu, forgetlast))
SetInitialItem(current_menu, current_item);
PopSubmenu();
}
if (selected && !TestOptionFlag(current_menu, forgetlast))
{
SetInitialItem(current_menu, current_item);
}
Undisplay_Menu(current_menu);
UnlockCursor();
XUngrabPointer(display, CurrentTime);
/* Push back any events that were lying around when menus started */
XFlush(display);
DisposeEvents(display,
(Mask) (PointerMotionMask | EnterWindowMask |
LeaveWindowMask));
RestoreEvents(display, &ev_save);
return(selected_item);
}
bool GrabPointer()
{
int tries = 10;
while((XGrabPointer(display,
RootWindow(display, MenuScreen(current_menu)),
True, CursorLockMask, GrabModeAsync,
GrabModeAsync, None,
wait_cursor, CurrentTime) != GrabSuccess)
&& tries--)
sleep(1);
if (tries)
return(TRUE);
else
return(FALSE);
}
/* Lock the cursor: make it disappear, and ignore events it generates. */
/* Optionally, confine it to a single window. */
/* (Using "None" for confine_window doesn't confine it. ) */
void LockCursor(confine_window)
Window confine_window;
{
int result;
locked = TRUE;
result = XGrabPointer(display,
RootWindow(display, MenuScreen(current_menu)),
True, CursorLockMask, GrabModeAsync,
GrabModeAsync, confine_window,
wait_cursor, CurrentTime);
return;
}
/* Unlock (and unconfine) the cursor. */
void UnlockCursor()
{
int result;
if (locked)
{
locked = FALSE;
result = XGrabPointer(display,
RootWindow(display, MenuScreen(current_menu)),
True, MenuEventMask,
GrabModeAsync, GrabModeAsync, None,
MenuCursor(current_menu), CurrentTime);
}
return;
}
/* Keep getting the X events, until finding one that may be interesting */
/* to the operation of the state machine. */
void GetNextSignificantEvent(displ,Event_Reply,init_button)
Display *displ;
XEvent *Event_Reply;
int init_button; /* the button that initiated the menu */
{
XEvent Next_Event_Reply;
bool InsignificantEvent = True;
/* Loop as long as any of a number of "insignificant" events */
/* are found; when the event no longer matches one of the tests, */
/* it is assumed to be "significant" and returned.*/
do
{
XNextEvent(displ, Event_Reply);
/* If this event is an "enter", check whether there is a */
/* "leave" for the same window already in the queue, */
/* immediately following it; if so, throw them both out */
/* and get the next event */
/* NOTE: might try to look further ahead, but this is */
/* tricky because other events might intervene. */
if ((EventType(*Event_Reply) == EnterNotify) &&
(EventXMode(*Event_Reply) == NotifyNormal) &&
(QLength(displ) > 0) &&
(MenuGetMenu(current_menu, EventXWindow(*Event_Reply))
!= current_menu))
{
XPeekEvent(displ,&Next_Event_Reply);
if ((EventType(Next_Event_Reply) == LeaveNotify) &&
(EventXMode(Next_Event_Reply) == NotifyNormal) &&
(EventXWindow(Next_Event_Reply) == EventXWindow(*Event_Reply)))
{
XNextEvent(displ, Event_Reply);
XNextEvent(displ, Event_Reply);
}
}
if (EventNotSignificant(*Event_Reply, init_button))
{
if (!(PointerEvent(*Event_Reply) || KeyEvent(*Event_Reply)
|| EventType(*Event_Reply) == Expose))
{
/* might be significant elsewhere -- save it for later */
AddEventToStore(&ev_save, *Event_Reply);
}
}
else
InsignificantEvent = FALSE;
}
while (InsignificantEvent);
return;
}
/* Check whether the event matches one of the events considered */
/* "not significant".*/
bool EventNotSignificant(Event_Reply, init_button)
XEvent Event_Reply;
int init_button;
{
/* Insignificant if not in following list */
return (!((EventType(Event_Reply) == ButtonRelease) ||
(EventType(Event_Reply) == MotionNotify) ||
(EventType(Event_Reply) == EnterNotify) ||
(EventType(Event_Reply) == Expose) ||
(EventType(Event_Reply) == LeaveNotify))
||
/* Insignificant if leave or enter is not "Normal" */
(((EventType(Event_Reply) == LeaveNotify) ||
(EventType(Event_Reply) == EnterNotify)) &&
(EventXMode(Event_Reply) != NotifyNormal))
||
/* Insignificant if hit button other than initial one */
((EventType(Event_Reply) == ButtonRelease) &&
(EventButton(Event_Reply) != init_button))
||
/* Insignificant if it's an expose and we're in savebits mode */
((EventType(Event_Reply) == Expose) &&
TestOptionFlag(current_menu, savebits))
||
/* Insignificant if tail end of a click -- and clicks allowed */
(click_allowed &&
(EventType(Event_Reply) == ButtonRelease) &&
(EventBTime(Event_Reply) - button_time < CLICK_TIME))
);
}
State LevelControlState(rep)
XEvent rep;
{
State next_state;
RTLMenu entered_menu;
RTLMenuItem entered_item;
switch (EventType(rep))
{
case MotionNotify:
case LeaveNotify:
next_state = LevelControl; /* loop back to this state */
break;
case EnterNotify:
/* Decide whether we've entered a menu window or item window */
entered_menu = MenuGetMenu(current_menu, EventXWindow(rep));
entered_item = MenuGetItem(current_menu,EventXWindow(rep));
if ((MenuIsNull(entered_menu)) && (ItemIsNull(entered_item)))
/* Must be some other window; carry on */
next_state = LevelControl;
else if (!ItemIsNull(entered_item) &&
MenuIsDisplayed(ItemMenu(entered_item)))
{
/* we entered an item, but not a window. This should only happen */
/* when we stayed in the parent of the current submenu. So, */
/* Pop that submenu and get to the item. */
if (level)
{
LockCursor(ItemWindow(entered_item));
PopSubmenu();
ProcessExposeEvents();
UnlockCursor();
current_item = entered_item;
Highlight(current_item);
next_state = GetItemState(rep);
}
else /* I must be very confused... */
{
next_state = Exit;
}
}
else if (!MenuIsNull(entered_menu)&&
MenuIsDisplayed(entered_menu))
{
/* entered a menu that is displayed */
while ((current_menu != entered_menu) && level)
/* drop down the menu that was entered */
PopSubmenu();
ProcessExposeEvents();
UnlockCursor();
if (current_menu == entered_menu)
next_state = Initial;
else
{
next_state = Exit;
}
}
else
next_state = LevelControl;
break;
case ButtonRelease:
next_state = Exit;
break;
default:
next_state = Exit;
break;
}
return next_state;
}
/* Figure out the status of the item we've just entered */
State GetItemState(rep)
XEvent rep;
{
int open_x;
State next_state;
if (ItemIsNull(current_item))
{
next_state = Exit;
}
else if (MenuIsNull(current_menu))
{
next_state = Exit;
}
else if (ItemIsLeaf(current_item) ||
(TestItemFlag(current_item, itemDisabled)))
{
if (MenuHasInitialItem(current_menu))
ClearInitialItem(current_menu);
next_state = Leaf;
}
else if (EventGetXCoord(rep) >= (int) ItemGetArrowPosition(current_item))
{
/* entered item in "auto pop-up zone", i.e., over pull-right arrow. */
LockCursor(ItemWindow(current_item));
TossExtraMoves(ItemWindow(current_item));
if (PushSubmenu(SelectChildX(current_menu, current_item, rep)))
{
LockCursor(ItemWindow(current_item));
PlacePointer(current_menu, current_item);
next_state = LevelControl;
ProcessExposeEvents();
}
else
next_state = CheckTrigger;
UnlockCursor();
}
else if (MenuHasInitialItem(current_menu))
{
/* Entered menu has initial item -- move to it */
current_item = GetInitialItem(current_menu);
open_x = ItemGetArrowPosition(current_item) +
EventXRootX(rep);
ClearInitialItem(current_menu);
LockCursor(ItemWindow(current_item));
if (PushSubmenu(open_x))
{
ProcessExposeEvents();
LockCursor(ItemWindow(current_item));
PlacePointer(current_menu, current_item);
next_state = Initial;
}
UnlockCursor();
}
else /* parent pull */
next_state = CheckTrigger;
return next_state;
}
State InitialState( rep)
XEvent rep;
{
State next_state;
switch (EventType(rep))
{
case EnterNotify:
if (MenuIsNull(current_menu))
{
next_state = Exit;
}
else if (EventXDetail(rep) == NotifyInferior)
next_state = Initial;
else
{
current_item = MenuGetItem(current_menu, EventXWindow(rep));
if (ItemIsNull(current_item))
{
next_state = Exit;
}
else
{
Highlight(current_item);
next_state = GetItemState(rep);
}
}
break;
case LeaveNotify:
/* Decide whether we're actually leaving */
/* this menu for another submenu or the root, */
/* or going into an item. */
next_state = (EventXDetail(rep) == NotifyInferior)?
Initial : LevelControl;
break;
case ButtonRelease:
next_state = Exit;
break;
case MotionNotify:
next_state = Initial;
break;
default:
next_state = Exit;
break;
}
return next_state;
}
#define NotSet -1
/* Look to see if pull-right is requested */
State CheckTriggerState(rep)
XEvent rep;
{
State next_state = CheckTrigger;
static int Trigger = NotSet;
static int OldX, NewX, childX;
if (MenuIsNull(current_menu) || ItemIsNull(current_item))
{
next_state = Exit;
goto exit;
}
if (Trigger == NotSet) /* set it */
{
Trigger = MIN(EventGetXCoord(rep) + MenuDelta(current_menu),
ItemGetArrowPosition(current_item));
NewX = NotSet;
}
switch (EventType(rep))
{
case LeaveNotify:
next_state = Initial;
Unhighlight(MenuGetItem(current_menu, EventXWindow(rep)));
Trigger = NotSet;
break;
case EnterNotify: /* Shouldn't really happen, but ... */
next_state = CheckTrigger;
break;
case ButtonRelease:
next_state = Exit;
Trigger = NotSet;
break;
case MotionNotify:
next_state = CheckTrigger;
OldX = NewX;
NewX = EventGetXCoord(rep);
if (NewX >= Trigger)
{
LockCursor(ItemWindow(current_item));
childX = SelectChildX(current_menu, current_item, rep);
Trigger = NotSet;
if (PushSubmenu(childX))
{
next_state = LevelControl;
ProcessExposeEvents();
LockCursor(ItemWindow(current_item));
PlacePointer(current_menu, current_item);
}
UnlockCursor();
}
else if (NewX < OldX) /* reverse motion */
Trigger = MIN(Trigger, NewX + MenuDelta(current_menu));
break;
default:
next_state = Exit;
break;
}
exit:
return next_state;
}
State LeafState(rep,selected)
XEvent rep;
bool *selected;
{
State next_state;
switch(EventType(rep))
{
case LeaveNotify:
Unhighlight(MenuGetItem(current_menu, EventXWindow(rep)));
next_state = Initial;
break;
case ButtonRelease:
if (!TestItemFlag(current_item, itemDisabled))
*selected = TRUE;
next_state = Exit;
break;
case EnterNotify:
case MotionNotify: /* if events set right, this never happens */
next_state = Leaf;
break;
default:
next_state = Exit;
break;
}
return next_state;
}
bool PushSubmenu(x)
int x;
{
int y;
bool pushed;
RTLMenuItem new_current_item;
if (ItemIsNull(current_item))
{
pushed = FALSE;
}
else if (MenuIsNull(ItemSubmenu(current_item)))
{
pushed = FALSE;
}
else if (ItemIsNull(MenuItems(ItemSubmenu(current_item))))
/* submenu has no items -- don't push, but not an error */
pushed = FALSE;
else
{
y = ItemGetMiddleY(current_item);
++level;
if (new_current_item =
Display_Menu(ItemSubmenu(current_item), current_menu, x, y))
{
XFlush(display);
current_menu = ItemSubmenu(current_item);
current_item = new_current_item;
pushed = TRUE;
}
else
{
pushed = FALSE;
}
}
return pushed;
}
void PopSubmenu()
{
RTLMenu parent;
RTLMenuItem item;
--level;
parent = MenuParent(current_menu);
Undisplay_Menu(current_menu);
current_menu = parent;
if (!MenuIsNull(current_menu))
{
item = MenuItemHighlighted(current_menu);
if (!ItemIsNull(item))
{
current_item = item;
}
}
return;
}
void Highlight(item)
RTLMenuItem item;
{
RTLMenuItem old_highlight;
old_highlight = MenuItemHighlighted(current_menu);
if ((item != old_highlight) && /* else, already highlighted */
(!ItemIsNull(item)))
{
if (!ItemIsNull(old_highlight))
Unhighlight(old_highlight);
if (!TestItemFlag(current_item, itemDisabled))
{
MenuInvert(ItemMenu(item), ItemWindow(item));
SetHighlightItem(ItemMenu(item), item);
}
}
return;
}
void Unhighlight(item)
RTLMenuItem item;
{
if (!ItemIsNull(item))
{
if (MenuItemHighlighted(current_menu) == item)
{
MenuInvert(ItemMenu(item), ItemWindow(item));
ResetHighlightItem(ItemMenu(item));
}
}
return;
}
void TossExtraMoves(window)
Window window;
{
XEvent ev;
while (XCheckTypedWindowEvent(display, window, MotionNotify, &ev));
return;
}
void ProcessExposeEvents()
{
RTLMenuItem item;
XEvent ev;
if (!TestOptionFlag(current_menu, savebits))
{
XSync(display,0);
while (XCheckTypedEvent(display, Expose, &ev))
{
item = MenuGetItem(current_menu, EventXWindow(ev));
if (!ItemIsNull(item))
Draw_Item(ItemMenu(item), item, EventEX(ev), EventEY(ev),
EventEWidth(ev), EventEHeight(ev));
}
}
return;
}