push2: tweak layout APIs etc.
[ardour.git] / libs / surfaces / push2 / menu.cc
1 /*
2         Copyright (C) 2016 Paul Davis
3
4         This program is free software; you can redistribute it and/or modify
5         it under the terms of the GNU General Public License as published by
6         the Free Software Foundation; either version 2 of the License, or
7         (at your option) any later version.
8
9         This program is distributed in the hope that it will be useful,
10         but WITHOUT ANY WARRANTY; without even the implied warranty of
11         MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12         GNU General Public License for more details.
13
14         You should have received a copy of the GNU General Public License
15         along with this program; if not, write to the Free Software
16         Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
17 */
18
19 #include <cairomm/context.h>
20 #include <cairomm/surface.h>
21 #include <cairomm/region.h>
22 #include <pangomm/layout.h>
23
24 #include "canvas/text.h"
25 #include "canvas/rectangle.h"
26 #include "canvas/colors.h"
27
28 #include "canvas.h"
29 #include "gui.h"
30 #include "push2.h"
31
32 using namespace ARDOUR;
33 using namespace std;
34 using namespace PBD;
35 using namespace Glib;
36 using namespace ArdourSurface;
37 using namespace ArdourCanvas;
38
39 #include "pbd/i18n.h"
40 #include "menu.h"
41
42 Push2Menu::Push2Menu (Item* parent, vector<string> s)
43         : Container (parent)
44         , baseline (-1)
45         , ncols (0)
46         , nrows (0)
47         , wrap (true)
48         , first (0)
49         , last (0)
50         , _active (0)
51 {
52         Pango::FontDescription fd ("Sans 10");
53
54         if (baseline < 0) {
55                 Push2Canvas* p2c = dynamic_cast<Push2Canvas*> (canvas());
56                 Glib::RefPtr<Pango::Layout> throwaway = Pango::Layout::create (p2c->image_context());
57                 throwaway->set_font_description (fd);
58                 throwaway->set_text (X_("Hg")); /* ascender + descender) */
59                 int h, w;
60                 throwaway->get_pixel_size (w, h);
61                 baseline = h;
62         }
63
64         active_bg = new Rectangle (this);
65
66         for (vector<string>::iterator si = s.begin(); si != s.end(); ++si) {
67                 Text* t = new Text (this);
68                 t->set_font_description (fd);
69                 t->set (*si);
70                 displays.push_back (t);
71         }
72
73 }
74
75 void
76 Push2Menu::set_layout (int c, int r)
77 {
78         ncols = c;
79         nrows = r;
80
81         set_active (_active);
82         rearrange (_active);
83 }
84
85 void
86 Push2Menu::rearrange (uint32_t initial_display)
87 {
88         if (initial_display >= displays.size()) {
89                 return;
90         }
91
92         vector<Text*>::iterator i = displays.begin();
93
94         /* move to first */
95
96         for (uint32_t n = 0; n < initial_display; ++n) {
97                 (*i)->hide ();
98                 ++i;
99         }
100
101         uint32_t index = initial_display;
102         uint32_t col = 0;
103         uint32_t row = 0;
104         bool active_shown = false;
105
106         while (i != displays.end()) {
107
108                 Coord x = col * Push2Canvas::inter_button_spacing();
109                 Coord y = 2 + (row * baseline);
110
111                 (*i)->set_position (Duple (x, y));
112
113                 if (index == _active) {
114                         active_bg->set (Rect (x - 1, y - 1,
115                                               x - 1 + Push2Canvas::inter_button_spacing(), y - 1 + baseline));
116                         active_bg->show ();
117                         active_shown = true;
118                 }
119
120                 (*i)->show ();
121                 last = index;
122                 ++i;
123                 ++index;
124
125                 if (++row >= nrows) {
126                         row = 0;
127                         if (++col >= ncols) {
128                                 /* no more to display */
129                                 break;
130                         }
131                 }
132
133         }
134
135         while (i != displays.end()) {
136                 (*i)->hide ();
137                 ++i;
138         }
139
140         if (!active_shown) {
141                 active_bg->hide ();
142         }
143
144         first = initial_display;
145
146         Rearranged (); /* EMIT SIGNAL */
147 }
148
149 void
150 Push2Menu::scroll (Direction dir, bool page)
151 {
152         switch (dir) {
153         case DirectionUp:
154                 if (_active == 0) {
155                         if (wrap) {
156                                 set_active (displays.size() - 1);
157                         }
158                 } else {
159                         set_active (_active - 1);
160                 }
161                 break;
162
163         case DirectionDown:
164                 if (_active == displays.size() - 1) {
165                         if (wrap) {
166                                 set_active (0);
167                         }
168                 } else {
169                         set_active (_active + 1);
170                 }
171                 break;
172
173         case DirectionLeft:
174                 if (page) {
175                         set_active (max (0, (int) (first - (nrows * ncols))));
176                 } else {
177                         if (_active / nrows == 0) {
178                                 /* in the first column, go to last column, same row */
179                                 if (wrap) {
180                                         set_active (displays.size() - 1 - active_row ());
181                                 }
182                         } else {
183                                 /* move to same row, previous column */
184                                 set_active (_active - nrows);
185                         }
186                 }
187                 break;
188
189         case DirectionRight:
190                 if (page) {
191                         set_active (min ((uint32_t) displays.size(), first + (nrows * ncols)));
192                 } else {
193                         if (_active / nrows == ncols) {
194                                 /* in the last column, go to same row in first column */
195                                 if (wrap) {
196                                 set_active (active_row());
197                                 }
198                         } else {
199                                 /* move to same row, next column */
200                                 set_active (_active + nrows);
201                         }
202                 }
203                 break;
204         }
205 }
206
207 void
208 Push2Menu::render (Rect const& area, Cairo::RefPtr<Cairo::Context> context) const
209 {
210         render_children (area, context);
211 }
212
213 void
214 Push2Menu::set_active (uint32_t index)
215 {
216         if (!parent() || (index == _active)) {
217                 return;
218         }
219
220         if (index >= displays.size()) {
221                 active_bg->hide ();
222                 return;
223         }
224
225         /* set text color for old active item, and the new one */
226
227         if (_active <= displays.size()) {
228                 displays[_active]->set_color (text_color);
229         }
230
231         displays[index]->set_color (contrast_color);
232
233         Duple p = displays[index]->position ();
234
235         active_bg->set (Rect (p.x - 1, p.y - 1, p.x - 1 + Push2Canvas::inter_button_spacing(), p.y - 1 + baseline ));
236         active_bg->show ();
237         _active = index;
238
239         if (_active < first) {
240
241                 /* we jumped before current visible range : try to put its column first
242                  */
243
244                 rearrange (active_top());
245
246         } else if (_active > last) {
247
248                 /* we jumped after current visible range : try putting its
249                  * column last
250                  */
251
252                 rearrange (active_top() - ((ncols - 1) * nrows));
253         }
254
255         ActiveChanged (); /* EMIT SIGNAL */
256 }
257
258 void
259 Push2Menu::set_text_color (Color c)
260 {
261         text_color = c;
262
263         for (vector<Text*>::iterator t = displays.begin(); t != displays.end(); ++t) {
264                 (*t)->set_color (c);
265         }
266
267 }
268
269 void
270 Push2Menu::set_active_color (Color c)
271 {
272         active_color = c;
273         contrast_color = contrasting_text_color (active_color);
274         if (active_bg) {
275                 active_bg->set_fill_color (c);
276         }
277
278         if (_active < displays.size()) {
279                 displays[_active]->set_color (contrast_color);
280         }
281 }
282
283 void
284 Push2Menu::set_font_description (Pango::FontDescription fd)
285 {
286         font_description = fd;
287
288         for (vector<Text*>::iterator t = displays.begin(); t != displays.end(); ++t) {
289                 (*t)->set_font_description (fd);
290         }
291 }