added close button to editable menu
[wmaker-crm.git] / WPrefs.app / editmenu.c
blobdef507e3eaa5006bde319ed527e040ce8e10918e
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 WMButton *closeB;
66 int titleHeight;
67 int itemHeight;
69 WEditMenuDelegate *delegate;
71 /* item dragging */
72 int draggedItem;
73 int dragX, dragY;
75 /* only for non-standalone menu */
76 WMSize maxSize;
77 WMSize minSize;
79 struct {
80 unsigned standalone:1;
81 unsigned isTitled:1;
83 unsigned acceptsDrop:1;
84 unsigned isFactory:1;
85 unsigned isSelectable:1;
86 unsigned isEditable:1;
88 unsigned isTornOff:1;
90 unsigned isDragging:1;
91 unsigned isEditing:1;
92 } flags;
93 } EditMenu;
97 /******************** WEditMenuItem ********************/
99 static void destroyEditMenuItem(WEditMenuItem *iPtr);
100 static void paintEditMenuItem(WEditMenuItem *iPtr);
101 static void handleItemEvents(XEvent *event, void *data);
103 static void handleItemClick(XEvent *event, void *data);
106 static W_Class EditMenuItemClass = 0;
109 W_Class
110 InitEditMenuItem(WMScreen *scr)
112 /* register our widget with WINGs and get our widget class ID */
113 if (!EditMenuItemClass) {
114 EditMenuItemClass = W_RegisterUserWidget();
117 return EditMenuItemClass;
121 WEditMenuItem*
122 WCreateEditMenuItem(WMWidget *parent, char *title, Bool isTitle)
124 WEditMenuItem *iPtr;
125 WMScreen *scr = WMWidgetScreen(parent);
127 if (!EditMenuItemClass)
128 InitEditMenuItem(scr);
131 iPtr = wmalloc(sizeof(WEditMenuItem));
133 memset(iPtr, 0, sizeof(WEditMenuItem));
135 iPtr->widgetClass = EditMenuItemClass;
137 iPtr->view = W_CreateView(W_VIEW(parent));
138 if (!iPtr->view) {
139 free(iPtr);
140 return NULL;
142 iPtr->view->self = iPtr;
144 iPtr->parent = parent;
146 iPtr->label = wstrdup(title);
148 iPtr->flags.isTitle = isTitle;
150 if (isTitle) {
151 WMSetWidgetBackgroundColor(iPtr, WMBlackColor(scr));
154 WMCreateEventHandler(iPtr->view, ExposureMask|StructureNotifyMask,
155 handleItemEvents, iPtr);
157 WMCreateEventHandler(iPtr->view, ButtonPressMask|ButtonReleaseMask
158 |ButtonMotionMask, handleItemClick, iPtr);
161 return iPtr;
165 char *WGetEditMenuItemTitle(WEditMenuItem *item)
167 return item->label;
171 void *WGetEditMenuItemData(WEditMenuItem *item)
173 return item->data;
177 void WSetEditMenuItemData(WEditMenuItem *item, void *data,
178 WMCallback *destroyer)
180 item->data = data;
181 item->destroyData = destroyer;
187 static void
188 paintEditMenuItem(WEditMenuItem *iPtr)
190 WMScreen *scr = WMWidgetScreen(iPtr);
191 WMColor *color;
192 Window win = W_VIEW(iPtr)->window;
193 int w = W_VIEW(iPtr)->size.width;
194 int h = W_VIEW(iPtr)->size.height;
195 WMFont *font = (iPtr->flags.isTitle ? scr->boldFont : scr->normalFont);
197 if (!iPtr->view->flags.realized)
198 return;
200 color = scr->black;
201 if (iPtr->flags.isTitle && !iPtr->flags.isHighlighted) {
202 color = scr->white;
206 XClearWindow(scr->display, win);
208 W_DrawRelief(scr, win, 0, 0, w+1, h, WRRaised);
210 WMDrawString(scr, win, WMColorGC(color), font, 5, 3, iPtr->label,
211 strlen(iPtr->label));
213 if (iPtr->submenu) {
214 /* draw the cascade indicator */
215 XDrawLine(scr->display,win,WMColorGC(scr->darkGray),
216 w-11, 6, w-6, h/2-1);
217 XDrawLine(scr->display,win,WMColorGC(scr->white),
218 w-11, h-8, w-6, h/2-1);
219 XDrawLine(scr->display,win,WMColorGC(scr->black),
220 w-12, 6, w-12, h-8);
225 static void
226 highlightItem(WEditMenuItem *iPtr, Bool high)
228 if (iPtr->flags.isTitle)
229 return;
231 iPtr->flags.isHighlighted = high;
233 if (high) {
234 WMSetWidgetBackgroundColor(iPtr, WMWhiteColor(WMWidgetScreen(iPtr)));
235 } else {
236 if (!iPtr->flags.isTitle) {
237 WMSetWidgetBackgroundColor(iPtr,
238 WMGrayColor(WMWidgetScreen(iPtr)));
239 } else {
240 WMSetWidgetBackgroundColor(iPtr,
241 WMBlackColor(WMWidgetScreen(iPtr)));
247 static int
248 getItemTextWidth(WEditMenuItem *iPtr)
250 WMScreen *scr = WMWidgetScreen(iPtr);
252 if (iPtr->flags.isTitle) {
253 return WMWidthOfString(scr->boldFont, iPtr->label,
254 strlen(iPtr->label));
255 } else {
256 return WMWidthOfString(scr->normalFont, iPtr->label,
257 strlen(iPtr->label));
263 static void
264 handleItemEvents(XEvent *event, void *data)
266 WEditMenuItem *iPtr = (WEditMenuItem*)data;
268 switch (event->type) {
269 case Expose:
270 if (event->xexpose.count!=0)
271 break;
272 paintEditMenuItem(iPtr);
273 break;
275 case DestroyNotify:
276 destroyEditMenuItem(iPtr);
277 break;
282 static void
283 destroyEditMenuItem(WEditMenuItem *iPtr)
285 if (iPtr->label)
286 free(iPtr->label);
287 if (iPtr->data && iPtr->destroyData)
288 (*iPtr->destroyData)(iPtr->data);
290 free(iPtr);
295 /******************** WEditMenu *******************/
297 static void destroyEditMenu(WEditMenu *mPtr);
299 static void updateMenuContents(WEditMenu *mPtr);
301 static void handleEvents(XEvent *event, void *data);
303 static void editItemLabel(WEditMenuItem *item);
304 static void stopEditItem(WEditMenu *menu, Bool apply);
307 static void deselectItem(WEditMenu *menu);
310 static W_Class EditMenuClass = 0;
313 W_Class
314 InitEditMenu(WMScreen *scr)
316 /* register our widget with WINGs and get our widget class ID */
317 if (!EditMenuClass) {
319 EditMenuClass = W_RegisterUserWidget();
322 return EditMenuClass;
327 typedef struct {
328 int flags;
329 int window_style;
330 int window_level;
331 int reserved;
332 Pixmap miniaturize_pixmap; /* pixmap for miniaturize button */
333 Pixmap close_pixmap; /* pixmap for close button */
334 Pixmap miniaturize_mask; /* miniaturize pixmap mask */
335 Pixmap close_mask; /* close pixmap mask */
336 int extra_flags;
337 } GNUstepWMAttributes;
340 #define GSWindowStyleAttr (1<<0)
341 #define GSWindowLevelAttr (1<<1)
344 static void
345 writeGNUstepWMAttr(WMScreen *scr, Window window, GNUstepWMAttributes *attr)
347 unsigned long data[9];
349 /* handle idiot compilers where array of CARD32 != struct of CARD32 */
350 data[0] = attr->flags;
351 data[1] = attr->window_style;
352 data[2] = attr->window_level;
353 data[3] = 0; /* reserved */
354 /* The X protocol says XIDs are 32bit */
355 data[4] = attr->miniaturize_pixmap;
356 data[5] = attr->close_pixmap;
357 data[6] = attr->miniaturize_mask;
358 data[7] = attr->close_mask;
359 data[8] = attr->extra_flags;
360 XChangeProperty(scr->display, window, scr->attribsAtom, scr->attribsAtom,
361 32, PropModeReplace, (unsigned char *)data, 9);
365 static void
366 realizeObserver(void *self, WMNotification *not)
368 WEditMenu *menu = (WEditMenu*)self;
369 GNUstepWMAttributes attribs;
371 memset(&attribs, 0, sizeof(GNUstepWMAttributes));
372 attribs.flags = GSWindowStyleAttr|GSWindowLevelAttr;
373 attribs.window_style = WMBorderlessWindowMask;
374 attribs.window_level = WMSubmenuWindowLevel;
376 writeGNUstepWMAttr(WMWidgetScreen(menu), menu->view->window, &attribs);
380 static void
381 itemSelectObserver(void *self, WMNotification *notif)
383 WEditMenu *menu = (WEditMenu*)self;
384 WEditMenu *rmenu;
386 rmenu = (WEditMenu*)WMGetNotificationObject(notif);
387 /* check whether rmenu is from the same hierarchy of menu? */
389 if (rmenu == menu) {
390 return;
393 if (menu->selectedItem && !menu->selectedItem->submenu) {
394 deselectItem(menu);
399 static WEditMenu*
400 makeEditMenu(WMScreen *scr, WMWidget *parent, char *title)
402 WEditMenu *mPtr;
403 WEditMenuItem *item;
405 if (!EditMenuClass)
406 InitEditMenu(scr);
409 mPtr = wmalloc(sizeof(WEditMenu));
410 memset(mPtr, 0, sizeof(WEditMenu));
412 mPtr->widgetClass = EditMenuClass;
414 if (parent) {
415 mPtr->view = W_CreateView(W_VIEW(parent));
416 mPtr->flags.standalone = 0;
417 } else {
418 mPtr->view = W_CreateTopView(scr);
419 mPtr->flags.standalone = 1;
421 if (!mPtr->view) {
422 free(mPtr);
423 return NULL;
425 mPtr->view->self = mPtr;
427 mPtr->flags.isSelectable = 1;
428 mPtr->flags.isEditable = 1;
430 W_SetViewBackgroundColor(mPtr->view, scr->darkGray);
432 WMAddNotificationObserver(realizeObserver, mPtr,
433 WMViewRealizedNotification, mPtr->view);
435 WMAddNotificationObserver(itemSelectObserver, mPtr,
436 "EditMenuItemSelected", NULL);
438 mPtr->items = WMCreateBag(4);
440 WMCreateEventHandler(mPtr->view, ExposureMask|StructureNotifyMask,
441 handleEvents, mPtr);
444 if (title != NULL) {
445 item = WCreateEditMenuItem(mPtr, title, True);
447 WMMapWidget(item);
448 WMPutInBag(mPtr->items, item);
450 mPtr->flags.isTitled = 1;
453 mPtr->itemHeight = WMFontHeight(scr->normalFont) + 6;
454 mPtr->titleHeight = WMFontHeight(scr->boldFont) + 8;
456 updateMenuContents(mPtr);
458 return mPtr;
462 WEditMenu*
463 WCreateEditMenu(WMScreen *scr, char *title)
465 return makeEditMenu(scr, NULL, title);
469 WEditMenu*
470 WCreateEditMenuPad(WMWidget *parent)
472 return makeEditMenu(WMWidgetScreen(parent), parent, NULL);
476 void
477 WSetEditMenuDelegate(WEditMenu *mPtr, WEditMenuDelegate *delegate)
479 mPtr->delegate = delegate;
483 WEditMenuItem*
484 WInsertMenuItemWithTitle(WEditMenu *mPtr, int index, char *title)
486 WEditMenuItem *item;
488 item = WCreateEditMenuItem(mPtr, title, False);
490 WMMapWidget(item);
492 if (index >= WMGetBagItemCount(mPtr->items)) {
493 WMPutInBag(mPtr->items, item);
494 } else {
495 if (index < 0)
496 index = 0;
497 if (mPtr->flags.isTitled)
498 index++;
499 WMInsertInBag(mPtr->items, index, item);
502 updateMenuContents(mPtr);
504 return item;
509 WEditMenuItem*
510 WAddMenuItemWithTitle(WEditMenu *mPtr, char *title)
512 return WInsertMenuItemWithTitle(mPtr, WMGetBagItemCount(mPtr->items),
513 title);
518 void
519 WSetEditMenuTitle(WEditMenu *mPtr, char *title)
521 WEditMenuItem *item;
523 item = WMGetFromBag(mPtr->items, 0);
525 free(item->label);
526 item->label = wstrdup(title);
527 updateMenuContents(mPtr);
531 void
532 WSetEditMenuAcceptsDrop(WEditMenu *mPtr, Bool flag)
534 mPtr->flags.acceptsDrop = flag;
538 void
539 WSetEditMenuSubmenu(WEditMenu *mPtr, WEditMenuItem *item, WEditMenu *submenu)
541 item->submenu = submenu;
542 submenu->parent = mPtr;
544 paintEditMenuItem(item);
548 WEditMenu*
549 WGetEditMenuSubmenu(WEditMenu *mPtr, WEditMenuItem *item)
551 return item->submenu;
555 static int simpleMatch(void *a, void *b)
557 if (a == b)
558 return 1;
559 else
560 return 0;
564 void
565 WRemoveEditMenuItem(WEditMenu *mPtr, WEditMenuItem *item)
567 int index;
569 index = WMFindInBag(mPtr->items, simpleMatch, item);
571 if (index == WBNotFound) {
572 return;
575 WMDeleteFromBag(mPtr->items, index);
577 updateMenuContents(mPtr);
581 void
582 WSetEditMenuSelectable(WEditMenu *mPtr, Bool flag)
584 mPtr->flags.isSelectable = flag;
588 void
589 WSetEditMenuEditable(WEditMenu *mPtr, Bool flag)
591 mPtr->flags.isEditable = flag;
595 void
596 WSetEditMenuIsFactory(WEditMenu *mPtr, Bool flag)
598 mPtr->flags.isFactory = flag;
602 void
603 WSetEditMenuMinSize(WEditMenu *mPtr, WMSize size)
605 mPtr->minSize.width = size.width;
606 mPtr->minSize.height = size.height;
610 void
611 WSetEditMenuMaxSize(WEditMenu *mPtr, WMSize size)
613 mPtr->maxSize.width = size.width;
614 mPtr->maxSize.height = size.height;
618 WMPoint
619 WGetEditMenuLocationForSubmenu(WEditMenu *mPtr, WEditMenu *submenu)
621 WMBagIterator iter;
622 WEditMenuItem *item;
623 int y;
625 if (mPtr->flags.isTitled)
626 y = -mPtr->titleHeight;
627 else
628 y = 0;
629 WM_ITERATE_BAG(mPtr->items, item, iter) {
630 if (item->submenu == submenu) {
631 WMPoint pt = WMGetViewScreenPosition(mPtr->view);
633 return wmkpoint(pt.x + mPtr->view->size.width, pt.y + y);
635 y += W_VIEW_HEIGHT(item->view);
638 puts("invalid submenu passed to WGetEditMenuLocationForSubmenu()");
640 return wmkpoint(0,0);
645 static void
646 closeMenuAction(WMWidget *w, void *data)
648 WEditMenu *menu = (WEditMenu*)data;
650 WMAddIdleHandler(WMDestroyWidget, menu->closeB);
651 menu->closeB = NULL;
653 WMUnmapWidget(menu);
657 void
658 WTearOffEditMenu(WEditMenu *menu, WEditMenu *submenu)
660 WEditMenuItem *item;
662 submenu->flags.isTornOff = 1;
664 item = (WEditMenuItem*)WMGetFromBag(submenu->items, 0);
666 submenu->closeB = WMCreateCommandButton(item);
667 WMResizeWidget(submenu->closeB, 15, 15);
668 WMMoveWidget(submenu->closeB, W_VIEW(submenu)->size.width - 20, 3);
669 WMRealizeWidget(submenu->closeB);
670 WMSetButtonText(submenu->closeB, "X");
671 WMSetButtonAction(submenu->closeB, closeMenuAction, submenu);
672 WMMapWidget(submenu->closeB);
674 if (menu->selectedItem && menu->selectedItem->submenu == submenu)
675 deselectItem(menu);
680 Bool
681 WEditMenuIsTornOff(WEditMenu *mPtr)
683 return mPtr->flags.isTornOff;
687 static void
688 updateMenuContents(WEditMenu *mPtr)
690 int newW, newH;
691 int w;
692 int i;
693 int iheight = mPtr->itemHeight;
694 int offs = 1;
695 WMBagIterator iter;
696 WEditMenuItem *item;
698 newW = 0;
699 newH = offs;
701 i = 0;
702 WM_ITERATE_BAG(mPtr->items, item, iter) {
703 w = getItemTextWidth(item);
705 newW = WMAX(w, newW);
707 WMMoveWidget(item, offs, newH);
708 if (i == 0 && mPtr->flags.isTitled) {
709 newH += mPtr->titleHeight;
710 } else {
711 newH += iheight;
713 i = 1;
716 newW += iheight + 10;
717 newH--;
719 if (mPtr->minSize.width)
720 newW = WMAX(newW, mPtr->minSize.width);
721 if (mPtr->maxSize.width)
722 newW = WMIN(newW, mPtr->maxSize.width);
724 if (mPtr->minSize.height)
725 newH = WMAX(newH, mPtr->minSize.height);
726 if (mPtr->maxSize.height)
727 newH = WMIN(newH, mPtr->maxSize.height);
729 W_ResizeView(mPtr->view, newW, newH+1);
731 newW -= 2*offs;
733 i = 0;
734 WM_ITERATE_BAG(mPtr->items, item, iter) {
735 if (i == 0 && mPtr->flags.isTitled) {
736 WMResizeWidget(item, newW, mPtr->titleHeight);
737 } else {
738 WMResizeWidget(item, newW, iheight);
740 i = 1;
745 static void
746 unmapMenu(WEditMenu *menu)
748 WMUnmapWidget(menu);
750 if (menu->selectedItem) {
751 deselectItem(menu);
756 static void
757 deselectItem(WEditMenu *menu)
759 WEditMenu *submenu;
760 WEditMenuItem *item = menu->selectedItem;
762 highlightItem(item, False);
764 if (menu->delegate && menu->delegate->itemDeselected) {
765 (*menu->delegate->itemDeselected)(menu->delegate, menu, item);
767 submenu = item->submenu;
769 if (submenu && !WEditMenuIsTornOff(submenu)) {
770 unmapMenu(submenu);
773 menu->selectedItem = NULL;
777 static void
778 selectItem(WEditMenu *menu, WEditMenuItem *item)
780 if (!menu->flags.isSelectable || menu->selectedItem == item) {
781 return;
783 if (menu->selectedItem) {
784 deselectItem(menu);
787 if (menu->flags.isEditing) {
788 stopEditItem(menu, False);
791 if (item && !item->flags.isTitle) {
792 highlightItem(item, True);
794 if (item->submenu) {
795 WMPoint pt;
796 XSizeHints *hints;
798 hints = XAllocSizeHints();
800 pt = WGetEditMenuLocationForSubmenu(menu, item->submenu);
802 hints->flags = USPosition;
803 hints->x = pt.x;
804 hints->y = pt.y;
806 WMMoveWidget(item->submenu, pt.x, pt.y);
807 XSetWMNormalHints(W_VIEW_DISPLAY(item->submenu->view),
808 W_VIEW_DRAWABLE(item->submenu->view),
809 hints);
810 XFree(hints);
811 WMMapWidget(item->submenu);
813 item->submenu->flags.isTornOff = 0;
816 WMPostNotificationName("EditMenuItemSelected", menu, NULL);
818 if (menu->delegate && menu->delegate->itemSelected) {
819 (*menu->delegate->itemSelected)(menu->delegate, menu, item);
823 menu->selectedItem = item;
827 static void
828 paintMenu(WEditMenu *mPtr)
830 W_View *view = mPtr->view;
832 W_DrawRelief(W_VIEW_SCREEN(view), W_VIEW_DRAWABLE(view), 0, 0,
833 W_VIEW_WIDTH(view), W_VIEW_HEIGHT(view), WRSimple);
837 static void
838 handleEvents(XEvent *event, void *data)
840 WEditMenu *mPtr = (WEditMenu*)data;
842 switch (event->type) {
843 case DestroyNotify:
844 destroyEditMenu(mPtr);
845 break;
847 case Expose:
848 if (event->xexpose.count == 0)
849 paintMenu(mPtr);
850 break;
857 /* -------------------------- Menu Label Editing ------------------------ */
860 static void
861 stopEditItem(WEditMenu *menu, Bool apply)
863 char *text;
865 if (apply) {
866 text = WMGetTextFieldText(menu->tfield);
868 free(menu->selectedItem->label);
869 menu->selectedItem->label = wstrdup(text);
871 updateMenuContents(menu);
873 WMUnmapWidget(menu->tfield);
874 menu->flags.isEditing = 0;
878 static void
879 textEndedEditing(struct WMTextFieldDelegate *self, WMNotification *notif)
881 WEditMenu *menu = (WEditMenu*)self->data;
882 int reason;
883 int i;
884 WEditMenuItem *item;
886 if (!menu->flags.isEditing)
887 return;
889 reason = (int)WMGetNotificationClientData(notif);
891 switch (reason) {
892 case WMEscapeTextMovement:
893 stopEditItem(menu, False);
894 break;
896 case WMReturnTextMovement:
897 stopEditItem(menu, True);
898 break;
900 case WMTabTextMovement:
901 stopEditItem(menu, True);
903 i = WMFindInBag(menu->items, simpleMatch, menu->selectedItem);
904 item = WMGetFromBag(menu->items, i+1);
905 if (item != NULL) {
906 selectItem(menu, item);
907 editItemLabel(item);
909 break;
911 case WMBacktabTextMovement:
912 stopEditItem(menu, True);
914 i = WMFindInBag(menu->items, simpleMatch, menu->selectedItem);
915 item = WMGetFromBag(menu->items, i-1);
916 if (item != NULL) {
917 selectItem(menu, item);
918 editItemLabel(item);
920 break;
926 static WMTextFieldDelegate textFieldDelegate = {
927 NULL,
928 NULL,
929 NULL,
930 textEndedEditing,
931 NULL,
932 NULL
936 static void
937 editItemLabel(WEditMenuItem *item)
939 WEditMenu *mPtr = item->parent;
940 WMTextField *tf;
941 int i;
943 if (!mPtr->flags.isEditable) {
944 return;
947 if (!mPtr->tfield) {
948 mPtr->tfield = WMCreateTextField(mPtr);
949 WMSetTextFieldBeveled(mPtr->tfield, False);
950 WMRealizeWidget(mPtr->tfield);
952 textFieldDelegate.data = mPtr;
954 WMSetTextFieldDelegate(mPtr->tfield, &textFieldDelegate);
956 tf = mPtr->tfield;
958 i = WMFindInBag(mPtr->items, simpleMatch, item);
960 WMSetTextFieldText(tf, item->label);
961 WMSelectTextFieldRange(tf, wmkrange(0, strlen(item->label)));
962 WMResizeWidget(tf, mPtr->view->size.width, mPtr->itemHeight);
963 WMMoveWidget(tf, 0, item->view->pos.y);
964 WMMapWidget(tf);
965 WMSetFocusToWidget(tf);
967 mPtr->flags.isEditing = 1;
972 /* -------------------------------------------------- */
975 static void
976 slideWindow(Display *dpy, Window win, int srcX, int srcY, int dstX, int dstY)
978 double x, y, dx, dy;
979 int i;
980 int iterations;
982 iterations = WMIN(25, WMAX(abs(dstX-srcX), abs(dstY-srcY)));
984 x = srcX;
985 y = srcY;
987 dx = (double)(dstX-srcX)/iterations;
988 dy = (double)(dstY-srcY)/iterations;
990 for (i = 0; i <= iterations; i++) {
991 XMoveWindow(dpy, win, x, y);
992 XFlush(dpy);
994 wusleep(800);
996 x += dx;
997 y += dy;
1002 static int errorHandler(Display *d, XErrorEvent *ev)
1004 /* just ignore */
1005 return 0;
1009 static WEditMenu*
1010 findMenuInWindow(Display *dpy, Window toplevel, int x, int y)
1012 Window foo, bar;
1013 Window *children;
1014 unsigned nchildren;
1015 int i;
1016 WEditMenu *menu;
1017 WMView *view;
1018 int (*oldHandler)(Display *, XErrorEvent *);
1020 view = W_GetViewForXWindow(dpy, toplevel);
1021 if (view && view->self && WMWidgetClass(view->self) == EditMenuClass) {
1022 menu = (WEditMenu*)view->self;
1023 if (menu->flags.acceptsDrop) {
1024 return menu;
1028 if (!XQueryTree(dpy, toplevel, &foo, &bar,
1029 &children, &nchildren) || children == NULL) {
1030 return NULL;
1033 oldHandler = XSetErrorHandler(errorHandler);
1035 /* first window that contains the point is the one */
1036 for (i = nchildren-1; i >= 0; i--) {
1037 XWindowAttributes attr;
1039 if (XGetWindowAttributes(dpy, children[i], &attr)
1040 && attr.map_state == IsViewable
1041 && x >= attr.x && y >= attr.y
1042 && x < attr.x + attr.width && y < attr.y + attr.height) {
1043 Window tmp;
1045 tmp = children[i];
1047 menu = findMenuInWindow(dpy, tmp, x - attr.x, y - attr.y);
1048 if (menu) {
1049 XFree(children);
1050 return menu;
1055 XSetErrorHandler(oldHandler);
1057 XFree(children);
1058 return NULL;
1062 static void
1063 handleDragOver(WEditMenu *menu, WMView *view, WEditMenuItem *item,
1064 int x, int y)
1066 WMScreen *scr = W_VIEW_SCREEN(menu->view);
1067 Window blaw;
1068 int mx, my;
1069 int offs;
1071 XTranslateCoordinates(scr->display, W_VIEW_DRAWABLE(menu->view),
1072 scr->rootWin, 0, 0, &mx, &my, &blaw);
1074 offs = menu->flags.standalone ? 0 : 1;
1076 W_MoveView(view, mx + offs, y);
1077 if (view->size.width != menu->view->size.width) {
1078 W_ResizeView(view, menu->view->size.width - 2*offs,
1079 menu->itemHeight);
1080 W_ResizeView(item->view, menu->view->size.width - 2*offs,
1081 menu->itemHeight);
1086 static void
1087 handleItemDrop(WEditMenu *menu, WEditMenuItem *item, int x, int y)
1089 WMScreen *scr = W_VIEW_SCREEN(menu->view);
1090 Window blaw;
1091 int mx, my;
1092 int index;
1094 XTranslateCoordinates(scr->display, W_VIEW_DRAWABLE(menu->view),
1095 scr->rootWin, 0, 0, &mx, &my, &blaw);
1097 index = y - my;
1098 if (menu->flags.isTitled) {
1099 index -= menu->titleHeight;
1101 index = (index + menu->itemHeight/2) / menu->itemHeight;
1102 if (index < 0)
1103 index = 0;
1105 if (menu->flags.isTitled) {
1106 index++;
1109 if (index > WMGetBagItemCount(menu->items)) {
1110 WMPutInBag(menu->items, item);
1111 } else {
1112 WMInsertInBag(menu->items, index, item);
1115 W_ReparentView(item->view, menu->view, 0, index*menu->itemHeight);
1117 item->parent = menu;
1118 if (item->submenu) {
1119 item->submenu->parent = menu;
1122 updateMenuContents(menu);
1126 static void
1127 dragMenu(WEditMenu *menu)
1129 WMScreen *scr = W_VIEW_SCREEN(menu->view);
1130 XEvent ev;
1131 Bool done = False;
1132 int dx, dy;
1133 unsigned blau;
1134 Window blaw;
1136 XGetGeometry(scr->display, W_VIEW_DRAWABLE(menu->view), &blaw, &dx, &dy,
1137 &blau, &blau, &blau, &blau);
1139 XTranslateCoordinates(scr->display, W_VIEW_DRAWABLE(menu->view),
1140 scr->rootWin, dx, dy, &dx, &dy, &blaw);
1142 dx = menu->dragX - dx;
1143 dy = menu->dragY - dy;
1145 XGrabPointer(scr->display, scr->rootWin, False,
1146 ButtonReleaseMask|ButtonMotionMask,
1147 GrabModeAsync, GrabModeAsync, None, scr->defaultCursor,
1148 CurrentTime);
1150 if (menu->parent)
1151 WTearOffEditMenu(menu->parent, menu);
1153 while (!done) {
1154 WMNextEvent(scr->display, &ev);
1156 switch (ev.type) {
1157 case ButtonRelease:
1158 done = True;
1159 break;
1161 case MotionNotify:
1162 WMMoveWidget(menu, ev.xmotion.x_root - dx,
1163 ev.xmotion.y_root - dy);
1164 break;
1166 default:
1167 WMHandleEvent(&ev);
1168 break;
1172 XUngrabPointer(scr->display, CurrentTime);
1176 static WEditMenu*
1177 duplicateMenu(WEditMenu *menu)
1179 WEditMenu *nmenu;
1180 WEditMenuItem *title;
1182 if (menu->flags.isTitled) {
1183 title = WMGetFromBag(menu->items, 0);
1186 nmenu = WCreateEditMenu(WMWidgetScreen(menu), title->label);
1188 memcpy(&nmenu->flags, &menu->flags, sizeof(menu->flags));
1189 nmenu->delegate = menu->delegate;
1191 WMRealizeWidget(nmenu);
1193 return nmenu;
1197 static void
1198 dragItem(WEditMenu *menu, WEditMenuItem *item)
1200 Display *dpy = W_VIEW_DISPLAY(menu->view);
1201 WMScreen *scr = W_VIEW_SCREEN(menu->view);
1202 int x, y;
1203 int dx, dy;
1204 Bool done = False;
1205 Window blaw;
1206 int blai;
1207 unsigned blau;
1208 Window win;
1209 WMView *dview;
1210 int orix, oriy;
1211 Bool enteredMenu = False;
1212 WMSize oldSize = item->view->size;
1213 WEditMenuItem *dritem = item;
1214 WEditMenu *dmenu = NULL;
1217 if (item->flags.isTitle) {
1218 WMRaiseWidget(menu);
1220 dragMenu(menu);
1222 return;
1226 selectItem(menu, NULL);
1228 win = scr->rootWin;
1230 XTranslateCoordinates(dpy, W_VIEW_DRAWABLE(item->view), win,
1231 0, 0, &orix, &oriy, &blaw);
1233 dview = W_CreateUnmanagedTopView(scr);
1234 W_SetViewBackgroundColor(dview, scr->black);
1235 W_ResizeView(dview, W_VIEW_WIDTH(item->view), W_VIEW_HEIGHT(item->view));
1236 W_MoveView(dview, orix, oriy);
1237 W_RealizeView(dview);
1239 if (menu->flags.isFactory) {
1240 dritem = WCreateEditMenuItem(menu, item->label, False);
1242 W_ReparentView(dritem->view, dview, 0, 0);
1243 WMResizeWidget(dritem, oldSize.width, oldSize.height);
1244 WMRealizeWidget(dritem);
1245 WMMapWidget(dritem);
1246 } else {
1247 W_ReparentView(item->view, dview, 0, 0);
1250 W_MapView(dview);
1252 dx = menu->dragX - orix;
1253 dy = menu->dragY - oriy;
1255 XGrabPointer(dpy, scr->rootWin, False,
1256 ButtonPressMask|ButtonReleaseMask|ButtonMotionMask,
1257 GrabModeAsync, GrabModeAsync, None, scr->defaultCursor,
1258 CurrentTime);
1260 while (!done) {
1261 XEvent ev;
1263 WMNextEvent(dpy, &ev);
1265 switch (ev.type) {
1266 case MotionNotify:
1267 XQueryPointer(dpy, win, &blaw, &blaw, &blai, &blai, &x, &y, &blau);
1269 dmenu = findMenuInWindow(dpy, win, x, y);
1271 if (dmenu) {
1272 handleDragOver(dmenu, dview, dritem, x - dx, y - dy);
1273 enteredMenu = True;
1274 } else {
1275 if (enteredMenu) {
1276 W_ResizeView(dview, oldSize.width, oldSize.height);
1277 W_ResizeView(dritem->view, oldSize.width, oldSize.height);
1278 enteredMenu = False;
1280 W_MoveView(dview, x - dx, y - dy);
1283 break;
1285 case ButtonRelease:
1286 done = True;
1287 break;
1289 default:
1290 WMHandleEvent(&ev);
1291 break;
1294 XUngrabPointer(dpy, CurrentTime);
1297 if (!enteredMenu) {
1298 Bool rem = True;
1300 if (!menu->flags.isFactory) {
1301 WMUnmapWidget(dritem);
1302 if (menu->delegate && menu->delegate->shouldRemoveItem) {
1303 rem = (*menu->delegate->shouldRemoveItem)(menu->delegate,
1304 menu, item);
1306 WMMapWidget(dritem);
1309 if (!rem || menu->flags.isFactory) {
1310 slideWindow(dpy, W_VIEW_DRAWABLE(dview), x-dx, y-dy, orix, oriy);
1312 if (!menu->flags.isFactory) {
1313 WRemoveEditMenuItem(menu, dritem);
1314 handleItemDrop(dmenu ? dmenu : menu, dritem, orix, oriy);
1316 } else {
1317 WRemoveEditMenuItem(menu, dritem);
1319 } else {
1320 WRemoveEditMenuItem(menu, dritem);
1322 if (menu->delegate && menu->delegate->itemCloned) {
1323 (*menu->delegate->itemCloned)(menu->delegate, menu,
1324 item, dritem);
1327 handleItemDrop(dmenu, dritem, x-dy, y-dy);
1329 if (item->submenu && menu->flags.isFactory) {
1330 WEditMenu *submenu;
1332 submenu = duplicateMenu(item->submenu);
1333 WSetEditMenuSubmenu(dmenu, dritem, submenu);
1337 /* can't destroy now because we're being called from
1338 * the event handler of the item. and destroying now,
1339 * would mean destroying the item too in some cases.
1341 WMAddIdleHandler((WMCallback*)W_DestroyView, dview);
1346 static void
1347 handleItemClick(XEvent *event, void *data)
1349 WEditMenuItem *item = (WEditMenuItem*)data;
1350 WEditMenu *menu = item->parent;
1352 switch (event->type) {
1353 case ButtonPress:
1354 selectItem(menu, item);
1356 if (WMIsDoubleClick(event)) {
1357 editItemLabel(item);
1360 menu->flags.isDragging = 1;
1361 menu->dragX = event->xbutton.x_root;
1362 menu->dragY = event->xbutton.y_root;
1363 break;
1365 case ButtonRelease:
1366 menu->flags.isDragging = 0;
1367 break;
1369 case MotionNotify:
1370 if (menu->flags.isDragging) {
1371 if (abs(event->xbutton.x_root - menu->dragX) > 5
1372 || abs(event->xbutton.y_root - menu->dragY) > 5) {
1373 menu->flags.isDragging = 0;
1374 dragItem(menu, item);
1377 break;
1382 static void
1383 destroyEditMenu(WEditMenu *mPtr)
1385 WEditMenuItem *item;
1386 WMBagIterator iter;
1388 WMRemoveNotificationObserver(mPtr);
1390 WM_ITERATE_BAG(mPtr->items, item, iter) {
1391 if (item->submenu) {
1392 WMDestroyWidget(item->submenu);
1396 WMFreeBag(mPtr->items);
1398 free(mPtr);