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