4f2566554ff5eea20f8cbcac6d69a5a5a72d91ef
[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::map;
29 using std::cerr;
30 using std::endl;
31 using namespace Gtkmm2ext;
32
33 CairoCell::CairoCell (int32_t id)
34         : _id (id)
35         , _visible (true)
36         , _xpad (2)
37 {
38         bbox.x = 0;
39         bbox.y = 0;
40         bbox.width = 0;
41         bbox.height = 0;
42 }
43
44 void
45 CairoColonCell::render (Cairo::RefPtr<Cairo::Context>& context)
46 {
47         /* two very small circles */
48         context->arc (bbox.x, bbox.y + (bbox.height/3.0), bbox.width/2.0, 0.0, M_PI*2.0);
49         context->fill ();
50         context->arc (bbox.x, bbox.y + (2.0 * bbox.height/3.0), bbox.width/2.0, 0.0, M_PI*2.0);
51         context->fill ();
52 }
53
54 void
55 CairoColonCell::set_size (Glib::RefPtr<Pango::Context>& context, const Pango::FontDescription& font)
56 {
57         Pango::FontMetrics metrics = context->get_metrics (font);
58         bbox.width = std::max (3.0, (0.25 * metrics.get_approximate_char_width() / PANGO_SCALE));
59         bbox.height = (metrics.get_ascent() + metrics.get_descent()) / PANGO_SCALE;
60 }
61
62 CairoTextCell::CairoTextCell (int32_t id, double wc)
63         : CairoCell (id)
64         , _width_chars (wc)
65 {
66 }
67
68 void
69 CairoTextCell::set_text (const std::string& txt)
70 {
71         layout->set_text (txt);
72 }
73
74 void
75 CairoTextCell::render (Cairo::RefPtr<Cairo::Context>& context)
76 {
77         if (!_visible || _width_chars == 0) {
78                 return;
79         }
80
81         context->move_to (bbox.x, bbox.y);
82         pango_cairo_update_layout (context->cobj(), layout->gobj());
83         pango_cairo_show_layout (context->cobj(), layout->gobj());
84 }
85
86 void
87 CairoTextCell::set_size (Glib::RefPtr<Pango::Context>& context, const Pango::FontDescription& font)
88 {
89         if (!layout) {
90                 layout = Pango::Layout::create (context);
91         }
92
93         layout->set_font_description (font);
94
95         Pango::FontMetrics metrics = context->get_metrics (font);
96
97         bbox.width = (_width_chars * metrics.get_approximate_digit_width ()) / PANGO_SCALE;
98         bbox.height = (metrics.get_ascent() + metrics.get_descent()) / PANGO_SCALE;
99 }
100
101 CairoEditableText::CairoEditableText ()
102         : editing_cell (0)
103         , _draw_bg (true)
104         , width (0)
105         , max_cell_height (0)
106         , height (0)
107         , _corner_radius (9)
108         , _xpad (5)
109         , _ypad (5)
110 {
111         add_events (Gdk::POINTER_MOTION_HINT_MASK | Gdk::SCROLL_MASK | Gdk::KEY_PRESS_MASK | Gdk::KEY_RELEASE_MASK |
112                     Gdk::BUTTON_PRESS_MASK | Gdk::BUTTON_RELEASE_MASK | Gdk::SCROLL_MASK);
113         set_flags (Gtk::CAN_FOCUS);
114
115         set_can_default (true);
116         set_receives_default (true);
117 }
118
119 CairoEditableText::~CairoEditableText ()
120 {
121         /* we don't own cells */
122 }
123
124 bool
125 CairoEditableText::on_scroll_event (GdkEventScroll* ev)
126 {
127         CairoCell* cell = find_cell (ev->x, ev->y);
128
129         if (cell) {
130                 return scroll (ev, cell);
131         }
132
133         return false;
134 }
135
136 bool
137 CairoEditableText::on_focus_in_event (GdkEventFocus* ev)
138 {
139         return false;
140 }
141
142 bool
143 CairoEditableText::on_focus_out_event (GdkEventFocus* ev)
144 {
145         if (editing_cell) {
146                 queue_draw_cell (editing_cell);
147                 editing_cell = 0;
148         }
149         return false;
150 }
151
152 void
153 CairoEditableText::add_cell (CairoCell* cell)
154 {
155         Glib::RefPtr<Pango::Context> context = get_pango_context ();
156         cell->set_size (context, _font);
157         cells.push_back (cell);
158         queue_resize ();
159         queue_draw ();
160 }
161
162 void
163 CairoEditableText::clear_cells ()
164 {
165         cells.clear ();
166         queue_resize ();
167         queue_draw ();
168 }
169
170 void
171 CairoEditableText::set_width_chars (CairoTextCell* cell, uint32_t wc)
172 {
173         if (cell) {
174                 cell->set_width_chars (wc);
175                 queue_resize ();
176         }
177 }
178
179 void
180 CairoEditableText::set_text (CairoTextCell* cell, const string& text)
181 {
182         cell->set_text (text);
183         queue_draw_cell (cell);
184 }
185
186 bool
187 CairoEditableText::on_expose_event (GdkEventExpose* ev)
188 {
189         Cairo::RefPtr<Cairo::Context> context = get_window()->create_cairo_context();
190
191         if (cells.empty()) {
192                 return true;
193         }
194
195         context->rectangle (ev->area.x, ev->area.y, ev->area.width, ev->area.height);
196         context->clip ();
197
198         if (_draw_bg) {
199                 context->set_source_rgba (bg_r, bg_g, bg_b, bg_a);
200                 if (_corner_radius) {
201                         rounded_rectangle (context, 0, 0, width, height, _corner_radius);
202                 } else {
203                         context->rectangle (0, 0, width, height);
204                 }
205                 context->fill ();
206         }
207
208         for (CellMap::iterator i = cells.begin(); i != cells.end(); ++i) {
209
210                 CairoCell* cell = (*i);
211
212                 /* is cell inside the expose area?
213                  */
214
215                 if (cell->intersects (ev->area)) {
216                         if (cell == editing_cell) {
217                                 context->set_source_rgba (edit_r, edit_b, edit_g, edit_a);
218                         } else {
219                                 context->set_source_rgba (r, g, b, a);
220                         }
221
222                         cell->render (context);
223                 }
224         }
225
226         return true;
227 }
228
229 void
230 CairoEditableText::queue_draw_cell (CairoCell* cell)
231 {
232         Glib::RefPtr<Gdk::Window> win = get_window();
233
234         if (!win) {
235                 return;
236         }
237
238         Gdk::Rectangle r;
239
240         r.set_x (cell->x());
241         r.set_y (cell->y());
242         r.set_width (cell->width());
243         r.set_height (cell->height());
244
245         Gdk::Region rg (r);
246         win->invalidate_region (rg, true);
247 }
248
249 CairoCell*
250 CairoEditableText::find_cell (uint32_t x, uint32_t y)
251 {
252         for (CellMap::iterator i = cells.begin(); i != cells.end(); ++i) {
253                 if ((*i)->covers (x, y)) {
254                         return (*i);
255                 }
256         }
257
258         return 0;
259 }
260
261 bool
262 CairoEditableText::on_button_press_event (GdkEventButton* ev)
263 {
264         CairoCell* cell = find_cell (ev->x, ev->y);
265         return button_press (ev, cell);
266 }
267
268 bool
269 CairoEditableText::on_button_release_event (GdkEventButton* ev)
270 {
271         CairoCell* cell = find_cell (ev->x, ev->y);
272         return button_release (ev, cell);
273 }
274
275 void
276 CairoEditableText::start_editing (CairoCell* cell)
277 {
278         stop_editing ();
279
280         if (cell) {
281                 editing_cell = cell;
282                 queue_draw_cell (cell);
283                 grab_focus ();
284         }
285 }
286
287 void
288 CairoEditableText::stop_editing ()
289 {
290         if (editing_cell) {
291                 queue_draw_cell (editing_cell);
292                 editing_cell = 0;
293         }
294 }
295
296 void
297 CairoEditableText::on_size_request (GtkRequisition* req)
298 {
299         double x = 0;
300
301         max_cell_height = 0;
302
303         x = _xpad;
304
305         CellMap::iterator i = cells.begin(); 
306
307         while (i != cells.end()) {
308                 CairoCell* cell = (*i);
309
310                 if (cell->visible()) {
311                         cell->set_position (x, _ypad);
312                 }
313
314                 x += cell->width();
315                 max_cell_height = std::max ((double) cell->height(), max_cell_height);
316
317                 ++i;
318
319                 if (i != cells.end()) {
320                         /* only add cell padding intra-cellularly */
321                         x += cell->xpad();
322                 } else {
323                         break;
324                 }
325         }
326
327         x += _xpad;
328
329         req->width = x;
330         req->height = max_cell_height + (_ypad * 2);
331 }
332
333 void
334 CairoEditableText::on_size_allocate (Gtk::Allocation& alloc)
335 {
336         Misc::on_size_allocate (alloc);
337
338         width = alloc.get_width();
339         height = alloc.get_height();
340 }
341
342 void
343 CairoEditableText::set_font (const std::string& str)
344 {
345         set_font (Pango::FontDescription (str));
346 }
347
348 void
349 CairoEditableText::set_font (const Pango::FontDescription& fd)
350 {
351         Glib::RefPtr<Pango::Context> context = get_pango_context ();
352
353         _font = fd;
354
355         for (CellMap::iterator i = cells.begin(); i != cells.end(); ++i) {
356                 (*i)->set_size (context, _font);
357         }
358
359         queue_resize ();
360         queue_draw ();
361 }