Added reset of pointer after memory free to avoid double-free crash
[wmaker-crm.git] / WPrefs.app / editmenu.c
blob244a5bebc95ecd182d1f4422cbbf027aa14dd382
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 iPtr->widgetClass = EditMenuItemClass;
130 iPtr->view = W_CreateView(W_VIEW(parent));
131 if (!iPtr->view) {
132 wfree(iPtr);
133 return NULL;
135 iPtr->view->self = iPtr;
137 iPtr->parent = parent;
139 iPtr->label = wstrdup(title);
141 iPtr->flags.isTitle = isTitle;
143 if (isTitle) {
144 WMSetWidgetBackgroundColor(iPtr, WMBlackColor(scr));
147 WMCreateEventHandler(iPtr->view, ExposureMask | StructureNotifyMask, handleItemEvents, iPtr);
149 WMCreateEventHandler(iPtr->view, ButtonPressMask | ButtonReleaseMask
150 | ButtonMotionMask, handleItemClick, iPtr);
152 return iPtr;
155 char *WGetEditMenuItemTitle(WEditMenuItem * item)
157 return item->label;
160 void *WGetEditMenuItemData(WEditMenuItem * item)
162 return item->data;
165 void WSetEditMenuItemData(WEditMenuItem * item, void *data, WMCallback * destroyer)
167 item->data = data;
168 item->destroyData = destroyer;
171 void WSetEditMenuItemImage(WEditMenuItem * item, WMPixmap * pixmap)
173 if (item->pixmap)
174 WMReleasePixmap(item->pixmap);
175 item->pixmap = WMRetainPixmap(pixmap);
178 static void paintEditMenuItem(WEditMenuItem * iPtr)
180 WMScreen *scr = WMWidgetScreen(iPtr);
181 WMColor *color;
182 Window win = W_VIEW(iPtr)->window;
183 int w = W_VIEW(iPtr)->size.width;
184 int h = W_VIEW(iPtr)->size.height;
185 WMFont *font = (iPtr->flags.isTitle ? scr->boldFont : scr->normalFont);
187 if (!iPtr->view->flags.realized)
188 return;
190 color = scr->black;
191 if (iPtr->flags.isTitle && !iPtr->flags.isHighlighted) {
192 color = scr->white;
195 XClearWindow(scr->display, win);
197 W_DrawRelief(scr, win, 0, 0, w + 1, h, WRRaised);
199 WMDrawString(scr, win, color, font, 5, 3, iPtr->label, strlen(iPtr->label));
201 if (iPtr->pixmap) {
202 WMSize size = WMGetPixmapSize(iPtr->pixmap);
204 WMDrawPixmap(iPtr->pixmap, win, w - size.width - 5, (h - size.height) / 2);
205 } else if (iPtr->submenu) {
206 /* draw the cascade indicator */
207 XDrawLine(scr->display, win, WMColorGC(scr->darkGray), w - 11, 6, w - 6, h / 2 - 1);
208 XDrawLine(scr->display, win, WMColorGC(scr->white), w - 11, h - 8, w - 6, h / 2 - 1);
209 XDrawLine(scr->display, win, WMColorGC(scr->black), w - 12, 6, w - 12, h - 8);
213 static void highlightItem(WEditMenuItem * iPtr, Bool high)
215 if (iPtr->flags.isTitle)
216 return;
218 iPtr->flags.isHighlighted = high;
220 if (high) {
221 WMSetWidgetBackgroundColor(iPtr, WMWhiteColor(WMWidgetScreen(iPtr)));
222 } else {
223 if (!iPtr->flags.isTitle) {
224 WMSetWidgetBackgroundColor(iPtr, WMGrayColor(WMWidgetScreen(iPtr)));
225 } else {
226 WMSetWidgetBackgroundColor(iPtr, WMBlackColor(WMWidgetScreen(iPtr)));
231 static int getItemTextWidth(WEditMenuItem * iPtr)
233 WMScreen *scr = WMWidgetScreen(iPtr);
235 if (iPtr->flags.isTitle) {
236 return WMWidthOfString(scr->boldFont, iPtr->label, strlen(iPtr->label));
237 } else {
238 return WMWidthOfString(scr->normalFont, iPtr->label, strlen(iPtr->label));
242 static void handleItemEvents(XEvent * event, void *data)
244 WEditMenuItem *iPtr = (WEditMenuItem *) data;
246 switch (event->type) {
247 case Expose:
248 if (event->xexpose.count != 0)
249 break;
250 paintEditMenuItem(iPtr);
251 break;
253 case DestroyNotify:
254 destroyEditMenuItem(iPtr);
255 break;
259 static void destroyEditMenuItem(WEditMenuItem * iPtr)
261 if (iPtr->label)
262 wfree(iPtr->label);
263 if (iPtr->data && iPtr->destroyData)
264 (*iPtr->destroyData) (iPtr->data);
265 if (iPtr->submenu)
266 WMDestroyWidget(iPtr->submenu);
268 wfree(iPtr);
271 /******************** WEditMenu *******************/
273 static void destroyEditMenu(WEditMenu * mPtr);
275 static void updateMenuContents(WEditMenu * mPtr);
277 static void handleEvents(XEvent * event, void *data);
279 static void editItemLabel(WEditMenuItem * item);
281 static void stopEditItem(WEditMenu * menu, Bool apply);
283 static void deselectItem(WEditMenu * menu);
285 static W_Class EditMenuClass = 0;
287 W_Class InitEditMenu(WMScreen * scr)
289 /* register our widget with WINGs and get our widget class ID */
290 if (!EditMenuClass) {
292 EditMenuClass = W_RegisterUserWidget();
295 return EditMenuClass;
298 typedef struct {
299 int flags;
300 int window_style;
301 int window_level;
302 int reserved;
303 Pixmap miniaturize_pixmap; /* pixmap for miniaturize button */
304 Pixmap close_pixmap; /* pixmap for close button */
305 Pixmap miniaturize_mask; /* miniaturize pixmap mask */
306 Pixmap close_mask; /* close pixmap mask */
307 int extra_flags;
308 } GNUstepWMAttributes;
310 #define GSWindowStyleAttr (1<<0)
311 #define GSWindowLevelAttr (1<<1)
313 static void writeGNUstepWMAttr(WMScreen * scr, Window window, GNUstepWMAttributes * attr)
315 unsigned long data[9];
317 /* handle idiot compilers where array of CARD32 != struct of CARD32 */
318 data[0] = attr->flags;
319 data[1] = attr->window_style;
320 data[2] = attr->window_level;
321 data[3] = 0; /* reserved */
322 /* The X protocol says XIDs are 32bit */
323 data[4] = attr->miniaturize_pixmap;
324 data[5] = attr->close_pixmap;
325 data[6] = attr->miniaturize_mask;
326 data[7] = attr->close_mask;
327 data[8] = attr->extra_flags;
328 XChangeProperty(scr->display, window, scr->attribsAtom, scr->attribsAtom,
329 32, PropModeReplace, (unsigned char *)data, 9);
332 static void realizeObserver(void *self, WMNotification * not)
334 WEditMenu *menu = (WEditMenu *) self;
335 GNUstepWMAttributes attribs;
337 memset(&attribs, 0, sizeof(GNUstepWMAttributes));
338 attribs.flags = GSWindowStyleAttr | GSWindowLevelAttr;
339 attribs.window_style = WMBorderlessWindowMask;
340 attribs.window_level = WMSubmenuWindowLevel;
342 writeGNUstepWMAttr(WMWidgetScreen(menu), menu->view->window, &attribs);
345 static void itemSelectObserver(void *self, WMNotification * notif)
347 WEditMenu *menu = (WEditMenu *) self;
348 WEditMenu *rmenu;
350 rmenu = (WEditMenu *) WMGetNotificationObject(notif);
351 /* check whether rmenu is from the same hierarchy of menu? */
353 if (rmenu == menu) {
354 return;
357 if (menu->selectedItem) {
358 if (!menu->selectedItem->submenu)
359 deselectItem(menu);
360 if (menu->flags.isEditing)
361 stopEditItem(menu, False);
365 static WEditMenu *makeEditMenu(WMScreen * scr, WMWidget * parent, char *title)
367 WEditMenu *mPtr;
368 WEditMenuItem *item;
370 if (!EditMenuClass)
371 InitEditMenu(scr);
373 mPtr = wmalloc(sizeof(WEditMenu));
375 mPtr->widgetClass = EditMenuClass;
377 if (parent) {
378 mPtr->view = W_CreateView(W_VIEW(parent));
379 mPtr->flags.standalone = 0;
380 } else {
381 mPtr->view = W_CreateTopView(scr);
382 mPtr->flags.standalone = 1;
384 if (!mPtr->view) {
385 wfree(mPtr);
386 return NULL;
388 mPtr->view->self = mPtr;
390 mPtr->flags.isSelectable = 1;
391 mPtr->flags.isEditable = 1;
393 W_SetViewBackgroundColor(mPtr->view, scr->darkGray);
395 WMAddNotificationObserver(realizeObserver, mPtr, WMViewRealizedNotification, mPtr->view);
397 WMAddNotificationObserver(itemSelectObserver, mPtr, "EditMenuItemSelected", NULL);
399 mPtr->items = WMCreateArray(4);
401 WMCreateEventHandler(mPtr->view, ExposureMask | StructureNotifyMask, handleEvents, mPtr);
403 if (title != NULL) {
404 item = WCreateEditMenuItem(mPtr, title, True);
406 WMMapWidget(item);
407 WMAddToArray(mPtr->items, item);
409 mPtr->flags.isTitled = 1;
412 mPtr->itemHeight = WMFontHeight(scr->normalFont) + 6;
413 mPtr->titleHeight = WMFontHeight(scr->boldFont) + 8;
415 updateMenuContents(mPtr);
417 return mPtr;
420 WEditMenu *WCreateEditMenu(WMScreen * scr, char *title)
422 return makeEditMenu(scr, NULL, title);
425 WEditMenu *WCreateEditMenuPad(WMWidget * parent)
427 return makeEditMenu(WMWidgetScreen(parent), parent, NULL);
430 void WSetEditMenuDelegate(WEditMenu * mPtr, WEditMenuDelegate * delegate)
432 mPtr->delegate = delegate;
435 WEditMenuItem *WInsertMenuItemWithTitle(WEditMenu * mPtr, int index, char *title)
437 WEditMenuItem *item;
439 item = WCreateEditMenuItem(mPtr, title, False);
441 WMMapWidget(item);
443 if (index >= WMGetArrayItemCount(mPtr->items)) {
444 WMAddToArray(mPtr->items, item);
445 } else {
446 if (index < 0)
447 index = 0;
448 if (mPtr->flags.isTitled)
449 index++;
450 WMInsertInArray(mPtr->items, index, item);
453 updateMenuContents(mPtr);
455 return item;
458 WEditMenuItem *WGetEditMenuItem(WEditMenu * mPtr, int index)
460 if (index >= WMGetArrayItemCount(mPtr->items))
461 return NULL;
463 return WMGetFromArray(mPtr->items, index + (mPtr->flags.isTitled ? 1 : 0));
466 WEditMenuItem *WAddMenuItemWithTitle(WEditMenu * mPtr, char *title)
468 return WInsertMenuItemWithTitle(mPtr, WMGetArrayItemCount(mPtr->items), title);
471 void WSetEditMenuTitle(WEditMenu * mPtr, char *title)
473 WEditMenuItem *item;
475 item = WMGetFromArray(mPtr->items, 0);
477 wfree(item->label);
478 item->label = wstrdup(title);
480 updateMenuContents(mPtr);
482 WMRedisplayWidget(item);
485 char *WGetEditMenuTitle(WEditMenu * mPtr)
487 WEditMenuItem *item;
489 item = WMGetFromArray(mPtr->items, 0);
491 return item->label;
494 void WSetEditMenuAcceptsDrop(WEditMenu * mPtr, Bool flag)
496 mPtr->flags.acceptsDrop = flag;
499 void WSetEditMenuSubmenu(WEditMenu * mPtr, WEditMenuItem * item, WEditMenu * submenu)
501 item->submenu = submenu;
502 submenu->parent = mPtr;
504 paintEditMenuItem(item);
507 WEditMenu *WGetEditMenuSubmenu(WEditMenu * mPtr, WEditMenuItem * item)
509 return item->submenu;
512 void WRemoveEditMenuItem(WEditMenu * mPtr, WEditMenuItem * item)
514 if (WMRemoveFromArray(mPtr->items, item) != 0) {
515 updateMenuContents(mPtr);
519 void WSetEditMenuSelectable(WEditMenu * mPtr, Bool flag)
521 mPtr->flags.isSelectable = flag;
524 void WSetEditMenuEditable(WEditMenu * mPtr, Bool flag)
526 mPtr->flags.isEditable = flag;
529 void WSetEditMenuIsFactory(WEditMenu * mPtr, Bool flag)
531 mPtr->flags.isFactory = flag;
534 void WSetEditMenuMinSize(WEditMenu * mPtr, WMSize size)
536 mPtr->minSize.width = size.width;
537 mPtr->minSize.height = size.height;
540 void WSetEditMenuMaxSize(WEditMenu * mPtr, WMSize size)
542 mPtr->maxSize.width = size.width;
543 mPtr->maxSize.height = size.height;
546 WMPoint WGetEditMenuLocationForSubmenu(WEditMenu * mPtr, WEditMenu * submenu)
548 WMArrayIterator iter;
549 WEditMenuItem *item;
550 int y;
552 if (mPtr->flags.isTitled)
553 y = -mPtr->titleHeight;
554 else
555 y = 0;
556 WM_ITERATE_ARRAY(mPtr->items, item, iter) {
557 if (item->submenu == submenu) {
558 WMPoint pt = WMGetViewScreenPosition(mPtr->view);
560 return wmkpoint(pt.x + mPtr->view->size.width, pt.y + y);
562 y += W_VIEW_HEIGHT(item->view);
565 puts("invalid submenu passed to WGetEditMenuLocationForSubmenu()");
567 return wmkpoint(0, 0);
570 static void closeMenuAction(WMWidget * w, void *data)
572 WEditMenu *menu = (WEditMenu *) data;
574 WMAddIdleHandler(WMDestroyWidget, menu->closeB);
575 menu->closeB = NULL;
577 WEditMenuHide(menu);
580 void WTearOffEditMenu(WEditMenu * menu, WEditMenu * submenu)
582 WEditMenuItem *item;
584 submenu->flags.isTornOff = 1;
586 item = (WEditMenuItem *) WMGetFromArray(submenu->items, 0);
588 submenu->closeB = WMCreateCommandButton(item);
589 WMResizeWidget(submenu->closeB, 15, 15);
590 WMMoveWidget(submenu->closeB, W_VIEW(submenu)->size.width - 20, 3);
591 WMRealizeWidget(submenu->closeB);
592 WMSetButtonText(submenu->closeB, "X");
593 WMSetButtonAction(submenu->closeB, closeMenuAction, submenu);
594 WMMapWidget(submenu->closeB);
596 if (menu->selectedItem && menu->selectedItem->submenu == submenu)
597 deselectItem(menu);
600 Bool WEditMenuIsTornOff(WEditMenu * mPtr)
602 return mPtr->flags.isTornOff;
605 void WEditMenuHide(WEditMenu * mPtr)
607 WEditMenuItem *item;
608 int i = 0;
610 if (WMWidgetIsMapped(mPtr)) {
611 WMUnmapWidget(mPtr);
612 mPtr->flags.wasMapped = 1;
613 } else {
614 mPtr->flags.wasMapped = 0;
616 while ((item = WGetEditMenuItem(mPtr, i++))) {
617 WEditMenu *submenu;
619 submenu = WGetEditMenuSubmenu(mPtr, item);
620 if (submenu) {
621 WEditMenuHide(submenu);
626 void WEditMenuUnhide(WEditMenu * mPtr)
628 WEditMenuItem *item;
629 int i = 0;
631 if (mPtr->flags.wasMapped) {
632 WMMapWidget(mPtr);
634 while ((item = WGetEditMenuItem(mPtr, i++))) {
635 WEditMenu *submenu;
637 submenu = WGetEditMenuSubmenu(mPtr, item);
638 if (submenu) {
639 WEditMenuUnhide(submenu);
644 void WEditMenuShowAt(WEditMenu * menu, int x, int y)
646 XSizeHints *hints;
648 hints = XAllocSizeHints();
650 hints->flags = USPosition;
651 hints->x = x;
652 hints->y = y;
654 WMMoveWidget(menu, x, y);
655 XSetWMNormalHints(W_VIEW_DISPLAY(menu->view), W_VIEW_DRAWABLE(menu->view), hints);
656 XFree(hints);
658 WMMapWidget(menu);
661 static void updateMenuContents(WEditMenu * mPtr)
663 int newW, newH;
664 int w;
665 int i;
666 int iheight = mPtr->itemHeight;
667 int offs = 1;
668 WMArrayIterator iter;
669 WEditMenuItem *item;
671 newW = 0;
672 newH = offs;
674 i = 0;
675 WM_ITERATE_ARRAY(mPtr->items, item, iter) {
676 w = getItemTextWidth(item);
678 newW = WMAX(w, newW);
680 WMMoveWidget(item, offs, newH);
681 if (i == 0 && mPtr->flags.isTitled) {
682 newH += mPtr->titleHeight;
683 } else {
684 newH += iheight;
686 i = 1;
689 newW += iheight + 10;
690 newH--;
692 if (mPtr->minSize.width)
693 newW = WMAX(newW, mPtr->minSize.width);
694 if (mPtr->maxSize.width)
695 newW = WMIN(newW, mPtr->maxSize.width);
697 if (mPtr->minSize.height)
698 newH = WMAX(newH, mPtr->minSize.height);
699 if (mPtr->maxSize.height)
700 newH = WMIN(newH, mPtr->maxSize.height);
702 if (W_VIEW(mPtr)->size.width == newW && mPtr->view->size.height == newH + 1)
703 return;
705 W_ResizeView(mPtr->view, newW, newH + 1);
707 if (mPtr->closeB)
708 WMMoveWidget(mPtr->closeB, newW - 20, 3);
710 newW -= 2 * offs;
712 i = 0;
713 WM_ITERATE_ARRAY(mPtr->items, item, iter) {
714 if (i == 0 && mPtr->flags.isTitled) {
715 WMResizeWidget(item, newW, mPtr->titleHeight);
716 } else {
717 WMResizeWidget(item, newW, iheight);
719 i = 1;
723 static void deselectItem(WEditMenu * menu)
725 WEditMenu *submenu;
726 WEditMenuItem *item = menu->selectedItem;
728 highlightItem(item, False);
730 if (menu->delegate && menu->delegate->itemDeselected) {
731 (*menu->delegate->itemDeselected) (menu->delegate, menu, item);
733 submenu = item->submenu;
735 if (submenu && !WEditMenuIsTornOff(submenu)) {
736 WEditMenuHide(submenu);
739 menu->selectedItem = NULL;
742 static void selectItem(WEditMenu * menu, WEditMenuItem * item)
744 if (!menu->flags.isSelectable || menu->selectedItem == item) {
745 return;
747 if (menu->selectedItem) {
748 deselectItem(menu);
751 if (menu->flags.isEditing) {
752 stopEditItem(menu, False);
755 if (item && !item->flags.isTitle) {
756 highlightItem(item, True);
758 if (item->submenu && !W_VIEW_MAPPED(item->submenu->view)) {
759 WMPoint pt;
761 pt = WGetEditMenuLocationForSubmenu(menu, item->submenu);
763 WEditMenuShowAt(item->submenu, pt.x, pt.y);
765 item->submenu->flags.isTornOff = 0;
768 WMPostNotificationName("EditMenuItemSelected", menu, NULL);
770 if (menu->delegate && menu->delegate->itemSelected) {
771 (*menu->delegate->itemSelected) (menu->delegate, menu, item);
775 menu->selectedItem = item;
778 static void paintMenu(WEditMenu * mPtr)
780 W_View *view = mPtr->view;
782 W_DrawRelief(W_VIEW_SCREEN(view), W_VIEW_DRAWABLE(view), 0, 0,
783 W_VIEW_WIDTH(view), W_VIEW_HEIGHT(view), WRSimple);
786 static void handleEvents(XEvent * event, void *data)
788 WEditMenu *mPtr = (WEditMenu *) data;
790 switch (event->type) {
791 case DestroyNotify:
792 destroyEditMenu(mPtr);
793 break;
795 case Expose:
796 if (event->xexpose.count == 0)
797 paintMenu(mPtr);
798 break;
802 /* -------------------------- Menu Label Editing ------------------------ */
804 static void stopEditItem(WEditMenu * menu, Bool apply)
806 char *text;
808 if (apply) {
809 text = WMGetTextFieldText(menu->tfield);
811 wfree(menu->selectedItem->label);
812 menu->selectedItem->label = wstrdup(text);
814 updateMenuContents(menu);
816 if (menu->delegate && menu->delegate->itemEdited) {
817 (*menu->delegate->itemEdited) (menu->delegate, menu, menu->selectedItem);
821 WMUnmapWidget(menu->tfield);
822 menu->flags.isEditing = 0;
825 static void textEndedEditing(struct WMTextFieldDelegate *self, WMNotification * notif)
827 WEditMenu *menu = (WEditMenu *) self->data;
828 uintptr_t reason;
829 int i;
830 WEditMenuItem *item;
832 if (!menu->flags.isEditing)
833 return;
835 reason = (uintptr_t)WMGetNotificationClientData(notif);
837 switch (reason) {
838 case WMEscapeTextMovement:
839 stopEditItem(menu, False);
840 break;
842 case WMReturnTextMovement:
843 stopEditItem(menu, True);
844 break;
846 case WMTabTextMovement:
847 stopEditItem(menu, True);
849 i = WMGetFirstInArray(menu->items, menu->selectedItem);
850 item = WMGetFromArray(menu->items, i + 1);
851 if (item != NULL) {
852 selectItem(menu, item);
853 editItemLabel(item);
855 break;
857 case WMBacktabTextMovement:
858 stopEditItem(menu, True);
860 i = WMGetFirstInArray(menu->items, menu->selectedItem);
861 item = WMGetFromArray(menu->items, i - 1);
862 if (item != NULL) {
863 selectItem(menu, item);
864 editItemLabel(item);
866 break;
870 static WMTextFieldDelegate textFieldDelegate = {
871 NULL,
872 NULL,
873 NULL,
874 textEndedEditing,
875 NULL,
876 NULL
879 static void editItemLabel(WEditMenuItem * item)
881 WEditMenu *mPtr = item->parent;
882 WMTextField *tf;
884 if (!mPtr->flags.isEditable) {
885 return;
888 if (!mPtr->tfield) {
889 mPtr->tfield = WMCreateTextField(mPtr);
890 WMSetTextFieldBeveled(mPtr->tfield, False);
891 WMRealizeWidget(mPtr->tfield);
893 mPtr->tdelegate = wmalloc(sizeof(WMTextFieldDelegate));
894 memcpy(mPtr->tdelegate, &textFieldDelegate, sizeof(WMTextFieldDelegate));
896 mPtr->tdelegate->data = mPtr;
898 WMSetTextFieldDelegate(mPtr->tfield, mPtr->tdelegate);
900 tf = mPtr->tfield;
902 WMSetTextFieldText(tf, item->label);
903 WMSelectTextFieldRange(tf, wmkrange(0, strlen(item->label)));
904 WMResizeWidget(tf, mPtr->view->size.width, mPtr->itemHeight);
905 WMMoveWidget(tf, 0, item->view->pos.y);
906 WMMapWidget(tf);
907 WMSetFocusToWidget(tf);
909 mPtr->flags.isEditing = 1;
912 /* -------------------------------------------------- */
914 static void slideWindow(Display * dpy, Window win, int srcX, int srcY, int dstX, int dstY)
916 double x, y, dx, dy;
917 int i;
918 int iterations;
920 iterations = WMIN(25, WMAX(abs(dstX - srcX), abs(dstY - srcY)));
922 x = srcX;
923 y = srcY;
925 dx = (double)(dstX - srcX) / iterations;
926 dy = (double)(dstY - srcY) / iterations;
928 for (i = 0; i <= iterations; i++) {
929 XMoveWindow(dpy, win, x, y);
930 XFlush(dpy);
932 wusleep(800);
934 x += dx;
935 y += dy;
939 static int errorHandler(Display * d, XErrorEvent * ev)
941 /* just ignore */
942 return 0;
945 static WEditMenu *findMenuInWindow(Display * dpy, Window toplevel, int x, int y)
947 Window foo, bar;
948 Window *children;
949 unsigned nchildren;
950 int i;
951 WEditMenu *menu;
952 WMView *view;
953 int (*oldHandler) (Display *, XErrorEvent *);
955 view = W_GetViewForXWindow(dpy, toplevel);
956 if (view && view->self && WMWidgetClass(view->self) == EditMenuClass) {
957 menu = (WEditMenu *) view->self;
958 if (menu->flags.acceptsDrop) {
959 return menu;
963 if (!XQueryTree(dpy, toplevel, &foo, &bar, &children, &nchildren) || children == NULL) {
964 return NULL;
967 oldHandler = XSetErrorHandler(errorHandler);
969 /* first window that contains the point is the one */
970 for (i = nchildren - 1; i >= 0; i--) {
971 XWindowAttributes attr;
973 if (XGetWindowAttributes(dpy, children[i], &attr)
974 && attr.map_state == IsViewable
975 && x >= attr.x && y >= attr.y && x < attr.x + attr.width && y < attr.y + attr.height) {
976 Window tmp;
978 tmp = children[i];
980 menu = findMenuInWindow(dpy, tmp, x - attr.x, y - attr.y);
981 if (menu) {
982 XFree(children);
983 return menu;
988 XSetErrorHandler(oldHandler);
990 XFree(children);
991 return NULL;
994 static void handleDragOver(WEditMenu * menu, WMView * view, WEditMenuItem * item, int x, int y)
996 WMScreen *scr = W_VIEW_SCREEN(menu->view);
997 Window blaw;
998 int mx, my;
999 int offs;
1001 XTranslateCoordinates(scr->display, W_VIEW_DRAWABLE(menu->view), scr->rootWin, 0, 0, &mx, &my, &blaw);
1003 offs = menu->flags.standalone ? 0 : 1;
1005 W_MoveView(view, mx + offs, y);
1006 if (view->size.width != menu->view->size.width) {
1007 W_ResizeView(view, menu->view->size.width - 2 * offs, menu->itemHeight);
1008 W_ResizeView(item->view, menu->view->size.width - 2 * offs, menu->itemHeight);
1012 static void handleItemDrop(WEditMenu * menu, WEditMenuItem * item, int x, int y)
1014 WMScreen *scr = W_VIEW_SCREEN(menu->view);
1015 Window blaw;
1016 int mx, my;
1017 int index;
1019 XTranslateCoordinates(scr->display, W_VIEW_DRAWABLE(menu->view), scr->rootWin, 0, 0, &mx, &my, &blaw);
1021 index = y - my;
1022 if (menu->flags.isTitled) {
1023 index -= menu->titleHeight;
1025 index = (index + menu->itemHeight / 2) / menu->itemHeight;
1026 if (index < 0)
1027 index = 0;
1029 if (menu->flags.isTitled) {
1030 index++;
1033 if (index > WMGetArrayItemCount(menu->items)) {
1034 WMAddToArray(menu->items, item);
1035 } else {
1036 WMInsertInArray(menu->items, index, item);
1039 W_ReparentView(item->view, menu->view, 0, index * menu->itemHeight);
1041 item->parent = menu;
1042 if (item->submenu) {
1043 item->submenu->parent = menu;
1046 updateMenuContents(menu);
1049 static void dragMenu(WEditMenu * menu)
1051 WMScreen *scr = W_VIEW_SCREEN(menu->view);
1052 XEvent ev;
1053 Bool done = False;
1054 int dx, dy;
1055 unsigned blau;
1056 Window blaw;
1058 XGetGeometry(scr->display, W_VIEW_DRAWABLE(menu->view), &blaw, &dx, &dy, &blau, &blau, &blau, &blau);
1060 XTranslateCoordinates(scr->display, W_VIEW_DRAWABLE(menu->view), scr->rootWin, dx, dy, &dx, &dy, &blaw);
1062 dx = menu->dragX - dx;
1063 dy = menu->dragY - dy;
1065 XGrabPointer(scr->display, scr->rootWin, False,
1066 ButtonReleaseMask | ButtonMotionMask,
1067 GrabModeAsync, GrabModeAsync, None, scr->defaultCursor, CurrentTime);
1069 if (menu->parent)
1070 WTearOffEditMenu(menu->parent, menu);
1072 while (!done) {
1073 WMNextEvent(scr->display, &ev);
1075 switch (ev.type) {
1076 case ButtonRelease:
1077 done = True;
1078 break;
1080 case MotionNotify:
1081 while (XCheckMaskEvent(scr->display, ButtonMotionMask, &ev)) ;
1083 WMMoveWidget(menu, ev.xmotion.x_root - dx, ev.xmotion.y_root - dy);
1084 break;
1086 default:
1087 WMHandleEvent(&ev);
1088 break;
1092 XUngrabPointer(scr->display, CurrentTime);
1095 static WEditMenuItem *duplicateItem(WEditMenuItem * item)
1097 WEditMenuItem *nitem;
1099 nitem = WCreateEditMenuItem(item->parent, item->label, False);
1100 if (item->pixmap)
1101 nitem->pixmap = WMRetainPixmap(item->pixmap);
1103 return nitem;
1106 static WEditMenu *duplicateMenu(WEditMenu * menu)
1108 WEditMenu *nmenu;
1109 WEditMenuItem *item;
1110 WMArrayIterator iter;
1111 Bool first = menu->flags.isTitled;
1113 nmenu = WCreateEditMenu(WMWidgetScreen(menu), WGetEditMenuTitle(menu));
1115 memcpy(&nmenu->flags, &menu->flags, sizeof(menu->flags));
1116 nmenu->delegate = menu->delegate;
1118 WM_ITERATE_ARRAY(menu->items, item, iter) {
1119 WEditMenuItem *nitem;
1121 if (first) {
1122 first = False;
1123 continue;
1126 nitem = WAddMenuItemWithTitle(nmenu, item->label);
1127 if (item->pixmap)
1128 WSetEditMenuItemImage(nitem, item->pixmap);
1130 if (menu->delegate && menu->delegate->itemCloned) {
1131 (*menu->delegate->itemCloned) (menu->delegate, menu, item, nitem);
1135 WMRealizeWidget(nmenu);
1137 return nmenu;
1140 static void dragItem(WEditMenu * menu, WEditMenuItem * item, Bool copy)
1142 static XColor black = { 0, 0, 0, 0, DoRed | DoGreen | DoBlue, 0 };
1143 static XColor green = { 0x0045b045, 0x4500, 0xb000, 0x4500, DoRed | DoGreen | DoBlue, 0 };
1144 static XColor back = { 0, 0xffff, 0xffff, 0xffff, DoRed | DoGreen | DoBlue, 0 };
1145 Display *dpy = W_VIEW_DISPLAY(menu->view);
1146 WMScreen *scr = W_VIEW_SCREEN(menu->view);
1147 int x, y;
1148 int dx, dy;
1149 Bool done = False;
1150 Window blaw;
1151 int blai;
1152 unsigned blau;
1153 Window win;
1154 WMView *dview;
1155 int orix, oriy;
1156 Bool enteredMenu = False;
1157 WMSize oldSize = item->view->size;
1158 WEditMenuItem *dritem = item;
1159 WEditMenu *dmenu = NULL;
1161 if (item->flags.isTitle) {
1162 WMRaiseWidget(menu);
1164 dragMenu(menu);
1166 return;
1169 selectItem(menu, NULL);
1171 win = scr->rootWin;
1173 XTranslateCoordinates(dpy, W_VIEW_DRAWABLE(item->view), win, 0, 0, &orix, &oriy, &blaw);
1175 dview = W_CreateUnmanagedTopView(scr);
1176 W_SetViewBackgroundColor(dview, scr->black);
1177 W_ResizeView(dview, W_VIEW_WIDTH(item->view), W_VIEW_HEIGHT(item->view));
1178 W_MoveView(dview, orix, oriy);
1179 W_RealizeView(dview);
1181 if (menu->flags.isFactory || copy) {
1182 dritem = duplicateItem(item);
1184 W_ReparentView(dritem->view, dview, 0, 0);
1185 WMResizeWidget(dritem, oldSize.width, oldSize.height);
1186 WMRealizeWidget(dritem);
1187 WMMapWidget(dritem);
1188 } else {
1189 W_ReparentView(item->view, dview, 0, 0);
1192 W_MapView(dview);
1194 dx = menu->dragX - orix;
1195 dy = menu->dragY - oriy;
1197 XGrabPointer(dpy, scr->rootWin, False,
1198 ButtonPressMask | ButtonReleaseMask | ButtonMotionMask,
1199 GrabModeAsync, GrabModeAsync, None, scr->defaultCursor, CurrentTime);
1201 if (menu->flags.acceptsDrop)
1202 XRecolorCursor(dpy, scr->defaultCursor, &green, &back);
1204 while (!done) {
1205 XEvent ev;
1207 WMNextEvent(dpy, &ev);
1209 switch (ev.type) {
1210 case MotionNotify:
1211 while (XCheckMaskEvent(dpy, ButtonMotionMask, &ev)) ;
1213 XQueryPointer(dpy, win, &blaw, &blaw, &blai, &blai, &x, &y, &blau);
1215 dmenu = findMenuInWindow(dpy, win, x, y);
1217 if (dmenu) {
1218 handleDragOver(dmenu, dview, dritem, x - dx, y - dy);
1219 if (!enteredMenu) {
1220 enteredMenu = True;
1221 XRecolorCursor(dpy, scr->defaultCursor, &green, &back);
1223 } else {
1224 if (enteredMenu) {
1225 W_ResizeView(dview, oldSize.width, oldSize.height);
1226 W_ResizeView(dritem->view, oldSize.width, oldSize.height);
1227 enteredMenu = False;
1228 XRecolorCursor(dpy, scr->defaultCursor, &black, &back);
1230 W_MoveView(dview, x - dx, y - dy);
1233 break;
1235 case ButtonRelease:
1236 done = True;
1237 break;
1239 default:
1240 WMHandleEvent(&ev);
1241 break;
1244 XRecolorCursor(dpy, scr->defaultCursor, &black, &back);
1246 XUngrabPointer(dpy, CurrentTime);
1248 if (!enteredMenu) {
1249 Bool rem = True;
1251 if (!menu->flags.isFactory && !copy) {
1252 W_UnmapView(dview);
1253 if (menu->delegate && menu->delegate->shouldRemoveItem) {
1254 rem = (*menu->delegate->shouldRemoveItem) (menu->delegate, menu, item);
1256 W_MapView(dview);
1259 if (!rem || menu->flags.isFactory || copy) {
1260 slideWindow(dpy, W_VIEW_DRAWABLE(dview), x - dx, y - dy, orix, oriy);
1262 if (!menu->flags.isFactory && !copy) {
1263 WRemoveEditMenuItem(menu, dritem);
1264 handleItemDrop(dmenu ? dmenu : menu, dritem, orix, oriy);
1266 } else {
1267 WRemoveEditMenuItem(menu, dritem);
1269 } else {
1270 WRemoveEditMenuItem(menu, dritem);
1272 if (menu->delegate && menu->delegate->itemCloned && (menu->flags.isFactory || copy)) {
1273 (*menu->delegate->itemCloned) (menu->delegate, menu, item, dritem);
1276 handleItemDrop(dmenu, dritem, x - dy, y - dy);
1278 if (item->submenu && (menu->flags.isFactory || copy)) {
1279 WEditMenu *submenu;
1281 submenu = duplicateMenu(item->submenu);
1282 WSetEditMenuSubmenu(dmenu, dritem, submenu);
1286 /* can't destroy now because we're being called from
1287 * the event handler of the item. and destroying now,
1288 * would mean destroying the item too in some cases.
1290 WMAddIdleHandler((WMCallback *) W_DestroyView, dview);
1293 static void handleItemClick(XEvent * event, void *data)
1295 WEditMenuItem *item = (WEditMenuItem *) data;
1296 WEditMenu *menu = item->parent;
1298 switch (event->type) {
1299 case ButtonPress:
1300 selectItem(menu, item);
1302 if (WMIsDoubleClick(event)) {
1303 editItemLabel(item);
1306 menu->flags.isDragging = 1;
1307 menu->dragX = event->xbutton.x_root;
1308 menu->dragY = event->xbutton.y_root;
1309 break;
1311 case ButtonRelease:
1312 menu->flags.isDragging = 0;
1313 break;
1315 case MotionNotify:
1316 if (menu->flags.isDragging) {
1317 if (abs(event->xbutton.x_root - menu->dragX) > 5
1318 || abs(event->xbutton.y_root - menu->dragY) > 5) {
1319 menu->flags.isDragging = 0;
1320 dragItem(menu, item, event->xbutton.state & ControlMask);
1323 break;
1327 static void destroyEditMenu(WEditMenu * mPtr)
1329 WMRemoveNotificationObserver(mPtr);
1331 WMFreeArray(mPtr->items);
1333 wfree(mPtr->tdelegate);
1335 wfree(mPtr);