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