Fix broken whitespace. I'd apologize for the compile times if it was my fault :D
[ardour.git] / libs / gtkmm2ext / cairocell.cc
1 /*
2   Copyright (C) 2011 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
20 #include <algorithm>
21 #include <cmath>
22
23 #include "gtkmm2ext/cairocell.h"
24 #include "gtkmm2ext/utils.h"
25
26 using std::string;
27 using std::map;
28 using namespace Gtkmm2ext;
29
30 CairoCell::CairoCell ()
31         : _visible (true)
32         , _xpad (5)
33 {
34         bbox.x = 0;
35         bbox.y = 0;
36         bbox.width = 0;
37         bbox.height = 0;
38 }
39
40 CairoTextCell::CairoTextCell (uint32_t wc)
41         : _width_chars (wc)
42 {
43 }
44
45 void
46 CairoTextCell::render (Cairo::RefPtr<Cairo::Context>& context)
47 {
48         if (!_visible || _width_chars == 0) {
49                 return;
50         }
51
52         context->move_to (bbox.x, bbox.y);
53         pango_cairo_update_layout (context->cobj(), layout->gobj());
54         pango_cairo_show_layout (context->cobj(), layout->gobj());
55 }
56
57 void
58 CairoTextCell::set_size (Glib::RefPtr<Pango::Context>& context, const Pango::FontDescription& font)
59 {
60         layout = Pango::Layout::create (context);
61         layout->set_font_description (font);
62
63         Pango::FontMetrics metrics = context->get_metrics (font);
64
65         bbox.width = (_width_chars * metrics.get_approximate_digit_width ()) / PANGO_SCALE;
66         bbox.height = (metrics.get_ascent() + metrics.get_descent()) / PANGO_SCALE;
67 }
68
69 CairoCell*
70 CairoEditableText::get_cell (uint32_t id)
71 {
72         CellMap::iterator i = cells.find (id);
73         if (i == cells.end()) {
74                 return 0;
75         }
76         return i->second;
77 }
78
79 CairoEditableText::CairoEditableText ()
80         : editing_id (0)
81         , editing_pos (0)
82         , width (0)
83         , max_cell_height (0)
84         , height (0)
85         , corner_radius (24)
86         , xpad (10)
87         , ypad (5)
88 {
89         add_events (Gdk::POINTER_MOTION_HINT_MASK | Gdk::SCROLL_MASK | Gdk::KEY_PRESS_MASK | Gdk::KEY_RELEASE_MASK |
90                     Gdk::BUTTON_PRESS_MASK | Gdk::BUTTON_RELEASE_MASK);
91         set_flags (Gtk::CAN_FOCUS);
92         set_can_default (true);
93         set_receives_default (true);
94 }
95
96 bool
97 CairoEditableText::on_focus_in_event (GdkEventFocus* ev)
98 {
99         return false;
100 }
101
102 bool
103 CairoEditableText::on_focus_out_event (GdkEventFocus* ev)
104 {
105         if (editing_id) {
106                 CairoCell* cell = get_cell (editing_id);
107                 queue_draw_cell (cell);
108                 editing_id = 0;
109                 editing_pos = 0;
110         }
111
112         return false;
113 }
114
115 void
116 CairoEditableText::add_cell (uint32_t id, CairoCell* cell)
117 {
118         if (id > 0) {
119                 Glib::RefPtr<Pango::Context> context = get_pango_context ();
120                 cell->set_size (context, font);
121
122                 cells[id] = cell; /* we own it */
123         }
124 }
125
126 void
127 CairoEditableText::set_text (uint32_t id, const string& text)
128 {
129         CellMap::iterator i = cells.find (id);
130
131         if (i == cells.end()) {
132                 return;
133         }
134
135         CairoTextCell* textcell = dynamic_cast<CairoTextCell*> (i->second);
136
137         if (textcell) {
138                 set_text (textcell, text);
139         }
140 }
141
142 void
143 CairoEditableText::set_text (CairoTextCell* cell, const string& text)
144 {
145         cell->set_text (text);
146         queue_draw_cell (cell);
147 }
148
149 bool
150 CairoEditableText::on_expose_event (GdkEventExpose* ev)
151 {
152         Cairo::RefPtr<Cairo::Context> context = get_window()->create_cairo_context();
153
154         if (cells.empty()) {
155                 return true;
156         }
157
158         context->rectangle (ev->area.x, ev->area.y, ev->area.width, ev->area.height);
159         context->clip ();
160
161         context->set_source_rgba (bg_r, bg_g, bg_b, bg_a);
162         rounded_rectangle (context, 0, 0, width, height, corner_radius);
163         context->fill ();
164
165         for (CellMap::iterator i = cells.begin(); i != cells.end(); ++i) {
166
167                 uint32_t id = i->first;
168                 CairoCell* cell = i->second;
169
170                 /* is cell inside the expose area?
171                  */
172
173                 if (cell->intersects (ev->area)) {
174
175                         if (id == editing_id) {
176                                 context->set_source_rgba (edit_r, edit_b, edit_g, edit_a);
177                         } else {
178                                 context->set_source_rgba (r, g, b, a);
179                         }
180
181                         cell->render (context);
182                 }
183         }
184         return true;
185 }
186
187 void
188 CairoEditableText::queue_draw_cell (CairoCell* cell)
189 {
190         Glib::RefPtr<Gdk::Window> win = get_window();
191
192         if (!win) {
193                 return;
194         }
195
196         Gdk::Rectangle r;
197
198         r.set_x (cell->x());
199         r.set_y (cell->y());
200         r.set_width (cell->width());
201         r.set_height (cell->height());
202
203         Gdk::Region rg (r);
204         win->invalidate_region (rg, true);
205 }
206
207 CairoCell*
208 CairoEditableText::find_cell (uint32_t x, uint32_t y, uint32_t& id)
209 {
210         for (CellMap::iterator i = cells.begin(); i != cells.end(); ++i) {
211                 if (i->second->covers (x, y)) {
212                         id = i->first;
213                         return i->second;
214                 }
215         }
216
217         return 0;
218 }
219
220 bool
221 CairoEditableText::on_button_press_event (GdkEventButton* ev)
222 {
223         uint32_t id;
224         CairoCell* cell;
225
226         if (editing_id) {
227                 cell = get_cell (editing_id);
228                 /* redraw the old cell */
229                 queue_draw_cell (cell);
230         }
231
232         cell = find_cell (ev->x, ev->y, id);
233
234         if (!cell) {
235                 editing_id = 0;
236                 return false;
237         }
238
239         grab_focus ();
240         editing_id = id;
241         editing_pos = 0;
242
243         /* redraw the new cell (maybe the same as the old - no real cost) */
244         queue_draw_cell (cell);
245
246         return true;
247 }
248
249 bool
250 CairoEditableText::on_button_release_event (GdkEventButton* ev)
251 {
252         return true;
253 }
254
255 bool
256 CairoEditableText::on_key_press_event (GdkEventKey* ev)
257 {
258         if (!editing_id) {
259                 return true;
260         }
261
262         bool commit_change = false;
263
264         CairoCell* cell = get_cell (editing_id);
265
266         if (!cell) {
267                 return true;
268         }
269
270         CairoTextCell* text_cell = dynamic_cast<CairoTextCell*> (cell);
271
272         if (!text_cell) {
273                 return true;
274         }
275
276         string txt = text_cell->get_text ();
277
278         switch (ev->keyval) {
279         case GDK_Tab:
280                 queue_draw_cell (cell);
281                 edit_next_cell ();
282                 break;
283
284         case GDK_0:
285         case GDK_KP_0:
286                 txt[editing_pos] = '0';
287                 commit_change = true;
288                 break;
289         case GDK_1:
290         case GDK_KP_1:
291                 txt[editing_pos] = '1';
292                 commit_change = true;
293                 break;
294         case GDK_2:
295         case GDK_KP_2:
296                 txt[editing_pos] = '2';
297                 commit_change = true;
298                 break;
299         case GDK_3:
300         case GDK_KP_3:
301                 txt[editing_pos] = '3';
302                 commit_change = true;
303                 break;
304         case GDK_4:
305         case GDK_KP_4:
306                 txt[editing_pos] = '4';
307                 commit_change = true;
308                 break;
309         case GDK_5:
310         case GDK_KP_5:
311                 txt[editing_pos] = '5';
312                 commit_change = true;
313                 break;
314         case GDK_6:
315         case GDK_KP_6:
316                 txt[editing_pos] = '6';
317                 commit_change = true;
318                 break;
319         case GDK_7:
320         case GDK_KP_7:
321                 txt[editing_pos] = '7';
322                 commit_change = true;
323                 break;
324         case GDK_8:
325         case GDK_KP_8:
326                 txt[editing_pos] = '8';
327                 commit_change = true;
328                 break;
329         case GDK_9:
330         case GDK_KP_9:
331                 txt[editing_pos] = '9';
332                 commit_change = true;
333                 break;
334
335         case GDK_Right:
336                 if (editing_pos < text_cell->width_chars() - 1) {
337                         editing_pos++;
338                 }
339                 break;
340
341         case GDK_Left:
342                 if (editing_pos > 0) {
343                         editing_pos--;
344                 }
345                 break;
346
347         default:
348                 break;
349         }
350
351         if (commit_change) {
352                 set_text (text_cell, txt);
353
354                 if (++editing_pos >= text_cell->width_chars()) {
355                         edit_next_cell ();
356                 }
357         }
358
359
360         return true;
361 }
362
363 void
364 CairoEditableText::edit_next_cell ()
365 {
366         CairoCell* next;
367         CairoTextCell* next_text;
368         uint32_t next_id = editing_id + 1;
369
370         while (true) {
371                 next = get_cell (next_id);
372
373                 if (!next || !next->visible() || (next_text = dynamic_cast<CairoTextCell*> (next)) != 0) {
374                         break;
375                 }
376
377                 next_id += 1;
378         }
379
380         if (next) {
381                 editing_id = next_id;
382                 editing_pos = 0;
383                 queue_draw_cell (next_text);
384         } else {
385                 editing_id = 0;
386                 editing_pos = 0;
387         }
388 }
389
390 bool
391 CairoEditableText::on_key_release_event (GdkEventKey* ev)
392 {
393         return true;
394 }
395
396 void
397 CairoEditableText::on_size_request (GtkRequisition* req)
398 {
399         double x = 0;
400
401         max_cell_height = 0;
402
403         x = xpad;
404
405         for (CellMap::iterator i = cells.begin(); i != cells.end(); ++i) {
406                 CairoCell* cell = i->second;
407
408                 if (cell->visible()) {
409                         cell->set_position (x, ypad);
410                 }
411
412                 x += cell->width() + cell->xpad();
413                 max_cell_height = std::max ((double) cell->height(), max_cell_height);
414         }
415
416         x += xpad;
417
418         req->width = x;
419         req->height = max_cell_height + (ypad * 2);
420 }
421
422 void
423 CairoEditableText::on_size_allocate (Gtk::Allocation& alloc)
424 {
425         Misc::on_size_allocate (alloc);
426
427         width = alloc.get_width();
428         height = alloc.get_height();
429 }