- Fixed WMArray.
[wmaker-crm.git] / WPrefs.app / editmenu.c
blobc8e4f4ab5d2a2d1113a0d95a1bd3f28da9ac47b7
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*
168 WGetEditMenuItemTitle(WEditMenuItem *item)
170 return item->label;
173 void*
174 WGetEditMenuItemData(WEditMenuItem *item)
176 return item->data;
180 void
181 WSetEditMenuItemData(WEditMenuItem *item, void *data, WMCallback *destroyer)
183 item->data = data;
184 item->destroyData = destroyer;
188 void
189 WSetEditMenuItemImage(WEditMenuItem *item, WMPixmap *pixmap)
191 if (item->pixmap)
192 WMReleasePixmap(item->pixmap);
193 item->pixmap = WMRetainPixmap(pixmap);
197 static void
198 paintEditMenuItem(WEditMenuItem *iPtr)
200 WMScreen *scr = WMWidgetScreen(iPtr);
201 WMColor *color;
202 Window win = W_VIEW(iPtr)->window;
203 int w = W_VIEW(iPtr)->size.width;
204 int h = W_VIEW(iPtr)->size.height;
205 WMFont *font = (iPtr->flags.isTitle ? scr->boldFont : scr->normalFont);
207 if (!iPtr->view->flags.realized)
208 return;
210 color = scr->black;
211 if (iPtr->flags.isTitle && !iPtr->flags.isHighlighted) {
212 color = scr->white;
216 XClearWindow(scr->display, win);
218 W_DrawRelief(scr, win, 0, 0, w+1, h, WRRaised);
220 WMDrawString(scr, win, WMColorGC(color), font, 5, 3, iPtr->label,
221 strlen(iPtr->label));
223 if (iPtr->pixmap) {
224 WMSize size = WMGetPixmapSize(iPtr->pixmap);
226 WMDrawPixmap(iPtr->pixmap, win, w - size.width - 5,
227 (h - size.height)/2);
228 } else if (iPtr->submenu) {
229 /* draw the cascade indicator */
230 XDrawLine(scr->display,win,WMColorGC(scr->darkGray),
231 w-11, 6, w-6, h/2-1);
232 XDrawLine(scr->display,win,WMColorGC(scr->white),
233 w-11, h-8, w-6, h/2-1);
234 XDrawLine(scr->display,win,WMColorGC(scr->black),
235 w-12, 6, w-12, h-8);
240 static void
241 highlightItem(WEditMenuItem *iPtr, Bool high)
243 if (iPtr->flags.isTitle)
244 return;
246 iPtr->flags.isHighlighted = high;
248 if (high) {
249 WMSetWidgetBackgroundColor(iPtr, WMWhiteColor(WMWidgetScreen(iPtr)));
250 } else {
251 if (!iPtr->flags.isTitle) {
252 WMSetWidgetBackgroundColor(iPtr,
253 WMGrayColor(WMWidgetScreen(iPtr)));
254 } else {
255 WMSetWidgetBackgroundColor(iPtr,
256 WMBlackColor(WMWidgetScreen(iPtr)));
262 static int
263 getItemTextWidth(WEditMenuItem *iPtr)
265 WMScreen *scr = WMWidgetScreen(iPtr);
267 if (iPtr->flags.isTitle) {
268 return WMWidthOfString(scr->boldFont, iPtr->label,
269 strlen(iPtr->label));
270 } else {
271 return WMWidthOfString(scr->normalFont, iPtr->label,
272 strlen(iPtr->label));
278 static void
279 handleItemEvents(XEvent *event, void *data)
281 WEditMenuItem *iPtr = (WEditMenuItem*)data;
283 switch (event->type) {
284 case Expose:
285 if (event->xexpose.count!=0)
286 break;
287 paintEditMenuItem(iPtr);
288 break;
290 case DestroyNotify:
291 destroyEditMenuItem(iPtr);
292 break;
297 static void
298 destroyEditMenuItem(WEditMenuItem *iPtr)
300 if (iPtr->label)
301 free(iPtr->label);
302 if (iPtr->data && iPtr->destroyData)
303 (*iPtr->destroyData)(iPtr->data);
304 if (iPtr->submenu)
305 WMDestroyWidget(iPtr->submenu);
307 free(iPtr);
312 /******************** WEditMenu *******************/
314 static void destroyEditMenu(WEditMenu *mPtr);
316 static void updateMenuContents(WEditMenu *mPtr);
318 static void handleEvents(XEvent *event, void *data);
320 static void editItemLabel(WEditMenuItem *item);
322 static void stopEditItem(WEditMenu *menu, Bool apply);
324 static void deselectItem(WEditMenu *menu);
327 static W_Class EditMenuClass = 0;
330 W_Class
331 InitEditMenu(WMScreen *scr)
333 /* register our widget with WINGs and get our widget class ID */
334 if (!EditMenuClass) {
336 EditMenuClass = W_RegisterUserWidget();
339 return EditMenuClass;
344 typedef struct {
345 int flags;
346 int window_style;
347 int window_level;
348 int reserved;
349 Pixmap miniaturize_pixmap; /* pixmap for miniaturize button */
350 Pixmap close_pixmap; /* pixmap for close button */
351 Pixmap miniaturize_mask; /* miniaturize pixmap mask */
352 Pixmap close_mask; /* close pixmap mask */
353 int extra_flags;
354 } GNUstepWMAttributes;
357 #define GSWindowStyleAttr (1<<0)
358 #define GSWindowLevelAttr (1<<1)
361 static void
362 writeGNUstepWMAttr(WMScreen *scr, Window window, GNUstepWMAttributes *attr)
364 unsigned long data[9];
366 /* handle idiot compilers where array of CARD32 != struct of CARD32 */
367 data[0] = attr->flags;
368 data[1] = attr->window_style;
369 data[2] = attr->window_level;
370 data[3] = 0; /* reserved */
371 /* The X protocol says XIDs are 32bit */
372 data[4] = attr->miniaturize_pixmap;
373 data[5] = attr->close_pixmap;
374 data[6] = attr->miniaturize_mask;
375 data[7] = attr->close_mask;
376 data[8] = attr->extra_flags;
377 XChangeProperty(scr->display, window, scr->attribsAtom, scr->attribsAtom,
378 32, PropModeReplace, (unsigned char *)data, 9);
382 static void
383 realizeObserver(void *self, WMNotification *not)
385 WEditMenu *menu = (WEditMenu*)self;
386 GNUstepWMAttributes attribs;
388 memset(&attribs, 0, sizeof(GNUstepWMAttributes));
389 attribs.flags = GSWindowStyleAttr|GSWindowLevelAttr;
390 attribs.window_style = WMBorderlessWindowMask;
391 attribs.window_level = WMSubmenuWindowLevel;
393 writeGNUstepWMAttr(WMWidgetScreen(menu), menu->view->window, &attribs);
397 static void
398 itemSelectObserver(void *self, WMNotification *notif)
400 WEditMenu *menu = (WEditMenu*)self;
401 WEditMenu *rmenu;
403 rmenu = (WEditMenu*)WMGetNotificationObject(notif);
404 /* check whether rmenu is from the same hierarchy of menu? */
406 if (rmenu == menu) {
407 return;
410 if (menu->selectedItem) {
411 if (!menu->selectedItem->submenu)
412 deselectItem(menu);
413 if (menu->flags.isEditing)
414 stopEditItem(menu, False);
419 static WEditMenu*
420 makeEditMenu(WMScreen *scr, WMWidget *parent, char *title)
422 WEditMenu *mPtr;
423 WEditMenuItem *item;
425 if (!EditMenuClass)
426 InitEditMenu(scr);
429 mPtr = wmalloc(sizeof(WEditMenu));
430 memset(mPtr, 0, sizeof(WEditMenu));
432 mPtr->widgetClass = EditMenuClass;
434 if (parent) {
435 mPtr->view = W_CreateView(W_VIEW(parent));
436 mPtr->flags.standalone = 0;
437 } else {
438 mPtr->view = W_CreateTopView(scr);
439 mPtr->flags.standalone = 1;
441 if (!mPtr->view) {
442 free(mPtr);
443 return NULL;
445 mPtr->view->self = mPtr;
447 mPtr->flags.isSelectable = 1;
448 mPtr->flags.isEditable = 1;
450 W_SetViewBackgroundColor(mPtr->view, scr->darkGray);
452 WMAddNotificationObserver(realizeObserver, mPtr,
453 WMViewRealizedNotification, mPtr->view);
455 WMAddNotificationObserver(itemSelectObserver, mPtr,
456 "EditMenuItemSelected", NULL);
458 mPtr->items = WMCreateBag(4);
460 WMCreateEventHandler(mPtr->view, ExposureMask|StructureNotifyMask,
461 handleEvents, mPtr);
464 if (title != NULL) {
465 item = WCreateEditMenuItem(mPtr, title, True);
467 WMMapWidget(item);
468 WMPutInBag(mPtr->items, item);
470 mPtr->flags.isTitled = 1;
473 mPtr->itemHeight = WMFontHeight(scr->normalFont) + 6;
474 mPtr->titleHeight = WMFontHeight(scr->boldFont) + 8;
476 updateMenuContents(mPtr);
478 return mPtr;
482 WEditMenu*
483 WCreateEditMenu(WMScreen *scr, char *title)
485 return makeEditMenu(scr, NULL, title);
489 WEditMenu*
490 WCreateEditMenuPad(WMWidget *parent)
492 return makeEditMenu(WMWidgetScreen(parent), parent, NULL);
496 void
497 WSetEditMenuDelegate(WEditMenu *mPtr, WEditMenuDelegate *delegate)
499 mPtr->delegate = delegate;
503 WEditMenuItem*
504 WInsertMenuItemWithTitle(WEditMenu *mPtr, int index, char *title)
506 WEditMenuItem *item;
508 item = WCreateEditMenuItem(mPtr, title, False);
510 WMMapWidget(item);
512 if (index >= WMGetBagItemCount(mPtr->items)) {
513 WMPutInBag(mPtr->items, item);
514 } else {
515 if (index < 0)
516 index = 0;
517 if (mPtr->flags.isTitled)
518 index++;
519 WMInsertInBag(mPtr->items, index, item);
522 updateMenuContents(mPtr);
524 return item;
528 WEditMenuItem*
529 WGetEditMenuItem(WEditMenu *mPtr, int index)
531 if (index >= WMGetBagItemCount(mPtr->items))
532 return NULL;
533 else
534 return WMGetFromBag(mPtr->items, index + (mPtr->flags.isTitled ? 1 : 0));
538 WEditMenuItem*
539 WAddMenuItemWithTitle(WEditMenu *mPtr, char *title)
541 return WInsertMenuItemWithTitle(mPtr, WMGetBagItemCount(mPtr->items),
542 title);
547 void
548 WSetEditMenuTitle(WEditMenu *mPtr, char *title)
550 WEditMenuItem *item;
552 item = WMGetFromBag(mPtr->items, 0);
554 free(item->label);
555 item->label = wstrdup(title);
557 updateMenuContents(mPtr);
559 WMRedisplayWidget(item);
564 char*
565 WGetEditMenuTitle(WEditMenu *mPtr)
567 WEditMenuItem *item;
569 item = WMGetFromBag(mPtr->items, 0);
571 return item->label;
575 void
576 WSetEditMenuAcceptsDrop(WEditMenu *mPtr, Bool flag)
578 mPtr->flags.acceptsDrop = flag;
582 void
583 WSetEditMenuSubmenu(WEditMenu *mPtr, WEditMenuItem *item, WEditMenu *submenu)
585 item->submenu = submenu;
586 submenu->parent = mPtr;
588 paintEditMenuItem(item);
592 WEditMenu*
593 WGetEditMenuSubmenu(WEditMenu *mPtr, WEditMenuItem *item)
595 return item->submenu;
599 static int
600 simpleMatch(void *a, void *b)
602 return ((a == b) ? 1 : 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 WEditMenuHide(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 void
794 WEditMenuHide(WEditMenu *menu)
796 WMUnmapWidget(menu);
798 if (menu->selectedItem) {
799 deselectItem(menu);
804 void
805 WEditMenuUnhide(WEditMenu *menu)
807 WMMapWidget(menu);
811 void
812 WEditMenuShowAt(WEditMenu *menu, int x, int y)
814 WMMoveWidget(menu, x, y);
815 WMMapWidget(menu);
819 static void
820 deselectItem(WEditMenu *menu)
822 WEditMenu *submenu;
823 WEditMenuItem *item = menu->selectedItem;
825 highlightItem(item, False);
827 if (menu->delegate && menu->delegate->itemDeselected) {
828 (*menu->delegate->itemDeselected)(menu->delegate, menu, item);
830 submenu = item->submenu;
832 if (submenu && !WEditMenuIsTornOff(submenu)) {
833 WEditMenuHide(submenu);
836 menu->selectedItem = NULL;
840 static void
841 selectItem(WEditMenu *menu, WEditMenuItem *item)
843 if (!menu->flags.isSelectable || menu->selectedItem == item) {
844 return;
846 if (menu->selectedItem) {
847 deselectItem(menu);
850 if (menu->flags.isEditing) {
851 stopEditItem(menu, False);
854 if (item && !item->flags.isTitle) {
855 highlightItem(item, True);
857 if (item->submenu && !W_VIEW_MAPPED(item->submenu->view)) {
858 WMPoint pt;
859 XSizeHints *hints;
861 hints = XAllocSizeHints();
863 pt = WGetEditMenuLocationForSubmenu(menu, item->submenu);
865 hints->flags = USPosition;
866 hints->x = pt.x;
867 hints->y = pt.y;
869 WMMoveWidget(item->submenu, pt.x, pt.y);
870 XSetWMNormalHints(W_VIEW_DISPLAY(item->submenu->view),
871 W_VIEW_DRAWABLE(item->submenu->view),
872 hints);
873 XFree(hints);
874 WMMapWidget(item->submenu);
876 item->submenu->flags.isTornOff = 0;
879 WMPostNotificationName("EditMenuItemSelected", menu, NULL);
881 if (menu->delegate && menu->delegate->itemSelected) {
882 (*menu->delegate->itemSelected)(menu->delegate, menu, item);
886 menu->selectedItem = item;
890 static void
891 paintMenu(WEditMenu *mPtr)
893 W_View *view = mPtr->view;
895 W_DrawRelief(W_VIEW_SCREEN(view), W_VIEW_DRAWABLE(view), 0, 0,
896 W_VIEW_WIDTH(view), W_VIEW_HEIGHT(view), WRSimple);
900 static void
901 handleEvents(XEvent *event, void *data)
903 WEditMenu *mPtr = (WEditMenu*)data;
905 switch (event->type) {
906 case DestroyNotify:
907 destroyEditMenu(mPtr);
908 break;
910 case Expose:
911 if (event->xexpose.count == 0)
912 paintMenu(mPtr);
913 break;
920 /* -------------------------- Menu Label Editing ------------------------ */
923 static void
924 stopEditItem(WEditMenu *menu, Bool apply)
926 char *text;
928 if (apply) {
929 text = WMGetTextFieldText(menu->tfield);
931 free(menu->selectedItem->label);
932 menu->selectedItem->label = wstrdup(text);
934 updateMenuContents(menu);
936 if (menu->delegate && menu->delegate->itemEdited) {
937 (*menu->delegate->itemEdited)(menu->delegate, menu,
938 menu->selectedItem);
942 WMUnmapWidget(menu->tfield);
943 menu->flags.isEditing = 0;
947 static void
948 textEndedEditing(struct WMTextFieldDelegate *self, WMNotification *notif)
950 WEditMenu *menu = (WEditMenu*)self->data;
951 int reason;
952 int i;
953 WEditMenuItem *item;
955 if (!menu->flags.isEditing)
956 return;
958 reason = (int)WMGetNotificationClientData(notif);
960 switch (reason) {
961 case WMEscapeTextMovement:
962 stopEditItem(menu, False);
963 break;
965 case WMReturnTextMovement:
966 stopEditItem(menu, True);
967 break;
969 case WMTabTextMovement:
970 stopEditItem(menu, True);
972 i = WMFindInBag(menu->items, simpleMatch, menu->selectedItem);
973 item = WMGetFromBag(menu->items, i+1);
974 if (item != NULL) {
975 selectItem(menu, item);
976 editItemLabel(item);
978 break;
980 case WMBacktabTextMovement:
981 stopEditItem(menu, True);
983 i = WMFindInBag(menu->items, simpleMatch, menu->selectedItem);
984 item = WMGetFromBag(menu->items, i-1);
985 if (item != NULL) {
986 selectItem(menu, item);
987 editItemLabel(item);
989 break;
995 static WMTextFieldDelegate textFieldDelegate = {
996 NULL,
997 NULL,
998 NULL,
999 textEndedEditing,
1000 NULL,
1001 NULL
1005 static void
1006 editItemLabel(WEditMenuItem *item)
1008 WEditMenu *mPtr = item->parent;
1009 WMTextField *tf;
1010 int i;
1012 if (!mPtr->flags.isEditable) {
1013 return;
1016 if (!mPtr->tfield) {
1017 mPtr->tfield = WMCreateTextField(mPtr);
1018 WMSetTextFieldBeveled(mPtr->tfield, False);
1019 WMRealizeWidget(mPtr->tfield);
1021 mPtr->tdelegate = wmalloc(sizeof(WMTextFieldDelegate));
1022 memcpy(mPtr->tdelegate, &textFieldDelegate,
1023 sizeof(WMTextFieldDelegate));
1025 mPtr->tdelegate->data = mPtr;
1027 WMSetTextFieldDelegate(mPtr->tfield, mPtr->tdelegate);
1029 tf = mPtr->tfield;
1031 i = WMFindInBag(mPtr->items, simpleMatch, item);
1033 WMSetTextFieldText(tf, item->label);
1034 WMSelectTextFieldRange(tf, wmkrange(0, strlen(item->label)));
1035 WMResizeWidget(tf, mPtr->view->size.width, mPtr->itemHeight);
1036 WMMoveWidget(tf, 0, item->view->pos.y);
1037 WMMapWidget(tf);
1038 WMSetFocusToWidget(tf);
1040 mPtr->flags.isEditing = 1;
1045 /* -------------------------------------------------- */
1048 static void
1049 slideWindow(Display *dpy, Window win, int srcX, int srcY, int dstX, int dstY)
1051 double x, y, dx, dy;
1052 int i;
1053 int iterations;
1055 iterations = WMIN(25, WMAX(abs(dstX-srcX), abs(dstY-srcY)));
1057 x = srcX;
1058 y = srcY;
1060 dx = (double)(dstX-srcX)/iterations;
1061 dy = (double)(dstY-srcY)/iterations;
1063 for (i = 0; i <= iterations; i++) {
1064 XMoveWindow(dpy, win, x, y);
1065 XFlush(dpy);
1067 wusleep(800);
1069 x += dx;
1070 y += dy;
1075 static int
1076 errorHandler(Display *d, XErrorEvent *ev)
1078 /* just ignore */
1079 return 0;
1083 static WEditMenu*
1084 findMenuInWindow(Display *dpy, Window toplevel, int x, int y)
1086 Window foo, bar;
1087 Window *children;
1088 unsigned nchildren;
1089 int i;
1090 WEditMenu *menu;
1091 WMView *view;
1092 int (*oldHandler)(Display *, XErrorEvent *);
1094 view = W_GetViewForXWindow(dpy, toplevel);
1095 if (view && view->self && WMWidgetClass(view->self) == EditMenuClass) {
1096 menu = (WEditMenu*)view->self;
1097 if (menu->flags.acceptsDrop) {
1098 return menu;
1102 if (!XQueryTree(dpy, toplevel, &foo, &bar,
1103 &children, &nchildren) || children == NULL) {
1104 return NULL;
1107 oldHandler = XSetErrorHandler(errorHandler);
1109 /* first window that contains the point is the one */
1110 for (i = nchildren-1; i >= 0; i--) {
1111 XWindowAttributes attr;
1113 if (XGetWindowAttributes(dpy, children[i], &attr)
1114 && attr.map_state == IsViewable
1115 && x >= attr.x && y >= attr.y
1116 && x < attr.x + attr.width && y < attr.y + attr.height) {
1117 Window tmp;
1119 tmp = children[i];
1121 menu = findMenuInWindow(dpy, tmp, x - attr.x, y - attr.y);
1122 if (menu) {
1123 XFree(children);
1124 return menu;
1129 XSetErrorHandler(oldHandler);
1131 XFree(children);
1132 return NULL;
1136 static void
1137 handleDragOver(WEditMenu *menu, WMView *view, WEditMenuItem *item,
1138 int x, int y)
1140 WMScreen *scr = W_VIEW_SCREEN(menu->view);
1141 Window blaw;
1142 int mx, my;
1143 int offs;
1145 XTranslateCoordinates(scr->display, W_VIEW_DRAWABLE(menu->view),
1146 scr->rootWin, 0, 0, &mx, &my, &blaw);
1148 offs = menu->flags.standalone ? 0 : 1;
1150 W_MoveView(view, mx + offs, y);
1151 if (view->size.width != menu->view->size.width) {
1152 W_ResizeView(view, menu->view->size.width - 2*offs,
1153 menu->itemHeight);
1154 W_ResizeView(item->view, menu->view->size.width - 2*offs,
1155 menu->itemHeight);
1160 static void
1161 handleItemDrop(WEditMenu *menu, WEditMenuItem *item, int x, int y)
1163 WMScreen *scr = W_VIEW_SCREEN(menu->view);
1164 Window blaw;
1165 int mx, my;
1166 int index;
1168 XTranslateCoordinates(scr->display, W_VIEW_DRAWABLE(menu->view),
1169 scr->rootWin, 0, 0, &mx, &my, &blaw);
1171 index = y - my;
1172 if (menu->flags.isTitled) {
1173 index -= menu->titleHeight;
1175 index = (index + menu->itemHeight/2) / menu->itemHeight;
1176 if (index < 0)
1177 index = 0;
1179 if (menu->flags.isTitled) {
1180 index++;
1183 if (index > WMGetBagItemCount(menu->items)) {
1184 WMPutInBag(menu->items, item);
1185 } else {
1186 WMInsertInBag(menu->items, index, item);
1189 W_ReparentView(item->view, menu->view, 0, index*menu->itemHeight);
1191 item->parent = menu;
1192 if (item->submenu) {
1193 item->submenu->parent = menu;
1196 updateMenuContents(menu);
1200 static void
1201 dragMenu(WEditMenu *menu)
1203 WMScreen *scr = W_VIEW_SCREEN(menu->view);
1204 XEvent ev;
1205 Bool done = False;
1206 int dx, dy;
1207 unsigned blau;
1208 Window blaw;
1210 XGetGeometry(scr->display, W_VIEW_DRAWABLE(menu->view), &blaw, &dx, &dy,
1211 &blau, &blau, &blau, &blau);
1213 XTranslateCoordinates(scr->display, W_VIEW_DRAWABLE(menu->view),
1214 scr->rootWin, dx, dy, &dx, &dy, &blaw);
1216 dx = menu->dragX - dx;
1217 dy = menu->dragY - dy;
1219 XGrabPointer(scr->display, scr->rootWin, False,
1220 ButtonReleaseMask|ButtonMotionMask,
1221 GrabModeAsync, GrabModeAsync, None, scr->defaultCursor,
1222 CurrentTime);
1224 if (menu->parent)
1225 WTearOffEditMenu(menu->parent, menu);
1227 while (!done) {
1228 WMNextEvent(scr->display, &ev);
1230 switch (ev.type) {
1231 case ButtonRelease:
1232 done = True;
1233 break;
1235 case MotionNotify:
1236 while (XCheckMaskEvent(scr->display, ButtonMotionMask, &ev)) ;
1238 WMMoveWidget(menu, ev.xmotion.x_root - dx,
1239 ev.xmotion.y_root - dy);
1240 break;
1242 default:
1243 WMHandleEvent(&ev);
1244 break;
1248 XUngrabPointer(scr->display, CurrentTime);
1253 static WEditMenuItem*
1254 duplicateItem(WEditMenuItem *item)
1256 WEditMenuItem *nitem;
1258 nitem = WCreateEditMenuItem(item->parent, item->label, False);
1259 if (item->pixmap)
1260 nitem->pixmap = WMRetainPixmap(item->pixmap);
1262 return nitem;
1268 static WEditMenu*
1269 duplicateMenu(WEditMenu *menu)
1271 WEditMenu *nmenu;
1272 WEditMenuItem *item;
1273 WMBagIterator iter;
1274 Bool first = menu->flags.isTitled;
1276 nmenu = WCreateEditMenu(WMWidgetScreen(menu), WGetEditMenuTitle(menu));
1278 memcpy(&nmenu->flags, &menu->flags, sizeof(menu->flags));
1279 nmenu->delegate = menu->delegate;
1281 WM_ITERATE_BAG(menu->items, item, iter) {
1282 WEditMenuItem *nitem;
1284 if (first) {
1285 first = False;
1286 continue;
1289 nitem = WAddMenuItemWithTitle(nmenu, item->label);
1290 if (item->pixmap)
1291 WSetEditMenuItemImage(nitem, item->pixmap);
1293 if (menu->delegate && menu->delegate->itemCloned) {
1294 (*menu->delegate->itemCloned)(menu->delegate, menu,
1295 item, nitem);
1299 WMRealizeWidget(nmenu);
1301 return nmenu;
1306 static void
1307 dragItem(WEditMenu *menu, WEditMenuItem *item)
1309 Display *dpy = W_VIEW_DISPLAY(menu->view);
1310 WMScreen *scr = W_VIEW_SCREEN(menu->view);
1311 int x, y;
1312 int dx, dy;
1313 Bool done = False;
1314 Window blaw;
1315 int blai;
1316 unsigned blau;
1317 Window win;
1318 WMView *dview;
1319 int orix, oriy;
1320 Bool enteredMenu = False;
1321 WMSize oldSize = item->view->size;
1322 WEditMenuItem *dritem = item;
1323 WEditMenu *dmenu = NULL;
1326 if (item->flags.isTitle) {
1327 WMRaiseWidget(menu);
1329 dragMenu(menu);
1331 return;
1335 selectItem(menu, NULL);
1337 win = scr->rootWin;
1339 XTranslateCoordinates(dpy, W_VIEW_DRAWABLE(item->view), win,
1340 0, 0, &orix, &oriy, &blaw);
1342 dview = W_CreateUnmanagedTopView(scr);
1343 W_SetViewBackgroundColor(dview, scr->black);
1344 W_ResizeView(dview, W_VIEW_WIDTH(item->view), W_VIEW_HEIGHT(item->view));
1345 W_MoveView(dview, orix, oriy);
1346 W_RealizeView(dview);
1348 if (menu->flags.isFactory) {
1349 dritem = duplicateItem(item);
1351 W_ReparentView(dritem->view, dview, 0, 0);
1352 WMResizeWidget(dritem, oldSize.width, oldSize.height);
1353 WMRealizeWidget(dritem);
1354 WMMapWidget(dritem);
1355 } else {
1356 W_ReparentView(item->view, dview, 0, 0);
1359 W_MapView(dview);
1361 dx = menu->dragX - orix;
1362 dy = menu->dragY - oriy;
1364 XGrabPointer(dpy, scr->rootWin, False,
1365 ButtonPressMask|ButtonReleaseMask|ButtonMotionMask,
1366 GrabModeAsync, GrabModeAsync, None, scr->defaultCursor,
1367 CurrentTime);
1369 while (!done) {
1370 XEvent ev;
1372 WMNextEvent(dpy, &ev);
1374 switch (ev.type) {
1375 case MotionNotify:
1376 while (XCheckMaskEvent(dpy, ButtonMotionMask, &ev)) ;
1378 XQueryPointer(dpy, win, &blaw, &blaw, &blai, &blai, &x, &y, &blau);
1380 dmenu = findMenuInWindow(dpy, win, x, y);
1382 if (dmenu) {
1383 handleDragOver(dmenu, dview, dritem, x - dx, y - dy);
1384 enteredMenu = True;
1385 } else {
1386 if (enteredMenu) {
1387 W_ResizeView(dview, oldSize.width, oldSize.height);
1388 W_ResizeView(dritem->view, oldSize.width, oldSize.height);
1389 enteredMenu = False;
1391 W_MoveView(dview, x - dx, y - dy);
1394 break;
1396 case ButtonRelease:
1397 done = True;
1398 break;
1400 default:
1401 WMHandleEvent(&ev);
1402 break;
1405 XUngrabPointer(dpy, CurrentTime);
1408 if (!enteredMenu) {
1409 Bool rem = True;
1411 if (!menu->flags.isFactory) {
1412 W_UnmapView(dview);
1413 if (menu->delegate && menu->delegate->shouldRemoveItem) {
1414 rem = (*menu->delegate->shouldRemoveItem)(menu->delegate,
1415 menu, item);
1417 W_MapView(dview);
1420 if (!rem || menu->flags.isFactory) {
1421 slideWindow(dpy, W_VIEW_DRAWABLE(dview), x-dx, y-dy, orix, oriy);
1423 if (!menu->flags.isFactory) {
1424 WRemoveEditMenuItem(menu, dritem);
1425 handleItemDrop(dmenu ? dmenu : menu, dritem, orix, oriy);
1427 } else {
1428 WRemoveEditMenuItem(menu, dritem);
1430 } else {
1431 WRemoveEditMenuItem(menu, dritem);
1433 if (menu->delegate && menu->delegate->itemCloned
1434 && menu->flags.isFactory) {
1435 (*menu->delegate->itemCloned)(menu->delegate, menu,
1436 item, dritem);
1439 handleItemDrop(dmenu, dritem, x-dy, y-dy);
1441 if (item->submenu && menu->flags.isFactory) {
1442 WEditMenu *submenu;
1444 submenu = duplicateMenu(item->submenu);
1445 WSetEditMenuSubmenu(dmenu, dritem, submenu);
1449 /* can't destroy now because we're being called from
1450 * the event handler of the item. and destroying now,
1451 * would mean destroying the item too in some cases.
1453 WMAddIdleHandler((WMCallback*)W_DestroyView, dview);
1458 static void
1459 handleItemClick(XEvent *event, void *data)
1461 WEditMenuItem *item = (WEditMenuItem*)data;
1462 WEditMenu *menu = item->parent;
1464 switch (event->type) {
1465 case ButtonPress:
1466 selectItem(menu, item);
1468 if (WMIsDoubleClick(event)) {
1469 editItemLabel(item);
1472 menu->flags.isDragging = 1;
1473 menu->dragX = event->xbutton.x_root;
1474 menu->dragY = event->xbutton.y_root;
1475 break;
1477 case ButtonRelease:
1478 menu->flags.isDragging = 0;
1479 break;
1481 case MotionNotify:
1482 if (menu->flags.isDragging) {
1483 if (abs(event->xbutton.x_root - menu->dragX) > 5
1484 || abs(event->xbutton.y_root - menu->dragY) > 5) {
1485 menu->flags.isDragging = 0;
1486 dragItem(menu, item);
1489 break;
1494 static void
1495 destroyEditMenu(WEditMenu *mPtr)
1497 WEditMenuItem *item;
1498 WMBagIterator iter;
1500 WMRemoveNotificationObserver(mPtr);
1502 WM_ITERATE_BAG(mPtr->items, item, iter) {
1503 if (item->submenu) {
1504 WMDestroyWidget(item->submenu);
1508 WMFreeBag(mPtr->items);
1510 free(mPtr->tdelegate);
1512 free(mPtr);