ptformat: Update lib to upstream 3b60276
[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 #include <iostream>
23
24 #include "gtkmm2ext/cairocell.h"
25 #include "gtkmm2ext/utils.h"
26
27 using std::string;
28 using std::vector;
29 using std::map;
30 using std::max;
31 using std::cerr;
32 using std::endl;
33 using namespace Gtkmm2ext;
34
35 static const double cairo_font_fudge = 1.5;
36
37 CairoFontDescription::CairoFontDescription (Pango::FontDescription& fd)
38 {
39         _size = cairo_font_fudge * (fd.get_size() / PANGO_SCALE);
40
41         switch (fd.get_style()) {
42         case Pango::STYLE_NORMAL:
43                 _slant = Cairo::FONT_SLANT_NORMAL;
44                 break;
45         case Pango::STYLE_OBLIQUE:
46                 _slant = Cairo::FONT_SLANT_OBLIQUE;
47                 break;
48         case Pango::STYLE_ITALIC:
49                 _slant = Cairo::FONT_SLANT_ITALIC;
50                 break;
51         }
52
53         switch (fd.get_weight()) {
54         case Pango::WEIGHT_ULTRALIGHT:
55                 _weight = Cairo::FONT_WEIGHT_NORMAL;
56                 break;
57
58         case Pango::WEIGHT_LIGHT:
59                 _weight = Cairo::FONT_WEIGHT_NORMAL;
60                 break;
61
62         case Pango::WEIGHT_NORMAL:
63                 _weight = Cairo::FONT_WEIGHT_NORMAL;
64                 break;
65
66         case Pango::WEIGHT_SEMIBOLD:
67                 _weight = Cairo::FONT_WEIGHT_BOLD;
68                 break;
69
70         case Pango::WEIGHT_BOLD:
71                 _weight = Cairo::FONT_WEIGHT_BOLD;
72                 break;
73
74         case Pango::WEIGHT_ULTRABOLD:
75                 _weight = Cairo::FONT_WEIGHT_BOLD;
76                 break;
77
78         case Pango::WEIGHT_HEAVY:
79                 _weight = Cairo::FONT_WEIGHT_BOLD;
80                 break;
81
82         /* to silence warnings when compiling with newer pango versions. */
83         default:
84                 _weight = Cairo::FONT_WEIGHT_NORMAL;
85                 break;
86         }
87
88         face = fd.get_family();
89 }
90
91 CairoCell::CairoCell (int32_t id)
92         : _id (id)
93         , _visible (true)
94         , _xpad (0)
95 {
96         bbox.x = 0;
97         bbox.y = 0;
98         bbox.width = 0;
99         bbox.height = 0;
100 }
101
102 CairoTextCell::CairoTextCell (int32_t id, double wc, boost::shared_ptr<CairoFontDescription> font)
103         : CairoCell (id)
104         , _width_chars (wc)
105         , _font (font)
106         , y_offset (0)
107         , x_offset (0)
108 {
109 }
110
111 void
112 CairoTextCell::set_text (const std::string& txt)
113 {
114         _text = txt;
115 }
116
117 void
118 CairoTextCell::render (Cairo::RefPtr<Cairo::Context>& context)
119 {
120         if (!_visible || _width_chars == 0) {
121                 return;
122         }
123
124         context->save ();
125
126         context->rectangle (bbox.x, bbox.y, bbox.width, bbox.height);
127         context->clip ();
128
129         _font->apply (context);
130         context->move_to (bbox.x, bbox.y + bbox.height + y_offset);
131         context->show_text (_text);
132
133         context->restore ();
134 }
135
136 void
137 CairoTextCell::set_size (Cairo::RefPtr<Cairo::Context>& context)
138 {
139         const uint32_t lim = (uint32_t) ceil (_width_chars);
140         vector<char> buf(lim+1);
141         uint32_t n;
142         double max_width = 0.0;
143         double max_height = 0.0;
144         Cairo::TextExtents ext;
145         double bsum = 0;
146
147         buf[lim] = '\0';
148
149         _font->apply (context);
150
151         for (int digit = 0; digit < 10; digit++) {
152
153                 for (n = 0; n < lim; ++n) {
154                         buf[n] = '0' + digit;
155                 }
156
157                 context->get_text_extents (&buf[0], ext);
158
159                 max_width = max (ext.width + ext.x_bearing, max_width);
160                 max_height = max (ext.height, max_height);
161                 bsum += ext.x_bearing;
162         }
163
164         /* add the average x-bearing for all digits as right hand side padding */
165
166         bbox.width = max_width + (bsum/10.0);
167
168         /* some fonts and some digits get their extents computed "too small", so fudge this
169            by adding 2
170         */
171         bbox.height = max_height;
172 }
173
174 CairoCharCell::CairoCharCell (int32_t id, char c)
175         : CairoTextCell (id, 1)
176 {
177         _text = c;
178 }
179
180 void
181 CairoCharCell::set_size (Cairo::RefPtr<Cairo::Context>& context)
182 {
183         Cairo::TextExtents ext;
184
185         _font->apply (context);
186
187         {
188                 const char* buf = "8";
189                 context->get_text_extents (buf, ext);
190                 /* same height as an "8" */
191                 bbox.height = ext.height;
192         }
193
194         {
195                 const char* buf = ":";
196                 context->get_text_extents (buf, ext);
197                 bbox.width = ext.width + (2.0 * ext.x_bearing);
198                 /* center vertically */
199                 y_offset = (ext.height - bbox.height) / 2.0;
200         }
201 }
202
203 CairoEditableText::CairoEditableText (boost::shared_ptr<CairoFontDescription> font)
204         : editing_cell (0)
205         , _draw_bg (true)
206         , max_cell_width (0)
207         , max_cell_height (0)
208         , _corner_radius (9)
209         , _xpad (0)
210         , _ypad (0)
211 {
212         set_font (font);
213
214         add_events (Gdk::POINTER_MOTION_HINT_MASK | Gdk::SCROLL_MASK | Gdk::KEY_PRESS_MASK | Gdk::KEY_RELEASE_MASK |
215                     Gdk::BUTTON_PRESS_MASK | Gdk::BUTTON_RELEASE_MASK | Gdk::SCROLL_MASK);
216         set_flags (Gtk::CAN_FOCUS);
217
218         set_can_default (true);
219 }
220
221 CairoEditableText::~CairoEditableText ()
222 {
223         /* we don't own cells */
224 }
225
226 bool
227 CairoEditableText::on_scroll_event (GdkEventScroll* ev)
228 {
229         CairoCell* cell = find_cell (ev->x, ev->y);
230
231         if (cell) {
232                 return scroll (ev, cell);
233         }
234
235         return false;
236 }
237
238 bool
239 CairoEditableText::on_focus_in_event (GdkEventFocus*)
240 {
241         return false;
242 }
243
244 bool
245 CairoEditableText::on_focus_out_event (GdkEventFocus*)
246 {
247         if (editing_cell) {
248                 queue_draw_cell (editing_cell);
249                 editing_cell = 0;
250         }
251         return false;
252 }
253
254 void
255 CairoEditableText::add_cell (CairoCell* cell)
256 {
257         cells.push_back (cell);
258
259         CairoTextCell* tc = dynamic_cast<CairoTextCell*>(cell);
260
261         if (tc) {
262                 tc->set_font (_font);
263         }
264
265         queue_resize ();
266 }
267
268 void
269 CairoEditableText::clear_cells ()
270 {
271         cells.clear ();
272         queue_resize ();
273 }
274
275 void
276 CairoEditableText::set_width_chars (CairoTextCell* cell, uint32_t wc)
277 {
278         if (cell) {
279                 cell->set_width_chars (wc);
280                 queue_resize ();
281         }
282 }
283
284 void
285 CairoEditableText::set_text (CairoTextCell* cell, const string& text)
286 {
287         cell->set_text (text);
288         queue_draw_cell (cell);
289 }
290
291 bool
292 CairoEditableText::on_expose_event (GdkEventExpose* ev)
293 {
294         Glib::RefPtr<Gdk::Window> win = get_window ();
295
296         if (!win) {
297                 std::cerr << "CET: no window to draw on\n";
298                 return false;
299         }
300
301         Cairo::RefPtr<Cairo::Context> context = win->create_cairo_context();
302
303         if (cells.empty()) {
304                 return true;
305         }
306
307         context->rectangle (ev->area.x, ev->area.y, ev->area.width, ev->area.height);
308         context->clip ();
309
310         Gtk::Allocation alloc = get_allocation ();
311         double width = alloc.get_width();
312         double height = alloc.get_height ();
313
314         if (_draw_bg) {
315                 context->set_source_rgba (bg_r, bg_g, bg_b, bg_a);
316                 if (_corner_radius) {
317                         rounded_rectangle (context, 0, 0, width, height, _corner_radius);
318                 } else {
319                         context->rectangle (0, 0, width, height);
320                 }
321                 context->fill ();
322         }
323
324         for (CellMap::iterator i = cells.begin(); i != cells.end(); ++i) {
325
326                 CairoCell* cell = (*i);
327
328                 /* is cell inside the expose area?
329                  */
330
331                 if (cell->intersects (ev->area)) {
332                         if (cell == editing_cell) {
333                                 context->set_source_rgba (edit_r, edit_b, edit_g, edit_a);
334                         } else {
335                                 context->set_source_rgba (r, g, b, a);
336                         }
337
338                         cell->render (context);
339                 }
340         }
341
342         return true;
343 }
344
345 void
346 CairoEditableText::queue_draw_cell (CairoCell* cell)
347 {
348         Glib::RefPtr<Gdk::Window> win = get_window();
349
350         if (!win) {
351                 return;
352         }
353
354         Gdk::Rectangle r;
355
356         r.set_x (cell->x());
357         r.set_y (cell->y());
358         r.set_width (cell->width());
359         r.set_height (cell->height());
360
361         Gdk::Region rg (r);
362         win->invalidate_region (rg, true);
363 }
364
365 CairoCell*
366 CairoEditableText::find_cell (uint32_t x, uint32_t y)
367 {
368         for (CellMap::iterator i = cells.begin(); i != cells.end(); ++i) {
369                 if ((*i)->covers (x, y)) {
370                         return (*i);
371                 }
372         }
373
374         return 0;
375 }
376
377 bool
378 CairoEditableText::on_button_press_event (GdkEventButton* ev)
379 {
380         CairoCell* cell = find_cell (ev->x, ev->y);
381         return button_press (ev, cell);
382 }
383
384 bool
385 CairoEditableText::on_button_release_event (GdkEventButton* ev)
386 {
387         CairoCell* cell = find_cell (ev->x, ev->y);
388         return button_release (ev, cell);
389 }
390
391 void
392 CairoEditableText::start_editing (CairoCell* cell)
393 {
394         stop_editing ();
395
396         if (cell) {
397                 editing_cell = cell;
398                 queue_draw_cell (cell);
399                 grab_focus ();
400         }
401 }
402
403 void
404 CairoEditableText::stop_editing ()
405 {
406         if (editing_cell) {
407                 queue_draw_cell (editing_cell);
408                 editing_cell = 0;
409         }
410 }
411
412 void
413 CairoEditableText::set_cell_sizes ()
414 {
415         Glib::RefPtr<Gdk::Window> win = get_window();
416
417         if (!win) {
418                 return;
419         }
420
421         Cairo::RefPtr<Cairo::Context> context = win->create_cairo_context();
422
423         if (!context) {
424                 return;
425         }
426
427         for (CellMap::iterator i = cells.begin(); i != cells.end(); ++i) {
428                 (*i)->set_size (context);
429         }
430 }
431
432 void
433 CairoEditableText::on_size_request (GtkRequisition* req)
434 {
435         set_cell_sizes ();
436
437         max_cell_width = 0;
438         max_cell_height = 0;
439
440         for (CellMap::iterator i = cells.begin(); i != cells.end(); ++i) {
441                 max_cell_width += (*i)->width();
442                 max_cell_height = std::max ((double) (*i)->height(), max_cell_height);
443         }
444
445         req->width = max_cell_width;
446         req->height = max_cell_height;
447 }
448
449 void
450 CairoEditableText::on_size_allocate (Gtk::Allocation& alloc)
451 {
452         Misc::on_size_allocate (alloc);
453
454         /* position each cell so that its centered in the allocated space
455          */
456
457         double x = (alloc.get_width() - max_cell_width)/2.0;
458         double y = (alloc.get_height() - max_cell_height)/2.0;
459
460         CellMap::iterator i = cells.begin();
461
462         while (i != cells.end()) {
463                 CairoCell* cell = (*i);
464
465                 cell->set_position (x, y);
466                 x += cell->width ();
467
468                 if (++i != cells.end()) {
469                         /* only add cell padding intra-cellularly */
470                         x += cell->xpad();
471                 } else {
472                         break;
473                 }
474         }
475 }
476
477 void
478 CairoEditableText::set_font (Pango::FontDescription& fd)
479 {
480         boost::shared_ptr<CairoFontDescription> cd (new CairoFontDescription (fd));
481         set_font (cd);
482 }
483
484 void
485 CairoEditableText::set_font (boost::shared_ptr<CairoFontDescription> fd)
486 {
487         for (CellMap::iterator i = cells.begin(); i != cells.end(); ++i) {
488                 CairoTextCell* tc = dynamic_cast<CairoTextCell*>(*i);
489                 if (tc && (!tc->font() || tc->font() == _font)) {
490                         tc->set_font (fd);
491                 }
492         }
493
494         _font = fd;
495
496         queue_resize ();
497         queue_draw ();
498 }
499