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