1 // Licensed GNU LGPL v3 or later: http://www.gnu.org/licenses/lgpl.html
2 
3 #ifndef SPECTMORPH_LISTBOX_HH
4 #define SPECTMORPH_LISTBOX_HH
5 
6 namespace SpectMorph
7 {
8 
9 class ListBox : public Widget
10 {
11   std::vector<std::string> items;
12   int highlight_item = -1;
13   int m_selected_item = -1;
14   int items_per_page = 0;
15   int first_item = 0;
16   ScrollBar *scroll_bar = nullptr;
17   const double px_starty = 8;
18 public:
19   Signal<> signal_item_clicked;
20   Signal<> signal_item_double_clicked;
21 
ListBox(Widget * parent)22   ListBox (Widget *parent)
23     : Widget (parent)
24   {
25     scroll_bar = new ScrollBar (this, /* page_size */ 1, Orientation::VERTICAL);
26     connect (scroll_bar->signal_position_changed, [=] (double pos)
27       {
28         first_item = lrint (pos * items.size());
29         if (first_item < 0)
30           first_item = 0;
31         if (first_item > int (items.size()) - items_per_page)
32           first_item = items.size() - items_per_page;
33         update();
34       });
35     update_item_count();
36 
37     update_scrollbar_geometry();
38     connect (signal_width_changed, this, &ListBox::update_scrollbar_geometry);
39     connect (signal_height_changed, this, &ListBox::update_scrollbar_geometry);
40   }
41   void
update_scrollbar_geometry()42   update_scrollbar_geometry()
43   {
44     scroll_bar->set_x (width() - 24);
45     scroll_bar->set_y (8);
46     scroll_bar->set_width (16);
47     scroll_bar->set_height (height() - 16);
48   }
49   void
update_item_count()50   update_item_count()
51   {
52     first_item = 0;
53     scroll_bar->set_pos (0);
54     const int items_on_screen = (height() - 16) / 16;
55     if (items_on_screen < (int) items.size())
56       {
57         /* need to scroll items */
58         items_per_page = items_on_screen;
59         scroll_bar->set_page_size (items_per_page / double (items.size()));
60         scroll_bar->set_visible (true);
61       }
62     else
63       {
64         items_per_page = items.size();
65         scroll_bar->set_visible (false);
66       }
67   }
68   void
draw(const DrawEvent & devent)69   draw (const DrawEvent& devent) override
70   {
71     cairo_t *cr = devent.cr;
72     DrawUtils du (cr);
73 
74     double space = 2;
75     du.round_box (0, space, width(), height() - 2 * space, 1, 5, ThemeColor::FRAME);
76 
77     double starty = px_starty;
78     for (int i = first_item; i < first_item + items_per_page; i++)
79       {
80         const double box_width = scroll_bar->visible() ? width() - 28 : width() - 8;
81 
82         Color text_color (1, 1, 1);
83         if (m_selected_item == i)
84           {
85             du.round_box (4, starty, box_width, 16, 1, 5, Color::null(), ThemeColor::MENU_ITEM);
86             text_color = Color (0, 0, 0);
87           }
88         else if (highlight_item == i)
89           du.round_box (4, starty, box_width, 16, 1, 5, Color::null(), ThemeColor::MENU_BG);
90 
91         du.set_color (text_color);
92         du.text (items[i], 10, starty, box_width - 12, 16);
93         starty += 16;
94       }
95   }
96   void
add_item(const std::string & item_text)97   add_item (const std::string& item_text)
98   {
99     items.push_back (item_text);
100     update_item_count();
101   }
102   void
highlight_item_from_event(const MouseEvent & event)103   highlight_item_from_event (const MouseEvent& event)
104   {
105     int new_highlight_item = sm_bound<int> (0, first_item + (event.y - px_starty) / 16, items.size());
106 
107     if (new_highlight_item == (int) items.size()) /* highlight nothing */
108       new_highlight_item = -1;
109 
110     if (new_highlight_item != highlight_item)
111       {
112         highlight_item = new_highlight_item;
113         update();
114       }
115   }
116   void
mouse_move(const MouseEvent & event)117   mouse_move (const MouseEvent& event) override
118   {
119     highlight_item_from_event (event);
120   }
121   void
leave_event()122   leave_event() override
123   {
124     highlight_item = -1;
125     update();
126   }
127   void
mouse_press(const MouseEvent & event)128   mouse_press (const MouseEvent& event) override
129   {
130     highlight_item_from_event (event);
131 
132     if (event.button == LEFT_BUTTON)
133       {
134         if (event.double_click)
135           {
136             m_selected_item = highlight_item;
137             signal_item_double_clicked();
138           }
139         else
140           {
141             m_selected_item = highlight_item;
142             signal_item_clicked();
143             update();
144           }
145       }
146   }
147   bool
scroll(double dx,double dy)148   scroll (double dx, double dy) override
149   {
150     if (scroll_bar->visible())
151       return scroll_bar->scroll (dx, dy);
152     return false;
153   }
154   int
selected_item()155   selected_item()
156   {
157     return m_selected_item;
158   }
159   void
clear()160   clear()
161   {
162     items.clear();
163     m_selected_item = -1;
164     highlight_item = -1;
165     update_item_count();
166   }
167 };
168 
169 }
170 
171 #endif
172