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