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