Minor tidying-up.
[ardour.git] / gtk2_ardour / automation_time_axis.cc
1 /*
2     Copyright (C) 2000-2007 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 <utility>
21 #include <gtkmm2ext/barcontroller.h>
22 #include "pbd/memento_command.h"
23 #include "ardour/automation_control.h"
24 #include "ardour/event_type_map.h"
25 #include "ardour/route.h"
26 #include "ardour/session.h"
27
28 #include "ardour_ui.h"
29 #include "automation_time_axis.h"
30 #include "automation_streamview.h"
31 #include "global_signals.h"
32 #include "gui_thread.h"
33 #include "route_time_axis.h"
34 #include "automation_line.h"
35 #include "public_editor.h"
36 #include "simplerect.h"
37 #include "selection.h"
38 #include "rgb_macros.h"
39 #include "point_selection.h"
40 #include "canvas_impl.h"
41 #include "utils.h"
42
43 #include "i18n.h"
44
45 using namespace std;
46 using namespace ARDOUR;
47 using namespace PBD;
48 using namespace Gtk;
49 using namespace Gtkmm2ext;
50 using namespace Editing;
51
52 Pango::FontDescription* AutomationTimeAxisView::name_font = 0;
53 bool AutomationTimeAxisView::have_name_font = false;
54 const string AutomationTimeAxisView::state_node_name = "AutomationChild";
55
56
57 /** \a a the automatable object this time axis is to display data for.
58  * For route/track automation (e.g. gain) pass the route for both \r and \a.
59  * For route child (e.g. plugin) automation, pass the child for \a.
60  * For region automation (e.g. MIDI CC), pass null for \a.
61  */
62 AutomationTimeAxisView::AutomationTimeAxisView (
63         Session* s,
64         boost::shared_ptr<Route> r,
65         boost::shared_ptr<Automatable> a,
66         boost::shared_ptr<AutomationControl> c,
67         PublicEditor& e,
68         TimeAxisView& parent,
69         bool show_regions,
70         ArdourCanvas::Canvas& canvas,
71         const string & nom,
72         const string & nomparent
73         )
74         : AxisView (s)
75         , TimeAxisView (s, e, &parent, canvas)
76         , _route (r)
77         , _control (c)
78         , _automatable (a)
79         , _controller (AutomationController::create (a, c->parameter(), c))
80         , _base_rect (0)
81         , _view (show_regions ? new AutomationStreamView (*this) : 0)
82         , _name (nom)
83         , auto_button (X_("")) /* force addition of a label */
84 {
85         if (!have_name_font) {
86                 name_font = get_font_for_style (X_("AutomationTrackName"));
87                 have_name_font = true;
88         }
89
90         automation_menu = 0;
91         auto_off_item = 0;
92         auto_touch_item = 0;
93         auto_write_item = 0;
94         auto_play_item = 0;
95         mode_discrete_item = 0;
96         mode_line_item = 0;
97
98         ignore_state_request = false;
99         first_call_to_set_height = true;
100
101         _base_rect = new SimpleRect(*_canvas_display);
102         _base_rect->property_x1() = 0.0;
103         _base_rect->property_y1() = 0.0;
104         /** gnomecanvas sometimes converts this value to int or adds 2 to it, so it must be
105             set correctly to avoid overflow.
106         */
107         _base_rect->property_x2() = INT_MAX - 2;
108         _base_rect->property_outline_color_rgba() = ARDOUR_UI::config()->canvasvar_AutomationTrackOutline.get();
109
110         /* outline ends and bottom */
111         _base_rect->property_outline_what() = (guint32) (0x1|0x2|0x8);
112         _base_rect->property_fill_color_rgba() = ARDOUR_UI::config()->canvasvar_AutomationTrackFill.get();
113
114         _base_rect->set_data ("trackview", this);
115
116         _base_rect->signal_event().connect (sigc::bind (
117                         sigc::mem_fun (_editor, &PublicEditor::canvas_automation_track_event),
118                         _base_rect, this));
119
120         if (!a) {
121                 _base_rect->lower_to_bottom();
122         }
123
124         hide_button.add (*(manage (new Gtk::Image (::get_icon("hide")))));
125
126         auto_button.set_name ("TrackVisualButton");
127         hide_button.set_name ("TrackRemoveButton");
128
129         auto_button.unset_flags (Gtk::CAN_FOCUS);
130         hide_button.unset_flags (Gtk::CAN_FOCUS);
131
132         controls_table.set_no_show_all();
133
134         ARDOUR_UI::instance()->set_tip(auto_button, _("automation state"));
135         ARDOUR_UI::instance()->set_tip(hide_button, _("hide track"));
136
137         /* rearrange the name display */
138
139         /* we never show these for automation tracks, so make
140            life easier and remove them.
141         */
142
143         hide_name_entry();
144
145         /* move the name label over a bit */
146
147         string shortpname = _name;
148         bool shortened = false;
149
150         int ignore_width;
151         shortpname = fit_to_pixels (_name, 60, *name_font, ignore_width, true);
152
153         if (shortpname != _name ){
154                 shortened = true;
155         }
156
157         name_label.set_text (shortpname);
158         name_label.set_alignment (Gtk::ALIGN_CENTER, Gtk::ALIGN_CENTER);
159         name_label.set_name (X_("TrackParameterName"));
160
161         if (nomparent.length()) {
162
163                 /* limit the plug name string */
164
165                 string pname = fit_to_pixels (nomparent, 60, *name_font, ignore_width, true);
166                 if (pname != nomparent) {
167                         shortened = true;
168                 }
169
170                 plugname = new Label (pname);
171                 plugname->set_name (X_("TrackPlugName"));
172                 plugname->show();
173                 controls_table.remove (name_hbox);
174                 controls_table.attach (*plugname, 1, 5, 0, 1, Gtk::FILL|Gtk::EXPAND, Gtk::FILL|Gtk::EXPAND);
175                 plugname_packed = true;
176                 controls_table.attach (name_hbox, 1, 5, 1, 2, Gtk::FILL|Gtk::EXPAND, Gtk::FILL|Gtk::EXPAND);
177         } else {
178                 plugname = 0;
179                 plugname_packed = false;
180         }
181
182         if (shortened) {
183                 string tipname = nomparent;
184                 if (!tipname.empty()) {
185                         tipname += ": ";
186                 }
187                 tipname += _name;
188                 ARDOUR_UI::instance()->set_tip(controls_ebox, tipname);
189         }
190
191         /* add the buttons */
192         controls_table.attach (hide_button, 0, 1, 0, 1, Gtk::FILL|Gtk::EXPAND, Gtk::FILL|Gtk::EXPAND);
193
194         controls_table.attach (auto_button, 5, 8, 0, 1, Gtk::FILL|Gtk::EXPAND, Gtk::FILL|Gtk::EXPAND);
195
196         /* add bar controller */
197         controls_table.attach (*_controller.get(), 0, 8, 1, 2, Gtk::FILL|Gtk::EXPAND, Gtk::FILL|Gtk::EXPAND);
198
199         controls_table.show_all ();
200
201         hide_button.signal_clicked().connect (sigc::mem_fun(*this, &AutomationTimeAxisView::hide_clicked));
202         auto_button.signal_clicked().connect (sigc::mem_fun(*this, &AutomationTimeAxisView::auto_clicked));
203
204         controls_base_selected_name = X_("AutomationTrackControlsBaseSelected");
205         controls_base_unselected_name = X_("AutomationTrackControlsBase");
206         controls_ebox.set_name (controls_base_unselected_name);
207
208         XMLNode* xml_node = get_parent_with_state()->get_automation_child_xml_node (_control->parameter());
209
210         if (xml_node) {
211                 set_state (*xml_node, Stateful::loading_state_version);
212         }
213
214         /* ask for notifications of any new RegionViews */
215         if (show_regions) {
216
217                 assert(_view);
218                 _view->attach ();
219
220         } else {
221                 /* no regions, just a single line for the entire track (e.g. bus gain) */
222                 
223                 boost::shared_ptr<AutomationLine> line (
224                         new AutomationLine (
225                                 ARDOUR::EventTypeMap::instance().to_symbol(_control->parameter()),
226                                 *this,
227                                 *_canvas_display,
228                                 _control->alist()
229                                 )
230                         );
231
232                 line->set_line_color (ARDOUR_UI::config()->canvasvar_ProcessorAutomationLine.get());
233                 line->queue_reset ();
234                 add_line (line);
235         }
236
237         /* make sure labels etc. are correct */
238
239         automation_state_changed ();
240         ColorsChanged.connect (sigc::mem_fun (*this, &AutomationTimeAxisView::color_handler));
241 }
242
243 AutomationTimeAxisView::~AutomationTimeAxisView ()
244 {
245 }
246
247 void
248 AutomationTimeAxisView::auto_clicked ()
249 {
250         using namespace Menu_Helpers;
251
252         if (automation_menu == 0) {
253                 automation_menu = manage (new Menu);
254                 automation_menu->set_name ("ArdourContextMenu");
255                 MenuList& items (automation_menu->items());
256
257                 items.push_back (MenuElem (_("Manual"), sigc::bind (sigc::mem_fun(*this,
258                                 &AutomationTimeAxisView::set_automation_state), (AutoState) Off)));
259                 items.push_back (MenuElem (_("Play"), sigc::bind (sigc::mem_fun(*this,
260                                 &AutomationTimeAxisView::set_automation_state), (AutoState) Play)));
261                 items.push_back (MenuElem (_("Write"), sigc::bind (sigc::mem_fun(*this,
262                                 &AutomationTimeAxisView::set_automation_state), (AutoState) Write)));
263                 items.push_back (MenuElem (_("Touch"), sigc::bind (sigc::mem_fun(*this,
264                                 &AutomationTimeAxisView::set_automation_state), (AutoState) Touch)));
265         }
266
267         automation_menu->popup (1, gtk_get_current_event_time());
268 }
269
270 void
271 AutomationTimeAxisView::set_automation_state (AutoState state)
272 {
273         if (ignore_state_request) {
274                 return;
275         }
276
277         if (_automatable) {
278                 _automatable->set_parameter_automation_state (_control->parameter(), state);
279         }
280 #if 0
281         if (_route == _automatable) { // This is a time axis for route (not region) automation
282                 _route->set_parameter_automation_state (_control->parameter(), state);
283         }
284         
285         if (_control->list()) {
286                 _control->alist()->set_automation_state(state);
287         }
288 #endif
289         if (_view) {
290                 _view->set_automation_state (state);
291                 
292                 /* AutomationStreamViews don't signal when their automation state changes, so handle
293                    our updates `manually'.
294                 */
295                 automation_state_changed ();
296         }
297 }
298
299 void
300 AutomationTimeAxisView::automation_state_changed ()
301 {
302         AutoState state;
303
304         /* update button label */
305
306         if (_line) {
307                 state = _control->alist()->automation_state ();
308         } else if (_view) {
309                 state = _view->automation_state ();
310         } else {
311                 state = Off;
312         }
313
314         switch (state & (Off|Play|Touch|Write)) {
315         case Off:
316                 auto_button.set_label (_("Manual"));
317                 if (auto_off_item) {
318                         ignore_state_request = true;
319                         auto_off_item->set_active (true);
320                         auto_play_item->set_active (false);
321                         auto_touch_item->set_active (false);
322                         auto_write_item->set_active (false);
323                         ignore_state_request = false;
324                 }
325                 break;
326         case Play:
327                 auto_button.set_label (_("Play"));
328                 if (auto_play_item) {
329                         ignore_state_request = true;
330                         auto_play_item->set_active (true);
331                         auto_off_item->set_active (false);
332                         auto_touch_item->set_active (false);
333                         auto_write_item->set_active (false);
334                         ignore_state_request = false;
335                 }
336                 break;
337         case Write:
338                 auto_button.set_label (_("Write"));
339                 if (auto_write_item) {
340                         ignore_state_request = true;
341                         auto_write_item->set_active (true);
342                         auto_off_item->set_active (false);
343                         auto_play_item->set_active (false);
344                         auto_touch_item->set_active (false);
345                         ignore_state_request = false;
346                 }
347                 break;
348         case Touch:
349                 auto_button.set_label (_("Touch"));
350                 if (auto_touch_item) {
351                         ignore_state_request = true;
352                         auto_touch_item->set_active (true);
353                         auto_off_item->set_active (false);
354                         auto_play_item->set_active (false);
355                         auto_write_item->set_active (false);
356                         ignore_state_request = false;
357                 }
358                 break;
359         default:
360                 auto_button.set_label (_("???"));
361                 break;
362         }
363 }
364
365 /** The interpolation style of our AutomationList has changed, so update */
366 void
367 AutomationTimeAxisView::interpolation_changed (AutomationList::InterpolationStyle s)
368 {
369         if (mode_line_item && mode_discrete_item) {
370                 if (s == AutomationList::Discrete) {
371                         mode_discrete_item->set_active(true);
372                         mode_line_item->set_active(false);
373                 } else {
374                         mode_line_item->set_active(true);
375                         mode_discrete_item->set_active(false);
376                 }
377         }
378 }
379
380 /** A menu item has been selected to change our interpolation mode */
381 void
382 AutomationTimeAxisView::set_interpolation (AutomationList::InterpolationStyle style)
383 {
384         /* Tell our view's list, if we have one, otherwise tell our own.
385          * Everything else will be signalled back from that.
386          */
387         
388         if (_view) {
389                 _view->set_interpolation (style);
390         } else {
391                 _control->list()->set_interpolation (style);
392         }
393 }
394
395 void
396 AutomationTimeAxisView::clear_clicked ()
397 {
398         assert (_line || _view);
399         
400         _session->begin_reversible_command (_("clear automation"));
401         
402         if (_line) {
403                 _line->clear ();
404         } else if (_view) {
405                 _view->clear ();
406         }
407
408         _session->commit_reversible_command ();
409         _session->set_dirty ();
410 }
411
412 void
413 AutomationTimeAxisView::set_height (uint32_t h)
414 {
415         bool const changed = (height != (uint32_t) h) || first_call_to_set_height;
416         uint32_t const normal = preset_height (HeightNormal);
417         bool const changed_between_small_and_normal = ( (height < normal && h >= normal) || (height >= normal || h < normal) );
418
419         TimeAxisView* state_parent = get_parent_with_state ();
420         assert(state_parent);
421         XMLNode* xml_node = state_parent->get_automation_child_xml_node (_control->parameter());
422
423         TimeAxisView::set_height (h);
424         _base_rect->property_y2() = h;
425
426         if (_line) {
427                 _line->set_height(h);
428         }
429
430         if (_view) {
431                 _view->set_height(h);
432                 _view->update_contents_height();
433         }
434
435         char buf[32];
436         snprintf (buf, sizeof (buf), "%u", height);
437         if (xml_node) {
438                 xml_node->add_property ("height", buf);
439         }
440
441         if (changed_between_small_and_normal || first_call_to_set_height) {
442
443                 first_call_to_set_height = false;
444
445                 if (h >= preset_height (HeightNormal)) {
446                         controls_table.remove (name_hbox);
447
448                         if (plugname) {
449                                 if (plugname_packed) {
450                                         controls_table.remove (*plugname);
451                                         plugname_packed = false;
452                                 }
453                                 controls_table.attach (*plugname, 1, 5, 0, 1, Gtk::FILL|Gtk::EXPAND, Gtk::FILL|Gtk::EXPAND);
454                                 plugname_packed = true;
455                                 controls_table.attach (name_hbox, 1, 5, 1, 2, Gtk::FILL|Gtk::EXPAND, Gtk::FILL|Gtk::EXPAND);
456                         } else {
457                                 controls_table.attach (name_hbox, 1, 5, 0, 1, Gtk::FILL|Gtk::EXPAND, Gtk::FILL|Gtk::EXPAND);
458                         }
459                         hide_name_entry ();
460                         show_name_label ();
461                         name_hbox.show_all ();
462
463                         auto_button.show();
464                         hide_button.show_all();
465
466                 } else if (h >= preset_height (HeightSmall)) {
467                         controls_table.remove (name_hbox);
468                         if (plugname) {
469                                 if (plugname_packed) {
470                                         controls_table.remove (*plugname);
471                                         plugname_packed = false;
472                                 }
473                         }
474                         controls_table.attach (name_hbox, 1, 5, 0, 1, Gtk::FILL|Gtk::EXPAND, Gtk::FILL|Gtk::EXPAND);
475                         controls_table.hide_all ();
476                         hide_name_entry ();
477                         show_name_label ();
478                         name_hbox.show_all ();
479
480                         auto_button.hide();
481                         hide_button.hide();
482                 }
483         } else if (h >= preset_height (HeightNormal)) {
484                 cerr << "track grown, but neither changed_between_small_and_normal nor first_call_to_set_height set!" << endl;
485         }
486
487         if (changed) {
488                 if (canvas_item_visible (_canvas_display)) {
489                         /* only emit the signal if the height really changed and we were visible */
490                         _route->gui_changed ("visible_tracks", (void *) 0); /* EMIT_SIGNAL */
491                 }
492         }
493 }
494
495 void
496 AutomationTimeAxisView::set_samples_per_unit (double spu)
497 {
498         TimeAxisView::set_samples_per_unit (spu);
499
500         if (_line) {
501                 _line->reset ();
502         }
503
504         if (_view) {
505                 _view->set_samples_per_unit (spu);
506         }
507 }
508
509 void
510 AutomationTimeAxisView::hide_clicked ()
511 {
512         // LAME fix for refreshing the hide button
513         hide_button.set_sensitive(false);
514
515         set_marked_for_display (false);
516         hide ();
517
518         hide_button.set_sensitive(true);
519 }
520
521 void
522 AutomationTimeAxisView::build_display_menu ()
523 {
524         using namespace Menu_Helpers;
525
526         /* prepare it */
527
528         TimeAxisView::build_display_menu ();
529
530         /* now fill it with our stuff */
531
532         MenuList& items = display_menu->items();
533
534         items.push_back (MenuElem (_("Hide"), sigc::mem_fun(*this, &AutomationTimeAxisView::hide_clicked)));
535         items.push_back (SeparatorElem());
536         items.push_back (MenuElem (_("Clear"), sigc::mem_fun(*this, &AutomationTimeAxisView::clear_clicked)));
537         items.push_back (SeparatorElem());
538
539         /* state menu */
540
541         Menu* auto_state_menu = manage (new Menu);
542         auto_state_menu->set_name ("ArdourContextMenu");
543         MenuList& as_items = auto_state_menu->items();
544
545         as_items.push_back (CheckMenuElem (_("Manual"), sigc::bind (
546                         sigc::mem_fun(*this, &AutomationTimeAxisView::set_automation_state),
547                         (AutoState) Off)));
548         auto_off_item = dynamic_cast<CheckMenuItem*>(&as_items.back());
549
550         as_items.push_back (CheckMenuElem (_("Play"), sigc::bind (
551                         sigc::mem_fun(*this, &AutomationTimeAxisView::set_automation_state),
552                         (AutoState) Play)));
553         auto_play_item = dynamic_cast<CheckMenuItem*>(&as_items.back());
554
555         as_items.push_back (CheckMenuElem (_("Write"), sigc::bind (
556                         sigc::mem_fun(*this, &AutomationTimeAxisView::set_automation_state),
557                         (AutoState) Write)));
558         auto_write_item = dynamic_cast<CheckMenuItem*>(&as_items.back());
559
560         as_items.push_back (CheckMenuElem (_("Touch"), sigc::bind (
561                         sigc::mem_fun(*this, &AutomationTimeAxisView::set_automation_state),
562                         (AutoState) Touch)));
563         auto_touch_item = dynamic_cast<CheckMenuItem*>(&as_items.back());
564
565         items.push_back (MenuElem (_("State"), *auto_state_menu));
566
567         /* mode menu */
568
569         /* current interpolation state */
570         AutomationList::InterpolationStyle const s = _view ? _view->interpolation() : _control->list()->interpolation ();
571
572         if (EventTypeMap::instance().is_midi_parameter(_control->parameter())) {
573
574                 Menu* auto_mode_menu = manage (new Menu);
575                 auto_mode_menu->set_name ("ArdourContextMenu");
576                 MenuList& am_items = auto_mode_menu->items();
577
578                 RadioMenuItem::Group group;
579
580                 am_items.push_back (RadioMenuElem (group, _("Discrete"), sigc::bind (
581                                 sigc::mem_fun(*this, &AutomationTimeAxisView::set_interpolation),
582                                 AutomationList::Discrete)));
583                 mode_discrete_item = dynamic_cast<CheckMenuItem*>(&am_items.back());
584                 mode_discrete_item->set_active (s == AutomationList::Discrete);
585
586                 am_items.push_back (RadioMenuElem (group, _("Linear"), sigc::bind (
587                                 sigc::mem_fun(*this, &AutomationTimeAxisView::set_interpolation),
588                                 AutomationList::Linear)));
589                 mode_line_item = dynamic_cast<CheckMenuItem*>(&am_items.back());
590                 mode_line_item->set_active (s == AutomationList::Linear);
591
592                 items.push_back (MenuElem (_("Mode"), *auto_mode_menu));
593         }
594
595         /* make sure the automation menu state is correct */
596
597         automation_state_changed ();
598         interpolation_changed (s);
599 }
600
601 void
602 AutomationTimeAxisView::add_automation_event (ArdourCanvas::Item* /*item*/, GdkEvent* /*event*/, framepos_t when, double y)
603 {
604         if (!_line) {
605                 return;
606         }
607
608         double x = 0;
609
610         _canvas_display->w2i (x, y);
611
612         /* compute vertical fractional position */
613
614         y = 1.0 - (y / height);
615
616         /* map using line */
617
618         _line->view_to_model_coord (x, y);
619
620         _session->begin_reversible_command (_("add automation event"));
621         XMLNode& before = _control->alist()->get_state();
622
623         _control->alist()->add (when, y);
624
625         XMLNode& after = _control->alist()->get_state();
626         _session->commit_reversible_command (new MementoCommand<ARDOUR::AutomationList>(*_control->alist(), &before, &after));
627
628         _session->set_dirty ();
629 }
630
631 void
632 AutomationTimeAxisView::cut_copy_clear (Selection& selection, CutCopyOp op)
633 {
634         list<boost::shared_ptr<AutomationLine> > lines;
635         if (_line) {
636                 lines.push_back (_line);
637         } else if (_view) {
638                 lines = _view->get_lines ();
639         }
640
641         for (list<boost::shared_ptr<AutomationLine> >::iterator i = lines.begin(); i != lines.end(); ++i) {
642                 cut_copy_clear_one (**i, selection, op);
643         }
644 }
645
646 void
647 AutomationTimeAxisView::cut_copy_clear_one (AutomationLine& line, Selection& selection, CutCopyOp op)
648 {
649         boost::shared_ptr<Evoral::ControlList> what_we_got;
650         boost::shared_ptr<AutomationList> alist (line.the_list());
651
652         XMLNode &before = alist->get_state();
653
654         /* convert time selection to automation list model coordinates */
655         const Evoral::TimeConverter<double, ARDOUR::framepos_t>& tc = line.time_converter ();
656         double const start = tc.from (selection.time.front().start - tc.origin_b ());
657         double const end = tc.from (selection.time.front().end - tc.origin_b ());
658         
659         switch (op) {
660         case Cut:
661                 
662                 if ((what_we_got = alist->cut (start, end)) != 0) {
663                         _editor.get_cut_buffer().add (what_we_got);
664                         _session->add_command(new MementoCommand<AutomationList>(*alist.get(), &before, &alist->get_state()));
665                 }
666                 break;
667         case Copy:
668                 if ((what_we_got = alist->copy (start, end)) != 0) {
669                         _editor.get_cut_buffer().add (what_we_got);
670                 }
671                 break;
672
673         case Clear:
674                 if ((what_we_got = alist->cut (start, end)) != 0) {
675                         _session->add_command(new MementoCommand<AutomationList>(*alist.get(), &before, &alist->get_state()));
676                 }
677                 break;
678         }
679
680         if (what_we_got) {
681                 for (AutomationList::iterator x = what_we_got->begin(); x != what_we_got->end(); ++x) {
682                         double when = (*x)->when;
683                         double val  = (*x)->value;
684                         line.model_to_view_coord (when, val);
685                         (*x)->when = when;
686                         (*x)->value = val;
687                 }
688         }
689 }
690
691 void
692 AutomationTimeAxisView::reset_objects (PointSelection& selection)
693 {
694         list<boost::shared_ptr<AutomationLine> > lines;
695         if (_line) {
696                 lines.push_back (_line);
697         } else if (_view) {
698                 lines = _view->get_lines ();
699         }
700
701         for (list<boost::shared_ptr<AutomationLine> >::iterator i = lines.begin(); i != lines.end(); ++i) {
702                 reset_objects_one (**i, selection);
703         }
704 }
705
706 void
707 AutomationTimeAxisView::reset_objects_one (AutomationLine& line, PointSelection& selection)
708 {
709         boost::shared_ptr<AutomationList> alist(line.the_list());
710
711         _session->add_command (new MementoCommand<AutomationList>(*alist.get(), &alist->get_state(), 0));
712
713         for (PointSelection::iterator i = selection.begin(); i != selection.end(); ++i) {
714
715                 if ((*i).track != this) {
716                         continue;
717                 }
718
719                 alist->reset_range ((*i).start, (*i).end);
720         }
721 }
722
723 void
724 AutomationTimeAxisView::cut_copy_clear_objects (PointSelection& selection, CutCopyOp op)
725 {
726         list<boost::shared_ptr<AutomationLine> > lines;
727         if (_line) {
728                 lines.push_back (_line);
729         } else if (_view) {
730                 lines = _view->get_lines ();
731         }
732
733         for (list<boost::shared_ptr<AutomationLine> >::iterator i = lines.begin(); i != lines.end(); ++i) {
734                 cut_copy_clear_objects_one (**i, selection, op);
735         }
736 }
737
738 void
739 AutomationTimeAxisView::cut_copy_clear_objects_one (AutomationLine& line, PointSelection& selection, CutCopyOp op)
740 {
741         boost::shared_ptr<Evoral::ControlList> what_we_got;
742         boost::shared_ptr<AutomationList> alist(line.the_list());
743
744         XMLNode &before = alist->get_state();
745
746         for (PointSelection::iterator i = selection.begin(); i != selection.end(); ++i) {
747
748                 if ((*i).track != this) {
749                         continue;
750                 }
751
752                 switch (op) {
753                 case Cut:
754                         if ((what_we_got = alist->cut ((*i).start, (*i).end)) != 0) {
755                                 _editor.get_cut_buffer().add (what_we_got);
756                                 _session->add_command (new MementoCommand<AutomationList>(*alist.get(), new XMLNode (before), &alist->get_state()));
757                         }
758                         break;
759                 case Copy:
760                         if ((what_we_got = alist->copy ((*i).start, (*i).end)) != 0) {
761                                 _editor.get_cut_buffer().add (what_we_got);
762                         }
763                         break;
764
765                 case Clear:
766                         if ((what_we_got = alist->cut ((*i).start, (*i).end)) != 0) {
767                                 _session->add_command (new MementoCommand<AutomationList>(*alist.get(), new XMLNode (before), &alist->get_state()));
768                         }
769                         break;
770                 }
771         }
772
773         delete &before;
774
775         if (what_we_got) {
776                 for (AutomationList::iterator x = what_we_got->begin(); x != what_we_got->end(); ++x) {
777                         double when = (*x)->when;
778                         double val  = (*x)->value;
779                         line.model_to_view_coord (when, val);
780                         (*x)->when = when;
781                         (*x)->value = val;
782                 }
783         }
784 }
785
786 /** Paste a selection.
787  *  @param pos Position to paste to (session frames).
788  *  @param times Number of times to paste.
789  *  @param selection Selection to paste.
790  *  @param nth Index of the AutomationList within the selection to paste from.
791  */
792 bool
793 AutomationTimeAxisView::paste (framepos_t pos, float times, Selection& selection, size_t nth)
794 {
795         boost::shared_ptr<AutomationLine> line;
796         
797         if (_line) {
798                 line = _line;
799         } else if (_view) {
800                 line = _view->paste_line (pos);
801         }
802
803         if (!line) {
804                 return false;
805         }
806         
807         return paste_one (*line, pos, times, selection, nth);
808 }
809
810 bool
811 AutomationTimeAxisView::paste_one (AutomationLine& line, framepos_t pos, float times, Selection& selection, size_t nth)
812 {
813         AutomationSelection::iterator p;
814         boost::shared_ptr<AutomationList> alist(line.the_list());
815
816         for (p = selection.lines.begin(); p != selection.lines.end() && nth; ++p, --nth) {}
817
818         if (p == selection.lines.end()) {
819                 return false;
820         }
821
822         /* Make a copy of the list because we have to scale the
823            values from view coordinates to model coordinates, and we're
824            not supposed to modify the points in the selection.
825         */
826
827         AutomationList copy (**p);
828
829         for (AutomationList::iterator x = copy.begin(); x != copy.end(); ++x) {
830                 double when = (*x)->when;
831                 double val  = (*x)->value;
832                 line.view_to_model_coord (when, val);
833                 (*x)->when = when;
834                 (*x)->value = val;
835         }
836
837         double const model_pos = line.time_converter().from (pos - line.time_converter().origin_b ());
838
839         XMLNode &before = alist->get_state();
840         alist->paste (copy, model_pos, times);
841         _session->add_command (new MementoCommand<AutomationList>(*alist.get(), &before, &alist->get_state()));
842
843         return true;
844 }
845
846 void
847 AutomationTimeAxisView::get_selectables (framepos_t start, framepos_t end, double top, double bot, list<Selectable*>& results)
848 {
849         if (!_line && !_view) {
850                 return;
851         }
852
853         if (touched (top, bot)) {
854
855                 /* remember: this is X Window - coordinate space starts in upper left and moves down.
856                    _y_position is the "origin" or "top" of the track.
857                 */
858
859                 /* bottom of our track */
860                 double const mybot = _y_position + height;
861
862                 double topfrac;
863                 double botfrac;
864
865                 if (_y_position >= top && mybot <= bot) {
866
867                         /* _y_position is below top, mybot is above bot, so we're fully
868                            covered vertically.
869                         */
870
871                         topfrac = 1.0;
872                         botfrac = 0.0;
873
874                 } else {
875
876                         /* top and bot are within _y_position .. mybot */
877
878                         topfrac = 1.0 - ((top - _y_position) / height);
879                         botfrac = 1.0 - ((bot - _y_position) / height);
880
881                 }
882
883                 if (_line) {
884                         _line->get_selectables (start, end, botfrac, topfrac, results);
885                 } else if (_view) {
886                         _view->get_selectables (start, end, botfrac, topfrac, results);
887                 }
888         }
889 }
890
891 void
892 AutomationTimeAxisView::get_inverted_selectables (Selection& sel, list<Selectable*>& result)
893 {
894         if (_line) {
895                 _line->get_inverted_selectables (sel, result);
896         }
897 }
898
899 void
900 AutomationTimeAxisView::set_selected_points (PointSelection& points)
901 {
902         if (_line) {
903                 _line->set_selected_points (points);
904         } else if (_view) {
905                 _view->set_selected_points (points);
906         }
907 }
908
909 void
910 AutomationTimeAxisView::clear_lines ()
911 {
912         _line.reset();
913         _list_connections.drop_connections ();
914 }
915
916 void
917 AutomationTimeAxisView::add_line (boost::shared_ptr<AutomationLine> line)
918 {
919         assert(line);
920         assert(!_line);
921         assert(line->the_list() == _control->list());
922
923         _control->alist()->automation_state_changed.connect (
924                 _list_connections, invalidator (*this), boost::bind (&AutomationTimeAxisView::automation_state_changed, this), gui_context()
925                 );
926         
927         _control->alist()->InterpolationChanged.connect (
928                 _list_connections, invalidator (*this), boost::bind (&AutomationTimeAxisView::interpolation_changed, this, _1), gui_context()
929                 );
930
931         _line = line;
932         //_controller = AutomationController::create(_session, line->the_list(), _control);
933
934         line->set_height (height);
935
936         /* pick up the current state */
937         automation_state_changed ();
938
939         line->show();
940 }
941
942 void
943 AutomationTimeAxisView::entered()
944 {
945         if (_line) {
946                 _line->track_entered();
947         }
948 }
949
950 void
951 AutomationTimeAxisView::exited ()
952 {
953         if (_line) {
954                 _line->track_exited();
955         }
956 }
957
958 void
959 AutomationTimeAxisView::color_handler ()
960 {
961         if (_line) {
962                 _line->set_colors();
963         }
964 }
965
966 int
967 AutomationTimeAxisView::set_state (const XMLNode& node, int version)
968 {
969         TimeAxisView::set_state (node, version);
970
971         if (version < 3000) {
972                 return set_state_2X (node, version);
973         }
974         
975         XMLProperty const * type = node.property ("automation-id");
976         if (type && type->value () == ARDOUR::EventTypeMap::instance().to_symbol (_control->parameter())) {
977                 XMLProperty const * shown = node.property ("shown");
978                 if (shown && shown->value () == "yes") {
979                         set_marked_for_display (true);
980                         _canvas_display->show (); /* FIXME: necessary? show_at? */
981                 }
982         }
983
984         if (!_marked_for_display) {
985                 hide();
986         }
987
988         return 0;
989 }
990
991 int
992
993 AutomationTimeAxisView::set_state_2X (const XMLNode& node, int /*version*/)
994 {
995         if (node.name() == X_("gain") && _control->parameter() == Evoral::Parameter (GainAutomation)) {
996                 XMLProperty const * shown = node.property (X_("shown"));
997                 if (shown && string_is_affirmative (shown->value ())) {
998                         set_marked_for_display (true);
999                         _canvas_display->show (); /* FIXME: necessary? show_at? */
1000                 }
1001         }
1002
1003         if (!_marked_for_display) {
1004                 hide ();
1005         }
1006
1007         return 0;
1008 }
1009
1010 XMLNode*
1011 AutomationTimeAxisView::get_state_node ()
1012 {
1013         TimeAxisView* state_parent = get_parent_with_state ();
1014
1015         if (state_parent) {
1016                 return state_parent->get_automation_child_xml_node (_control->parameter());
1017         } else {
1018                 return 0;
1019         }
1020 }
1021
1022 void
1023 AutomationTimeAxisView::update_extra_xml_shown (bool editor_shown)
1024 {
1025         XMLNode* xml_node = get_state_node();
1026         if (xml_node) {
1027                 xml_node->add_property ("shown", editor_shown ? "yes" : "no");
1028         }
1029 }
1030
1031 guint32
1032 AutomationTimeAxisView::show_at (double y, int& nth, Gtk::VBox *parent)
1033 {
1034         update_extra_xml_shown (true);
1035
1036         return TimeAxisView::show_at (y, nth, parent);
1037 }
1038
1039 void
1040 AutomationTimeAxisView::hide ()
1041 {
1042         update_extra_xml_shown (false);
1043
1044         TimeAxisView::hide ();
1045 }
1046
1047 bool
1048 AutomationTimeAxisView::set_visibility (bool yn)
1049 {
1050         bool changed = TimeAxisView::set_visibility (yn);
1051
1052         if (changed) {
1053                 get_state_node()->add_property ("shown", yn ? X_("yes") : X_("no"));
1054         }
1055
1056         return changed;
1057 }
1058
1059 /** @return true if this view has any automation data to display */
1060 bool
1061 AutomationTimeAxisView::has_automation () const
1062 {
1063         return ( (_line && _line->npoints() > 0) || (_view && _view->has_automation()) );
1064 }
1065
1066 list<boost::shared_ptr<AutomationLine> >
1067 AutomationTimeAxisView::lines () const
1068 {
1069         list<boost::shared_ptr<AutomationLine> > lines;
1070         
1071         if (_line) {
1072                 lines.push_back (_line);
1073         } else if (_view) {
1074                 lines = _view->get_lines ();
1075         }
1076
1077         return lines;
1078 }