new menu editor updates
[wmaker-crm.git] / WPrefs.app / editmenu.c
blob8b0c8f8da658414303fd8ffa87e2c4b38d9f1b11
1 /* editmenu.c - editable menus
2 *
3 * WPrefs - Window Maker Preferences Program
4 *
5 * Copyright (c) 2000 Alfredo K. Kojima
6 *
7 * This program is free software; you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License as published by
9 * the Free Software Foundation; either version 2 of the License, or
10 * (at your option) any later version.
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
17 * You should have received a copy of the GNU General Public License
18 * along with this program; if not, write to the Free Software
19 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307,
20 * USA.
24 #include <WINGsP.h>
25 #include <WUtil.h>
26 #include <stdlib.h>
27 #include <assert.h>
28 #include <ctype.h>
30 #include "editmenu.h"
32 typedef struct W_EditMenuItem {
33 W_Class widgetClass;
34 WMView *view;
36 struct W_EditMenu *parent;
38 char *label;
40 void *data;
41 WMCallback *destroyData;
43 struct W_EditMenu *submenu; /* if it's a cascade, NULL otherwise */
45 struct {
46 unsigned isTitle:1;
47 unsigned isHighlighted:1;
48 } flags;
49 } EditMenuItem;
52 typedef struct W_EditMenu {
53 W_Class widgetClass;
54 WMView *view;
56 struct W_EditMenu *parent;
58 WMBag *items; /* EditMenuItem */
60 EditMenuItem *selectedItem;
62 WMTextField *tfield;
64 int titleHeight;
65 int itemHeight;
67 WEditMenuDelegate *delegate;
69 /* item dragging */
70 int draggedItem;
71 int dragX, dragY;
73 /* only for non-standalone menu */
74 WMSize maxSize;
75 WMSize minSize;
77 struct {
78 unsigned standalone:1;
79 unsigned isTitled:1;
81 unsigned acceptsDrop:1;
82 unsigned isFactory:1;
83 unsigned isSelectable:1;
84 unsigned isEditable:1;
86 unsigned isTornOff:1;
88 unsigned isDragging:1;
89 unsigned isEditing:1;
90 } flags;
91 } EditMenu;
95 /******************** WEditMenuItem ********************/
97 static void destroyEditMenuItem(WEditMenuItem *iPtr);
98 static void paintEditMenuItem(WEditMenuItem *iPtr);
99 static void handleItemEvents(XEvent *event, void *data);
101 static void handleItemClick(XEvent *event, void *data);
104 static W_Class EditMenuItemClass = 0;
107 W_Class
108 InitEditMenuItem(WMScreen *scr)
110 /* register our widget with WINGs and get our widget class ID */
111 if (!EditMenuItemClass) {
112 EditMenuItemClass = W_RegisterUserWidget();
115 return EditMenuItemClass;
119 WEditMenuItem*
120 WCreateEditMenuItem(WMWidget *parent, char *title, Bool isTitle)
122 WEditMenuItem *iPtr;
123 WMScreen *scr = WMWidgetScreen(parent);
125 if (!EditMenuItemClass)
126 InitEditMenuItem(scr);
129 iPtr = wmalloc(sizeof(WEditMenuItem));
131 memset(iPtr, 0, sizeof(WEditMenuItem));
133 iPtr->widgetClass = EditMenuItemClass;
135 iPtr->view = W_CreateView(W_VIEW(parent));
136 if (!iPtr->view) {
137 free(iPtr);
138 return NULL;
140 iPtr->view->self = iPtr;
142 iPtr->parent = parent;
144 iPtr->label = wstrdup(title);
146 iPtr->flags.isTitle = isTitle;
148 if (isTitle) {
149 WMSetWidgetBackgroundColor(iPtr, WMBlackColor(scr));
152 WMCreateEventHandler(iPtr->view, ExposureMask|StructureNotifyMask,
153 handleItemEvents, iPtr);
155 WMCreateEventHandler(iPtr->view, ButtonPressMask|ButtonReleaseMask
156 |ButtonMotionMask, handleItemClick, iPtr);
159 return iPtr;
163 char *WGetEditMenuItemTitle(WEditMenuItem *item)
165 return item->label;
169 void *WGetEditMenuItemData(WEditMenuItem *item)
171 return item->data;
175 void WSetEditMenuItemData(WEditMenuItem *item, void *data,
176 WMCallback *destroyer)
178 item->data = data;
179 item->destroyData = destroyer;
185 static void
186 paintEditMenuItem(WEditMenuItem *iPtr)
188 WMScreen *scr = WMWidgetScreen(iPtr);
189 WMColor *color;
190 Window win = W_VIEW(iPtr)->window;
191 int w = W_VIEW(iPtr)->size.width;
192 int h = W_VIEW(iPtr)->size.height;
193 WMFont *font = (iPtr->flags.isTitle ? scr->boldFont : scr->normalFont);
195 if (!iPtr->view->flags.realized)
196 return;
198 color = scr->black;
199 if (iPtr->flags.isTitle && !iPtr->flags.isHighlighted) {
200 color = scr->white;
204 XClearWindow(scr->display, win);
206 W_DrawRelief(scr, win, 0, 0, w+1, h, WRRaised);
208 WMDrawString(scr, win, WMColorGC(color), font, 5, 3, iPtr->label,
209 strlen(iPtr->label));
211 if (iPtr->submenu) {
212 /* draw the cascade indicator */
213 XDrawLine(scr->display,win,WMColorGC(scr->darkGray),
214 w-11, 6, w-6, h/2-1);
215 XDrawLine(scr->display,win,WMColorGC(scr->white),
216 w-11, h-8, w-6, h/2-1);
217 XDrawLine(scr->display,win,WMColorGC(scr->black),
218 w-12, 6, w-12, h-8);
223 static void
224 highlightItem(WEditMenuItem *iPtr, Bool high)
226 if (iPtr->flags.isTitle)
227 return;
229 iPtr->flags.isHighlighted = high;
231 if (high) {
232 WMSetWidgetBackgroundColor(iPtr, WMWhiteColor(WMWidgetScreen(iPtr)));
233 } else {
234 if (!iPtr->flags.isTitle) {
235 WMSetWidgetBackgroundColor(iPtr,
236 WMGrayColor(WMWidgetScreen(iPtr)));
237 } else {
238 WMSetWidgetBackgroundColor(iPtr,
239 WMBlackColor(WMWidgetScreen(iPtr)));
245 static int
246 getItemTextWidth(WEditMenuItem *iPtr)
248 WMScreen *scr = WMWidgetScreen(iPtr);
250 if (iPtr->flags.isTitle) {
251 return WMWidthOfString(scr->boldFont, iPtr->label,
252 strlen(iPtr->label));
253 } else {
254 return WMWidthOfString(scr->normalFont, iPtr->label,
255 strlen(iPtr->label));
261 static void
262 handleItemEvents(XEvent *event, void *data)
264 WEditMenuItem *iPtr = (WEditMenuItem*)data;
266 switch (event->type) {
267 case Expose:
268 if (event->xexpose.count!=0)
269 break;
270 paintEditMenuItem(iPtr);
271 break;
273 case DestroyNotify:
274 destroyEditMenuItem(iPtr);
275 break;
280 static void
281 destroyEditMenuItem(WEditMenuItem *iPtr)
283 if (iPtr->label)
284 free(iPtr->label);
285 if (iPtr->data && iPtr->destroyData)
286 (*iPtr->destroyData)(iPtr->data);
288 free(iPtr);
293 /******************** WEditMenu *******************/
295 static void destroyEditMenu(WEditMenu *mPtr);
297 static void updateMenuContents(WEditMenu *mPtr);
299 static void handleEvents(XEvent *event, void *data);
301 static void editItemLabel(WEditMenuItem *item);
302 static void stopEditItem(WEditMenu *menu, Bool apply);
305 static void deselectItem(WEditMenu *menu);
308 static W_Class EditMenuClass = 0;
311 W_Class
312 InitEditMenu(WMScreen *scr)
314 /* register our widget with WINGs and get our widget class ID */
315 if (!EditMenuClass) {
317 EditMenuClass = W_RegisterUserWidget();
320 return EditMenuClass;
325 typedef struct {
326 int flags;
327 int window_style;
328 int window_level;
329 int reserved;
330 Pixmap miniaturize_pixmap; /* pixmap for miniaturize button */
331 Pixmap close_pixmap; /* pixmap for close button */
332 Pixmap miniaturize_mask; /* miniaturize pixmap mask */
333 Pixmap close_mask; /* close pixmap mask */
334 int extra_flags;
335 } GNUstepWMAttributes;
338 #define GSWindowStyleAttr (1<<0)
339 #define GSWindowLevelAttr (1<<1)
342 static void
343 writeGNUstepWMAttr(WMScreen *scr, Window window, GNUstepWMAttributes *attr)
345 unsigned long data[9];
347 /* handle idiot compilers where array of CARD32 != struct of CARD32 */
348 data[0] = attr->flags;
349 data[1] = attr->window_style;
350 data[2] = attr->window_level;
351 data[3] = 0; /* reserved */
352 /* The X protocol says XIDs are 32bit */
353 data[4] = attr->miniaturize_pixmap;
354 data[5] = attr->close_pixmap;
355 data[6] = attr->miniaturize_mask;
356 data[7] = attr->close_mask;
357 data[8] = attr->extra_flags;
358 XChangeProperty(scr->display, window, scr->attribsAtom, scr->attribsAtom,
359 32, PropModeReplace, (unsigned char *)data, 9);
363 static void
364 realizeObserver(void *self, WMNotification *not)
366 WEditMenu *menu = (WEditMenu*)self;
367 GNUstepWMAttributes attribs;
369 memset(&attribs, 0, sizeof(GNUstepWMAttributes));
370 attribs.flags = GSWindowStyleAttr|GSWindowLevelAttr;
371 attribs.window_style = WMBorderlessWindowMask;
372 attribs.window_level = WMSubmenuWindowLevel;
374 writeGNUstepWMAttr(WMWidgetScreen(menu), menu->view->window, &attribs);
378 static void
379 itemSelectObserver(void *self, WMNotification *notif)
381 WEditMenu *menu = (WEditMenu*)self;
382 WEditMenu *rmenu;
384 rmenu = (WEditMenu*)WMGetNotificationObject(notif);
385 /* check whether rmenu is from the same hierarchy of menu? */
387 if (rmenu == menu) {
388 return;
391 if (menu->selectedItem && !menu->selectedItem->submenu) {
392 deselectItem(menu);
397 static WEditMenu*
398 makeEditMenu(WMScreen *scr, WMWidget *parent, char *title)
400 WEditMenu *mPtr;
401 WEditMenuItem *item;
403 if (!EditMenuClass)
404 InitEditMenu(scr);
407 mPtr = wmalloc(sizeof(WEditMenu));
408 memset(mPtr, 0, sizeof(WEditMenu));
410 mPtr->widgetClass = EditMenuClass;
412 if (parent) {
413 mPtr->view = W_CreateView(W_VIEW(parent));
414 mPtr->flags.standalone = 0;
415 } else {
416 mPtr->view = W_CreateTopView(scr);
417 mPtr->flags.standalone = 1;
419 if (!mPtr->view) {
420 free(mPtr);
421 return NULL;
423 mPtr->view->self = mPtr;
425 mPtr->flags.isSelectable = 1;
426 mPtr->flags.isEditable = 1;
428 W_SetViewBackgroundColor(mPtr->view, scr->darkGray);
430 WMAddNotificationObserver(realizeObserver, mPtr,
431 WMViewRealizedNotification, mPtr->view);
433 WMAddNotificationObserver(itemSelectObserver, mPtr,
434 "EditMenuItemSelected", NULL);
436 mPtr->items = WMCreateBag(4);
438 WMCreateEventHandler(mPtr->view, ExposureMask|StructureNotifyMask,
439 handleEvents, mPtr);
442 if (title != NULL) {
443 item = WCreateEditMenuItem(mPtr, title, True);
445 WMMapWidget(item);
446 WMPutInBag(mPtr->items, item);
448 mPtr->flags.isTitled = 1;
451 mPtr->itemHeight = WMFontHeight(scr->normalFont) + 6;
452 mPtr->titleHeight = WMFontHeight(scr->boldFont) + 8;
454 updateMenuContents(mPtr);
456 return mPtr;
460 WEditMenu*
461 WCreateEditMenu(WMScreen *scr, char *title)
463 return makeEditMenu(scr, NULL, title);
467 WEditMenu*
468 WCreateEditMenuPad(WMWidget *parent)
470 return makeEditMenu(WMWidgetScreen(parent), parent, NULL);
474 void
475 WSetEditMenuDelegate(WEditMenu *mPtr, WEditMenuDelegate *delegate)
477 mPtr->delegate = delegate;
481 WEditMenuItem*
482 WInsertMenuItemWithTitle(WEditMenu *mPtr, int index, char *title)
484 WEditMenuItem *item;
486 item = WCreateEditMenuItem(mPtr, title, False);
488 WMMapWidget(item);
490 if (index >= WMGetBagItemCount(mPtr->items)) {
491 WMPutInBag(mPtr->items, item);
492 } else {
493 if (index < 0)
494 index = 0;
495 if (mPtr->flags.isTitled)
496 index++;
497 WMInsertInBag(mPtr->items, index, item);
500 updateMenuContents(mPtr);
502 return item;
507 WEditMenuItem*
508 WAddMenuItemWithTitle(WEditMenu *mPtr, char *title)
510 return WInsertMenuItemWithTitle(mPtr, WMGetBagItemCount(mPtr->items),
511 title);
516 void
517 WSetEditMenuTitle(WEditMenu *mPtr, char *title)
519 WEditMenuItem *item;
521 item = WMGetFromBag(mPtr->items, 0);
523 free(item->label);
524 item->label = wstrdup(title);
525 updateMenuContents(mPtr);
529 void
530 WSetEditMenuAcceptsDrop(WEditMenu *mPtr, Bool flag)
532 mPtr->flags.acceptsDrop = flag;
536 void
537 WSetEditMenuSubmenu(WEditMenu *mPtr, WEditMenuItem *item, WEditMenu *submenu)
539 item->submenu = submenu;
540 submenu->parent = mPtr;
542 paintEditMenuItem(item);
546 WEditMenu*
547 WGetEditMenuSubmenu(WEditMenu *mPtr, WEditMenuItem *item)
549 return item->submenu;
553 static int simpleMatch(void *a, void *b)
555 if (a == b)
556 return 1;
557 else
558 return 0;
562 void
563 WRemoveEditMenuItem(WEditMenu *mPtr, WEditMenuItem *item)
565 int index;
567 index = WMFindInBag(mPtr->items, simpleMatch, item);
569 if (index == WBNotFound) {
570 return;
573 WMDeleteFromBag(mPtr->items, index);
575 updateMenuContents(mPtr);
579 void
580 WSetEditMenuSelectable(WEditMenu *mPtr, Bool flag)
582 mPtr->flags.isSelectable = flag;
586 void
587 WSetEditMenuEditable(WEditMenu *mPtr, Bool flag)
589 mPtr->flags.isEditable = flag;
593 void
594 WSetEditMenuIsFactory(WEditMenu *mPtr, Bool flag)
596 mPtr->flags.isFactory = flag;
600 void
601 WSetEditMenuMinSize(WEditMenu *mPtr, WMSize size)
603 mPtr->minSize.width = size.width;
604 mPtr->minSize.height = size.height;
608 void
609 WSetEditMenuMaxSize(WEditMenu *mPtr, WMSize size)
611 mPtr->maxSize.width = size.width;
612 mPtr->maxSize.height = size.height;
616 WMPoint
617 WGetEditMenuLocationForSubmenu(WEditMenu *mPtr, WEditMenu *submenu)
619 WMBagIterator iter;
620 WEditMenuItem *item;
621 int y;
623 if (mPtr->flags.isTitled)
624 y = -mPtr->titleHeight;
625 else
626 y = 0;
627 WM_ITERATE_BAG(mPtr->items, item, iter) {
628 if (item->submenu == submenu) {
629 WMPoint pt = WMGetViewScreenPosition(mPtr->view);
631 return wmkpoint(pt.x + mPtr->view->size.width, pt.y + y);
633 y += W_VIEW_HEIGHT(item->view);
636 puts("invalid submenu passed to WGetEditMenuLocationForSubmenu()");
638 return wmkpoint(0,0);
642 void
643 WTearOffEditMenu(WEditMenu *menu, WEditMenu *submenu)
645 submenu->flags.isTornOff = 1;
647 if (menu->selectedItem && menu->selectedItem->submenu == submenu)
648 deselectItem(menu);
653 Bool
654 WEditMenuIsTornOff(WEditMenu *mPtr)
656 return mPtr->flags.isTornOff;
660 static void
661 updateMenuContents(WEditMenu *mPtr)
663 int newW, newH;
664 int w;
665 int i;
666 int iheight = mPtr->itemHeight;
667 int offs = 1;
668 WMBagIterator iter;
669 WEditMenuItem *item;
671 newW = 0;
672 newH = offs;
674 i = 0;
675 WM_ITERATE_BAG(mPtr->items, item, iter) {
676 w = getItemTextWidth(item);
678 newW = WMAX(w, newW);
680 WMMoveWidget(item, offs, newH);
681 if (i == 0 && mPtr->flags.isTitled) {
682 newH += mPtr->titleHeight;
683 } else {
684 newH += iheight;
686 i = 1;
689 newW += iheight + 5;
690 newH--;
692 if (mPtr->minSize.width)
693 newW = WMAX(newW, mPtr->minSize.width);
694 if (mPtr->maxSize.width)
695 newW = WMIN(newW, mPtr->maxSize.width);
697 if (mPtr->minSize.height)
698 newH = WMAX(newH, mPtr->minSize.height);
699 if (mPtr->maxSize.height)
700 newH = WMIN(newH, mPtr->maxSize.height);
702 W_ResizeView(mPtr->view, newW, newH+1);
704 newW -= 2*offs;
706 i = 0;
707 WM_ITERATE_BAG(mPtr->items, item, iter) {
708 if (i == 0 && mPtr->flags.isTitled) {
709 WMResizeWidget(item, newW, mPtr->titleHeight);
710 } else {
711 WMResizeWidget(item, newW, iheight);
713 i = 1;
718 static void
719 unmapMenu(WEditMenu *menu)
721 WMUnmapWidget(menu);
723 if (menu->selectedItem) {
724 deselectItem(menu);
729 static void
730 deselectItem(WEditMenu *menu)
732 WEditMenu *submenu;
733 WEditMenuItem *item = menu->selectedItem;
735 highlightItem(item, False);
737 if (menu->delegate && menu->delegate->itemDeselected) {
738 (*menu->delegate->itemDeselected)(menu->delegate, menu, item);
740 submenu = item->submenu;
742 if (submenu && !WEditMenuIsTornOff(submenu)) {
743 unmapMenu(submenu);
746 menu->selectedItem = NULL;
750 static void
751 selectItem(WEditMenu *menu, WEditMenuItem *item)
753 if (!menu->flags.isSelectable || menu->selectedItem == item) {
754 return;
756 if (menu->selectedItem) {
757 deselectItem(menu);
760 if (menu->flags.isEditing) {
761 stopEditItem(menu, False);
764 if (item && !item->flags.isTitle) {
765 highlightItem(item, True);
767 if (item->submenu) {
768 WMPoint pt;
769 XSizeHints *hints;
771 hints = XAllocSizeHints();
773 pt = WGetEditMenuLocationForSubmenu(menu, item->submenu);
775 hints->flags = USPosition;
776 hints->x = pt.x;
777 hints->y = pt.y;
779 WMMoveWidget(item->submenu, pt.x, pt.y);
780 XSetWMNormalHints(W_VIEW_DISPLAY(item->submenu->view),
781 W_VIEW_DRAWABLE(item->submenu->view),
782 hints);
783 XFree(hints);
784 WMMapWidget(item->submenu);
786 item->submenu->flags.isTornOff = 0;
789 WMPostNotificationName("EditMenuItemSelected", menu, NULL);
791 if (menu->delegate && menu->delegate->itemSelected) {
792 (*menu->delegate->itemSelected)(menu->delegate, menu, item);
796 menu->selectedItem = item;
800 static void
801 paintMenu(WEditMenu *mPtr)
803 W_View *view = mPtr->view;
805 W_DrawRelief(W_VIEW_SCREEN(view), W_VIEW_DRAWABLE(view), 0, 0,
806 W_VIEW_WIDTH(view), W_VIEW_HEIGHT(view), WRSimple);
810 static void
811 handleEvents(XEvent *event, void *data)
813 WEditMenu *mPtr = (WEditMenu*)data;
815 switch (event->type) {
816 case DestroyNotify:
817 destroyEditMenu(mPtr);
818 break;
820 case Expose:
821 if (event->xexpose.count == 0)
822 paintMenu(mPtr);
823 break;
830 /* -------------------------- Menu Label Editing ------------------------ */
833 static void
834 stopEditItem(WEditMenu *menu, Bool apply)
836 char *text;
838 if (apply) {
839 text = WMGetTextFieldText(menu->tfield);
841 free(menu->selectedItem->label);
842 menu->selectedItem->label = wstrdup(text);
844 updateMenuContents(menu);
846 WMUnmapWidget(menu->tfield);
847 menu->flags.isEditing = 0;
851 static void
852 textEndedEditing(struct WMTextFieldDelegate *self, WMNotification *notif)
854 WEditMenu *menu = (WEditMenu*)self->data;
855 int reason;
856 int i;
857 WEditMenuItem *item;
859 if (!menu->flags.isEditing)
860 return;
862 reason = (int)WMGetNotificationClientData(notif);
864 switch (reason) {
865 case WMEscapeTextMovement:
866 stopEditItem(menu, False);
867 break;
869 case WMReturnTextMovement:
870 stopEditItem(menu, True);
871 break;
873 case WMTabTextMovement:
874 stopEditItem(menu, True);
876 i = WMFindInBag(menu->items, simpleMatch, menu->selectedItem);
877 item = WMGetFromBag(menu->items, i+1);
878 if (item != NULL) {
879 selectItem(menu, item);
880 editItemLabel(item);
882 break;
884 case WMBacktabTextMovement:
885 stopEditItem(menu, True);
887 i = WMFindInBag(menu->items, simpleMatch, menu->selectedItem);
888 item = WMGetFromBag(menu->items, i-1);
889 if (item != NULL) {
890 selectItem(menu, item);
891 editItemLabel(item);
893 break;
899 static WMTextFieldDelegate textFieldDelegate = {
900 NULL,
901 NULL,
902 NULL,
903 textEndedEditing,
904 NULL,
905 NULL
909 static void
910 editItemLabel(WEditMenuItem *item)
912 WEditMenu *mPtr = item->parent;
913 WMTextField *tf;
914 int i;
916 if (!mPtr->flags.isEditable) {
917 return;
920 if (!mPtr->tfield) {
921 mPtr->tfield = WMCreateTextField(mPtr);
922 WMSetTextFieldBeveled(mPtr->tfield, False);
923 WMRealizeWidget(mPtr->tfield);
925 textFieldDelegate.data = mPtr;
927 WMSetTextFieldDelegate(mPtr->tfield, &textFieldDelegate);
929 tf = mPtr->tfield;
931 i = WMFindInBag(mPtr->items, simpleMatch, item);
933 WMSetTextFieldText(tf, item->label);
934 WMSelectTextFieldRange(tf, wmkrange(0, strlen(item->label)));
935 WMResizeWidget(tf, mPtr->view->size.width, mPtr->itemHeight);
936 WMMoveWidget(tf, 0, item->view->pos.y);
937 WMMapWidget(tf);
938 WMSetFocusToWidget(tf);
940 mPtr->flags.isEditing = 1;
945 /* -------------------------------------------------- */
948 static void
949 slideWindow(Display *dpy, Window win, int srcX, int srcY, int dstX, int dstY)
951 double x, y, dx, dy;
952 int i;
953 int iterations;
955 iterations = WMIN(25, WMAX(abs(dstX-srcX), abs(dstY-srcY)));
957 x = srcX;
958 y = srcY;
960 dx = (double)(dstX-srcX)/iterations;
961 dy = (double)(dstY-srcY)/iterations;
963 for (i = 0; i <= iterations; i++) {
964 XMoveWindow(dpy, win, x, y);
965 XFlush(dpy);
967 wusleep(800);
969 x += dx;
970 y += dy;
975 static int errorHandler(Display *d, XErrorEvent *ev)
977 /* just ignore */
978 return 0;
982 static WEditMenu*
983 findMenuInWindow(Display *dpy, Window toplevel, int x, int y)
985 Window foo, bar;
986 Window *children;
987 unsigned nchildren;
988 int i;
989 WEditMenu *menu;
990 WMView *view;
991 int (*oldHandler)(Display *, XErrorEvent *);
993 view = W_GetViewForXWindow(dpy, toplevel);
994 if (view && view->self && WMWidgetClass(view->self) == EditMenuClass) {
995 menu = (WEditMenu*)view->self;
996 if (menu->flags.acceptsDrop) {
997 return menu;
1001 if (!XQueryTree(dpy, toplevel, &foo, &bar,
1002 &children, &nchildren) || children == NULL) {
1003 return NULL;
1006 oldHandler = XSetErrorHandler(errorHandler);
1008 /* first window that contains the point is the one */
1009 for (i = nchildren-1; i >= 0; i--) {
1010 XWindowAttributes attr;
1012 if (XGetWindowAttributes(dpy, children[i], &attr)
1013 && attr.map_state == IsViewable
1014 && x >= attr.x && y >= attr.y
1015 && x < attr.x + attr.width && y < attr.y + attr.height) {
1016 Window tmp;
1018 tmp = children[i];
1020 menu = findMenuInWindow(dpy, tmp, x - attr.x, y - attr.y);
1021 if (menu) {
1022 XFree(children);
1023 return menu;
1028 XSetErrorHandler(oldHandler);
1030 XFree(children);
1031 return NULL;
1035 static void
1036 handleDragOver(WEditMenu *menu, WMView *view, WEditMenuItem *item,
1037 int x, int y)
1039 WMScreen *scr = W_VIEW_SCREEN(menu->view);
1040 Window blaw;
1041 int mx, my;
1042 int offs;
1044 XTranslateCoordinates(scr->display, W_VIEW_DRAWABLE(menu->view),
1045 scr->rootWin, 0, 0, &mx, &my, &blaw);
1047 offs = menu->flags.standalone ? 0 : 1;
1049 W_MoveView(view, mx + offs, y);
1050 if (view->size.width != menu->view->size.width) {
1051 W_ResizeView(view, menu->view->size.width - 2*offs,
1052 menu->itemHeight);
1053 W_ResizeView(item->view, menu->view->size.width - 2*offs,
1054 menu->itemHeight);
1059 static void
1060 handleItemDrop(WEditMenu *menu, WEditMenuItem *item, int x, int y)
1062 WMScreen *scr = W_VIEW_SCREEN(menu->view);
1063 Window blaw;
1064 int mx, my;
1065 int index;
1067 XTranslateCoordinates(scr->display, W_VIEW_DRAWABLE(menu->view),
1068 scr->rootWin, 0, 0, &mx, &my, &blaw);
1070 index = y - my;
1071 if (menu->flags.isTitled) {
1072 index -= menu->titleHeight;
1074 index = (index + menu->itemHeight/2) / menu->itemHeight;
1075 if (index < 0)
1076 index = 0;
1078 if (menu->flags.isTitled) {
1079 index++;
1082 if (index > WMGetBagItemCount(menu->items)) {
1083 WMPutInBag(menu->items, item);
1084 } else {
1085 WMInsertInBag(menu->items, index, item);
1088 W_ReparentView(item->view, menu->view, 0, index*menu->itemHeight);
1090 item->parent = menu;
1091 if (item->submenu) {
1092 item->submenu->parent = menu;
1095 updateMenuContents(menu);
1099 static void
1100 dragMenu(WEditMenu *menu)
1102 WMScreen *scr = W_VIEW_SCREEN(menu->view);
1103 XEvent ev;
1104 Bool done = False;
1105 int dx, dy;
1106 unsigned blau;
1107 Window blaw;
1109 XGetGeometry(scr->display, W_VIEW_DRAWABLE(menu->view), &blaw, &dx, &dy,
1110 &blau, &blau, &blau, &blau);
1112 XTranslateCoordinates(scr->display, W_VIEW_DRAWABLE(menu->view),
1113 scr->rootWin, dx, dy, &dx, &dy, &blaw);
1115 dx = menu->dragX - dx;
1116 dy = menu->dragY - dy;
1118 XGrabPointer(scr->display, scr->rootWin, False,
1119 ButtonReleaseMask|ButtonMotionMask,
1120 GrabModeAsync, GrabModeAsync, None, scr->defaultCursor,
1121 CurrentTime);
1123 if (menu->parent)
1124 WTearOffEditMenu(menu->parent, menu);
1126 while (!done) {
1127 WMNextEvent(scr->display, &ev);
1129 switch (ev.type) {
1130 case ButtonRelease:
1131 done = True;
1132 break;
1134 case MotionNotify:
1135 WMMoveWidget(menu, ev.xmotion.x_root - dx,
1136 ev.xmotion.y_root - dy);
1137 break;
1139 default:
1140 WMHandleEvent(&ev);
1141 break;
1145 XUngrabPointer(scr->display, CurrentTime);
1149 static WEditMenu*
1150 duplicateMenu(WEditMenu *menu)
1152 WEditMenu *nmenu;
1153 WEditMenuItem *title;
1155 if (menu->flags.isTitled) {
1156 title = WMGetFromBag(menu->items, 0);
1159 nmenu = WCreateEditMenu(WMWidgetScreen(menu), title->label);
1161 memcpy(&nmenu->flags, &menu->flags, sizeof(menu->flags));
1162 nmenu->delegate = menu->delegate;
1164 WMRealizeWidget(nmenu);
1166 return nmenu;
1170 static void
1171 dragItem(WEditMenu *menu, WEditMenuItem *item)
1173 Display *dpy = W_VIEW_DISPLAY(menu->view);
1174 WMScreen *scr = W_VIEW_SCREEN(menu->view);
1175 int x, y;
1176 int dx, dy;
1177 Bool done = False;
1178 Window blaw;
1179 int blai;
1180 unsigned blau;
1181 Window win;
1182 WMView *dview;
1183 int orix, oriy;
1184 Bool enteredMenu = False;
1185 WMSize oldSize = item->view->size;
1186 WEditMenuItem *dritem = item;
1187 WEditMenu *dmenu = NULL;
1190 if (item->flags.isTitle) {
1191 WMRaiseWidget(menu);
1193 dragMenu(menu);
1195 return;
1199 selectItem(menu, NULL);
1201 win = scr->rootWin;
1203 XTranslateCoordinates(dpy, W_VIEW_DRAWABLE(item->view), win,
1204 0, 0, &orix, &oriy, &blaw);
1206 dview = W_CreateUnmanagedTopView(scr);
1207 W_SetViewBackgroundColor(dview, scr->black);
1208 W_ResizeView(dview, W_VIEW_WIDTH(item->view), W_VIEW_HEIGHT(item->view));
1209 W_MoveView(dview, orix, oriy);
1210 W_RealizeView(dview);
1212 if (menu->flags.isFactory) {
1213 dritem = WCreateEditMenuItem(menu, item->label, False);
1215 W_ReparentView(dritem->view, dview, 0, 0);
1216 WMResizeWidget(dritem, oldSize.width, oldSize.height);
1217 WMRealizeWidget(dritem);
1218 WMMapWidget(dritem);
1219 } else {
1220 W_ReparentView(item->view, dview, 0, 0);
1223 W_MapView(dview);
1225 dx = menu->dragX - orix;
1226 dy = menu->dragY - oriy;
1228 XGrabPointer(dpy, scr->rootWin, False,
1229 ButtonPressMask|ButtonReleaseMask|ButtonMotionMask,
1230 GrabModeAsync, GrabModeAsync, None, scr->defaultCursor,
1231 CurrentTime);
1233 while (!done) {
1234 XEvent ev;
1236 WMNextEvent(dpy, &ev);
1238 switch (ev.type) {
1239 case MotionNotify:
1240 XQueryPointer(dpy, win, &blaw, &blaw, &blai, &blai, &x, &y, &blau);
1242 dmenu = findMenuInWindow(dpy, win, x, y);
1244 if (dmenu) {
1245 handleDragOver(dmenu, dview, dritem, x - dx, y - dy);
1246 enteredMenu = True;
1247 } else {
1248 if (enteredMenu) {
1249 W_ResizeView(dview, oldSize.width, oldSize.height);
1250 W_ResizeView(dritem->view, oldSize.width, oldSize.height);
1251 enteredMenu = False;
1253 W_MoveView(dview, x - dx, y - dy);
1256 break;
1258 case ButtonRelease:
1259 done = True;
1260 break;
1262 default:
1263 WMHandleEvent(&ev);
1264 break;
1267 XUngrabPointer(dpy, CurrentTime);
1270 if (!enteredMenu) {
1271 Bool rem = True;
1273 if (!menu->flags.isFactory) {
1274 WMUnmapWidget(dritem);
1275 if (menu->delegate && menu->delegate->shouldRemoveItem) {
1276 rem = (*menu->delegate->shouldRemoveItem)(menu->delegate,
1277 menu, item);
1279 WMMapWidget(dritem);
1282 if (!rem || menu->flags.isFactory) {
1283 slideWindow(dpy, W_VIEW_DRAWABLE(dview), x-dx, y-dy, orix, oriy);
1285 if (!menu->flags.isFactory) {
1286 WRemoveEditMenuItem(menu, dritem);
1287 handleItemDrop(dmenu ? dmenu : menu, dritem, orix, oriy);
1289 } else {
1290 WRemoveEditMenuItem(menu, dritem);
1292 } else {
1293 WRemoveEditMenuItem(menu, dritem);
1295 if (menu->delegate && menu->delegate->itemCloned) {
1296 (*menu->delegate->itemCloned)(menu->delegate, menu,
1297 item, dritem);
1300 handleItemDrop(dmenu, dritem, x-dy, y-dy);
1302 if (item->submenu && menu->flags.isFactory) {
1303 WEditMenu *submenu;
1305 submenu = duplicateMenu(item->submenu);
1306 WSetEditMenuSubmenu(dmenu, dritem, submenu);
1310 /* can't destroy now because we're being called from
1311 * the event handler of the item. and destroying now,
1312 * would mean destroying the item too in some cases.
1314 WMAddIdleHandler((WMCallback*)W_DestroyView, dview);
1319 static void
1320 handleItemClick(XEvent *event, void *data)
1322 WEditMenuItem *item = (WEditMenuItem*)data;
1323 WEditMenu *menu = item->parent;
1325 switch (event->type) {
1326 case ButtonPress:
1327 selectItem(menu, item);
1329 if (WMIsDoubleClick(event)) {
1330 editItemLabel(item);
1333 menu->flags.isDragging = 1;
1334 menu->dragX = event->xbutton.x_root;
1335 menu->dragY = event->xbutton.y_root;
1336 break;
1338 case ButtonRelease:
1339 menu->flags.isDragging = 0;
1340 break;
1342 case MotionNotify:
1343 if (menu->flags.isDragging) {
1344 if (abs(event->xbutton.x_root - menu->dragX) > 5
1345 || abs(event->xbutton.y_root - menu->dragY) > 5) {
1346 menu->flags.isDragging = 0;
1347 dragItem(menu, item);
1350 break;
1355 static void
1356 destroyEditMenu(WEditMenu *mPtr)
1358 WEditMenuItem *item;
1359 WMBagIterator iter;
1361 WMRemoveNotificationObserver(mPtr);
1363 WM_ITERATE_BAG(mPtr->items, item, iter) {
1364 if (item->submenu) {
1365 WMDestroyWidget(item->submenu);
1369 WMFreeBag(mPtr->items);
1371 free(mPtr);