Merge with trunk R2978.
[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         cerr << "returning " << note_type << " based on " << text << endl;
247         return note_type;
248 }
249
250 void
251 TempoDialog::note_types_change ()
252 {
253         set_response_sensitive (RESPONSE_ACCEPT, true);
254 }
255
256
257 MeterDialog::MeterDialog (TempoMap& map, nframes_t frame, const string & action)
258         : ArdourDialog ("meter dialog"),
259           bpb_frame (_("Meter")),
260           ok_button (action),
261           cancel_button (_("Cancel")),
262           when_bar_label (_("Bar"), ALIGN_LEFT, ALIGN_CENTER),
263           when_beat_label (_("Beat"), ALIGN_LEFT, ALIGN_CENTER),
264           when_frame (_("Location"))
265 {
266         BBT_Time when;
267         frame = map.round_to_bar(frame,0); 
268         Meter meter (map.meter_at(frame));
269
270         map.bbt_time (frame, when);
271         init (when, meter.beats_per_bar(), meter.note_divisor(), true);
272 }
273
274 MeterDialog::MeterDialog (MeterSection& section, const string & action)
275         : ArdourDialog ("meter dialog"),
276           bpb_frame (_("Meter")),
277           ok_button (action),
278           cancel_button (_("Cancel")),
279           when_bar_label (_("Bar"), ALIGN_LEFT, ALIGN_CENTER),
280           when_beat_label (_("Beat"), ALIGN_LEFT, ALIGN_CENTER),
281           when_frame (_("Location"))
282 {
283         init (section.start(), section.beats_per_bar(), section.note_divisor(), section.movable());
284 }
285
286 void
287 MeterDialog::init (const BBT_Time& when, double bpb, double note_type, bool movable)
288 {
289         snprintf (buf, sizeof (buf), "%.2f", bpb);
290         bpb_entry.set_text (buf);
291         bpb_entry.select_region (0, -1);
292         Gtkmm2ext::set_size_request_to_display_given_text (bpb_entry, "999999g", 5, 5);
293
294         strings.push_back (_("whole (1)"));
295         strings.push_back (_("second (2)"));
296         strings.push_back (_("third (3)"));
297         strings.push_back (_("quarter (4)"));
298         strings.push_back (_("eighth (8)"));
299         strings.push_back (_("sixteenth (16)"));
300         strings.push_back (_("thirty-second (32)"));
301         
302         /* the string here needs to be the longest one to display */
303         const guint32 FUDGE = 20; // Combo's are stupid - they steal space from the entry for the button
304
305         // TRANSLATORS: this is not a mis-spelling of "thirty", we're including a vertical 
306         // descender to make sure the height gets computed properly.
307         Gtkmm2ext::set_size_request_to_display_given_text (note_types, _("thirtq-second (32)"), 7+FUDGE, 15);
308
309         set_popdown_strings (note_types, strings);
310
311         if (note_type==1.0f)
312                 note_types.set_active_text (_("whole (1)"));
313         else if (note_type==2.0f)
314                 note_types.set_active_text (_("second (2)"));
315         else if (note_type==3.0f)
316                 note_types.set_active_text (_("third (3)"));
317         else if (note_type==4.0f)
318                 note_types.set_active_text (_("quarter (4)"));
319         else if (note_type==8.0f)
320                 note_types.set_active_text (_("eighth (8)"));
321         else if (note_type==16.0f)
322                 note_types.set_active_text (_("sixteenth (16)"));
323         else if (note_type==32.0f)
324                 note_types.set_active_text (_("thirty-second (32)"));
325         else
326                 note_types.set_active_text (_("quarter (4)"));
327
328         Label* note_label = manage(new Label(_("Note Value:"), ALIGN_LEFT, ALIGN_CENTER));
329         Label* bpb_label = manage(new Label(_("Beats Per Bar:"), ALIGN_LEFT, ALIGN_CENTER));
330         Table* bpb_table = manage (new Table(2, 2));
331
332         bpb_table->attach (*bpb_label, 0, 1, 0, 1, FILL|EXPAND, FILL|EXPAND, 6, 6);
333         bpb_table->attach (bpb_entry, 1, 2, 0, 1, FILL|EXPAND, FILL|EXPAND, 6, 6);
334         bpb_table->attach (*note_label, 0, 1, 1, 2, FILL|EXPAND, FILL|EXPAND, 6, 6);
335         bpb_table->attach (note_types, 1, 2, 1, 2, FILL|EXPAND, SHRINK, 6, 6);
336         bpb_frame.add (*bpb_table);
337
338         if (movable) {
339                 snprintf (buf, sizeof (buf), "%" PRIu32, when.bars);
340                 when_bar_entry.set_text (buf);
341                 snprintf (buf, sizeof (buf), "%" PRIu32, when.beats);
342                 when_beat_entry.set_text (buf);
343                 
344                 when_bar_entry.set_name ("MetricEntry");
345                 when_beat_entry.set_name ("MetricEntry");
346                 
347                 when_bar_label.set_name ("MetricLabel");
348                 when_beat_label.set_name ("MetricLabel");
349                 
350                 Gtkmm2ext::set_size_request_to_display_given_text (when_bar_entry, "999g", 5, 7);
351                 Gtkmm2ext::set_size_request_to_display_given_text (when_beat_entry, "999g", 5, 7);
352                 
353                 when_table.set_homogeneous (true);
354                 when_table.set_row_spacings (2);
355                 when_table.set_col_spacings (2);
356                 when_table.set_border_width (6);
357                 
358                 when_table.attach (when_bar_label, 0, 1, 0, 1, AttachOptions(0), FILL|EXPAND);
359                 when_table.attach (when_bar_entry, 1, 2, 0, 1, AttachOptions(0), FILL|EXPAND);
360                 
361                 when_table.attach (when_beat_label, 0, 1, 1, 2, AttachOptions(0), AttachOptions(0));
362                 when_table.attach (when_beat_entry, 1, 2, 1, 2, AttachOptions(0), AttachOptions(0));
363
364                 HBox* when_hbox = manage (new HBox());
365                 Label* when_label = manage(new Label(_("Meter Begins at:"), ALIGN_LEFT, ALIGN_TOP));
366                 when_hbox->pack_end(when_table, PACK_EXPAND_PADDING, 6);
367                 when_hbox->pack_start(*when_label, PACK_EXPAND_PADDING, 6);
368
369                 when_frame.set_name ("MetricDialogFrame");
370                 when_frame.add (*when_hbox);
371                 
372                 get_vbox()->pack_end (when_frame, false, false);
373         }
374
375         get_vbox()->set_border_width (12);
376         get_vbox()->pack_start (bpb_frame, false, false);
377
378         bpb_frame.set_name ("MetricDialogFrame");
379         bpb_entry.set_name ("MetricEntry");
380
381         add_button (Stock::CANCEL, RESPONSE_CANCEL);
382         add_button (Stock::APPLY, RESPONSE_ACCEPT);
383         set_response_sensitive (RESPONSE_ACCEPT, false);
384         set_default_response (RESPONSE_ACCEPT);
385
386         get_vbox()->show_all ();
387
388         set_name ("MetricDialog");
389         bpb_entry.signal_activate().connect (bind (mem_fun (*this, &MeterDialog::response), RESPONSE_ACCEPT));
390         bpb_entry.signal_key_press_event().connect (mem_fun (*this, &MeterDialog::bpb_key_press), false);
391         bpb_entry.signal_key_release_event().connect (mem_fun (*this, &MeterDialog::bpb_key_release));
392         note_types.signal_changed().connect (mem_fun (*this, &MeterDialog::note_types_change));
393 }
394
395 bool
396 MeterDialog::bpb_key_press (GdkEventKey* ev)
397 {
398
399         switch (ev->keyval) { 
400
401         case GDK_0:
402         case GDK_1:
403         case GDK_2:
404         case GDK_3:
405         case GDK_4:
406         case GDK_5:
407         case GDK_6:
408         case GDK_7:
409         case GDK_8:
410         case GDK_9:
411         case GDK_KP_0:
412         case GDK_KP_1:
413         case GDK_KP_2:
414         case GDK_KP_3:
415         case GDK_KP_4:
416         case GDK_KP_5:
417         case GDK_KP_6:
418         case GDK_KP_7:
419         case GDK_KP_8:
420         case GDK_KP_9:
421         case GDK_period:
422         case GDK_comma:
423         case  GDK_KP_Delete:
424         case  GDK_KP_Enter:
425         case  GDK_Delete:
426         case  GDK_BackSpace:
427         case  GDK_Escape:
428         case  GDK_Return:
429         case  GDK_Home:
430         case  GDK_End:
431         case  GDK_Left:
432         case  GDK_Right:
433         case  GDK_Num_Lock:
434         case  GDK_Tab:
435                 return FALSE;
436         default:
437                 break;
438         }
439
440         return TRUE;
441 }
442
443 bool
444 MeterDialog::bpb_key_release (GdkEventKey* ev)
445 {
446         if (bpb_entry.get_text() != "") {
447                 set_response_sensitive (RESPONSE_ACCEPT, true);
448         } else {
449                 set_response_sensitive (RESPONSE_ACCEPT, false);
450         }
451         return false;
452 }
453
454 void
455 MeterDialog::note_types_change ()
456 {
457         set_response_sensitive (RESPONSE_ACCEPT, true);
458 }
459
460 double
461 MeterDialog::get_bpb ()
462 {
463         double bpb = 0;
464         
465         if (sscanf (bpb_entry.get_text().c_str(), "%lf", &bpb) != 1) {
466                 return 0;
467         }
468
469         return bpb;
470 }
471         
472 double
473 MeterDialog::get_note_type ()
474 {
475         double note_type = 0;
476         vector<string>::iterator i;
477         string text = note_types.get_active_text();
478         
479         for (i = strings.begin(); i != strings.end(); ++i) {
480                 if (text == *i) {
481                         if (sscanf (text.c_str(), "%*[^0-9]%lf", &note_type) != 1) {
482                                 error << string_compose(_("garbaged note type entry (%1)"), text) << endmsg;
483                                 return 0;
484                         } else {
485                                 break;
486                         }
487                 }
488         } 
489         
490         if (i == strings.end()) {
491                 if (sscanf (text.c_str(), "%lf", &note_type) != 1) {
492                         error << string_compose(_("incomprehensible note type entry (%1)"), text) << endmsg;
493                         return 0;
494                 }
495         }
496
497         return note_type;
498 }
499
500 bool
501 MeterDialog::get_bbt_time (BBT_Time& requested)
502 {
503
504         if (sscanf (when_bar_entry.get_text().c_str(), "%" PRIu32, &requested.bars) != 1) {
505                 return false;
506         }
507         
508         if (sscanf (when_beat_entry.get_text().c_str(), "%" PRIu32, &requested.beats) != 1) {
509                 return false;
510         }
511
512         requested.ticks = 0;
513
514         return true;
515 }