- added WMRemoveFromArrayMatching(array, match, cdata), which will remove the
[wmaker-crm.git] / WPrefs.app / editmenu.c
blob7723ca5220758d9134a94b00a9b29f37678c9344
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 <WINGs/WINGsP.h>
25 #include <WINGs/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 WMArray *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 wfree(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 wfree(iPtr->label);
304 if (iPtr->data && iPtr->destroyData)
305 (*iPtr->destroyData)(iPtr->data);
306 if (iPtr->submenu)
307 WMDestroyWidget(iPtr->submenu);
309 wfree(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 wfree(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 = WMCreateArray(4);
462 WMCreateEventHandler(mPtr->view, ExposureMask|StructureNotifyMask,
463 handleEvents, mPtr);
466 if (title != NULL) {
467 item = WCreateEditMenuItem(mPtr, title, True);
469 WMMapWidget(item);
470 WMAddToArray(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 >= WMGetArrayItemCount(mPtr->items)) {
515 WMAddToArray(mPtr->items, item);
516 } else {
517 if (index < 0)
518 index = 0;
519 if (mPtr->flags.isTitled)
520 index++;
521 WMInsertInArray(mPtr->items, index, item);
524 updateMenuContents(mPtr);
526 return item;
530 WEditMenuItem*
531 WGetEditMenuItem(WEditMenu *mPtr, int index)
533 if (index >= WMGetArrayItemCount(mPtr->items))
534 return NULL;
536 return WMGetFromArray(mPtr->items, index + (mPtr->flags.isTitled ? 1 : 0));
540 WEditMenuItem*
541 WAddMenuItemWithTitle(WEditMenu *mPtr, char *title)
543 return WInsertMenuItemWithTitle(mPtr, WMGetArrayItemCount(mPtr->items),
544 title);
549 void
550 WSetEditMenuTitle(WEditMenu *mPtr, char *title)
552 WEditMenuItem *item;
554 item = WMGetFromArray(mPtr->items, 0);
556 wfree(item->label);
557 item->label = wstrdup(title);
559 updateMenuContents(mPtr);
561 WMRedisplayWidget(item);
566 char*
567 WGetEditMenuTitle(WEditMenu *mPtr)
569 WEditMenuItem *item;
571 item = WMGetFromArray(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 void
602 WRemoveEditMenuItem(WEditMenu *mPtr, WEditMenuItem *item)
604 if (WMRemoveFromArray(mPtr->items, item) != 0) {
605 updateMenuContents(mPtr);
610 void
611 WSetEditMenuSelectable(WEditMenu *mPtr, Bool flag)
613 mPtr->flags.isSelectable = flag;
617 void
618 WSetEditMenuEditable(WEditMenu *mPtr, Bool flag)
620 mPtr->flags.isEditable = flag;
624 void
625 WSetEditMenuIsFactory(WEditMenu *mPtr, Bool flag)
627 mPtr->flags.isFactory = flag;
631 void
632 WSetEditMenuMinSize(WEditMenu *mPtr, WMSize size)
634 mPtr->minSize.width = size.width;
635 mPtr->minSize.height = size.height;
639 void
640 WSetEditMenuMaxSize(WEditMenu *mPtr, WMSize size)
642 mPtr->maxSize.width = size.width;
643 mPtr->maxSize.height = size.height;
647 WMPoint
648 WGetEditMenuLocationForSubmenu(WEditMenu *mPtr, WEditMenu *submenu)
650 WMArrayIterator iter;
651 WEditMenuItem *item;
652 int y;
654 if (mPtr->flags.isTitled)
655 y = -mPtr->titleHeight;
656 else
657 y = 0;
658 WM_ITERATE_ARRAY(mPtr->items, item, iter) {
659 if (item->submenu == submenu) {
660 WMPoint pt = WMGetViewScreenPosition(mPtr->view);
662 return wmkpoint(pt.x + mPtr->view->size.width, pt.y + y);
664 y += W_VIEW_HEIGHT(item->view);
667 puts("invalid submenu passed to WGetEditMenuLocationForSubmenu()");
669 return wmkpoint(0,0);
674 static void
675 closeMenuAction(WMWidget *w, void *data)
677 WEditMenu *menu = (WEditMenu*)data;
679 WMAddIdleHandler(WMDestroyWidget, menu->closeB);
680 menu->closeB = NULL;
682 WEditMenuHide(menu);
686 void
687 WTearOffEditMenu(WEditMenu *menu, WEditMenu *submenu)
689 WEditMenuItem *item;
691 submenu->flags.isTornOff = 1;
693 item = (WEditMenuItem*)WMGetFromArray(submenu->items, 0);
695 submenu->closeB = WMCreateCommandButton(item);
696 WMResizeWidget(submenu->closeB, 15, 15);
697 WMMoveWidget(submenu->closeB, W_VIEW(submenu)->size.width - 20, 3);
698 WMRealizeWidget(submenu->closeB);
699 WMSetButtonText(submenu->closeB, "X");
700 WMSetButtonAction(submenu->closeB, closeMenuAction, submenu);
701 WMMapWidget(submenu->closeB);
703 if (menu->selectedItem && menu->selectedItem->submenu == submenu)
704 deselectItem(menu);
709 Bool
710 WEditMenuIsTornOff(WEditMenu *mPtr)
712 return mPtr->flags.isTornOff;
717 void
718 WEditMenuHide(WEditMenu *mPtr)
720 WEditMenuItem *item;
721 int i = 0;
723 if (WMWidgetIsMapped(mPtr)) {
724 WMUnmapWidget(mPtr);
725 mPtr->flags.wasMapped = 1;
726 } else {
727 mPtr->flags.wasMapped = 0;
729 while ((item = WGetEditMenuItem(mPtr, i++))) {
730 WEditMenu *submenu;
732 submenu = WGetEditMenuSubmenu(mPtr, item);
733 if (submenu) {
734 WEditMenuHide(submenu);
741 void
742 WEditMenuUnhide(WEditMenu *mPtr)
744 WEditMenuItem *item;
745 int i = 0;
747 if (mPtr->flags.wasMapped) {
748 WMMapWidget(mPtr);
750 while ((item = WGetEditMenuItem(mPtr, i++))) {
751 WEditMenu *submenu;
753 submenu = WGetEditMenuSubmenu(mPtr, item);
754 if (submenu) {
755 WEditMenuUnhide(submenu);
761 void
762 WEditMenuShowAt(WEditMenu *menu, int x, int y)
764 XSizeHints *hints;
766 hints = XAllocSizeHints();
768 hints->flags = USPosition;
769 hints->x = x;
770 hints->y = y;
772 WMMoveWidget(menu, x, y);
773 XSetWMNormalHints(W_VIEW_DISPLAY(menu->view),
774 W_VIEW_DRAWABLE(menu->view),
775 hints);
776 XFree(hints);
778 WMMapWidget(menu);
782 static void
783 updateMenuContents(WEditMenu *mPtr)
785 int newW, newH;
786 int w;
787 int i;
788 int iheight = mPtr->itemHeight;
789 int offs = 1;
790 WMArrayIterator iter;
791 WEditMenuItem *item;
793 newW = 0;
794 newH = offs;
796 i = 0;
797 WM_ITERATE_ARRAY(mPtr->items, item, iter) {
798 w = getItemTextWidth(item);
800 newW = WMAX(w, newW);
802 WMMoveWidget(item, offs, newH);
803 if (i == 0 && mPtr->flags.isTitled) {
804 newH += mPtr->titleHeight;
805 } else {
806 newH += iheight;
808 i = 1;
811 newW += iheight + 10;
812 newH--;
814 if (mPtr->minSize.width)
815 newW = WMAX(newW, mPtr->minSize.width);
816 if (mPtr->maxSize.width)
817 newW = WMIN(newW, mPtr->maxSize.width);
819 if (mPtr->minSize.height)
820 newH = WMAX(newH, mPtr->minSize.height);
821 if (mPtr->maxSize.height)
822 newH = WMIN(newH, mPtr->maxSize.height);
824 if (W_VIEW(mPtr)->size.width == newW && mPtr->view->size.height == newH+1)
825 return;
827 W_ResizeView(mPtr->view, newW, newH+1);
829 if (mPtr->closeB)
830 WMMoveWidget(mPtr->closeB, newW - 20, 3);
832 newW -= 2*offs;
834 i = 0;
835 WM_ITERATE_ARRAY(mPtr->items, item, iter) {
836 if (i == 0 && mPtr->flags.isTitled) {
837 WMResizeWidget(item, newW, mPtr->titleHeight);
838 } else {
839 WMResizeWidget(item, newW, iheight);
841 i = 1;
846 static void
847 deselectItem(WEditMenu *menu)
849 WEditMenu *submenu;
850 WEditMenuItem *item = menu->selectedItem;
852 highlightItem(item, False);
854 if (menu->delegate && menu->delegate->itemDeselected) {
855 (*menu->delegate->itemDeselected)(menu->delegate, menu, item);
857 submenu = item->submenu;
859 if (submenu && !WEditMenuIsTornOff(submenu)) {
860 WEditMenuHide(submenu);
863 menu->selectedItem = NULL;
867 static void
868 selectItem(WEditMenu *menu, WEditMenuItem *item)
870 if (!menu->flags.isSelectable || menu->selectedItem == item) {
871 return;
873 if (menu->selectedItem) {
874 deselectItem(menu);
877 if (menu->flags.isEditing) {
878 stopEditItem(menu, False);
881 if (item && !item->flags.isTitle) {
882 highlightItem(item, True);
884 if (item->submenu && !W_VIEW_MAPPED(item->submenu->view)) {
885 WMPoint pt;
887 pt = WGetEditMenuLocationForSubmenu(menu, item->submenu);
889 WEditMenuShowAt(item->submenu, pt.x, pt.y);
891 item->submenu->flags.isTornOff = 0;
894 WMPostNotificationName("EditMenuItemSelected", menu, NULL);
896 if (menu->delegate && menu->delegate->itemSelected) {
897 (*menu->delegate->itemSelected)(menu->delegate, menu, item);
901 menu->selectedItem = item;
905 static void
906 paintMenu(WEditMenu *mPtr)
908 W_View *view = mPtr->view;
910 W_DrawRelief(W_VIEW_SCREEN(view), W_VIEW_DRAWABLE(view), 0, 0,
911 W_VIEW_WIDTH(view), W_VIEW_HEIGHT(view), WRSimple);
915 static void
916 handleEvents(XEvent *event, void *data)
918 WEditMenu *mPtr = (WEditMenu*)data;
920 switch (event->type) {
921 case DestroyNotify:
922 destroyEditMenu(mPtr);
923 break;
925 case Expose:
926 if (event->xexpose.count == 0)
927 paintMenu(mPtr);
928 break;
935 /* -------------------------- Menu Label Editing ------------------------ */
938 static void
939 stopEditItem(WEditMenu *menu, Bool apply)
941 char *text;
943 if (apply) {
944 text = WMGetTextFieldText(menu->tfield);
946 wfree(menu->selectedItem->label);
947 menu->selectedItem->label = wstrdup(text);
949 updateMenuContents(menu);
951 if (menu->delegate && menu->delegate->itemEdited) {
952 (*menu->delegate->itemEdited)(menu->delegate, menu,
953 menu->selectedItem);
957 WMUnmapWidget(menu->tfield);
958 menu->flags.isEditing = 0;
962 static void
963 textEndedEditing(struct WMTextFieldDelegate *self, WMNotification *notif)
965 WEditMenu *menu = (WEditMenu*)self->data;
966 int reason;
967 int i;
968 WEditMenuItem *item;
970 if (!menu->flags.isEditing)
971 return;
973 reason = (int)WMGetNotificationClientData(notif);
975 switch (reason) {
976 case WMEscapeTextMovement:
977 stopEditItem(menu, False);
978 break;
980 case WMReturnTextMovement:
981 stopEditItem(menu, True);
982 break;
984 case WMTabTextMovement:
985 stopEditItem(menu, True);
987 i = WMGetFirstInArray(menu->items, menu->selectedItem);
988 item = WMGetFromArray(menu->items, i+1);
989 if (item != NULL) {
990 selectItem(menu, item);
991 editItemLabel(item);
993 break;
995 case WMBacktabTextMovement:
996 stopEditItem(menu, True);
998 i = WMGetFirstInArray(menu->items, menu->selectedItem);
999 item = WMGetFromArray(menu->items, i-1);
1000 if (item != NULL) {
1001 selectItem(menu, item);
1002 editItemLabel(item);
1004 break;
1010 static WMTextFieldDelegate textFieldDelegate = {
1011 NULL,
1012 NULL,
1013 NULL,
1014 textEndedEditing,
1015 NULL,
1016 NULL
1020 static void
1021 editItemLabel(WEditMenuItem *item)
1023 WEditMenu *mPtr = item->parent;
1024 WMTextField *tf;
1026 if (!mPtr->flags.isEditable) {
1027 return;
1030 if (!mPtr->tfield) {
1031 mPtr->tfield = WMCreateTextField(mPtr);
1032 WMSetTextFieldBeveled(mPtr->tfield, False);
1033 WMRealizeWidget(mPtr->tfield);
1035 mPtr->tdelegate = wmalloc(sizeof(WMTextFieldDelegate));
1036 memcpy(mPtr->tdelegate, &textFieldDelegate,
1037 sizeof(WMTextFieldDelegate));
1039 mPtr->tdelegate->data = mPtr;
1041 WMSetTextFieldDelegate(mPtr->tfield, mPtr->tdelegate);
1043 tf = mPtr->tfield;
1045 WMSetTextFieldText(tf, item->label);
1046 WMSelectTextFieldRange(tf, wmkrange(0, strlen(item->label)));
1047 WMResizeWidget(tf, mPtr->view->size.width, mPtr->itemHeight);
1048 WMMoveWidget(tf, 0, item->view->pos.y);
1049 WMMapWidget(tf);
1050 WMSetFocusToWidget(tf);
1052 mPtr->flags.isEditing = 1;
1057 /* -------------------------------------------------- */
1060 static void
1061 slideWindow(Display *dpy, Window win, int srcX, int srcY, int dstX, int dstY)
1063 double x, y, dx, dy;
1064 int i;
1065 int iterations;
1067 iterations = WMIN(25, WMAX(abs(dstX-srcX), abs(dstY-srcY)));
1069 x = srcX;
1070 y = srcY;
1072 dx = (double)(dstX-srcX)/iterations;
1073 dy = (double)(dstY-srcY)/iterations;
1075 for (i = 0; i <= iterations; i++) {
1076 XMoveWindow(dpy, win, x, y);
1077 XFlush(dpy);
1079 wusleep(800);
1081 x += dx;
1082 y += dy;
1087 static int
1088 errorHandler(Display *d, XErrorEvent *ev)
1090 /* just ignore */
1091 return 0;
1095 static WEditMenu*
1096 findMenuInWindow(Display *dpy, Window toplevel, int x, int y)
1098 Window foo, bar;
1099 Window *children;
1100 unsigned nchildren;
1101 int i;
1102 WEditMenu *menu;
1103 WMView *view;
1104 int (*oldHandler)(Display *, XErrorEvent *);
1106 view = W_GetViewForXWindow(dpy, toplevel);
1107 if (view && view->self && WMWidgetClass(view->self) == EditMenuClass) {
1108 menu = (WEditMenu*)view->self;
1109 if (menu->flags.acceptsDrop) {
1110 return menu;
1114 if (!XQueryTree(dpy, toplevel, &foo, &bar,
1115 &children, &nchildren) || children == NULL) {
1116 return NULL;
1119 oldHandler = XSetErrorHandler(errorHandler);
1121 /* first window that contains the point is the one */
1122 for (i = nchildren-1; i >= 0; i--) {
1123 XWindowAttributes attr;
1125 if (XGetWindowAttributes(dpy, children[i], &attr)
1126 && attr.map_state == IsViewable
1127 && x >= attr.x && y >= attr.y
1128 && x < attr.x + attr.width && y < attr.y + attr.height) {
1129 Window tmp;
1131 tmp = children[i];
1133 menu = findMenuInWindow(dpy, tmp, x - attr.x, y - attr.y);
1134 if (menu) {
1135 XFree(children);
1136 return menu;
1141 XSetErrorHandler(oldHandler);
1143 XFree(children);
1144 return NULL;
1148 static void
1149 handleDragOver(WEditMenu *menu, WMView *view, WEditMenuItem *item,
1150 int x, int y)
1152 WMScreen *scr = W_VIEW_SCREEN(menu->view);
1153 Window blaw;
1154 int mx, my;
1155 int offs;
1157 XTranslateCoordinates(scr->display, W_VIEW_DRAWABLE(menu->view),
1158 scr->rootWin, 0, 0, &mx, &my, &blaw);
1160 offs = menu->flags.standalone ? 0 : 1;
1162 W_MoveView(view, mx + offs, y);
1163 if (view->size.width != menu->view->size.width) {
1164 W_ResizeView(view, menu->view->size.width - 2*offs,
1165 menu->itemHeight);
1166 W_ResizeView(item->view, menu->view->size.width - 2*offs,
1167 menu->itemHeight);
1172 static void
1173 handleItemDrop(WEditMenu *menu, WEditMenuItem *item, int x, int y)
1175 WMScreen *scr = W_VIEW_SCREEN(menu->view);
1176 Window blaw;
1177 int mx, my;
1178 int index;
1180 XTranslateCoordinates(scr->display, W_VIEW_DRAWABLE(menu->view),
1181 scr->rootWin, 0, 0, &mx, &my, &blaw);
1183 index = y - my;
1184 if (menu->flags.isTitled) {
1185 index -= menu->titleHeight;
1187 index = (index + menu->itemHeight/2) / menu->itemHeight;
1188 if (index < 0)
1189 index = 0;
1191 if (menu->flags.isTitled) {
1192 index++;
1195 if (index > WMGetArrayItemCount(menu->items)) {
1196 WMAddToArray(menu->items, item);
1197 } else {
1198 WMInsertInArray(menu->items, index, item);
1201 W_ReparentView(item->view, menu->view, 0, index*menu->itemHeight);
1203 item->parent = menu;
1204 if (item->submenu) {
1205 item->submenu->parent = menu;
1208 updateMenuContents(menu);
1212 static void
1213 dragMenu(WEditMenu *menu)
1215 WMScreen *scr = W_VIEW_SCREEN(menu->view);
1216 XEvent ev;
1217 Bool done = False;
1218 int dx, dy;
1219 unsigned blau;
1220 Window blaw;
1222 XGetGeometry(scr->display, W_VIEW_DRAWABLE(menu->view), &blaw, &dx, &dy,
1223 &blau, &blau, &blau, &blau);
1225 XTranslateCoordinates(scr->display, W_VIEW_DRAWABLE(menu->view),
1226 scr->rootWin, dx, dy, &dx, &dy, &blaw);
1228 dx = menu->dragX - dx;
1229 dy = menu->dragY - dy;
1231 XGrabPointer(scr->display, scr->rootWin, False,
1232 ButtonReleaseMask|ButtonMotionMask,
1233 GrabModeAsync, GrabModeAsync, None, scr->defaultCursor,
1234 CurrentTime);
1236 if (menu->parent)
1237 WTearOffEditMenu(menu->parent, menu);
1239 while (!done) {
1240 WMNextEvent(scr->display, &ev);
1242 switch (ev.type) {
1243 case ButtonRelease:
1244 done = True;
1245 break;
1247 case MotionNotify:
1248 while (XCheckMaskEvent(scr->display, ButtonMotionMask, &ev)) ;
1250 WMMoveWidget(menu, ev.xmotion.x_root - dx,
1251 ev.xmotion.y_root - dy);
1252 break;
1254 default:
1255 WMHandleEvent(&ev);
1256 break;
1260 XUngrabPointer(scr->display, CurrentTime);
1265 static WEditMenuItem*
1266 duplicateItem(WEditMenuItem *item)
1268 WEditMenuItem *nitem;
1270 nitem = WCreateEditMenuItem(item->parent, item->label, False);
1271 if (item->pixmap)
1272 nitem->pixmap = WMRetainPixmap(item->pixmap);
1274 return nitem;
1280 static WEditMenu*
1281 duplicateMenu(WEditMenu *menu)
1283 WEditMenu *nmenu;
1284 WEditMenuItem *item;
1285 WMArrayIterator iter;
1286 Bool first = menu->flags.isTitled;
1288 nmenu = WCreateEditMenu(WMWidgetScreen(menu), WGetEditMenuTitle(menu));
1290 memcpy(&nmenu->flags, &menu->flags, sizeof(menu->flags));
1291 nmenu->delegate = menu->delegate;
1293 WM_ITERATE_ARRAY(menu->items, item, iter) {
1294 WEditMenuItem *nitem;
1296 if (first) {
1297 first = False;
1298 continue;
1301 nitem = WAddMenuItemWithTitle(nmenu, item->label);
1302 if (item->pixmap)
1303 WSetEditMenuItemImage(nitem, item->pixmap);
1305 if (menu->delegate && menu->delegate->itemCloned) {
1306 (*menu->delegate->itemCloned)(menu->delegate, menu,
1307 item, nitem);
1311 WMRealizeWidget(nmenu);
1313 return nmenu;
1318 static void
1319 dragItem(WEditMenu *menu, WEditMenuItem *item, Bool copy)
1321 static XColor black = {0, 0,0,0, DoRed|DoGreen|DoBlue};
1322 static XColor green = {0x0045b045, 0x4500,0xb000,0x4500, DoRed|DoGreen|DoBlue};
1323 static XColor back = {0, 0xffff,0xffff,0xffff, DoRed|DoGreen|DoBlue};
1324 Display *dpy = W_VIEW_DISPLAY(menu->view);
1325 WMScreen *scr = W_VIEW_SCREEN(menu->view);
1326 int x, y;
1327 int dx, dy;
1328 Bool done = False;
1329 Window blaw;
1330 int blai;
1331 unsigned blau;
1332 Window win;
1333 WMView *dview;
1334 int orix, oriy;
1335 Bool enteredMenu = False;
1336 WMSize oldSize = item->view->size;
1337 WEditMenuItem *dritem = item;
1338 WEditMenu *dmenu = NULL;
1341 if (item->flags.isTitle) {
1342 WMRaiseWidget(menu);
1344 dragMenu(menu);
1346 return;
1349 selectItem(menu, NULL);
1351 win = scr->rootWin;
1353 XTranslateCoordinates(dpy, W_VIEW_DRAWABLE(item->view), win,
1354 0, 0, &orix, &oriy, &blaw);
1356 dview = W_CreateUnmanagedTopView(scr);
1357 W_SetViewBackgroundColor(dview, scr->black);
1358 W_ResizeView(dview, W_VIEW_WIDTH(item->view), W_VIEW_HEIGHT(item->view));
1359 W_MoveView(dview, orix, oriy);
1360 W_RealizeView(dview);
1362 if (menu->flags.isFactory || copy) {
1363 dritem = duplicateItem(item);
1365 W_ReparentView(dritem->view, dview, 0, 0);
1366 WMResizeWidget(dritem, oldSize.width, oldSize.height);
1367 WMRealizeWidget(dritem);
1368 WMMapWidget(dritem);
1369 } else {
1370 W_ReparentView(item->view, dview, 0, 0);
1373 W_MapView(dview);
1375 dx = menu->dragX - orix;
1376 dy = menu->dragY - oriy;
1378 XGrabPointer(dpy, scr->rootWin, False,
1379 ButtonPressMask|ButtonReleaseMask|ButtonMotionMask,
1380 GrabModeAsync, GrabModeAsync, None, scr->defaultCursor,
1381 CurrentTime);
1383 if (menu->flags.acceptsDrop)
1384 XRecolorCursor(dpy, scr->defaultCursor, &green, &back);
1386 while (!done) {
1387 XEvent ev;
1389 WMNextEvent(dpy, &ev);
1391 switch (ev.type) {
1392 case MotionNotify:
1393 while (XCheckMaskEvent(dpy, ButtonMotionMask, &ev)) ;
1395 XQueryPointer(dpy, win, &blaw, &blaw, &blai, &blai, &x, &y, &blau);
1397 dmenu = findMenuInWindow(dpy, win, x, y);
1399 if (dmenu) {
1400 handleDragOver(dmenu, dview, dritem, x - dx, y - dy);
1401 if (!enteredMenu) {
1402 enteredMenu = True;
1403 XRecolorCursor(dpy, scr->defaultCursor, &green, &back);
1405 } else {
1406 if (enteredMenu) {
1407 W_ResizeView(dview, oldSize.width, oldSize.height);
1408 W_ResizeView(dritem->view, oldSize.width, oldSize.height);
1409 enteredMenu = False;
1410 XRecolorCursor(dpy, scr->defaultCursor, &black, &back);
1412 W_MoveView(dview, x - dx, y - dy);
1415 break;
1417 case ButtonRelease:
1418 done = True;
1419 break;
1421 default:
1422 WMHandleEvent(&ev);
1423 break;
1426 XRecolorCursor(dpy, scr->defaultCursor, &black, &back);
1428 XUngrabPointer(dpy, CurrentTime);
1431 if (!enteredMenu) {
1432 Bool rem = True;
1434 if (!menu->flags.isFactory && !copy) {
1435 W_UnmapView(dview);
1436 if (menu->delegate && menu->delegate->shouldRemoveItem) {
1437 rem = (*menu->delegate->shouldRemoveItem)(menu->delegate,
1438 menu, item);
1440 W_MapView(dview);
1443 if (!rem || menu->flags.isFactory || copy) {
1444 slideWindow(dpy, W_VIEW_DRAWABLE(dview), x-dx, y-dy, orix, oriy);
1446 if (!menu->flags.isFactory && !copy) {
1447 WRemoveEditMenuItem(menu, dritem);
1448 handleItemDrop(dmenu ? dmenu : menu, dritem, orix, oriy);
1450 } else {
1451 WRemoveEditMenuItem(menu, dritem);
1453 } else {
1454 WRemoveEditMenuItem(menu, dritem);
1456 if (menu->delegate && menu->delegate->itemCloned
1457 && (menu->flags.isFactory || copy)) {
1458 (*menu->delegate->itemCloned)(menu->delegate, menu,
1459 item, dritem);
1462 handleItemDrop(dmenu, dritem, x-dy, y-dy);
1464 if (item->submenu && (menu->flags.isFactory || copy)) {
1465 WEditMenu *submenu;
1467 submenu = duplicateMenu(item->submenu);
1468 WSetEditMenuSubmenu(dmenu, dritem, submenu);
1472 /* can't destroy now because we're being called from
1473 * the event handler of the item. and destroying now,
1474 * would mean destroying the item too in some cases.
1476 WMAddIdleHandler((WMCallback*)W_DestroyView, dview);
1481 static void
1482 handleItemClick(XEvent *event, void *data)
1484 WEditMenuItem *item = (WEditMenuItem*)data;
1485 WEditMenu *menu = item->parent;
1487 switch (event->type) {
1488 case ButtonPress:
1489 selectItem(menu, item);
1491 if (WMIsDoubleClick(event)) {
1492 editItemLabel(item);
1495 menu->flags.isDragging = 1;
1496 menu->dragX = event->xbutton.x_root;
1497 menu->dragY = event->xbutton.y_root;
1498 break;
1500 case ButtonRelease:
1501 menu->flags.isDragging = 0;
1502 break;
1504 case MotionNotify:
1505 if (menu->flags.isDragging) {
1506 if (abs(event->xbutton.x_root - menu->dragX) > 5
1507 || abs(event->xbutton.y_root - menu->dragY) > 5) {
1508 menu->flags.isDragging = 0;
1509 dragItem(menu, item, event->xbutton.state & ControlMask);
1512 break;
1517 static void
1518 destroyEditMenu(WEditMenu *mPtr)
1520 WEditMenuItem *item;
1521 WMArrayIterator iter;
1523 WMRemoveNotificationObserver(mPtr);
1525 WM_ITERATE_ARRAY(mPtr->items, item, iter) {
1526 if (item->submenu) {
1527 WMDestroyWidget(item->submenu);
1531 WMFreeArray(mPtr->items);
1533 wfree(mPtr->tdelegate);
1535 wfree(mPtr);