new hide/unhide functions
[wmaker-crm.git] / WPrefs.app / editmenu.c
blob6ead13ac8bd8fe2a31f6a8a3162e0f72d5cd966d
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;
96 unsigned wasMapped:1;
97 } flags;
98 } EditMenu;
102 /******************** WEditMenuItem ********************/
104 static void destroyEditMenuItem(WEditMenuItem *iPtr);
105 static void paintEditMenuItem(WEditMenuItem *iPtr);
106 static void handleItemEvents(XEvent *event, void *data);
108 static void handleItemClick(XEvent *event, void *data);
111 static W_Class EditMenuItemClass = 0;
114 W_Class
115 InitEditMenuItem(WMScreen *scr)
117 /* register our widget with WINGs and get our widget class ID */
118 if (!EditMenuItemClass) {
119 EditMenuItemClass = W_RegisterUserWidget();
122 return EditMenuItemClass;
126 WEditMenuItem*
127 WCreateEditMenuItem(WMWidget *parent, char *title, Bool isTitle)
129 WEditMenuItem *iPtr;
130 WMScreen *scr = WMWidgetScreen(parent);
132 if (!EditMenuItemClass)
133 InitEditMenuItem(scr);
136 iPtr = wmalloc(sizeof(WEditMenuItem));
138 memset(iPtr, 0, sizeof(WEditMenuItem));
140 iPtr->widgetClass = EditMenuItemClass;
142 iPtr->view = W_CreateView(W_VIEW(parent));
143 if (!iPtr->view) {
144 free(iPtr);
145 return NULL;
147 iPtr->view->self = iPtr;
149 iPtr->parent = parent;
151 iPtr->label = wstrdup(title);
153 iPtr->flags.isTitle = isTitle;
155 if (isTitle) {
156 WMSetWidgetBackgroundColor(iPtr, WMBlackColor(scr));
159 WMCreateEventHandler(iPtr->view, ExposureMask|StructureNotifyMask,
160 handleItemEvents, iPtr);
162 WMCreateEventHandler(iPtr->view, ButtonPressMask|ButtonReleaseMask
163 |ButtonMotionMask, handleItemClick, iPtr);
166 return iPtr;
169 char*
170 WGetEditMenuItemTitle(WEditMenuItem *item)
172 return item->label;
175 void*
176 WGetEditMenuItemData(WEditMenuItem *item)
178 return item->data;
182 void
183 WSetEditMenuItemData(WEditMenuItem *item, void *data, WMCallback *destroyer)
185 item->data = data;
186 item->destroyData = destroyer;
190 void
191 WSetEditMenuItemImage(WEditMenuItem *item, WMPixmap *pixmap)
193 if (item->pixmap)
194 WMReleasePixmap(item->pixmap);
195 item->pixmap = WMRetainPixmap(pixmap);
199 static void
200 paintEditMenuItem(WEditMenuItem *iPtr)
202 WMScreen *scr = WMWidgetScreen(iPtr);
203 WMColor *color;
204 Window win = W_VIEW(iPtr)->window;
205 int w = W_VIEW(iPtr)->size.width;
206 int h = W_VIEW(iPtr)->size.height;
207 WMFont *font = (iPtr->flags.isTitle ? scr->boldFont : scr->normalFont);
209 if (!iPtr->view->flags.realized)
210 return;
212 color = scr->black;
213 if (iPtr->flags.isTitle && !iPtr->flags.isHighlighted) {
214 color = scr->white;
218 XClearWindow(scr->display, win);
220 W_DrawRelief(scr, win, 0, 0, w+1, h, WRRaised);
222 WMDrawString(scr, win, WMColorGC(color), font, 5, 3, iPtr->label,
223 strlen(iPtr->label));
225 if (iPtr->pixmap) {
226 WMSize size = WMGetPixmapSize(iPtr->pixmap);
228 WMDrawPixmap(iPtr->pixmap, win, w - size.width - 5,
229 (h - size.height)/2);
230 } else if (iPtr->submenu) {
231 /* draw the cascade indicator */
232 XDrawLine(scr->display,win,WMColorGC(scr->darkGray),
233 w-11, 6, w-6, h/2-1);
234 XDrawLine(scr->display,win,WMColorGC(scr->white),
235 w-11, h-8, w-6, h/2-1);
236 XDrawLine(scr->display,win,WMColorGC(scr->black),
237 w-12, 6, w-12, h-8);
242 static void
243 highlightItem(WEditMenuItem *iPtr, Bool high)
245 if (iPtr->flags.isTitle)
246 return;
248 iPtr->flags.isHighlighted = high;
250 if (high) {
251 WMSetWidgetBackgroundColor(iPtr, WMWhiteColor(WMWidgetScreen(iPtr)));
252 } else {
253 if (!iPtr->flags.isTitle) {
254 WMSetWidgetBackgroundColor(iPtr,
255 WMGrayColor(WMWidgetScreen(iPtr)));
256 } else {
257 WMSetWidgetBackgroundColor(iPtr,
258 WMBlackColor(WMWidgetScreen(iPtr)));
264 static int
265 getItemTextWidth(WEditMenuItem *iPtr)
267 WMScreen *scr = WMWidgetScreen(iPtr);
269 if (iPtr->flags.isTitle) {
270 return WMWidthOfString(scr->boldFont, iPtr->label,
271 strlen(iPtr->label));
272 } else {
273 return WMWidthOfString(scr->normalFont, iPtr->label,
274 strlen(iPtr->label));
280 static void
281 handleItemEvents(XEvent *event, void *data)
283 WEditMenuItem *iPtr = (WEditMenuItem*)data;
285 switch (event->type) {
286 case Expose:
287 if (event->xexpose.count!=0)
288 break;
289 paintEditMenuItem(iPtr);
290 break;
292 case DestroyNotify:
293 destroyEditMenuItem(iPtr);
294 break;
299 static void
300 destroyEditMenuItem(WEditMenuItem *iPtr)
302 if (iPtr->label)
303 free(iPtr->label);
304 if (iPtr->data && iPtr->destroyData)
305 (*iPtr->destroyData)(iPtr->data);
306 if (iPtr->submenu)
307 WMDestroyWidget(iPtr->submenu);
309 free(iPtr);
314 /******************** WEditMenu *******************/
316 static void destroyEditMenu(WEditMenu *mPtr);
318 static void updateMenuContents(WEditMenu *mPtr);
320 static void handleEvents(XEvent *event, void *data);
322 static void editItemLabel(WEditMenuItem *item);
324 static void stopEditItem(WEditMenu *menu, Bool apply);
326 static void deselectItem(WEditMenu *menu);
329 static W_Class EditMenuClass = 0;
332 W_Class
333 InitEditMenu(WMScreen *scr)
335 /* register our widget with WINGs and get our widget class ID */
336 if (!EditMenuClass) {
338 EditMenuClass = W_RegisterUserWidget();
341 return EditMenuClass;
346 typedef struct {
347 int flags;
348 int window_style;
349 int window_level;
350 int reserved;
351 Pixmap miniaturize_pixmap; /* pixmap for miniaturize button */
352 Pixmap close_pixmap; /* pixmap for close button */
353 Pixmap miniaturize_mask; /* miniaturize pixmap mask */
354 Pixmap close_mask; /* close pixmap mask */
355 int extra_flags;
356 } GNUstepWMAttributes;
359 #define GSWindowStyleAttr (1<<0)
360 #define GSWindowLevelAttr (1<<1)
363 static void
364 writeGNUstepWMAttr(WMScreen *scr, Window window, GNUstepWMAttributes *attr)
366 unsigned long data[9];
368 /* handle idiot compilers where array of CARD32 != struct of CARD32 */
369 data[0] = attr->flags;
370 data[1] = attr->window_style;
371 data[2] = attr->window_level;
372 data[3] = 0; /* reserved */
373 /* The X protocol says XIDs are 32bit */
374 data[4] = attr->miniaturize_pixmap;
375 data[5] = attr->close_pixmap;
376 data[6] = attr->miniaturize_mask;
377 data[7] = attr->close_mask;
378 data[8] = attr->extra_flags;
379 XChangeProperty(scr->display, window, scr->attribsAtom, scr->attribsAtom,
380 32, PropModeReplace, (unsigned char *)data, 9);
384 static void
385 realizeObserver(void *self, WMNotification *not)
387 WEditMenu *menu = (WEditMenu*)self;
388 GNUstepWMAttributes attribs;
390 memset(&attribs, 0, sizeof(GNUstepWMAttributes));
391 attribs.flags = GSWindowStyleAttr|GSWindowLevelAttr;
392 attribs.window_style = WMBorderlessWindowMask;
393 attribs.window_level = WMSubmenuWindowLevel;
395 writeGNUstepWMAttr(WMWidgetScreen(menu), menu->view->window, &attribs);
399 static void
400 itemSelectObserver(void *self, WMNotification *notif)
402 WEditMenu *menu = (WEditMenu*)self;
403 WEditMenu *rmenu;
405 rmenu = (WEditMenu*)WMGetNotificationObject(notif);
406 /* check whether rmenu is from the same hierarchy of menu? */
408 if (rmenu == menu) {
409 return;
412 if (menu->selectedItem) {
413 if (!menu->selectedItem->submenu)
414 deselectItem(menu);
415 if (menu->flags.isEditing)
416 stopEditItem(menu, False);
421 static WEditMenu*
422 makeEditMenu(WMScreen *scr, WMWidget *parent, char *title)
424 WEditMenu *mPtr;
425 WEditMenuItem *item;
427 if (!EditMenuClass)
428 InitEditMenu(scr);
431 mPtr = wmalloc(sizeof(WEditMenu));
432 memset(mPtr, 0, sizeof(WEditMenu));
434 mPtr->widgetClass = EditMenuClass;
436 if (parent) {
437 mPtr->view = W_CreateView(W_VIEW(parent));
438 mPtr->flags.standalone = 0;
439 } else {
440 mPtr->view = W_CreateTopView(scr);
441 mPtr->flags.standalone = 1;
443 if (!mPtr->view) {
444 free(mPtr);
445 return NULL;
447 mPtr->view->self = mPtr;
449 mPtr->flags.isSelectable = 1;
450 mPtr->flags.isEditable = 1;
452 W_SetViewBackgroundColor(mPtr->view, scr->darkGray);
454 WMAddNotificationObserver(realizeObserver, mPtr,
455 WMViewRealizedNotification, mPtr->view);
457 WMAddNotificationObserver(itemSelectObserver, mPtr,
458 "EditMenuItemSelected", NULL);
460 mPtr->items = WMCreateBag(4);
462 WMCreateEventHandler(mPtr->view, ExposureMask|StructureNotifyMask,
463 handleEvents, mPtr);
466 if (title != NULL) {
467 item = WCreateEditMenuItem(mPtr, title, True);
469 WMMapWidget(item);
470 WMPutInBag(mPtr->items, item);
472 mPtr->flags.isTitled = 1;
475 mPtr->itemHeight = WMFontHeight(scr->normalFont) + 6;
476 mPtr->titleHeight = WMFontHeight(scr->boldFont) + 8;
478 updateMenuContents(mPtr);
480 return mPtr;
484 WEditMenu*
485 WCreateEditMenu(WMScreen *scr, char *title)
487 return makeEditMenu(scr, NULL, title);
491 WEditMenu*
492 WCreateEditMenuPad(WMWidget *parent)
494 return makeEditMenu(WMWidgetScreen(parent), parent, NULL);
498 void
499 WSetEditMenuDelegate(WEditMenu *mPtr, WEditMenuDelegate *delegate)
501 mPtr->delegate = delegate;
505 WEditMenuItem*
506 WInsertMenuItemWithTitle(WEditMenu *mPtr, int index, char *title)
508 WEditMenuItem *item;
510 item = WCreateEditMenuItem(mPtr, title, False);
512 WMMapWidget(item);
514 if (index >= WMGetBagItemCount(mPtr->items)) {
515 WMPutInBag(mPtr->items, item);
516 } else {
517 if (index < 0)
518 index = 0;
519 if (mPtr->flags.isTitled)
520 index++;
521 WMInsertInBag(mPtr->items, index, item);
524 updateMenuContents(mPtr);
526 return item;
530 WEditMenuItem*
531 WGetEditMenuItem(WEditMenu *mPtr, int index)
533 if (index >= WMGetBagItemCount(mPtr->items))
534 return NULL;
535 else
536 return WMGetFromBag(mPtr->items, index + (mPtr->flags.isTitled ? 1 : 0));
540 WEditMenuItem*
541 WAddMenuItemWithTitle(WEditMenu *mPtr, char *title)
543 return WInsertMenuItemWithTitle(mPtr, WMGetBagItemCount(mPtr->items),
544 title);
549 void
550 WSetEditMenuTitle(WEditMenu *mPtr, char *title)
552 WEditMenuItem *item;
554 item = WMGetFromBag(mPtr->items, 0);
556 free(item->label);
557 item->label = wstrdup(title);
559 updateMenuContents(mPtr);
561 WMRedisplayWidget(item);
566 char*
567 WGetEditMenuTitle(WEditMenu *mPtr)
569 WEditMenuItem *item;
571 item = WMGetFromBag(mPtr->items, 0);
573 return item->label;
577 void
578 WSetEditMenuAcceptsDrop(WEditMenu *mPtr, Bool flag)
580 mPtr->flags.acceptsDrop = flag;
584 void
585 WSetEditMenuSubmenu(WEditMenu *mPtr, WEditMenuItem *item, WEditMenu *submenu)
587 item->submenu = submenu;
588 submenu->parent = mPtr;
590 paintEditMenuItem(item);
594 WEditMenu*
595 WGetEditMenuSubmenu(WEditMenu *mPtr, WEditMenuItem *item)
597 return item->submenu;
601 static int
602 simpleMatch(void *a, void *b)
604 return ((a == b) ? 1 : 0);
608 void
609 WRemoveEditMenuItem(WEditMenu *mPtr, WEditMenuItem *item)
611 int index;
613 index = WMFindInBag(mPtr->items, simpleMatch, item);
615 if (index == WBNotFound) {
616 return;
619 WMDeleteFromBag(mPtr->items, index);
621 updateMenuContents(mPtr);
625 void
626 WSetEditMenuSelectable(WEditMenu *mPtr, Bool flag)
628 mPtr->flags.isSelectable = flag;
632 void
633 WSetEditMenuEditable(WEditMenu *mPtr, Bool flag)
635 mPtr->flags.isEditable = flag;
639 void
640 WSetEditMenuIsFactory(WEditMenu *mPtr, Bool flag)
642 mPtr->flags.isFactory = flag;
646 void
647 WSetEditMenuMinSize(WEditMenu *mPtr, WMSize size)
649 mPtr->minSize.width = size.width;
650 mPtr->minSize.height = size.height;
654 void
655 WSetEditMenuMaxSize(WEditMenu *mPtr, WMSize size)
657 mPtr->maxSize.width = size.width;
658 mPtr->maxSize.height = size.height;
662 WMPoint
663 WGetEditMenuLocationForSubmenu(WEditMenu *mPtr, WEditMenu *submenu)
665 WMBagIterator iter;
666 WEditMenuItem *item;
667 int y;
669 if (mPtr->flags.isTitled)
670 y = -mPtr->titleHeight;
671 else
672 y = 0;
673 WM_ITERATE_BAG(mPtr->items, item, iter) {
674 if (item->submenu == submenu) {
675 WMPoint pt = WMGetViewScreenPosition(mPtr->view);
677 return wmkpoint(pt.x + mPtr->view->size.width, pt.y + y);
679 y += W_VIEW_HEIGHT(item->view);
682 puts("invalid submenu passed to WGetEditMenuLocationForSubmenu()");
684 return wmkpoint(0,0);
689 static void
690 closeMenuAction(WMWidget *w, void *data)
692 WEditMenu *menu = (WEditMenu*)data;
694 WMAddIdleHandler(WMDestroyWidget, menu->closeB);
695 menu->closeB = NULL;
697 WEditMenuHide(menu);
701 void
702 WTearOffEditMenu(WEditMenu *menu, WEditMenu *submenu)
704 WEditMenuItem *item;
706 submenu->flags.isTornOff = 1;
708 item = (WEditMenuItem*)WMGetFromBag(submenu->items, 0);
710 submenu->closeB = WMCreateCommandButton(item);
711 WMResizeWidget(submenu->closeB, 15, 15);
712 WMMoveWidget(submenu->closeB, W_VIEW(submenu)->size.width - 20, 3);
713 WMRealizeWidget(submenu->closeB);
714 WMSetButtonText(submenu->closeB, "X");
715 WMSetButtonAction(submenu->closeB, closeMenuAction, submenu);
716 WMMapWidget(submenu->closeB);
718 if (menu->selectedItem && menu->selectedItem->submenu == submenu)
719 deselectItem(menu);
724 Bool
725 WEditMenuIsTornOff(WEditMenu *mPtr)
727 return mPtr->flags.isTornOff;
732 void
733 WEditMenuHide(WEditMenu *mPtr)
735 WEditMenuItem *item;
736 int i = 0;
738 if (WMWidgetIsMapped(mPtr)) {
739 WMUnmapWidget(mPtr);
740 mPtr->flags.wasMapped = 1;
741 } else {
742 mPtr->flags.wasMapped = 0;
744 while ((item = WGetEditMenuItem(mPtr, i++))) {
745 WEditMenu *submenu;
747 submenu = WGetEditMenuSubmenu(mPtr, item);
748 if (submenu) {
749 WEditMenuHide(submenu);
756 void
757 WEditMenuUnhide(WEditMenu *mPtr)
759 WEditMenuItem *item;
760 int i = 0;
762 if (mPtr->flags.wasMapped) {
763 WMMapWidget(mPtr);
765 while ((item = WGetEditMenuItem(mPtr, i++))) {
766 WEditMenu *submenu;
768 submenu = WGetEditMenuSubmenu(mPtr, item);
769 if (submenu) {
770 WEditMenuUnhide(submenu);
776 void
777 WEditMenuShowAt(WEditMenu *menu, int x, int y)
779 XSizeHints *hints;
781 hints = XAllocSizeHints();
783 hints->flags = USPosition;
784 hints->x = x;
785 hints->y = y;
787 WMMoveWidget(menu, x, y);
788 XSetWMNormalHints(W_VIEW_DISPLAY(menu->view),
789 W_VIEW_DRAWABLE(menu->view),
790 hints);
791 XFree(hints);
792 WMMapWidget(menu);
796 static void
797 updateMenuContents(WEditMenu *mPtr)
799 int newW, newH;
800 int w;
801 int i;
802 int iheight = mPtr->itemHeight;
803 int offs = 1;
804 WMBagIterator iter;
805 WEditMenuItem *item;
807 newW = 0;
808 newH = offs;
810 i = 0;
811 WM_ITERATE_BAG(mPtr->items, item, iter) {
812 w = getItemTextWidth(item);
814 newW = WMAX(w, newW);
816 WMMoveWidget(item, offs, newH);
817 if (i == 0 && mPtr->flags.isTitled) {
818 newH += mPtr->titleHeight;
819 } else {
820 newH += iheight;
822 i = 1;
825 newW += iheight + 10;
826 newH--;
828 if (mPtr->minSize.width)
829 newW = WMAX(newW, mPtr->minSize.width);
830 if (mPtr->maxSize.width)
831 newW = WMIN(newW, mPtr->maxSize.width);
833 if (mPtr->minSize.height)
834 newH = WMAX(newH, mPtr->minSize.height);
835 if (mPtr->maxSize.height)
836 newH = WMIN(newH, mPtr->maxSize.height);
838 if (W_VIEW(mPtr)->size.width == newW && mPtr->view->size.height == newH+1)
839 return;
841 W_ResizeView(mPtr->view, newW, newH+1);
843 if (mPtr->closeB)
844 WMMoveWidget(mPtr->closeB, newW - 20, 3);
846 newW -= 2*offs;
848 i = 0;
849 WM_ITERATE_BAG(mPtr->items, item, iter) {
850 if (i == 0 && mPtr->flags.isTitled) {
851 WMResizeWidget(item, newW, mPtr->titleHeight);
852 } else {
853 WMResizeWidget(item, newW, iheight);
855 i = 1;
860 static void
861 deselectItem(WEditMenu *menu)
863 WEditMenu *submenu;
864 WEditMenuItem *item = menu->selectedItem;
866 highlightItem(item, False);
868 if (menu->delegate && menu->delegate->itemDeselected) {
869 (*menu->delegate->itemDeselected)(menu->delegate, menu, item);
871 submenu = item->submenu;
873 if (submenu && !WEditMenuIsTornOff(submenu)) {
874 WEditMenuHide(submenu);
877 menu->selectedItem = NULL;
881 static void
882 selectItem(WEditMenu *menu, WEditMenuItem *item)
884 if (!menu->flags.isSelectable || menu->selectedItem == item) {
885 return;
887 if (menu->selectedItem) {
888 deselectItem(menu);
891 if (menu->flags.isEditing) {
892 stopEditItem(menu, False);
895 if (item && !item->flags.isTitle) {
896 highlightItem(item, True);
898 if (item->submenu && !W_VIEW_MAPPED(item->submenu->view)) {
899 WMPoint pt;
901 pt = WGetEditMenuLocationForSubmenu(menu, item->submenu);
903 WEditMenuShowAt(item->submenu, pt.x, pt.y);
905 item->submenu->flags.isTornOff = 0;
908 WMPostNotificationName("EditMenuItemSelected", menu, NULL);
910 if (menu->delegate && menu->delegate->itemSelected) {
911 (*menu->delegate->itemSelected)(menu->delegate, menu, item);
915 menu->selectedItem = item;
919 static void
920 paintMenu(WEditMenu *mPtr)
922 W_View *view = mPtr->view;
924 W_DrawRelief(W_VIEW_SCREEN(view), W_VIEW_DRAWABLE(view), 0, 0,
925 W_VIEW_WIDTH(view), W_VIEW_HEIGHT(view), WRSimple);
929 static void
930 handleEvents(XEvent *event, void *data)
932 WEditMenu *mPtr = (WEditMenu*)data;
934 switch (event->type) {
935 case DestroyNotify:
936 destroyEditMenu(mPtr);
937 break;
939 case Expose:
940 if (event->xexpose.count == 0)
941 paintMenu(mPtr);
942 break;
949 /* -------------------------- Menu Label Editing ------------------------ */
952 static void
953 stopEditItem(WEditMenu *menu, Bool apply)
955 char *text;
957 if (apply) {
958 text = WMGetTextFieldText(menu->tfield);
960 free(menu->selectedItem->label);
961 menu->selectedItem->label = wstrdup(text);
963 updateMenuContents(menu);
965 if (menu->delegate && menu->delegate->itemEdited) {
966 (*menu->delegate->itemEdited)(menu->delegate, menu,
967 menu->selectedItem);
971 WMUnmapWidget(menu->tfield);
972 menu->flags.isEditing = 0;
976 static void
977 textEndedEditing(struct WMTextFieldDelegate *self, WMNotification *notif)
979 WEditMenu *menu = (WEditMenu*)self->data;
980 int reason;
981 int i;
982 WEditMenuItem *item;
984 if (!menu->flags.isEditing)
985 return;
987 reason = (int)WMGetNotificationClientData(notif);
989 switch (reason) {
990 case WMEscapeTextMovement:
991 stopEditItem(menu, False);
992 break;
994 case WMReturnTextMovement:
995 stopEditItem(menu, True);
996 break;
998 case WMTabTextMovement:
999 stopEditItem(menu, True);
1001 i = WMFindInBag(menu->items, simpleMatch, menu->selectedItem);
1002 item = WMGetFromBag(menu->items, i+1);
1003 if (item != NULL) {
1004 selectItem(menu, item);
1005 editItemLabel(item);
1007 break;
1009 case WMBacktabTextMovement:
1010 stopEditItem(menu, True);
1012 i = WMFindInBag(menu->items, simpleMatch, menu->selectedItem);
1013 item = WMGetFromBag(menu->items, i-1);
1014 if (item != NULL) {
1015 selectItem(menu, item);
1016 editItemLabel(item);
1018 break;
1024 static WMTextFieldDelegate textFieldDelegate = {
1025 NULL,
1026 NULL,
1027 NULL,
1028 textEndedEditing,
1029 NULL,
1030 NULL
1034 static void
1035 editItemLabel(WEditMenuItem *item)
1037 WEditMenu *mPtr = item->parent;
1038 WMTextField *tf;
1039 int i;
1041 if (!mPtr->flags.isEditable) {
1042 return;
1045 if (!mPtr->tfield) {
1046 mPtr->tfield = WMCreateTextField(mPtr);
1047 WMSetTextFieldBeveled(mPtr->tfield, False);
1048 WMRealizeWidget(mPtr->tfield);
1050 mPtr->tdelegate = wmalloc(sizeof(WMTextFieldDelegate));
1051 memcpy(mPtr->tdelegate, &textFieldDelegate,
1052 sizeof(WMTextFieldDelegate));
1054 mPtr->tdelegate->data = mPtr;
1056 WMSetTextFieldDelegate(mPtr->tfield, mPtr->tdelegate);
1058 tf = mPtr->tfield;
1060 i = WMFindInBag(mPtr->items, simpleMatch, item);
1062 WMSetTextFieldText(tf, item->label);
1063 WMSelectTextFieldRange(tf, wmkrange(0, strlen(item->label)));
1064 WMResizeWidget(tf, mPtr->view->size.width, mPtr->itemHeight);
1065 WMMoveWidget(tf, 0, item->view->pos.y);
1066 WMMapWidget(tf);
1067 WMSetFocusToWidget(tf);
1069 mPtr->flags.isEditing = 1;
1074 /* -------------------------------------------------- */
1077 static void
1078 slideWindow(Display *dpy, Window win, int srcX, int srcY, int dstX, int dstY)
1080 double x, y, dx, dy;
1081 int i;
1082 int iterations;
1084 iterations = WMIN(25, WMAX(abs(dstX-srcX), abs(dstY-srcY)));
1086 x = srcX;
1087 y = srcY;
1089 dx = (double)(dstX-srcX)/iterations;
1090 dy = (double)(dstY-srcY)/iterations;
1092 for (i = 0; i <= iterations; i++) {
1093 XMoveWindow(dpy, win, x, y);
1094 XFlush(dpy);
1096 wusleep(800);
1098 x += dx;
1099 y += dy;
1104 static int
1105 errorHandler(Display *d, XErrorEvent *ev)
1107 /* just ignore */
1108 return 0;
1112 static WEditMenu*
1113 findMenuInWindow(Display *dpy, Window toplevel, int x, int y)
1115 Window foo, bar;
1116 Window *children;
1117 unsigned nchildren;
1118 int i;
1119 WEditMenu *menu;
1120 WMView *view;
1121 int (*oldHandler)(Display *, XErrorEvent *);
1123 view = W_GetViewForXWindow(dpy, toplevel);
1124 if (view && view->self && WMWidgetClass(view->self) == EditMenuClass) {
1125 menu = (WEditMenu*)view->self;
1126 if (menu->flags.acceptsDrop) {
1127 return menu;
1131 if (!XQueryTree(dpy, toplevel, &foo, &bar,
1132 &children, &nchildren) || children == NULL) {
1133 return NULL;
1136 oldHandler = XSetErrorHandler(errorHandler);
1138 /* first window that contains the point is the one */
1139 for (i = nchildren-1; i >= 0; i--) {
1140 XWindowAttributes attr;
1142 if (XGetWindowAttributes(dpy, children[i], &attr)
1143 && attr.map_state == IsViewable
1144 && x >= attr.x && y >= attr.y
1145 && x < attr.x + attr.width && y < attr.y + attr.height) {
1146 Window tmp;
1148 tmp = children[i];
1150 menu = findMenuInWindow(dpy, tmp, x - attr.x, y - attr.y);
1151 if (menu) {
1152 XFree(children);
1153 return menu;
1158 XSetErrorHandler(oldHandler);
1160 XFree(children);
1161 return NULL;
1165 static void
1166 handleDragOver(WEditMenu *menu, WMView *view, WEditMenuItem *item,
1167 int x, int y)
1169 WMScreen *scr = W_VIEW_SCREEN(menu->view);
1170 Window blaw;
1171 int mx, my;
1172 int offs;
1174 XTranslateCoordinates(scr->display, W_VIEW_DRAWABLE(menu->view),
1175 scr->rootWin, 0, 0, &mx, &my, &blaw);
1177 offs = menu->flags.standalone ? 0 : 1;
1179 W_MoveView(view, mx + offs, y);
1180 if (view->size.width != menu->view->size.width) {
1181 W_ResizeView(view, menu->view->size.width - 2*offs,
1182 menu->itemHeight);
1183 W_ResizeView(item->view, menu->view->size.width - 2*offs,
1184 menu->itemHeight);
1189 static void
1190 handleItemDrop(WEditMenu *menu, WEditMenuItem *item, int x, int y)
1192 WMScreen *scr = W_VIEW_SCREEN(menu->view);
1193 Window blaw;
1194 int mx, my;
1195 int index;
1197 XTranslateCoordinates(scr->display, W_VIEW_DRAWABLE(menu->view),
1198 scr->rootWin, 0, 0, &mx, &my, &blaw);
1200 index = y - my;
1201 if (menu->flags.isTitled) {
1202 index -= menu->titleHeight;
1204 index = (index + menu->itemHeight/2) / menu->itemHeight;
1205 if (index < 0)
1206 index = 0;
1208 if (menu->flags.isTitled) {
1209 index++;
1212 if (index > WMGetBagItemCount(menu->items)) {
1213 WMPutInBag(menu->items, item);
1214 } else {
1215 WMInsertInBag(menu->items, index, item);
1218 W_ReparentView(item->view, menu->view, 0, index*menu->itemHeight);
1220 item->parent = menu;
1221 if (item->submenu) {
1222 item->submenu->parent = menu;
1225 updateMenuContents(menu);
1229 static void
1230 dragMenu(WEditMenu *menu)
1232 WMScreen *scr = W_VIEW_SCREEN(menu->view);
1233 XEvent ev;
1234 Bool done = False;
1235 int dx, dy;
1236 unsigned blau;
1237 Window blaw;
1239 XGetGeometry(scr->display, W_VIEW_DRAWABLE(menu->view), &blaw, &dx, &dy,
1240 &blau, &blau, &blau, &blau);
1242 XTranslateCoordinates(scr->display, W_VIEW_DRAWABLE(menu->view),
1243 scr->rootWin, dx, dy, &dx, &dy, &blaw);
1245 dx = menu->dragX - dx;
1246 dy = menu->dragY - dy;
1248 XGrabPointer(scr->display, scr->rootWin, False,
1249 ButtonReleaseMask|ButtonMotionMask,
1250 GrabModeAsync, GrabModeAsync, None, scr->defaultCursor,
1251 CurrentTime);
1253 if (menu->parent)
1254 WTearOffEditMenu(menu->parent, menu);
1256 while (!done) {
1257 WMNextEvent(scr->display, &ev);
1259 switch (ev.type) {
1260 case ButtonRelease:
1261 done = True;
1262 break;
1264 case MotionNotify:
1265 while (XCheckMaskEvent(scr->display, ButtonMotionMask, &ev)) ;
1267 WMMoveWidget(menu, ev.xmotion.x_root - dx,
1268 ev.xmotion.y_root - dy);
1269 break;
1271 default:
1272 WMHandleEvent(&ev);
1273 break;
1277 XUngrabPointer(scr->display, CurrentTime);
1282 static WEditMenuItem*
1283 duplicateItem(WEditMenuItem *item)
1285 WEditMenuItem *nitem;
1287 nitem = WCreateEditMenuItem(item->parent, item->label, False);
1288 if (item->pixmap)
1289 nitem->pixmap = WMRetainPixmap(item->pixmap);
1291 return nitem;
1297 static WEditMenu*
1298 duplicateMenu(WEditMenu *menu)
1300 WEditMenu *nmenu;
1301 WEditMenuItem *item;
1302 WMBagIterator iter;
1303 Bool first = menu->flags.isTitled;
1305 nmenu = WCreateEditMenu(WMWidgetScreen(menu), WGetEditMenuTitle(menu));
1307 memcpy(&nmenu->flags, &menu->flags, sizeof(menu->flags));
1308 nmenu->delegate = menu->delegate;
1310 WM_ITERATE_BAG(menu->items, item, iter) {
1311 WEditMenuItem *nitem;
1313 if (first) {
1314 first = False;
1315 continue;
1318 nitem = WAddMenuItemWithTitle(nmenu, item->label);
1319 if (item->pixmap)
1320 WSetEditMenuItemImage(nitem, item->pixmap);
1322 if (menu->delegate && menu->delegate->itemCloned) {
1323 (*menu->delegate->itemCloned)(menu->delegate, menu,
1324 item, nitem);
1328 WMRealizeWidget(nmenu);
1330 return nmenu;
1335 static void
1336 dragItem(WEditMenu *menu, WEditMenuItem *item)
1338 static XColor black = {0, 0,0,0, DoRed|DoGreen|DoBlue};
1339 static XColor green = {0x0045b045, 0x4500,0xb000,0x4500, DoRed|DoGreen|DoBlue};
1340 static XColor back = {0, 0xffff,0xffff,0xffff, DoRed|DoGreen|DoBlue};
1341 Display *dpy = W_VIEW_DISPLAY(menu->view);
1342 WMScreen *scr = W_VIEW_SCREEN(menu->view);
1343 int x, y;
1344 int dx, dy;
1345 Bool done = False;
1346 Window blaw;
1347 int blai;
1348 unsigned blau;
1349 Window win;
1350 WMView *dview;
1351 int orix, oriy;
1352 Bool enteredMenu = False;
1353 WMSize oldSize = item->view->size;
1354 WEditMenuItem *dritem = item;
1355 WEditMenu *dmenu = NULL;
1358 if (item->flags.isTitle) {
1359 WMRaiseWidget(menu);
1361 dragMenu(menu);
1363 return;
1366 selectItem(menu, NULL);
1368 win = scr->rootWin;
1370 XTranslateCoordinates(dpy, W_VIEW_DRAWABLE(item->view), win,
1371 0, 0, &orix, &oriy, &blaw);
1373 dview = W_CreateUnmanagedTopView(scr);
1374 W_SetViewBackgroundColor(dview, scr->black);
1375 W_ResizeView(dview, W_VIEW_WIDTH(item->view), W_VIEW_HEIGHT(item->view));
1376 W_MoveView(dview, orix, oriy);
1377 W_RealizeView(dview);
1379 if (menu->flags.isFactory) {
1380 dritem = duplicateItem(item);
1382 W_ReparentView(dritem->view, dview, 0, 0);
1383 WMResizeWidget(dritem, oldSize.width, oldSize.height);
1384 WMRealizeWidget(dritem);
1385 WMMapWidget(dritem);
1386 } else {
1387 W_ReparentView(item->view, dview, 0, 0);
1390 W_MapView(dview);
1392 dx = menu->dragX - orix;
1393 dy = menu->dragY - oriy;
1395 XGrabPointer(dpy, scr->rootWin, False,
1396 ButtonPressMask|ButtonReleaseMask|ButtonMotionMask,
1397 GrabModeAsync, GrabModeAsync, None, scr->defaultCursor,
1398 CurrentTime);
1400 if (menu->flags.acceptsDrop)
1401 XRecolorCursor(dpy, scr->defaultCursor, &green, &back);
1403 while (!done) {
1404 XEvent ev;
1406 WMNextEvent(dpy, &ev);
1408 switch (ev.type) {
1409 case MotionNotify:
1410 while (XCheckMaskEvent(dpy, ButtonMotionMask, &ev)) ;
1412 XQueryPointer(dpy, win, &blaw, &blaw, &blai, &blai, &x, &y, &blau);
1414 dmenu = findMenuInWindow(dpy, win, x, y);
1416 if (dmenu) {
1417 handleDragOver(dmenu, dview, dritem, x - dx, y - dy);
1418 if (!enteredMenu) {
1419 enteredMenu = True;
1420 XRecolorCursor(dpy, scr->defaultCursor, &green, &back);
1422 } else {
1423 if (enteredMenu) {
1424 W_ResizeView(dview, oldSize.width, oldSize.height);
1425 W_ResizeView(dritem->view, oldSize.width, oldSize.height);
1426 enteredMenu = False;
1427 XRecolorCursor(dpy, scr->defaultCursor, &black, &back);
1429 W_MoveView(dview, x - dx, y - dy);
1432 break;
1434 case ButtonRelease:
1435 done = True;
1436 break;
1438 default:
1439 WMHandleEvent(&ev);
1440 break;
1443 XRecolorCursor(dpy, scr->defaultCursor, &black, &back);
1445 XUngrabPointer(dpy, CurrentTime);
1448 if (!enteredMenu) {
1449 Bool rem = True;
1451 if (!menu->flags.isFactory) {
1452 W_UnmapView(dview);
1453 if (menu->delegate && menu->delegate->shouldRemoveItem) {
1454 rem = (*menu->delegate->shouldRemoveItem)(menu->delegate,
1455 menu, item);
1457 W_MapView(dview);
1460 if (!rem || menu->flags.isFactory) {
1461 slideWindow(dpy, W_VIEW_DRAWABLE(dview), x-dx, y-dy, orix, oriy);
1463 if (!menu->flags.isFactory) {
1464 WRemoveEditMenuItem(menu, dritem);
1465 handleItemDrop(dmenu ? dmenu : menu, dritem, orix, oriy);
1467 } else {
1468 WRemoveEditMenuItem(menu, dritem);
1470 } else {
1471 WRemoveEditMenuItem(menu, dritem);
1473 if (menu->delegate && menu->delegate->itemCloned
1474 && menu->flags.isFactory) {
1475 (*menu->delegate->itemCloned)(menu->delegate, menu,
1476 item, dritem);
1479 handleItemDrop(dmenu, dritem, x-dy, y-dy);
1481 if (item->submenu && menu->flags.isFactory) {
1482 WEditMenu *submenu;
1484 submenu = duplicateMenu(item->submenu);
1485 WSetEditMenuSubmenu(dmenu, dritem, submenu);
1489 /* can't destroy now because we're being called from
1490 * the event handler of the item. and destroying now,
1491 * would mean destroying the item too in some cases.
1493 WMAddIdleHandler((WMCallback*)W_DestroyView, dview);
1498 static void
1499 handleItemClick(XEvent *event, void *data)
1501 WEditMenuItem *item = (WEditMenuItem*)data;
1502 WEditMenu *menu = item->parent;
1504 switch (event->type) {
1505 case ButtonPress:
1506 selectItem(menu, item);
1508 if (WMIsDoubleClick(event)) {
1509 editItemLabel(item);
1512 menu->flags.isDragging = 1;
1513 menu->dragX = event->xbutton.x_root;
1514 menu->dragY = event->xbutton.y_root;
1515 break;
1517 case ButtonRelease:
1518 menu->flags.isDragging = 0;
1519 break;
1521 case MotionNotify:
1522 if (menu->flags.isDragging) {
1523 if (abs(event->xbutton.x_root - menu->dragX) > 5
1524 || abs(event->xbutton.y_root - menu->dragY) > 5) {
1525 menu->flags.isDragging = 0;
1526 dragItem(menu, item);
1529 break;
1534 static void
1535 destroyEditMenu(WEditMenu *mPtr)
1537 WEditMenuItem *item;
1538 WMBagIterator iter;
1540 WMRemoveNotificationObserver(mPtr);
1542 WM_ITERATE_BAG(mPtr->items, item, iter) {
1543 if (item->submenu) {
1544 WMDestroyWidget(item->submenu);
1548 WMFreeBag(mPtr->items);
1550 free(mPtr->tdelegate);
1552 free(mPtr);