changed indentation to use spaces only
[wmaker-crm.git] / WPrefs.app / editmenu.c
blob470d6741fd430d6c89082e6416c5593c5c3d3183
1 /* editmenu.c - editable menus
3 * WPrefs - Window Maker Preferences Program
5 * Copyright (c) 2000-2003 Alfredo K. Kojima
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, color, font, 5, 3, iPtr->label, strlen(iPtr->label));
224 if (iPtr->pixmap) {
225 WMSize size = WMGetPixmapSize(iPtr->pixmap);
227 WMDrawPixmap(iPtr->pixmap, win, w - size.width - 5,
228 (h - size.height)/2);
229 } else if (iPtr->submenu) {
230 /* draw the cascade indicator */
231 XDrawLine(scr->display,win,WMColorGC(scr->darkGray),
232 w-11, 6, w-6, h/2-1);
233 XDrawLine(scr->display,win,WMColorGC(scr->white),
234 w-11, h-8, w-6, h/2-1);
235 XDrawLine(scr->display,win,WMColorGC(scr->black),
236 w-12, 6, w-12, h-8);
241 static void
242 highlightItem(WEditMenuItem *iPtr, Bool high)
244 if (iPtr->flags.isTitle)
245 return;
247 iPtr->flags.isHighlighted = high;
249 if (high) {
250 WMSetWidgetBackgroundColor(iPtr, WMWhiteColor(WMWidgetScreen(iPtr)));
251 } else {
252 if (!iPtr->flags.isTitle) {
253 WMSetWidgetBackgroundColor(iPtr,
254 WMGrayColor(WMWidgetScreen(iPtr)));
255 } else {
256 WMSetWidgetBackgroundColor(iPtr,
257 WMBlackColor(WMWidgetScreen(iPtr)));
263 static int
264 getItemTextWidth(WEditMenuItem *iPtr)
266 WMScreen *scr = WMWidgetScreen(iPtr);
268 if (iPtr->flags.isTitle) {
269 return WMWidthOfString(scr->boldFont, iPtr->label,
270 strlen(iPtr->label));
271 } else {
272 return WMWidthOfString(scr->normalFont, iPtr->label,
273 strlen(iPtr->label));
279 static void
280 handleItemEvents(XEvent *event, void *data)
282 WEditMenuItem *iPtr = (WEditMenuItem*)data;
284 switch (event->type) {
285 case Expose:
286 if (event->xexpose.count!=0)
287 break;
288 paintEditMenuItem(iPtr);
289 break;
291 case DestroyNotify:
292 destroyEditMenuItem(iPtr);
293 break;
298 static void
299 destroyEditMenuItem(WEditMenuItem *iPtr)
301 if (iPtr->label)
302 wfree(iPtr->label);
303 if (iPtr->data && iPtr->destroyData)
304 (*iPtr->destroyData)(iPtr->data);
305 if (iPtr->submenu)
306 WMDestroyWidget(iPtr->submenu);
308 wfree(iPtr);
313 /******************** WEditMenu *******************/
315 static void destroyEditMenu(WEditMenu *mPtr);
317 static void updateMenuContents(WEditMenu *mPtr);
319 static void handleEvents(XEvent *event, void *data);
321 static void editItemLabel(WEditMenuItem *item);
323 static void stopEditItem(WEditMenu *menu, Bool apply);
325 static void deselectItem(WEditMenu *menu);
328 static W_Class EditMenuClass = 0;
331 W_Class
332 InitEditMenu(WMScreen *scr)
334 /* register our widget with WINGs and get our widget class ID */
335 if (!EditMenuClass) {
337 EditMenuClass = W_RegisterUserWidget();
340 return EditMenuClass;
345 typedef struct {
346 int flags;
347 int window_style;
348 int window_level;
349 int reserved;
350 Pixmap miniaturize_pixmap; /* pixmap for miniaturize button */
351 Pixmap close_pixmap; /* pixmap for close button */
352 Pixmap miniaturize_mask; /* miniaturize pixmap mask */
353 Pixmap close_mask; /* close pixmap mask */
354 int extra_flags;
355 } GNUstepWMAttributes;
358 #define GSWindowStyleAttr (1<<0)
359 #define GSWindowLevelAttr (1<<1)
362 static void
363 writeGNUstepWMAttr(WMScreen *scr, Window window, GNUstepWMAttributes *attr)
365 unsigned long data[9];
367 /* handle idiot compilers where array of CARD32 != struct of CARD32 */
368 data[0] = attr->flags;
369 data[1] = attr->window_style;
370 data[2] = attr->window_level;
371 data[3] = 0; /* reserved */
372 /* The X protocol says XIDs are 32bit */
373 data[4] = attr->miniaturize_pixmap;
374 data[5] = attr->close_pixmap;
375 data[6] = attr->miniaturize_mask;
376 data[7] = attr->close_mask;
377 data[8] = attr->extra_flags;
378 XChangeProperty(scr->display, window, scr->attribsAtom, scr->attribsAtom,
379 32, PropModeReplace, (unsigned char *)data, 9);
383 static void
384 realizeObserver(void *self, WMNotification *not)
386 WEditMenu *menu = (WEditMenu*)self;
387 GNUstepWMAttributes attribs;
389 memset(&attribs, 0, sizeof(GNUstepWMAttributes));
390 attribs.flags = GSWindowStyleAttr|GSWindowLevelAttr;
391 attribs.window_style = WMBorderlessWindowMask;
392 attribs.window_level = WMSubmenuWindowLevel;
394 writeGNUstepWMAttr(WMWidgetScreen(menu), menu->view->window, &attribs);
398 static void
399 itemSelectObserver(void *self, WMNotification *notif)
401 WEditMenu *menu = (WEditMenu*)self;
402 WEditMenu *rmenu;
404 rmenu = (WEditMenu*)WMGetNotificationObject(notif);
405 /* check whether rmenu is from the same hierarchy of menu? */
407 if (rmenu == menu) {
408 return;
411 if (menu->selectedItem) {
412 if (!menu->selectedItem->submenu)
413 deselectItem(menu);
414 if (menu->flags.isEditing)
415 stopEditItem(menu, False);
420 static WEditMenu*
421 makeEditMenu(WMScreen *scr, WMWidget *parent, char *title)
423 WEditMenu *mPtr;
424 WEditMenuItem *item;
426 if (!EditMenuClass)
427 InitEditMenu(scr);
430 mPtr = wmalloc(sizeof(WEditMenu));
431 memset(mPtr, 0, sizeof(WEditMenu));
433 mPtr->widgetClass = EditMenuClass;
435 if (parent) {
436 mPtr->view = W_CreateView(W_VIEW(parent));
437 mPtr->flags.standalone = 0;
438 } else {
439 mPtr->view = W_CreateTopView(scr);
440 mPtr->flags.standalone = 1;
442 if (!mPtr->view) {
443 wfree(mPtr);
444 return NULL;
446 mPtr->view->self = mPtr;
448 mPtr->flags.isSelectable = 1;
449 mPtr->flags.isEditable = 1;
451 W_SetViewBackgroundColor(mPtr->view, scr->darkGray);
453 WMAddNotificationObserver(realizeObserver, mPtr,
454 WMViewRealizedNotification, mPtr->view);
456 WMAddNotificationObserver(itemSelectObserver, mPtr,
457 "EditMenuItemSelected", NULL);
459 mPtr->items = WMCreateArray(4);
461 WMCreateEventHandler(mPtr->view, ExposureMask|StructureNotifyMask,
462 handleEvents, mPtr);
465 if (title != NULL) {
466 item = WCreateEditMenuItem(mPtr, title, True);
468 WMMapWidget(item);
469 WMAddToArray(mPtr->items, item);
471 mPtr->flags.isTitled = 1;
474 mPtr->itemHeight = WMFontHeight(scr->normalFont) + 6;
475 mPtr->titleHeight = WMFontHeight(scr->boldFont) + 8;
477 updateMenuContents(mPtr);
479 return mPtr;
483 WEditMenu*
484 WCreateEditMenu(WMScreen *scr, char *title)
486 return makeEditMenu(scr, NULL, title);
490 WEditMenu*
491 WCreateEditMenuPad(WMWidget *parent)
493 return makeEditMenu(WMWidgetScreen(parent), parent, NULL);
497 void
498 WSetEditMenuDelegate(WEditMenu *mPtr, WEditMenuDelegate *delegate)
500 mPtr->delegate = delegate;
504 WEditMenuItem*
505 WInsertMenuItemWithTitle(WEditMenu *mPtr, int index, char *title)
507 WEditMenuItem *item;
509 item = WCreateEditMenuItem(mPtr, title, False);
511 WMMapWidget(item);
513 if (index >= WMGetArrayItemCount(mPtr->items)) {
514 WMAddToArray(mPtr->items, item);
515 } else {
516 if (index < 0)
517 index = 0;
518 if (mPtr->flags.isTitled)
519 index++;
520 WMInsertInArray(mPtr->items, index, item);
523 updateMenuContents(mPtr);
525 return item;
529 WEditMenuItem*
530 WGetEditMenuItem(WEditMenu *mPtr, int index)
532 if (index >= WMGetArrayItemCount(mPtr->items))
533 return NULL;
535 return WMGetFromArray(mPtr->items, index + (mPtr->flags.isTitled ? 1 : 0));
539 WEditMenuItem*
540 WAddMenuItemWithTitle(WEditMenu *mPtr, char *title)
542 return WInsertMenuItemWithTitle(mPtr, WMGetArrayItemCount(mPtr->items),
543 title);
548 void
549 WSetEditMenuTitle(WEditMenu *mPtr, char *title)
551 WEditMenuItem *item;
553 item = WMGetFromArray(mPtr->items, 0);
555 wfree(item->label);
556 item->label = wstrdup(title);
558 updateMenuContents(mPtr);
560 WMRedisplayWidget(item);
565 char*
566 WGetEditMenuTitle(WEditMenu *mPtr)
568 WEditMenuItem *item;
570 item = WMGetFromArray(mPtr->items, 0);
572 return item->label;
576 void
577 WSetEditMenuAcceptsDrop(WEditMenu *mPtr, Bool flag)
579 mPtr->flags.acceptsDrop = flag;
583 void
584 WSetEditMenuSubmenu(WEditMenu *mPtr, WEditMenuItem *item, WEditMenu *submenu)
586 item->submenu = submenu;
587 submenu->parent = mPtr;
589 paintEditMenuItem(item);
593 WEditMenu*
594 WGetEditMenuSubmenu(WEditMenu *mPtr, WEditMenuItem *item)
596 return item->submenu;
600 void
601 WRemoveEditMenuItem(WEditMenu *mPtr, WEditMenuItem *item)
603 if (WMRemoveFromArray(mPtr->items, item) != 0) {
604 updateMenuContents(mPtr);
609 void
610 WSetEditMenuSelectable(WEditMenu *mPtr, Bool flag)
612 mPtr->flags.isSelectable = flag;
616 void
617 WSetEditMenuEditable(WEditMenu *mPtr, Bool flag)
619 mPtr->flags.isEditable = flag;
623 void
624 WSetEditMenuIsFactory(WEditMenu *mPtr, Bool flag)
626 mPtr->flags.isFactory = flag;
630 void
631 WSetEditMenuMinSize(WEditMenu *mPtr, WMSize size)
633 mPtr->minSize.width = size.width;
634 mPtr->minSize.height = size.height;
638 void
639 WSetEditMenuMaxSize(WEditMenu *mPtr, WMSize size)
641 mPtr->maxSize.width = size.width;
642 mPtr->maxSize.height = size.height;
646 WMPoint
647 WGetEditMenuLocationForSubmenu(WEditMenu *mPtr, WEditMenu *submenu)
649 WMArrayIterator iter;
650 WEditMenuItem *item;
651 int y;
653 if (mPtr->flags.isTitled)
654 y = -mPtr->titleHeight;
655 else
656 y = 0;
657 WM_ITERATE_ARRAY(mPtr->items, item, iter) {
658 if (item->submenu == submenu) {
659 WMPoint pt = WMGetViewScreenPosition(mPtr->view);
661 return wmkpoint(pt.x + mPtr->view->size.width, pt.y + y);
663 y += W_VIEW_HEIGHT(item->view);
666 puts("invalid submenu passed to WGetEditMenuLocationForSubmenu()");
668 return wmkpoint(0,0);
673 static void
674 closeMenuAction(WMWidget *w, void *data)
676 WEditMenu *menu = (WEditMenu*)data;
678 WMAddIdleHandler(WMDestroyWidget, menu->closeB);
679 menu->closeB = NULL;
681 WEditMenuHide(menu);
685 void
686 WTearOffEditMenu(WEditMenu *menu, WEditMenu *submenu)
688 WEditMenuItem *item;
690 submenu->flags.isTornOff = 1;
692 item = (WEditMenuItem*)WMGetFromArray(submenu->items, 0);
694 submenu->closeB = WMCreateCommandButton(item);
695 WMResizeWidget(submenu->closeB, 15, 15);
696 WMMoveWidget(submenu->closeB, W_VIEW(submenu)->size.width - 20, 3);
697 WMRealizeWidget(submenu->closeB);
698 WMSetButtonText(submenu->closeB, "X");
699 WMSetButtonAction(submenu->closeB, closeMenuAction, submenu);
700 WMMapWidget(submenu->closeB);
702 if (menu->selectedItem && menu->selectedItem->submenu == submenu)
703 deselectItem(menu);
708 Bool
709 WEditMenuIsTornOff(WEditMenu *mPtr)
711 return mPtr->flags.isTornOff;
716 void
717 WEditMenuHide(WEditMenu *mPtr)
719 WEditMenuItem *item;
720 int i = 0;
722 if (WMWidgetIsMapped(mPtr)) {
723 WMUnmapWidget(mPtr);
724 mPtr->flags.wasMapped = 1;
725 } else {
726 mPtr->flags.wasMapped = 0;
728 while ((item = WGetEditMenuItem(mPtr, i++))) {
729 WEditMenu *submenu;
731 submenu = WGetEditMenuSubmenu(mPtr, item);
732 if (submenu) {
733 WEditMenuHide(submenu);
740 void
741 WEditMenuUnhide(WEditMenu *mPtr)
743 WEditMenuItem *item;
744 int i = 0;
746 if (mPtr->flags.wasMapped) {
747 WMMapWidget(mPtr);
749 while ((item = WGetEditMenuItem(mPtr, i++))) {
750 WEditMenu *submenu;
752 submenu = WGetEditMenuSubmenu(mPtr, item);
753 if (submenu) {
754 WEditMenuUnhide(submenu);
760 void
761 WEditMenuShowAt(WEditMenu *menu, int x, int y)
763 XSizeHints *hints;
765 hints = XAllocSizeHints();
767 hints->flags = USPosition;
768 hints->x = x;
769 hints->y = y;
771 WMMoveWidget(menu, x, y);
772 XSetWMNormalHints(W_VIEW_DISPLAY(menu->view),
773 W_VIEW_DRAWABLE(menu->view),
774 hints);
775 XFree(hints);
777 WMMapWidget(menu);
781 static void
782 updateMenuContents(WEditMenu *mPtr)
784 int newW, newH;
785 int w;
786 int i;
787 int iheight = mPtr->itemHeight;
788 int offs = 1;
789 WMArrayIterator iter;
790 WEditMenuItem *item;
792 newW = 0;
793 newH = offs;
795 i = 0;
796 WM_ITERATE_ARRAY(mPtr->items, item, iter) {
797 w = getItemTextWidth(item);
799 newW = WMAX(w, newW);
801 WMMoveWidget(item, offs, newH);
802 if (i == 0 && mPtr->flags.isTitled) {
803 newH += mPtr->titleHeight;
804 } else {
805 newH += iheight;
807 i = 1;
810 newW += iheight + 10;
811 newH--;
813 if (mPtr->minSize.width)
814 newW = WMAX(newW, mPtr->minSize.width);
815 if (mPtr->maxSize.width)
816 newW = WMIN(newW, mPtr->maxSize.width);
818 if (mPtr->minSize.height)
819 newH = WMAX(newH, mPtr->minSize.height);
820 if (mPtr->maxSize.height)
821 newH = WMIN(newH, mPtr->maxSize.height);
823 if (W_VIEW(mPtr)->size.width == newW && mPtr->view->size.height == newH+1)
824 return;
826 W_ResizeView(mPtr->view, newW, newH+1);
828 if (mPtr->closeB)
829 WMMoveWidget(mPtr->closeB, newW - 20, 3);
831 newW -= 2*offs;
833 i = 0;
834 WM_ITERATE_ARRAY(mPtr->items, item, iter) {
835 if (i == 0 && mPtr->flags.isTitled) {
836 WMResizeWidget(item, newW, mPtr->titleHeight);
837 } else {
838 WMResizeWidget(item, newW, iheight);
840 i = 1;
845 static void
846 deselectItem(WEditMenu *menu)
848 WEditMenu *submenu;
849 WEditMenuItem *item = menu->selectedItem;
851 highlightItem(item, False);
853 if (menu->delegate && menu->delegate->itemDeselected) {
854 (*menu->delegate->itemDeselected)(menu->delegate, menu, item);
856 submenu = item->submenu;
858 if (submenu && !WEditMenuIsTornOff(submenu)) {
859 WEditMenuHide(submenu);
862 menu->selectedItem = NULL;
866 static void
867 selectItem(WEditMenu *menu, WEditMenuItem *item)
869 if (!menu->flags.isSelectable || menu->selectedItem == item) {
870 return;
872 if (menu->selectedItem) {
873 deselectItem(menu);
876 if (menu->flags.isEditing) {
877 stopEditItem(menu, False);
880 if (item && !item->flags.isTitle) {
881 highlightItem(item, True);
883 if (item->submenu && !W_VIEW_MAPPED(item->submenu->view)) {
884 WMPoint pt;
886 pt = WGetEditMenuLocationForSubmenu(menu, item->submenu);
888 WEditMenuShowAt(item->submenu, pt.x, pt.y);
890 item->submenu->flags.isTornOff = 0;
893 WMPostNotificationName("EditMenuItemSelected", menu, NULL);
895 if (menu->delegate && menu->delegate->itemSelected) {
896 (*menu->delegate->itemSelected)(menu->delegate, menu, item);
900 menu->selectedItem = item;
904 static void
905 paintMenu(WEditMenu *mPtr)
907 W_View *view = mPtr->view;
909 W_DrawRelief(W_VIEW_SCREEN(view), W_VIEW_DRAWABLE(view), 0, 0,
910 W_VIEW_WIDTH(view), W_VIEW_HEIGHT(view), WRSimple);
914 static void
915 handleEvents(XEvent *event, void *data)
917 WEditMenu *mPtr = (WEditMenu*)data;
919 switch (event->type) {
920 case DestroyNotify:
921 destroyEditMenu(mPtr);
922 break;
924 case Expose:
925 if (event->xexpose.count == 0)
926 paintMenu(mPtr);
927 break;
934 /* -------------------------- Menu Label Editing ------------------------ */
937 static void
938 stopEditItem(WEditMenu *menu, Bool apply)
940 char *text;
942 if (apply) {
943 text = WMGetTextFieldText(menu->tfield);
945 wfree(menu->selectedItem->label);
946 menu->selectedItem->label = wstrdup(text);
948 updateMenuContents(menu);
950 if (menu->delegate && menu->delegate->itemEdited) {
951 (*menu->delegate->itemEdited)(menu->delegate, menu,
952 menu->selectedItem);
956 WMUnmapWidget(menu->tfield);
957 menu->flags.isEditing = 0;
961 static void
962 textEndedEditing(struct WMTextFieldDelegate *self, WMNotification *notif)
964 WEditMenu *menu = (WEditMenu*)self->data;
965 int reason;
966 int i;
967 WEditMenuItem *item;
969 if (!menu->flags.isEditing)
970 return;
972 reason = (int)WMGetNotificationClientData(notif);
974 switch (reason) {
975 case WMEscapeTextMovement:
976 stopEditItem(menu, False);
977 break;
979 case WMReturnTextMovement:
980 stopEditItem(menu, True);
981 break;
983 case WMTabTextMovement:
984 stopEditItem(menu, True);
986 i = WMGetFirstInArray(menu->items, menu->selectedItem);
987 item = WMGetFromArray(menu->items, i+1);
988 if (item != NULL) {
989 selectItem(menu, item);
990 editItemLabel(item);
992 break;
994 case WMBacktabTextMovement:
995 stopEditItem(menu, True);
997 i = WMGetFirstInArray(menu->items, menu->selectedItem);
998 item = WMGetFromArray(menu->items, i-1);
999 if (item != NULL) {
1000 selectItem(menu, item);
1001 editItemLabel(item);
1003 break;
1009 static WMTextFieldDelegate textFieldDelegate = {
1010 NULL,
1011 NULL,
1012 NULL,
1013 textEndedEditing,
1014 NULL,
1015 NULL
1019 static void
1020 editItemLabel(WEditMenuItem *item)
1022 WEditMenu *mPtr = item->parent;
1023 WMTextField *tf;
1025 if (!mPtr->flags.isEditable) {
1026 return;
1029 if (!mPtr->tfield) {
1030 mPtr->tfield = WMCreateTextField(mPtr);
1031 WMSetTextFieldBeveled(mPtr->tfield, False);
1032 WMRealizeWidget(mPtr->tfield);
1034 mPtr->tdelegate = wmalloc(sizeof(WMTextFieldDelegate));
1035 memcpy(mPtr->tdelegate, &textFieldDelegate,
1036 sizeof(WMTextFieldDelegate));
1038 mPtr->tdelegate->data = mPtr;
1040 WMSetTextFieldDelegate(mPtr->tfield, mPtr->tdelegate);
1042 tf = mPtr->tfield;
1044 WMSetTextFieldText(tf, item->label);
1045 WMSelectTextFieldRange(tf, wmkrange(0, strlen(item->label)));
1046 WMResizeWidget(tf, mPtr->view->size.width, mPtr->itemHeight);
1047 WMMoveWidget(tf, 0, item->view->pos.y);
1048 WMMapWidget(tf);
1049 WMSetFocusToWidget(tf);
1051 mPtr->flags.isEditing = 1;
1056 /* -------------------------------------------------- */
1059 static void
1060 slideWindow(Display *dpy, Window win, int srcX, int srcY, int dstX, int dstY)
1062 double x, y, dx, dy;
1063 int i;
1064 int iterations;
1066 iterations = WMIN(25, WMAX(abs(dstX-srcX), abs(dstY-srcY)));
1068 x = srcX;
1069 y = srcY;
1071 dx = (double)(dstX-srcX)/iterations;
1072 dy = (double)(dstY-srcY)/iterations;
1074 for (i = 0; i <= iterations; i++) {
1075 XMoveWindow(dpy, win, x, y);
1076 XFlush(dpy);
1078 wusleep(800);
1080 x += dx;
1081 y += dy;
1086 static int
1087 errorHandler(Display *d, XErrorEvent *ev)
1089 /* just ignore */
1090 return 0;
1094 static WEditMenu*
1095 findMenuInWindow(Display *dpy, Window toplevel, int x, int y)
1097 Window foo, bar;
1098 Window *children;
1099 unsigned nchildren;
1100 int i;
1101 WEditMenu *menu;
1102 WMView *view;
1103 int (*oldHandler)(Display *, XErrorEvent *);
1105 view = W_GetViewForXWindow(dpy, toplevel);
1106 if (view && view->self && WMWidgetClass(view->self) == EditMenuClass) {
1107 menu = (WEditMenu*)view->self;
1108 if (menu->flags.acceptsDrop) {
1109 return menu;
1113 if (!XQueryTree(dpy, toplevel, &foo, &bar,
1114 &children, &nchildren) || children == NULL) {
1115 return NULL;
1118 oldHandler = XSetErrorHandler(errorHandler);
1120 /* first window that contains the point is the one */
1121 for (i = nchildren-1; i >= 0; i--) {
1122 XWindowAttributes attr;
1124 if (XGetWindowAttributes(dpy, children[i], &attr)
1125 && attr.map_state == IsViewable
1126 && x >= attr.x && y >= attr.y
1127 && x < attr.x + attr.width && y < attr.y + attr.height) {
1128 Window tmp;
1130 tmp = children[i];
1132 menu = findMenuInWindow(dpy, tmp, x - attr.x, y - attr.y);
1133 if (menu) {
1134 XFree(children);
1135 return menu;
1140 XSetErrorHandler(oldHandler);
1142 XFree(children);
1143 return NULL;
1147 static void
1148 handleDragOver(WEditMenu *menu, WMView *view, WEditMenuItem *item,
1149 int x, int y)
1151 WMScreen *scr = W_VIEW_SCREEN(menu->view);
1152 Window blaw;
1153 int mx, my;
1154 int offs;
1156 XTranslateCoordinates(scr->display, W_VIEW_DRAWABLE(menu->view),
1157 scr->rootWin, 0, 0, &mx, &my, &blaw);
1159 offs = menu->flags.standalone ? 0 : 1;
1161 W_MoveView(view, mx + offs, y);
1162 if (view->size.width != menu->view->size.width) {
1163 W_ResizeView(view, menu->view->size.width - 2*offs,
1164 menu->itemHeight);
1165 W_ResizeView(item->view, menu->view->size.width - 2*offs,
1166 menu->itemHeight);
1171 static void
1172 handleItemDrop(WEditMenu *menu, WEditMenuItem *item, int x, int y)
1174 WMScreen *scr = W_VIEW_SCREEN(menu->view);
1175 Window blaw;
1176 int mx, my;
1177 int index;
1179 XTranslateCoordinates(scr->display, W_VIEW_DRAWABLE(menu->view),
1180 scr->rootWin, 0, 0, &mx, &my, &blaw);
1182 index = y - my;
1183 if (menu->flags.isTitled) {
1184 index -= menu->titleHeight;
1186 index = (index + menu->itemHeight/2) / menu->itemHeight;
1187 if (index < 0)
1188 index = 0;
1190 if (menu->flags.isTitled) {
1191 index++;
1194 if (index > WMGetArrayItemCount(menu->items)) {
1195 WMAddToArray(menu->items, item);
1196 } else {
1197 WMInsertInArray(menu->items, index, item);
1200 W_ReparentView(item->view, menu->view, 0, index*menu->itemHeight);
1202 item->parent = menu;
1203 if (item->submenu) {
1204 item->submenu->parent = menu;
1207 updateMenuContents(menu);
1211 static void
1212 dragMenu(WEditMenu *menu)
1214 WMScreen *scr = W_VIEW_SCREEN(menu->view);
1215 XEvent ev;
1216 Bool done = False;
1217 int dx, dy;
1218 unsigned blau;
1219 Window blaw;
1221 XGetGeometry(scr->display, W_VIEW_DRAWABLE(menu->view), &blaw, &dx, &dy,
1222 &blau, &blau, &blau, &blau);
1224 XTranslateCoordinates(scr->display, W_VIEW_DRAWABLE(menu->view),
1225 scr->rootWin, dx, dy, &dx, &dy, &blaw);
1227 dx = menu->dragX - dx;
1228 dy = menu->dragY - dy;
1230 XGrabPointer(scr->display, scr->rootWin, False,
1231 ButtonReleaseMask|ButtonMotionMask,
1232 GrabModeAsync, GrabModeAsync, None, scr->defaultCursor,
1233 CurrentTime);
1235 if (menu->parent)
1236 WTearOffEditMenu(menu->parent, menu);
1238 while (!done) {
1239 WMNextEvent(scr->display, &ev);
1241 switch (ev.type) {
1242 case ButtonRelease:
1243 done = True;
1244 break;
1246 case MotionNotify:
1247 while (XCheckMaskEvent(scr->display, ButtonMotionMask, &ev)) ;
1249 WMMoveWidget(menu, ev.xmotion.x_root - dx,
1250 ev.xmotion.y_root - dy);
1251 break;
1253 default:
1254 WMHandleEvent(&ev);
1255 break;
1259 XUngrabPointer(scr->display, CurrentTime);
1264 static WEditMenuItem*
1265 duplicateItem(WEditMenuItem *item)
1267 WEditMenuItem *nitem;
1269 nitem = WCreateEditMenuItem(item->parent, item->label, False);
1270 if (item->pixmap)
1271 nitem->pixmap = WMRetainPixmap(item->pixmap);
1273 return nitem;
1279 static WEditMenu*
1280 duplicateMenu(WEditMenu *menu)
1282 WEditMenu *nmenu;
1283 WEditMenuItem *item;
1284 WMArrayIterator iter;
1285 Bool first = menu->flags.isTitled;
1287 nmenu = WCreateEditMenu(WMWidgetScreen(menu), WGetEditMenuTitle(menu));
1289 memcpy(&nmenu->flags, &menu->flags, sizeof(menu->flags));
1290 nmenu->delegate = menu->delegate;
1292 WM_ITERATE_ARRAY(menu->items, item, iter) {
1293 WEditMenuItem *nitem;
1295 if (first) {
1296 first = False;
1297 continue;
1300 nitem = WAddMenuItemWithTitle(nmenu, item->label);
1301 if (item->pixmap)
1302 WSetEditMenuItemImage(nitem, item->pixmap);
1304 if (menu->delegate && menu->delegate->itemCloned) {
1305 (*menu->delegate->itemCloned)(menu->delegate, menu,
1306 item, nitem);
1310 WMRealizeWidget(nmenu);
1312 return nmenu;
1317 static void
1318 dragItem(WEditMenu *menu, WEditMenuItem *item, Bool copy)
1320 static XColor black = {0, 0,0,0, DoRed|DoGreen|DoBlue};
1321 static XColor green = {0x0045b045, 0x4500,0xb000,0x4500, DoRed|DoGreen|DoBlue};
1322 static XColor back = {0, 0xffff,0xffff,0xffff, DoRed|DoGreen|DoBlue};
1323 Display *dpy = W_VIEW_DISPLAY(menu->view);
1324 WMScreen *scr = W_VIEW_SCREEN(menu->view);
1325 int x, y;
1326 int dx, dy;
1327 Bool done = False;
1328 Window blaw;
1329 int blai;
1330 unsigned blau;
1331 Window win;
1332 WMView *dview;
1333 int orix, oriy;
1334 Bool enteredMenu = False;
1335 WMSize oldSize = item->view->size;
1336 WEditMenuItem *dritem = item;
1337 WEditMenu *dmenu = NULL;
1340 if (item->flags.isTitle) {
1341 WMRaiseWidget(menu);
1343 dragMenu(menu);
1345 return;
1348 selectItem(menu, NULL);
1350 win = scr->rootWin;
1352 XTranslateCoordinates(dpy, W_VIEW_DRAWABLE(item->view), win,
1353 0, 0, &orix, &oriy, &blaw);
1355 dview = W_CreateUnmanagedTopView(scr);
1356 W_SetViewBackgroundColor(dview, scr->black);
1357 W_ResizeView(dview, W_VIEW_WIDTH(item->view), W_VIEW_HEIGHT(item->view));
1358 W_MoveView(dview, orix, oriy);
1359 W_RealizeView(dview);
1361 if (menu->flags.isFactory || copy) {
1362 dritem = duplicateItem(item);
1364 W_ReparentView(dritem->view, dview, 0, 0);
1365 WMResizeWidget(dritem, oldSize.width, oldSize.height);
1366 WMRealizeWidget(dritem);
1367 WMMapWidget(dritem);
1368 } else {
1369 W_ReparentView(item->view, dview, 0, 0);
1372 W_MapView(dview);
1374 dx = menu->dragX - orix;
1375 dy = menu->dragY - oriy;
1377 XGrabPointer(dpy, scr->rootWin, False,
1378 ButtonPressMask|ButtonReleaseMask|ButtonMotionMask,
1379 GrabModeAsync, GrabModeAsync, None, scr->defaultCursor,
1380 CurrentTime);
1382 if (menu->flags.acceptsDrop)
1383 XRecolorCursor(dpy, scr->defaultCursor, &green, &back);
1385 while (!done) {
1386 XEvent ev;
1388 WMNextEvent(dpy, &ev);
1390 switch (ev.type) {
1391 case MotionNotify:
1392 while (XCheckMaskEvent(dpy, ButtonMotionMask, &ev)) ;
1394 XQueryPointer(dpy, win, &blaw, &blaw, &blai, &blai, &x, &y, &blau);
1396 dmenu = findMenuInWindow(dpy, win, x, y);
1398 if (dmenu) {
1399 handleDragOver(dmenu, dview, dritem, x - dx, y - dy);
1400 if (!enteredMenu) {
1401 enteredMenu = True;
1402 XRecolorCursor(dpy, scr->defaultCursor, &green, &back);
1404 } else {
1405 if (enteredMenu) {
1406 W_ResizeView(dview, oldSize.width, oldSize.height);
1407 W_ResizeView(dritem->view, oldSize.width, oldSize.height);
1408 enteredMenu = False;
1409 XRecolorCursor(dpy, scr->defaultCursor, &black, &back);
1411 W_MoveView(dview, x - dx, y - dy);
1414 break;
1416 case ButtonRelease:
1417 done = True;
1418 break;
1420 default:
1421 WMHandleEvent(&ev);
1422 break;
1425 XRecolorCursor(dpy, scr->defaultCursor, &black, &back);
1427 XUngrabPointer(dpy, CurrentTime);
1430 if (!enteredMenu) {
1431 Bool rem = True;
1433 if (!menu->flags.isFactory && !copy) {
1434 W_UnmapView(dview);
1435 if (menu->delegate && menu->delegate->shouldRemoveItem) {
1436 rem = (*menu->delegate->shouldRemoveItem)(menu->delegate,
1437 menu, item);
1439 W_MapView(dview);
1442 if (!rem || menu->flags.isFactory || copy) {
1443 slideWindow(dpy, W_VIEW_DRAWABLE(dview), x-dx, y-dy, orix, oriy);
1445 if (!menu->flags.isFactory && !copy) {
1446 WRemoveEditMenuItem(menu, dritem);
1447 handleItemDrop(dmenu ? dmenu : menu, dritem, orix, oriy);
1449 } else {
1450 WRemoveEditMenuItem(menu, dritem);
1452 } else {
1453 WRemoveEditMenuItem(menu, dritem);
1455 if (menu->delegate && menu->delegate->itemCloned
1456 && (menu->flags.isFactory || copy)) {
1457 (*menu->delegate->itemCloned)(menu->delegate, menu,
1458 item, dritem);
1461 handleItemDrop(dmenu, dritem, x-dy, y-dy);
1463 if (item->submenu && (menu->flags.isFactory || copy)) {
1464 WEditMenu *submenu;
1466 submenu = duplicateMenu(item->submenu);
1467 WSetEditMenuSubmenu(dmenu, dritem, submenu);
1471 /* can't destroy now because we're being called from
1472 * the event handler of the item. and destroying now,
1473 * would mean destroying the item too in some cases.
1475 WMAddIdleHandler((WMCallback*)W_DestroyView, dview);
1480 static void
1481 handleItemClick(XEvent *event, void *data)
1483 WEditMenuItem *item = (WEditMenuItem*)data;
1484 WEditMenu *menu = item->parent;
1486 switch (event->type) {
1487 case ButtonPress:
1488 selectItem(menu, item);
1490 if (WMIsDoubleClick(event)) {
1491 editItemLabel(item);
1494 menu->flags.isDragging = 1;
1495 menu->dragX = event->xbutton.x_root;
1496 menu->dragY = event->xbutton.y_root;
1497 break;
1499 case ButtonRelease:
1500 menu->flags.isDragging = 0;
1501 break;
1503 case MotionNotify:
1504 if (menu->flags.isDragging) {
1505 if (abs(event->xbutton.x_root - menu->dragX) > 5
1506 || abs(event->xbutton.y_root - menu->dragY) > 5) {
1507 menu->flags.isDragging = 0;
1508 dragItem(menu, item, event->xbutton.state & ControlMask);
1511 break;
1516 static void
1517 destroyEditMenu(WEditMenu *mPtr)
1519 WEditMenuItem *item;
1520 WMArrayIterator iter;
1522 WMRemoveNotificationObserver(mPtr);
1524 WM_ITERATE_ARRAY(mPtr->items, item, iter) {
1525 if (item->submenu) {
1526 WMDestroyWidget(item->submenu);
1530 WMFreeArray(mPtr->items);
1532 wfree(mPtr->tdelegate);
1534 wfree(mPtr);