fixed some bugs in menu editor
[wmaker-crm.git] / WPrefs.app / editmenu.c
blob142120652b7d95d27337b5c4cf8cd4f3ddd72b72
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;
39 WMPixmap *pixmap; /* pixmap to show at left */
41 void *data;
42 WMCallback *destroyData;
44 struct W_EditMenu *submenu; /* if it's a cascade, NULL otherwise */
46 struct {
47 unsigned isTitle:1;
48 unsigned isHighlighted:1;
49 } flags;
50 } EditMenuItem;
53 typedef struct W_EditMenu {
54 W_Class widgetClass;
55 WMView *view;
57 struct W_EditMenu *parent;
59 WMBag *items; /* EditMenuItem */
61 EditMenuItem *selectedItem;
63 WMTextField *tfield;
65 WMButton *closeB;
67 int titleHeight;
68 int itemHeight;
70 WEditMenuDelegate *delegate;
72 WMTextFieldDelegate *tdelegate;
74 /* item dragging */
75 int draggedItem;
76 int dragX, dragY;
78 /* only for non-standalone menu */
79 WMSize maxSize;
80 WMSize minSize;
82 struct {
83 unsigned standalone:1;
84 unsigned isTitled:1;
86 unsigned acceptsDrop:1;
87 unsigned isFactory:1;
88 unsigned isSelectable:1;
89 unsigned isEditable:1;
91 unsigned isTornOff:1;
93 unsigned isDragging:1;
94 unsigned isEditing:1;
95 } flags;
96 } EditMenu;
100 /******************** WEditMenuItem ********************/
102 static void destroyEditMenuItem(WEditMenuItem *iPtr);
103 static void paintEditMenuItem(WEditMenuItem *iPtr);
104 static void handleItemEvents(XEvent *event, void *data);
106 static void handleItemClick(XEvent *event, void *data);
109 static W_Class EditMenuItemClass = 0;
112 W_Class
113 InitEditMenuItem(WMScreen *scr)
115 /* register our widget with WINGs and get our widget class ID */
116 if (!EditMenuItemClass) {
117 EditMenuItemClass = W_RegisterUserWidget();
120 return EditMenuItemClass;
124 WEditMenuItem*
125 WCreateEditMenuItem(WMWidget *parent, char *title, Bool isTitle)
127 WEditMenuItem *iPtr;
128 WMScreen *scr = WMWidgetScreen(parent);
130 if (!EditMenuItemClass)
131 InitEditMenuItem(scr);
134 iPtr = wmalloc(sizeof(WEditMenuItem));
136 memset(iPtr, 0, sizeof(WEditMenuItem));
138 iPtr->widgetClass = EditMenuItemClass;
140 iPtr->view = W_CreateView(W_VIEW(parent));
141 if (!iPtr->view) {
142 free(iPtr);
143 return NULL;
145 iPtr->view->self = iPtr;
147 iPtr->parent = parent;
149 iPtr->label = wstrdup(title);
151 iPtr->flags.isTitle = isTitle;
153 if (isTitle) {
154 WMSetWidgetBackgroundColor(iPtr, WMBlackColor(scr));
157 WMCreateEventHandler(iPtr->view, ExposureMask|StructureNotifyMask,
158 handleItemEvents, iPtr);
160 WMCreateEventHandler(iPtr->view, ButtonPressMask|ButtonReleaseMask
161 |ButtonMotionMask, handleItemClick, iPtr);
164 return iPtr;
167 char *WGetEditMenuItemTitle(WEditMenuItem *item)
169 return item->label;
172 void *WGetEditMenuItemData(WEditMenuItem *item)
174 return item->data;
178 void WSetEditMenuItemData(WEditMenuItem *item, void *data,
179 WMCallback *destroyer)
181 item->data = data;
182 item->destroyData = destroyer;
186 void WSetEditMenuItemImage(WEditMenuItem *item, WMPixmap *pixmap)
188 if (item->pixmap)
189 WMReleasePixmap(item->pixmap);
190 item->pixmap = WMRetainPixmap(pixmap);
194 static void
195 paintEditMenuItem(WEditMenuItem *iPtr)
197 WMScreen *scr = WMWidgetScreen(iPtr);
198 WMColor *color;
199 Window win = W_VIEW(iPtr)->window;
200 int w = W_VIEW(iPtr)->size.width;
201 int h = W_VIEW(iPtr)->size.height;
202 WMFont *font = (iPtr->flags.isTitle ? scr->boldFont : scr->normalFont);
204 if (!iPtr->view->flags.realized)
205 return;
207 color = scr->black;
208 if (iPtr->flags.isTitle && !iPtr->flags.isHighlighted) {
209 color = scr->white;
213 XClearWindow(scr->display, win);
215 W_DrawRelief(scr, win, 0, 0, w+1, h, WRRaised);
217 WMDrawString(scr, win, WMColorGC(color), font, 5, 3, iPtr->label,
218 strlen(iPtr->label));
220 if (iPtr->pixmap) {
221 WMSize size = WMGetPixmapSize(iPtr->pixmap);
223 WMDrawPixmap(iPtr->pixmap, win, w - size.width - 5,
224 (h - size.height)/2);
225 } else if (iPtr->submenu) {
226 /* draw the cascade indicator */
227 XDrawLine(scr->display,win,WMColorGC(scr->darkGray),
228 w-11, 6, w-6, h/2-1);
229 XDrawLine(scr->display,win,WMColorGC(scr->white),
230 w-11, h-8, w-6, h/2-1);
231 XDrawLine(scr->display,win,WMColorGC(scr->black),
232 w-12, 6, w-12, h-8);
237 static void
238 highlightItem(WEditMenuItem *iPtr, Bool high)
240 if (iPtr->flags.isTitle)
241 return;
243 iPtr->flags.isHighlighted = high;
245 if (high) {
246 WMSetWidgetBackgroundColor(iPtr, WMWhiteColor(WMWidgetScreen(iPtr)));
247 } else {
248 if (!iPtr->flags.isTitle) {
249 WMSetWidgetBackgroundColor(iPtr,
250 WMGrayColor(WMWidgetScreen(iPtr)));
251 } else {
252 WMSetWidgetBackgroundColor(iPtr,
253 WMBlackColor(WMWidgetScreen(iPtr)));
259 static int
260 getItemTextWidth(WEditMenuItem *iPtr)
262 WMScreen *scr = WMWidgetScreen(iPtr);
264 if (iPtr->flags.isTitle) {
265 return WMWidthOfString(scr->boldFont, iPtr->label,
266 strlen(iPtr->label));
267 } else {
268 return WMWidthOfString(scr->normalFont, iPtr->label,
269 strlen(iPtr->label));
275 static void
276 handleItemEvents(XEvent *event, void *data)
278 WEditMenuItem *iPtr = (WEditMenuItem*)data;
280 switch (event->type) {
281 case Expose:
282 if (event->xexpose.count!=0)
283 break;
284 paintEditMenuItem(iPtr);
285 break;
287 case DestroyNotify:
288 destroyEditMenuItem(iPtr);
289 break;
294 static void
295 destroyEditMenuItem(WEditMenuItem *iPtr)
297 if (iPtr->label)
298 free(iPtr->label);
299 if (iPtr->data && iPtr->destroyData)
300 (*iPtr->destroyData)(iPtr->data);
301 if (iPtr->submenu)
302 WMDestroyWidget(iPtr->submenu);
304 free(iPtr);
309 /******************** WEditMenu *******************/
311 static void destroyEditMenu(WEditMenu *mPtr);
313 static void updateMenuContents(WEditMenu *mPtr);
315 static void handleEvents(XEvent *event, void *data);
317 static void editItemLabel(WEditMenuItem *item);
318 static void stopEditItem(WEditMenu *menu, Bool apply);
321 static void unmapMenu(WEditMenu *menu);
322 static void deselectItem(WEditMenu *menu);
325 static W_Class EditMenuClass = 0;
328 W_Class
329 InitEditMenu(WMScreen *scr)
331 /* register our widget with WINGs and get our widget class ID */
332 if (!EditMenuClass) {
334 EditMenuClass = W_RegisterUserWidget();
337 return EditMenuClass;
342 typedef struct {
343 int flags;
344 int window_style;
345 int window_level;
346 int reserved;
347 Pixmap miniaturize_pixmap; /* pixmap for miniaturize button */
348 Pixmap close_pixmap; /* pixmap for close button */
349 Pixmap miniaturize_mask; /* miniaturize pixmap mask */
350 Pixmap close_mask; /* close pixmap mask */
351 int extra_flags;
352 } GNUstepWMAttributes;
355 #define GSWindowStyleAttr (1<<0)
356 #define GSWindowLevelAttr (1<<1)
359 static void
360 writeGNUstepWMAttr(WMScreen *scr, Window window, GNUstepWMAttributes *attr)
362 unsigned long data[9];
364 /* handle idiot compilers where array of CARD32 != struct of CARD32 */
365 data[0] = attr->flags;
366 data[1] = attr->window_style;
367 data[2] = attr->window_level;
368 data[3] = 0; /* reserved */
369 /* The X protocol says XIDs are 32bit */
370 data[4] = attr->miniaturize_pixmap;
371 data[5] = attr->close_pixmap;
372 data[6] = attr->miniaturize_mask;
373 data[7] = attr->close_mask;
374 data[8] = attr->extra_flags;
375 XChangeProperty(scr->display, window, scr->attribsAtom, scr->attribsAtom,
376 32, PropModeReplace, (unsigned char *)data, 9);
380 static void
381 realizeObserver(void *self, WMNotification *not)
383 WEditMenu *menu = (WEditMenu*)self;
384 GNUstepWMAttributes attribs;
386 memset(&attribs, 0, sizeof(GNUstepWMAttributes));
387 attribs.flags = GSWindowStyleAttr|GSWindowLevelAttr;
388 attribs.window_style = WMBorderlessWindowMask;
389 attribs.window_level = WMSubmenuWindowLevel;
391 writeGNUstepWMAttr(WMWidgetScreen(menu), menu->view->window, &attribs);
395 static void
396 itemSelectObserver(void *self, WMNotification *notif)
398 WEditMenu *menu = (WEditMenu*)self;
399 WEditMenu *rmenu;
401 rmenu = (WEditMenu*)WMGetNotificationObject(notif);
402 /* check whether rmenu is from the same hierarchy of menu? */
404 if (rmenu == menu) {
405 return;
408 if (menu->selectedItem) {
409 if (!menu->selectedItem->submenu)
410 deselectItem(menu);
411 if (menu->flags.isEditing)
412 stopEditItem(menu, False);
417 static WEditMenu*
418 makeEditMenu(WMScreen *scr, WMWidget *parent, char *title)
420 WEditMenu *mPtr;
421 WEditMenuItem *item;
423 if (!EditMenuClass)
424 InitEditMenu(scr);
427 mPtr = wmalloc(sizeof(WEditMenu));
428 memset(mPtr, 0, sizeof(WEditMenu));
430 mPtr->widgetClass = EditMenuClass;
432 if (parent) {
433 mPtr->view = W_CreateView(W_VIEW(parent));
434 mPtr->flags.standalone = 0;
435 } else {
436 mPtr->view = W_CreateTopView(scr);
437 mPtr->flags.standalone = 1;
439 if (!mPtr->view) {
440 free(mPtr);
441 return NULL;
443 mPtr->view->self = mPtr;
445 mPtr->flags.isSelectable = 1;
446 mPtr->flags.isEditable = 1;
448 W_SetViewBackgroundColor(mPtr->view, scr->darkGray);
450 WMAddNotificationObserver(realizeObserver, mPtr,
451 WMViewRealizedNotification, mPtr->view);
453 WMAddNotificationObserver(itemSelectObserver, mPtr,
454 "EditMenuItemSelected", NULL);
456 mPtr->items = WMCreateBag(4);
458 WMCreateEventHandler(mPtr->view, ExposureMask|StructureNotifyMask,
459 handleEvents, mPtr);
462 if (title != NULL) {
463 item = WCreateEditMenuItem(mPtr, title, True);
465 WMMapWidget(item);
466 WMPutInBag(mPtr->items, item);
468 mPtr->flags.isTitled = 1;
471 mPtr->itemHeight = WMFontHeight(scr->normalFont) + 6;
472 mPtr->titleHeight = WMFontHeight(scr->boldFont) + 8;
474 updateMenuContents(mPtr);
476 return mPtr;
480 WEditMenu*
481 WCreateEditMenu(WMScreen *scr, char *title)
483 return makeEditMenu(scr, NULL, title);
487 WEditMenu*
488 WCreateEditMenuPad(WMWidget *parent)
490 return makeEditMenu(WMWidgetScreen(parent), parent, NULL);
494 void
495 WSetEditMenuDelegate(WEditMenu *mPtr, WEditMenuDelegate *delegate)
497 mPtr->delegate = delegate;
501 WEditMenuItem*
502 WInsertMenuItemWithTitle(WEditMenu *mPtr, int index, char *title)
504 WEditMenuItem *item;
506 item = WCreateEditMenuItem(mPtr, title, False);
508 WMMapWidget(item);
510 if (index >= WMGetBagItemCount(mPtr->items)) {
511 WMPutInBag(mPtr->items, item);
512 } else {
513 if (index < 0)
514 index = 0;
515 if (mPtr->flags.isTitled)
516 index++;
517 WMInsertInBag(mPtr->items, index, item);
520 updateMenuContents(mPtr);
522 return item;
526 WEditMenuItem*
527 WGetEditMenuItem(WEditMenu *mPtr, int index)
529 if (index >= WMGetBagItemCount(mPtr->items))
530 return NULL;
531 else
532 return WMGetFromBag(mPtr->items, index + (mPtr->flags.isTitled ? 1 : 0));
536 WEditMenuItem*
537 WAddMenuItemWithTitle(WEditMenu *mPtr, char *title)
539 return WInsertMenuItemWithTitle(mPtr, WMGetBagItemCount(mPtr->items),
540 title);
545 void
546 WSetEditMenuTitle(WEditMenu *mPtr, char *title)
548 WEditMenuItem *item;
550 item = WMGetFromBag(mPtr->items, 0);
552 free(item->label);
553 item->label = wstrdup(title);
555 updateMenuContents(mPtr);
557 WMRedisplayWidget(item);
562 char*
563 WGetEditMenuTitle(WEditMenu *mPtr)
565 WEditMenuItem *item;
567 item = WMGetFromBag(mPtr->items, 0);
569 return item->label;
573 void
574 WSetEditMenuAcceptsDrop(WEditMenu *mPtr, Bool flag)
576 mPtr->flags.acceptsDrop = flag;
580 void
581 WSetEditMenuSubmenu(WEditMenu *mPtr, WEditMenuItem *item, WEditMenu *submenu)
583 item->submenu = submenu;
584 submenu->parent = mPtr;
586 paintEditMenuItem(item);
590 WEditMenu*
591 WGetEditMenuSubmenu(WEditMenu *mPtr, WEditMenuItem *item)
593 return item->submenu;
597 static int simpleMatch(void *a, void *b)
599 if (a == b)
600 return 1;
601 else
602 return 0;
606 void
607 WRemoveEditMenuItem(WEditMenu *mPtr, WEditMenuItem *item)
609 int index;
611 index = WMFindInBag(mPtr->items, simpleMatch, item);
613 if (index == WBNotFound) {
614 return;
617 WMDeleteFromBag(mPtr->items, index);
619 updateMenuContents(mPtr);
623 void
624 WSetEditMenuSelectable(WEditMenu *mPtr, Bool flag)
626 mPtr->flags.isSelectable = flag;
630 void
631 WSetEditMenuEditable(WEditMenu *mPtr, Bool flag)
633 mPtr->flags.isEditable = flag;
637 void
638 WSetEditMenuIsFactory(WEditMenu *mPtr, Bool flag)
640 mPtr->flags.isFactory = flag;
644 void
645 WSetEditMenuMinSize(WEditMenu *mPtr, WMSize size)
647 mPtr->minSize.width = size.width;
648 mPtr->minSize.height = size.height;
652 void
653 WSetEditMenuMaxSize(WEditMenu *mPtr, WMSize size)
655 mPtr->maxSize.width = size.width;
656 mPtr->maxSize.height = size.height;
660 WMPoint
661 WGetEditMenuLocationForSubmenu(WEditMenu *mPtr, WEditMenu *submenu)
663 WMBagIterator iter;
664 WEditMenuItem *item;
665 int y;
667 if (mPtr->flags.isTitled)
668 y = -mPtr->titleHeight;
669 else
670 y = 0;
671 WM_ITERATE_BAG(mPtr->items, item, iter) {
672 if (item->submenu == submenu) {
673 WMPoint pt = WMGetViewScreenPosition(mPtr->view);
675 return wmkpoint(pt.x + mPtr->view->size.width, pt.y + y);
677 y += W_VIEW_HEIGHT(item->view);
680 puts("invalid submenu passed to WGetEditMenuLocationForSubmenu()");
682 return wmkpoint(0,0);
687 static void
688 closeMenuAction(WMWidget *w, void *data)
690 WEditMenu *menu = (WEditMenu*)data;
692 WMAddIdleHandler(WMDestroyWidget, menu->closeB);
693 menu->closeB = NULL;
695 unmapMenu(menu);
699 void
700 WTearOffEditMenu(WEditMenu *menu, WEditMenu *submenu)
702 WEditMenuItem *item;
704 submenu->flags.isTornOff = 1;
706 item = (WEditMenuItem*)WMGetFromBag(submenu->items, 0);
708 submenu->closeB = WMCreateCommandButton(item);
709 WMResizeWidget(submenu->closeB, 15, 15);
710 WMMoveWidget(submenu->closeB, W_VIEW(submenu)->size.width - 20, 3);
711 WMRealizeWidget(submenu->closeB);
712 WMSetButtonText(submenu->closeB, "X");
713 WMSetButtonAction(submenu->closeB, closeMenuAction, submenu);
714 WMMapWidget(submenu->closeB);
716 if (menu->selectedItem && menu->selectedItem->submenu == submenu)
717 deselectItem(menu);
722 Bool
723 WEditMenuIsTornOff(WEditMenu *mPtr)
725 return mPtr->flags.isTornOff;
729 static void
730 updateMenuContents(WEditMenu *mPtr)
732 int newW, newH;
733 int w;
734 int i;
735 int iheight = mPtr->itemHeight;
736 int offs = 1;
737 WMBagIterator iter;
738 WEditMenuItem *item;
740 newW = 0;
741 newH = offs;
743 i = 0;
744 WM_ITERATE_BAG(mPtr->items, item, iter) {
745 w = getItemTextWidth(item);
747 newW = WMAX(w, newW);
749 WMMoveWidget(item, offs, newH);
750 if (i == 0 && mPtr->flags.isTitled) {
751 newH += mPtr->titleHeight;
752 } else {
753 newH += iheight;
755 i = 1;
758 newW += iheight + 10;
759 newH--;
761 if (mPtr->minSize.width)
762 newW = WMAX(newW, mPtr->minSize.width);
763 if (mPtr->maxSize.width)
764 newW = WMIN(newW, mPtr->maxSize.width);
766 if (mPtr->minSize.height)
767 newH = WMAX(newH, mPtr->minSize.height);
768 if (mPtr->maxSize.height)
769 newH = WMIN(newH, mPtr->maxSize.height);
771 if (W_VIEW(mPtr)->size.width == newW && mPtr->view->size.height == newH+1)
772 return;
774 W_ResizeView(mPtr->view, newW, newH+1);
776 if (mPtr->closeB)
777 WMMoveWidget(mPtr->closeB, newW - 20, 3);
779 newW -= 2*offs;
781 i = 0;
782 WM_ITERATE_BAG(mPtr->items, item, iter) {
783 if (i == 0 && mPtr->flags.isTitled) {
784 WMResizeWidget(item, newW, mPtr->titleHeight);
785 } else {
786 WMResizeWidget(item, newW, iheight);
788 i = 1;
793 static void
794 unmapMenu(WEditMenu *menu)
796 WMUnmapWidget(menu);
798 if (menu->selectedItem) {
799 deselectItem(menu);
804 static void
805 deselectItem(WEditMenu *menu)
807 WEditMenu *submenu;
808 WEditMenuItem *item = menu->selectedItem;
810 highlightItem(item, False);
812 if (menu->delegate && menu->delegate->itemDeselected) {
813 (*menu->delegate->itemDeselected)(menu->delegate, menu, item);
815 submenu = item->submenu;
817 if (submenu && !WEditMenuIsTornOff(submenu)) {
818 unmapMenu(submenu);
821 menu->selectedItem = NULL;
825 static void
826 selectItem(WEditMenu *menu, WEditMenuItem *item)
828 if (!menu->flags.isSelectable || menu->selectedItem == item) {
829 return;
831 if (menu->selectedItem) {
832 deselectItem(menu);
835 if (menu->flags.isEditing) {
836 stopEditItem(menu, False);
839 if (item && !item->flags.isTitle) {
840 highlightItem(item, True);
842 if (item->submenu && !W_VIEW_MAPPED(item->submenu->view)) {
843 WMPoint pt;
844 XSizeHints *hints;
846 hints = XAllocSizeHints();
848 pt = WGetEditMenuLocationForSubmenu(menu, item->submenu);
850 hints->flags = USPosition;
851 hints->x = pt.x;
852 hints->y = pt.y;
854 WMMoveWidget(item->submenu, pt.x, pt.y);
855 XSetWMNormalHints(W_VIEW_DISPLAY(item->submenu->view),
856 W_VIEW_DRAWABLE(item->submenu->view),
857 hints);
858 XFree(hints);
859 WMMapWidget(item->submenu);
861 item->submenu->flags.isTornOff = 0;
864 WMPostNotificationName("EditMenuItemSelected", menu, NULL);
866 if (menu->delegate && menu->delegate->itemSelected) {
867 (*menu->delegate->itemSelected)(menu->delegate, menu, item);
871 menu->selectedItem = item;
875 static void
876 paintMenu(WEditMenu *mPtr)
878 W_View *view = mPtr->view;
880 W_DrawRelief(W_VIEW_SCREEN(view), W_VIEW_DRAWABLE(view), 0, 0,
881 W_VIEW_WIDTH(view), W_VIEW_HEIGHT(view), WRSimple);
885 static void
886 handleEvents(XEvent *event, void *data)
888 WEditMenu *mPtr = (WEditMenu*)data;
890 switch (event->type) {
891 case DestroyNotify:
892 destroyEditMenu(mPtr);
893 break;
895 case Expose:
896 if (event->xexpose.count == 0)
897 paintMenu(mPtr);
898 break;
905 /* -------------------------- Menu Label Editing ------------------------ */
908 static void
909 stopEditItem(WEditMenu *menu, Bool apply)
911 char *text;
913 if (apply) {
914 text = WMGetTextFieldText(menu->tfield);
916 free(menu->selectedItem->label);
917 menu->selectedItem->label = wstrdup(text);
919 updateMenuContents(menu);
921 if (menu->delegate && menu->delegate->itemEdited) {
922 (*menu->delegate->itemEdited)(menu->delegate, menu,
923 menu->selectedItem);
927 WMUnmapWidget(menu->tfield);
928 menu->flags.isEditing = 0;
932 static void
933 textEndedEditing(struct WMTextFieldDelegate *self, WMNotification *notif)
935 WEditMenu *menu = (WEditMenu*)self->data;
936 int reason;
937 int i;
938 WEditMenuItem *item;
940 if (!menu->flags.isEditing)
941 return;
943 reason = (int)WMGetNotificationClientData(notif);
945 switch (reason) {
946 case WMEscapeTextMovement:
947 stopEditItem(menu, False);
948 break;
950 case WMReturnTextMovement:
951 stopEditItem(menu, True);
952 break;
954 case WMTabTextMovement:
955 stopEditItem(menu, True);
957 i = WMFindInBag(menu->items, simpleMatch, menu->selectedItem);
958 item = WMGetFromBag(menu->items, i+1);
959 if (item != NULL) {
960 selectItem(menu, item);
961 editItemLabel(item);
963 break;
965 case WMBacktabTextMovement:
966 stopEditItem(menu, True);
968 i = WMFindInBag(menu->items, simpleMatch, menu->selectedItem);
969 item = WMGetFromBag(menu->items, i-1);
970 if (item != NULL) {
971 selectItem(menu, item);
972 editItemLabel(item);
974 break;
980 static WMTextFieldDelegate textFieldDelegate = {
981 NULL,
982 NULL,
983 NULL,
984 textEndedEditing,
985 NULL,
986 NULL
990 static void
991 editItemLabel(WEditMenuItem *item)
993 WEditMenu *mPtr = item->parent;
994 WMTextField *tf;
995 int i;
997 if (!mPtr->flags.isEditable) {
998 return;
1001 if (!mPtr->tfield) {
1002 mPtr->tfield = WMCreateTextField(mPtr);
1003 WMSetTextFieldBeveled(mPtr->tfield, False);
1004 WMRealizeWidget(mPtr->tfield);
1006 mPtr->tdelegate = wmalloc(sizeof(WMTextFieldDelegate));
1007 memcpy(mPtr->tdelegate, &textFieldDelegate,
1008 sizeof(WMTextFieldDelegate));
1010 mPtr->tdelegate->data = mPtr;
1012 WMSetTextFieldDelegate(mPtr->tfield, mPtr->tdelegate);
1014 tf = mPtr->tfield;
1016 i = WMFindInBag(mPtr->items, simpleMatch, item);
1018 WMSetTextFieldText(tf, item->label);
1019 WMSelectTextFieldRange(tf, wmkrange(0, strlen(item->label)));
1020 WMResizeWidget(tf, mPtr->view->size.width, mPtr->itemHeight);
1021 WMMoveWidget(tf, 0, item->view->pos.y);
1022 WMMapWidget(tf);
1023 WMSetFocusToWidget(tf);
1025 mPtr->flags.isEditing = 1;
1030 /* -------------------------------------------------- */
1033 static void
1034 slideWindow(Display *dpy, Window win, int srcX, int srcY, int dstX, int dstY)
1036 double x, y, dx, dy;
1037 int i;
1038 int iterations;
1040 iterations = WMIN(25, WMAX(abs(dstX-srcX), abs(dstY-srcY)));
1042 x = srcX;
1043 y = srcY;
1045 dx = (double)(dstX-srcX)/iterations;
1046 dy = (double)(dstY-srcY)/iterations;
1048 for (i = 0; i <= iterations; i++) {
1049 XMoveWindow(dpy, win, x, y);
1050 XFlush(dpy);
1052 wusleep(800);
1054 x += dx;
1055 y += dy;
1060 static int errorHandler(Display *d, XErrorEvent *ev)
1062 /* just ignore */
1063 return 0;
1067 static WEditMenu*
1068 findMenuInWindow(Display *dpy, Window toplevel, int x, int y)
1070 Window foo, bar;
1071 Window *children;
1072 unsigned nchildren;
1073 int i;
1074 WEditMenu *menu;
1075 WMView *view;
1076 int (*oldHandler)(Display *, XErrorEvent *);
1078 view = W_GetViewForXWindow(dpy, toplevel);
1079 if (view && view->self && WMWidgetClass(view->self) == EditMenuClass) {
1080 menu = (WEditMenu*)view->self;
1081 if (menu->flags.acceptsDrop) {
1082 return menu;
1086 if (!XQueryTree(dpy, toplevel, &foo, &bar,
1087 &children, &nchildren) || children == NULL) {
1088 return NULL;
1091 oldHandler = XSetErrorHandler(errorHandler);
1093 /* first window that contains the point is the one */
1094 for (i = nchildren-1; i >= 0; i--) {
1095 XWindowAttributes attr;
1097 if (XGetWindowAttributes(dpy, children[i], &attr)
1098 && attr.map_state == IsViewable
1099 && x >= attr.x && y >= attr.y
1100 && x < attr.x + attr.width && y < attr.y + attr.height) {
1101 Window tmp;
1103 tmp = children[i];
1105 menu = findMenuInWindow(dpy, tmp, x - attr.x, y - attr.y);
1106 if (menu) {
1107 XFree(children);
1108 return menu;
1113 XSetErrorHandler(oldHandler);
1115 XFree(children);
1116 return NULL;
1120 static void
1121 handleDragOver(WEditMenu *menu, WMView *view, WEditMenuItem *item,
1122 int x, int y)
1124 WMScreen *scr = W_VIEW_SCREEN(menu->view);
1125 Window blaw;
1126 int mx, my;
1127 int offs;
1129 XTranslateCoordinates(scr->display, W_VIEW_DRAWABLE(menu->view),
1130 scr->rootWin, 0, 0, &mx, &my, &blaw);
1132 offs = menu->flags.standalone ? 0 : 1;
1134 W_MoveView(view, mx + offs, y);
1135 if (view->size.width != menu->view->size.width) {
1136 W_ResizeView(view, menu->view->size.width - 2*offs,
1137 menu->itemHeight);
1138 W_ResizeView(item->view, menu->view->size.width - 2*offs,
1139 menu->itemHeight);
1144 static void
1145 handleItemDrop(WEditMenu *menu, WEditMenuItem *item, int x, int y)
1147 WMScreen *scr = W_VIEW_SCREEN(menu->view);
1148 Window blaw;
1149 int mx, my;
1150 int index;
1152 XTranslateCoordinates(scr->display, W_VIEW_DRAWABLE(menu->view),
1153 scr->rootWin, 0, 0, &mx, &my, &blaw);
1155 index = y - my;
1156 if (menu->flags.isTitled) {
1157 index -= menu->titleHeight;
1159 index = (index + menu->itemHeight/2) / menu->itemHeight;
1160 if (index < 0)
1161 index = 0;
1163 if (menu->flags.isTitled) {
1164 index++;
1167 if (index > WMGetBagItemCount(menu->items)) {
1168 WMPutInBag(menu->items, item);
1169 } else {
1170 WMInsertInBag(menu->items, index, item);
1173 W_ReparentView(item->view, menu->view, 0, index*menu->itemHeight);
1175 item->parent = menu;
1176 if (item->submenu) {
1177 item->submenu->parent = menu;
1180 updateMenuContents(menu);
1184 static void
1185 dragMenu(WEditMenu *menu)
1187 WMScreen *scr = W_VIEW_SCREEN(menu->view);
1188 XEvent ev;
1189 Bool done = False;
1190 int dx, dy;
1191 unsigned blau;
1192 Window blaw;
1194 XGetGeometry(scr->display, W_VIEW_DRAWABLE(menu->view), &blaw, &dx, &dy,
1195 &blau, &blau, &blau, &blau);
1197 XTranslateCoordinates(scr->display, W_VIEW_DRAWABLE(menu->view),
1198 scr->rootWin, dx, dy, &dx, &dy, &blaw);
1200 dx = menu->dragX - dx;
1201 dy = menu->dragY - dy;
1203 XGrabPointer(scr->display, scr->rootWin, False,
1204 ButtonReleaseMask|ButtonMotionMask,
1205 GrabModeAsync, GrabModeAsync, None, scr->defaultCursor,
1206 CurrentTime);
1208 if (menu->parent)
1209 WTearOffEditMenu(menu->parent, menu);
1211 while (!done) {
1212 WMNextEvent(scr->display, &ev);
1214 switch (ev.type) {
1215 case ButtonRelease:
1216 done = True;
1217 break;
1219 case MotionNotify:
1220 while (XCheckMaskEvent(scr->display, ButtonMotionMask, &ev)) ;
1222 WMMoveWidget(menu, ev.xmotion.x_root - dx,
1223 ev.xmotion.y_root - dy);
1224 break;
1226 default:
1227 WMHandleEvent(&ev);
1228 break;
1232 XUngrabPointer(scr->display, CurrentTime);
1237 static WEditMenuItem*
1238 duplicateItem(WEditMenuItem *item)
1240 WEditMenuItem *nitem;
1242 nitem = WCreateEditMenuItem(item->parent, item->label, False);
1243 if (item->pixmap)
1244 nitem->pixmap = WMRetainPixmap(item->pixmap);
1246 return nitem;
1252 static WEditMenu*
1253 duplicateMenu(WEditMenu *menu)
1255 WEditMenu *nmenu;
1256 WEditMenuItem *item;
1257 WMBagIterator iter;
1258 Bool first = menu->flags.isTitled;
1260 nmenu = WCreateEditMenu(WMWidgetScreen(menu), WGetEditMenuTitle(menu));
1262 memcpy(&nmenu->flags, &menu->flags, sizeof(menu->flags));
1263 nmenu->delegate = menu->delegate;
1265 WM_ITERATE_BAG(menu->items, item, iter) {
1266 WEditMenuItem *nitem;
1268 if (first) {
1269 first = False;
1270 continue;
1273 nitem = WAddMenuItemWithTitle(nmenu, item->label);
1274 if (item->pixmap)
1275 WSetEditMenuItemImage(nitem, item->pixmap);
1277 if (menu->delegate && menu->delegate->itemCloned) {
1278 (*menu->delegate->itemCloned)(menu->delegate, menu,
1279 item, nitem);
1283 WMRealizeWidget(nmenu);
1285 return nmenu;
1290 static void
1291 dragItem(WEditMenu *menu, WEditMenuItem *item)
1293 Display *dpy = W_VIEW_DISPLAY(menu->view);
1294 WMScreen *scr = W_VIEW_SCREEN(menu->view);
1295 int x, y;
1296 int dx, dy;
1297 Bool done = False;
1298 Window blaw;
1299 int blai;
1300 unsigned blau;
1301 Window win;
1302 WMView *dview;
1303 int orix, oriy;
1304 Bool enteredMenu = False;
1305 WMSize oldSize = item->view->size;
1306 WEditMenuItem *dritem = item;
1307 WEditMenu *dmenu = NULL;
1310 if (item->flags.isTitle) {
1311 WMRaiseWidget(menu);
1313 dragMenu(menu);
1315 return;
1319 selectItem(menu, NULL);
1321 win = scr->rootWin;
1323 XTranslateCoordinates(dpy, W_VIEW_DRAWABLE(item->view), win,
1324 0, 0, &orix, &oriy, &blaw);
1326 dview = W_CreateUnmanagedTopView(scr);
1327 W_SetViewBackgroundColor(dview, scr->black);
1328 W_ResizeView(dview, W_VIEW_WIDTH(item->view), W_VIEW_HEIGHT(item->view));
1329 W_MoveView(dview, orix, oriy);
1330 W_RealizeView(dview);
1332 if (menu->flags.isFactory) {
1333 dritem = duplicateItem(item);
1335 W_ReparentView(dritem->view, dview, 0, 0);
1336 WMResizeWidget(dritem, oldSize.width, oldSize.height);
1337 WMRealizeWidget(dritem);
1338 WMMapWidget(dritem);
1339 } else {
1340 W_ReparentView(item->view, dview, 0, 0);
1343 W_MapView(dview);
1345 dx = menu->dragX - orix;
1346 dy = menu->dragY - oriy;
1348 XGrabPointer(dpy, scr->rootWin, False,
1349 ButtonPressMask|ButtonReleaseMask|ButtonMotionMask,
1350 GrabModeAsync, GrabModeAsync, None, scr->defaultCursor,
1351 CurrentTime);
1353 while (!done) {
1354 XEvent ev;
1356 WMNextEvent(dpy, &ev);
1358 switch (ev.type) {
1359 case MotionNotify:
1360 while (XCheckMaskEvent(dpy, ButtonMotionMask, &ev)) ;
1362 XQueryPointer(dpy, win, &blaw, &blaw, &blai, &blai, &x, &y, &blau);
1364 dmenu = findMenuInWindow(dpy, win, x, y);
1366 if (dmenu) {
1367 handleDragOver(dmenu, dview, dritem, x - dx, y - dy);
1368 enteredMenu = True;
1369 } else {
1370 if (enteredMenu) {
1371 W_ResizeView(dview, oldSize.width, oldSize.height);
1372 W_ResizeView(dritem->view, oldSize.width, oldSize.height);
1373 enteredMenu = False;
1375 W_MoveView(dview, x - dx, y - dy);
1378 break;
1380 case ButtonRelease:
1381 done = True;
1382 break;
1384 default:
1385 WMHandleEvent(&ev);
1386 break;
1389 XUngrabPointer(dpy, CurrentTime);
1392 if (!enteredMenu) {
1393 Bool rem = True;
1395 if (!menu->flags.isFactory) {
1396 W_UnmapView(dview);
1397 if (menu->delegate && menu->delegate->shouldRemoveItem) {
1398 rem = (*menu->delegate->shouldRemoveItem)(menu->delegate,
1399 menu, item);
1401 W_MapView(dview);
1404 if (!rem || menu->flags.isFactory) {
1405 slideWindow(dpy, W_VIEW_DRAWABLE(dview), x-dx, y-dy, orix, oriy);
1407 if (!menu->flags.isFactory) {
1408 WRemoveEditMenuItem(menu, dritem);
1409 handleItemDrop(dmenu ? dmenu : menu, dritem, orix, oriy);
1411 } else {
1412 WRemoveEditMenuItem(menu, dritem);
1414 } else {
1415 WRemoveEditMenuItem(menu, dritem);
1417 if (menu->delegate && menu->delegate->itemCloned
1418 && menu->flags.isFactory) {
1419 (*menu->delegate->itemCloned)(menu->delegate, menu,
1420 item, dritem);
1423 handleItemDrop(dmenu, dritem, x-dy, y-dy);
1425 if (item->submenu && menu->flags.isFactory) {
1426 WEditMenu *submenu;
1428 submenu = duplicateMenu(item->submenu);
1429 WSetEditMenuSubmenu(dmenu, dritem, submenu);
1433 /* can't destroy now because we're being called from
1434 * the event handler of the item. and destroying now,
1435 * would mean destroying the item too in some cases.
1437 WMAddIdleHandler((WMCallback*)W_DestroyView, dview);
1442 static void
1443 handleItemClick(XEvent *event, void *data)
1445 WEditMenuItem *item = (WEditMenuItem*)data;
1446 WEditMenu *menu = item->parent;
1448 switch (event->type) {
1449 case ButtonPress:
1450 selectItem(menu, item);
1452 if (WMIsDoubleClick(event)) {
1453 editItemLabel(item);
1456 menu->flags.isDragging = 1;
1457 menu->dragX = event->xbutton.x_root;
1458 menu->dragY = event->xbutton.y_root;
1459 break;
1461 case ButtonRelease:
1462 menu->flags.isDragging = 0;
1463 break;
1465 case MotionNotify:
1466 if (menu->flags.isDragging) {
1467 if (abs(event->xbutton.x_root - menu->dragX) > 5
1468 || abs(event->xbutton.y_root - menu->dragY) > 5) {
1469 menu->flags.isDragging = 0;
1470 dragItem(menu, item);
1473 break;
1478 static void
1479 destroyEditMenu(WEditMenu *mPtr)
1481 WEditMenuItem *item;
1482 WMBagIterator iter;
1484 WMRemoveNotificationObserver(mPtr);
1486 WM_ITERATE_BAG(mPtr->items, item, iter) {
1487 if (item->submenu) {
1488 WMDestroyWidget(item->submenu);
1492 WMFreeBag(mPtr->items);
1494 free(mPtr->tdelegate);
1496 free(mPtr);