Turn wApplication{Activate,Deactivate} into real functions
[wmaker-crm.git] / WPrefs.app / editmenu.c
blobc5c3f4146374a7c24ccbaddb2a7d86e43843547b
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 along
18 * with this program; if not, write to the Free Software Foundation, Inc.,
19 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
22 #include <WINGs/WINGsP.h>
23 #include <WINGs/WUtil.h>
24 #include <stdlib.h>
25 #include <stdint.h>
26 #include <assert.h>
27 #include <ctype.h>
29 #include "editmenu.h"
31 typedef struct W_EditMenuItem {
32 W_Class widgetClass;
33 WMView *view;
35 struct W_EditMenu *parent;
37 char *label;
38 WMPixmap *pixmap; /* pixmap to show at left */
40 void *data;
41 WMCallback *destroyData;
43 struct W_EditMenu *submenu; /* if it's a cascade, NULL otherwise */
45 struct {
46 unsigned isTitle:1;
47 unsigned isHighlighted:1;
48 } flags;
49 } EditMenuItem;
51 typedef struct W_EditMenu {
52 W_Class widgetClass;
53 WMView *view;
55 struct W_EditMenu *parent;
57 WMArray *items; /* EditMenuItem */
59 EditMenuItem *selectedItem;
61 WMTextField *tfield;
63 WMButton *closeB;
65 int titleHeight;
66 int itemHeight;
68 WEditMenuDelegate *delegate;
70 WMTextFieldDelegate *tdelegate;
72 /* item dragging */
73 int draggedItem;
74 int dragX, dragY;
76 /* only for non-standalone menu */
77 WMSize maxSize;
78 WMSize minSize;
80 struct {
81 unsigned standalone:1;
82 unsigned isTitled:1;
84 unsigned acceptsDrop:1;
85 unsigned isFactory:1;
86 unsigned isSelectable:1;
87 unsigned isEditable:1;
89 unsigned isTornOff:1;
91 unsigned isDragging:1;
92 unsigned isEditing:1;
94 unsigned wasMapped:1;
95 } flags;
96 } EditMenu;
98 /******************** WEditMenuItem ********************/
100 static void destroyEditMenuItem(WEditMenuItem * iPtr);
101 static void paintEditMenuItem(WEditMenuItem * iPtr);
102 static void handleItemEvents(XEvent * event, void *data);
104 static void handleItemClick(XEvent * event, void *data);
106 static W_Class EditMenuItemClass = 0;
108 W_Class InitEditMenuItem(WMScreen * scr)
110 /* register our widget with WINGs and get our widget class ID */
111 if (!EditMenuItemClass) {
112 EditMenuItemClass = W_RegisterUserWidget();
115 return EditMenuItemClass;
118 WEditMenuItem *WCreateEditMenuItem(WMWidget * parent, char *title, Bool isTitle)
120 WEditMenuItem *iPtr;
121 WMScreen *scr = WMWidgetScreen(parent);
123 if (!EditMenuItemClass)
124 InitEditMenuItem(scr);
126 iPtr = wmalloc(sizeof(WEditMenuItem));
128 memset(iPtr, 0, sizeof(WEditMenuItem));
130 iPtr->widgetClass = EditMenuItemClass;
132 iPtr->view = W_CreateView(W_VIEW(parent));
133 if (!iPtr->view) {
134 wfree(iPtr);
135 return NULL;
137 iPtr->view->self = iPtr;
139 iPtr->parent = parent;
141 iPtr->label = wstrdup(title);
143 iPtr->flags.isTitle = isTitle;
145 if (isTitle) {
146 WMSetWidgetBackgroundColor(iPtr, WMBlackColor(scr));
149 WMCreateEventHandler(iPtr->view, ExposureMask | StructureNotifyMask, handleItemEvents, iPtr);
151 WMCreateEventHandler(iPtr->view, ButtonPressMask | ButtonReleaseMask
152 | ButtonMotionMask, handleItemClick, iPtr);
154 return iPtr;
157 char *WGetEditMenuItemTitle(WEditMenuItem * item)
159 return item->label;
162 void *WGetEditMenuItemData(WEditMenuItem * item)
164 return item->data;
167 void WSetEditMenuItemData(WEditMenuItem * item, void *data, WMCallback * destroyer)
169 item->data = data;
170 item->destroyData = destroyer;
173 void WSetEditMenuItemImage(WEditMenuItem * item, WMPixmap * pixmap)
175 if (item->pixmap)
176 WMReleasePixmap(item->pixmap);
177 item->pixmap = WMRetainPixmap(pixmap);
180 static void paintEditMenuItem(WEditMenuItem * iPtr)
182 WMScreen *scr = WMWidgetScreen(iPtr);
183 WMColor *color;
184 Window win = W_VIEW(iPtr)->window;
185 int w = W_VIEW(iPtr)->size.width;
186 int h = W_VIEW(iPtr)->size.height;
187 WMFont *font = (iPtr->flags.isTitle ? scr->boldFont : scr->normalFont);
189 if (!iPtr->view->flags.realized)
190 return;
192 color = scr->black;
193 if (iPtr->flags.isTitle && !iPtr->flags.isHighlighted) {
194 color = scr->white;
197 XClearWindow(scr->display, win);
199 W_DrawRelief(scr, win, 0, 0, w + 1, h, WRRaised);
201 WMDrawString(scr, win, color, font, 5, 3, iPtr->label, strlen(iPtr->label));
203 if (iPtr->pixmap) {
204 WMSize size = WMGetPixmapSize(iPtr->pixmap);
206 WMDrawPixmap(iPtr->pixmap, win, w - size.width - 5, (h - size.height) / 2);
207 } else if (iPtr->submenu) {
208 /* draw the cascade indicator */
209 XDrawLine(scr->display, win, WMColorGC(scr->darkGray), w - 11, 6, w - 6, h / 2 - 1);
210 XDrawLine(scr->display, win, WMColorGC(scr->white), w - 11, h - 8, w - 6, h / 2 - 1);
211 XDrawLine(scr->display, win, WMColorGC(scr->black), w - 12, 6, w - 12, h - 8);
215 static void highlightItem(WEditMenuItem * iPtr, Bool high)
217 if (iPtr->flags.isTitle)
218 return;
220 iPtr->flags.isHighlighted = high;
222 if (high) {
223 WMSetWidgetBackgroundColor(iPtr, WMWhiteColor(WMWidgetScreen(iPtr)));
224 } else {
225 if (!iPtr->flags.isTitle) {
226 WMSetWidgetBackgroundColor(iPtr, WMGrayColor(WMWidgetScreen(iPtr)));
227 } else {
228 WMSetWidgetBackgroundColor(iPtr, WMBlackColor(WMWidgetScreen(iPtr)));
233 static int getItemTextWidth(WEditMenuItem * iPtr)
235 WMScreen *scr = WMWidgetScreen(iPtr);
237 if (iPtr->flags.isTitle) {
238 return WMWidthOfString(scr->boldFont, iPtr->label, strlen(iPtr->label));
239 } else {
240 return WMWidthOfString(scr->normalFont, iPtr->label, strlen(iPtr->label));
244 static void handleItemEvents(XEvent * event, void *data)
246 WEditMenuItem *iPtr = (WEditMenuItem *) data;
248 switch (event->type) {
249 case Expose:
250 if (event->xexpose.count != 0)
251 break;
252 paintEditMenuItem(iPtr);
253 break;
255 case DestroyNotify:
256 destroyEditMenuItem(iPtr);
257 break;
261 static void destroyEditMenuItem(WEditMenuItem * iPtr)
263 if (iPtr->label)
264 wfree(iPtr->label);
265 if (iPtr->data && iPtr->destroyData)
266 (*iPtr->destroyData) (iPtr->data);
267 if (iPtr->submenu)
268 WMDestroyWidget(iPtr->submenu);
270 wfree(iPtr);
273 /******************** WEditMenu *******************/
275 static void destroyEditMenu(WEditMenu * mPtr);
277 static void updateMenuContents(WEditMenu * mPtr);
279 static void handleEvents(XEvent * event, void *data);
281 static void editItemLabel(WEditMenuItem * item);
283 static void stopEditItem(WEditMenu * menu, Bool apply);
285 static void deselectItem(WEditMenu * menu);
287 static W_Class EditMenuClass = 0;
289 W_Class InitEditMenu(WMScreen * scr)
291 /* register our widget with WINGs and get our widget class ID */
292 if (!EditMenuClass) {
294 EditMenuClass = W_RegisterUserWidget();
297 return EditMenuClass;
300 typedef struct {
301 int flags;
302 int window_style;
303 int window_level;
304 int reserved;
305 Pixmap miniaturize_pixmap; /* pixmap for miniaturize button */
306 Pixmap close_pixmap; /* pixmap for close button */
307 Pixmap miniaturize_mask; /* miniaturize pixmap mask */
308 Pixmap close_mask; /* close pixmap mask */
309 int extra_flags;
310 } GNUstepWMAttributes;
312 #define GSWindowStyleAttr (1<<0)
313 #define GSWindowLevelAttr (1<<1)
315 static void writeGNUstepWMAttr(WMScreen * scr, Window window, GNUstepWMAttributes * attr)
317 unsigned long data[9];
319 /* handle idiot compilers where array of CARD32 != struct of CARD32 */
320 data[0] = attr->flags;
321 data[1] = attr->window_style;
322 data[2] = attr->window_level;
323 data[3] = 0; /* reserved */
324 /* The X protocol says XIDs are 32bit */
325 data[4] = attr->miniaturize_pixmap;
326 data[5] = attr->close_pixmap;
327 data[6] = attr->miniaturize_mask;
328 data[7] = attr->close_mask;
329 data[8] = attr->extra_flags;
330 XChangeProperty(scr->display, window, scr->attribsAtom, scr->attribsAtom,
331 32, PropModeReplace, (unsigned char *)data, 9);
334 static void realizeObserver(void *self, WMNotification * not)
336 WEditMenu *menu = (WEditMenu *) self;
337 GNUstepWMAttributes attribs;
339 memset(&attribs, 0, sizeof(GNUstepWMAttributes));
340 attribs.flags = GSWindowStyleAttr | GSWindowLevelAttr;
341 attribs.window_style = WMBorderlessWindowMask;
342 attribs.window_level = WMSubmenuWindowLevel;
344 writeGNUstepWMAttr(WMWidgetScreen(menu), menu->view->window, &attribs);
347 static void itemSelectObserver(void *self, WMNotification * notif)
349 WEditMenu *menu = (WEditMenu *) self;
350 WEditMenu *rmenu;
352 rmenu = (WEditMenu *) WMGetNotificationObject(notif);
353 /* check whether rmenu is from the same hierarchy of menu? */
355 if (rmenu == menu) {
356 return;
359 if (menu->selectedItem) {
360 if (!menu->selectedItem->submenu)
361 deselectItem(menu);
362 if (menu->flags.isEditing)
363 stopEditItem(menu, False);
367 static WEditMenu *makeEditMenu(WMScreen * scr, WMWidget * parent, char *title)
369 WEditMenu *mPtr;
370 WEditMenuItem *item;
372 if (!EditMenuClass)
373 InitEditMenu(scr);
375 mPtr = wmalloc(sizeof(WEditMenu));
376 memset(mPtr, 0, sizeof(WEditMenu));
378 mPtr->widgetClass = EditMenuClass;
380 if (parent) {
381 mPtr->view = W_CreateView(W_VIEW(parent));
382 mPtr->flags.standalone = 0;
383 } else {
384 mPtr->view = W_CreateTopView(scr);
385 mPtr->flags.standalone = 1;
387 if (!mPtr->view) {
388 wfree(mPtr);
389 return NULL;
391 mPtr->view->self = mPtr;
393 mPtr->flags.isSelectable = 1;
394 mPtr->flags.isEditable = 1;
396 W_SetViewBackgroundColor(mPtr->view, scr->darkGray);
398 WMAddNotificationObserver(realizeObserver, mPtr, WMViewRealizedNotification, mPtr->view);
400 WMAddNotificationObserver(itemSelectObserver, mPtr, "EditMenuItemSelected", NULL);
402 mPtr->items = WMCreateArray(4);
404 WMCreateEventHandler(mPtr->view, ExposureMask | StructureNotifyMask, handleEvents, mPtr);
406 if (title != NULL) {
407 item = WCreateEditMenuItem(mPtr, title, True);
409 WMMapWidget(item);
410 WMAddToArray(mPtr->items, item);
412 mPtr->flags.isTitled = 1;
415 mPtr->itemHeight = WMFontHeight(scr->normalFont) + 6;
416 mPtr->titleHeight = WMFontHeight(scr->boldFont) + 8;
418 updateMenuContents(mPtr);
420 return mPtr;
423 WEditMenu *WCreateEditMenu(WMScreen * scr, char *title)
425 return makeEditMenu(scr, NULL, title);
428 WEditMenu *WCreateEditMenuPad(WMWidget * parent)
430 return makeEditMenu(WMWidgetScreen(parent), parent, NULL);
433 void WSetEditMenuDelegate(WEditMenu * mPtr, WEditMenuDelegate * delegate)
435 mPtr->delegate = delegate;
438 WEditMenuItem *WInsertMenuItemWithTitle(WEditMenu * mPtr, int index, char *title)
440 WEditMenuItem *item;
442 item = WCreateEditMenuItem(mPtr, title, False);
444 WMMapWidget(item);
446 if (index >= WMGetArrayItemCount(mPtr->items)) {
447 WMAddToArray(mPtr->items, item);
448 } else {
449 if (index < 0)
450 index = 0;
451 if (mPtr->flags.isTitled)
452 index++;
453 WMInsertInArray(mPtr->items, index, item);
456 updateMenuContents(mPtr);
458 return item;
461 WEditMenuItem *WGetEditMenuItem(WEditMenu * mPtr, int index)
463 if (index >= WMGetArrayItemCount(mPtr->items))
464 return NULL;
466 return WMGetFromArray(mPtr->items, index + (mPtr->flags.isTitled ? 1 : 0));
469 WEditMenuItem *WAddMenuItemWithTitle(WEditMenu * mPtr, char *title)
471 return WInsertMenuItemWithTitle(mPtr, WMGetArrayItemCount(mPtr->items), title);
474 void WSetEditMenuTitle(WEditMenu * mPtr, char *title)
476 WEditMenuItem *item;
478 item = WMGetFromArray(mPtr->items, 0);
480 wfree(item->label);
481 item->label = wstrdup(title);
483 updateMenuContents(mPtr);
485 WMRedisplayWidget(item);
488 char *WGetEditMenuTitle(WEditMenu * mPtr)
490 WEditMenuItem *item;
492 item = WMGetFromArray(mPtr->items, 0);
494 return item->label;
497 void WSetEditMenuAcceptsDrop(WEditMenu * mPtr, Bool flag)
499 mPtr->flags.acceptsDrop = flag;
502 void WSetEditMenuSubmenu(WEditMenu * mPtr, WEditMenuItem * item, WEditMenu * submenu)
504 item->submenu = submenu;
505 submenu->parent = mPtr;
507 paintEditMenuItem(item);
510 WEditMenu *WGetEditMenuSubmenu(WEditMenu * mPtr, WEditMenuItem * item)
512 return item->submenu;
515 void WRemoveEditMenuItem(WEditMenu * mPtr, WEditMenuItem * item)
517 if (WMRemoveFromArray(mPtr->items, item) != 0) {
518 updateMenuContents(mPtr);
522 void WSetEditMenuSelectable(WEditMenu * mPtr, Bool flag)
524 mPtr->flags.isSelectable = flag;
527 void WSetEditMenuEditable(WEditMenu * mPtr, Bool flag)
529 mPtr->flags.isEditable = flag;
532 void WSetEditMenuIsFactory(WEditMenu * mPtr, Bool flag)
534 mPtr->flags.isFactory = flag;
537 void WSetEditMenuMinSize(WEditMenu * mPtr, WMSize size)
539 mPtr->minSize.width = size.width;
540 mPtr->minSize.height = size.height;
543 void WSetEditMenuMaxSize(WEditMenu * mPtr, WMSize size)
545 mPtr->maxSize.width = size.width;
546 mPtr->maxSize.height = size.height;
549 WMPoint WGetEditMenuLocationForSubmenu(WEditMenu * mPtr, WEditMenu * submenu)
551 WMArrayIterator iter;
552 WEditMenuItem *item;
553 int y;
555 if (mPtr->flags.isTitled)
556 y = -mPtr->titleHeight;
557 else
558 y = 0;
559 WM_ITERATE_ARRAY(mPtr->items, item, iter) {
560 if (item->submenu == submenu) {
561 WMPoint pt = WMGetViewScreenPosition(mPtr->view);
563 return wmkpoint(pt.x + mPtr->view->size.width, pt.y + y);
565 y += W_VIEW_HEIGHT(item->view);
568 puts("invalid submenu passed to WGetEditMenuLocationForSubmenu()");
570 return wmkpoint(0, 0);
573 static void closeMenuAction(WMWidget * w, void *data)
575 WEditMenu *menu = (WEditMenu *) data;
577 WMAddIdleHandler(WMDestroyWidget, menu->closeB);
578 menu->closeB = NULL;
580 WEditMenuHide(menu);
583 void WTearOffEditMenu(WEditMenu * menu, WEditMenu * submenu)
585 WEditMenuItem *item;
587 submenu->flags.isTornOff = 1;
589 item = (WEditMenuItem *) WMGetFromArray(submenu->items, 0);
591 submenu->closeB = WMCreateCommandButton(item);
592 WMResizeWidget(submenu->closeB, 15, 15);
593 WMMoveWidget(submenu->closeB, W_VIEW(submenu)->size.width - 20, 3);
594 WMRealizeWidget(submenu->closeB);
595 WMSetButtonText(submenu->closeB, "X");
596 WMSetButtonAction(submenu->closeB, closeMenuAction, submenu);
597 WMMapWidget(submenu->closeB);
599 if (menu->selectedItem && menu->selectedItem->submenu == submenu)
600 deselectItem(menu);
603 Bool WEditMenuIsTornOff(WEditMenu * mPtr)
605 return mPtr->flags.isTornOff;
608 void WEditMenuHide(WEditMenu * mPtr)
610 WEditMenuItem *item;
611 int i = 0;
613 if (WMWidgetIsMapped(mPtr)) {
614 WMUnmapWidget(mPtr);
615 mPtr->flags.wasMapped = 1;
616 } else {
617 mPtr->flags.wasMapped = 0;
619 while ((item = WGetEditMenuItem(mPtr, i++))) {
620 WEditMenu *submenu;
622 submenu = WGetEditMenuSubmenu(mPtr, item);
623 if (submenu) {
624 WEditMenuHide(submenu);
629 void WEditMenuUnhide(WEditMenu * mPtr)
631 WEditMenuItem *item;
632 int i = 0;
634 if (mPtr->flags.wasMapped) {
635 WMMapWidget(mPtr);
637 while ((item = WGetEditMenuItem(mPtr, i++))) {
638 WEditMenu *submenu;
640 submenu = WGetEditMenuSubmenu(mPtr, item);
641 if (submenu) {
642 WEditMenuUnhide(submenu);
647 void WEditMenuShowAt(WEditMenu * menu, int x, int y)
649 XSizeHints *hints;
651 hints = XAllocSizeHints();
653 hints->flags = USPosition;
654 hints->x = x;
655 hints->y = y;
657 WMMoveWidget(menu, x, y);
658 XSetWMNormalHints(W_VIEW_DISPLAY(menu->view), W_VIEW_DRAWABLE(menu->view), hints);
659 XFree(hints);
661 WMMapWidget(menu);
664 static void updateMenuContents(WEditMenu * mPtr)
666 int newW, newH;
667 int w;
668 int i;
669 int iheight = mPtr->itemHeight;
670 int offs = 1;
671 WMArrayIterator iter;
672 WEditMenuItem *item;
674 newW = 0;
675 newH = offs;
677 i = 0;
678 WM_ITERATE_ARRAY(mPtr->items, item, iter) {
679 w = getItemTextWidth(item);
681 newW = WMAX(w, newW);
683 WMMoveWidget(item, offs, newH);
684 if (i == 0 && mPtr->flags.isTitled) {
685 newH += mPtr->titleHeight;
686 } else {
687 newH += iheight;
689 i = 1;
692 newW += iheight + 10;
693 newH--;
695 if (mPtr->minSize.width)
696 newW = WMAX(newW, mPtr->minSize.width);
697 if (mPtr->maxSize.width)
698 newW = WMIN(newW, mPtr->maxSize.width);
700 if (mPtr->minSize.height)
701 newH = WMAX(newH, mPtr->minSize.height);
702 if (mPtr->maxSize.height)
703 newH = WMIN(newH, mPtr->maxSize.height);
705 if (W_VIEW(mPtr)->size.width == newW && mPtr->view->size.height == newH + 1)
706 return;
708 W_ResizeView(mPtr->view, newW, newH + 1);
710 if (mPtr->closeB)
711 WMMoveWidget(mPtr->closeB, newW - 20, 3);
713 newW -= 2 * offs;
715 i = 0;
716 WM_ITERATE_ARRAY(mPtr->items, item, iter) {
717 if (i == 0 && mPtr->flags.isTitled) {
718 WMResizeWidget(item, newW, mPtr->titleHeight);
719 } else {
720 WMResizeWidget(item, newW, iheight);
722 i = 1;
726 static void deselectItem(WEditMenu * menu)
728 WEditMenu *submenu;
729 WEditMenuItem *item = menu->selectedItem;
731 highlightItem(item, False);
733 if (menu->delegate && menu->delegate->itemDeselected) {
734 (*menu->delegate->itemDeselected) (menu->delegate, menu, item);
736 submenu = item->submenu;
738 if (submenu && !WEditMenuIsTornOff(submenu)) {
739 WEditMenuHide(submenu);
742 menu->selectedItem = NULL;
745 static void selectItem(WEditMenu * menu, WEditMenuItem * item)
747 if (!menu->flags.isSelectable || menu->selectedItem == item) {
748 return;
750 if (menu->selectedItem) {
751 deselectItem(menu);
754 if (menu->flags.isEditing) {
755 stopEditItem(menu, False);
758 if (item && !item->flags.isTitle) {
759 highlightItem(item, True);
761 if (item->submenu && !W_VIEW_MAPPED(item->submenu->view)) {
762 WMPoint pt;
764 pt = WGetEditMenuLocationForSubmenu(menu, item->submenu);
766 WEditMenuShowAt(item->submenu, pt.x, pt.y);
768 item->submenu->flags.isTornOff = 0;
771 WMPostNotificationName("EditMenuItemSelected", menu, NULL);
773 if (menu->delegate && menu->delegate->itemSelected) {
774 (*menu->delegate->itemSelected) (menu->delegate, menu, item);
778 menu->selectedItem = item;
781 static void paintMenu(WEditMenu * mPtr)
783 W_View *view = mPtr->view;
785 W_DrawRelief(W_VIEW_SCREEN(view), W_VIEW_DRAWABLE(view), 0, 0,
786 W_VIEW_WIDTH(view), W_VIEW_HEIGHT(view), WRSimple);
789 static void handleEvents(XEvent * event, void *data)
791 WEditMenu *mPtr = (WEditMenu *) data;
793 switch (event->type) {
794 case DestroyNotify:
795 destroyEditMenu(mPtr);
796 break;
798 case Expose:
799 if (event->xexpose.count == 0)
800 paintMenu(mPtr);
801 break;
805 /* -------------------------- Menu Label Editing ------------------------ */
807 static void stopEditItem(WEditMenu * menu, Bool apply)
809 char *text;
811 if (apply) {
812 text = WMGetTextFieldText(menu->tfield);
814 wfree(menu->selectedItem->label);
815 menu->selectedItem->label = wstrdup(text);
817 updateMenuContents(menu);
819 if (menu->delegate && menu->delegate->itemEdited) {
820 (*menu->delegate->itemEdited) (menu->delegate, menu, menu->selectedItem);
824 WMUnmapWidget(menu->tfield);
825 menu->flags.isEditing = 0;
828 static void textEndedEditing(struct WMTextFieldDelegate *self, WMNotification * notif)
830 WEditMenu *menu = (WEditMenu *) self->data;
831 uintptr_t reason;
832 int i;
833 WEditMenuItem *item;
835 if (!menu->flags.isEditing)
836 return;
838 reason = (uintptr_t)WMGetNotificationClientData(notif);
840 switch (reason) {
841 case WMEscapeTextMovement:
842 stopEditItem(menu, False);
843 break;
845 case WMReturnTextMovement:
846 stopEditItem(menu, True);
847 break;
849 case WMTabTextMovement:
850 stopEditItem(menu, True);
852 i = WMGetFirstInArray(menu->items, menu->selectedItem);
853 item = WMGetFromArray(menu->items, i + 1);
854 if (item != NULL) {
855 selectItem(menu, item);
856 editItemLabel(item);
858 break;
860 case WMBacktabTextMovement:
861 stopEditItem(menu, True);
863 i = WMGetFirstInArray(menu->items, menu->selectedItem);
864 item = WMGetFromArray(menu->items, i - 1);
865 if (item != NULL) {
866 selectItem(menu, item);
867 editItemLabel(item);
869 break;
873 static WMTextFieldDelegate textFieldDelegate = {
874 NULL,
875 NULL,
876 NULL,
877 textEndedEditing,
878 NULL,
879 NULL
882 static void editItemLabel(WEditMenuItem * item)
884 WEditMenu *mPtr = item->parent;
885 WMTextField *tf;
887 if (!mPtr->flags.isEditable) {
888 return;
891 if (!mPtr->tfield) {
892 mPtr->tfield = WMCreateTextField(mPtr);
893 WMSetTextFieldBeveled(mPtr->tfield, False);
894 WMRealizeWidget(mPtr->tfield);
896 mPtr->tdelegate = wmalloc(sizeof(WMTextFieldDelegate));
897 memcpy(mPtr->tdelegate, &textFieldDelegate, sizeof(WMTextFieldDelegate));
899 mPtr->tdelegate->data = mPtr;
901 WMSetTextFieldDelegate(mPtr->tfield, mPtr->tdelegate);
903 tf = mPtr->tfield;
905 WMSetTextFieldText(tf, item->label);
906 WMSelectTextFieldRange(tf, wmkrange(0, strlen(item->label)));
907 WMResizeWidget(tf, mPtr->view->size.width, mPtr->itemHeight);
908 WMMoveWidget(tf, 0, item->view->pos.y);
909 WMMapWidget(tf);
910 WMSetFocusToWidget(tf);
912 mPtr->flags.isEditing = 1;
915 /* -------------------------------------------------- */
917 static void slideWindow(Display * dpy, Window win, int srcX, int srcY, int dstX, int dstY)
919 double x, y, dx, dy;
920 int i;
921 int iterations;
923 iterations = WMIN(25, WMAX(abs(dstX - srcX), abs(dstY - srcY)));
925 x = srcX;
926 y = srcY;
928 dx = (double)(dstX - srcX) / iterations;
929 dy = (double)(dstY - srcY) / iterations;
931 for (i = 0; i <= iterations; i++) {
932 XMoveWindow(dpy, win, x, y);
933 XFlush(dpy);
935 wusleep(800);
937 x += dx;
938 y += dy;
942 static int errorHandler(Display * d, XErrorEvent * ev)
944 /* just ignore */
945 return 0;
948 static WEditMenu *findMenuInWindow(Display * dpy, Window toplevel, int x, int y)
950 Window foo, bar;
951 Window *children;
952 unsigned nchildren;
953 int i;
954 WEditMenu *menu;
955 WMView *view;
956 int (*oldHandler) (Display *, XErrorEvent *);
958 view = W_GetViewForXWindow(dpy, toplevel);
959 if (view && view->self && WMWidgetClass(view->self) == EditMenuClass) {
960 menu = (WEditMenu *) view->self;
961 if (menu->flags.acceptsDrop) {
962 return menu;
966 if (!XQueryTree(dpy, toplevel, &foo, &bar, &children, &nchildren) || children == NULL) {
967 return NULL;
970 oldHandler = XSetErrorHandler(errorHandler);
972 /* first window that contains the point is the one */
973 for (i = nchildren - 1; i >= 0; i--) {
974 XWindowAttributes attr;
976 if (XGetWindowAttributes(dpy, children[i], &attr)
977 && attr.map_state == IsViewable
978 && x >= attr.x && y >= attr.y && x < attr.x + attr.width && y < attr.y + attr.height) {
979 Window tmp;
981 tmp = children[i];
983 menu = findMenuInWindow(dpy, tmp, x - attr.x, y - attr.y);
984 if (menu) {
985 XFree(children);
986 return menu;
991 XSetErrorHandler(oldHandler);
993 XFree(children);
994 return NULL;
997 static void handleDragOver(WEditMenu * menu, WMView * view, WEditMenuItem * item, int x, int y)
999 WMScreen *scr = W_VIEW_SCREEN(menu->view);
1000 Window blaw;
1001 int mx, my;
1002 int offs;
1004 XTranslateCoordinates(scr->display, W_VIEW_DRAWABLE(menu->view), scr->rootWin, 0, 0, &mx, &my, &blaw);
1006 offs = menu->flags.standalone ? 0 : 1;
1008 W_MoveView(view, mx + offs, y);
1009 if (view->size.width != menu->view->size.width) {
1010 W_ResizeView(view, menu->view->size.width - 2 * offs, menu->itemHeight);
1011 W_ResizeView(item->view, menu->view->size.width - 2 * offs, menu->itemHeight);
1015 static void handleItemDrop(WEditMenu * menu, WEditMenuItem * item, int x, int y)
1017 WMScreen *scr = W_VIEW_SCREEN(menu->view);
1018 Window blaw;
1019 int mx, my;
1020 int index;
1022 XTranslateCoordinates(scr->display, W_VIEW_DRAWABLE(menu->view), scr->rootWin, 0, 0, &mx, &my, &blaw);
1024 index = y - my;
1025 if (menu->flags.isTitled) {
1026 index -= menu->titleHeight;
1028 index = (index + menu->itemHeight / 2) / menu->itemHeight;
1029 if (index < 0)
1030 index = 0;
1032 if (menu->flags.isTitled) {
1033 index++;
1036 if (index > WMGetArrayItemCount(menu->items)) {
1037 WMAddToArray(menu->items, item);
1038 } else {
1039 WMInsertInArray(menu->items, index, item);
1042 W_ReparentView(item->view, menu->view, 0, index * menu->itemHeight);
1044 item->parent = menu;
1045 if (item->submenu) {
1046 item->submenu->parent = menu;
1049 updateMenuContents(menu);
1052 static void dragMenu(WEditMenu * menu)
1054 WMScreen *scr = W_VIEW_SCREEN(menu->view);
1055 XEvent ev;
1056 Bool done = False;
1057 int dx, dy;
1058 unsigned blau;
1059 Window blaw;
1061 XGetGeometry(scr->display, W_VIEW_DRAWABLE(menu->view), &blaw, &dx, &dy, &blau, &blau, &blau, &blau);
1063 XTranslateCoordinates(scr->display, W_VIEW_DRAWABLE(menu->view), scr->rootWin, dx, dy, &dx, &dy, &blaw);
1065 dx = menu->dragX - dx;
1066 dy = menu->dragY - dy;
1068 XGrabPointer(scr->display, scr->rootWin, False,
1069 ButtonReleaseMask | ButtonMotionMask,
1070 GrabModeAsync, GrabModeAsync, None, scr->defaultCursor, CurrentTime);
1072 if (menu->parent)
1073 WTearOffEditMenu(menu->parent, menu);
1075 while (!done) {
1076 WMNextEvent(scr->display, &ev);
1078 switch (ev.type) {
1079 case ButtonRelease:
1080 done = True;
1081 break;
1083 case MotionNotify:
1084 while (XCheckMaskEvent(scr->display, ButtonMotionMask, &ev)) ;
1086 WMMoveWidget(menu, ev.xmotion.x_root - dx, ev.xmotion.y_root - dy);
1087 break;
1089 default:
1090 WMHandleEvent(&ev);
1091 break;
1095 XUngrabPointer(scr->display, CurrentTime);
1098 static WEditMenuItem *duplicateItem(WEditMenuItem * item)
1100 WEditMenuItem *nitem;
1102 nitem = WCreateEditMenuItem(item->parent, item->label, False);
1103 if (item->pixmap)
1104 nitem->pixmap = WMRetainPixmap(item->pixmap);
1106 return nitem;
1109 static WEditMenu *duplicateMenu(WEditMenu * menu)
1111 WEditMenu *nmenu;
1112 WEditMenuItem *item;
1113 WMArrayIterator iter;
1114 Bool first = menu->flags.isTitled;
1116 nmenu = WCreateEditMenu(WMWidgetScreen(menu), WGetEditMenuTitle(menu));
1118 memcpy(&nmenu->flags, &menu->flags, sizeof(menu->flags));
1119 nmenu->delegate = menu->delegate;
1121 WM_ITERATE_ARRAY(menu->items, item, iter) {
1122 WEditMenuItem *nitem;
1124 if (first) {
1125 first = False;
1126 continue;
1129 nitem = WAddMenuItemWithTitle(nmenu, item->label);
1130 if (item->pixmap)
1131 WSetEditMenuItemImage(nitem, item->pixmap);
1133 if (menu->delegate && menu->delegate->itemCloned) {
1134 (*menu->delegate->itemCloned) (menu->delegate, menu, item, nitem);
1138 WMRealizeWidget(nmenu);
1140 return nmenu;
1143 static void dragItem(WEditMenu * menu, WEditMenuItem * item, Bool copy)
1145 static XColor black = { 0, 0, 0, 0, DoRed | DoGreen | DoBlue, 0 };
1146 static XColor green = { 0x0045b045, 0x4500, 0xb000, 0x4500, DoRed | DoGreen | DoBlue, 0 };
1147 static XColor back = { 0, 0xffff, 0xffff, 0xffff, DoRed | DoGreen | DoBlue, 0 };
1148 Display *dpy = W_VIEW_DISPLAY(menu->view);
1149 WMScreen *scr = W_VIEW_SCREEN(menu->view);
1150 int x, y;
1151 int dx, dy;
1152 Bool done = False;
1153 Window blaw;
1154 int blai;
1155 unsigned blau;
1156 Window win;
1157 WMView *dview;
1158 int orix, oriy;
1159 Bool enteredMenu = False;
1160 WMSize oldSize = item->view->size;
1161 WEditMenuItem *dritem = item;
1162 WEditMenu *dmenu = NULL;
1164 if (item->flags.isTitle) {
1165 WMRaiseWidget(menu);
1167 dragMenu(menu);
1169 return;
1172 selectItem(menu, NULL);
1174 win = scr->rootWin;
1176 XTranslateCoordinates(dpy, W_VIEW_DRAWABLE(item->view), win, 0, 0, &orix, &oriy, &blaw);
1178 dview = W_CreateUnmanagedTopView(scr);
1179 W_SetViewBackgroundColor(dview, scr->black);
1180 W_ResizeView(dview, W_VIEW_WIDTH(item->view), W_VIEW_HEIGHT(item->view));
1181 W_MoveView(dview, orix, oriy);
1182 W_RealizeView(dview);
1184 if (menu->flags.isFactory || copy) {
1185 dritem = duplicateItem(item);
1187 W_ReparentView(dritem->view, dview, 0, 0);
1188 WMResizeWidget(dritem, oldSize.width, oldSize.height);
1189 WMRealizeWidget(dritem);
1190 WMMapWidget(dritem);
1191 } else {
1192 W_ReparentView(item->view, dview, 0, 0);
1195 W_MapView(dview);
1197 dx = menu->dragX - orix;
1198 dy = menu->dragY - oriy;
1200 XGrabPointer(dpy, scr->rootWin, False,
1201 ButtonPressMask | ButtonReleaseMask | ButtonMotionMask,
1202 GrabModeAsync, GrabModeAsync, None, scr->defaultCursor, CurrentTime);
1204 if (menu->flags.acceptsDrop)
1205 XRecolorCursor(dpy, scr->defaultCursor, &green, &back);
1207 while (!done) {
1208 XEvent ev;
1210 WMNextEvent(dpy, &ev);
1212 switch (ev.type) {
1213 case MotionNotify:
1214 while (XCheckMaskEvent(dpy, ButtonMotionMask, &ev)) ;
1216 XQueryPointer(dpy, win, &blaw, &blaw, &blai, &blai, &x, &y, &blau);
1218 dmenu = findMenuInWindow(dpy, win, x, y);
1220 if (dmenu) {
1221 handleDragOver(dmenu, dview, dritem, x - dx, y - dy);
1222 if (!enteredMenu) {
1223 enteredMenu = True;
1224 XRecolorCursor(dpy, scr->defaultCursor, &green, &back);
1226 } else {
1227 if (enteredMenu) {
1228 W_ResizeView(dview, oldSize.width, oldSize.height);
1229 W_ResizeView(dritem->view, oldSize.width, oldSize.height);
1230 enteredMenu = False;
1231 XRecolorCursor(dpy, scr->defaultCursor, &black, &back);
1233 W_MoveView(dview, x - dx, y - dy);
1236 break;
1238 case ButtonRelease:
1239 done = True;
1240 break;
1242 default:
1243 WMHandleEvent(&ev);
1244 break;
1247 XRecolorCursor(dpy, scr->defaultCursor, &black, &back);
1249 XUngrabPointer(dpy, CurrentTime);
1251 if (!enteredMenu) {
1252 Bool rem = True;
1254 if (!menu->flags.isFactory && !copy) {
1255 W_UnmapView(dview);
1256 if (menu->delegate && menu->delegate->shouldRemoveItem) {
1257 rem = (*menu->delegate->shouldRemoveItem) (menu->delegate, menu, item);
1259 W_MapView(dview);
1262 if (!rem || menu->flags.isFactory || copy) {
1263 slideWindow(dpy, W_VIEW_DRAWABLE(dview), x - dx, y - dy, orix, oriy);
1265 if (!menu->flags.isFactory && !copy) {
1266 WRemoveEditMenuItem(menu, dritem);
1267 handleItemDrop(dmenu ? dmenu : menu, dritem, orix, oriy);
1269 } else {
1270 WRemoveEditMenuItem(menu, dritem);
1272 } else {
1273 WRemoveEditMenuItem(menu, dritem);
1275 if (menu->delegate && menu->delegate->itemCloned && (menu->flags.isFactory || copy)) {
1276 (*menu->delegate->itemCloned) (menu->delegate, menu, item, dritem);
1279 handleItemDrop(dmenu, dritem, x - dy, y - dy);
1281 if (item->submenu && (menu->flags.isFactory || copy)) {
1282 WEditMenu *submenu;
1284 submenu = duplicateMenu(item->submenu);
1285 WSetEditMenuSubmenu(dmenu, dritem, submenu);
1289 /* can't destroy now because we're being called from
1290 * the event handler of the item. and destroying now,
1291 * would mean destroying the item too in some cases.
1293 WMAddIdleHandler((WMCallback *) W_DestroyView, dview);
1296 static void handleItemClick(XEvent * event, void *data)
1298 WEditMenuItem *item = (WEditMenuItem *) data;
1299 WEditMenu *menu = item->parent;
1301 switch (event->type) {
1302 case ButtonPress:
1303 selectItem(menu, item);
1305 if (WMIsDoubleClick(event)) {
1306 editItemLabel(item);
1309 menu->flags.isDragging = 1;
1310 menu->dragX = event->xbutton.x_root;
1311 menu->dragY = event->xbutton.y_root;
1312 break;
1314 case ButtonRelease:
1315 menu->flags.isDragging = 0;
1316 break;
1318 case MotionNotify:
1319 if (menu->flags.isDragging) {
1320 if (abs(event->xbutton.x_root - menu->dragX) > 5
1321 || abs(event->xbutton.y_root - menu->dragY) > 5) {
1322 menu->flags.isDragging = 0;
1323 dragItem(menu, item, event->xbutton.state & ControlMask);
1326 break;
1330 static void destroyEditMenu(WEditMenu * mPtr)
1332 WMRemoveNotificationObserver(mPtr);
1334 WMFreeArray(mPtr->items);
1336 wfree(mPtr->tdelegate);
1338 wfree(mPtr);