2 * Copyright (C) 2005-2018 Paul Davis <paul@linuxaudiosystems.com>
3 * Copyright (C) 2005 Taybin Rutkin <taybin@taybin.com>
4 * Copyright (C) 2006-2012 David Robillard <d@drobilla.net>
5 * Copyright (C) 2010-2012 Carl Hetherington <carl@carlh.net>
6 * Copyright (C) 2013-2019 Robin Gareus <robin@gareus.org>
7 * Copyright (C) 2014-2015 Nick Mainsbridge <mainsbridge@gmail.com>
9 * This program is free software; you can redistribute it and/or modify
10 * it under the terms of the GNU General Public License as published by
11 * the Free Software Foundation; either version 2 of the License, or
12 * (at your option) any later version.
14 * This program is distributed in the hope that it will be useful,
15 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 * GNU General Public License for more details.
19 * You should have received a copy of the GNU General Public License along
20 * with this program; if not, write to the Free Software Foundation, Inc.,
21 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
26 #include <gtkmm/listviewtext.h>
27 #include <gtkmm/stock.h>
29 #include "pbd/memento_command.h"
30 #include "pbd/stateful_diff_command.h"
32 #include "widgets/tooltips.h"
34 #include "ardour/region.h"
35 #include "ardour/session.h"
36 #include "ardour/source.h"
38 #include "ardour_ui.h"
39 #include "clock_group.h"
40 #include "main_clock.h"
41 #include "gui_thread.h"
42 #include "region_editor.h"
43 #include "public_editor.h"
47 using namespace ARDOUR;
50 using namespace Gtkmm2ext;
52 RegionEditor::RegionEditor (Session* s, boost::shared_ptr<Region> r)
53 : ArdourDialog (_("Region"))
57 , name_label (_("Name:"))
58 , audition_button (_("Audition"))
59 , _clock_group (new ClockGroup)
60 , position_clock (X_("regionposition"), true, "", true, false)
61 , end_clock (X_("regionend"), true, "", true, false)
62 , length_clock (X_("regionlength"), true, "", true, false, true)
63 , sync_offset_relative_clock (X_("regionsyncoffsetrelative"), true, "", true, false)
64 , sync_offset_absolute_clock (X_("regionsyncoffsetabsolute"), true, "", true, false)
65 /* XXX cannot file start yet */
66 , start_clock (X_("regionstart"), true, "", false, false)
71 _clock_group->set_clock_mode (ARDOUR_UI::instance()->primary_clock->mode());
72 ARDOUR_UI::instance()->primary_clock->mode_changed.connect (sigc::mem_fun (*this, &RegionEditor::set_clock_mode_from_primary));
74 _clock_group->add (position_clock);
75 _clock_group->add (end_clock);
76 _clock_group->add (length_clock);
77 _clock_group->add (sync_offset_relative_clock);
78 _clock_group->add (sync_offset_absolute_clock);
79 _clock_group->add (start_clock);
81 position_clock.set_session (_session);
82 end_clock.set_session (_session);
83 length_clock.set_session (_session);
84 sync_offset_relative_clock.set_session (_session);
85 sync_offset_absolute_clock.set_session (_session);
86 start_clock.set_session (_session);
88 ArdourWidgets::set_tooltip (audition_button, _("audition this region"));
90 audition_button.unset_flags (Gtk::CAN_FOCUS);
92 audition_button.set_events (audition_button.get_events() & ~(Gdk::ENTER_NOTIFY_MASK|Gdk::LEAVE_NOTIFY_MASK));
94 name_entry.set_name ("RegionEditorEntry");
95 name_label.set_name ("RegionEditorLabel");
96 position_label.set_name ("RegionEditorLabel");
97 position_label.set_text (_("Position:"));
98 end_label.set_name ("RegionEditorLabel");
99 end_label.set_text (_("End:"));
100 length_label.set_name ("RegionEditorLabel");
101 length_label.set_text (_("Length:"));
102 sync_relative_label.set_name ("RegionEditorLabel");
103 sync_relative_label.set_text (_("Sync point (relative to region):"));
104 sync_absolute_label.set_name ("RegionEditorLabel");
105 sync_absolute_label.set_text (_("Sync point (absolute):"));
106 start_label.set_name ("RegionEditorLabel");
107 start_label.set_text (_("File start:"));
108 _sources_label.set_name ("RegionEditorLabel");
110 if (_region->n_channels() > 1) {
111 _sources_label.set_text (_("Sources:"));
113 _sources_label.set_text (_("Source:"));
116 _table.set_col_spacings (12);
117 _table.set_row_spacings (6);
118 _table.set_border_width (12);
120 name_label.set_alignment (1, 0.5);
121 position_label.set_alignment (1, 0.5);
122 end_label.set_alignment (1, 0.5);
123 length_label.set_alignment (1, 0.5);
124 sync_relative_label.set_alignment (1, 0.5);
125 sync_absolute_label.set_alignment (1, 0.5);
126 start_label.set_alignment (1, 0.5);
127 _sources_label.set_alignment (1, 0.5);
129 Gtk::HBox* nb = Gtk::manage (new Gtk::HBox);
131 nb->pack_start (name_entry);
132 nb->pack_start (audition_button, false, false);
134 _table.attach (name_label, 0, 1, _table_row, _table_row + 1, Gtk::FILL, Gtk::FILL);
135 _table.attach (*nb, 1, 2, _table_row, _table_row + 1, Gtk::FILL | Gtk::EXPAND, Gtk::FILL);
138 _table.attach (position_label, 0, 1, _table_row, _table_row + 1, Gtk::FILL, Gtk::FILL);
139 _table.attach (position_clock, 1, 2, _table_row, _table_row + 1, Gtk::FILL | Gtk::EXPAND, Gtk::FILL);
142 _table.attach (end_label, 0, 1, _table_row, _table_row + 1, Gtk::FILL, Gtk::FILL);
143 _table.attach (end_clock, 1, 2, _table_row, _table_row + 1, Gtk::FILL | Gtk::EXPAND, Gtk::FILL);
146 _table.attach (length_label, 0, 1, _table_row, _table_row + 1, Gtk::FILL, Gtk::FILL);
147 _table.attach (length_clock, 1, 2, _table_row, _table_row + 1, Gtk::FILL | Gtk::EXPAND, Gtk::FILL);
150 _table.attach (sync_relative_label, 0, 1, _table_row, _table_row + 1, Gtk::FILL, Gtk::FILL);
151 _table.attach (sync_offset_relative_clock, 1, 2, _table_row, _table_row + 1, Gtk::FILL | Gtk::EXPAND, Gtk::FILL);
154 _table.attach (sync_absolute_label, 0, 1, _table_row, _table_row + 1, Gtk::FILL, Gtk::FILL);
155 _table.attach (sync_offset_absolute_clock, 1, 2, _table_row, _table_row + 1, Gtk::FILL | Gtk::EXPAND, Gtk::FILL);
158 _table.attach (start_label, 0, 1, _table_row, _table_row + 1, Gtk::FILL, Gtk::FILL);
159 _table.attach (start_clock, 1, 2, _table_row, _table_row + 1, Gtk::FILL | Gtk::EXPAND, Gtk::FILL);
162 _table.attach (_sources_label, 0, 1, _table_row, _table_row + 1, Gtk::FILL, Gtk::FILL);
163 _table.attach (_sources, 1, 2, _table_row, _table_row + 1, Gtk::FILL | Gtk::EXPAND, Gtk::FILL);
166 get_vbox()->pack_start (_table, true, true);
168 add_button (Gtk::Stock::CLOSE, Gtk::RESPONSE_ACCEPT);
170 set_name ("RegionEditorWindow");
171 add_events (Gdk::KEY_PRESS_MASK|Gdk::KEY_RELEASE_MASK);
173 signal_response().connect (sigc::mem_fun (*this, &RegionEditor::handle_response));
175 set_title (string_compose (_("Region '%1'"), _region->name()));
177 for (uint32_t i = 0; i < _region->n_channels(); ++i) {
178 _sources.append_text (_region->source(i)->name());
181 _sources.set_headers_visible (false);
182 Gtk::CellRendererText* t = dynamic_cast<Gtk::CellRendererText*> (_sources.get_column_cell_renderer(0));
184 t->property_ellipsize() = Pango::ELLIPSIZE_END;
190 PropertyChange change;
192 change.add (ARDOUR::Properties::start);
193 change.add (ARDOUR::Properties::length);
194 change.add (ARDOUR::Properties::position);
195 change.add (ARDOUR::Properties::sync_position);
197 bounds_changed (change);
199 _region->PropertyChanged.connect (state_connection, invalidator (*this), boost::bind (&RegionEditor::region_changed, this, _1), gui_context());
201 spin_arrow_grab = false;
203 connect_editor_events ();
206 RegionEditor::~RegionEditor ()
212 RegionEditor::set_clock_mode_from_primary ()
214 _clock_group->set_clock_mode (ARDOUR_UI::instance()->primary_clock->mode());
218 RegionEditor::region_changed (const PBD::PropertyChange& what_changed)
220 if (what_changed.contains (ARDOUR::Properties::name)) {
224 PropertyChange interesting_stuff;
226 interesting_stuff.add (ARDOUR::Properties::position);
227 interesting_stuff.add (ARDOUR::Properties::length);
228 interesting_stuff.add (ARDOUR::Properties::start);
229 interesting_stuff.add (ARDOUR::Properties::sync_position);
231 if (what_changed.contains (interesting_stuff)) {
232 bounds_changed (what_changed);
237 RegionEditor::bpressed (GdkEventButton* ev, Gtk::SpinButton* /*but*/, void (RegionEditor::*/*pmf*/)())
239 switch (ev->button) {
243 if (ev->type == GDK_BUTTON_PRESS) { /* no double clicks here */
244 if (!spin_arrow_grab) {
245 // GTK2FIX probably nuke the region editor
246 // if ((ev->window == but->gobj()->panel)) {
247 // spin_arrow_grab = true;
260 RegionEditor::breleased (GdkEventButton* /*ev*/, Gtk::SpinButton* /*but*/, void (RegionEditor::*pmf)())
262 if (spin_arrow_grab) {
264 spin_arrow_grab = false;
270 RegionEditor::connect_editor_events ()
272 name_entry.signal_changed().connect (sigc::mem_fun(*this, &RegionEditor::name_entry_changed));
274 position_clock.ValueChanged.connect (sigc::mem_fun(*this, &RegionEditor::position_clock_changed));
275 end_clock.ValueChanged.connect (sigc::mem_fun(*this, &RegionEditor::end_clock_changed));
276 length_clock.ValueChanged.connect (sigc::mem_fun(*this, &RegionEditor::length_clock_changed));
277 sync_offset_absolute_clock.ValueChanged.connect (sigc::mem_fun (*this, &RegionEditor::sync_offset_absolute_clock_changed));
278 sync_offset_relative_clock.ValueChanged.connect (sigc::mem_fun (*this, &RegionEditor::sync_offset_relative_clock_changed));
280 audition_button.signal_toggled().connect (sigc::mem_fun(*this, &RegionEditor::audition_button_toggled));
282 _session->AuditionActive.connect (audition_connection, invalidator (*this), boost::bind (&RegionEditor::audition_state_changed, this, _1), gui_context());
286 RegionEditor::position_clock_changed ()
288 bool in_command = false;
289 boost::shared_ptr<Playlist> pl = _region->playlist();
292 PublicEditor::instance().begin_reversible_command (_("change region start position"));
295 _region->clear_changes ();
296 _region->set_position (position_clock.current_time());
297 _session->add_command(new StatefulDiffCommand (_region));
301 PublicEditor::instance().commit_reversible_command ();
306 RegionEditor::end_clock_changed ()
308 bool in_command = false;
309 boost::shared_ptr<Playlist> pl = _region->playlist();
312 PublicEditor::instance().begin_reversible_command (_("change region end position"));
315 _region->clear_changes ();
316 _region->trim_end (end_clock.current_time());
317 _session->add_command(new StatefulDiffCommand (_region));
321 PublicEditor::instance().commit_reversible_command ();
324 end_clock.set (_region->position() + _region->length() - 1, true);
328 RegionEditor::length_clock_changed ()
330 samplecnt_t samples = length_clock.current_time();
331 bool in_command = false;
332 boost::shared_ptr<Playlist> pl = _region->playlist();
335 PublicEditor::instance().begin_reversible_command (_("change region length"));
338 _region->clear_changes ();
339 _region->trim_end (_region->position() + samples - 1);
340 _session->add_command(new StatefulDiffCommand (_region));
344 PublicEditor::instance().commit_reversible_command ();
347 length_clock.set (_region->length());
351 RegionEditor::audition_button_toggled ()
353 if (audition_button.get_active()) {
354 _session->audition_region (_region);
356 _session->cancel_audition ();
361 RegionEditor::name_changed ()
363 if (name_entry.get_text() != _region->name()) {
364 name_entry.set_text (_region->name());
369 RegionEditor::bounds_changed (const PropertyChange& what_changed)
371 if (what_changed.contains (ARDOUR::Properties::position) && what_changed.contains (ARDOUR::Properties::length)) {
372 position_clock.set (_region->position(), true);
373 end_clock.set (_region->position() + _region->length() - 1, true);
374 length_clock.set (_region->length(), true);
375 } else if (what_changed.contains (ARDOUR::Properties::position)) {
376 position_clock.set (_region->position(), true);
377 end_clock.set (_region->position() + _region->length() - 1, true);
378 } else if (what_changed.contains (ARDOUR::Properties::length)) {
379 end_clock.set (_region->position() + _region->length() - 1, true);
380 length_clock.set (_region->length(), true);
383 if (what_changed.contains (ARDOUR::Properties::sync_position) || what_changed.contains (ARDOUR::Properties::position)) {
385 sampleoffset_t off = _region->sync_offset (dir);
390 if (what_changed.contains (ARDOUR::Properties::sync_position)) {
391 sync_offset_relative_clock.set (off, true);
394 sync_offset_absolute_clock.set (off + _region->position (), true);
397 if (what_changed.contains (ARDOUR::Properties::start)) {
398 start_clock.set (_region->start(), true);
403 RegionEditor::activation ()
409 RegionEditor::name_entry_changed ()
411 if (name_entry.get_text() != _region->name()) {
412 _region->set_name (name_entry.get_text());
417 RegionEditor::audition_state_changed (bool yn)
419 ENSURE_GUI_THREAD (*this, &RegionEditor::audition_state_changed, yn)
422 audition_button.set_active (false);
427 RegionEditor::sync_offset_absolute_clock_changed ()
429 PublicEditor::instance().begin_reversible_command (_("change region sync point"));
431 _region->clear_changes ();
432 _region->set_sync_position (sync_offset_absolute_clock.current_time());
433 _session->add_command (new StatefulDiffCommand (_region));
435 PublicEditor::instance().commit_reversible_command ();
439 RegionEditor::sync_offset_relative_clock_changed ()
441 PublicEditor::instance().begin_reversible_command (_("change region sync point"));
443 _region->clear_changes ();
444 _region->set_sync_position (sync_offset_relative_clock.current_time() + _region->position ());
445 _session->add_command (new StatefulDiffCommand (_region));
447 PublicEditor::instance().commit_reversible_command ();
451 RegionEditor::on_delete_event (GdkEventAny*)
453 PropertyChange change;
455 change.add (ARDOUR::Properties::start);
456 change.add (ARDOUR::Properties::length);
457 change.add (ARDOUR::Properties::position);
458 change.add (ARDOUR::Properties::sync_position);
460 bounds_changed (change);
466 RegionEditor::handle_response (int)