e8068ba92fe9df5f891a50b1d8121dfb81655bde
[ardour.git] / gtk2_ardour / time_info_box.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 "pbd/compose.h"
22
23 #include "gtkmm2ext/cairocell.h"
24 #include "gtkmm2ext/gui_thread.h"
25 #include "gtkmm2ext/utils.h"
26
27 #include "ardour/location.h"
28 #include "ardour/session.h"
29
30 #include "time_info_box.h"
31 #include "audio_clock.h"
32 #include "editor.h"
33
34 #include "i18n.h"
35
36 using namespace Gtk;
37 using namespace ARDOUR;
38 using std::min;
39 using std::max;
40
41 TimeInfoBox::TimeInfoBox ()
42         : Table (4, 4)
43         , syncing_selection (false)
44         , syncing_punch (false)
45 {
46         selection_start = new AudioClock ("selection-start", false, "SelectionClockDisplay", false, false, false, false);
47         selection_end = new AudioClock ("selection-end", false, "SelectionClockDisplay", false, false, false, false);
48         selection_length = new AudioClock ("selection-length", false, "SelectionClockDisplay", false, false, true, false);
49
50         punch_start = new AudioClock ("punch-start", false, "PunchClockDisplay", false, false, false, false);
51         punch_end = new AudioClock ("punch-end", false, "PunchClockDisplay", false, false, false, false);
52
53         bool bg = true;
54
55         CairoEditableText& ss (selection_start->main_display());
56         ss.set_ypad (1);
57         ss.set_xpad (1);
58         ss.set_corner_radius (0);
59         ss.set_draw_background (bg);
60
61         CairoEditableText& se (selection_end->main_display());
62         se.set_ypad (1);
63         se.set_xpad (1);
64         se.set_corner_radius (0);
65         se.set_draw_background (bg);
66
67         CairoEditableText& sl (selection_length->main_display());
68         sl.set_ypad (1);
69         sl.set_xpad (2);
70         sl.set_corner_radius (0);
71         sl.set_draw_background (bg);
72
73         CairoEditableText& ps (punch_start->main_display());
74         ps.set_ypad (1);
75         ps.set_xpad (2);
76         ps.set_corner_radius (0);
77         ps.set_draw_background (bg);
78
79         CairoEditableText& pe (punch_end->main_display());
80         pe.set_ypad (1);
81         pe.set_xpad (2);
82         pe.set_corner_radius (0);
83         pe.set_draw_background (bg);
84
85         selection_title.set_markup (string_compose ("<span size=\"x-small\">%1</span>", _("Selection")));
86         punch_title.set_markup (string_compose ("<span size=\"x-small\">%1</span>", _("Punch")));
87
88         set_homogeneous (false);
89         set_spacings (0);
90         set_border_width (2);
91         set_col_spacings (2);
92
93         /* a bit more spacing between the two "sides" */
94         set_col_spacing (1, 10);
95
96         Gtk::Label* l;
97
98         attach (selection_title, 0, 2, 0, 1);
99         l = manage (new Label);
100         l->set_markup (string_compose ("<span size=\"x-small\">%1</span>", _("Start")));
101         attach (*l, 0, 1, 1, 2);
102         attach (*selection_start, 1, 2, 1, 2);
103         l = manage (new Label);
104         l->set_markup (string_compose ("<span size=\"x-small\">%1</span>", _("End")));
105         attach (*l, 0, 1, 2, 3);
106         attach (*selection_end, 1, 2, 2, 3);
107         l = manage (new Label);
108         l->set_markup (string_compose ("<span size=\"x-small\">%1</span>", _("Length")));
109         attach (*l, 0, 1, 3, 4);
110         attach (*selection_length, 1, 2, 3, 4);
111
112         attach (punch_title, 2, 4, 0, 1);
113         l = manage (new Label);
114         l->set_markup (string_compose ("<span size=\"x-small\">%1</span>", _("In")));
115         attach (*l, 2, 3, 1, 2);
116         attach (*punch_start, 3, 4, 1, 2);
117         l = manage (new Label);
118         l->set_markup (string_compose ("<span size=\"x-small\">%1</span>", _("Out")));
119         attach (*l, 2, 3, 2, 3);
120         attach (*punch_end, 3, 4, 2, 3);
121
122         show_all ();
123
124         selection_start->mode_changed.connect (sigc::bind (sigc::mem_fun (*this, &TimeInfoBox::sync_selection_mode), selection_start));
125         selection_end->mode_changed.connect (sigc::bind (sigc::mem_fun (*this, &TimeInfoBox::sync_selection_mode), selection_end));
126         selection_length->mode_changed.connect (sigc::bind (sigc::mem_fun (*this, &TimeInfoBox::sync_selection_mode), selection_length));
127
128         punch_start->mode_changed.connect (sigc::bind (sigc::mem_fun (*this, &TimeInfoBox::sync_punch_mode), punch_start));
129         punch_end->mode_changed.connect (sigc::bind (sigc::mem_fun (*this, &TimeInfoBox::sync_punch_mode), punch_end));
130
131         selection_start->signal_button_release_event().connect (sigc::bind (sigc::mem_fun (*this, &TimeInfoBox::clock_button_release_event), selection_start), true);
132         selection_end->signal_button_release_event().connect (sigc::bind (sigc::mem_fun (*this, &TimeInfoBox::clock_button_release_event), selection_end), true);
133
134         punch_start->signal_button_release_event().connect (sigc::bind (sigc::mem_fun (*this, &TimeInfoBox::clock_button_release_event), punch_start), true);
135         punch_end->signal_button_release_event().connect (sigc::bind (sigc::mem_fun (*this, &TimeInfoBox::clock_button_release_event), punch_end), true);
136
137         Editor::instance().get_selection().TimeChanged.connect (sigc::mem_fun (*this, &TimeInfoBox::selection_changed));
138         Editor::instance().get_selection().RegionsChanged.connect (sigc::mem_fun (*this, &TimeInfoBox::selection_changed));
139
140         Editor::instance().MouseModeChanged.connect (editor_connections, invalidator(*this), ui_bind (&TimeInfoBox::track_mouse_mode, this), gui_context());
141 }
142
143 TimeInfoBox::~TimeInfoBox ()
144 {
145         delete selection_length;
146         delete selection_end;
147         delete selection_start;
148         
149         delete punch_start;
150         delete punch_end;
151 }
152
153 void
154 TimeInfoBox::track_mouse_mode ()
155 {
156         selection_changed ();
157 }
158
159 bool
160 TimeInfoBox::clock_button_release_event (GdkEventButton* ev, AudioClock* src)
161 {
162         if (!_session) {
163                 return false;
164         }
165
166         if (ev->button == 1) {
167                 _session->request_locate (src->current_time ());
168                 return true;
169         }
170
171         return false;
172 }
173
174 void
175 TimeInfoBox::sync_selection_mode (AudioClock* src)
176 {
177         if (!syncing_selection) {
178                 syncing_selection = true;
179                 selection_start->set_mode (src->mode());
180                 selection_end->set_mode (src->mode());
181                 selection_length->set_mode (src->mode());
182                 syncing_selection = false;
183         }
184 }
185
186 void
187 TimeInfoBox::sync_punch_mode (AudioClock* src)
188 {
189         if (!syncing_punch) {
190                 syncing_punch = true;
191                 punch_start->set_mode (src->mode());
192                 punch_end->set_mode (src->mode());
193                 syncing_punch = false;
194         }
195 }
196         
197
198 void
199 TimeInfoBox::set_session (Session* s)
200 {
201         SessionHandlePtr::set_session (s);
202
203         selection_start->set_session (s);
204         selection_end->set_session (s);
205         selection_length->set_session (s);
206
207         punch_start->set_session (s);
208         punch_end->set_session (s);
209
210         if (s) {
211                 Location* punch = s->locations()->auto_punch_location ();
212                 
213                 if (punch) {
214                         watch_punch (punch);
215                 }
216                 
217                 _session->auto_punch_location_changed.connect (_session_connections, MISSING_INVALIDATOR, 
218                                                                boost::bind (&TimeInfoBox::punch_location_changed, this, _1), gui_context());
219         }
220 }
221
222 void
223 TimeInfoBox::selection_changed ()
224 {
225         framepos_t s, e;
226         Selection& selection (Editor::instance().get_selection());
227
228         switch (Editor::instance().current_mouse_mode()) {
229
230         case Editing::MouseObject:
231                 if (Editor::instance().internal_editing()) {
232                         /* displaying MIDI note selection is tricky */
233                         
234                         selection_start->set_off (true);
235                         selection_end->set_off (true);
236                         selection_length->set_off (true);
237
238                 } else {
239                         if (selection.regions.empty()) {
240                                 if (selection.points.empty()) {
241                                         selection_start->set_off (true);
242                                         selection_end->set_off (true);
243                                         selection_length->set_off (true);
244                                 } else {
245                                         s = max_framepos;
246                                         e = 0;
247                                         for (PointSelection::iterator i = selection.points.begin(); i != selection.points.end(); ++i) {
248                                                 s = min (s, (framepos_t) i->start);
249                                                 e = max (e, (framepos_t) i->end);
250                                         }
251                                         selection_start->set_off (false);
252                                         selection_end->set_off (false);
253                                         selection_length->set_off (false);
254                                         selection_start->set (s);
255                                         selection_end->set (e);
256                                         selection_length->set (e - s + 1);
257                                 }
258                         } else {
259                                 s = selection.regions.start();
260                                 e = selection.regions.end_frame();
261                                 selection_start->set_off (false);
262                                 selection_end->set_off (false);
263                                 selection_length->set_off (false);
264                                 selection_start->set (s);
265                                 selection_end->set (e);
266                                 selection_length->set (e - s + 1);
267                         }
268                 }
269                 break;
270
271         case Editing::MouseRange:
272                 if (selection.time.empty()) {
273                         selection_start->set_off (true);
274                         selection_end->set_off (true);
275                         selection_length->set_off (true);
276                 } else {
277                         selection_start->set_off (false);
278                         selection_end->set_off (false);
279                         selection_length->set_off (false);
280                         selection_start->set (selection.time.start());
281                         selection_end->set (selection.time.end_frame());
282                         selection_length->set (selection.time.length());
283                 }
284                 break;
285
286         default:
287                 selection_start->set_off (true);
288                 selection_end->set_off (true);
289                 selection_length->set_off (true);       
290                 break;
291         }
292 }
293
294 void
295 TimeInfoBox::punch_location_changed (Location* loc)
296 {
297         if (loc) {
298                 watch_punch (loc);
299         } 
300 }
301
302 void
303 TimeInfoBox::watch_punch (Location* punch)
304 {
305         punch_connections.drop_connections ();
306
307         punch->start_changed.connect (punch_connections, MISSING_INVALIDATOR, boost::bind (&TimeInfoBox::punch_changed, this, _1), gui_context());
308         punch->end_changed.connect (punch_connections, MISSING_INVALIDATOR, boost::bind (&TimeInfoBox::punch_changed, this, _1), gui_context());
309
310         punch_changed (punch);
311 }
312
313 void
314 TimeInfoBox::punch_changed (Location* loc)
315 {
316         if (!loc) {
317                 punch_start->set (99999999);
318                 punch_end->set (999999999);
319                 return;
320         }
321
322         punch_start->set (loc->start());
323         punch_end->set (loc->end());
324 }       
325
326 bool
327 TimeInfoBox::on_expose_event (GdkEventExpose* ev)
328 {
329         {
330                 int x, y;
331                 Gtk::Widget* window_parent;
332                 Glib::RefPtr<Gdk::Window> win = Gtkmm2ext::window_to_draw_on (*this, &window_parent);
333
334                 if (win) {
335                 
336                         Cairo::RefPtr<Cairo::Context> context = win->create_cairo_context();
337
338 #if 0                   
339                         translate_coordinates (*window_parent, ev->area.x, ev->area.y, x, y);
340                         context->rectangle (x, y, ev->area.width, ev->area.height);
341                         context->clip ();
342 #endif
343                         translate_coordinates (*window_parent, 0, 0, x, y);
344                         context->set_source_rgba (0.149, 0.149, 0.149, 1.0);
345                         Gtkmm2ext::rounded_rectangle (context, x, y, get_allocation().get_width(), get_allocation().get_height(), 5);
346                         context->fill ();
347                 }
348         }
349
350         Table::on_expose_event (ev);
351
352         return false;
353 }