Use XMLNode::get/set_property API in LocationUI class
[ardour.git] / gtk2_ardour / location_ui.cc
1 /*
2     Copyright (C) 2000 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 <cmath>
21 #include <cstdlib>
22
23 #include <gtkmm2ext/utils.h>
24
25 #include "ardour/session.h"
26 #include "pbd/memento_command.h"
27
28 #include "ardour_ui.h"
29 #include "clock_group.h"
30 #include "enums_convert.h"
31 #include "main_clock.h"
32 #include "gui_thread.h"
33 #include "keyboard.h"
34 #include "location_ui.h"
35 #include "prompter.h"
36 #include "utils.h"
37 #include "public_editor.h"
38 #include "tooltips.h"
39 #include "ui_config.h"
40
41 #include "pbd/i18n.h"
42
43 using namespace std;
44 using namespace ARDOUR;
45 using namespace ARDOUR_UI_UTILS;
46 using namespace PBD;
47 using namespace Gtk;
48 using namespace Gtkmm2ext;
49
50 LocationEditRow::LocationEditRow(Session * sess, Location * loc, int32_t num)
51         : SessionHandlePtr (0) /* explicitly set below */
52         , location(0)
53         , item_table (1, 6, false)
54         , start_clock (X_("locationstart"), true, "", true, false)
55         , start_to_playhead_button (_("Use PH"))
56         , locate_to_start_button (_("Goto"))
57         , end_clock (X_("locationend"), true, "", true, false)
58         , end_to_playhead_button (_("Use PH"))
59         , locate_to_end_button (_("Goto"))
60         , length_clock (X_("locationlength"), true, "", true, false, true)
61         , cd_check_button (_("CD"))
62         , hide_check_button (_("Hide"))
63         , lock_check_button (_("Lock"))
64         , glue_check_button (_("Glue"))
65         , _clock_group (0)
66 {
67         i_am_the_modifier = 0;
68
69         remove_button.set_icon (ArdourIcon::CloseCross);
70         remove_button.set_events (remove_button.get_events() & ~(Gdk::ENTER_NOTIFY_MASK|Gdk::LEAVE_NOTIFY_MASK));
71
72         number_label.set_name ("LocationEditNumberLabel");
73         name_label.set_name ("LocationEditNameLabel");
74         name_entry.set_name ("LocationEditNameEntry");
75         cd_check_button.set_name ("LocationEditCdButton");
76         hide_check_button.set_name ("LocationEditHideButton");
77         lock_check_button.set_name ("LocationEditLockButton");
78         glue_check_button.set_name ("LocationEditGlueButton");
79         isrc_label.set_name ("LocationEditNumberLabel");
80         isrc_entry.set_name ("LocationEditNameEntry");
81         scms_check_button.set_name ("LocationEditCdButton");
82         preemph_check_button.set_name ("LocationEditCdButton");
83         performer_label.set_name ("LocationEditNumberLabel");
84         performer_entry.set_name ("LocationEditNameEntry");
85         composer_label.set_name ("LocationEditNumberLabel");
86         composer_entry.set_name ("LocationEditNameEntry");
87
88         isrc_label.set_text (X_("ISRC:"));
89         performer_label.set_text (_("Performer:"));
90         composer_label.set_text (_("Composer:"));
91         scms_label.set_text (X_("SCMS"));
92         preemph_label.set_text (_("Pre-Emphasis"));
93
94         isrc_entry.set_size_request (112, -1);
95         isrc_entry.set_max_length(12);
96         isrc_entry.set_editable (true);
97
98         performer_entry.set_size_request (100, -1);
99         performer_entry.set_editable (true);
100
101         composer_entry.set_size_request (100, -1);
102         composer_entry.set_editable (true);
103
104         name_label.set_alignment (0, 0.5);
105
106         Gtk::HBox* front_spacing = manage (new HBox);
107         front_spacing->set_size_request (20, -1);
108         Gtk::HBox* mid_spacing = manage (new HBox);
109         mid_spacing->set_size_request (20, -1);
110
111         cd_track_details_hbox.set_spacing (4);
112         cd_track_details_hbox.pack_start (*front_spacing, false, false);
113         cd_track_details_hbox.pack_start (isrc_label, false, false);
114         cd_track_details_hbox.pack_start (isrc_entry, false, false);
115         cd_track_details_hbox.pack_start (performer_label, false, false);
116         cd_track_details_hbox.pack_start (performer_entry, true, true);
117         cd_track_details_hbox.pack_start (composer_label, false, false);
118         cd_track_details_hbox.pack_start (composer_entry, true, true);
119         cd_track_details_hbox.pack_start (*mid_spacing, false, false);
120         cd_track_details_hbox.pack_start (scms_label, false, false);
121         cd_track_details_hbox.pack_start (scms_check_button, false, false);
122         cd_track_details_hbox.pack_start (preemph_label, false, false);
123         cd_track_details_hbox.pack_start (preemph_check_button, false, false);
124
125         isrc_entry.signal_changed().connect (sigc::mem_fun(*this, &LocationEditRow::isrc_entry_changed));
126         performer_entry.signal_changed().connect (sigc::mem_fun(*this, &LocationEditRow::performer_entry_changed));
127         composer_entry.signal_changed().connect (sigc::mem_fun(*this, &LocationEditRow::composer_entry_changed));
128         scms_check_button.signal_toggled().connect(sigc::mem_fun(*this, &LocationEditRow::scms_toggled));
129         preemph_check_button.signal_toggled().connect(sigc::mem_fun(*this, &LocationEditRow::preemph_toggled));
130
131         set_session (sess);
132
133         start_hbox.set_spacing (2);
134         start_hbox.pack_start (locate_to_start_button, false, false);
135         start_hbox.pack_start (start_clock, false, false);
136         start_hbox.pack_start (start_to_playhead_button, false, false);
137
138         /* this is always in this location, no matter what the location is */
139
140         item_table.attach (remove_button, 8, 9, 0, 1, SHRINK, SHRINK, 4, 1);
141         item_table.attach (start_hbox, 0, 1, 0, 1, FILL, Gtk::AttachOptions(0), 4, 0);
142
143         start_to_playhead_button.signal_clicked.connect (sigc::bind (sigc::mem_fun (*this, &LocationEditRow::to_playhead_button_pressed), LocStart));
144         locate_to_start_button.signal_clicked.connect (sigc::bind (sigc::mem_fun (*this, &LocationEditRow::locate_button_pressed), LocStart));
145         start_clock.ValueChanged.connect (sigc::bind (sigc::mem_fun (*this, &LocationEditRow::clock_changed), LocStart));
146         start_clock.signal_button_press_event().connect (sigc::bind (sigc::mem_fun (*this, &LocationEditRow::locate_to_clock), &start_clock), false);
147
148         end_hbox.set_spacing (2);
149         end_hbox.pack_start (locate_to_end_button, false, false);
150         end_hbox.pack_start (end_clock, false, false);
151         end_hbox.pack_start (end_to_playhead_button, false, false);
152
153         end_to_playhead_button.signal_clicked.connect (sigc::bind (sigc::mem_fun (*this, &LocationEditRow::to_playhead_button_pressed), LocEnd));
154         locate_to_end_button.signal_clicked.connect (sigc::bind (sigc::mem_fun (*this, &LocationEditRow::locate_button_pressed), LocEnd));
155         end_clock.ValueChanged.connect (sigc::bind (sigc::mem_fun (*this, &LocationEditRow::clock_changed), LocEnd));
156         end_clock.signal_button_press_event().connect (sigc::bind (sigc::mem_fun (*this, &LocationEditRow::locate_to_clock), &end_clock), false);
157
158         length_clock.ValueChanged.connect (sigc::bind ( sigc::mem_fun(*this, &LocationEditRow::clock_changed), LocLength));
159
160         cd_check_button.signal_toggled().connect(sigc::mem_fun(*this, &LocationEditRow::cd_toggled));
161         hide_check_button.signal_toggled().connect(sigc::mem_fun(*this, &LocationEditRow::hide_toggled));
162         lock_check_button.signal_toggled().connect(sigc::mem_fun(*this, &LocationEditRow::lock_toggled));
163         glue_check_button.signal_toggled().connect(sigc::mem_fun(*this, &LocationEditRow::glue_toggled));
164
165         remove_button.signal_clicked.connect(sigc::mem_fun(*this, &LocationEditRow::remove_button_pressed));
166
167         pack_start(item_table, true, true);
168
169         set_location (loc);
170         set_number (num);
171         cd_toggled(); // show/hide cd-track details
172 }
173
174 LocationEditRow::~LocationEditRow()
175 {
176         if (location) {
177                 connections.drop_connections ();
178         }
179
180         if (_clock_group) {
181                 _clock_group->remove (start_clock);
182                 _clock_group->remove (end_clock);
183                 _clock_group->remove (length_clock);
184         }
185 }
186
187 void
188 LocationEditRow::set_clock_group (ClockGroup& cg)
189 {
190         if (_clock_group) {
191                 _clock_group->remove (start_clock);
192                 _clock_group->remove (end_clock);
193                 _clock_group->remove (length_clock);
194         }
195
196         _clock_group = &cg;
197
198         _clock_group->add (start_clock);
199         _clock_group->add (end_clock);
200         _clock_group->add (length_clock);
201 }
202
203 void
204 LocationEditRow::set_session (Session *sess)
205 {
206         SessionHandlePtr::set_session (sess);
207
208         if (!_session) {
209                 return;
210         }
211
212         start_clock.set_session (_session);
213         end_clock.set_session (_session);
214         length_clock.set_session (_session);
215 }
216
217 void
218 LocationEditRow::set_number (int num)
219 {
220         number = num;
221
222         if (number >= 0 ) {
223                 number_label.set_text (string_compose ("%1", number));
224         }
225 }
226
227 void
228 LocationEditRow::set_location (Location *loc)
229 {
230         if (location) {
231                 connections.drop_connections ();
232         }
233
234         location = loc;
235
236         if (!location) {
237                 return;
238         }
239
240         ++i_am_the_modifier;
241
242         if (!hide_check_button.get_parent()) {
243                 item_table.attach (hide_check_button, 5, 6, 0, 1, FILL, Gtk::FILL, 4, 0);
244                 item_table.attach (lock_check_button, 6, 7, 0, 1, FILL, Gtk::FILL, 4, 0);
245                 item_table.attach (glue_check_button, 7, 8, 0, 1, FILL, Gtk::FILL, 4, 0);
246         }
247         hide_check_button.set_active (location->is_hidden());
248         lock_check_button.set_active (location->locked());
249         glue_check_button.set_active (location->position_lock_style() == MusicTime);
250
251         if (location->is_auto_loop() || location-> is_auto_punch()) {
252                 // use label instead of entry
253
254                 name_label.set_text (location->name());
255                 name_label.set_size_request (80, -1);
256
257                 remove_button.hide ();
258
259                 if (!name_label.get_parent()) {
260                         item_table.attach (name_label, 2, 3, 0, 1, EXPAND|FILL, FILL, 4, 0);
261                 }
262
263                 name_label.show();
264
265         } else {
266
267                 name_entry.set_text (location->name());
268                 name_entry.set_size_request (100, -1);
269                 name_entry.set_editable (true);
270                 name_entry.signal_changed().connect (sigc::mem_fun(*this, &LocationEditRow::name_entry_changed));
271
272                 if (!name_entry.get_parent()) {
273                         item_table.attach (name_entry, 2, 3, 0, 1, FILL | EXPAND, FILL, 4, 0);
274                 }
275                 name_entry.show();
276
277                 if (!cd_check_button.get_parent()) {
278                         item_table.attach (cd_check_button, 4, 5, 0, 1, FILL, Gtk::AttachOptions (0), 4, 0);
279                 }
280
281                 if (location->is_session_range()) {
282                         remove_button.set_sensitive (false);
283                 }
284
285                 cd_check_button.set_active (location->is_cd_marker());
286                 cd_check_button.show();
287
288                 if (location->start() == _session->current_start_frame()) {
289                         cd_check_button.set_sensitive (false);
290                 } else {
291                         cd_check_button.set_sensitive (true);
292                 }
293
294                 hide_check_button.show();
295                 lock_check_button.show();
296                 glue_check_button.show();
297         }
298
299         start_clock.set (location->start(), true);
300
301
302         if (!location->is_mark()) {
303                 if (!end_hbox.get_parent()) {
304                         item_table.attach (end_hbox, 1, 2, 0, 1, FILL, Gtk::AttachOptions (0), 4, 0);
305                 }
306                 if (!length_clock.get_parent()) {
307                         end_hbox.pack_start (length_clock, false, false, 4);
308                 }
309
310                 end_clock.set (location->end(), true);
311                 length_clock.set (location->length(), true);
312
313                 end_clock.show();
314                 length_clock.show();
315
316                 if (location->is_cd_marker()) {
317                         show_cd_track_details ();
318                 }
319
320                 set_tooltip (&remove_button, _("Remove this range"));
321                 set_tooltip (start_clock, _("Start time - middle click to locate here"));
322                 set_tooltip (end_clock, _("End time - middle click to locate here"));
323                 set_tooltip (length_clock, _("Length"));
324
325                 set_tooltip (&start_to_playhead_button, _("Set range start from playhead location"));
326                 set_tooltip (&end_to_playhead_button, _("Set range end from playhead location"));
327
328         } else {
329
330                 set_tooltip (&remove_button, _("Remove this marker"));
331                 set_tooltip (start_clock, _("Position - middle click to locate here"));
332
333                 set_tooltip (&start_to_playhead_button, _("Set marker time from playhead location"));
334
335                 end_clock.hide();
336                 length_clock.hide();
337         }
338
339         set_clock_editable_status ();
340
341         --i_am_the_modifier;
342
343         /* connect to per-location signals, since this row only cares about this location */
344
345         location->NameChanged.connect (connections, invalidator (*this), boost::bind (&LocationEditRow::name_changed, this), gui_context());
346         location->StartChanged.connect (connections, invalidator (*this), boost::bind (&LocationEditRow::start_changed, this), gui_context());
347         location->EndChanged.connect (connections, invalidator (*this), boost::bind (&LocationEditRow::end_changed, this), gui_context());
348         location->Changed.connect (connections, invalidator (*this), boost::bind (&LocationEditRow::location_changed, this), gui_context());
349         location->FlagsChanged.connect (connections, invalidator (*this), boost::bind (&LocationEditRow::flags_changed, this), gui_context());
350         location->LockChanged.connect (connections, invalidator (*this), boost::bind (&LocationEditRow::lock_changed, this), gui_context());
351         location->PositionLockStyleChanged.connect (connections, invalidator (*this), boost::bind (&LocationEditRow::position_lock_style_changed, this), gui_context());
352 }
353
354 void
355 LocationEditRow::name_entry_changed ()
356 {
357         ENSURE_GUI_THREAD (*this, &LocationEditRow::name_entry_changed);
358
359         if (i_am_the_modifier || !location) {
360                 return;
361         }
362
363         location->set_name (name_entry.get_text());
364 }
365
366
367 void
368 LocationEditRow::isrc_entry_changed ()
369 {
370         ENSURE_GUI_THREAD (*this, &LocationEditRow::isrc_entry_changed);
371
372         if (i_am_the_modifier || !location) return;
373
374         if (isrc_entry.get_text() != "" ) {
375
376                 location->cd_info["isrc"] = isrc_entry.get_text();
377
378         } else {
379                 location->cd_info.erase("isrc");
380         }
381 }
382
383 void
384 LocationEditRow::performer_entry_changed ()
385 {
386         ENSURE_GUI_THREAD (*this, &LocationEditRow::performer_entry_changed);
387
388         if (i_am_the_modifier || !location) return;
389
390         if (performer_entry.get_text() != "") {
391                 location->cd_info["performer"] = performer_entry.get_text();
392         } else {
393                 location->cd_info.erase("performer");
394         }
395 }
396
397 void
398 LocationEditRow::composer_entry_changed ()
399 {
400         ENSURE_GUI_THREAD (*this, &LocationEditRow::composer_entry_changed);
401
402         if (i_am_the_modifier || !location) return;
403
404         if (composer_entry.get_text() != "") {
405                 location->cd_info["composer"] = composer_entry.get_text();
406         } else {
407                 location->cd_info.erase("composer");
408         }
409 }
410
411 void
412 LocationEditRow::to_playhead_button_pressed (LocationPart part)
413 {
414         if (!location) {
415                 return;
416         }
417
418         const int32_t divisions = PublicEditor::instance().get_grid_music_divisions (0);
419
420         switch (part) {
421                 case LocStart:
422                         location->set_start (_session->transport_frame (), false, true, divisions);
423                         break;
424                 case LocEnd:
425                         location->set_end (_session->transport_frame (), false, true,divisions);
426                         if (location->is_session_range()) {
427                                 _session->set_end_is_free (false);
428                         }
429                         break;
430                 default:
431                         break;
432         }
433 }
434
435 void
436 LocationEditRow::locate_button_pressed (LocationPart part)
437 {
438         switch (part) {
439                 case LocStart:
440                         _session->request_locate (start_clock.current_time());
441                         break;
442                 case LocEnd:
443                         _session->request_locate (end_clock.current_time());
444                         break;
445                 default:
446                         break;
447         }
448 }
449
450 bool
451 LocationEditRow::locate_to_clock (GdkEventButton* ev, AudioClock* clock)
452 {
453         if (Keyboard::is_button2_event (ev)) {
454                 _session->request_locate (clock->current_time());
455                 return true;
456         }
457         return false;
458 }
459
460 void
461 LocationEditRow::clock_changed (LocationPart part)
462 {
463         if (i_am_the_modifier || !location) {
464                 return;
465         }
466
467         const int32_t divisions = PublicEditor::instance().get_grid_music_divisions (0);
468
469         switch (part) {
470                 case LocStart:
471                         location->set_start (start_clock.current_time(), false, true, divisions);
472                         break;
473                 case LocEnd:
474                         location->set_end (end_clock.current_time(), false, true, divisions);
475                         if (location->is_session_range()) {
476                                 _session->set_end_is_free (false);
477                         }
478                         break;
479                 case LocLength:
480                         location->set_end (location->start() + length_clock.current_duration(), false, true, divisions);
481                         if (location->is_session_range()) {
482                                 _session->set_end_is_free (false);
483                         }
484                 default:
485                         break;
486         }
487 }
488
489 void
490 LocationEditRow::show_cd_track_details ()
491 {
492         if (location->cd_info.find("isrc") != location->cd_info.end()) {
493                 isrc_entry.set_text(location->cd_info["isrc"]);
494         }
495         if (location->cd_info.find("performer") != location->cd_info.end()) {
496                 performer_entry.set_text(location->cd_info["performer"]);
497         }
498         if (location->cd_info.find("composer") != location->cd_info.end()) {
499                 composer_entry.set_text(location->cd_info["composer"]);
500         }
501         if (location->cd_info.find("scms") != location->cd_info.end()) {
502                 scms_check_button.set_active(true);
503         }
504         if (location->cd_info.find("preemph") != location->cd_info.end()) {
505                 preemph_check_button.set_active(true);
506         }
507
508
509         if (!cd_track_details_hbox.get_parent()) {
510                 item_table.attach (cd_track_details_hbox, 0, 7, 1, 2, FILL | EXPAND, FILL, 4, 0);
511         }
512         // item_table.resize(2, 7);
513         cd_track_details_hbox.show_all();
514 }
515
516 void
517 LocationEditRow::cd_toggled ()
518 {
519         if (i_am_the_modifier || !location) {
520                 return;
521         }
522
523         //if (cd_check_button.get_active() == location->is_cd_marker()) {
524         //      return;
525         //}
526
527         if (cd_check_button.get_active()) {
528                 if (location->start() <= _session->current_start_frame()) {
529                         error << _("You cannot put a CD marker at the start of the session") << endmsg;
530                         cd_check_button.set_active (false);
531                         return;
532                 }
533         }
534
535         location->set_cd (cd_check_button.get_active(), this);
536
537         if (location->is_cd_marker()) {
538
539                 show_cd_track_details ();
540
541         } else if (cd_track_details_hbox.get_parent()){
542
543                 item_table.remove (cd_track_details_hbox);
544                 //        item_table.resize(1, 7);
545                 redraw_ranges(); /*     EMIT_SIGNAL */
546         }
547 }
548
549 void
550 LocationEditRow::hide_toggled ()
551 {
552         if (i_am_the_modifier || !location) {
553                 return;
554         }
555
556         location->set_hidden (hide_check_button.get_active(), this);
557 }
558
559 void
560 LocationEditRow::lock_toggled ()
561 {
562         if (i_am_the_modifier || !location) {
563                 return;
564         }
565
566         if (location->locked()) {
567                 location->unlock ();
568         } else {
569                 location->lock ();
570         }
571 }
572
573 void
574 LocationEditRow::glue_toggled ()
575 {
576         if (i_am_the_modifier || !location) {
577                 return;
578         }
579
580         if (location->position_lock_style() == AudioTime) {
581                 location->set_position_lock_style (MusicTime);
582         } else {
583                 location->set_position_lock_style (AudioTime);
584         }
585 }
586
587 void
588 LocationEditRow::remove_button_pressed ()
589 {
590         if (!location) {
591                 return;
592         }
593
594         remove_requested (location); /* EMIT_SIGNAL */
595 }
596
597
598
599 void
600 LocationEditRow::scms_toggled ()
601 {
602         if (i_am_the_modifier || !location) return;
603
604         if (scms_check_button.get_active()) {
605           location->cd_info["scms"] = "on";
606         } else {
607           location->cd_info.erase("scms");
608         }
609
610 }
611
612 void
613 LocationEditRow::preemph_toggled ()
614 {
615         if (i_am_the_modifier || !location) return;
616
617         if (preemph_check_button.get_active()) {
618           location->cd_info["preemph"] = "on";
619         } else {
620           location->cd_info.erase("preemph");
621         }
622 }
623
624 void
625 LocationEditRow::end_changed ()
626 {
627         ENSURE_GUI_THREAD (*this, &LocationEditRow::end_changed, loc)
628
629         if (!location) return;
630
631         // update end and length
632         i_am_the_modifier++;
633
634         end_clock.set (location->end());
635         length_clock.set (location->length());
636
637         i_am_the_modifier--;
638 }
639
640 void
641 LocationEditRow::start_changed ()
642 {
643         if (!location) return;
644
645         // update end and length
646         i_am_the_modifier++;
647
648         start_clock.set (location->start());
649
650         if (location->start() == _session->current_start_frame()) {
651                 cd_check_button.set_sensitive (false);
652         } else {
653                 cd_check_button.set_sensitive (true);
654         }
655
656         i_am_the_modifier--;
657 }
658
659 void
660 LocationEditRow::name_changed ()
661 {
662         if (!location) return;
663
664         // update end and length
665         i_am_the_modifier++;
666
667         name_entry.set_text(location->name());
668         name_label.set_text(location->name());
669
670         i_am_the_modifier--;
671
672 }
673
674 void
675 LocationEditRow::location_changed ()
676 {
677
678         if (!location) return;
679
680         i_am_the_modifier++;
681
682         start_clock.set (location->start());
683         end_clock.set (location->end());
684         length_clock.set (location->length());
685
686         set_clock_editable_status ();
687
688         i_am_the_modifier--;
689
690 }
691
692 void
693 LocationEditRow::flags_changed ()
694 {
695         if (!location) {
696                 return;
697         }
698
699         i_am_the_modifier++;
700
701         cd_check_button.set_active (location->is_cd_marker());
702         hide_check_button.set_active (location->is_hidden());
703         glue_check_button.set_active (location->position_lock_style() == MusicTime);
704
705         i_am_the_modifier--;
706 }
707
708 void
709 LocationEditRow::lock_changed ()
710 {
711         if (!location) {
712                 return;
713         }
714
715         i_am_the_modifier++;
716
717         lock_check_button.set_active (location->locked());
718
719         set_clock_editable_status ();
720
721         i_am_the_modifier--;
722 }
723
724 void
725 LocationEditRow::position_lock_style_changed ()
726 {
727         if (!location) {
728                 return;
729         }
730
731         i_am_the_modifier++;
732
733         glue_check_button.set_active (location->position_lock_style() == MusicTime);
734
735         i_am_the_modifier--;
736 }
737
738 void
739 LocationEditRow::focus_name()
740 {
741         name_entry.grab_focus ();
742 }
743
744 void
745 LocationEditRow::set_clock_editable_status ()
746 {
747         start_clock.set_editable (!location->locked());
748         end_clock.set_editable (!location->locked());
749         length_clock.set_editable (!location->locked());
750 }
751
752 /*------------------------------------------------------------------------*/
753
754 LocationUI::LocationUI (std::string state_node_name)
755         : add_location_button (_("New Marker"))
756         , add_range_button (_("New Range"))
757         , _mode (AudioClock::Frames)
758         , _mode_set (false)
759         , _state_node_name (state_node_name)
760 {
761         i_am_the_modifier = 0;
762
763         _clock_group = new ClockGroup;
764
765         VBox* vbox = manage (new VBox);
766
767         Table* table = manage (new Table (2, 2));
768         table->set_spacings (2);
769         table->set_col_spacing (0, 32);
770         int table_row = 0;
771
772         Label* l = manage (new Label (_("<b>Loop/Punch Ranges</b>")));
773         l->set_alignment (0, 0.5);
774         l->set_use_markup (true);
775         table->attach (*l, 0, 2, table_row, table_row + 1);
776         ++table_row;
777
778         loop_edit_row.set_clock_group (*_clock_group);
779         punch_edit_row.set_clock_group (*_clock_group);
780
781         loop_punch_box.set_border_width (6); // 5 + 1 px framebox-border
782         loop_punch_box.pack_start (loop_edit_row, false, false);
783         loop_punch_box.pack_start (punch_edit_row, false, false);
784
785         table->attach (loop_punch_box, 1, 2, table_row, table_row + 1);
786         ++table_row;
787
788         vbox->pack_start (*table, false, false);
789
790         table = manage (new Table (3, 2));
791         table->set_spacings (2);
792         table->set_col_spacing (0, 32);
793         table_row = 0;
794
795         table->attach (*manage (new Label ("")), 0, 2, table_row, table_row + 1, Gtk::SHRINK, Gtk::SHRINK);
796         ++table_row;
797
798         l = manage (new Label (_("<b>Markers (Including CD Index)</b>")));
799         l->set_alignment (0, 0.5);
800         l->set_use_markup (true);
801         table->attach (*l, 0, 2, table_row, table_row + 1, Gtk::FILL | Gtk::EXPAND, Gtk::SHRINK);
802         ++table_row;
803
804         location_rows.set_name("LocationLocRows");
805         location_rows_scroller.add (location_rows);
806         location_rows_scroller.set_name ("LocationLocRowsScroller");
807         location_rows_scroller.set_policy (Gtk::POLICY_NEVER, Gtk::POLICY_AUTOMATIC);
808         location_rows_scroller.set_size_request (-1, 130);
809
810         newest_location = 0;
811
812         loc_frame_box.set_spacing (5);
813         loc_frame_box.set_border_width (5);
814         loc_frame_box.set_name("LocationFrameBox");
815
816         loc_frame_box.pack_start (location_rows_scroller, true, true);
817
818         add_location_button.set_name ("LocationAddLocationButton");
819
820         table->attach (loc_frame_box, 0, 2, table_row, table_row + 1);
821         ++table_row;
822
823         loc_range_panes.add (*table);
824
825         table = manage (new Table (3, 2));
826         table->set_spacings (2);
827         table->set_col_spacing (0, 32);
828         table_row = 0;
829
830         table->attach (*manage (new Label ("")), 0, 2, table_row, table_row + 1, Gtk::SHRINK, Gtk::SHRINK);
831         ++table_row;
832
833         l = manage (new Label (_("<b>Ranges (Including CD Track Ranges)</b>")));
834         l->set_alignment (0, 0.5);
835         l->set_use_markup (true);
836         table->attach (*l, 0, 2, table_row, table_row + 1, Gtk::FILL | Gtk::EXPAND, Gtk::SHRINK);
837         ++table_row;
838
839         range_rows.set_name("LocationRangeRows");
840         range_rows_scroller.add (range_rows);
841         range_rows_scroller.set_name ("LocationRangeRowsScroller");
842         range_rows_scroller.set_policy (Gtk::POLICY_NEVER, Gtk::POLICY_AUTOMATIC);
843         range_rows_scroller.set_size_request (-1, 130);
844
845         range_frame_box.set_spacing (5);
846         range_frame_box.set_name("LocationFrameBox");
847         range_frame_box.set_border_width (5);
848         range_frame_box.pack_start (range_rows_scroller, true, true);
849
850         add_range_button.set_name ("LocationAddRangeButton");
851
852         table->attach (range_frame_box, 0, 2, table_row, table_row + 1);
853         ++table_row;
854
855         loc_range_panes.add (*table);
856
857         HBox* add_button_box = manage (new HBox);
858         add_button_box->pack_start (add_location_button, true, true);
859         add_button_box->pack_start (add_range_button, true, true);
860
861         vbox->pack_start (loc_range_panes, true, true);
862         vbox->pack_start (*add_button_box, false, false);
863
864         pack_start (*vbox);
865
866         add_location_button.signal_clicked().connect (sigc::mem_fun(*this, &LocationUI::add_new_location));
867         add_range_button.signal_clicked().connect (sigc::mem_fun(*this, &LocationUI::add_new_range));
868
869         show_all ();
870
871         signal_map().connect (sigc::mem_fun (*this, &LocationUI::refresh_location_list));
872 }
873
874 LocationUI::~LocationUI()
875 {
876         loop_edit_row.unset_clock_group ();
877         punch_edit_row.unset_clock_group ();
878         delete _clock_group;
879 }
880
881 gint
882 LocationUI::do_location_remove (ARDOUR::Location *loc)
883 {
884         /* this is handled internally by Locations, but there's
885            no point saving state etc. when we know the marker
886            cannot be removed.
887         */
888
889         if (loc->is_session_range()) {
890                 return FALSE;
891         }
892
893         PublicEditor::instance().begin_reversible_command (_("remove marker"));
894         XMLNode &before = _session->locations()->get_state();
895         _session->locations()->remove (loc);
896         XMLNode &after = _session->locations()->get_state();
897         _session->add_command(new MementoCommand<Locations>(*(_session->locations()), &before, &after));
898         PublicEditor::instance().commit_reversible_command ();
899
900         return FALSE;
901 }
902
903 void
904 LocationUI::location_remove_requested (ARDOUR::Location *loc)
905 {
906         // must do this to prevent problems when destroying
907         // the effective sender of this event
908
909         Glib::signal_idle().connect (sigc::bind (sigc::mem_fun(*this, &LocationUI::do_location_remove), loc));
910 }
911
912
913 void
914 LocationUI::location_redraw_ranges ()
915 {
916         range_rows.hide();
917         range_rows.show();
918 }
919
920 struct LocationSortByStart {
921         bool operator() (Location *a, Location *b) {
922                 return a->start() < b->start();
923         }
924 };
925
926 void
927 LocationUI::location_added (Location* location)
928 {
929         if (location->is_auto_punch()) {
930                 punch_edit_row.set_location(location);
931         } else if (location->is_auto_loop()) {
932                 loop_edit_row.set_location(location);
933         } else if (location->is_range_marker() || location->is_mark()) {
934                 Locations::LocationList loc = _session->locations()->list ();
935                 loc.sort (LocationSortByStart ());
936
937                 LocationEditRow* erow = manage (new LocationEditRow (_session, location));
938
939                 erow->set_clock_group (*_clock_group);
940                 erow->remove_requested.connect (sigc::mem_fun (*this, &LocationUI::location_remove_requested));
941
942                 Box_Helpers::BoxList & children = location->is_range_marker() ? range_rows.children () : location_rows.children ();
943
944                 /* Step through the location list and the GUI list to find the place to insert */
945                 Locations::LocationList::iterator i = loc.begin ();
946                 Box_Helpers::BoxList::iterator j = children.begin ();
947                 while (i != loc.end()) {
948
949                         if (location->flags() != (*i)->flags()) {
950                                 /* Skip locations in the session list that aren't of the right type */
951                                 ++i;
952                                 continue;
953                         }
954
955                         if (*i == location) {
956                                 children.insert (j, Box_Helpers::Element (*erow, PACK_SHRINK, 1, PACK_START));
957                                 break;
958                         }
959
960                         ++i;
961
962                         if (j != children.end()) {
963                                 ++j;
964                         }
965                 }
966
967                 range_rows.show_all ();
968                 location_rows.show_all ();
969
970                 if (location == newest_location) {
971                         newest_location = 0;
972                         erow->focus_name();
973                 }
974         }
975 }
976
977 void
978 LocationUI::location_removed (Location* location)
979 {
980         ENSURE_GUI_THREAD (*this, &LocationUI::location_removed, location)
981
982         if (location->is_auto_punch()) {
983                 punch_edit_row.set_location(0);
984         } else if (location->is_auto_loop()) {
985                 loop_edit_row.set_location(0);
986         } else if (location->is_range_marker() || location->is_mark()) {
987                 Box_Helpers::BoxList& children = location->is_range_marker() ? range_rows.children () : location_rows.children ();
988                 for (Box_Helpers::BoxList::iterator i = children.begin(); i != children.end(); ++i) {
989                         LocationEditRow* r = dynamic_cast<LocationEditRow*> (i->get_widget());
990                         if (r && r->get_location() == location) {
991                                 children.erase (i);
992                                 break;
993                         }
994                 }
995         }
996 }
997
998 void
999 LocationUI::map_locations (const Locations::LocationList& locations)
1000 {
1001         Locations::LocationList::iterator i;
1002         gint n;
1003         int mark_n = 0;
1004         Locations::LocationList temp = locations;
1005         LocationSortByStart cmp;
1006
1007         temp.sort (cmp);
1008
1009         for (n = 0, i = temp.begin(); i != temp.end(); ++n, ++i) {
1010
1011                 Location* location = *i;
1012
1013                 if (location->is_mark()) {
1014                         LocationEditRow* erow = manage (new LocationEditRow (_session, location, mark_n));
1015
1016                         erow->set_clock_group (*_clock_group);
1017                         erow->remove_requested.connect (sigc::mem_fun(*this, &LocationUI::location_remove_requested));
1018                         erow->redraw_ranges.connect (sigc::mem_fun(*this, &LocationUI::location_redraw_ranges));
1019
1020                         Box_Helpers::BoxList & loc_children = location_rows.children();
1021                         loc_children.push_back(Box_Helpers::Element(*erow, PACK_SHRINK, 1, PACK_START));
1022                 } else if (location->is_auto_punch()) {
1023                         punch_edit_row.set_session (_session);
1024                         punch_edit_row.set_location (location);
1025                         punch_edit_row.show_all();
1026                 } else if (location->is_auto_loop()) {
1027                         loop_edit_row.set_session (_session);
1028                         loop_edit_row.set_location (location);
1029                         loop_edit_row.show_all();
1030                 } else {
1031                         LocationEditRow* erow = manage (new LocationEditRow(_session, location));
1032
1033                         erow->set_clock_group (*_clock_group);
1034                         erow->remove_requested.connect (sigc::mem_fun(*this, &LocationUI::location_remove_requested));
1035
1036                         Box_Helpers::BoxList & range_children = range_rows.children();
1037                         range_children.push_back(Box_Helpers::Element(*erow,  PACK_SHRINK, 1, PACK_START));
1038                 }
1039         }
1040
1041         range_rows.show_all();
1042         location_rows.show_all();
1043 }
1044
1045 void
1046 LocationUI::add_new_location()
1047 {
1048         string markername;
1049
1050         if (_session) {
1051                 framepos_t where = _session->audible_frame();
1052                 _session->locations()->next_available_name(markername,"mark");
1053                 Location *location = new Location (*_session, where, where, markername, Location::IsMark);
1054                 if (UIConfiguration::instance().get_name_new_markers()) {
1055                         newest_location = location;
1056                 }
1057                 PublicEditor::instance().begin_reversible_command (_("add marker"));
1058                 XMLNode &before = _session->locations()->get_state();
1059                 _session->locations()->add (location, true);
1060                 XMLNode &after = _session->locations()->get_state();
1061                 _session->add_command (new MementoCommand<Locations>(*(_session->locations()), &before, &after));
1062                 PublicEditor::instance().commit_reversible_command ();
1063         }
1064
1065 }
1066
1067 void
1068 LocationUI::add_new_range()
1069 {
1070         string rangename;
1071
1072         if (_session) {
1073                 framepos_t where = _session->audible_frame();
1074                 _session->locations()->next_available_name(rangename,"unnamed");
1075                 Location *location = new Location (*_session, where, where, rangename, Location::IsRangeMarker);
1076                 PublicEditor::instance().begin_reversible_command (_("add range marker"));
1077                 XMLNode &before = _session->locations()->get_state();
1078                 _session->locations()->add (location, true);
1079                 XMLNode &after = _session->locations()->get_state();
1080                 _session->add_command (new MementoCommand<Locations>(*(_session->locations()), &before, &after));
1081                 PublicEditor::instance().commit_reversible_command ();
1082         }
1083 }
1084
1085 void
1086 LocationUI::refresh_location_list ()
1087 {
1088         ENSURE_GUI_THREAD (*this, &LocationUI::refresh_location_list)
1089         using namespace Box_Helpers;
1090
1091         // this is just too expensive to do when window is not shown
1092         if (!is_mapped()) {
1093                 return;
1094         }
1095
1096         BoxList & loc_children = location_rows.children();
1097         BoxList & range_children = range_rows.children();
1098
1099         loc_children.clear();
1100         range_children.clear();
1101
1102         if (_session) {
1103                 _session->locations()->apply (*this, &LocationUI::map_locations);
1104         }
1105 }
1106
1107 void
1108 LocationUI::set_session(ARDOUR::Session* s)
1109 {
1110         SessionHandlePtr::set_session (s);
1111
1112         if (_session) {
1113                 _session->locations()->added.connect (_session_connections, invalidator (*this), boost::bind (&LocationUI::location_added, this, _1), gui_context());
1114                 _session->locations()->removed.connect (_session_connections, invalidator (*this), boost::bind (&LocationUI::location_removed, this, _1), gui_context());
1115                 _session->locations()->changed.connect (_session_connections, invalidator (*this), boost::bind (&LocationUI::refresh_location_list, this), gui_context());
1116
1117                 _clock_group->set_clock_mode (clock_mode_from_session_instant_xml ());
1118         } else {
1119                 _mode_set = false;
1120         }
1121
1122         loop_edit_row.set_session (s);
1123         punch_edit_row.set_session (s);
1124
1125         refresh_location_list ();
1126 }
1127
1128 void
1129 LocationUI::session_going_away()
1130 {
1131         ENSURE_GUI_THREAD (*this, &LocationUI::session_going_away);
1132
1133         using namespace Box_Helpers;
1134         BoxList & loc_children = location_rows.children();
1135         BoxList & range_children = range_rows.children();
1136
1137         loc_children.clear();
1138         range_children.clear();
1139
1140         loop_edit_row.set_session (0);
1141         loop_edit_row.set_location (0);
1142
1143         punch_edit_row.set_session (0);
1144         punch_edit_row.set_location (0);
1145
1146         _mode_set = false;
1147
1148         SessionHandlePtr::session_going_away ();
1149 }
1150
1151 XMLNode &
1152 LocationUI::get_state () const
1153 {
1154         XMLNode* node = new XMLNode (_state_node_name);
1155         node->set_property (X_("clock-mode"), _clock_group->clock_mode ());
1156         return *node;
1157 }
1158
1159 int
1160 LocationUI::set_state (const XMLNode& node)
1161 {
1162         if (node.name() != _state_node_name) {
1163                 return -1;
1164         }
1165
1166         if (!node.get_property (X_("clock-mode"), _mode)) {
1167                 return -1;
1168         }
1169
1170         _mode_set = true;
1171         _clock_group->set_clock_mode (_mode);
1172         return 0;
1173 }
1174
1175 AudioClock::Mode
1176 LocationUI::clock_mode_from_session_instant_xml ()
1177 {
1178         if (_mode_set) {
1179                 return _mode;
1180         }
1181
1182         XMLNode* node = _session->instant_xml (_state_node_name);
1183         if (!node) {
1184                 return ARDOUR_UI::instance()->secondary_clock->mode();
1185         }
1186
1187         if (!node->get_property (X_("clock-mode"), _mode)) {
1188                 return ARDOUR_UI::instance()->secondary_clock->mode();
1189         }
1190
1191         _mode_set = true;
1192         return _mode;
1193 }
1194
1195
1196 /*------------------------*/
1197
1198 LocationUIWindow::LocationUIWindow ()
1199         : ArdourWindow (S_("Ranges|Locations"))
1200 {
1201         set_wmclass(X_("ardour_locations"), PROGRAM_NAME);
1202         set_name ("LocationWindow");
1203
1204         add (_ui);
1205 }
1206
1207 LocationUIWindow::~LocationUIWindow()
1208 {
1209 }
1210
1211 void
1212 LocationUIWindow::on_map ()
1213 {
1214         ArdourWindow::on_map ();
1215         _ui.refresh_location_list();
1216 }
1217
1218 bool
1219 LocationUIWindow::on_delete_event (GdkEventAny*)
1220 {
1221         return false;
1222 }
1223
1224 void
1225 LocationUIWindow::set_session (Session *s)
1226 {
1227         ArdourWindow::set_session (s);
1228         _ui.set_session (s);
1229         _ui.show_all ();
1230 }
1231
1232 void
1233 LocationUIWindow::session_going_away ()
1234 {
1235         ArdourWindow::session_going_away ();
1236         hide_all();
1237 }