Merge with 2.0-ongoing R2988
[ardour.git] / gtk2_ardour / tempo_dialog.cc
1 /*
2     Copyright (C) 2000-2007 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 snprintf, grrr 
21
22 #include <gtkmm/stock.h>
23 #include <gtkmm2ext/utils.h>
24
25 #include "tempo_dialog.h"
26 #include "utils.h"
27
28 #include "i18n.h"
29
30 using namespace Gtk;
31 using namespace Gtkmm2ext;
32 using namespace ARDOUR;
33 using namespace PBD;
34
35 TempoDialog::TempoDialog (TempoMap& map, nframes_t frame, const string & action)
36         : ArdourDialog (_("edit tempo")),
37           bpm_adjustment (60.0, 1.0, 999.9, 0.1, 1.0, 1.0),
38           bpm_spinner (bpm_adjustment),
39           bpm_frame (_("Tempo")),
40           ok_button (action),
41           cancel_button (_("Cancel")),
42           when_bar_label (_("Bar"), ALIGN_LEFT, ALIGN_CENTER),
43           when_beat_label (_("Beat"), ALIGN_LEFT, ALIGN_CENTER),
44           when_table (2, 2),
45           when_frame (_("Location"))
46 {
47         BBT_Time when;
48         Tempo tempo (map.tempo_at (frame));
49         map.bbt_time (frame, when);
50
51         init (when, tempo.beats_per_minute(), tempo.note_type(), true);
52 }
53
54 TempoDialog::TempoDialog (TempoSection& section, const string & action)
55         : ArdourDialog ("tempo dialog"),
56           bpm_adjustment (60.0, 1.0, 999.9, 0.1, 1.0, 1.0),
57           bpm_spinner (bpm_adjustment),
58           bpm_frame (_("Tempo")),
59           ok_button (action),
60           cancel_button (_("Cancel")),
61           when_bar_label (_("Bar"), ALIGN_LEFT, ALIGN_CENTER),
62           when_beat_label (_("Beat"), ALIGN_LEFT, ALIGN_CENTER),
63           when_table (2, 2),
64           when_frame (_("Location"))
65 {
66         init (section.start(), section.beats_per_minute(), section.note_type(), section.movable());
67 }
68
69 void
70 TempoDialog::init (const BBT_Time& when, double bpm, double note_type, bool movable)
71 {
72         bpm_spinner.set_numeric (true);
73         bpm_spinner.set_digits (2);
74         bpm_spinner.set_wrap (true);
75         bpm_spinner.set_value (bpm);
76
77         strings.push_back (_("whole (1)"));
78         strings.push_back (_("second (2)"));
79         strings.push_back (_("third (3)"));
80         strings.push_back (_("quarter (4)"));
81         strings.push_back (_("eighth (8)"));
82         strings.push_back (_("sixteenth (16)"));
83         strings.push_back (_("thirty-second (32)"));
84         
85         /* the string here needs to be the longest one to display */
86         const guint32 FUDGE = 20; // Combo's are stupid - they steal space from the entry for the button
87         // TRANSLATORS: this is not a mis-spelling of "thirty", we're including a vertical 
88         // descender to make sure the height gets computed properly.
89         Gtkmm2ext::set_size_request_to_display_given_text (note_types, "thirtq-second (32)", 7+FUDGE, 15);
90
91         set_popdown_strings (note_types, strings);
92
93         if (note_type==1.0f)
94                 note_types.set_active_text (_("whole (1)"));
95         else if (note_type==2.0f)
96                 note_types.set_active_text (_("second (2)"));
97         else if (note_type==3.0f)
98                 note_types.set_active_text (_("third (3)"));
99         else if (note_type==4.0f)
100                 note_types.set_active_text (_("quarter (4)"));
101         else if (note_type==8.0f)
102                 note_types.set_active_text (_("eighth (8)"));
103         else if (note_type==16.0f)
104                 note_types.set_active_text (_("sixteenth (16)"));
105         else if (note_type==32.0f)
106                 note_types.set_active_text (_("thirty-second (32)"));
107         else
108                 note_types.set_active_text (_("quarter (4)"));
109
110         Label* bpm_label = manage(new Label(_("Beats Per Minute:"), ALIGN_LEFT, ALIGN_CENTER));
111
112         hspacer1.set_border_width (6);
113         hspacer1.pack_end (bpm_spinner, PACK_EXPAND_PADDING);
114         hspacer1.pack_start (*bpm_label, PACK_EXPAND_PADDING);
115         vspacer1.set_border_width (6);
116         vspacer1.pack_start (hspacer1, PACK_EXPAND_PADDING);
117
118         hspacer2.set_border_width (6);
119         hspacer2.pack_start (note_types, PACK_EXPAND_PADDING);
120
121         bpm_frame.add (vspacer1);
122
123         if (movable) {
124                 snprintf (buf, sizeof (buf), "%" PRIu32, when.bars);
125                 when_bar_entry.set_text (buf);
126                 snprintf (buf, sizeof (buf), "%" PRIu32, when.beats);
127                 when_beat_entry.set_text (buf);
128                 
129                 when_bar_entry.set_name ("MetricEntry");
130                 when_beat_entry.set_name ("MetricEntry");
131                 
132                 when_bar_label.set_name ("MetricLabel");
133                 when_beat_label.set_name ("MetricLabel");
134                 
135                 Gtkmm2ext::set_size_request_to_display_given_text (when_bar_entry, "999g", 5, 7);
136                 Gtkmm2ext::set_size_request_to_display_given_text (when_beat_entry, "999g", 5, 7);
137                 
138                 when_table.set_homogeneous (true);
139                 when_table.set_row_spacings (2);
140                 when_table.set_col_spacings (2);
141                 when_table.set_border_width (6);
142                 
143                 when_table.attach (when_bar_label, 0, 1, 0, 1, AttachOptions(0), FILL|EXPAND);
144                 when_table.attach (when_bar_entry, 1, 2, 0, 1, AttachOptions(0), FILL|EXPAND);
145                 
146                 when_table.attach (when_beat_label, 0, 1, 1, 2, AttachOptions(0), AttachOptions(0));
147                 when_table.attach (when_beat_entry, 1, 2, 1, 2, AttachOptions(0), AttachOptions(0));
148                 
149                 HBox* when_hbox = manage (new HBox());
150                 Label* when_label = manage(new Label(_("Tempo Begins at:"), ALIGN_LEFT, ALIGN_TOP));
151                 when_hbox->pack_end(when_table, PACK_EXPAND_PADDING, 6);
152                 when_hbox->pack_start(*when_label, PACK_EXPAND_PADDING, 6);
153
154                 when_frame.set_name ("MetricDialogFrame");
155                 when_frame.add (*when_hbox);
156
157                 get_vbox()->pack_end (when_frame, false, false);
158                 when_frame.show_all();
159
160         }
161
162         bpm_frame.set_name ("MetricDialogFrame");
163         bpm_spinner.set_name ("MetricEntry");
164
165         get_vbox()->set_border_width (12);
166         get_vbox()->pack_end (bpm_frame, false, false);
167
168         add_button (Stock::CANCEL, RESPONSE_CANCEL);
169         add_button (Stock::APPLY, RESPONSE_ACCEPT);
170         set_response_sensitive (RESPONSE_ACCEPT, false);
171         set_default_response (RESPONSE_ACCEPT);
172
173         bpm_frame.show_all ();
174         bpm_spinner.show ();
175
176         set_name ("MetricDialog");
177
178         bpm_spinner.signal_activate().connect (bind (mem_fun (*this, &TempoDialog::response), RESPONSE_ACCEPT));
179         bpm_spinner.signal_button_press_event().connect (mem_fun (*this, &TempoDialog::bpm_button_press), false);
180         bpm_spinner.signal_button_release_event().connect (mem_fun (*this, &TempoDialog::bpm_button_release), false);
181         note_types.signal_changed().connect (mem_fun (*this, &TempoDialog::note_types_change));
182 }
183
184 bool
185 TempoDialog::bpm_button_press (GdkEventButton* ev)
186 {
187         return false;
188 }
189
190 bool
191 TempoDialog::bpm_button_release (GdkEventButton* ev)
192 {       
193         /* the value has been modified, accept should work now */
194
195         set_response_sensitive (RESPONSE_ACCEPT, true);
196         return false;
197 }
198
199 double 
200 TempoDialog::get_bpm ()
201 {
202         return bpm_spinner.get_value ();
203 }       
204
205 bool
206 TempoDialog::get_bbt_time (BBT_Time& requested)
207 {
208         if (sscanf (when_bar_entry.get_text().c_str(), "%" PRIu32, &requested.bars) != 1) {
209                 return false;
210         }
211         
212         if (sscanf (when_beat_entry.get_text().c_str(), "%" PRIu32, &requested.beats) != 1) {
213                 return false;
214         }
215
216         requested.ticks = 0;
217
218         return true;
219 }
220
221 double
222 TempoDialog::get_note_type ()
223 {
224         double note_type = 0;
225         vector<string>::iterator i;
226         string text = note_types.get_active_text();
227         
228         for (i = strings.begin(); i != strings.end(); ++i) {
229                 if (text == *i) {
230                         if (sscanf (text.c_str(), "%*[^0-9]%lf", &note_type) != 1) {
231                                 error << string_compose(_("garbaged note type entry (%1)"), text) << endmsg;
232                                 return 0;
233                         } else {
234                                 break;
235                         }
236                 }
237         } 
238         
239         if (i == strings.end()) {
240                 if (sscanf (text.c_str(), "%lf", &note_type) != 1) {
241                         error << string_compose(_("incomprehensible note type entry (%1)"), text) << endmsg;
242                         return 0;
243                 }
244         }
245
246         return note_type;
247 }
248
249 void
250 TempoDialog::note_types_change ()
251 {
252         set_response_sensitive (RESPONSE_ACCEPT, true);
253 }
254
255
256 MeterDialog::MeterDialog (TempoMap& map, nframes_t frame, const string & action)
257         : ArdourDialog ("meter dialog"),
258           bpb_frame (_("Meter")),
259           ok_button (action),
260           cancel_button (_("Cancel")),
261           when_bar_label (_("Bar"), ALIGN_LEFT, ALIGN_CENTER),
262           when_beat_label (_("Beat"), ALIGN_LEFT, ALIGN_CENTER),
263           when_frame (_("Location"))
264 {
265         BBT_Time when;
266         frame = map.round_to_bar(frame,0); 
267         Meter meter (map.meter_at(frame));
268
269         map.bbt_time (frame, when);
270         init (when, meter.beats_per_bar(), meter.note_divisor(), true);
271 }
272
273 MeterDialog::MeterDialog (MeterSection& section, const string & action)
274         : ArdourDialog ("meter dialog"),
275           bpb_frame (_("Meter")),
276           ok_button (action),
277           cancel_button (_("Cancel")),
278           when_bar_label (_("Bar"), ALIGN_LEFT, ALIGN_CENTER),
279           when_beat_label (_("Beat"), ALIGN_LEFT, ALIGN_CENTER),
280           when_frame (_("Location"))
281 {
282         init (section.start(), section.beats_per_bar(), section.note_divisor(), section.movable());
283 }
284
285 void
286 MeterDialog::init (const BBT_Time& when, double bpb, double note_type, bool movable)
287 {
288         snprintf (buf, sizeof (buf), "%.2f", bpb);
289         bpb_entry.set_text (buf);
290         bpb_entry.select_region (0, -1);
291         Gtkmm2ext::set_size_request_to_display_given_text (bpb_entry, "999999g", 5, 5);
292
293         strings.push_back (_("whole (1)"));
294         strings.push_back (_("second (2)"));
295         strings.push_back (_("third (3)"));
296         strings.push_back (_("quarter (4)"));
297         strings.push_back (_("eighth (8)"));
298         strings.push_back (_("sixteenth (16)"));
299         strings.push_back (_("thirty-second (32)"));
300         
301         /* the string here needs to be the longest one to display */
302         const guint32 FUDGE = 20; // Combo's are stupid - they steal space from the entry for the button
303
304         // TRANSLATORS: this is not a mis-spelling of "thirty", we're including a vertical 
305         // descender to make sure the height gets computed properly.
306         Gtkmm2ext::set_size_request_to_display_given_text (note_types, _("thirtq-second (32)"), 7+FUDGE, 15);
307
308         set_popdown_strings (note_types, strings);
309
310         if (note_type==1.0f)
311                 note_types.set_active_text (_("whole (1)"));
312         else if (note_type==2.0f)
313                 note_types.set_active_text (_("second (2)"));
314         else if (note_type==3.0f)
315                 note_types.set_active_text (_("third (3)"));
316         else if (note_type==4.0f)
317                 note_types.set_active_text (_("quarter (4)"));
318         else if (note_type==8.0f)
319                 note_types.set_active_text (_("eighth (8)"));
320         else if (note_type==16.0f)
321                 note_types.set_active_text (_("sixteenth (16)"));
322         else if (note_type==32.0f)
323                 note_types.set_active_text (_("thirty-second (32)"));
324         else
325                 note_types.set_active_text (_("quarter (4)"));
326
327         Label* note_label = manage(new Label(_("Note Value:"), ALIGN_LEFT, ALIGN_CENTER));
328         Label* bpb_label = manage(new Label(_("Beats Per Bar:"), ALIGN_LEFT, ALIGN_CENTER));
329         Table* bpb_table = manage (new Table(2, 2));
330
331         bpb_table->attach (*bpb_label, 0, 1, 0, 1, FILL|EXPAND, FILL|EXPAND, 6, 6);
332         bpb_table->attach (bpb_entry, 1, 2, 0, 1, FILL|EXPAND, FILL|EXPAND, 6, 6);
333         bpb_table->attach (*note_label, 0, 1, 1, 2, FILL|EXPAND, FILL|EXPAND, 6, 6);
334         bpb_table->attach (note_types, 1, 2, 1, 2, FILL|EXPAND, SHRINK, 6, 6);
335         bpb_frame.add (*bpb_table);
336
337         if (movable) {
338                 snprintf (buf, sizeof (buf), "%" PRIu32, when.bars);
339                 when_bar_entry.set_text (buf);
340                 snprintf (buf, sizeof (buf), "%" PRIu32, when.beats);
341                 when_beat_entry.set_text (buf);
342                 
343                 when_bar_entry.set_name ("MetricEntry");
344                 when_beat_entry.set_name ("MetricEntry");
345                 
346                 when_bar_label.set_name ("MetricLabel");
347                 when_beat_label.set_name ("MetricLabel");
348                 
349                 Gtkmm2ext::set_size_request_to_display_given_text (when_bar_entry, "999g", 5, 7);
350                 Gtkmm2ext::set_size_request_to_display_given_text (when_beat_entry, "999g", 5, 7);
351                 
352                 when_table.set_homogeneous (true);
353                 when_table.set_row_spacings (2);
354                 when_table.set_col_spacings (2);
355                 when_table.set_border_width (6);
356                 
357                 when_table.attach (when_bar_label, 0, 1, 0, 1, AttachOptions(0), FILL|EXPAND);
358                 when_table.attach (when_bar_entry, 1, 2, 0, 1, AttachOptions(0), FILL|EXPAND);
359                 
360                 when_table.attach (when_beat_label, 0, 1, 1, 2, AttachOptions(0), AttachOptions(0));
361                 when_table.attach (when_beat_entry, 1, 2, 1, 2, AttachOptions(0), AttachOptions(0));
362
363                 HBox* when_hbox = manage (new HBox());
364                 Label* when_label = manage(new Label(_("Meter Begins at:"), ALIGN_LEFT, ALIGN_TOP));
365                 when_hbox->pack_end(when_table, PACK_EXPAND_PADDING, 6);
366                 when_hbox->pack_start(*when_label, PACK_EXPAND_PADDING, 6);
367
368                 when_frame.set_name ("MetricDialogFrame");
369                 when_frame.add (*when_hbox);
370                 
371                 get_vbox()->pack_end (when_frame, false, false);
372         }
373
374         get_vbox()->set_border_width (12);
375         get_vbox()->pack_start (bpb_frame, false, false);
376
377         bpb_frame.set_name ("MetricDialogFrame");
378         bpb_entry.set_name ("MetricEntry");
379
380         add_button (Stock::CANCEL, RESPONSE_CANCEL);
381         add_button (Stock::APPLY, RESPONSE_ACCEPT);
382         set_response_sensitive (RESPONSE_ACCEPT, false);
383         set_default_response (RESPONSE_ACCEPT);
384
385         get_vbox()->show_all ();
386
387         set_name ("MetricDialog");
388         bpb_entry.signal_activate().connect (bind (mem_fun (*this, &MeterDialog::response), RESPONSE_ACCEPT));
389         bpb_entry.signal_key_press_event().connect (mem_fun (*this, &MeterDialog::bpb_key_press), false);
390         bpb_entry.signal_key_release_event().connect (mem_fun (*this, &MeterDialog::bpb_key_release));
391         note_types.signal_changed().connect (mem_fun (*this, &MeterDialog::note_types_change));
392 }
393
394 bool
395 MeterDialog::bpb_key_press (GdkEventKey* ev)
396 {
397
398         switch (ev->keyval) { 
399
400         case GDK_0:
401         case GDK_1:
402         case GDK_2:
403         case GDK_3:
404         case GDK_4:
405         case GDK_5:
406         case GDK_6:
407         case GDK_7:
408         case GDK_8:
409         case GDK_9:
410         case GDK_KP_0:
411         case GDK_KP_1:
412         case GDK_KP_2:
413         case GDK_KP_3:
414         case GDK_KP_4:
415         case GDK_KP_5:
416         case GDK_KP_6:
417         case GDK_KP_7:
418         case GDK_KP_8:
419         case GDK_KP_9:
420         case GDK_period:
421         case GDK_comma:
422         case  GDK_KP_Delete:
423         case  GDK_KP_Enter:
424         case  GDK_Delete:
425         case  GDK_BackSpace:
426         case  GDK_Escape:
427         case  GDK_Return:
428         case  GDK_Home:
429         case  GDK_End:
430         case  GDK_Left:
431         case  GDK_Right:
432         case  GDK_Num_Lock:
433         case  GDK_Tab:
434                 return FALSE;
435         default:
436                 break;
437         }
438
439         return TRUE;
440 }
441
442 bool
443 MeterDialog::bpb_key_release (GdkEventKey* ev)
444 {
445         if (bpb_entry.get_text() != "") {
446                 set_response_sensitive (RESPONSE_ACCEPT, true);
447         } else {
448                 set_response_sensitive (RESPONSE_ACCEPT, false);
449         }
450         return false;
451 }
452
453 void
454 MeterDialog::note_types_change ()
455 {
456         set_response_sensitive (RESPONSE_ACCEPT, true);
457 }
458
459 double
460 MeterDialog::get_bpb ()
461 {
462         double bpb = 0;
463         
464         if (sscanf (bpb_entry.get_text().c_str(), "%lf", &bpb) != 1) {
465                 return 0;
466         }
467
468         return bpb;
469 }
470         
471 double
472 MeterDialog::get_note_type ()
473 {
474         double note_type = 0;
475         vector<string>::iterator i;
476         string text = note_types.get_active_text();
477         
478         for (i = strings.begin(); i != strings.end(); ++i) {
479                 if (text == *i) {
480                         if (sscanf (text.c_str(), "%*[^0-9]%lf", &note_type) != 1) {
481                                 error << string_compose(_("garbaged note type entry (%1)"), text) << endmsg;
482                                 return 0;
483                         } else {
484                                 break;
485                         }
486                 }
487         } 
488         
489         if (i == strings.end()) {
490                 if (sscanf (text.c_str(), "%lf", &note_type) != 1) {
491                         error << string_compose(_("incomprehensible note type entry (%1)"), text) << endmsg;
492                         return 0;
493                 }
494         }
495
496         return note_type;
497 }
498
499 bool
500 MeterDialog::get_bbt_time (BBT_Time& requested)
501 {
502
503         if (sscanf (when_bar_entry.get_text().c_str(), "%" PRIu32, &requested.bars) != 1) {
504                 return false;
505         }
506         
507         if (sscanf (when_beat_entry.get_text().c_str(), "%" PRIu32, &requested.beats) != 1) {
508                 return false;
509         }
510
511         requested.ticks = 0;
512
513         return true;
514 }