1 /*
2  * Copyright (C) 2020 Linux Studio Plugins Project <https://lsp-plug.in/>
3  *           (C) 2020 Vladimir Sadovnikov <sadko4u@gmail.com>
4  *
5  * This file is part of lsp-plugins
6  * Created on: 18 сент. 2017 г.
7  *
8  * lsp-plugins is free software: you can redistribute it and/or modify
9  * it under the terms of the GNU Lesser General Public License as published by
10  * the Free Software Foundation, either version 3 of the License, or
11  * any later version.
12  *
13  * lsp-plugins is distributed in the hope that it will be useful,
14  * but WITHOUT ANY WARRANTY; without even the implied warranty of
15  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16  * GNU Lesser General Public License for more details.
17  *
18  * You should have received a copy of the GNU Lesser General Public License
19  * along with lsp-plugins. If not, see <https://www.gnu.org/licenses/>.
20  */
21 
22 #include <ui/tk/tk.h>
23 
24 namespace lsp
25 {
26     namespace tk
27     {
28         const w_class_t LSPMenu::metadata = { "LSPMenu", &LSPWidgetContainer::metadata };
29 
30         //-----------------------------------------------------------------------------
31         // LSPMenu::LSPMenuWindow implementation
MenuWindow(LSPDisplay * dpy,LSPMenu * menu,size_t screen)32         LSPMenu::MenuWindow::MenuWindow(LSPDisplay *dpy, LSPMenu *menu, size_t screen):
33             LSPWindow(dpy, NULL, screen)
34         {
35             pMenu       = menu;
36         }
37 
~MenuWindow()38         LSPMenu::MenuWindow::~MenuWindow()
39         {
40         }
41 
find_widget(ssize_t x,ssize_t y)42         LSPWidget *LSPMenu::MenuWindow::find_widget(ssize_t x, ssize_t y)
43         {
44             return (pMenu != NULL) ? pMenu->find_widget(x, y) : NULL;
45         }
46 
render(ISurface * s,bool force)47         void LSPMenu::MenuWindow::render(ISurface *s, bool force)
48         {
49             if (pMenu != NULL)
50                 pMenu->render(s, force);
51             else
52                 LSPWindow::render(s, force);
53         }
54 
get_handler(ws_event_t * e)55         LSPMenu *LSPMenu::MenuWindow::get_handler(ws_event_t *e)
56         {
57             LSPMenu *handler = (pMenu != NULL) ? pMenu->check_inside_submenu(e) : NULL;
58             if (handler == NULL)
59                 handler = pMenu;
60             return handler;
61         }
62 
on_mouse_down(const ws_event_t * e)63         status_t LSPMenu::MenuWindow::on_mouse_down(const ws_event_t *e)
64         {
65             ws_event_t xev = *e;
66             LSPMenu *handler = get_handler(&xev);
67             return (handler != NULL) ? handler->on_mouse_down(&xev) : LSPWindow::on_mouse_down(&xev);
68         }
69 
on_mouse_up(const ws_event_t * e)70         status_t LSPMenu::MenuWindow::on_mouse_up(const ws_event_t *e)
71         {
72             ws_event_t xev = *e;
73             LSPMenu *handler = get_handler(&xev);
74             return (handler != NULL) ? handler->on_mouse_up(&xev) : LSPWindow::on_mouse_up(&xev);
75         }
76 
on_mouse_scroll(const ws_event_t * e)77         status_t LSPMenu::MenuWindow::on_mouse_scroll(const ws_event_t *e)
78         {
79             ws_event_t xev = *e;
80             LSPMenu *handler = get_handler(&xev);
81             return (handler != NULL) ? handler->on_mouse_scroll(e) : LSPWindow::on_mouse_scroll(e);
82         }
83 
on_mouse_move(const ws_event_t * e)84         status_t LSPMenu::MenuWindow::on_mouse_move(const ws_event_t *e)
85         {
86             ws_event_t xev = *e;
87             LSPMenu *handler = get_handler(&xev);
88             return (handler != NULL) ? handler->on_mouse_move(&xev) : LSPWindow::on_mouse_move(&xev);
89         }
90 
size_request(size_request_t * r)91         void LSPMenu::MenuWindow::size_request(size_request_t *r)
92         {
93             if (pMenu != NULL)
94                 pMenu->size_request(r);
95 
96             // Limit the size of window with the screen size
97             pDisplay->display()->screen_size(screen(), &r->nMaxWidth, &r->nMaxHeight);
98             if ((r->nMinWidth > 0) && (r->nMinWidth > r->nMaxWidth))
99                 r->nMinWidth    = r->nMaxWidth;
100             if ((r->nMinHeight > 0) && (r->nMinHeight > r->nMaxHeight))
101                 r->nMinHeight   = r->nMaxHeight;
102         }
103 
104         //-----------------------------------------------------------------------------
105         // LSPMenu implementation
LSPMenu(LSPDisplay * dpy)106         LSPMenu::LSPMenu(LSPDisplay *dpy):
107             LSPWidgetContainer(dpy),
108             sFont(this),
109             sSelColor(this),
110             sBorderColor(this)
111         {
112             pWindow     = NULL;
113             pParentMenu = NULL;
114             pActiveMenu = NULL;
115             nPopupLeft  = -1;
116             nPopupTop   = -1;
117             nSelected   = SEL_NONE;
118             nScroll     = 0;
119             nScrollMax  = 0;
120             nBorder     = 1;
121             nSpacing    = 6;
122             nMBState    = 0;
123 
124             sPadding.set(16, 16, 0, 0);
125 
126             nFlags     &= ~F_VISIBLE;
127             pClass      = &metadata;
128 
129             sScroll.bind(pDisplay);
130             sScroll.set_handler(timer_handler, this);
131         }
132 
~LSPMenu()133         LSPMenu::~LSPMenu()
134         {
135             do_destroy();
136         }
137 
init()138         status_t LSPMenu::init()
139         {
140             status_t result = LSPWidget::init();
141             if (result != STATUS_OK)
142                 return result;
143 
144             if (pDisplay != NULL)
145             {
146                 // Get theme
147                 LSPTheme *theme = pDisplay->theme();
148                 if (theme != NULL)
149                     sFont.init(theme->font());
150             }
151 
152             init_color(C_BACKGROUND, sFont.color());
153             init_color(C_BACKGROUND, &sBorderColor);
154             init_color(C_LABEL_TEXT, &sBgColor);
155             init_color(C_KNOB_SCALE, &sSelColor);
156 
157             return STATUS_OK;
158         }
159 
destroy()160         void LSPMenu::destroy()
161         {
162             do_destroy();
163             LSPWidgetContainer::destroy();
164         }
165 
do_destroy()166         void LSPMenu::do_destroy()
167         {
168             size_t n            = vItems.size();
169             for (size_t i=0; i<n; ++i)
170             {
171                 LSPMenuItem *item   = vItems.at(i);
172                 if (item == NULL)
173                     continue;
174 
175                 unlink_widget(item);
176             }
177 
178             vItems.flush();
179 
180             if (pWindow != NULL)
181             {
182                 pWindow->destroy();
183                 delete pWindow;
184                 pWindow = NULL;
185             }
186         }
187 
188 //        LSPWidget *LSPMenu::find_widget(ssize_t x, ssize_t y)
189 //        {
190 //            size_t items = vItems.size();
191 //            for (size_t i=0; i<items; ++i)
192 //            {
193 //                LSPMenuItem *w = vItems.at(i);
194 //                if ((w == NULL) || (w->hidden()))
195 //                    continue;
196 //                if (w->inside(x, y))
197 //                    return w;
198 //            }
199 //
200 //            return NULL;
201 //        }
202 
set_border(size_t value)203         void LSPMenu::set_border(size_t value)
204         {
205             if (nBorder == value)
206                 return;
207             nBorder = value;
208             query_resize();
209         }
210 
set_spacing(size_t value)211         void LSPMenu::set_spacing(size_t value)
212         {
213             if (nSpacing == value)
214                 return;
215             nSpacing = value;
216             query_resize();
217         }
218 
set_scroll(ssize_t scroll)219         void LSPMenu::set_scroll(ssize_t scroll)
220         {
221             if (scroll < 0)
222                 scroll = 0;
223             else if (scroll > nScrollMax)
224                 scroll = nScrollMax;
225 
226             if (nScroll == scroll)
227                 return;
228             nScroll = scroll;
229 
230             query_draw();
231             if (pWindow != NULL)
232                 pWindow->query_draw();
233         }
234 
query_resize()235         void LSPMenu::query_resize()
236         {
237             LSPWidgetContainer::query_resize();
238             if (pWindow != NULL)
239                 pWindow->query_resize();
240         }
241 
add(LSPWidget * child)242         status_t LSPMenu::add(LSPWidget *child)
243         {
244             LSPMenuItem *item = widget_cast<LSPMenuItem>(child);
245             if (child == NULL)
246                 return STATUS_BAD_ARGUMENTS;
247 
248             if (!vItems.add(item))
249                 return STATUS_NO_MEM;
250 
251             item->set_parent(this);
252 
253             query_resize();
254             return STATUS_SUCCESS;
255         }
256 
remove(LSPWidget * child)257         status_t LSPMenu::remove(LSPWidget *child)
258         {
259             size_t n            = vItems.size();
260             for (size_t i=0; i<n; ++i)
261             {
262                 LSPMenuItem *item   = vItems.at(i);
263                 if (item == child)
264                 {
265                     query_resize();
266                     return (vItems.remove(i)) ? STATUS_OK : STATUS_UNKNOWN_ERR;
267                 }
268             }
269 
270             return STATUS_NOT_FOUND;
271         }
272 
check_inside_submenu(ws_event_t * ev)273         LSPMenu *LSPMenu::check_inside_submenu(ws_event_t *ev)
274         {
275             LSPMenu *handler = NULL;
276             if ((pActiveMenu != NULL) &&
277                 (pActiveMenu->pWindow != NULL) &&
278                 (pActiveMenu->pWindow->visible()))
279             {
280                 // Get window geometry
281                 realize_t r1, r2;
282                 pWindow->get_absolute_geometry(&r1);
283                 pActiveMenu->pWindow->get_absolute_geometry(&r2);
284                 lsp_trace("wnd=%p, active=%p, ev={%d, %d}, r1={%d, %d}, r2={%d, %d}",
285                         pWindow, pActiveMenu,
286                         int(ev->nLeft), int(ev->nTop),
287                         int(r1.nLeft), int(r1.nTop),
288                         int(r2.nLeft), int(r2.nTop)
289                     );
290 
291                 ws_event_t xev = *ev;
292                 xev.nLeft   = r1.nLeft + ev->nLeft - r2.nLeft;
293                 xev.nTop    = r1.nTop  + ev->nTop - r2.nTop;
294 
295                 if ((handler = pActiveMenu->check_inside_submenu(&xev)) != NULL)
296                 {
297                     *ev         = xev;
298                     return handler;
299                 }
300             }
301 
302             if ((pWindow != NULL) &&
303                 (pWindow->visible()))
304             {
305                 lsp_trace("check ev {%d, %d} inside of wnd %p, {%d, %d, %d, %d} x { %d, %d }",
306                         int(ev->nLeft), int(ev->nTop),
307                         pWindow,
308                         int(pWindow->left()), int(pWindow->top()),
309                         int(pWindow->right()), int(pWindow->bottom()),
310                         int(pWindow->width()), int(pWindow->height())
311                         );
312 
313                 if ((ev->nLeft >= 0) &&
314                     (ev->nTop >= 0) &&
315                     (ev->nLeft < pWindow->width()) &&
316                     (ev->nTop < pWindow->height()))
317                     return this;
318             }
319 
320             return NULL;
321         }
322 
find_item(ssize_t mx,ssize_t my,ssize_t * ry)323         ssize_t LSPMenu::find_item(ssize_t mx, ssize_t my, ssize_t *ry)
324         {
325 //            // Are we inside of submenu?
326 //            if (check_inside_submenu(mx, my))
327 //                return nSelected;
328 
329             if ((mx < 0) || (mx >= sSize.nWidth))
330                 return SEL_NONE;
331             if ((my < 0) || (my >= sSize.nHeight))
332                 return SEL_NONE;
333 
334             font_parameters_t fp;
335             sFont.get_parameters(&fp);
336 
337             ssize_t separator = fp.Height * 0.5f + nSpacing;
338             fp.Height      += nSpacing;
339 
340             if (nScrollMax > 0)
341             {
342                 if ((nScroll > 0) && (my < ssize_t(nBorder + separator))) // Top button
343                     return SEL_TOP_SCROLL;
344                 else if ((nScroll < nScrollMax) && (my > ssize_t(sSize.nHeight - nBorder - separator))) // Bottom button
345                     return SEL_BOTTOM_SCROLL;
346             }
347 
348             // Iterate over all menu items
349             ssize_t y       = sPadding.top() + nBorder - nScroll;
350             size_t n        = vItems.size();
351 
352             for (size_t i=0; i < n; ++i)
353             {
354                 LSPMenuItem *item = vItems.get(i);
355                 if ((item == NULL) || (!item->visible()))
356                     continue;
357 
358                 if (item->is_separator())
359                     y += separator;
360                 else
361                 {
362                     if ((my >= y) && (my < (y + fp.Height)))
363                     {
364                         if (ry != NULL)
365                             *ry     = y;
366                         return i;
367                     }
368 
369                     y += fp.Height;
370                 }
371             }
372 
373             return SEL_NONE;
374         }
375 
draw(ISurface * s)376         void LSPMenu::draw(ISurface *s)
377         {
378             // Prepare palette
379             Color bg_color(sBgColor);
380             Color border(sBorderColor);
381             Color font(sFont.raw_color());
382             Color sel(sSelColor);
383             Color tmp;
384 
385             border.scale_lightness(brightness());
386             font.scale_lightness(brightness());
387             sel.scale_lightness(brightness());
388 
389             // Draw background
390             s->clear(bg_color);
391 
392             font_parameters_t fp;
393             text_parameters_t tp;
394             sFont.get_parameters(s, &fp);
395 
396             ssize_t separator = fp.Height * 0.5f + nSpacing;
397             ssize_t sep_len = sSize.nWidth - (nBorder + nSpacing) * 2;
398             ssize_t hspace  = nSpacing >> 1;
399 
400             fp.Height      += nSpacing;
401             ssize_t y       = sPadding.top() + nBorder - nScroll;
402             ssize_t x       = sPadding.left() + nBorder;
403             size_t n        = vItems.size();
404 
405             LSPString text;
406 
407             for (size_t i=0; i < n; ++i)
408             {
409                 LSPMenuItem *item = vItems.get(i);
410                 if ((item == NULL) || (!item->visible()))
411                     continue;
412 
413 //                lsp_trace("x,y = %d, %d", int(x), int(y));
414 
415                 if (y >= sSize.nHeight)
416                     break;
417 
418                 if (item->is_separator())
419                 {
420                     if (y > (-separator))
421                     {
422                         if (sep_len > 0)
423                             s->fill_rect(x - sPadding.left() + nSpacing, y + (separator >> 1), sep_len, 1, border);
424                     }
425 
426                     y += separator;
427                 }
428                 else
429                 {
430                     if (y > (-fp.Height))
431                     {
432                         item->text()->format(&text);
433                         if (nSelected == ssize_t(i))
434                         {
435                             s->fill_rect(nBorder, y, sSize.nWidth - nBorder*2, fp.Height, sel);
436                             tmp.copy(bg_color);
437                         }
438                         else
439                             tmp.copy(font);
440 
441                         if (!text.is_empty())
442                             sFont.draw(s, x, y + fp.Ascent + hspace, tmp, &text);
443 
444                         if (item->has_submenu())
445                         {
446                             sFont.get_text_parameters(s, &tp, "►");
447                             sFont.draw(s, sSize.nWidth - nBorder - nSpacing - tp.XAdvance - 2, y + fp.Ascent + hspace, tmp, "►");
448                         }
449                     }
450 
451                     y += fp.Height;
452                 }
453             }
454 
455             if (nScrollMax > 0)
456             {
457                 float cx = sSize.nWidth * 0.5f;
458                 float aa = s->set_antialiasing(true);
459 
460                 // Top button
461                 if (nScroll > 0)
462                 {
463                     s->fill_rect(nBorder, nBorder, sSize.nWidth - nBorder * 2, separator, bg_color);
464                     if (nSelected == SEL_TOP_SCROLL)
465                     {
466                         tmp.copy(bg_color);
467                         s->fill_rect(nBorder + 1, nBorder + 1, sSize.nWidth - (nBorder + 1)* 2, separator - 1, border);
468                     }
469                     else
470                         tmp.copy(font);
471 
472                     // Draw arrow up
473                     s->fill_triangle(
474                         cx, nBorder + 3,
475                         cx + separator, nBorder + separator - 2,
476                         cx - separator, nBorder + separator - 2,
477                         tmp);
478                 }
479                 else if (sPadding.top() > 0)
480                     s->fill_rect(nBorder, nBorder, sSize.nWidth - nBorder * 2, sPadding.top(), bg_color);
481 
482                 // Bottom button
483                 if (nScroll < nScrollMax)
484                 {
485                     s->fill_rect(nBorder, sSize.nHeight - nBorder - separator,
486                         sSize.nWidth - nBorder * 2, separator, bg_color);
487 
488                     if (nSelected == SEL_BOTTOM_SCROLL)
489                     {
490                         tmp.copy(bg_color);
491                         s->fill_rect(nBorder + 1, sSize.nHeight - nBorder - separator,
492                             sSize.nWidth - (nBorder + 1) * 2, separator - 1, border);
493                     }
494                     else
495                         tmp.copy(font);
496 
497                     // Draw arrow down
498                     s->fill_triangle(
499                         cx, sSize.nHeight - nBorder - 3,
500                         cx + separator, sSize.nHeight - nBorder - separator + 2,
501                         cx - separator, sSize.nHeight - nBorder - separator + 2,
502                         tmp);
503                 }
504                 else if (sPadding.bottom() > 0)
505                     s->fill_rect(nBorder, sSize.nHeight - nBorder - sPadding.bottom(),
506                         sSize.nWidth - nBorder * 2, sPadding.bottom(), bg_color);
507 
508                 // Restore anti-aliasing
509                 s->set_antialiasing(aa);
510             }
511 
512             if (nBorder > 0)
513                 s->fill_frame(0, 0, sSize.nWidth, sSize.nHeight,
514                     nBorder, nBorder, sSize.nWidth - nBorder * 2, sSize.nHeight - nBorder * 2, border);
515         }
516 
hide()517         bool LSPMenu::hide()
518         {
519             // Forget the parent menu
520             pParentMenu = NULL;
521 
522             // Hide active submenu if present
523             if (pActiveMenu != NULL)
524             {
525                 pActiveMenu->hide();
526                 pActiveMenu = NULL;
527             }
528 
529             // Hide window showing menu
530             if (pWindow != NULL)
531                 pWindow->hide();
532 
533             if (!is_visible())
534                 return false;
535 
536             return LSPWidgetContainer::hide();
537         }
538 
show()539         bool LSPMenu::show()
540         {
541             if (is_visible())
542                 return false;
543 
544             size_t screen = pDisplay->display()->default_screen();
545             LSPWindow *top = widget_cast<LSPWindow>(toplevel());
546             if (top != NULL)
547                 screen = top->screen();
548 
549             return show(screen, nPopupLeft, nPopupTop);
550         }
551 
show(size_t screen)552         bool LSPMenu::show(size_t screen)
553         {
554             return show(screen, nPopupLeft, nPopupTop);
555         }
556 
show(LSPWidget * w)557         bool LSPMenu::show(LSPWidget *w)
558         {
559             return show(w, nPopupLeft, nPopupTop);
560         }
561 
show(LSPWidget * w,ssize_t x,ssize_t y)562         bool LSPMenu::show(LSPWidget *w, ssize_t x, ssize_t y)
563         {
564             if (is_visible())
565                 return false;
566 
567             size_t screen = pDisplay->display()->default_screen();
568             LSPWindow *top = widget_cast<LSPWindow>(toplevel());
569             if (top != NULL)
570                 screen = top->screen();
571 
572             return show(w, screen, x, y);
573         }
574 
show(LSPWidget * w,const ws_event_t * ev)575         bool LSPMenu::show(LSPWidget *w, const ws_event_t *ev)
576         {
577             if (ev == NULL)
578                 return show(w, nPopupLeft, nPopupTop);
579 
580             realize_t r;
581             r.nLeft     = 0;
582             r.nTop      = 0;
583             r.nWidth    = 0;
584             r.nHeight   = 0;
585 
586             LSPWindow *parent = widget_cast<LSPWindow>(w->toplevel());
587             if (parent != NULL)
588                 parent->get_absolute_geometry(&r);
589 
590             return show(w, r.nLeft + ev->nLeft, r.nTop + ev->nTop);
591         }
592 
show(size_t screen,ssize_t left,ssize_t top)593         bool LSPMenu::show(size_t screen, ssize_t left, ssize_t top)
594         {
595             return show(NULL, screen, left, top);
596         }
597 
show(LSPWidget * w,size_t screen,ssize_t left,ssize_t top)598         bool LSPMenu::show(LSPWidget *w, size_t screen, ssize_t left, ssize_t top)
599         {
600             if (is_visible())
601                 return false;
602 
603             // Determine what screen to use
604             IDisplay *dpy = pDisplay->display();
605             if (screen >= dpy->screens())
606                 screen = dpy->default_screen();
607 
608             // Now we are ready to create window
609             if (pWindow == NULL)
610             {
611                 // Create window
612                 pWindow = new MenuWindow(pDisplay, this, screen);
613                 if (pWindow == NULL)
614                     return false;
615 
616                 // Initialize window
617                 status_t result = pWindow->init();
618                 if (result != STATUS_OK)
619                 {
620                     pWindow->destroy();
621                     delete pWindow;
622                     pWindow = NULL;
623                     return false;
624                 }
625 
626                 pWindow->set_border_style(BS_POPUP);
627                 pWindow->actions()->set_actions(WA_POPUP);
628             }
629 
630             // Get initial window geometry
631             realize_t wr;
632             pWindow->get_geometry(&wr);
633             if (left >= 0)
634                 wr.nLeft        = left;
635             else if (wr.nLeft < 0)
636                 wr.nLeft        = 0;
637             if (top >= 0)
638                 wr.nTop         = top;
639             else if (wr.nTop < 0)
640                 wr.nTop         = 0;
641 
642 
643             // Now request size and adjust location
644             size_request_t sr;
645             pWindow->size_request(&sr);
646 
647             ssize_t sw = 0, sh = 0;
648             dpy->screen_size(pWindow->screen(), &sw, &sh);
649             ssize_t xlast = wr.nLeft + sr.nMinWidth, ylast = wr.nTop + sr.nMinHeight;
650 
651             if (xlast >  sw)
652                 wr.nLeft   -= (xlast - sw);
653             if (ylast > sh)
654                 wr.nTop    -= (ylast - sh);
655             wr.nWidth       = sr.nMinWidth;
656             wr.nHeight      = sr.nMinHeight;
657 
658             // Now we can set the geometry and show window
659             pWindow->set_geometry(&wr);
660             wr.nLeft        = 0;
661             wr.nTop         = 0;
662             realize(&wr);
663             nSelected       = SEL_NONE;
664 
665             pWindow->show(w);
666 
667             // Need to perform grabbing?
668             pParentMenu = widget_cast<LSPMenu>(w);
669             if (pParentMenu == NULL)
670                 pWindow->grab_events(GRAB_MENU);
671 
672             return LSPWidgetContainer::show();
673         }
674 
size_request(size_request_t * r)675         void LSPMenu::size_request(size_request_t *r)
676         {
677             r->nMinWidth    = 0;
678             r->nMinHeight   = 0;
679             r->nMaxWidth    = -1;
680             r->nMaxHeight   = -1;
681 
682             // Create surface
683             ISurface *s = pDisplay->create_surface(1, 1);
684             if (s == NULL)
685                 return;
686 
687             // Estimate the size of menu
688             font_parameters_t fp;
689             text_parameters_t tp;
690             sFont.get_parameters(s, &fp);
691             size_t n = vItems.size();
692             ssize_t separator = fp.Height * 0.5f;
693             ssize_t subitem = 0;
694 
695             LSPString text;
696             for (size_t i=0; i<n; ++i)
697             {
698                 LSPMenuItem *mi = vItems.at(i);
699                 if ((mi == NULL) || (!mi->visible()))
700                     continue;
701 
702                 if (mi->is_separator())
703                 {
704                     r->nMinHeight += separator + nSpacing;
705                     if (r->nMinWidth < fp.Height)
706                         r->nMinWidth = fp.Height;
707                 }
708                 else
709                 {
710                     r->nMinHeight  += fp.Height + nSpacing;
711                     ssize_t width   = (mi->submenu() != NULL) ? separator : 0;
712 
713                     mi->text()->format(&text);
714                     if (!text.is_empty())
715                     {
716                         sFont.get_text_parameters(s, &tp, &text);
717                         width          += tp.XAdvance;
718                     }
719 
720                     if ((subitem <= 0) && (mi->has_submenu()))
721                     {
722                         sFont.get_text_parameters(s, &tp, "►");
723                         subitem        += tp.XAdvance + 2;
724                     }
725 
726                     if (r->nMinWidth < width)
727                         r->nMinWidth        = width;
728                 }
729             }
730 
731             r->nMinWidth    += nBorder * 2 + subitem + sPadding.horizontal();
732             r->nMinHeight   += nBorder * 2 + sPadding.vertical();
733 
734             // Destroy surface
735             s->destroy();
736             delete s;
737         }
738 
realize(const realize_t * r)739         void LSPMenu::realize(const realize_t *r)
740         {
741             LSPWidgetContainer::realize(r);
742 
743             size_request_t sr;
744             size_request(&sr);
745 
746             nScrollMax      = sr.nMinHeight - r->nHeight;
747             set_scroll(nScroll);
748 //            lsp_trace("scroll_max = %d, scroll = %d", int(nScrollMax), int(nScroll));
749 
750             query_draw();
751             if (pWindow != NULL)
752                 pWindow->query_draw();
753         }
754 
on_mouse_down(const ws_event_t * e)755         status_t LSPMenu::on_mouse_down(const ws_event_t *e)
756         {
757             if (nMBState == 0)
758             {
759                 if (!inside(e->nLeft, e->nTop))
760                 {
761                     hide();
762                     return STATUS_OK;
763                 }
764             }
765 
766             nMBState |= (1 << e->nCode);
767             ssize_t iy = 0;
768             ssize_t sel = find_item(e->nLeft, e->nTop, &iy);
769             selection_changed(sel, iy);
770 
771             return STATUS_OK;
772         }
773 
on_mouse_up(const ws_event_t * e)774         status_t LSPMenu::on_mouse_up(const ws_event_t *e)
775         {
776             if ((nMBState == (1 << MCB_LEFT)) && (e->nCode == MCB_LEFT))
777             {
778                 LSPMenu *parent = this;
779                 while (parent->pParentMenu != NULL)
780                     parent  = parent->pParentMenu;
781 
782                 // Cleanup mouse button state flag
783                 nMBState &= ~ (1 << e->nCode);
784 
785                 // Selection was found ?
786                 LSPMenuItem *item = NULL;
787                 ssize_t iy = 0;
788                 ssize_t sel = find_item(e->nLeft, e->nTop, &iy);
789 
790                 // Notify that selection has changed
791                 selection_changed(sel, iy);
792 
793                 if (sel >= 0)
794                 {
795                     item = vItems.get(sel);
796                     if (item == NULL)
797                         parent->hide();
798                     else if (item->hidden())
799                     {
800                         item    = NULL;
801                         parent->hide();
802                     }
803                     else if (!item->has_submenu())
804                         parent->hide();
805 
806                     if (item != NULL)
807                     {
808                         ws_event_t ev = *e;
809                         item->slots()->execute(LSPSLOT_SUBMIT, item, &ev);
810                     }
811                 }
812                 else
813                 {
814                     if ((sel != SEL_TOP_SCROLL) && (sel != SEL_BOTTOM_SCROLL))
815                         parent->hide();
816                 }
817             }
818             else
819             {
820                 // Cleanup mouse button state flag
821                 nMBState &= ~ (1 << e->nCode);
822                 if (nMBState == 0)
823                     hide();
824             }
825 
826             return STATUS_OK;
827         }
828 
on_mouse_scroll(const ws_event_t * e)829         status_t LSPMenu::on_mouse_scroll(const ws_event_t *e)
830         {
831             font_parameters_t fp;
832             sFont.get_parameters(&fp);
833             ssize_t amount = fp.Height + nSpacing;
834             if (amount < 1)
835                 amount = 1;
836 
837             ssize_t scroll = nScroll;
838             if (e->nCode == MCD_UP)
839                 set_scroll(nScroll - amount);
840             else if (e->nCode == MCD_DOWN)
841                 set_scroll(nScroll + amount);
842 
843             if (scroll != nScroll)
844             {
845                 ssize_t sel = nSelected, iy = 0;
846                 nSelected   = find_item(e->nLeft, e->nTop, &iy);
847 
848                 if (sel != nSelected)
849                 {
850                     // Notify that selection has changed
851                     selection_changed(nSelected, iy);
852 
853                     // Query for draw
854                     query_draw();
855                     if (pWindow != NULL)
856                         pWindow->query_draw();
857                 }
858             }
859 
860             return STATUS_OK;
861         }
862 
selection_changed(ssize_t sel,ssize_t iy)863         void LSPMenu::selection_changed(ssize_t sel, ssize_t iy)
864         {
865             // Get menu item
866             LSPMenuItem *item = (sel >= 0) ? vItems.get(sel) : NULL;
867             if ((item != NULL) && (pActiveMenu == item->submenu()))
868                 return;
869 
870             if (pActiveMenu != NULL)
871             {
872                 pActiveMenu->hide();
873                 pActiveMenu = NULL;
874             }
875 
876             if (item == NULL)
877                 return;
878 
879             if ((pActiveMenu = item->submenu()) == NULL)
880                 return;
881 
882             // Get screen size
883             IDisplay *dpy = pDisplay->display();
884             ssize_t sw = 0, sh = 0;
885             dpy->screen_size(pWindow->screen(), &sw, &sh);
886 
887             // Get window geometry
888             realize_t wr;
889             pWindow->get_geometry(&wr);
890             ssize_t xlast = wr.nLeft + wr.nWidth;
891 
892             // Estimate active window size
893             size_request_t sr;
894             pActiveMenu->size_request(&sr);
895             if (sr.nMinWidth < 0)
896                 sr.nMinWidth = 0;
897 
898             if ((xlast + sr.nMinWidth) < sw)
899                 pActiveMenu->show(this, xlast, iy + wr.nTop);
900             else
901                 pActiveMenu->show(this, wr.nLeft - sr.nMinWidth, iy + wr.nTop);
902         }
903 
on_mouse_move(const ws_event_t * e)904         status_t LSPMenu::on_mouse_move(const ws_event_t *e)
905         {
906             lsp_trace("x=%d, y=%d", int(e->nLeft), int(e->nTop));
907             ssize_t sel = nSelected;
908             ssize_t iy  = 0;
909             nSelected   = find_item(e->nLeft, e->nTop, &iy);
910 
911             if (sel != nSelected)
912             {
913 //                lsp_trace("selection changed. Was %d, now %d", int(sel), int(nSelected));
914                 // Update timer status
915                 if ((nSelected == SEL_TOP_SCROLL) || (nSelected == SEL_BOTTOM_SCROLL))
916                     sScroll.launch(0, 25);
917                 else
918                 {
919                     sScroll.cancel();
920                     selection_changed(nSelected, iy);
921                 }
922 
923                 // Query for draw
924                 query_draw();
925                 if (pWindow != NULL)
926                     pWindow->query_draw();
927             }
928 
929             return STATUS_OK;
930         }
931 
timer_handler(timestamp_t time,void * arg)932         status_t LSPMenu::timer_handler(timestamp_t time, void *arg)
933         {
934             LSPMenu *_this = static_cast<LSPMenu *>(arg);
935             if (_this == NULL)
936                 return STATUS_BAD_ARGUMENTS;
937             _this->update_scroll();
938             return STATUS_OK;
939         }
940 
update_scroll()941         void LSPMenu::update_scroll()
942         {
943             font_parameters_t fp;
944             sFont.get_parameters(&fp);
945             ssize_t amount = fp.Height * 0.5f;
946             if (amount < 1)
947                 amount = 1;
948 
949             switch (nSelected)
950             {
951                 case SEL_TOP_SCROLL:
952                     set_scroll(nScroll - amount);
953                     if (nScroll <= 0)
954                         sScroll.cancel();
955                     break;
956 
957                 case SEL_BOTTOM_SCROLL:
958                     set_scroll(nScroll + amount);
959                     if (nScroll >= nScrollMax)
960                         sScroll.cancel();
961                     break;
962 
963                 default:
964                     sScroll.cancel();
965                     break;
966             }
967         }
968 
969     } /* namespace tk */
970 } /* namespace lsp */
971