more updates to editable menu
[wmaker-crm.git] / WPrefs.app / editmenu.c
blob381cc2af7754be34e1581038737c048569f1bde7
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 /* item dragging */
73 int draggedItem;
74 int dragX, dragY;
76 /* only for non-standalone menu */
77 WMSize maxSize;
78 WMSize minSize;
80 struct {
81 unsigned standalone:1;
82 unsigned isTitled:1;
84 unsigned acceptsDrop:1;
85 unsigned isFactory:1;
86 unsigned isSelectable:1;
87 unsigned isEditable:1;
89 unsigned isTornOff:1;
91 unsigned isDragging:1;
92 unsigned isEditing:1;
93 } flags;
94 } EditMenu;
98 /******************** WEditMenuItem ********************/
100 static void destroyEditMenuItem(WEditMenuItem *iPtr);
101 static void paintEditMenuItem(WEditMenuItem *iPtr);
102 static void handleItemEvents(XEvent *event, void *data);
104 static void handleItemClick(XEvent *event, void *data);
107 static W_Class EditMenuItemClass = 0;
110 W_Class
111 InitEditMenuItem(WMScreen *scr)
113 /* register our widget with WINGs and get our widget class ID */
114 if (!EditMenuItemClass) {
115 EditMenuItemClass = W_RegisterUserWidget();
118 return EditMenuItemClass;
122 WEditMenuItem*
123 WCreateEditMenuItem(WMWidget *parent, char *title, Bool isTitle)
125 WEditMenuItem *iPtr;
126 WMScreen *scr = WMWidgetScreen(parent);
128 if (!EditMenuItemClass)
129 InitEditMenuItem(scr);
132 iPtr = wmalloc(sizeof(WEditMenuItem));
134 memset(iPtr, 0, sizeof(WEditMenuItem));
136 iPtr->widgetClass = EditMenuItemClass;
138 iPtr->view = W_CreateView(W_VIEW(parent));
139 if (!iPtr->view) {
140 free(iPtr);
141 return NULL;
143 iPtr->view->self = iPtr;
145 iPtr->parent = parent;
147 iPtr->label = wstrdup(title);
149 iPtr->flags.isTitle = isTitle;
151 if (isTitle) {
152 WMSetWidgetBackgroundColor(iPtr, WMBlackColor(scr));
155 WMCreateEventHandler(iPtr->view, ExposureMask|StructureNotifyMask,
156 handleItemEvents, iPtr);
158 WMCreateEventHandler(iPtr->view, ButtonPressMask|ButtonReleaseMask
159 |ButtonMotionMask, handleItemClick, iPtr);
162 return iPtr;
166 char *WGetEditMenuItemTitle(WEditMenuItem *item)
168 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;
185 void WSetEditMenuItemImage(WEditMenuItem *item, WMPixmap *pixmap)
187 if (item->pixmap)
188 WMReleasePixmap(item->pixmap);
189 item->pixmap = WMRetainPixmap(pixmap);
193 static void
194 paintEditMenuItem(WEditMenuItem *iPtr)
196 WMScreen *scr = WMWidgetScreen(iPtr);
197 WMColor *color;
198 Window win = W_VIEW(iPtr)->window;
199 int w = W_VIEW(iPtr)->size.width;
200 int h = W_VIEW(iPtr)->size.height;
201 WMFont *font = (iPtr->flags.isTitle ? scr->boldFont : scr->normalFont);
203 if (!iPtr->view->flags.realized)
204 return;
206 color = scr->black;
207 if (iPtr->flags.isTitle && !iPtr->flags.isHighlighted) {
208 color = scr->white;
212 XClearWindow(scr->display, win);
214 W_DrawRelief(scr, win, 0, 0, w+1, h, WRRaised);
216 WMDrawString(scr, win, WMColorGC(color), font, 5, 3, iPtr->label,
217 strlen(iPtr->label));
219 if (iPtr->pixmap) {
220 WMSize size = WMGetPixmapSize(iPtr->pixmap);
222 WMDrawPixmap(iPtr->pixmap, win, w - size.width - 5,
223 (h - size.height)/2);
224 } else if (iPtr->submenu) {
225 /* draw the cascade indicator */
226 XDrawLine(scr->display,win,WMColorGC(scr->darkGray),
227 w-11, 6, w-6, h/2-1);
228 XDrawLine(scr->display,win,WMColorGC(scr->white),
229 w-11, h-8, w-6, h/2-1);
230 XDrawLine(scr->display,win,WMColorGC(scr->black),
231 w-12, 6, w-12, h-8);
236 static void
237 highlightItem(WEditMenuItem *iPtr, Bool high)
239 if (iPtr->flags.isTitle)
240 return;
242 iPtr->flags.isHighlighted = high;
244 if (high) {
245 WMSetWidgetBackgroundColor(iPtr, WMWhiteColor(WMWidgetScreen(iPtr)));
246 } else {
247 if (!iPtr->flags.isTitle) {
248 WMSetWidgetBackgroundColor(iPtr,
249 WMGrayColor(WMWidgetScreen(iPtr)));
250 } else {
251 WMSetWidgetBackgroundColor(iPtr,
252 WMBlackColor(WMWidgetScreen(iPtr)));
258 static int
259 getItemTextWidth(WEditMenuItem *iPtr)
261 WMScreen *scr = WMWidgetScreen(iPtr);
263 if (iPtr->flags.isTitle) {
264 return WMWidthOfString(scr->boldFont, iPtr->label,
265 strlen(iPtr->label));
266 } else {
267 return WMWidthOfString(scr->normalFont, iPtr->label,
268 strlen(iPtr->label));
274 static void
275 handleItemEvents(XEvent *event, void *data)
277 WEditMenuItem *iPtr = (WEditMenuItem*)data;
279 switch (event->type) {
280 case Expose:
281 if (event->xexpose.count!=0)
282 break;
283 paintEditMenuItem(iPtr);
284 break;
286 case DestroyNotify:
287 destroyEditMenuItem(iPtr);
288 break;
293 static void
294 destroyEditMenuItem(WEditMenuItem *iPtr)
296 if (iPtr->label)
297 free(iPtr->label);
298 if (iPtr->data && iPtr->destroyData)
299 (*iPtr->destroyData)(iPtr->data);
301 free(iPtr);
306 /******************** WEditMenu *******************/
308 static void destroyEditMenu(WEditMenu *mPtr);
310 static void updateMenuContents(WEditMenu *mPtr);
312 static void handleEvents(XEvent *event, void *data);
314 static void editItemLabel(WEditMenuItem *item);
315 static void stopEditItem(WEditMenu *menu, Bool apply);
318 static void unmapMenu(WEditMenu *menu);
319 static void deselectItem(WEditMenu *menu);
322 static W_Class EditMenuClass = 0;
325 W_Class
326 InitEditMenu(WMScreen *scr)
328 /* register our widget with WINGs and get our widget class ID */
329 if (!EditMenuClass) {
331 EditMenuClass = W_RegisterUserWidget();
334 return EditMenuClass;
339 typedef struct {
340 int flags;
341 int window_style;
342 int window_level;
343 int reserved;
344 Pixmap miniaturize_pixmap; /* pixmap for miniaturize button */
345 Pixmap close_pixmap; /* pixmap for close button */
346 Pixmap miniaturize_mask; /* miniaturize pixmap mask */
347 Pixmap close_mask; /* close pixmap mask */
348 int extra_flags;
349 } GNUstepWMAttributes;
352 #define GSWindowStyleAttr (1<<0)
353 #define GSWindowLevelAttr (1<<1)
356 static void
357 writeGNUstepWMAttr(WMScreen *scr, Window window, GNUstepWMAttributes *attr)
359 unsigned long data[9];
361 /* handle idiot compilers where array of CARD32 != struct of CARD32 */
362 data[0] = attr->flags;
363 data[1] = attr->window_style;
364 data[2] = attr->window_level;
365 data[3] = 0; /* reserved */
366 /* The X protocol says XIDs are 32bit */
367 data[4] = attr->miniaturize_pixmap;
368 data[5] = attr->close_pixmap;
369 data[6] = attr->miniaturize_mask;
370 data[7] = attr->close_mask;
371 data[8] = attr->extra_flags;
372 XChangeProperty(scr->display, window, scr->attribsAtom, scr->attribsAtom,
373 32, PropModeReplace, (unsigned char *)data, 9);
377 static void
378 realizeObserver(void *self, WMNotification *not)
380 WEditMenu *menu = (WEditMenu*)self;
381 GNUstepWMAttributes attribs;
383 memset(&attribs, 0, sizeof(GNUstepWMAttributes));
384 attribs.flags = GSWindowStyleAttr|GSWindowLevelAttr;
385 attribs.window_style = WMBorderlessWindowMask;
386 attribs.window_level = WMSubmenuWindowLevel;
388 writeGNUstepWMAttr(WMWidgetScreen(menu), menu->view->window, &attribs);
392 static void
393 itemSelectObserver(void *self, WMNotification *notif)
395 WEditMenu *menu = (WEditMenu*)self;
396 WEditMenu *rmenu;
398 rmenu = (WEditMenu*)WMGetNotificationObject(notif);
399 /* check whether rmenu is from the same hierarchy of menu? */
401 if (rmenu == menu) {
402 return;
405 if (menu->selectedItem && !menu->selectedItem->submenu) {
406 deselectItem(menu);
411 static WEditMenu*
412 makeEditMenu(WMScreen *scr, WMWidget *parent, char *title)
414 WEditMenu *mPtr;
415 WEditMenuItem *item;
417 if (!EditMenuClass)
418 InitEditMenu(scr);
421 mPtr = wmalloc(sizeof(WEditMenu));
422 memset(mPtr, 0, sizeof(WEditMenu));
424 mPtr->widgetClass = EditMenuClass;
426 if (parent) {
427 mPtr->view = W_CreateView(W_VIEW(parent));
428 mPtr->flags.standalone = 0;
429 } else {
430 mPtr->view = W_CreateTopView(scr);
431 mPtr->flags.standalone = 1;
433 if (!mPtr->view) {
434 free(mPtr);
435 return NULL;
437 mPtr->view->self = mPtr;
439 mPtr->flags.isSelectable = 1;
440 mPtr->flags.isEditable = 1;
442 W_SetViewBackgroundColor(mPtr->view, scr->darkGray);
444 WMAddNotificationObserver(realizeObserver, mPtr,
445 WMViewRealizedNotification, mPtr->view);
447 WMAddNotificationObserver(itemSelectObserver, mPtr,
448 "EditMenuItemSelected", NULL);
450 mPtr->items = WMCreateBag(4);
452 WMCreateEventHandler(mPtr->view, ExposureMask|StructureNotifyMask,
453 handleEvents, mPtr);
456 if (title != NULL) {
457 item = WCreateEditMenuItem(mPtr, title, True);
459 WMMapWidget(item);
460 WMPutInBag(mPtr->items, item);
462 mPtr->flags.isTitled = 1;
465 mPtr->itemHeight = WMFontHeight(scr->normalFont) + 6;
466 mPtr->titleHeight = WMFontHeight(scr->boldFont) + 8;
468 updateMenuContents(mPtr);
470 return mPtr;
474 WEditMenu*
475 WCreateEditMenu(WMScreen *scr, char *title)
477 return makeEditMenu(scr, NULL, title);
481 WEditMenu*
482 WCreateEditMenuPad(WMWidget *parent)
484 return makeEditMenu(WMWidgetScreen(parent), parent, NULL);
488 void
489 WSetEditMenuDelegate(WEditMenu *mPtr, WEditMenuDelegate *delegate)
491 mPtr->delegate = delegate;
495 WEditMenuItem*
496 WInsertMenuItemWithTitle(WEditMenu *mPtr, int index, char *title)
498 WEditMenuItem *item;
500 item = WCreateEditMenuItem(mPtr, title, False);
502 WMMapWidget(item);
504 if (index >= WMGetBagItemCount(mPtr->items)) {
505 WMPutInBag(mPtr->items, item);
506 } else {
507 if (index < 0)
508 index = 0;
509 if (mPtr->flags.isTitled)
510 index++;
511 WMInsertInBag(mPtr->items, index, item);
514 updateMenuContents(mPtr);
516 return item;
520 WEditMenuItem*
521 WGetEditMenuItem(WEditMenu *mPtr, int index)
523 if (index >= WMGetBagItemCount(mPtr->items))
524 return NULL;
525 else
526 return WMGetFromBag(mPtr->items, index + (mPtr->flags.isTitled ? 1 : 0));
530 WEditMenuItem*
531 WAddMenuItemWithTitle(WEditMenu *mPtr, char *title)
533 return WInsertMenuItemWithTitle(mPtr, WMGetBagItemCount(mPtr->items),
534 title);
539 void
540 WSetEditMenuTitle(WEditMenu *mPtr, char *title)
542 WEditMenuItem *item;
544 item = WMGetFromBag(mPtr->items, 0);
546 free(item->label);
547 item->label = wstrdup(title);
548 updateMenuContents(mPtr);
553 char*
554 WGetEditMenuTitle(WEditMenu *mPtr)
556 WEditMenuItem *item;
558 item = WMGetFromBag(mPtr->items, 0);
560 return item->label;
564 void
565 WSetEditMenuAcceptsDrop(WEditMenu *mPtr, Bool flag)
567 mPtr->flags.acceptsDrop = flag;
571 void
572 WSetEditMenuSubmenu(WEditMenu *mPtr, WEditMenuItem *item, WEditMenu *submenu)
574 item->submenu = submenu;
575 submenu->parent = mPtr;
577 paintEditMenuItem(item);
581 WEditMenu*
582 WGetEditMenuSubmenu(WEditMenu *mPtr, WEditMenuItem *item)
584 return item->submenu;
588 static int simpleMatch(void *a, void *b)
590 if (a == b)
591 return 1;
592 else
593 return 0;
597 void
598 WRemoveEditMenuItem(WEditMenu *mPtr, WEditMenuItem *item)
600 int index;
602 index = WMFindInBag(mPtr->items, simpleMatch, item);
604 if (index == WBNotFound) {
605 return;
608 WMDeleteFromBag(mPtr->items, index);
610 updateMenuContents(mPtr);
614 void
615 WSetEditMenuSelectable(WEditMenu *mPtr, Bool flag)
617 mPtr->flags.isSelectable = flag;
621 void
622 WSetEditMenuEditable(WEditMenu *mPtr, Bool flag)
624 mPtr->flags.isEditable = flag;
628 void
629 WSetEditMenuIsFactory(WEditMenu *mPtr, Bool flag)
631 mPtr->flags.isFactory = flag;
635 void
636 WSetEditMenuMinSize(WEditMenu *mPtr, WMSize size)
638 mPtr->minSize.width = size.width;
639 mPtr->minSize.height = size.height;
643 void
644 WSetEditMenuMaxSize(WEditMenu *mPtr, WMSize size)
646 mPtr->maxSize.width = size.width;
647 mPtr->maxSize.height = size.height;
651 WMPoint
652 WGetEditMenuLocationForSubmenu(WEditMenu *mPtr, WEditMenu *submenu)
654 WMBagIterator iter;
655 WEditMenuItem *item;
656 int y;
658 if (mPtr->flags.isTitled)
659 y = -mPtr->titleHeight;
660 else
661 y = 0;
662 WM_ITERATE_BAG(mPtr->items, item, iter) {
663 if (item->submenu == submenu) {
664 WMPoint pt = WMGetViewScreenPosition(mPtr->view);
666 return wmkpoint(pt.x + mPtr->view->size.width, pt.y + y);
668 y += W_VIEW_HEIGHT(item->view);
671 puts("invalid submenu passed to WGetEditMenuLocationForSubmenu()");
673 return wmkpoint(0,0);
678 static void
679 closeMenuAction(WMWidget *w, void *data)
681 WEditMenu *menu = (WEditMenu*)data;
683 WMAddIdleHandler(WMDestroyWidget, menu->closeB);
684 menu->closeB = NULL;
686 unmapMenu(menu);
690 void
691 WTearOffEditMenu(WEditMenu *menu, WEditMenu *submenu)
693 WEditMenuItem *item;
695 submenu->flags.isTornOff = 1;
697 item = (WEditMenuItem*)WMGetFromBag(submenu->items, 0);
699 submenu->closeB = WMCreateCommandButton(item);
700 WMResizeWidget(submenu->closeB, 15, 15);
701 WMMoveWidget(submenu->closeB, W_VIEW(submenu)->size.width - 20, 3);
702 WMRealizeWidget(submenu->closeB);
703 WMSetButtonText(submenu->closeB, "X");
704 WMSetButtonAction(submenu->closeB, closeMenuAction, submenu);
705 WMMapWidget(submenu->closeB);
707 if (menu->selectedItem && menu->selectedItem->submenu == submenu)
708 deselectItem(menu);
713 Bool
714 WEditMenuIsTornOff(WEditMenu *mPtr)
716 return mPtr->flags.isTornOff;
720 static void
721 updateMenuContents(WEditMenu *mPtr)
723 int newW, newH;
724 int w;
725 int i;
726 int iheight = mPtr->itemHeight;
727 int offs = 1;
728 WMBagIterator iter;
729 WEditMenuItem *item;
731 newW = 0;
732 newH = offs;
734 i = 0;
735 WM_ITERATE_BAG(mPtr->items, item, iter) {
736 w = getItemTextWidth(item);
738 newW = WMAX(w, newW);
740 WMMoveWidget(item, offs, newH);
741 if (i == 0 && mPtr->flags.isTitled) {
742 newH += mPtr->titleHeight;
743 } else {
744 newH += iheight;
746 i = 1;
749 newW += iheight + 10;
750 newH--;
752 if (mPtr->minSize.width)
753 newW = WMAX(newW, mPtr->minSize.width);
754 if (mPtr->maxSize.width)
755 newW = WMIN(newW, mPtr->maxSize.width);
757 if (mPtr->minSize.height)
758 newH = WMAX(newH, mPtr->minSize.height);
759 if (mPtr->maxSize.height)
760 newH = WMIN(newH, mPtr->maxSize.height);
762 W_ResizeView(mPtr->view, newW, newH+1);
764 if (mPtr->closeB)
765 WMMoveWidget(mPtr->closeB, newW - 20, 3);
767 newW -= 2*offs;
769 i = 0;
770 WM_ITERATE_BAG(mPtr->items, item, iter) {
771 if (i == 0 && mPtr->flags.isTitled) {
772 WMResizeWidget(item, newW, mPtr->titleHeight);
773 } else {
774 WMResizeWidget(item, newW, iheight);
776 i = 1;
781 static void
782 unmapMenu(WEditMenu *menu)
784 WMUnmapWidget(menu);
786 if (menu->selectedItem) {
787 deselectItem(menu);
792 static void
793 deselectItem(WEditMenu *menu)
795 WEditMenu *submenu;
796 WEditMenuItem *item = menu->selectedItem;
798 highlightItem(item, False);
800 if (menu->delegate && menu->delegate->itemDeselected) {
801 (*menu->delegate->itemDeselected)(menu->delegate, menu, item);
803 submenu = item->submenu;
805 if (submenu && !WEditMenuIsTornOff(submenu)) {
806 unmapMenu(submenu);
809 menu->selectedItem = NULL;
813 static void
814 selectItem(WEditMenu *menu, WEditMenuItem *item)
816 if (!menu->flags.isSelectable || menu->selectedItem == item) {
817 return;
819 if (menu->selectedItem) {
820 deselectItem(menu);
823 if (menu->flags.isEditing) {
824 stopEditItem(menu, False);
827 if (item && !item->flags.isTitle) {
828 highlightItem(item, True);
830 if (item->submenu) {
831 WMPoint pt;
832 XSizeHints *hints;
834 hints = XAllocSizeHints();
836 pt = WGetEditMenuLocationForSubmenu(menu, item->submenu);
838 hints->flags = USPosition;
839 hints->x = pt.x;
840 hints->y = pt.y;
842 WMMoveWidget(item->submenu, pt.x, pt.y);
843 XSetWMNormalHints(W_VIEW_DISPLAY(item->submenu->view),
844 W_VIEW_DRAWABLE(item->submenu->view),
845 hints);
846 XFree(hints);
847 WMMapWidget(item->submenu);
849 item->submenu->flags.isTornOff = 0;
852 WMPostNotificationName("EditMenuItemSelected", menu, NULL);
854 if (menu->delegate && menu->delegate->itemSelected) {
855 (*menu->delegate->itemSelected)(menu->delegate, menu, item);
859 menu->selectedItem = item;
863 static void
864 paintMenu(WEditMenu *mPtr)
866 W_View *view = mPtr->view;
868 W_DrawRelief(W_VIEW_SCREEN(view), W_VIEW_DRAWABLE(view), 0, 0,
869 W_VIEW_WIDTH(view), W_VIEW_HEIGHT(view), WRSimple);
873 static void
874 handleEvents(XEvent *event, void *data)
876 WEditMenu *mPtr = (WEditMenu*)data;
878 switch (event->type) {
879 case DestroyNotify:
880 destroyEditMenu(mPtr);
881 break;
883 case Expose:
884 if (event->xexpose.count == 0)
885 paintMenu(mPtr);
886 break;
893 /* -------------------------- Menu Label Editing ------------------------ */
896 static void
897 stopEditItem(WEditMenu *menu, Bool apply)
899 char *text;
901 if (apply) {
902 text = WMGetTextFieldText(menu->tfield);
904 free(menu->selectedItem->label);
905 menu->selectedItem->label = wstrdup(text);
907 updateMenuContents(menu);
909 if (menu->delegate && menu->delegate->itemEdited) {
910 (*menu->delegate->itemEdited)(menu->delegate, menu,
911 menu->selectedItem);
915 WMUnmapWidget(menu->tfield);
916 menu->flags.isEditing = 0;
920 static void
921 textEndedEditing(struct WMTextFieldDelegate *self, WMNotification *notif)
923 WEditMenu *menu = (WEditMenu*)self->data;
924 int reason;
925 int i;
926 WEditMenuItem *item;
928 if (!menu->flags.isEditing)
929 return;
931 reason = (int)WMGetNotificationClientData(notif);
933 switch (reason) {
934 case WMEscapeTextMovement:
935 stopEditItem(menu, False);
936 break;
938 case WMReturnTextMovement:
939 stopEditItem(menu, True);
940 break;
942 case WMTabTextMovement:
943 stopEditItem(menu, True);
945 i = WMFindInBag(menu->items, simpleMatch, menu->selectedItem);
946 item = WMGetFromBag(menu->items, i+1);
947 if (item != NULL) {
948 selectItem(menu, item);
949 editItemLabel(item);
951 break;
953 case WMBacktabTextMovement:
954 stopEditItem(menu, True);
956 i = WMFindInBag(menu->items, simpleMatch, menu->selectedItem);
957 item = WMGetFromBag(menu->items, i-1);
958 if (item != NULL) {
959 selectItem(menu, item);
960 editItemLabel(item);
962 break;
968 static WMTextFieldDelegate textFieldDelegate = {
969 NULL,
970 NULL,
971 NULL,
972 textEndedEditing,
973 NULL,
974 NULL
978 static void
979 editItemLabel(WEditMenuItem *item)
981 WEditMenu *mPtr = item->parent;
982 WMTextField *tf;
983 int i;
985 if (!mPtr->flags.isEditable) {
986 return;
989 if (!mPtr->tfield) {
990 mPtr->tfield = WMCreateTextField(mPtr);
991 WMSetTextFieldBeveled(mPtr->tfield, False);
992 WMRealizeWidget(mPtr->tfield);
994 textFieldDelegate.data = mPtr;
996 WMSetTextFieldDelegate(mPtr->tfield, &textFieldDelegate);
998 tf = mPtr->tfield;
1000 i = WMFindInBag(mPtr->items, simpleMatch, item);
1002 WMSetTextFieldText(tf, item->label);
1003 WMSelectTextFieldRange(tf, wmkrange(0, strlen(item->label)));
1004 WMResizeWidget(tf, mPtr->view->size.width, mPtr->itemHeight);
1005 WMMoveWidget(tf, 0, item->view->pos.y);
1006 WMMapWidget(tf);
1007 WMSetFocusToWidget(tf);
1009 mPtr->flags.isEditing = 1;
1014 /* -------------------------------------------------- */
1017 static void
1018 slideWindow(Display *dpy, Window win, int srcX, int srcY, int dstX, int dstY)
1020 double x, y, dx, dy;
1021 int i;
1022 int iterations;
1024 iterations = WMIN(25, WMAX(abs(dstX-srcX), abs(dstY-srcY)));
1026 x = srcX;
1027 y = srcY;
1029 dx = (double)(dstX-srcX)/iterations;
1030 dy = (double)(dstY-srcY)/iterations;
1032 for (i = 0; i <= iterations; i++) {
1033 XMoveWindow(dpy, win, x, y);
1034 XFlush(dpy);
1036 wusleep(800);
1038 x += dx;
1039 y += dy;
1044 static int errorHandler(Display *d, XErrorEvent *ev)
1046 /* just ignore */
1047 return 0;
1051 static WEditMenu*
1052 findMenuInWindow(Display *dpy, Window toplevel, int x, int y)
1054 Window foo, bar;
1055 Window *children;
1056 unsigned nchildren;
1057 int i;
1058 WEditMenu *menu;
1059 WMView *view;
1060 int (*oldHandler)(Display *, XErrorEvent *);
1062 view = W_GetViewForXWindow(dpy, toplevel);
1063 if (view && view->self && WMWidgetClass(view->self) == EditMenuClass) {
1064 menu = (WEditMenu*)view->self;
1065 if (menu->flags.acceptsDrop) {
1066 return menu;
1070 if (!XQueryTree(dpy, toplevel, &foo, &bar,
1071 &children, &nchildren) || children == NULL) {
1072 return NULL;
1075 oldHandler = XSetErrorHandler(errorHandler);
1077 /* first window that contains the point is the one */
1078 for (i = nchildren-1; i >= 0; i--) {
1079 XWindowAttributes attr;
1081 if (XGetWindowAttributes(dpy, children[i], &attr)
1082 && attr.map_state == IsViewable
1083 && x >= attr.x && y >= attr.y
1084 && x < attr.x + attr.width && y < attr.y + attr.height) {
1085 Window tmp;
1087 tmp = children[i];
1089 menu = findMenuInWindow(dpy, tmp, x - attr.x, y - attr.y);
1090 if (menu) {
1091 XFree(children);
1092 return menu;
1097 XSetErrorHandler(oldHandler);
1099 XFree(children);
1100 return NULL;
1104 static void
1105 handleDragOver(WEditMenu *menu, WMView *view, WEditMenuItem *item,
1106 int x, int y)
1108 WMScreen *scr = W_VIEW_SCREEN(menu->view);
1109 Window blaw;
1110 int mx, my;
1111 int offs;
1113 XTranslateCoordinates(scr->display, W_VIEW_DRAWABLE(menu->view),
1114 scr->rootWin, 0, 0, &mx, &my, &blaw);
1116 offs = menu->flags.standalone ? 0 : 1;
1118 W_MoveView(view, mx + offs, y);
1119 if (view->size.width != menu->view->size.width) {
1120 W_ResizeView(view, menu->view->size.width - 2*offs,
1121 menu->itemHeight);
1122 W_ResizeView(item->view, menu->view->size.width - 2*offs,
1123 menu->itemHeight);
1128 static void
1129 handleItemDrop(WEditMenu *menu, WEditMenuItem *item, int x, int y)
1131 WMScreen *scr = W_VIEW_SCREEN(menu->view);
1132 Window blaw;
1133 int mx, my;
1134 int index;
1136 XTranslateCoordinates(scr->display, W_VIEW_DRAWABLE(menu->view),
1137 scr->rootWin, 0, 0, &mx, &my, &blaw);
1139 index = y - my;
1140 if (menu->flags.isTitled) {
1141 index -= menu->titleHeight;
1143 index = (index + menu->itemHeight/2) / menu->itemHeight;
1144 if (index < 0)
1145 index = 0;
1147 if (menu->flags.isTitled) {
1148 index++;
1151 if (index > WMGetBagItemCount(menu->items)) {
1152 WMPutInBag(menu->items, item);
1153 } else {
1154 WMInsertInBag(menu->items, index, item);
1157 W_ReparentView(item->view, menu->view, 0, index*menu->itemHeight);
1159 item->parent = menu;
1160 if (item->submenu) {
1161 item->submenu->parent = menu;
1164 updateMenuContents(menu);
1168 static void
1169 dragMenu(WEditMenu *menu)
1171 WMScreen *scr = W_VIEW_SCREEN(menu->view);
1172 XEvent ev;
1173 Bool done = False;
1174 int dx, dy;
1175 unsigned blau;
1176 Window blaw;
1178 XGetGeometry(scr->display, W_VIEW_DRAWABLE(menu->view), &blaw, &dx, &dy,
1179 &blau, &blau, &blau, &blau);
1181 XTranslateCoordinates(scr->display, W_VIEW_DRAWABLE(menu->view),
1182 scr->rootWin, dx, dy, &dx, &dy, &blaw);
1184 dx = menu->dragX - dx;
1185 dy = menu->dragY - dy;
1187 XGrabPointer(scr->display, scr->rootWin, False,
1188 ButtonReleaseMask|ButtonMotionMask,
1189 GrabModeAsync, GrabModeAsync, None, scr->defaultCursor,
1190 CurrentTime);
1192 if (menu->parent)
1193 WTearOffEditMenu(menu->parent, menu);
1195 while (!done) {
1196 WMNextEvent(scr->display, &ev);
1198 switch (ev.type) {
1199 case ButtonRelease:
1200 done = True;
1201 break;
1203 case MotionNotify:
1204 WMMoveWidget(menu, ev.xmotion.x_root - dx,
1205 ev.xmotion.y_root - dy);
1206 break;
1208 default:
1209 WMHandleEvent(&ev);
1210 break;
1214 XUngrabPointer(scr->display, CurrentTime);
1218 static WEditMenu*
1219 duplicateMenu(WEditMenu *menu)
1221 WEditMenu *nmenu;
1222 WEditMenuItem *title;
1224 if (menu->flags.isTitled) {
1225 title = WMGetFromBag(menu->items, 0);
1228 nmenu = WCreateEditMenu(WMWidgetScreen(menu), title->label);
1230 memcpy(&nmenu->flags, &menu->flags, sizeof(menu->flags));
1231 nmenu->delegate = menu->delegate;
1233 WMRealizeWidget(nmenu);
1235 return nmenu;
1240 static WEditMenuItem*
1241 duplicateItem(WEditMenuItem *item)
1243 WEditMenuItem *nitem;
1245 nitem = WCreateEditMenuItem(item->parent, item->label, False);
1246 if (item->pixmap)
1247 nitem->pixmap = WMRetainPixmap(item->pixmap);
1249 return nitem;
1253 static void
1254 dragItem(WEditMenu *menu, WEditMenuItem *item)
1256 Display *dpy = W_VIEW_DISPLAY(menu->view);
1257 WMScreen *scr = W_VIEW_SCREEN(menu->view);
1258 int x, y;
1259 int dx, dy;
1260 Bool done = False;
1261 Window blaw;
1262 int blai;
1263 unsigned blau;
1264 Window win;
1265 WMView *dview;
1266 int orix, oriy;
1267 Bool enteredMenu = False;
1268 WMSize oldSize = item->view->size;
1269 WEditMenuItem *dritem = item;
1270 WEditMenu *dmenu = NULL;
1273 if (item->flags.isTitle) {
1274 WMRaiseWidget(menu);
1276 dragMenu(menu);
1278 return;
1282 selectItem(menu, NULL);
1284 win = scr->rootWin;
1286 XTranslateCoordinates(dpy, W_VIEW_DRAWABLE(item->view), win,
1287 0, 0, &orix, &oriy, &blaw);
1289 dview = W_CreateUnmanagedTopView(scr);
1290 W_SetViewBackgroundColor(dview, scr->black);
1291 W_ResizeView(dview, W_VIEW_WIDTH(item->view), W_VIEW_HEIGHT(item->view));
1292 W_MoveView(dview, orix, oriy);
1293 W_RealizeView(dview);
1295 if (menu->flags.isFactory) {
1296 dritem = duplicateItem(item);
1298 W_ReparentView(dritem->view, dview, 0, 0);
1299 WMResizeWidget(dritem, oldSize.width, oldSize.height);
1300 WMRealizeWidget(dritem);
1301 WMMapWidget(dritem);
1302 } else {
1303 W_ReparentView(item->view, dview, 0, 0);
1306 W_MapView(dview);
1308 dx = menu->dragX - orix;
1309 dy = menu->dragY - oriy;
1311 XGrabPointer(dpy, scr->rootWin, False,
1312 ButtonPressMask|ButtonReleaseMask|ButtonMotionMask,
1313 GrabModeAsync, GrabModeAsync, None, scr->defaultCursor,
1314 CurrentTime);
1316 while (!done) {
1317 XEvent ev;
1319 WMNextEvent(dpy, &ev);
1321 switch (ev.type) {
1322 case MotionNotify:
1323 XQueryPointer(dpy, win, &blaw, &blaw, &blai, &blai, &x, &y, &blau);
1325 dmenu = findMenuInWindow(dpy, win, x, y);
1327 if (dmenu) {
1328 handleDragOver(dmenu, dview, dritem, x - dx, y - dy);
1329 enteredMenu = True;
1330 } else {
1331 if (enteredMenu) {
1332 W_ResizeView(dview, oldSize.width, oldSize.height);
1333 W_ResizeView(dritem->view, oldSize.width, oldSize.height);
1334 enteredMenu = False;
1336 W_MoveView(dview, x - dx, y - dy);
1339 break;
1341 case ButtonRelease:
1342 done = True;
1343 break;
1345 default:
1346 WMHandleEvent(&ev);
1347 break;
1350 XUngrabPointer(dpy, CurrentTime);
1353 if (!enteredMenu) {
1354 Bool rem = True;
1356 if (!menu->flags.isFactory) {
1357 W_UnmapView(dview);
1358 if (menu->delegate && menu->delegate->shouldRemoveItem) {
1359 rem = (*menu->delegate->shouldRemoveItem)(menu->delegate,
1360 menu, item);
1362 W_MapView(dview);
1365 if (!rem || menu->flags.isFactory) {
1366 slideWindow(dpy, W_VIEW_DRAWABLE(dview), x-dx, y-dy, orix, oriy);
1368 if (!menu->flags.isFactory) {
1369 WRemoveEditMenuItem(menu, dritem);
1370 handleItemDrop(dmenu ? dmenu : menu, dritem, orix, oriy);
1372 } else {
1373 WRemoveEditMenuItem(menu, dritem);
1375 } else {
1376 WRemoveEditMenuItem(menu, dritem);
1378 if (menu->delegate && menu->delegate->itemCloned
1379 && menu->flags.isFactory) {
1380 (*menu->delegate->itemCloned)(menu->delegate, menu,
1381 item, dritem);
1384 handleItemDrop(dmenu, dritem, x-dy, y-dy);
1386 if (item->submenu && menu->flags.isFactory) {
1387 WEditMenu *submenu;
1389 submenu = duplicateMenu(item->submenu);
1390 WSetEditMenuSubmenu(dmenu, dritem, submenu);
1394 /* can't destroy now because we're being called from
1395 * the event handler of the item. and destroying now,
1396 * would mean destroying the item too in some cases.
1398 WMAddIdleHandler((WMCallback*)W_DestroyView, dview);
1403 static void
1404 handleItemClick(XEvent *event, void *data)
1406 WEditMenuItem *item = (WEditMenuItem*)data;
1407 WEditMenu *menu = item->parent;
1409 switch (event->type) {
1410 case ButtonPress:
1411 selectItem(menu, item);
1413 if (WMIsDoubleClick(event)) {
1414 editItemLabel(item);
1417 menu->flags.isDragging = 1;
1418 menu->dragX = event->xbutton.x_root;
1419 menu->dragY = event->xbutton.y_root;
1420 break;
1422 case ButtonRelease:
1423 menu->flags.isDragging = 0;
1424 break;
1426 case MotionNotify:
1427 if (menu->flags.isDragging) {
1428 if (abs(event->xbutton.x_root - menu->dragX) > 5
1429 || abs(event->xbutton.y_root - menu->dragY) > 5) {
1430 menu->flags.isDragging = 0;
1431 dragItem(menu, item);
1434 break;
1439 static void
1440 destroyEditMenu(WEditMenu *mPtr)
1442 WEditMenuItem *item;
1443 WMBagIterator iter;
1445 WMRemoveNotificationObserver(mPtr);
1447 WM_ITERATE_BAG(mPtr->items, item, iter) {
1448 if (item->submenu) {
1449 WMDestroyWidget(item->submenu);
1453 WMFreeBag(mPtr->items);
1455 free(mPtr);