Fix 'Location window is blank' (#5538)
[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                 ARDOUR_UI::instance()->set_tip (remove_button, _("Remove this range"));
311                 ARDOUR_UI::instance()->set_tip (start_clock, _("Start time - middle click to locate here"));
312                 ARDOUR_UI::instance()->set_tip (end_clock, _("End time - middle click to locate here"));
313                 ARDOUR_UI::instance()->set_tip (length_clock, _("Length"));
314
315                 ARDOUR_UI::instance()->tooltips().set_tip (start_to_playhead_button, _("Set range start from playhead location"));
316                 ARDOUR_UI::instance()->tooltips().set_tip (end_to_playhead_button, _("Set range end from playhead location"));
317                 
318         } else {
319
320                 ARDOUR_UI::instance()->set_tip (remove_button, _("Remove this marker"));
321                 ARDOUR_UI::instance()->set_tip (start_clock, _("Position - middle click to locate here"));
322
323                 ARDOUR_UI::instance()->tooltips().set_tip (start_to_playhead_button, _("Set marker time from playhead location"));
324
325                 end_clock.hide();
326                 length_clock.hide();
327         }
328
329         set_clock_editable_status ();
330
331         --i_am_the_modifier;
332
333         location->start_changed.connect (connections, invalidator (*this), boost::bind (&LocationEditRow::start_changed, this, _1), gui_context());
334         location->end_changed.connect (connections, invalidator (*this), boost::bind (&LocationEditRow::end_changed, this, _1), gui_context());
335         location->name_changed.connect (connections, invalidator (*this), boost::bind (&LocationEditRow::name_changed, this, _1), gui_context());
336         location->changed.connect (connections, invalidator (*this), boost::bind (&LocationEditRow::location_changed, this, _1), gui_context());
337         location->FlagsChanged.connect (connections, invalidator (*this), boost::bind (&LocationEditRow::flags_changed, this, _1, _2), gui_context());
338         location->LockChanged.connect (connections, invalidator (*this), boost::bind (&LocationEditRow::lock_changed, this, _1), gui_context());
339         location->PositionLockStyleChanged.connect (connections, invalidator (*this), boost::bind (&LocationEditRow::position_lock_style_changed, this, _1), gui_context());
340 }
341
342 void
343 LocationEditRow::name_entry_changed ()
344 {
345         ENSURE_GUI_THREAD (*this, &LocationEditRow::name_entry_changed)
346
347         if (i_am_the_modifier || !location) {
348                 return;
349         }
350
351         location->set_name (name_entry.get_text());
352 }
353
354
355 void
356 LocationEditRow::isrc_entry_changed ()
357 {
358         ENSURE_GUI_THREAD (*this, &LocationEditRow::isrc_entry_changed)
359
360         if (i_am_the_modifier || !location) return;
361
362         if (isrc_entry.get_text() != "" ) {
363
364           location->cd_info["isrc"] = isrc_entry.get_text();
365
366         } else {
367           location->cd_info.erase("isrc");
368         }
369 }
370
371 void
372 LocationEditRow::performer_entry_changed ()
373 {
374         ENSURE_GUI_THREAD (*this, &LocationEditRow::performer_entry_changed)
375
376         if (i_am_the_modifier || !location) return;
377
378         if (performer_entry.get_text() != "") {
379           location->cd_info["performer"] = performer_entry.get_text();
380         } else {
381           location->cd_info.erase("performer");
382         }
383 }
384
385 void
386 LocationEditRow::composer_entry_changed ()
387 {
388         ENSURE_GUI_THREAD (*this, &LocationEditRow::composer_entry_changed)
389
390         if (i_am_the_modifier || !location) return;
391
392         if (composer_entry.get_text() != "") {
393         location->cd_info["composer"] = composer_entry.get_text();
394         } else {
395           location->cd_info.erase("composer");
396         }
397 }
398
399 void
400 LocationEditRow::to_playhead_button_pressed (LocationPart part)
401 {
402         if (!location) {
403                 return;
404         }
405
406         switch (part) {
407         case LocStart:
408                 location->set_start (_session->transport_frame ());
409                 break;
410         case LocEnd:
411                 location->set_end (_session->transport_frame ());
412                 break;
413         default:
414                 break;
415         }
416 }
417
418 bool
419 LocationEditRow::locate_to_clock (GdkEventButton* ev, AudioClock* clock)
420 {
421         if (Keyboard::is_button2_event (ev)) {
422                 _session->request_locate (clock->current_time());
423                 return true;
424         }
425         return false;
426 }
427
428 void
429 LocationEditRow::clock_changed (LocationPart part)
430 {
431         if (i_am_the_modifier || !location) {
432                 return;
433         }
434
435         switch (part) {
436         case LocStart:
437                 location->set_start (start_clock.current_time());
438                 break;
439         case LocEnd:
440                 location->set_end (end_clock.current_time());
441                 break;
442         case LocLength:
443                 location->set_end (location->start() + length_clock.current_duration());
444         default:
445                 break;
446         }
447 }
448
449 void
450 LocationEditRow::cd_toggled ()
451 {
452         if (i_am_the_modifier || !location) {
453                 return;
454         }
455
456         //if (cd_check_button.get_active() == location->is_cd_marker()) {
457         //      return;
458         //}
459
460         if (cd_check_button.get_active()) {
461                 if (location->start() <= _session->current_start_frame()) {
462                         error << _("You cannot put a CD marker at the start of the session") << endmsg;
463                         cd_check_button.set_active (false);
464                         return;
465                 }
466         }
467
468         location->set_cd (cd_check_button.get_active(), this);
469
470         if (location->is_cd_marker() && !(location->is_mark())) {
471
472                 if (location->cd_info.find("isrc") != location->cd_info.end()) {
473                         isrc_entry.set_text(location->cd_info["isrc"]);
474                 }
475                 if (location->cd_info.find("performer") != location->cd_info.end()) {
476                         performer_entry.set_text(location->cd_info["performer"]);
477                 }
478                 if (location->cd_info.find("composer") != location->cd_info.end()) {
479                         composer_entry.set_text(location->cd_info["composer"]);
480                 }
481                 if (location->cd_info.find("scms") != location->cd_info.end()) {
482                         scms_check_button.set_active(true);
483                 }
484                 if (location->cd_info.find("preemph") != location->cd_info.end()) {
485                         preemph_check_button.set_active(true);
486                 }
487
488                 if (!cd_track_details_hbox.get_parent()) {
489                         item_table.attach (cd_track_details_hbox, 0, 7, 1, 2, FILL | EXPAND, FILL, 4, 0);
490                 }
491                 // item_table.resize(2, 7);
492                 cd_track_details_hbox.show_all();
493
494         } else if (cd_track_details_hbox.get_parent()){
495
496                 item_table.remove (cd_track_details_hbox);
497                 //        item_table.resize(1, 7);
498                 redraw_ranges(); /*     EMIT_SIGNAL */
499         }
500 }
501
502 void
503 LocationEditRow::hide_toggled ()
504 {
505         if (i_am_the_modifier || !location) {
506                 return;
507         }
508
509         location->set_hidden (hide_check_button.get_active(), this);
510 }
511
512 void
513 LocationEditRow::lock_toggled ()
514 {
515         if (i_am_the_modifier || !location) {
516                 return;
517         }
518
519         if (location->locked()) {
520                 location->unlock ();
521         } else {
522                 location->lock ();
523         }
524 }
525
526 void
527 LocationEditRow::glue_toggled ()
528 {
529         if (i_am_the_modifier || !location) {
530                 return;
531         }
532
533         if (location->position_lock_style() == AudioTime) {
534                 location->set_position_lock_style (MusicTime);
535         } else {
536                 location->set_position_lock_style (AudioTime);
537         }
538 }
539
540 void
541 LocationEditRow::remove_button_pressed ()
542 {
543         if (!location) {
544                 return;
545         }
546
547         remove_requested (location); /* EMIT_SIGNAL */
548 }
549
550
551
552 void
553 LocationEditRow::scms_toggled ()
554 {
555         if (i_am_the_modifier || !location) return;
556
557         if (scms_check_button.get_active()) {
558           location->cd_info["scms"] = "on";
559         } else {
560           location->cd_info.erase("scms");
561         }
562
563 }
564
565 void
566 LocationEditRow::preemph_toggled ()
567 {
568         if (i_am_the_modifier || !location) return;
569
570         if (preemph_check_button.get_active()) {
571           location->cd_info["preemph"] = "on";
572         } else {
573           location->cd_info.erase("preemph");
574         }
575 }
576
577 void
578 LocationEditRow::end_changed (ARDOUR::Location *)
579 {
580         ENSURE_GUI_THREAD (*this, &LocationEditRow::end_changed, loc)
581
582         if (!location) return;
583
584         // update end and length
585         i_am_the_modifier++;
586
587         end_clock.set (location->end());
588         length_clock.set (location->length());
589
590         i_am_the_modifier--;
591 }
592
593 void
594 LocationEditRow::start_changed (ARDOUR::Location*)
595 {
596         if (!location) return;
597
598         // update end and length
599         i_am_the_modifier++;
600
601         start_clock.set (location->start());
602
603         if (location->start() == _session->current_start_frame()) {
604                 cd_check_button.set_sensitive (false);
605         } else {
606                 cd_check_button.set_sensitive (true);
607         }
608
609         i_am_the_modifier--;
610 }
611
612 void
613 LocationEditRow::name_changed (ARDOUR::Location *)
614 {
615         if (!location) return;
616
617         // update end and length
618         i_am_the_modifier++;
619
620         name_entry.set_text(location->name());
621         name_label.set_text(location->name());
622
623         i_am_the_modifier--;
624
625 }
626
627 void
628 LocationEditRow::location_changed (ARDOUR::Location*)
629 {
630
631         if (!location) return;
632
633         i_am_the_modifier++;
634
635         start_clock.set (location->start());
636         end_clock.set (location->end());
637         length_clock.set (location->length());
638
639         set_clock_editable_status ();
640
641         i_am_the_modifier--;
642
643 }
644
645 void
646 LocationEditRow::flags_changed (ARDOUR::Location*, void *)
647 {
648         if (!location) {
649                 return;
650         }
651
652         i_am_the_modifier++;
653
654         cd_check_button.set_active (location->is_cd_marker());
655         hide_check_button.set_active (location->is_hidden());
656         glue_check_button.set_active (location->position_lock_style() == MusicTime);
657
658         i_am_the_modifier--;
659 }
660
661 void
662 LocationEditRow::lock_changed (ARDOUR::Location*)
663 {
664         if (!location) {
665                 return;
666         }
667
668         i_am_the_modifier++;
669
670         lock_check_button.set_active (location->locked());
671
672         set_clock_editable_status ();
673
674         i_am_the_modifier--;
675 }
676
677 void
678 LocationEditRow::position_lock_style_changed (ARDOUR::Location*)
679 {
680         if (!location) {
681                 return;
682         }
683
684         i_am_the_modifier++;
685
686         glue_check_button.set_active (location->position_lock_style() == MusicTime);
687
688         i_am_the_modifier--;
689 }
690
691 void
692 LocationEditRow::focus_name()
693 {
694         name_entry.grab_focus ();
695 }
696
697 void
698 LocationEditRow::set_clock_editable_status ()
699 {
700         start_clock.set_editable (!location->locked());
701         end_clock.set_editable (!location->locked());
702         length_clock.set_editable (!location->locked());
703 }
704
705 /*------------------------------------------------------------------------*/
706
707 LocationUI::LocationUI ()
708         : add_location_button (_("New Marker"))
709         , add_range_button (_("New Range"))
710 {
711         i_am_the_modifier = 0;
712
713         _clock_group = new ClockGroup;
714
715         VBox* vbox = manage (new VBox);
716
717         Table* table = manage (new Table (2, 2));
718         table->set_spacings (2);
719         table->set_col_spacing (0, 32);
720         int table_row = 0;
721
722         Label* l = manage (new Label (_("<b>Loop/Punch Ranges</b>")));
723         l->set_alignment (0, 0.5);
724         l->set_use_markup (true);
725         table->attach (*l, 0, 2, table_row, table_row + 1);
726         ++table_row;
727
728         loop_edit_row.set_clock_group (*_clock_group);
729         punch_edit_row.set_clock_group (*_clock_group);
730
731         loop_punch_box.pack_start (loop_edit_row, false, false);
732         loop_punch_box.pack_start (punch_edit_row, false, false);
733
734         table->attach (loop_punch_box, 1, 2, table_row, table_row + 1);
735         ++table_row;
736
737         vbox->pack_start (*table, false, false);
738
739         table = manage (new Table (3, 2));
740         table->set_spacings (2);
741         table->set_col_spacing (0, 32);
742         table_row = 0;
743
744         table->attach (*manage (new Label ("")), 0, 2, table_row, table_row + 1, Gtk::SHRINK, Gtk::SHRINK);
745         ++table_row;
746
747         l = manage (new Label (_("<b>Markers (Including CD Index)</b>")));
748         l->set_alignment (0, 0.5);
749         l->set_use_markup (true);
750         table->attach (*l, 0, 2, table_row, table_row + 1, Gtk::FILL | Gtk::EXPAND, Gtk::SHRINK);
751         ++table_row;
752
753         location_rows.set_name("LocationLocRows");
754         location_rows_scroller.add (location_rows);
755         location_rows_scroller.set_name ("LocationLocRowsScroller");
756         location_rows_scroller.set_policy (Gtk::POLICY_NEVER, Gtk::POLICY_AUTOMATIC);
757         location_rows_scroller.set_size_request (-1, 130);
758
759         newest_location = 0;
760
761         loc_frame_box.set_spacing (5);
762         loc_frame_box.set_border_width (5);
763         loc_frame_box.set_name("LocationFrameBox");
764
765         loc_frame_box.pack_start (location_rows_scroller, true, true);
766
767         add_location_button.set_name ("LocationAddLocationButton");
768
769         table->attach (loc_frame_box, 0, 2, table_row, table_row + 1);
770         ++table_row;
771
772         loc_range_panes.pack1 (*table, true, false);
773
774         table = manage (new Table (3, 2));
775         table->set_spacings (2);
776         table->set_col_spacing (0, 32);
777         table_row = 0;
778
779         table->attach (*manage (new Label ("")), 0, 2, table_row, table_row + 1, Gtk::SHRINK, Gtk::SHRINK);
780         ++table_row;
781
782         l = manage (new Label (_("<b>Ranges (Including CD Track Ranges)</b>")));
783         l->set_alignment (0, 0.5);
784         l->set_use_markup (true);
785         table->attach (*l, 0, 2, table_row, table_row + 1, Gtk::FILL | Gtk::EXPAND, Gtk::SHRINK);
786         ++table_row;
787
788         range_rows.set_name("LocationRangeRows");
789         range_rows_scroller.add (range_rows);
790         range_rows_scroller.set_name ("LocationRangeRowsScroller");
791         range_rows_scroller.set_policy (Gtk::POLICY_NEVER, Gtk::POLICY_AUTOMATIC);
792         range_rows_scroller.set_size_request (-1, 130);
793
794         range_frame_box.set_spacing (5);
795         range_frame_box.set_name("LocationFrameBox");
796         range_frame_box.set_border_width (5);
797         range_frame_box.pack_start (range_rows_scroller, true, true);
798
799         add_range_button.set_name ("LocationAddRangeButton");
800
801         table->attach (range_frame_box, 0, 2, table_row, table_row + 1);
802         ++table_row;
803
804         loc_range_panes.pack2 (*table, true, false);
805
806         HBox* add_button_box = manage (new HBox);
807         add_button_box->pack_start (add_location_button, true, true);
808         add_button_box->pack_start (add_range_button, true, true);
809
810         vbox->pack_start (loc_range_panes, true, true);
811         vbox->pack_start (*add_button_box, false, false);
812
813         pack_start (*vbox);
814
815         add_location_button.signal_clicked().connect (sigc::mem_fun(*this, &LocationUI::add_new_location));
816         add_range_button.signal_clicked().connect (sigc::mem_fun(*this, &LocationUI::add_new_range));
817
818         show_all ();
819
820         signal_map().connect (sigc::mem_fun (*this, &LocationUI::refresh_location_list));
821 }
822
823 LocationUI::~LocationUI()
824 {
825         delete _clock_group;
826 }
827
828 gint
829 LocationUI::do_location_remove (ARDOUR::Location *loc)
830 {
831         /* this is handled internally by Locations, but there's
832            no point saving state etc. when we know the marker
833            cannot be removed.
834         */
835
836         if (loc->is_session_range()) {
837                 return FALSE;
838         }
839
840         _session->begin_reversible_command (_("remove marker"));
841         XMLNode &before = _session->locations()->get_state();
842         _session->locations()->remove (loc);
843         XMLNode &after = _session->locations()->get_state();
844         _session->add_command(new MementoCommand<Locations>(*(_session->locations()), &before, &after));
845         _session->commit_reversible_command ();
846
847         return FALSE;
848 }
849
850 void
851 LocationUI::location_remove_requested (ARDOUR::Location *loc)
852 {
853         // must do this to prevent problems when destroying
854         // the effective sender of this event
855
856         Glib::signal_idle().connect (sigc::bind (sigc::mem_fun(*this, &LocationUI::do_location_remove), loc));
857 }
858
859
860 void
861 LocationUI::location_redraw_ranges ()
862 {
863         range_rows.hide();
864         range_rows.show();
865 }
866
867 struct LocationSortByStart {
868         bool operator() (Location *a, Location *b) {
869                 return a->start() < b->start();
870         }
871 };
872
873 void
874 LocationUI::location_added (Location* location)
875 {
876         if (location->is_auto_punch()) {
877                 punch_edit_row.set_location(location);
878         } else if (location->is_auto_loop()) {
879                 loop_edit_row.set_location(location);
880         } else if (location->is_range_marker() || location->is_mark()) {
881                 Locations::LocationList loc = _session->locations()->list ();
882                 loc.sort (LocationSortByStart ());
883
884                 LocationEditRow* erow = manage (new LocationEditRow (_session, location));
885                 
886                 erow->set_clock_group (*_clock_group);
887                 erow->remove_requested.connect (sigc::mem_fun (*this, &LocationUI::location_remove_requested));
888
889                 Box_Helpers::BoxList & children = location->is_range_marker() ? range_rows.children () : location_rows.children ();
890
891                 /* Step through the location list and the GUI list to find the place to insert */
892                 Locations::LocationList::iterator i = loc.begin ();
893                 Box_Helpers::BoxList::iterator j = children.begin ();
894                 while (i != loc.end()) {
895
896                         if (location->flags() != (*i)->flags()) {
897                                 /* Skip locations in the session list that aren't of the right type */
898                                 ++i;
899                                 continue;
900                         }
901
902                         if (*i == location) {
903                                 children.insert (j, Box_Helpers::Element (*erow, PACK_SHRINK, 1, PACK_START));
904                                 break;
905                         }
906
907                         ++i;
908
909                         if (j != children.end()) {
910                                 ++j;
911                         }
912                 }
913
914                 range_rows.show_all ();
915                 location_rows.show_all ();
916
917                 if (location == newest_location) {
918                         newest_location = 0;
919                         erow->focus_name();
920                 }
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                 } else if (location->is_auto_punch()) {
971                         punch_edit_row.set_session (_session);
972                         punch_edit_row.set_location (location);
973                         punch_edit_row.show_all();
974                 } else if (location->is_auto_loop()) {
975                         loop_edit_row.set_session (_session);
976                         loop_edit_row.set_location (location);
977                         loop_edit_row.show_all();
978                 } else {
979                         LocationEditRow* erow = manage (new LocationEditRow(_session, location));
980
981                         erow->set_clock_group (*_clock_group);
982                         erow->remove_requested.connect (sigc::mem_fun(*this, &LocationUI::location_remove_requested));
983
984                         Box_Helpers::BoxList & range_children = range_rows.children();
985                         range_children.push_back(Box_Helpers::Element(*erow,  PACK_SHRINK, 1, PACK_START));
986                 }
987         }
988
989         range_rows.show_all();
990         location_rows.show_all();
991 }
992
993 void
994 LocationUI::add_new_location()
995 {
996         string markername;
997
998         if (_session) {
999                 framepos_t where = _session->audible_frame();
1000                 _session->locations()->next_available_name(markername,"mark");
1001                 Location *location = new Location (*_session, where, where, markername, Location::IsMark);
1002                 if (Config->get_name_new_markers()) {
1003                         newest_location = location;
1004                 }
1005                 _session->begin_reversible_command (_("add marker"));
1006                 XMLNode &before = _session->locations()->get_state();
1007                 _session->locations()->add (location, true);
1008                 XMLNode &after = _session->locations()->get_state();
1009                 _session->add_command (new MementoCommand<Locations>(*(_session->locations()), &before, &after));
1010                 _session->commit_reversible_command ();
1011         }
1012
1013 }
1014
1015 void
1016 LocationUI::add_new_range()
1017 {
1018         string rangename;
1019
1020         if (_session) {
1021                 framepos_t where = _session->audible_frame();
1022                 _session->locations()->next_available_name(rangename,"unnamed");
1023                 Location *location = new Location (*_session, where, where, rangename, Location::IsRangeMarker);
1024                 _session->begin_reversible_command (_("add range marker"));
1025                 XMLNode &before = _session->locations()->get_state();
1026                 _session->locations()->add (location, true);
1027                 XMLNode &after = _session->locations()->get_state();
1028                 _session->add_command (new MementoCommand<Locations>(*(_session->locations()), &before, &after));
1029                 _session->commit_reversible_command ();
1030         }
1031 }
1032
1033 void
1034 LocationUI::refresh_location_list ()
1035 {
1036         ENSURE_GUI_THREAD (*this, &LocationUI::refresh_location_list)
1037         using namespace Box_Helpers;
1038
1039         // this is just too expensive to do when window is not shown
1040         if (!is_mapped()) {
1041                 return;
1042         }
1043
1044         BoxList & loc_children = location_rows.children();
1045         BoxList & range_children = range_rows.children();
1046
1047         loc_children.clear();
1048         range_children.clear();
1049
1050         if (_session) {
1051                 _session->locations()->apply (*this, &LocationUI::map_locations);
1052         }
1053 }
1054
1055 void
1056 LocationUI::set_session(ARDOUR::Session* s)
1057 {
1058         SessionHandlePtr::set_session (s);
1059
1060         if (_session) {
1061                 _session->locations()->changed.connect (_session_connections, invalidator (*this), boost::bind (&LocationUI::locations_changed, this, _1), gui_context());
1062                 _session->locations()->StateChanged.connect (_session_connections, invalidator (*this), boost::bind (&LocationUI::refresh_location_list, this), gui_context());
1063                 _session->locations()->added.connect (_session_connections, invalidator (*this), boost::bind (&LocationUI::location_added, this, _1), gui_context());
1064                 _session->locations()->removed.connect (_session_connections, invalidator (*this), boost::bind (&LocationUI::location_removed, this, _1), gui_context());
1065                 _clock_group->set_clock_mode (clock_mode_from_session_instant_xml ());
1066         }
1067
1068         loop_edit_row.set_session (s);
1069         punch_edit_row.set_session (s);
1070
1071         refresh_location_list ();
1072 }
1073
1074 void
1075 LocationUI::locations_changed (Locations::Change c)
1076 {
1077         /* removal is signalled by both a removed and a changed signal emission from Locations,
1078            so we don't need to refresh the list on a removal
1079         */
1080         if (c != Locations::REMOVAL) {
1081                 refresh_location_list ();
1082         }
1083 }
1084
1085 void
1086 LocationUI::session_going_away()
1087 {
1088         ENSURE_GUI_THREAD (*this, &LocationUI::session_going_away);
1089
1090         using namespace Box_Helpers;
1091         BoxList & loc_children = location_rows.children();
1092         BoxList & range_children = range_rows.children();
1093
1094         loc_children.clear();
1095         range_children.clear();
1096
1097         loop_edit_row.set_session (0);
1098         loop_edit_row.set_location (0);
1099
1100         punch_edit_row.set_session (0);
1101         punch_edit_row.set_location (0);
1102
1103         SessionHandlePtr::session_going_away ();
1104 }
1105
1106 XMLNode &
1107 LocationUI::get_state () const
1108 {
1109         XMLNode* node = new XMLNode (X_("LocationUI"));
1110         node->add_property (X_("clock-mode"), enum_2_string (_clock_group->clock_mode ()));
1111         return *node;
1112 }
1113
1114 AudioClock::Mode
1115 LocationUI::clock_mode_from_session_instant_xml () const
1116 {
1117         XMLNode* node = _session->instant_xml (X_("LocationUI"));
1118         if (!node) {
1119                 return AudioClock::Frames;
1120         }
1121
1122         XMLProperty* p = node->property (X_("clock-mode"));
1123         if (!p) {
1124                 return ARDOUR_UI::instance()->secondary_clock->mode();
1125         }
1126               
1127         return (AudioClock::Mode) string_2_enum (p->value (), AudioClock::Mode);
1128 }
1129
1130
1131 /*------------------------*/
1132
1133 LocationUIWindow::LocationUIWindow ()
1134         : ArdourWindow (_("Locations"))
1135 {
1136         set_wmclass(X_("ardour_locations"), PROGRAM_NAME);
1137         set_name ("LocationWindow");
1138
1139         add (_ui);
1140 }
1141
1142 LocationUIWindow::~LocationUIWindow()
1143 {
1144 }
1145
1146 void
1147 LocationUIWindow::on_map ()
1148 {
1149         ArdourWindow::on_map ();
1150         _ui.refresh_location_list();
1151 }
1152
1153 bool
1154 LocationUIWindow::on_delete_event (GdkEventAny*)
1155 {
1156         hide ();
1157         return true;
1158 }
1159
1160 void
1161 LocationUIWindow::set_session (Session *s)
1162 {
1163         ArdourWindow::set_session (s);
1164         _ui.set_session (s);
1165         _ui.show_all ();
1166 }
1167
1168 void
1169 LocationUIWindow::session_going_away ()
1170 {
1171         ArdourWindow::session_going_away ();
1172         hide_all();
1173 }