aboutsummaryrefslogtreecommitdiff
path: root/src/menu.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/menu.c')
-rw-r--r--src/menu.c799
1 files changed, 799 insertions, 0 deletions
diff --git a/src/menu.c b/src/menu.c
new file mode 100644
index 0000000..9f6b80c
--- /dev/null
+++ b/src/menu.c
@@ -0,0 +1,799 @@
+/***************************************************************************
+ * Menu functions display and handling functions.
+ * Copyright (C) 2004 Joe Wingbermuehle
+ ***************************************************************************/
+
+#include "jwm.h"
+#include "menu.h"
+#include "font.h"
+#include "client.h"
+#include "color.h"
+#include "icon.h"
+#include "image.h"
+#include "main.h"
+#include "cursor.h"
+#include "key.h"
+#include "button.h"
+#include "event.h"
+
+#define BASE_ICON_OFFSET 3
+
+typedef enum {
+ MENU_NOSELECTION = 0,
+ MENU_LEAVE = 1,
+ MENU_SUBSELECT = 2
+} MenuSelectionType;
+
+/* Submenu arrow, 4 x 7 pixels */
+static char menu_bitmap[] = {
+ 0x01, 0x03, 0x07, 0x0F, 0x07, 0x03, 0x01
+};
+
+static int ShowSubmenu(Menu *menu, Menu *parent, int x, int y);
+
+static void CreateMenu(Menu *menu, int x, int y);
+static void HideMenu(Menu *menu);
+static void DrawMenu(Menu *menu);
+static void RedrawMenuTree(Menu *menu);
+
+static int MenuLoop(Menu *menu);
+static MenuSelectionType UpdateMotion(Menu *menu, XEvent *event);
+
+static void UpdateMenu(Menu *menu);
+static void DrawMenuItem(Menu *menu, MenuItem *item, int index);
+static MenuItem *GetMenuItem(Menu *menu, int index);
+static int GetNextMenuIndex(Menu *menu);
+static int GetPreviousMenuIndex(Menu *menu);
+static int GetMenuIndex(Menu *menu, int index);
+static void SetPosition(Menu *tp, int index);
+
+static MenuAction *menuAction = NULL;
+
+int menuShown = 0;
+
+/***************************************************************************
+ ***************************************************************************/
+void InitializeMenu(Menu *menu) {
+
+ MenuItem *np;
+ int index, temp;
+ int hasSubmenu;
+ int userHeight;
+
+ menu->textOffset = 0;
+ menu->itemCount = 0;
+
+ /* Compute the max size needed */
+ userHeight = menu->itemHeight;
+ if(userHeight < 0) {
+ userHeight = 0;
+ }
+ menu->itemHeight = GetStringHeight(FONT_MENU);
+ for(np = menu->items; np; np = np->next) {
+ if(np->iconName) {
+ np->icon = LoadNamedIcon(np->iconName);
+ if(np->icon) {
+ if(userHeight == 0) {
+ if(menu->itemHeight < (int)np->icon->image->height) {
+ menu->itemHeight = np->icon->image->height;
+ }
+ if(menu->textOffset < (int)np->icon->image->width + 4) {
+ menu->textOffset = np->icon->image->width + 4;
+ }
+ }
+ }
+ } else {
+ np->icon = NULL;
+ }
+ ++menu->itemCount;
+ }
+ menu->itemHeight += BASE_ICON_OFFSET * 2;
+
+ if(userHeight) {
+ menu->itemHeight = userHeight + BASE_ICON_OFFSET * 2;
+ menu->textOffset = menu->itemHeight + BASE_ICON_OFFSET * 2;
+ }
+
+ menu->width = 5;
+ menu->parent = NULL;
+ menu->parentOffset = 0;
+
+ menu->height = 1;
+ if(menu->label) {
+ menu->height += menu->itemHeight;
+ }
+
+ /* Nothing else to do if there is nothing in the menu. */
+ if(menu->itemCount == 0) {
+ return;
+ }
+
+ menu->offsets = Allocate(sizeof(int) * menu->itemCount);
+
+ hasSubmenu = 0;
+ index = 0;
+ for(np = menu->items; np; np = np->next) {
+ menu->offsets[index++] = menu->height;
+ if(np->type == MENU_ITEM_SEPARATOR) {
+ menu->height += 5;
+ } else {
+ menu->height += menu->itemHeight;
+ }
+ if(np->name) {
+ temp = GetStringWidth(FONT_MENU, np->name);
+ if(temp > menu->width) {
+ menu->width = temp;
+ }
+ }
+ if(np->submenu) {
+ hasSubmenu = 7;
+ InitializeMenu(np->submenu);
+ }
+ }
+ menu->height += 2;
+ menu->width += 15 + hasSubmenu + menu->textOffset;
+
+}
+
+/***************************************************************************
+ ***************************************************************************/
+void ShowMenu(Menu *menu, RunMenuCommandType runner, int x, int y) {
+
+ int mouseStatus, keyboardStatus;
+
+ mouseStatus = GrabMouseForMenu();
+ keyboardStatus = JXGrabKeyboard(display, rootWindow, False,
+ GrabModeAsync, GrabModeAsync, CurrentTime);
+ if(!mouseStatus || keyboardStatus != GrabSuccess) {
+ return;
+ }
+
+ ShowSubmenu(menu, NULL, x, y);
+
+ JXUngrabKeyboard(display, CurrentTime);
+ JXUngrabPointer(display, CurrentTime);
+ RefocusClient();
+
+ if(menuAction) {
+ (runner)(menuAction);
+ menuAction = NULL;
+ }
+
+}
+
+/***************************************************************************
+ ***************************************************************************/
+void DestroyMenu(Menu *menu) {
+ MenuItem *np;
+
+ if(menu) {
+ while(menu->items) {
+ np = menu->items->next;
+ if(menu->items->name) {
+ Release(menu->items->name);
+ }
+ switch(menu->items->action.type) {
+ case MA_EXECUTE:
+ case MA_EXIT:
+ if(menu->items->action.data.str) {
+ Release(menu->items->action.data.str);
+ }
+ break;
+ default:
+ break;
+ }
+ if(menu->items->iconName) {
+ Release(menu->items->iconName);
+ }
+ if(menu->items->submenu) {
+ DestroyMenu(menu->items->submenu);
+ }
+ Release(menu->items);
+ menu->items = np;
+ }
+ if(menu->label) {
+ Release(menu->label);
+ }
+ if(menu->offsets) {
+ Release(menu->offsets);
+ }
+ Release(menu);
+ menu = NULL;
+ }
+}
+
+/***************************************************************************
+ ***************************************************************************/
+int ShowSubmenu(Menu *menu, Menu *parent, int x, int y) {
+ int status;
+
+ menu->parent = parent;
+ CreateMenu(menu, x, y);
+
+ ++menuShown;
+ status = MenuLoop(menu);
+ --menuShown;
+
+ HideMenu(menu);
+
+ return status;
+}
+
+/***************************************************************************
+ * Returns 0 if no selection was made or 1 if a selection was made.
+ ***************************************************************************/
+int MenuLoop(Menu *menu) {
+
+ XEvent event;
+ MenuItem *ip;
+ int count;
+ int hadMotion;
+ int pressx, pressy;
+
+ hadMotion = 0;
+
+ GetMousePosition(&pressx, &pressy);
+
+ for(;;) {
+
+ WaitForEvent(&event);
+
+ switch(event.type) {
+ case Expose:
+ RedrawMenuTree(menu);
+ break;
+
+ case ButtonPress:
+
+ pressx = -100;
+ pressy = -100;
+
+ case KeyPress:
+ case MotionNotify:
+ hadMotion = 1;
+ switch(UpdateMotion(menu, &event)) {
+ case MENU_NOSELECTION: /* no selection */
+ break;
+ case MENU_LEAVE: /* mouse left the menu */
+ JXPutBackEvent(display, &event);
+ return 0;
+ case MENU_SUBSELECT: /* selection made */
+ return 1;
+ }
+ break;
+
+ case ButtonRelease:
+
+ if(event.xbutton.button == Button4) {
+ break;
+ }
+ if(event.xbutton.button == Button5) {
+ break;
+ }
+ if(!hadMotion) {
+ break;
+ }
+ if(abs(event.xbutton.x_root - pressx) < doubleClickDelta) {
+ if(abs(event.xbutton.y_root - pressy) < doubleClickDelta) {
+ break;
+ }
+ }
+
+ if(menu->currentIndex >= 0) {
+ count = 0;
+ for(ip = menu->items; ip; ip = ip->next) {
+ if(count == menu->currentIndex) {
+ menuAction = &ip->action;
+ break;
+ }
+ ++count;
+ }
+ }
+ return 1;
+ default:
+ break;
+ }
+
+ }
+}
+
+/***************************************************************************
+ ***************************************************************************/
+void CreateMenu(Menu *menu, int x, int y) {
+
+ XSetWindowAttributes attr;
+ unsigned long attrMask;
+ int temp;
+
+ menu->lastIndex = -1;
+ menu->currentIndex = -1;
+
+ if(x + menu->width > rootWidth) {
+ if(menu->parent) {
+ x = menu->parent->x - menu->width;
+ } else {
+ x = rootWidth - menu->width;
+ }
+ }
+ temp = y;
+ if(y + menu->height > rootHeight) {
+ y = rootHeight - menu->height;
+ }
+ if(y < 0) {
+ y = 0;
+ }
+
+ menu->x = x;
+ menu->y = y;
+ menu->parentOffset = temp - y;
+
+ attrMask = 0;
+
+ attrMask |= CWEventMask;
+ attr.event_mask = ExposureMask;
+
+ attrMask |= CWBackPixel;
+ attr.background_pixel = colors[COLOR_MENU_BG];
+
+ attrMask |= CWSaveUnder;
+ attr.save_under = True;
+
+ menu->window = JXCreateWindow(display, rootWindow, x, y,
+ menu->width, menu->height, 0, CopyFromParent, InputOutput,
+ CopyFromParent, attrMask, &attr);
+
+ JXMapRaised(display, menu->window);
+
+}
+
+/***************************************************************************
+ ***************************************************************************/
+void HideMenu(Menu *menu) {
+
+ JXDestroyWindow(display, menu->window);
+
+}
+
+/***************************************************************************
+ ***************************************************************************/
+void RedrawMenuTree(Menu *menu) {
+
+ if(menu->parent) {
+ RedrawMenuTree(menu->parent);
+ }
+
+ DrawMenu(menu);
+ UpdateMenu(menu);
+
+}
+
+/***************************************************************************
+ ***************************************************************************/
+void DrawMenu(Menu *menu) {
+
+ MenuItem *np;
+ int x;
+
+ if(menu->label) {
+ DrawMenuItem(menu, NULL, -1);
+ }
+
+ x = 0;
+ for(np = menu->items; np; np = np->next) {
+ DrawMenuItem(menu, np, x);
+ ++x;
+ }
+
+ JXSetForeground(display, rootGC, colors[COLOR_MENU_UP]);
+ JXDrawLine(display, menu->window, rootGC,
+ 0, 0, menu->width - 1, 0);
+ JXDrawLine(display, menu->window, rootGC,
+ 0, 1, menu->width - 2, 1);
+ JXDrawLine(display, menu->window, rootGC,
+ 0, 2, 0, menu->height - 1);
+ JXDrawLine(display, menu->window, rootGC,
+ 1, 2, 1, menu->height - 2);
+
+ JXSetForeground(display, rootGC, colors[COLOR_MENU_DOWN]);
+ JXDrawLine(display, menu->window, rootGC,
+ 1, menu->height - 1, menu->width - 1, menu->height - 1);
+ JXDrawLine(display, menu->window, rootGC,
+ 2, menu->height - 2, menu->width - 1, menu->height - 2);
+ JXDrawLine(display, menu->window, rootGC,
+ menu->width - 1, 1, menu->width - 1, menu->height - 3);
+ JXDrawLine(display, menu->window, rootGC,
+ menu->width - 2, 2, menu->width - 2, menu->height - 3);
+
+}
+
+/***************************************************************************
+ ***************************************************************************/
+MenuSelectionType UpdateMotion(Menu *menu, XEvent *event) {
+
+ MenuItem *ip;
+ Menu *tp;
+ Window subwindow;
+ int x, y;
+
+ if(event->type == MotionNotify) {
+
+ SetMousePosition(event->xmotion.x_root, event->xmotion.y_root);
+ DiscardMotionEvents(event, menu->window);
+
+ x = event->xmotion.x_root - menu->x;
+ y = event->xmotion.y_root - menu->y;
+ subwindow = event->xmotion.subwindow;
+
+ } else if(event->type == ButtonPress) {
+
+ if(menu->currentIndex >= 0 || !menu->parent) {
+ tp = menu;
+ } else {
+ tp = menu->parent;
+ }
+
+ y = -1;
+ if(event->xbutton.button == Button4) {
+ y = GetPreviousMenuIndex(tp);
+ } else if(event->xbutton.button == Button5) {
+ y = GetNextMenuIndex(tp);
+ }
+
+ if(y >= 0) {
+ SetPosition(tp, y);
+ }
+
+ return MENU_NOSELECTION;
+
+ } else if(event->type == KeyPress) {
+
+ if(menu->currentIndex >= 0 || !menu->parent) {
+ tp = menu;
+ } else {
+ tp = menu->parent;
+ }
+
+ y = -1;
+ switch(GetKey(&event->xkey) & 0xFF) {
+ case KEY_UP:
+ y = GetPreviousMenuIndex(tp);
+ break;
+ case KEY_DOWN:
+ y = GetNextMenuIndex(tp);
+ break;
+ case KEY_RIGHT:
+ tp = menu;
+ y = 0;
+ break;
+ case KEY_LEFT:
+ if(tp->parent) {
+ tp = tp->parent;
+ if(tp->currentIndex >= 0) {
+ y = tp->currentIndex;
+ } else {
+ y = 0;
+ }
+ }
+ break;
+ case KEY_ESC:
+ return MENU_SUBSELECT;
+ case KEY_ENTER:
+ if(tp->currentIndex >= 0) {
+ x = 0;
+ for(ip = tp->items; ip; ip = ip->next) {
+ if(x == tp->currentIndex) {
+ menuAction = &ip->action;
+ break;
+ }
+ ++x;
+ }
+ }
+ return MENU_SUBSELECT;
+ default:
+ break;
+ }
+
+ if(y >= 0) {
+ SetPosition(tp, y);
+ }
+
+ return MENU_NOSELECTION;
+
+ } else {
+ Debug("invalid event type in menu.c:UpdateMotion");
+ return MENU_SUBSELECT;
+ }
+
+ /* Update the selection on the current menu */
+ if(x > 0 && y > 0 && x < menu->width && y < menu->height) {
+ menu->currentIndex = GetMenuIndex(menu, y);
+ } else if(menu->parent && subwindow != menu->parent->window) {
+
+ /* Leave if over a menu window. */
+ for(tp = menu->parent->parent; tp; tp = tp->parent) {
+ if(tp->window == subwindow) {
+ return MENU_LEAVE;
+ }
+ }
+ menu->currentIndex = -1;
+
+ } else {
+
+ /* Leave if over the parent, but not on this selection. */
+ tp = menu->parent;
+ if(tp && subwindow == tp->window) {
+ if(y < menu->parentOffset
+ || y > tp->itemHeight + menu->parentOffset) {
+ return MENU_LEAVE;
+ }
+ }
+
+ menu->currentIndex = -1;
+
+ }
+
+ /* Move the menu if needed. */
+ if(menu->height > rootHeight && menu->currentIndex >= 0) {
+
+ /* If near the top, shift down. */
+ if(y + menu->y <= 0) {
+ if(menu->currentIndex > 0) {
+ --menu->currentIndex;
+ SetPosition(menu, menu->currentIndex);
+ }
+ }
+
+ /* If near the bottom, shift up. */
+ if(y + menu->y + menu->itemHeight / 2 >= rootHeight) {
+ if(menu->currentIndex + 1 < menu->itemCount) {
+ ++menu->currentIndex;
+ SetPosition(menu, menu->currentIndex);
+ }
+ }
+
+ }
+
+ if(menu->lastIndex != menu->currentIndex) {
+ UpdateMenu(menu);
+ menu->lastIndex = menu->currentIndex;
+ }
+
+ /* If the selected item is a submenu, show it. */
+ ip = GetMenuItem(menu, menu->currentIndex);
+ if(ip && ip->submenu) {
+ if(ShowSubmenu(ip->submenu, menu, menu->x + menu->width,
+ menu->y + menu->offsets[menu->currentIndex])) {
+
+ /* Item selected; destroy the menu tree. */
+ return MENU_SUBSELECT;
+
+ } else {
+
+ /* No selection made. */
+ UpdateMenu(menu);
+
+ }
+ }
+
+ return MENU_NOSELECTION;
+
+}
+
+/***************************************************************************
+ ***************************************************************************/
+void UpdateMenu(Menu *menu) {
+
+ ButtonNode button;
+ Pixmap pixmap;
+ MenuItem *ip;
+
+ /* Clear the old selection. */
+ ip = GetMenuItem(menu, menu->lastIndex);
+ DrawMenuItem(menu, ip, menu->lastIndex);
+
+ /* Highlight the new selection. */
+ ip = GetMenuItem(menu, menu->currentIndex);
+ if(ip) {
+
+ if(ip->type == MENU_ITEM_SEPARATOR) {
+ return;
+ }
+
+ ResetButton(&button, menu->window, rootGC);
+ button.type = BUTTON_MENU_ACTIVE;
+ button.font = FONT_MENU;
+ button.width = menu->width - 5;
+ button.height = menu->itemHeight - 2;
+ button.icon = ip->icon;
+ button.text = ip->name;
+ button.x = 2;
+ button.y = menu->offsets[menu->currentIndex] + 1;
+ DrawButton(&button);
+
+ if(ip->submenu) {
+ pixmap = JXCreatePixmapFromBitmapData(display, menu->window,
+ menu_bitmap, 4, 7, colors[COLOR_MENU_ACTIVE_FG],
+ colors[COLOR_MENU_ACTIVE_BG], rootDepth);
+ JXCopyArea(display, pixmap, menu->window, rootGC, 0, 0, 4, 7,
+ menu->width - 9,
+ menu->offsets[menu->currentIndex] + menu->itemHeight / 2 - 4);
+ JXFreePixmap(display, pixmap);
+ }
+ }
+
+}
+
+/***************************************************************************
+ ***************************************************************************/
+void DrawMenuItem(Menu *menu, MenuItem *item, int index) {
+
+ ButtonNode button;
+ Pixmap pixmap;
+
+ Assert(menu);
+
+ if(!item) {
+ if(index == -1 && menu->label) {
+ ResetButton(&button, menu->window, rootGC);
+ button.x = 2;
+ button.y = 2;
+ button.width = menu->width - 5;
+ button.height = menu->itemHeight - 2;
+ button.font = FONT_MENU;
+ button.type = BUTTON_LABEL;
+ button.text = menu->label;
+ button.alignment = ALIGN_CENTER;
+ DrawButton(&button);
+ }
+ return;
+ }
+
+ if(item->name) {
+
+ ResetButton(&button, menu->window, rootGC);
+ button.x = 2;
+ button.y = 1 + menu->offsets[index];
+ button.font = FONT_MENU;
+ button.type = BUTTON_LABEL;
+ button.width = menu->width - 5;
+ button.height = menu->itemHeight - 2;
+ button.text = item->name;
+ button.icon = item->icon;
+ DrawButton(&button);
+
+ } else if(item->type == MENU_ITEM_SEPARATOR) {
+
+ JXSetForeground(display, rootGC, colors[COLOR_MENU_DOWN]);
+ JXDrawLine(display, menu->window, rootGC, 4,
+ menu->offsets[index] + 2, menu->width - 6,
+ menu->offsets[index] + 2);
+ JXSetForeground(display, rootGC, colors[COLOR_MENU_UP]);
+ JXDrawLine(display, menu->window, rootGC, 4,
+ menu->offsets[index] + 3, menu->width - 6,
+ menu->offsets[index] + 3);
+
+ }
+
+ if(item->submenu) {
+
+ pixmap = JXCreatePixmapFromBitmapData(display, menu->window,
+ menu_bitmap, 4, 7, colors[COLOR_MENU_FG],
+ colors[COLOR_MENU_BG], rootDepth);
+ JXCopyArea(display, pixmap, menu->window, rootGC, 0, 0, 4, 7,
+ menu->width - 9, menu->offsets[index] + menu->itemHeight / 2 - 4);
+ JXFreePixmap(display, pixmap);
+
+ }
+
+}
+
+/***************************************************************************
+ ***************************************************************************/
+int GetNextMenuIndex(Menu *menu) {
+
+ MenuItem *item;
+ int x;
+
+ for(x = menu->currentIndex + 1; x < menu->itemCount; x++) {
+ item = GetMenuItem(menu, x);
+ if(item->type != MENU_ITEM_SEPARATOR) {
+ return x;
+ }
+ }
+
+ return 0;
+
+}
+
+/***************************************************************************
+ ***************************************************************************/
+int GetPreviousMenuIndex(Menu *menu) {
+
+ MenuItem *item;
+ int x;
+
+ for(x = menu->currentIndex - 1; x >= 0; x--) {
+ item = GetMenuItem(menu, x);
+ if(item->type != MENU_ITEM_SEPARATOR) {
+ return x;
+ }
+ }
+
+ return menu->itemCount - 1;
+
+}
+
+/***************************************************************************
+ ***************************************************************************/
+int GetMenuIndex(Menu *menu, int y) {
+
+ int x;
+
+ if(y < menu->offsets[0]) {
+ return -1;
+ }
+ for(x = 0; x < menu->itemCount - 1; x++) {
+ if(y >= menu->offsets[x] && y < menu->offsets[x + 1]) {
+ return x;
+ }
+ }
+ return x;
+
+}
+
+/***************************************************************************
+ ***************************************************************************/
+MenuItem *GetMenuItem(Menu *menu, int index) {
+
+ MenuItem *ip;
+
+ if(index >= 0) {
+ for(ip = menu->items; ip; ip = ip->next) {
+ if(!index) {
+ return ip;
+ }
+ --index;
+ }
+ } else {
+ ip = NULL;
+ }
+
+ return ip;
+
+}
+
+/***************************************************************************
+ ***************************************************************************/
+void SetPosition(Menu *tp, int index) {
+
+ int y;
+ int updated;
+
+ y = tp->offsets[index] + tp->itemHeight / 2;
+
+ if(tp->height > rootHeight) {
+
+ updated = 0;
+ while(y + tp->y < tp->itemHeight / 2) {
+ tp->y += tp->itemHeight;
+ updated = tp->itemHeight;
+ }
+ while(y + tp->y >= rootHeight) {
+ tp->y -= tp->itemHeight;
+ updated = -tp->itemHeight;
+ }
+ if(updated) {
+ JXMoveWindow(display, tp->window, tp->x, tp->y);
+ y += updated;
+ }
+
+ }
+
+ /* We need to do this twice so the event gets registered
+ * on the submenu if one exists. */
+ MoveMouse(tp->window, 6, y);
+ MoveMouse(tp->window, 6, y);
+
+}
+
+