switch to using boost::signals2 instead of sigc++, at least for libardour. not finish...
[ardour.git] / gtk2_ardour / editor_tempodisplay.cc
1 /*
2     Copyright (C) 2002 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 <cstdio> // for sprintf, grrr
21 #include <cstdlib>
22 #include <cmath>
23 #include <string>
24 #include <climits>
25
26 #include <libgnomecanvasmm.h>
27
28 #include "pbd/error.h"
29 #include "pbd/memento_command.h"
30
31 #include <gtkmm2ext/utils.h>
32 #include <gtkmm2ext/gtk_ui.h>
33
34 #include "ardour/session.h"
35 #include "ardour/tempo.h"
36 #include <gtkmm2ext/doi.h>
37 #include <gtkmm2ext/utils.h>
38
39 #include "editor.h"
40 #include "marker.h"
41 #include "simpleline.h"
42 #include "tempo_dialog.h"
43 #include "rgb_macros.h"
44 #include "gui_thread.h"
45 #include "time_axis_view.h"
46 #include "ardour_ui.h"
47 #include "tempo_lines.h"
48
49 #include "i18n.h"
50
51 using namespace std;
52 using namespace ARDOUR;
53 using namespace PBD;
54 using namespace Gtk;
55 using namespace Gtkmm2ext;
56 using namespace Editing;
57
58 void
59 Editor::remove_metric_marks ()
60 {
61         /* don't delete these while handling events, just punt till the GUI is idle */
62
63         for (Marks::iterator x = metric_marks.begin(); x != metric_marks.end(); ++x) {
64                 delete_when_idle (*x);
65         }
66         metric_marks.clear ();
67 }
68
69 void
70 Editor::draw_metric_marks (const Metrics& metrics)
71 {
72
73         const MeterSection *ms;
74         const TempoSection *ts;
75         char buf[64];
76
77         remove_metric_marks ();
78
79         for (Metrics::const_iterator i = metrics.begin(); i != metrics.end(); ++i) {
80
81                 if ((ms = dynamic_cast<const MeterSection*>(*i)) != 0) {
82                         snprintf (buf, sizeof(buf), "%g/%g", ms->beats_per_bar(), ms->note_divisor ());
83                         metric_marks.push_back (new MeterMarker (*this, *meter_group, ARDOUR_UI::config()->canvasvar_MeterMarker.get(), buf,
84                                                                  *(const_cast<MeterSection*>(ms))));
85                 } else if ((ts = dynamic_cast<const TempoSection*>(*i)) != 0) {
86                         snprintf (buf, sizeof (buf), "%.2f", ts->beats_per_minute());
87                         metric_marks.push_back (new TempoMarker (*this, *tempo_group, ARDOUR_UI::config()->canvasvar_TempoMarker.get(), buf,
88                                                                  *(const_cast<TempoSection*>(ts))));
89                 }
90
91         }
92
93 }
94
95 void
96 Editor::tempo_map_changed (Change ignored)
97 {
98         if (!_session) {
99                 return;
100         }
101
102         ENSURE_GUI_THREAD (*this, &Editor::tempo_map_changed, ignored)
103
104         if (tempo_lines)
105                 tempo_lines->tempo_map_changed();
106
107         compute_current_bbt_points(leftmost_frame, leftmost_frame + current_page_frames());
108         _session->tempo_map().apply_with_metrics (*this, &Editor::draw_metric_marks); // redraw metric markers
109         redraw_measures ();
110 }
111
112 void
113 Editor::redisplay_tempo (bool immediate_redraw)
114 {
115         if (!_session) {
116                 return;
117         }
118
119         compute_current_bbt_points (leftmost_frame, leftmost_frame + current_page_frames()); // redraw rulers and measures
120
121         compute_current_bbt_points (leftmost_frame, leftmost_frame + current_page_frames());
122         if (immediate_redraw) {
123                 redraw_measures ();
124         } else {
125 #ifdef GTKOSX
126                 redraw_measures ();
127 #else
128                 Glib::signal_idle().connect (sigc::mem_fun (*this, &Editor::redraw_measures));
129 #endif
130         }
131         update_tempo_based_rulers (); // redraw rulers and measures
132 }
133
134 void
135 Editor::compute_current_bbt_points (nframes_t leftmost, nframes_t rightmost)
136 {
137         if (!_session) {
138                 return;
139         }
140
141         BBT_Time previous_beat, next_beat; // the beats previous to the leftmost frame and after the rightmost frame
142
143         _session->bbt_time(leftmost, previous_beat);
144         _session->bbt_time(rightmost, next_beat);
145
146         if (previous_beat.beats > 1) {
147                 previous_beat.beats -= 1;
148         } else if (previous_beat.bars > 1) {
149                 previous_beat.bars--;
150                 previous_beat.beats += 1;
151         }
152         previous_beat.ticks = 0;
153
154         if (_session->tempo_map().meter_at(rightmost).beats_per_bar () > next_beat.beats + 1) {
155                 next_beat.beats += 1;
156         } else {
157                 next_beat.bars += 1;
158                 next_beat.beats = 1;
159         }
160         next_beat.ticks = 0;
161
162         delete current_bbt_points;
163         current_bbt_points = 0;
164
165         current_bbt_points = _session->tempo_map().get_points (_session->tempo_map().frame_time (previous_beat), _session->tempo_map().frame_time (next_beat) + 1);
166 }
167
168 void
169 Editor::hide_measures ()
170 {
171         if (tempo_lines)
172                 tempo_lines->hide();
173 }
174
175 bool
176 Editor::redraw_measures ()
177 {
178         draw_measures ();
179         return false;
180 }
181
182 void
183 Editor::draw_measures ()
184 {
185         if (_session == 0 || _show_measures == false ||
186             !current_bbt_points || current_bbt_points->empty()) {
187                 return;
188         }
189
190         if (tempo_lines == 0) {
191                 tempo_lines = new TempoLines(*track_canvas, time_line_group, physical_screen_height);
192         }
193
194         tempo_lines->draw(*current_bbt_points, frames_per_unit);
195 }
196
197 void
198 Editor::mouse_add_new_tempo_event (nframes64_t frame)
199 {
200         if (_session == 0) {
201                 return;
202         }
203
204         TempoMap& map(_session->tempo_map());
205         TempoDialog tempo_dialog (map, frame, _("add"));
206
207         tempo_dialog.set_position (Gtk::WIN_POS_MOUSE);
208         //this causes compiz to display no border.
209         //tempo_dialog.signal_realize().connect (sigc::bind (sigc::ptr_fun (set_decoration), &tempo_dialog, Gdk::WMDecoration (Gdk::DECOR_BORDER|Gdk::DECOR_RESIZEH)));
210
211         ensure_float (tempo_dialog);
212
213         switch (tempo_dialog.run()) {
214         case RESPONSE_ACCEPT:
215                 break;
216         default:
217                 return;
218         }
219
220         double bpm = 0;
221         BBT_Time requested;
222
223         bpm = tempo_dialog.get_bpm ();
224         double nt = tempo_dialog.get_note_type();
225         bpm = max (0.01, bpm);
226
227         tempo_dialog.get_bbt_time (requested);
228
229         begin_reversible_command (_("add tempo mark"));
230         XMLNode &before = map.get_state();
231         map.add_tempo (Tempo (bpm,nt), requested);
232         XMLNode &after = map.get_state();
233         _session->add_command(new MementoCommand<TempoMap>(map, &before, &after));
234         commit_reversible_command ();
235
236         //map.dump (cerr);
237 }
238
239 void
240 Editor::mouse_add_new_meter_event (nframes64_t frame)
241 {
242         if (_session == 0) {
243                 return;
244         }
245
246
247         TempoMap& map(_session->tempo_map());
248         MeterDialog meter_dialog (map, frame, _("add"));
249
250         meter_dialog.set_position (Gtk::WIN_POS_MOUSE);
251
252         //this causes compiz to display no border..
253         //meter_dialog.signal_realize().connect (sigc::bind (sigc::ptr_fun (set_decoration), &meter_dialog, Gdk::WMDecoration (Gdk::DECOR_BORDER|Gdk::DECOR_RESIZEH)));
254
255         ensure_float (meter_dialog);
256
257         switch (meter_dialog.run ()) {
258         case RESPONSE_ACCEPT:
259                 break;
260         default:
261                 return;
262         }
263
264         double bpb = meter_dialog.get_bpb ();
265         bpb = max (1.0, bpb); // XXX is this a reasonable limit?
266
267         double note_type = meter_dialog.get_note_type ();
268         BBT_Time requested;
269
270         meter_dialog.get_bbt_time (requested);
271
272         begin_reversible_command (_("add meter mark"));
273         XMLNode &before = map.get_state();
274         map.add_meter (Meter (bpb, note_type), requested);
275         _session->add_command(new MementoCommand<TempoMap>(map, &before, &map.get_state()));
276         commit_reversible_command ();
277
278         //map.dump (cerr);
279 }
280
281 void
282 Editor::remove_tempo_marker (ArdourCanvas::Item* item)
283 {
284         Marker* marker;
285         TempoMarker* tempo_marker;
286
287         if ((marker = reinterpret_cast<Marker *> (item->get_data ("marker"))) == 0) {
288                 fatal << _("programming error: tempo marker canvas item has no marker object pointer!") << endmsg;
289                 /*NOTREACHED*/
290         }
291
292         if ((tempo_marker = dynamic_cast<TempoMarker*> (marker)) == 0) {
293                 fatal << _("programming error: marker for tempo is not a tempo marker!") << endmsg;
294                 /*NOTREACHED*/
295         }
296
297         if (tempo_marker->tempo().movable()) {
298           Glib::signal_idle().connect (sigc::bind (sigc::mem_fun(*this, &Editor::real_remove_tempo_marker), &tempo_marker->tempo()));
299         }
300 }
301
302 void
303 Editor::edit_meter_section (MeterSection* section)
304 {
305         MeterDialog meter_dialog (*section, _("done"));
306
307         meter_dialog.set_position (Gtk::WIN_POS_MOUSE);
308
309         ensure_float (meter_dialog);
310
311         switch (meter_dialog.run()) {
312         case RESPONSE_ACCEPT:
313                 break;
314         default:
315                 return;
316         }
317
318         double bpb = meter_dialog.get_bpb ();
319         bpb = max (1.0, bpb); // XXX is this a reasonable limit?
320
321         double note_type = meter_dialog.get_note_type ();
322
323         begin_reversible_command (_("replace tempo mark"));
324         XMLNode &before = _session->tempo_map().get_state();
325         _session->tempo_map().replace_meter (*section, Meter (bpb, note_type));
326         XMLNode &after = _session->tempo_map().get_state();
327         _session->add_command(new MementoCommand<TempoMap>(_session->tempo_map(), &before, &after));
328         commit_reversible_command ();
329 }
330
331 void
332 Editor::edit_tempo_section (TempoSection* section)
333 {
334         TempoDialog tempo_dialog (*section, _("done"));
335
336         tempo_dialog.set_position (Gtk::WIN_POS_MOUSE);
337
338         ensure_float (tempo_dialog);
339
340         switch (tempo_dialog.run ()) {
341         case RESPONSE_ACCEPT:
342                 break;
343         default:
344                 return;
345         }
346
347         double bpm = tempo_dialog.get_bpm ();
348         double nt = tempo_dialog.get_note_type ();
349         BBT_Time when;
350         tempo_dialog.get_bbt_time(when);
351         bpm = max (0.01, bpm);
352
353         cerr << "Editing tempo section to be at " << when << endl;
354         _session->tempo_map().dump (cerr);
355         begin_reversible_command (_("replace tempo mark"));
356         XMLNode &before = _session->tempo_map().get_state();
357         _session->tempo_map().replace_tempo (*section, Tempo (bpm,nt));
358         _session->tempo_map().dump (cerr);
359         _session->tempo_map().move_tempo (*section, when);
360         _session->tempo_map().dump (cerr);
361         XMLNode &after = _session->tempo_map().get_state();
362         _session->add_command (new MementoCommand<TempoMap>(_session->tempo_map(), &before, &after));
363         commit_reversible_command ();
364 }
365
366 void
367 Editor::edit_tempo_marker (ArdourCanvas::Item *item)
368 {
369         Marker* marker;
370         TempoMarker* tempo_marker;
371
372         if ((marker = reinterpret_cast<Marker *> (item->get_data ("marker"))) == 0) {
373                 fatal << _("programming error: tempo marker canvas item has no marker object pointer!") << endmsg;
374                 /*NOTREACHED*/
375         }
376
377         if ((tempo_marker = dynamic_cast<TempoMarker*> (marker)) == 0) {
378                 fatal << _("programming error: marker for tempo is not a tempo marker!") << endmsg;
379                 /*NOTREACHED*/
380         }
381
382         edit_tempo_section (&tempo_marker->tempo());
383 }
384
385 void
386 Editor::edit_meter_marker (ArdourCanvas::Item *item)
387 {
388         Marker* marker;
389         MeterMarker* meter_marker;
390
391         if ((marker = reinterpret_cast<Marker *> (item->get_data ("marker"))) == 0) {
392                 fatal << _("programming error: tempo marker canvas item has no marker object pointer!") << endmsg;
393                 /*NOTREACHED*/
394         }
395
396         if ((meter_marker = dynamic_cast<MeterMarker*> (marker)) == 0) {
397                 fatal << _("programming error: marker for meter is not a meter marker!") << endmsg;
398                 /*NOTREACHED*/
399         }
400
401         edit_meter_section (&meter_marker->meter());
402 }
403
404 gint
405 Editor::real_remove_tempo_marker (TempoSection *section)
406 {
407         begin_reversible_command (_("remove tempo mark"));
408         XMLNode &before = _session->tempo_map().get_state();
409         _session->tempo_map().remove_tempo (*section);
410         XMLNode &after = _session->tempo_map().get_state();
411         _session->add_command(new MementoCommand<TempoMap>(_session->tempo_map(), &before, &after));
412         commit_reversible_command ();
413
414         return FALSE;
415 }
416
417 void
418 Editor::remove_meter_marker (ArdourCanvas::Item* item)
419 {
420         Marker* marker;
421         MeterMarker* meter_marker;
422
423         if ((marker = reinterpret_cast<Marker *> (item->get_data ("marker"))) == 0) {
424                 fatal << _("programming error: meter marker canvas item has no marker object pointer!") << endmsg;
425                 /*NOTREACHED*/
426         }
427
428         if ((meter_marker = dynamic_cast<MeterMarker*> (marker)) == 0) {
429                 fatal << _("programming error: marker for meter is not a meter marker!") << endmsg;
430                 /*NOTREACHED*/
431         }
432
433         if (meter_marker->meter().movable()) {
434           Glib::signal_idle().connect (sigc::bind (sigc::mem_fun(*this, &Editor::real_remove_meter_marker), &meter_marker->meter()));
435         }
436 }
437
438 gint
439 Editor::real_remove_meter_marker (MeterSection *section)
440 {
441         begin_reversible_command (_("remove tempo mark"));
442         XMLNode &before = _session->tempo_map().get_state();
443         _session->tempo_map().remove_meter (*section);
444         XMLNode &after = _session->tempo_map().get_state();
445         _session->add_command(new MementoCommand<TempoMap>(_session->tempo_map(), &before, &after));
446         commit_reversible_command ();
447
448         return FALSE;
449 }