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