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