Gnome::Canvas -> ArdourCanvas and some other small fixes
[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     $Id$
19 */
20
21 #include <cstdio> // for sprintf, grrr 
22 #include <cstdlib>
23 #include <cmath>
24 #include <string>
25 #include <climits>
26
27 #include <libgnomecanvasmm/libgnomecanvasmm.h>
28
29 #include <pbd/error.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
38 #include "editor.h"
39 #include "marker.h"
40 #include "canvas-simpleline.h"
41 #include "tempo_dialog.h"
42 #include "rgb_macros.h"
43 #include "gui_thread.h"
44
45 #include "i18n.h"
46
47 using namespace std;
48 using namespace sigc;
49 using namespace ARDOUR;
50 using namespace Gtk;
51 using namespace Editing;
52
53 void
54 Editor::remove_metric_marks ()
55 {
56         /* don't delete these while handling events, just punt till the GUI is idle */
57
58         for (Marks::iterator x = metric_marks.begin(); x != metric_marks.end(); ++x) {
59                 delete_when_idle (*x);
60         }
61         metric_marks.clear ();
62 }       
63
64 void
65 Editor::draw_metric_marks (const Metrics& metrics)
66 {
67         for (Metrics::const_iterator i = metrics.begin(); i != metrics.end(); ++i) {
68                 const MeterSection *ms;
69                 const TempoSection *ts;
70                 char buf[64];
71                 
72                 if ((ms = dynamic_cast<const MeterSection*>(*i)) != 0) {
73                         snprintf (buf, sizeof(buf), "%g/%g", ms->beats_per_bar(), ms->note_divisor ());
74                         metric_marks.push_back (new MeterMarker (*this, *meter_group, color_map[cMeterMarker], buf, 
75                                                                  *(const_cast<MeterSection*>(ms)), PublicEditor::canvas_meter_marker_event));
76                 } else if ((ts = dynamic_cast<const TempoSection*>(*i)) != 0) {
77                         snprintf (buf, sizeof (buf), "%.2f", ts->beats_per_minute());
78                         metric_marks.push_back (new TempoMarker (*this, *tempo_group, color_map[cTempoMarker], buf, 
79                                                                  *(const_cast<TempoSection*>(ts)), PublicEditor::canvas_tempo_marker_event));
80                 }
81                 
82         }
83 }
84
85 void
86 Editor::tempo_map_changed (Change ignored)
87 {
88         ENSURE_GUI_THREAD(bind (mem_fun(*this, &Editor::tempo_map_changed), ignored));
89         
90         if (current_bbt_points) {
91                 delete current_bbt_points;
92                 current_bbt_points = 0;
93         }
94
95         if (session) {
96                 current_bbt_points = session->tempo_map().get_points (leftmost_frame, leftmost_frame + current_page_frames());
97         } else {
98                 current_bbt_points = 0;
99         }
100
101         redisplay_tempo ();
102 }
103
104 void
105 Editor::redisplay_tempo ()
106 {
107         update_tempo_based_rulers ();
108
109         remove_metric_marks (); 
110         hide_measures ();
111
112         if (session && current_bbt_points) {
113                 session->tempo_map().apply_with_metrics (*this, &Editor::draw_metric_marks);
114                 draw_measures ();
115         }
116         
117 }
118
119 void
120 Editor::hide_measures ()
121 {
122         for (TimeLineList::iterator i = used_measure_lines.begin(); i != used_measure_lines.end(); ++i) {
123                 (*i)->hide();
124                 free_measure_lines.push_back (*i);
125         }
126         used_measure_lines.clear ();
127 }
128
129 ArdourCanvas::Line *
130 Editor::get_time_line ()
131 {
132          ArdourCanvas::Line *line;
133
134         if (free_measure_lines.empty()) {
135                 line = new ArdourCanvas::Line (*time_line_group);
136                 // cerr << "measure line @ " << line << endl;
137                 used_measure_lines.push_back (line);
138         } else {
139                 line = free_measure_lines.front();
140                 free_measure_lines.erase (free_measure_lines.begin());
141                 used_measure_lines.push_back (line);
142         }
143
144         return line;
145 }
146
147 void
148 Editor::draw_measures ()
149 {
150         if (session == 0 || _show_measures == false) {
151                 return;
152         }
153
154         TempoMap::BBTPointList::iterator i;
155         TempoMap::BBTPointList *all_bbt_points;
156         ArdourCanvas::Line *line;
157         gdouble xpos, last_xpos;
158         uint32_t cnt;
159         uint32_t color;
160
161         if (current_bbt_points == 0 || current_bbt_points->empty()) {
162                 return;
163         }
164
165         all_bbt_points = session->tempo_map().get_points (leftmost_frame, leftmost_frame + current_page_frames());
166
167         cnt = 0;
168         last_xpos = 0;
169
170         /* get the first bar spacing */
171
172         gdouble last_beat = DBL_MAX;
173         gdouble beat_spacing = 0;
174
175         for (i = all_bbt_points->begin(); i != all_bbt_points->end() && beat_spacing == 0; ++i) {
176                 TempoMap::BBTPoint& p = (*i);
177
178                 switch (p.type) {
179                 case TempoMap::Bar:
180                         break;
181
182                 case TempoMap::Beat:
183                         xpos = p.frame / (gdouble) frames_per_unit;
184                         if (last_beat < xpos) {
185                                 beat_spacing = xpos - last_beat;
186                         }
187                         last_beat = xpos;
188                 }
189         }
190
191         for (i = all_bbt_points->begin(); i != all_bbt_points->end(); ++i) {
192
193                 TempoMap::BBTPoint& p = (*i);
194
195                 switch (p.type) {
196                 case TempoMap::Bar:
197                         break;
198
199                 case TempoMap::Beat:
200                         xpos = p.frame / (gdouble) frames_per_unit;
201
202                         if (p.beat == 1) {
203                                 color = color_map[cMeasureLineBeat];
204                         } else {
205                                 color = color_map[cMeasureLineBar];
206
207                                 /* only draw beat lines if the gaps between beats
208                                    are large.
209                                 */
210
211                                 if (beat_spacing < 25.0) {
212                                         break;
213                                 }
214                         }
215
216                         if (cnt == 0 || xpos - last_xpos > 4.0) {
217                                 line = get_time_line ();
218                                 line->set_property ("x1", xpos);
219                                 line->set_property ("x2", xpos);
220                                 line->set_property ("y2", (gdouble) canvas_height);
221                                 line->set_property ("color_rgba", color);
222                                 line->raise_to_top();
223                                 line->show();
224                                 last_xpos = xpos;       
225                                 ++cnt;
226                         } 
227                         break;
228                 }
229         }
230
231         delete all_bbt_points;
232
233         /* the cursors are always on top of everything */
234
235         cursor_group->raise_to_top();
236         time_line_group->lower_to_bottom();
237 }
238
239 void
240 Editor::mouse_add_new_tempo_event (jack_nframes_t frame)
241 {
242         if (session == 0) {
243                 return;
244         }
245
246
247         TempoMap& map(session->tempo_map());
248         TempoDialog tempo_dialog (map, frame, _("add"));
249         
250         tempo_dialog.bpm_entry.signal_activate().connect (bind (mem_fun (tempo_dialog, &ArdourDialog::stop), 0));
251         tempo_dialog.ok_button.signal_clicked().connect (bind (mem_fun (tempo_dialog, &ArdourDialog::stop), 0));
252         tempo_dialog.cancel_button.signal_clicked().connect (bind (mem_fun (tempo_dialog, &ArdourDialog::stop), -1));
253
254         tempo_dialog.set_position (Gtk::WIN_POS_MOUSE);
255         // GTK2FIX
256         // tempo_dialog.realize ();
257         // tempo_dialog.get_window()->set_decorations (Gdk::WMDecoration (Gdk::DECOR_BORDER|Gdk::DECOR_RESIZEH));
258
259         ensure_float (tempo_dialog);
260
261         tempo_dialog.run();
262
263         if (tempo_dialog.run_status() == 0) {
264                 
265                 double bpm = 0;
266                 BBT_Time requested;
267
268                 bpm = tempo_dialog.get_bpm ();
269                 bpm = max (0.01, bpm);
270
271                 tempo_dialog.get_bbt_time (requested);
272
273                 begin_reversible_command (_("add tempo mark"));
274                 session->add_undo (map.get_memento());
275                 map.add_tempo (Tempo (bpm), requested);
276                 session->add_redo_no_execute (map.get_memento());
277                 commit_reversible_command ();
278
279                 map.dump (cerr);
280         }
281 }
282
283 void
284 Editor::mouse_add_new_meter_event (jack_nframes_t frame)
285 {
286         if (session == 0) {
287                 return;
288         }
289
290
291         TempoMap& map(session->tempo_map());
292         MeterDialog meter_dialog (map, frame, _("add"));
293
294         meter_dialog.ok_button.signal_clicked().connect (bind (mem_fun (meter_dialog, &ArdourDialog::stop), 0));
295         meter_dialog.cancel_button.signal_clicked().connect (bind (mem_fun (meter_dialog, &ArdourDialog::stop), -1));
296
297         meter_dialog.set_position (Gtk::WIN_POS_MOUSE);
298         // GTK2FIX
299         // meter_dialog.realize ();
300         // meter_dialog.get_window()->set_decorations (Gdk::WMDecoration (Gdk::DECOR_BORDER|Gdk::DECOR_RESIZEH));
301
302         ensure_float (meter_dialog);
303         
304         meter_dialog.run ();
305         
306         if (meter_dialog.run_status() == 0) {
307                 
308                 double bpb = meter_dialog.get_bpb ();
309                 bpb = max (1.0, bpb); // XXX is this a reasonable limit?
310
311                 double note_type = meter_dialog.get_note_type ();
312                 BBT_Time requested;
313
314                 meter_dialog.get_bbt_time (requested);
315
316                 begin_reversible_command (_("add meter mark"));
317                 session->add_undo (map.get_memento());
318                 map.add_meter (Meter (bpb, note_type), requested);
319                 session->add_redo_no_execute (map.get_memento());
320                 commit_reversible_command ();
321
322                 map.dump (cerr);
323         }
324 }
325
326 void
327 Editor::remove_tempo_marker (ArdourCanvas::Item* item)
328 {
329         Marker* marker;
330         TempoMarker* tempo_marker;
331
332         if ((marker = reinterpret_cast<Marker *> (gtk_object_get_data (GTK_OBJECT(item), "marker"))) == 0) {
333                 fatal << _("programming error: tempo marker canvas item has no marker object pointer!") << endmsg;
334                 /*NOTREACHED*/
335         }
336
337         if ((tempo_marker = dynamic_cast<TempoMarker*> (marker)) == 0) {
338                 fatal << _("programming error: marker for tempo is not a tempo marker!") << endmsg;
339                 /*NOTREACHED*/
340         }               
341
342         if (tempo_marker->tempo().movable()) {
343           Glib::signal_idle().connect (bind (mem_fun(*this, &Editor::real_remove_tempo_marker), &tempo_marker->tempo()));
344         }
345 }
346
347 void
348 Editor::edit_meter_section (MeterSection* section)
349 {
350         MeterDialog meter_dialog (*section, _("done"));
351
352         meter_dialog.ok_button.signal_clicked().connect (bind (mem_fun (meter_dialog, &ArdourDialog::stop), 0));
353         meter_dialog.cancel_button.signal_clicked().connect (bind (mem_fun (meter_dialog, &ArdourDialog::stop), -1));
354
355         meter_dialog.set_position (Gtk::WIN_POS_MOUSE);
356         // GTK2FIX
357         // meter_dialog.realize ();
358         // meter_dialog.get_window()->set_decorations (Gdk::WMDecoration (Gdk::DECOR_BORDER|Gdk::DECOR_RESIZEH));
359
360         ensure_float (meter_dialog);
361
362         meter_dialog.run ();
363
364         if (meter_dialog.run_status() == 0) {
365
366                 double bpb = meter_dialog.get_bpb ();
367                 bpb = max (1.0, bpb); // XXX is this a reasonable limit?
368
369                 double note_type = meter_dialog.get_note_type ();
370
371                 begin_reversible_command (_("replace tempo mark"));
372                 session->add_undo (session->tempo_map().get_memento());
373                 session->tempo_map().replace_meter (*section, Meter (bpb, note_type));
374                 session->add_redo_no_execute (session->tempo_map().get_memento());
375                 commit_reversible_command ();
376         }
377 }
378
379 void
380 Editor::edit_tempo_section (TempoSection* section)
381 {
382         TempoDialog tempo_dialog (*section, _("done"));
383
384         tempo_dialog.bpm_entry.signal_activate().connect (bind (mem_fun (tempo_dialog, &ArdourDialog::stop), 0));
385         tempo_dialog.ok_button.signal_clicked().connect (bind (mem_fun (tempo_dialog, &ArdourDialog::stop), 0));
386         tempo_dialog.cancel_button.signal_clicked().connect (bind (mem_fun (tempo_dialog, &ArdourDialog::stop), -1));
387
388         tempo_dialog.set_position (Gtk::WIN_POS_MOUSE);
389         // GTK2FIX
390         // tempo_dialog.realize ();
391         // tempo_dialog.get_window()->set_decorations (Gdk::WMDecoration (Gdk::DECOR_BORDER|Gdk::DECOR_RESIZEH));
392
393         ensure_float (tempo_dialog);
394         
395         tempo_dialog.run ();
396
397         if (tempo_dialog.run_status() == 0) {
398
399                 double bpm = tempo_dialog.get_bpm ();
400                 BBT_Time when;
401                 tempo_dialog.get_bbt_time(when);
402                 bpm = max (0.01, bpm);
403
404                 begin_reversible_command (_("replace tempo mark"));
405                 session->add_undo (session->tempo_map().get_memento());
406                 session->tempo_map().replace_tempo (*section, Tempo (bpm));
407                 session->tempo_map().move_tempo (*section, when);
408                 session->add_redo_no_execute (session->tempo_map().get_memento());
409                 commit_reversible_command ();
410         }
411 }
412
413 void
414 Editor::edit_tempo_marker (ArdourCanvas::Item *item)
415 {
416         Marker* marker;
417         TempoMarker* tempo_marker;
418
419         if ((marker = reinterpret_cast<Marker *> (gtk_object_get_data (GTK_OBJECT(item), "marker"))) == 0) {
420                 fatal << _("programming error: tempo marker canvas item has no marker object pointer!") << endmsg;
421                 /*NOTREACHED*/
422         }
423
424         if ((tempo_marker = dynamic_cast<TempoMarker*> (marker)) == 0) {
425                 fatal << _("programming error: marker for tempo is not a tempo marker!") << endmsg;
426                 /*NOTREACHED*/
427         }               
428
429         edit_tempo_section (&tempo_marker->tempo());
430 }
431
432 void
433 Editor::edit_meter_marker (ArdourCanvas::Item *item)
434 {
435         Marker* marker;
436         MeterMarker* meter_marker;
437
438         if ((marker = reinterpret_cast<Marker *> (gtk_object_get_data (GTK_OBJECT(item), "marker"))) == 0) {
439                 fatal << _("programming error: tempo marker canvas item has no marker object pointer!") << endmsg;
440                 /*NOTREACHED*/
441         }
442
443         if ((meter_marker = dynamic_cast<MeterMarker*> (marker)) == 0) {
444                 fatal << _("programming error: marker for meter is not a meter marker!") << endmsg;
445                 /*NOTREACHED*/
446         }               
447         
448         edit_meter_section (&meter_marker->meter());
449 }
450
451 gint
452 Editor::real_remove_tempo_marker (TempoSection *section)
453 {
454         begin_reversible_command (_("remove tempo mark"));
455         session->add_undo (session->tempo_map().get_memento());
456         session->tempo_map().remove_tempo (*section);
457         session->add_redo_no_execute (session->tempo_map().get_memento());
458         commit_reversible_command ();
459
460         return FALSE;
461 }
462
463 void
464 Editor::remove_meter_marker (ArdourCanvas::Item* item)
465 {
466         Marker* marker;
467         MeterMarker* meter_marker;
468
469         if ((marker = reinterpret_cast<Marker *> (gtk_object_get_data (GTK_OBJECT(item), "marker"))) == 0) {
470                 fatal << _("programming error: meter marker canvas item has no marker object pointer!") << endmsg;
471                 /*NOTREACHED*/
472         }
473
474         if ((meter_marker = dynamic_cast<MeterMarker*> (marker)) == 0) {
475                 fatal << _("programming error: marker for meter is not a meter marker!") << endmsg;
476                 /*NOTREACHED*/
477         }               
478
479         if (meter_marker->meter().movable()) {
480           Glib::signal_idle().connect (bind (mem_fun(*this, &Editor::real_remove_meter_marker), &meter_marker->meter()));
481         }
482 }
483
484 gint
485 Editor::real_remove_meter_marker (MeterSection *section)
486 {
487         begin_reversible_command (_("remove tempo mark"));
488         session->add_undo (session->tempo_map().get_memento());
489         session->tempo_map().remove_meter (*section);
490         session->add_redo_no_execute (session->tempo_map().get_memento());
491         commit_reversible_command ();
492         return FALSE;
493 }