1c3f0180957b8a98e0594219b576b4103796f26a
[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 (PropertyChange 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
108         compute_current_bbt_points(leftmost_frame, leftmost_frame + current_page_frames());
109         _session->tempo_map().apply_with_metrics (*this, &Editor::draw_metric_marks); // redraw metric markers
110         redraw_measures ();
111 }
112
113 void
114 Editor::redisplay_tempo (bool immediate_redraw)
115 {
116         if (!_session) {
117                 return;
118         }
119
120         compute_current_bbt_points (leftmost_frame, leftmost_frame + current_page_frames()); // redraw rulers and measures
121
122         compute_current_bbt_points (leftmost_frame, leftmost_frame + current_page_frames());
123         if (immediate_redraw) {
124                 redraw_measures ();
125         } else {
126 #ifdef GTKOSX
127                 redraw_measures ();
128 #else
129                 Glib::signal_idle().connect (sigc::mem_fun (*this, &Editor::redraw_measures));
130 #endif
131         }
132         update_tempo_based_rulers (); // redraw rulers and measures
133 }
134
135 void
136 Editor::compute_current_bbt_points (nframes_t leftmost, nframes_t rightmost)
137 {
138         if (!_session) {
139                 return;
140         }
141
142         BBT_Time previous_beat, next_beat; // the beats previous to the leftmost frame and after the rightmost frame
143
144         _session->bbt_time(leftmost, previous_beat);
145         _session->bbt_time(rightmost, next_beat);
146
147         if (previous_beat.beats > 1) {
148                 previous_beat.beats -= 1;
149         } else if (previous_beat.bars > 1) {
150                 previous_beat.bars--;
151                 previous_beat.beats += 1;
152         }
153         previous_beat.ticks = 0;
154
155         if (_session->tempo_map().meter_at(rightmost).beats_per_bar () > next_beat.beats + 1) {
156                 next_beat.beats += 1;
157         } else {
158                 next_beat.bars += 1;
159                 next_beat.beats = 1;
160         }
161         next_beat.ticks = 0;
162
163         delete current_bbt_points;
164         current_bbt_points = 0;
165
166         current_bbt_points = _session->tempo_map().get_points (_session->tempo_map().frame_time (previous_beat), _session->tempo_map().frame_time (next_beat) + 1);
167 }
168
169 void
170 Editor::hide_measures ()
171 {
172         if (tempo_lines)
173                 tempo_lines->hide();
174 }
175
176 bool
177 Editor::redraw_measures ()
178 {
179         draw_measures ();
180         return false;
181 }
182
183 void
184 Editor::draw_measures ()
185 {
186         if (_session == 0 || _show_measures == false ||
187             !current_bbt_points || current_bbt_points->empty()) {
188                 return;
189         }
190
191         if (tempo_lines == 0) {
192                 tempo_lines = new TempoLines(*track_canvas, time_line_group, physical_screen_height);
193         }
194
195         tempo_lines->draw(*current_bbt_points, frames_per_unit);
196 }
197
198 void
199 Editor::mouse_add_new_tempo_event (nframes64_t frame)
200 {
201         if (_session == 0) {
202                 return;
203         }
204
205         TempoMap& map(_session->tempo_map());
206         TempoDialog tempo_dialog (map, frame, _("add"));
207
208         tempo_dialog.set_position (Gtk::WIN_POS_MOUSE);
209         //this causes compiz to display no border.
210         //tempo_dialog.signal_realize().connect (sigc::bind (sigc::ptr_fun (set_decoration), &tempo_dialog, Gdk::WMDecoration (Gdk::DECOR_BORDER|Gdk::DECOR_RESIZEH)));
211
212         ensure_float (tempo_dialog);
213
214         switch (tempo_dialog.run()) {
215         case RESPONSE_ACCEPT:
216                 break;
217         default:
218                 return;
219         }
220
221         double bpm = 0;
222         BBT_Time requested;
223
224         bpm = tempo_dialog.get_bpm ();
225         double nt = tempo_dialog.get_note_type();
226         bpm = max (0.01, bpm);
227
228         tempo_dialog.get_bbt_time (requested);
229
230         begin_reversible_command (_("add tempo mark"));
231         XMLNode &before = map.get_state();
232         map.add_tempo (Tempo (bpm,nt), requested);
233         XMLNode &after = map.get_state();
234         _session->add_command(new MementoCommand<TempoMap>(map, &before, &after));
235         commit_reversible_command ();
236
237         //map.dump (cerr);
238 }
239
240 void
241 Editor::mouse_add_new_meter_event (nframes64_t frame)
242 {
243         if (_session == 0) {
244                 return;
245         }
246
247
248         TempoMap& map(_session->tempo_map());
249         MeterDialog meter_dialog (map, frame, _("add"));
250
251         meter_dialog.set_position (Gtk::WIN_POS_MOUSE);
252
253         //this causes compiz to display no border..
254         //meter_dialog.signal_realize().connect (sigc::bind (sigc::ptr_fun (set_decoration), &meter_dialog, Gdk::WMDecoration (Gdk::DECOR_BORDER|Gdk::DECOR_RESIZEH)));
255
256         ensure_float (meter_dialog);
257
258         switch (meter_dialog.run ()) {
259         case RESPONSE_ACCEPT:
260                 break;
261         default:
262                 return;
263         }
264
265         double bpb = meter_dialog.get_bpb ();
266         bpb = max (1.0, bpb); // XXX is this a reasonable limit?
267
268         double note_type = meter_dialog.get_note_type ();
269         BBT_Time requested;
270
271         meter_dialog.get_bbt_time (requested);
272
273         begin_reversible_command (_("add meter mark"));
274         XMLNode &before = map.get_state();
275         map.add_meter (Meter (bpb, note_type), requested);
276         _session->add_command(new MementoCommand<TempoMap>(map, &before, &map.get_state()));
277         commit_reversible_command ();
278
279         //map.dump (cerr);
280 }
281
282 void
283 Editor::remove_tempo_marker (ArdourCanvas::Item* item)
284 {
285         Marker* marker;
286         TempoMarker* tempo_marker;
287
288         if ((marker = reinterpret_cast<Marker *> (item->get_data ("marker"))) == 0) {
289                 fatal << _("programming error: tempo marker canvas item has no marker object pointer!") << endmsg;
290                 /*NOTREACHED*/
291         }
292
293         if ((tempo_marker = dynamic_cast<TempoMarker*> (marker)) == 0) {
294                 fatal << _("programming error: marker for tempo is not a tempo marker!") << endmsg;
295                 /*NOTREACHED*/
296         }
297
298         if (tempo_marker->tempo().movable()) {
299           Glib::signal_idle().connect (sigc::bind (sigc::mem_fun(*this, &Editor::real_remove_tempo_marker), &tempo_marker->tempo()));
300         }
301 }
302
303 void
304 Editor::edit_meter_section (MeterSection* section)
305 {
306         MeterDialog meter_dialog (*section, _("done"));
307
308         meter_dialog.set_position (Gtk::WIN_POS_MOUSE);
309
310         ensure_float (meter_dialog);
311
312         switch (meter_dialog.run()) {
313         case RESPONSE_ACCEPT:
314                 break;
315         default:
316                 return;
317         }
318
319         double bpb = meter_dialog.get_bpb ();
320         bpb = max (1.0, bpb); // XXX is this a reasonable limit?
321
322         double note_type = meter_dialog.get_note_type ();
323
324         begin_reversible_command (_("replace tempo mark"));
325         XMLNode &before = _session->tempo_map().get_state();
326         _session->tempo_map().replace_meter (*section, Meter (bpb, note_type));
327         XMLNode &after = _session->tempo_map().get_state();
328         _session->add_command(new MementoCommand<TempoMap>(_session->tempo_map(), &before, &after));
329         commit_reversible_command ();
330 }
331
332 void
333 Editor::edit_tempo_section (TempoSection* section)
334 {
335         TempoDialog tempo_dialog (*section, _("done"));
336
337         tempo_dialog.set_position (Gtk::WIN_POS_MOUSE);
338
339         ensure_float (tempo_dialog);
340
341         switch (tempo_dialog.run ()) {
342         case RESPONSE_ACCEPT:
343                 break;
344         default:
345                 return;
346         }
347
348         double bpm = tempo_dialog.get_bpm ();
349         double nt = tempo_dialog.get_note_type ();
350         BBT_Time when;
351         tempo_dialog.get_bbt_time(when);
352         bpm = max (0.01, bpm);
353
354         cerr << "Editing tempo section to be at " << when << endl;
355         _session->tempo_map().dump (cerr);
356         begin_reversible_command (_("replace tempo mark"));
357         XMLNode &before = _session->tempo_map().get_state();
358         _session->tempo_map().replace_tempo (*section, Tempo (bpm,nt));
359         _session->tempo_map().dump (cerr);
360         _session->tempo_map().move_tempo (*section, when);
361         _session->tempo_map().dump (cerr);
362         XMLNode &after = _session->tempo_map().get_state();
363         _session->add_command (new MementoCommand<TempoMap>(_session->tempo_map(), &before, &after));
364         commit_reversible_command ();
365 }
366
367 void
368 Editor::edit_tempo_marker (ArdourCanvas::Item *item)
369 {
370         Marker* marker;
371         TempoMarker* tempo_marker;
372
373         if ((marker = reinterpret_cast<Marker *> (item->get_data ("marker"))) == 0) {
374                 fatal << _("programming error: tempo marker canvas item has no marker object pointer!") << endmsg;
375                 /*NOTREACHED*/
376         }
377
378         if ((tempo_marker = dynamic_cast<TempoMarker*> (marker)) == 0) {
379                 fatal << _("programming error: marker for tempo is not a tempo marker!") << endmsg;
380                 /*NOTREACHED*/
381         }
382
383         edit_tempo_section (&tempo_marker->tempo());
384 }
385
386 void
387 Editor::edit_meter_marker (ArdourCanvas::Item *item)
388 {
389         Marker* marker;
390         MeterMarker* meter_marker;
391
392         if ((marker = reinterpret_cast<Marker *> (item->get_data ("marker"))) == 0) {
393                 fatal << _("programming error: tempo marker canvas item has no marker object pointer!") << endmsg;
394                 /*NOTREACHED*/
395         }
396
397         if ((meter_marker = dynamic_cast<MeterMarker*> (marker)) == 0) {
398                 fatal << _("programming error: marker for meter is not a meter marker!") << endmsg;
399                 /*NOTREACHED*/
400         }
401
402         edit_meter_section (&meter_marker->meter());
403 }
404
405 gint
406 Editor::real_remove_tempo_marker (TempoSection *section)
407 {
408         begin_reversible_command (_("remove tempo mark"));
409         XMLNode &before = _session->tempo_map().get_state();
410         _session->tempo_map().remove_tempo (*section);
411         XMLNode &after = _session->tempo_map().get_state();
412         _session->add_command(new MementoCommand<TempoMap>(_session->tempo_map(), &before, &after));
413         commit_reversible_command ();
414
415         return FALSE;
416 }
417
418 void
419 Editor::remove_meter_marker (ArdourCanvas::Item* item)
420 {
421         Marker* marker;
422         MeterMarker* meter_marker;
423
424         if ((marker = reinterpret_cast<Marker *> (item->get_data ("marker"))) == 0) {
425                 fatal << _("programming error: meter marker canvas item has no marker object pointer!") << endmsg;
426                 /*NOTREACHED*/
427         }
428
429         if ((meter_marker = dynamic_cast<MeterMarker*> (marker)) == 0) {
430                 fatal << _("programming error: marker for meter is not a meter marker!") << endmsg;
431                 /*NOTREACHED*/
432         }
433
434         if (meter_marker->meter().movable()) {
435           Glib::signal_idle().connect (sigc::bind (sigc::mem_fun(*this, &Editor::real_remove_meter_marker), &meter_marker->meter()));
436         }
437 }
438
439 gint
440 Editor::real_remove_meter_marker (MeterSection *section)
441 {
442         begin_reversible_command (_("remove tempo mark"));
443         XMLNode &before = _session->tempo_map().get_state();
444         _session->tempo_map().remove_meter (*section);
445         XMLNode &after = _session->tempo_map().get_state();
446         _session->add_command(new MementoCommand<TempoMap>(_session->tempo_map(), &before, &after));
447         commit_reversible_command ();
448
449         return FALSE;
450 }