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