1 /* editmenu.c - editable menus
2 *
3 * WPrefs - Window Maker Preferences Program
4 *
5 * Copyright (c) 2000-2003 Alfredo K. Kojima
6 *
7 * This program is free software; you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License as published by
9 * the Free Software Foundation; either version 2 of the License, or
10 * (at your option) any later version.
11 *
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.
16 *
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.
20 */
21
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>
28
29 #include "editmenu.h"
30
31 typedef struct W_EditMenuItem {
32 W_Class widgetClass;
33 WMView *view;
34
35 struct W_EditMenu *parent;
36
37 char *label;
38 WMPixmap *pixmap; /* pixmap to show at left */
39
40 void *data;
41 WMCallback *destroyData;
42
43 struct W_EditMenu *submenu; /* if it's a cascade, NULL otherwise */
44
45 struct {
46 unsigned isTitle:1;
47 unsigned isHighlighted:1;
48 } flags;
49 } EditMenuItem;
50
51 typedef struct W_EditMenu {
52 W_Class widgetClass;
53 WMView *view;
54
55 struct W_EditMenu *parent;
56
57 WMArray *items; /* EditMenuItem */
58
59 EditMenuItem *selectedItem;
60
61 WMTextField *tfield;
62
63 WMButton *closeB;
64
65 int titleHeight;
66 int itemHeight;
67
68 WEditMenuDelegate *delegate;
69
70 WMTextFieldDelegate *tdelegate;
71
72 /* item dragging */
73 int draggedItem;
74 int dragX, dragY;
75
76 /* only for non-standalone menu */
77 WMSize maxSize;
78 WMSize minSize;
79
80 struct {
81 unsigned standalone:1;
82 unsigned isTitled:1;
83
84 unsigned acceptsDrop:1;
85 unsigned isFactory:1;
86 unsigned isSelectable:1;
87 unsigned isEditable:1;
88
89 unsigned isTornOff:1;
90
91 unsigned isDragging:1;
92 unsigned isEditing:1;
93
94 unsigned wasMapped:1;
95 } flags;
96 } EditMenu;
97
98 /******************** WEditMenuItem ********************/
99
100 static void destroyEditMenuItem(WEditMenuItem * iPtr);
101 static void paintEditMenuItem(WEditMenuItem * iPtr);
102 static void handleItemEvents(XEvent * event, void *data);
103
104 static void handleItemClick(XEvent * event, void *data);
105
106 static W_Class EditMenuItemClass = 0;
107
InitEditMenuItem(void)108 W_Class InitEditMenuItem(void)
109 {
110 /* register our widget with WINGs and get our widget class ID */
111 if (!EditMenuItemClass) {
112 EditMenuItemClass = W_RegisterUserWidget();
113 }
114
115 return EditMenuItemClass;
116 }
117
WCreateEditMenuItem(WMWidget * parent,const char * title,Bool isTitle)118 WEditMenuItem *WCreateEditMenuItem(WMWidget * parent, const char *title, Bool isTitle)
119 {
120 WEditMenuItem *iPtr;
121 WMScreen *scr = WMWidgetScreen(parent);
122
123 if (!EditMenuItemClass)
124 InitEditMenuItem();
125
126 iPtr = wmalloc(sizeof(WEditMenuItem));
127
128 iPtr->widgetClass = EditMenuItemClass;
129
130 iPtr->view = W_CreateView(W_VIEW(parent));
131 if (!iPtr->view) {
132 wfree(iPtr);
133 return NULL;
134 }
135 iPtr->view->self = iPtr;
136
137 iPtr->parent = parent;
138
139 iPtr->label = wstrdup(title);
140
141 iPtr->flags.isTitle = isTitle;
142
143 if (isTitle) {
144 WMSetWidgetBackgroundColor(iPtr, WMBlackColor(scr));
145 }
146
147 WMCreateEventHandler(iPtr->view, ExposureMask | StructureNotifyMask, handleItemEvents, iPtr);
148
149 WMCreateEventHandler(iPtr->view, ButtonPressMask | ButtonReleaseMask
150 | ButtonMotionMask, handleItemClick, iPtr);
151
152 return iPtr;
153 }
154
WGetEditMenuItemTitle(WEditMenuItem * item)155 char *WGetEditMenuItemTitle(WEditMenuItem * item)
156 {
157 return item->label;
158 }
159
WGetEditMenuItemData(WEditMenuItem * item)160 void *WGetEditMenuItemData(WEditMenuItem * item)
161 {
162 return item->data;
163 }
164
WSetEditMenuItemData(WEditMenuItem * item,void * data,WMCallback * destroyer)165 void WSetEditMenuItemData(WEditMenuItem * item, void *data, WMCallback * destroyer)
166 {
167 item->data = data;
168 item->destroyData = destroyer;
169 }
170
WSetEditMenuItemImage(WEditMenuItem * item,WMPixmap * pixmap)171 void WSetEditMenuItemImage(WEditMenuItem * item, WMPixmap * pixmap)
172 {
173 if (item->pixmap)
174 WMReleasePixmap(item->pixmap);
175 item->pixmap = WMRetainPixmap(pixmap);
176 }
177
paintEditMenuItem(WEditMenuItem * iPtr)178 static void paintEditMenuItem(WEditMenuItem * iPtr)
179 {
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);
186
187 if (!iPtr->view->flags.realized)
188 return;
189
190 color = scr->black;
191 if (iPtr->flags.isTitle && !iPtr->flags.isHighlighted) {
192 color = scr->white;
193 }
194
195 XClearWindow(scr->display, win);
196
197 W_DrawRelief(scr, win, 0, 0, w + 1, h, WRRaised);
198
199 WMDrawString(scr, win, color, font, 5, 3, iPtr->label, strlen(iPtr->label));
200
201 if (iPtr->pixmap) {
202 WMSize size = WMGetPixmapSize(iPtr->pixmap);
203
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);
210 }
211 }
212
highlightItem(WEditMenuItem * iPtr,Bool high)213 static void highlightItem(WEditMenuItem * iPtr, Bool high)
214 {
215 if (iPtr->flags.isTitle)
216 return;
217
218 iPtr->flags.isHighlighted = high;
219
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)));
227 }
228 }
229 }
230
getItemTextWidth(WEditMenuItem * iPtr)231 static int getItemTextWidth(WEditMenuItem * iPtr)
232 {
233 WMScreen *scr = WMWidgetScreen(iPtr);
234
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));
239 }
240 }
241
handleItemEvents(XEvent * event,void * data)242 static void handleItemEvents(XEvent * event, void *data)
243 {
244 WEditMenuItem *iPtr = (WEditMenuItem *) data;
245
246 switch (event->type) {
247 case Expose:
248 if (event->xexpose.count != 0)
249 break;
250 paintEditMenuItem(iPtr);
251 break;
252
253 case DestroyNotify:
254 destroyEditMenuItem(iPtr);
255 break;
256 }
257 }
258
destroyEditMenuItem(WEditMenuItem * iPtr)259 static void destroyEditMenuItem(WEditMenuItem * iPtr)
260 {
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);
267
268 wfree(iPtr);
269 }
270
271 /******************** WEditMenu *******************/
272
273 static void destroyEditMenu(WEditMenu * mPtr);
274
275 static void updateMenuContents(WEditMenu * mPtr);
276
277 static void handleEvents(XEvent * event, void *data);
278
279 static void editItemLabel(WEditMenuItem * item);
280
281 static void stopEditItem(WEditMenu * menu, Bool apply);
282
283 static void deselectItem(WEditMenu * menu);
284
285 static W_Class EditMenuClass = 0;
286
InitEditMenu(void)287 W_Class InitEditMenu(void)
288 {
289 /* register our widget with WINGs and get our widget class ID */
290 if (!EditMenuClass) {
291
292 EditMenuClass = W_RegisterUserWidget();
293 }
294
295 return EditMenuClass;
296 }
297
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;
309
310 #define GSWindowStyleAttr (1<<0)
311 #define GSWindowLevelAttr (1<<1)
312
writeGNUstepWMAttr(WMScreen * scr,Window window,GNUstepWMAttributes * attr)313 static void writeGNUstepWMAttr(WMScreen * scr, Window window, GNUstepWMAttributes * attr)
314 {
315 unsigned long data[9];
316
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);
330 }
331
realizeObserver(void * self,WMNotification * not)332 static void realizeObserver(void *self, WMNotification * not)
333 {
334 WEditMenu *menu = (WEditMenu *) self;
335 GNUstepWMAttributes attribs;
336
337 /* Parameter not used, but tell the compiler that it is ok */
338 (void) not;
339
340 memset(&attribs, 0, sizeof(GNUstepWMAttributes));
341 attribs.flags = GSWindowStyleAttr | GSWindowLevelAttr;
342 attribs.window_style = WMBorderlessWindowMask;
343 attribs.window_level = WMSubmenuWindowLevel;
344
345 writeGNUstepWMAttr(WMWidgetScreen(menu), menu->view->window, &attribs);
346 }
347
itemSelectObserver(void * self,WMNotification * notif)348 static void itemSelectObserver(void *self, WMNotification * notif)
349 {
350 WEditMenu *menu = (WEditMenu *) self;
351 WEditMenu *rmenu;
352
353 rmenu = (WEditMenu *) WMGetNotificationObject(notif);
354 /* check whether rmenu is from the same hierarchy of menu? */
355
356 if (rmenu == menu) {
357 return;
358 }
359
360 if (menu->selectedItem) {
361 if (!menu->selectedItem->submenu)
362 deselectItem(menu);
363 if (menu->flags.isEditing)
364 stopEditItem(menu, False);
365 }
366 }
367
makeEditMenu(WMScreen * scr,WMWidget * parent,const char * title)368 static WEditMenu *makeEditMenu(WMScreen * scr, WMWidget * parent, const char *title)
369 {
370 WEditMenu *mPtr;
371 WEditMenuItem *item;
372
373 if (!EditMenuClass)
374 InitEditMenu();
375
376 mPtr = wmalloc(sizeof(WEditMenu));
377
378 mPtr->widgetClass = EditMenuClass;
379
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;
386 }
387 if (!mPtr->view) {
388 wfree(mPtr);
389 return NULL;
390 }
391 mPtr->view->self = mPtr;
392
393 mPtr->flags.isSelectable = 1;
394 mPtr->flags.isEditable = 1;
395
396 W_SetViewBackgroundColor(mPtr->view, scr->darkGray);
397
398 WMAddNotificationObserver(realizeObserver, mPtr, WMViewRealizedNotification, mPtr->view);
399
400 WMAddNotificationObserver(itemSelectObserver, mPtr, "EditMenuItemSelected", NULL);
401
402 mPtr->items = WMCreateArray(4);
403
404 WMCreateEventHandler(mPtr->view, ExposureMask | StructureNotifyMask, handleEvents, mPtr);
405
406 if (title != NULL) {
407 item = WCreateEditMenuItem(mPtr, title, True);
408
409 WMMapWidget(item);
410 WMAddToArray(mPtr->items, item);
411
412 mPtr->flags.isTitled = 1;
413 }
414
415 mPtr->itemHeight = WMFontHeight(scr->normalFont) + 6;
416 mPtr->titleHeight = WMFontHeight(scr->boldFont) + 8;
417
418 updateMenuContents(mPtr);
419
420 return mPtr;
421 }
422
WCreateEditMenu(WMScreen * scr,const char * title)423 WEditMenu *WCreateEditMenu(WMScreen * scr, const char *title)
424 {
425 return makeEditMenu(scr, NULL, title);
426 }
427
WCreateEditMenuPad(WMWidget * parent)428 WEditMenu *WCreateEditMenuPad(WMWidget * parent)
429 {
430 return makeEditMenu(WMWidgetScreen(parent), parent, NULL);
431 }
432
WSetEditMenuDelegate(WEditMenu * mPtr,WEditMenuDelegate * delegate)433 void WSetEditMenuDelegate(WEditMenu * mPtr, WEditMenuDelegate * delegate)
434 {
435 mPtr->delegate = delegate;
436 }
437
WInsertMenuItemWithTitle(WEditMenu * mPtr,int index,const char * title)438 WEditMenuItem *WInsertMenuItemWithTitle(WEditMenu * mPtr, int index, const char *title)
439 {
440 WEditMenuItem *item;
441
442 item = WCreateEditMenuItem(mPtr, title, False);
443
444 WMMapWidget(item);
445
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);
454 }
455
456 updateMenuContents(mPtr);
457
458 return item;
459 }
460
WGetEditMenuItem(WEditMenu * mPtr,int index)461 WEditMenuItem *WGetEditMenuItem(WEditMenu * mPtr, int index)
462 {
463 if (index >= WMGetArrayItemCount(mPtr->items))
464 return NULL;
465
466 return WMGetFromArray(mPtr->items, index + (mPtr->flags.isTitled ? 1 : 0));
467 }
468
WAddMenuItemWithTitle(WEditMenu * mPtr,const char * title)469 WEditMenuItem *WAddMenuItemWithTitle(WEditMenu * mPtr, const char *title)
470 {
471 return WInsertMenuItemWithTitle(mPtr, WMGetArrayItemCount(mPtr->items), title);
472 }
473
WSetEditMenuTitle(WEditMenu * mPtr,const char * title)474 void WSetEditMenuTitle(WEditMenu * mPtr, const char *title)
475 {
476 WEditMenuItem *item;
477
478 item = WMGetFromArray(mPtr->items, 0);
479
480 wfree(item->label);
481 item->label = wstrdup(title);
482
483 updateMenuContents(mPtr);
484
485 WMRedisplayWidget(item);
486 }
487
WGetEditMenuTitle(WEditMenu * mPtr)488 char *WGetEditMenuTitle(WEditMenu * mPtr)
489 {
490 WEditMenuItem *item;
491
492 item = WMGetFromArray(mPtr->items, 0);
493
494 return item->label;
495 }
496
WSetEditMenuAcceptsDrop(WEditMenu * mPtr,Bool flag)497 void WSetEditMenuAcceptsDrop(WEditMenu * mPtr, Bool flag)
498 {
499 mPtr->flags.acceptsDrop = flag;
500 }
501
WSetEditMenuSubmenu(WEditMenu * mPtr,WEditMenuItem * item,WEditMenu * submenu)502 void WSetEditMenuSubmenu(WEditMenu * mPtr, WEditMenuItem * item, WEditMenu * submenu)
503 {
504 item->submenu = submenu;
505 submenu->parent = mPtr;
506
507 paintEditMenuItem(item);
508 }
509
WGetEditMenuSubmenu(WEditMenuItem * item)510 WEditMenu *WGetEditMenuSubmenu(WEditMenuItem *item)
511 {
512 return item->submenu;
513 }
514
WRemoveEditMenuItem(WEditMenu * mPtr,WEditMenuItem * item)515 void WRemoveEditMenuItem(WEditMenu * mPtr, WEditMenuItem * item)
516 {
517 if (WMRemoveFromArray(mPtr->items, item) != 0) {
518 updateMenuContents(mPtr);
519 }
520 }
521
WSetEditMenuSelectable(WEditMenu * mPtr,Bool flag)522 void WSetEditMenuSelectable(WEditMenu * mPtr, Bool flag)
523 {
524 mPtr->flags.isSelectable = flag;
525 }
526
WSetEditMenuEditable(WEditMenu * mPtr,Bool flag)527 void WSetEditMenuEditable(WEditMenu * mPtr, Bool flag)
528 {
529 mPtr->flags.isEditable = flag;
530 }
531
WSetEditMenuIsFactory(WEditMenu * mPtr,Bool flag)532 void WSetEditMenuIsFactory(WEditMenu * mPtr, Bool flag)
533 {
534 mPtr->flags.isFactory = flag;
535 }
536
WSetEditMenuMinSize(WEditMenu * mPtr,WMSize size)537 void WSetEditMenuMinSize(WEditMenu * mPtr, WMSize size)
538 {
539 mPtr->minSize.width = size.width;
540 mPtr->minSize.height = size.height;
541 }
542
WSetEditMenuMaxSize(WEditMenu * mPtr,WMSize size)543 void WSetEditMenuMaxSize(WEditMenu * mPtr, WMSize size)
544 {
545 mPtr->maxSize.width = size.width;
546 mPtr->maxSize.height = size.height;
547 }
548
WGetEditMenuLocationForSubmenu(WEditMenu * mPtr,WEditMenu * submenu)549 WMPoint WGetEditMenuLocationForSubmenu(WEditMenu * mPtr, WEditMenu * submenu)
550 {
551 WMArrayIterator iter;
552 WEditMenuItem *item;
553 int y;
554
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);
562
563 return wmkpoint(pt.x + mPtr->view->size.width, pt.y + y);
564 }
565 y += W_VIEW_HEIGHT(item->view);
566 }
567
568 puts("invalid submenu passed to WGetEditMenuLocationForSubmenu()");
569
570 return wmkpoint(0, 0);
571 }
572
closeMenuAction(WMWidget * w,void * data)573 static void closeMenuAction(WMWidget * w, void *data)
574 {
575 WEditMenu *menu = (WEditMenu *) data;
576
577 /* Parameter not used, but tell the compiler that it is ok */
578 (void) w;
579
580 WMAddIdleHandler(WMDestroyWidget, menu->closeB);
581 menu->closeB = NULL;
582
583 WEditMenuHide(menu);
584 }
585
WTearOffEditMenu(WEditMenu * menu,WEditMenu * submenu)586 void WTearOffEditMenu(WEditMenu * menu, WEditMenu * submenu)
587 {
588 WEditMenuItem *item;
589
590 submenu->flags.isTornOff = 1;
591
592 item = (WEditMenuItem *) WMGetFromArray(submenu->items, 0);
593
594 submenu->closeB = WMCreateCommandButton(item);
595 WMResizeWidget(submenu->closeB, 15, 15);
596 WMMoveWidget(submenu->closeB, W_VIEW(submenu)->size.width - 20, 3);
597 WMRealizeWidget(submenu->closeB);
598 WMSetButtonText(submenu->closeB, "X");
599 WMSetButtonAction(submenu->closeB, closeMenuAction, submenu);
600 WMMapWidget(submenu->closeB);
601
602 if (menu->selectedItem && menu->selectedItem->submenu == submenu)
603 deselectItem(menu);
604 }
605
WEditMenuIsTornOff(WEditMenu * mPtr)606 Bool WEditMenuIsTornOff(WEditMenu * mPtr)
607 {
608 return mPtr->flags.isTornOff;
609 }
610
WEditMenuHide(WEditMenu * mPtr)611 void WEditMenuHide(WEditMenu * mPtr)
612 {
613 WEditMenuItem *item;
614 int i = 0;
615
616 if (WMWidgetIsMapped(mPtr)) {
617 WMUnmapWidget(mPtr);
618 mPtr->flags.wasMapped = 1;
619 } else {
620 mPtr->flags.wasMapped = 0;
621 }
622 while ((item = WGetEditMenuItem(mPtr, i++))) {
623 WEditMenu *submenu;
624
625 submenu = WGetEditMenuSubmenu(item);
626 if (submenu) {
627 WEditMenuHide(submenu);
628 }
629 }
630 }
631
WEditMenuUnhide(WEditMenu * mPtr)632 void WEditMenuUnhide(WEditMenu * mPtr)
633 {
634 WEditMenuItem *item;
635 int i = 0;
636
637 if (mPtr->flags.wasMapped) {
638 WMMapWidget(mPtr);
639 }
640 while ((item = WGetEditMenuItem(mPtr, i++))) {
641 WEditMenu *submenu;
642
643 submenu = WGetEditMenuSubmenu(item);
644 if (submenu) {
645 WEditMenuUnhide(submenu);
646 }
647 }
648 }
649
WEditMenuShowAt(WEditMenu * menu,int x,int y)650 void WEditMenuShowAt(WEditMenu * menu, int x, int y)
651 {
652 XSizeHints *hints;
653
654 hints = XAllocSizeHints();
655
656 hints->flags = USPosition;
657 hints->x = x;
658 hints->y = y;
659
660 WMMoveWidget(menu, x, y);
661 XSetWMNormalHints(W_VIEW_DISPLAY(menu->view), W_VIEW_DRAWABLE(menu->view), hints);
662 XFree(hints);
663
664 WMMapWidget(menu);
665 }
666
updateMenuContents(WEditMenu * mPtr)667 static void updateMenuContents(WEditMenu * mPtr)
668 {
669 int newW, newH;
670 int w;
671 int i;
672 int iheight = mPtr->itemHeight;
673 int offs = 1;
674 WMArrayIterator iter;
675 WEditMenuItem *item;
676
677 newW = 0;
678 newH = offs;
679
680 i = 0;
681 WM_ITERATE_ARRAY(mPtr->items, item, iter) {
682 w = getItemTextWidth(item);
683
684 newW = WMAX(w, newW);
685
686 WMMoveWidget(item, offs, newH);
687 if (i == 0 && mPtr->flags.isTitled) {
688 newH += mPtr->titleHeight;
689 } else {
690 newH += iheight;
691 }
692 i = 1;
693 }
694
695 newW += iheight + 10;
696 newH--;
697
698 if (mPtr->minSize.width)
699 newW = WMAX(newW, mPtr->minSize.width);
700 if (mPtr->maxSize.width)
701 newW = WMIN(newW, mPtr->maxSize.width);
702
703 if (mPtr->minSize.height)
704 newH = WMAX(newH, mPtr->minSize.height);
705 if (mPtr->maxSize.height)
706 newH = WMIN(newH, mPtr->maxSize.height);
707
708 if (W_VIEW(mPtr)->size.width == newW && mPtr->view->size.height == newH + 1)
709 return;
710
711 W_ResizeView(mPtr->view, newW, newH + 1);
712
713 if (mPtr->closeB)
714 WMMoveWidget(mPtr->closeB, newW - 20, 3);
715
716 newW -= 2 * offs;
717
718 i = 0;
719 WM_ITERATE_ARRAY(mPtr->items, item, iter) {
720 if (i == 0 && mPtr->flags.isTitled) {
721 WMResizeWidget(item, newW, mPtr->titleHeight);
722 } else {
723 WMResizeWidget(item, newW, iheight);
724 }
725 i = 1;
726 }
727 }
728
deselectItem(WEditMenu * menu)729 static void deselectItem(WEditMenu * menu)
730 {
731 WEditMenu *submenu;
732 WEditMenuItem *item = menu->selectedItem;
733
734 highlightItem(item, False);
735
736 if (menu->delegate && menu->delegate->itemDeselected) {
737 (*menu->delegate->itemDeselected) (menu->delegate, menu, item);
738 }
739 submenu = item->submenu;
740
741 if (submenu && !WEditMenuIsTornOff(submenu)) {
742 WEditMenuHide(submenu);
743 }
744
745 menu->selectedItem = NULL;
746 }
747
selectItem(WEditMenu * menu,WEditMenuItem * item)748 static void selectItem(WEditMenu * menu, WEditMenuItem * item)
749 {
750 if (!menu->flags.isSelectable || menu->selectedItem == item) {
751 return;
752 }
753 if (menu->selectedItem) {
754 deselectItem(menu);
755 }
756
757 if (menu->flags.isEditing) {
758 stopEditItem(menu, False);
759 }
760
761 if (item && !item->flags.isTitle) {
762 highlightItem(item, True);
763
764 if (item->submenu && !W_VIEW_MAPPED(item->submenu->view)) {
765 WMPoint pt;
766
767 pt = WGetEditMenuLocationForSubmenu(menu, item->submenu);
768
769 WEditMenuShowAt(item->submenu, pt.x, pt.y);
770
771 item->submenu->flags.isTornOff = 0;
772 }
773
774 WMPostNotificationName("EditMenuItemSelected", menu, NULL);
775
776 if (menu->delegate && menu->delegate->itemSelected) {
777 (*menu->delegate->itemSelected) (menu->delegate, menu, item);
778 }
779 }
780
781 menu->selectedItem = item;
782 }
783
paintMenu(WEditMenu * mPtr)784 static void paintMenu(WEditMenu * mPtr)
785 {
786 W_View *view = mPtr->view;
787
788 W_DrawRelief(W_VIEW_SCREEN(view), W_VIEW_DRAWABLE(view), 0, 0,
789 W_VIEW_WIDTH(view), W_VIEW_HEIGHT(view), WRSimple);
790 }
791
handleEvents(XEvent * event,void * data)792 static void handleEvents(XEvent * event, void *data)
793 {
794 WEditMenu *mPtr = (WEditMenu *) data;
795
796 switch (event->type) {
797 case DestroyNotify:
798 destroyEditMenu(mPtr);
799 break;
800
801 case Expose:
802 if (event->xexpose.count == 0)
803 paintMenu(mPtr);
804 break;
805 }
806 }
807
808 /* -------------------------- Menu Label Editing ------------------------ */
809
stopEditItem(WEditMenu * menu,Bool apply)810 static void stopEditItem(WEditMenu * menu, Bool apply)
811 {
812 if (apply) {
813 wfree(menu->selectedItem->label);
814 menu->selectedItem->label = WMGetTextFieldText(menu->tfield);
815
816 updateMenuContents(menu);
817
818 if (menu->delegate && menu->delegate->itemEdited) {
819 (*menu->delegate->itemEdited) (menu->delegate, menu, menu->selectedItem);
820 }
821
822 }
823 WMUnmapWidget(menu->tfield);
824 menu->flags.isEditing = 0;
825 }
826
textEndedEditing(struct WMTextFieldDelegate * self,WMNotification * notif)827 static void textEndedEditing(struct WMTextFieldDelegate *self, WMNotification * notif)
828 {
829 WEditMenu *menu = (WEditMenu *) self->data;
830 uintptr_t reason;
831 int i;
832 WEditMenuItem *item;
833
834 if (!menu->flags.isEditing)
835 return;
836
837 reason = (uintptr_t)WMGetNotificationClientData(notif);
838
839 switch (reason) {
840 case WMEscapeTextMovement:
841 stopEditItem(menu, False);
842 break;
843
844 case WMReturnTextMovement:
845 stopEditItem(menu, True);
846 break;
847
848 case WMTabTextMovement:
849 stopEditItem(menu, True);
850
851 i = WMGetFirstInArray(menu->items, menu->selectedItem);
852 item = WMGetFromArray(menu->items, i + 1);
853 if (item != NULL) {
854 selectItem(menu, item);
855 editItemLabel(item);
856 }
857 break;
858
859 case WMBacktabTextMovement:
860 stopEditItem(menu, True);
861
862 i = WMGetFirstInArray(menu->items, menu->selectedItem);
863 item = WMGetFromArray(menu->items, i - 1);
864 if (item != NULL) {
865 selectItem(menu, item);
866 editItemLabel(item);
867 }
868 break;
869 }
870 }
871
872 static WMTextFieldDelegate textFieldDelegate = {
873 NULL,
874 NULL,
875 NULL,
876 textEndedEditing,
877 NULL,
878 NULL
879 };
880
editItemLabel(WEditMenuItem * item)881 static void editItemLabel(WEditMenuItem * item)
882 {
883 WEditMenu *mPtr = item->parent;
884 WMTextField *tf;
885
886 if (!mPtr->flags.isEditable) {
887 return;
888 }
889
890 if (!mPtr->tfield) {
891 mPtr->tfield = WMCreateTextField(mPtr);
892 WMSetTextFieldBeveled(mPtr->tfield, False);
893 WMRealizeWidget(mPtr->tfield);
894
895 mPtr->tdelegate = wmalloc(sizeof(WMTextFieldDelegate));
896 memcpy(mPtr->tdelegate, &textFieldDelegate, sizeof(WMTextFieldDelegate));
897
898 mPtr->tdelegate->data = mPtr;
899
900 WMSetTextFieldDelegate(mPtr->tfield, mPtr->tdelegate);
901 }
902 tf = mPtr->tfield;
903
904 WMSetTextFieldText(tf, item->label);
905 WMSelectTextFieldRange(tf, wmkrange(0, strlen(item->label)));
906 WMResizeWidget(tf, mPtr->view->size.width, mPtr->itemHeight);
907 WMMoveWidget(tf, 0, item->view->pos.y);
908 WMMapWidget(tf);
909 WMSetFocusToWidget(tf);
910
911 mPtr->flags.isEditing = 1;
912 }
913
914 /* -------------------------------------------------- */
915
slideWindow(Display * dpy,Window win,int srcX,int srcY,int dstX,int dstY)916 static void slideWindow(Display * dpy, Window win, int srcX, int srcY, int dstX, int dstY)
917 {
918 double x, y, dx, dy;
919 int i;
920 int iterations;
921
922 iterations = WMIN(25, WMAX(abs(dstX - srcX), abs(dstY - srcY)));
923
924 x = srcX;
925 y = srcY;
926
927 dx = (double)(dstX - srcX) / iterations;
928 dy = (double)(dstY - srcY) / iterations;
929
930 for (i = 0; i <= iterations; i++) {
931 XMoveWindow(dpy, win, x, y);
932 XFlush(dpy);
933
934 wusleep(800);
935
936 x += dx;
937 y += dy;
938 }
939 }
940
errorHandler(Display * d,XErrorEvent * ev)941 static int errorHandler(Display * d, XErrorEvent * ev)
942 {
943 /* Parameter not used, but tell the compiler that it is ok */
944 (void) d;
945 (void) ev;
946
947 /* just ignore */
948 return 0;
949 }
950
findMenuInWindow(Display * dpy,Window toplevel,int x,int y)951 static WEditMenu *findMenuInWindow(Display * dpy, Window toplevel, int x, int y)
952 {
953 Window foo, bar;
954 Window *children;
955 unsigned nchildren;
956 int i;
957 WEditMenu *menu;
958 WMView *view;
959 int (*oldHandler) (Display *, XErrorEvent *);
960
961 view = W_GetViewForXWindow(dpy, toplevel);
962 if (view && view->self && WMWidgetClass(view->self) == EditMenuClass) {
963 menu = (WEditMenu *) view->self;
964 if (menu->flags.acceptsDrop) {
965 return menu;
966 }
967 }
968
969 if (!XQueryTree(dpy, toplevel, &foo, &bar, &children, &nchildren) || children == NULL) {
970 return NULL;
971 }
972
973 oldHandler = XSetErrorHandler(errorHandler);
974
975 /* first window that contains the point is the one */
976 for (i = nchildren - 1; i >= 0; i--) {
977 XWindowAttributes attr;
978
979 if (XGetWindowAttributes(dpy, children[i], &attr)
980 && attr.map_state == IsViewable
981 && x >= attr.x && y >= attr.y && x < attr.x + attr.width && y < attr.y + attr.height) {
982 Window tmp;
983
984 tmp = children[i];
985
986 menu = findMenuInWindow(dpy, tmp, x - attr.x, y - attr.y);
987 if (menu) {
988 XFree(children);
989 return menu;
990 }
991 }
992 }
993
994 XSetErrorHandler(oldHandler);
995
996 XFree(children);
997 return NULL;
998 }
999
handleDragOver(WEditMenu * menu,WMView * view,WEditMenuItem * item,int y)1000 static void handleDragOver(WEditMenu *menu, WMView *view, WEditMenuItem *item, int y)
1001 {
1002 WMScreen *scr = W_VIEW_SCREEN(menu->view);
1003 Window blaw;
1004 int mx, my;
1005 int offs;
1006
1007 XTranslateCoordinates(scr->display, W_VIEW_DRAWABLE(menu->view), scr->rootWin, 0, 0, &mx, &my, &blaw);
1008
1009 offs = menu->flags.standalone ? 0 : 1;
1010
1011 W_MoveView(view, mx + offs, y);
1012 if (view->size.width != menu->view->size.width) {
1013 W_ResizeView(view, menu->view->size.width - 2 * offs, menu->itemHeight);
1014 W_ResizeView(item->view, menu->view->size.width - 2 * offs, menu->itemHeight);
1015 }
1016 }
1017
handleItemDrop(WEditMenu * menu,WEditMenuItem * item,int y)1018 static void handleItemDrop(WEditMenu *menu, WEditMenuItem *item, int y)
1019 {
1020 WMScreen *scr = W_VIEW_SCREEN(menu->view);
1021 Window blaw;
1022 int mx, my;
1023 int index;
1024
1025 XTranslateCoordinates(scr->display, W_VIEW_DRAWABLE(menu->view), scr->rootWin, 0, 0, &mx, &my, &blaw);
1026
1027 index = y - my;
1028 if (menu->flags.isTitled) {
1029 index -= menu->titleHeight;
1030 }
1031 index = (index + menu->itemHeight / 2) / menu->itemHeight;
1032 if (index < 0)
1033 index = 0;
1034
1035 if (menu->flags.isTitled) {
1036 index++;
1037 }
1038
1039 if (index > WMGetArrayItemCount(menu->items)) {
1040 WMAddToArray(menu->items, item);
1041 } else {
1042 WMInsertInArray(menu->items, index, item);
1043 }
1044
1045 W_ReparentView(item->view, menu->view, 0, index * menu->itemHeight);
1046
1047 item->parent = menu;
1048 if (item->submenu) {
1049 item->submenu->parent = menu;
1050 }
1051
1052 updateMenuContents(menu);
1053 }
1054
dragMenu(WEditMenu * menu)1055 static void dragMenu(WEditMenu * menu)
1056 {
1057 WMScreen *scr = W_VIEW_SCREEN(menu->view);
1058 XEvent ev;
1059 Bool done = False;
1060 int dx, dy;
1061 unsigned blau;
1062 Window blaw;
1063
1064 XGetGeometry(scr->display, W_VIEW_DRAWABLE(menu->view), &blaw, &dx, &dy, &blau, &blau, &blau, &blau);
1065
1066 XTranslateCoordinates(scr->display, W_VIEW_DRAWABLE(menu->view), scr->rootWin, dx, dy, &dx, &dy, &blaw);
1067
1068 dx = menu->dragX - dx;
1069 dy = menu->dragY - dy;
1070
1071 XGrabPointer(scr->display, scr->rootWin, False,
1072 ButtonReleaseMask | ButtonMotionMask,
1073 GrabModeAsync, GrabModeAsync, None, scr->defaultCursor, CurrentTime);
1074
1075 if (menu->parent)
1076 WTearOffEditMenu(menu->parent, menu);
1077
1078 while (!done) {
1079 WMNextEvent(scr->display, &ev);
1080
1081 switch (ev.type) {
1082 case ButtonRelease:
1083 done = True;
1084 break;
1085
1086 case MotionNotify:
1087 while (XCheckMaskEvent(scr->display, ButtonMotionMask, &ev)) ;
1088
1089 WMMoveWidget(menu, ev.xmotion.x_root - dx, ev.xmotion.y_root - dy);
1090 break;
1091
1092 default:
1093 WMHandleEvent(&ev);
1094 break;
1095 }
1096 }
1097
1098 XUngrabPointer(scr->display, CurrentTime);
1099 }
1100
duplicateItem(WEditMenuItem * item)1101 static WEditMenuItem *duplicateItem(WEditMenuItem * item)
1102 {
1103 WEditMenuItem *nitem;
1104
1105 nitem = WCreateEditMenuItem(item->parent, item->label, False);
1106 if (item->pixmap)
1107 nitem->pixmap = WMRetainPixmap(item->pixmap);
1108
1109 return nitem;
1110 }
1111
duplicateMenu(WEditMenu * menu)1112 static WEditMenu *duplicateMenu(WEditMenu * menu)
1113 {
1114 WEditMenu *nmenu;
1115 WEditMenuItem *item;
1116 WMArrayIterator iter;
1117 Bool first = menu->flags.isTitled;
1118
1119 nmenu = WCreateEditMenu(WMWidgetScreen(menu), WGetEditMenuTitle(menu));
1120
1121 memcpy(&nmenu->flags, &menu->flags, sizeof(menu->flags));
1122 nmenu->delegate = menu->delegate;
1123
1124 WM_ITERATE_ARRAY(menu->items, item, iter) {
1125 WEditMenuItem *nitem;
1126
1127 if (first) {
1128 first = False;
1129 continue;
1130 }
1131
1132 nitem = WAddMenuItemWithTitle(nmenu, item->label);
1133 if (item->pixmap)
1134 WSetEditMenuItemImage(nitem, item->pixmap);
1135
1136 if (menu->delegate && menu->delegate->itemCloned) {
1137 (*menu->delegate->itemCloned) (menu->delegate, menu, item, nitem);
1138 }
1139 }
1140
1141 WMRealizeWidget(nmenu);
1142
1143 return nmenu;
1144 }
1145
dragItem(WEditMenu * menu,WEditMenuItem * item,Bool copy)1146 static void dragItem(WEditMenu * menu, WEditMenuItem * item, Bool copy)
1147 {
1148 static XColor black = { 0, 0, 0, 0, DoRed | DoGreen | DoBlue, 0 };
1149 static XColor green = { 0x0045b045, 0x4500, 0xb000, 0x4500, DoRed | DoGreen | DoBlue, 0 };
1150 static XColor back = { 0, 0xffff, 0xffff, 0xffff, DoRed | DoGreen | DoBlue, 0 };
1151 Display *dpy = W_VIEW_DISPLAY(menu->view);
1152 WMScreen *scr = W_VIEW_SCREEN(menu->view);
1153 int x, y;
1154 int dx, dy;
1155 Bool done = False;
1156 Window blaw;
1157 int blai;
1158 unsigned blau;
1159 Window win;
1160 WMView *dview;
1161 int orix, oriy;
1162 Bool enteredMenu = False;
1163 WMSize oldSize = item->view->size;
1164 WEditMenuItem *dritem = item;
1165 WEditMenu *dmenu = NULL;
1166
1167 if (item->flags.isTitle) {
1168 WMRaiseWidget(menu);
1169
1170 dragMenu(menu);
1171
1172 return;
1173 }
1174
1175 selectItem(menu, NULL);
1176
1177 win = scr->rootWin;
1178
1179 XTranslateCoordinates(dpy, W_VIEW_DRAWABLE(item->view), win, 0, 0, &orix, &oriy, &blaw);
1180
1181 dview = W_CreateUnmanagedTopView(scr);
1182 W_SetViewBackgroundColor(dview, scr->black);
1183 W_ResizeView(dview, W_VIEW_WIDTH(item->view), W_VIEW_HEIGHT(item->view));
1184 W_MoveView(dview, orix, oriy);
1185 W_RealizeView(dview);
1186
1187 if (menu->flags.isFactory || copy) {
1188 dritem = duplicateItem(item);
1189
1190 W_ReparentView(dritem->view, dview, 0, 0);
1191 WMResizeWidget(dritem, oldSize.width, oldSize.height);
1192 WMRealizeWidget(dritem);
1193 WMMapWidget(dritem);
1194 } else {
1195 W_ReparentView(item->view, dview, 0, 0);
1196 }
1197
1198 W_MapView(dview);
1199
1200 dx = menu->dragX - orix;
1201 dy = menu->dragY - oriy;
1202
1203 XGrabPointer(dpy, scr->rootWin, False,
1204 ButtonPressMask | ButtonReleaseMask | ButtonMotionMask,
1205 GrabModeAsync, GrabModeAsync, None, scr->defaultCursor, CurrentTime);
1206
1207 if (menu->flags.acceptsDrop)
1208 XRecolorCursor(dpy, scr->defaultCursor, &green, &back);
1209
1210 while (!done) {
1211 XEvent ev;
1212
1213 WMNextEvent(dpy, &ev);
1214
1215 switch (ev.type) {
1216 case MotionNotify:
1217 while (XCheckMaskEvent(dpy, ButtonMotionMask, &ev)) ;
1218
1219 XQueryPointer(dpy, win, &blaw, &blaw, &blai, &blai, &x, &y, &blau);
1220
1221 dmenu = findMenuInWindow(dpy, win, x, y);
1222
1223 if (dmenu) {
1224 handleDragOver(dmenu, dview, dritem, y - dy);
1225 if (!enteredMenu) {
1226 enteredMenu = True;
1227 XRecolorCursor(dpy, scr->defaultCursor, &green, &back);
1228 }
1229 } else {
1230 if (enteredMenu) {
1231 W_ResizeView(dview, oldSize.width, oldSize.height);
1232 W_ResizeView(dritem->view, oldSize.width, oldSize.height);
1233 enteredMenu = False;
1234 XRecolorCursor(dpy, scr->defaultCursor, &black, &back);
1235 }
1236 W_MoveView(dview, x - dx, y - dy);
1237 }
1238
1239 break;
1240
1241 case ButtonRelease:
1242 done = True;
1243 break;
1244
1245 default:
1246 WMHandleEvent(&ev);
1247 break;
1248 }
1249 }
1250 XRecolorCursor(dpy, scr->defaultCursor, &black, &back);
1251
1252 XUngrabPointer(dpy, CurrentTime);
1253
1254 if (!enteredMenu) {
1255 Bool rem = True;
1256
1257 if (!menu->flags.isFactory && !copy) {
1258 W_UnmapView(dview);
1259 if (menu->delegate && menu->delegate->shouldRemoveItem) {
1260 rem = (*menu->delegate->shouldRemoveItem) (menu->delegate, menu, item);
1261 }
1262 W_MapView(dview);
1263 }
1264
1265 if (!rem || menu->flags.isFactory || copy) {
1266 slideWindow(dpy, W_VIEW_DRAWABLE(dview), x - dx, y - dy, orix, oriy);
1267
1268 if (!menu->flags.isFactory && !copy) {
1269 WRemoveEditMenuItem(menu, dritem);
1270 handleItemDrop(dmenu ? dmenu : menu, dritem, oriy);
1271 }
1272 } else {
1273 WRemoveEditMenuItem(menu, dritem);
1274 }
1275 } else {
1276 WRemoveEditMenuItem(menu, dritem);
1277
1278 if (menu->delegate && menu->delegate->itemCloned && (menu->flags.isFactory || copy)) {
1279 (*menu->delegate->itemCloned) (menu->delegate, menu, item, dritem);
1280 }
1281
1282 handleItemDrop(dmenu, dritem, y - dy);
1283
1284 if (item->submenu && (menu->flags.isFactory || copy)) {
1285 WEditMenu *submenu;
1286
1287 submenu = duplicateMenu(item->submenu);
1288 WSetEditMenuSubmenu(dmenu, dritem, submenu);
1289 }
1290 }
1291
1292 /* can't destroy now because we're being called from
1293 * the event handler of the item. and destroying now,
1294 * would mean destroying the item too in some cases.
1295 */
1296 WMAddIdleHandler((WMCallback *) W_DestroyView, dview);
1297 }
1298
handleItemClick(XEvent * event,void * data)1299 static void handleItemClick(XEvent * event, void *data)
1300 {
1301 WEditMenuItem *item = (WEditMenuItem *) data;
1302 WEditMenu *menu = item->parent;
1303
1304 switch (event->type) {
1305 case ButtonPress:
1306 selectItem(menu, item);
1307
1308 if (WMIsDoubleClick(event)) {
1309 editItemLabel(item);
1310 }
1311
1312 menu->flags.isDragging = 1;
1313 menu->dragX = event->xbutton.x_root;
1314 menu->dragY = event->xbutton.y_root;
1315 break;
1316
1317 case ButtonRelease:
1318 menu->flags.isDragging = 0;
1319 break;
1320
1321 case MotionNotify:
1322 if (menu->flags.isDragging) {
1323 if (abs(event->xbutton.x_root - menu->dragX) > 5
1324 || abs(event->xbutton.y_root - menu->dragY) > 5) {
1325 menu->flags.isDragging = 0;
1326 dragItem(menu, item, event->xbutton.state & ControlMask);
1327 }
1328 }
1329 break;
1330 }
1331 }
1332
destroyEditMenu(WEditMenu * mPtr)1333 static void destroyEditMenu(WEditMenu * mPtr)
1334 {
1335 WMRemoveNotificationObserver(mPtr);
1336
1337 WMFreeArray(mPtr->items);
1338
1339 wfree(mPtr->tdelegate);
1340
1341 wfree(mPtr);
1342 }
1343