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