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