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