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