ardour
editor_route_groups.cc
Go to the documentation of this file.
1 /*
2  Copyright (C) 2000 Paul Davis
3 
4  This program is free software; you can redistribute it and/or modify
5  it under the terms of the GNU General Public License as published by
6  the Free Software Foundation; either version 2 of the License, or
7  (at your option) any later version.
8 
9  This program is distributed in the hope that it will be useful,
10  but WITHOUT ANY WARRANTY; without even the implied warranty of
11  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12  GNU General Public License for more details.
13 
14  You should have received a copy of the GNU General Public License
15  along with this program; if not, write to the Free Software
16  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
17 
18 */
19 
20 #ifdef WAF_BUILD
21 #include "gtk2ardour-config.h"
22 #endif
23 
24 #include <cstdlib>
25 #include <cmath>
26 
27 #include "fix_carbon.h"
28 
29 #include "gtkmm2ext/gtk_ui.h"
31 
32 #include "ardour/route_group.h"
33 #include "ardour/route.h"
34 #include "ardour/session.h"
35 
36 #include "ardour_ui.h"
37 #include "editor.h"
38 #include "editor_group_tabs.h"
39 #include "editor_route_groups.h"
40 #include "editor_routes.h"
41 #include "gui_thread.h"
42 #include "keyboard.h"
43 #include "marker.h"
44 #include "prompter.h"
45 #include "route_group_dialog.h"
46 #include "route_time_axis.h"
47 #include "time_axis_view.h"
48 #include "utils.h"
49 
50 #include "i18n.h"
51 
52 using namespace std;
53 using namespace ARDOUR;
54 using namespace ARDOUR_UI_UTILS;
55 using namespace PBD;
56 using namespace Gtk;
58 
59 struct ColumnInfo {
60  int index;
61  const char* label;
62  const char* tooltip;
63 };
64 
66  : EditorComponent (e)
67  , _in_row_change (false)
68  , _in_rebuild (false)
69 {
70  _model = ListStore::create (_columns);
71  _display.set_model (_model);
72 
74  TreeViewColumn* color_column = manage (new TreeViewColumn ("", *color_renderer));
75 
76  color_column->add_attribute (color_renderer->property_color(), _columns.gdkcolor);
77 
78  _display.append_column (*color_column);
79 
80  _display.append_column ("", _columns.text);
81  _display.append_column ("", _columns.is_visible);
82  _display.append_column ("", _columns.active_state);
83  _display.append_column ("", _columns.gain);
84  _display.append_column ("", _columns.gain_relative);
85  _display.append_column ("", _columns.mute);
86  _display.append_column ("", _columns.solo);
87  _display.append_column ("", _columns.record);
88  _display.append_column ("", _columns.monitoring);
89  _display.append_column ("", _columns.select);
90  _display.append_column ("", _columns.active_shared);
91 
92  TreeViewColumn* col;
93  Gtk::Label* l;
94 
95  ColumnInfo ci[] = {
96  { 0, _("Col"), _("Group Tab Color") },
97  { 1, _("Name"), _("Name of Group") },
98  { 2, S_("Visible|V"), _("Group is visible?") },
99  { 3, _("On"), _("Group is enabled?") },
100  { 4, S_("Group|G"), _("Sharing Gain?") },
101  { 5, S_("Relative|Rel"), _("Relative Gain Changes?") },
102  { 6, S_("Mute|M"), _("Sharing Mute?") },
103  { 7, S_("Solo|S"), _("Sharing Solo?") },
104  { 8, _("Rec"), _("Sharing Record-enable Status?") },
105  { 9, S_("Monitoring|Mon"), _("Sharing Monitoring Choice?") },
106  { 10, S_("Selection|Sel"), _("Sharing Selected/Editing Status?") },
107  { 11, S_("Active|A"), _("Sharing Active Status?") },
108  { -1, 0, 0 }
109  };
110 
111 
112  for (int i = 0; ci[i].index >= 0; ++i) {
113  col = _display.get_column (ci[i].index);
114  l = manage (new Label (ci[i].label));
115  ARDOUR_UI::instance()->set_tip (*l, ci[i].tooltip);
116  col->set_widget (*l);
117  l->show ();
118 
119  col->set_data (X_("colnum"), GUINT_TO_POINTER(i));
120  if (i == 1) {
121  col->set_expand (true);
122  } else {
123  col->set_expand (false);
124  col->set_alignment (ALIGN_CENTER);
125  }
126  }
127 
128  _display.set_headers_visible (true);
129 
130  color_dialog.get_colorsel()->set_has_opacity_control (false);
131  color_dialog.get_colorsel()->set_has_palette (true);
132  color_dialog.get_ok_button()->signal_clicked().connect (sigc::bind (sigc::mem_fun (color_dialog, &Gtk::Dialog::response), RESPONSE_ACCEPT));
133  color_dialog.get_cancel_button()->signal_clicked().connect (sigc::bind (sigc::mem_fun (color_dialog, &Gtk::Dialog::response), RESPONSE_CANCEL));
134 
135  /* name is directly editable */
136 
137  CellRendererText* name_cell = dynamic_cast<CellRendererText*>(_display.get_column_cell_renderer (1));
138  name_cell->property_editable() = true;
139  name_cell->signal_edited().connect (sigc::mem_fun (*this, &EditorRouteGroups::name_edit));
140 
141  for (int i = 1; ci[i].index >= 0; ++i) {
142  CellRendererToggle* active_cell = dynamic_cast <CellRendererToggle*> (_display.get_column_cell_renderer (i));
143 
144  if (active_cell) {
145  active_cell->property_activatable() = true;
146  active_cell->property_radio() = false;
147  }
148  }
149 
150  _model->signal_row_changed().connect (sigc::mem_fun (*this, &EditorRouteGroups::row_change));
151  /* What signal would you guess was emitted when the rows of your treeview are reordered
152  by a drag and drop? signal_rows_reordered? That would be far too easy.
153  No, signal_row_deleted().
154  */
155  _model->signal_row_deleted().connect (sigc::mem_fun (*this, &EditorRouteGroups::row_deleted));
156 
157  _display.set_name ("EditGroupList");
158  _display.get_selection()->set_mode (SELECTION_SINGLE);
159  _display.set_headers_visible (true);
160  _display.set_reorderable (false);
161  _display.set_rules_hint (true);
162 
163  _scroller.add (_display);
164  _scroller.set_policy (POLICY_AUTOMATIC, POLICY_AUTOMATIC);
165 
166  _display.signal_button_press_event().connect (sigc::mem_fun(*this, &EditorRouteGroups::button_press_event), false);
167 
168  HBox* button_box = manage (new HBox());
169  button_box->set_homogeneous (true);
170 
171  Button* add_button = manage (new Button ());
172  Button* remove_button = manage (new Button ());
173 
174  Widget* w;
175 
176  w = manage (new Image (Stock::ADD, ICON_SIZE_BUTTON));
177  w->show();
178  add_button->add (*w);
179 
180  w = manage (new Image (Stock::REMOVE, ICON_SIZE_BUTTON));
181  w->show();
182  remove_button->add (*w);
183 
184  add_button->signal_clicked().connect (sigc::hide_return (sigc::mem_fun (*this, &EditorRouteGroups::run_new_group_dialog)));
185  remove_button->signal_clicked().connect (sigc::mem_fun (*this, &EditorRouteGroups::remove_selected));
186 
187  button_box->pack_start (*add_button);
188  button_box->pack_start (*remove_button);
189 
190  _display_packer.pack_start (_scroller, true, true);
191  _display_packer.pack_start (*button_box, false, false);
192 }
193 
194 void
196 {
197  Glib::RefPtr<TreeSelection> selection = _display.get_selection();
198  TreeView::Selection::ListHandle_Path rows = selection->get_selected_rows ();
199 
200  if (rows.empty() || _session->deletion_in_progress()) {
201  return;
202  }
203 
204  TreeView::Selection::ListHandle_Path::iterator i = rows.begin();
205  TreeIter iter;
206 
207  /* selection mode is single, so rows.begin() is it */
208 
209  if ((iter = _model->get_iter (*i))) {
210 
211  RouteGroup* rg = (*iter)[_columns.routegroup];
212 
213  if (rg) {
215  }
216  }
217 }
218 
219 void
221 {
223 }
224 
225 bool
227 {
228  TreeModel::Path path;
229  TreeIter iter;
230  RouteGroup* group = 0;
231  TreeViewColumn* column;
232  int cellx;
233  int celly;
234  bool ret = false;
235  Gdk::Color c;
236  bool val;
237 
238  bool const p = _display.get_path_at_pos ((int)ev->x, (int)ev->y, path, column, cellx, celly);
239 
240  if (p) {
241  iter = _model->get_iter (path);
242  }
243 
244  if (iter) {
245  group = (*iter)[_columns.routegroup];
246  }
247 
248  if (Keyboard::is_context_menu_event (ev)) {
249  _editor->_group_tabs->get_menu(group)->popup (1, ev->time);
250  return true;
251  }
252 
253  if (!p) {
254  /* cancel selection */
255  _display.get_selection()->unselect_all ();
256  /* end any editing by grabbing focus */
257  _display.grab_focus ();
258  return true;
259  }
260 
261  group = (*iter)[_columns.routegroup];
262 
263  switch (GPOINTER_TO_UINT (column->get_data (X_("colnum")))) {
264  case 0:
265  c = (*iter)[_columns.gdkcolor];
266  color_dialog.get_colorsel()->set_previous_color (c);
267  color_dialog.get_colorsel()->set_current_color (c);
268 
269  switch (color_dialog.run()) {
270  case RESPONSE_CANCEL:
271  break;
272  case RESPONSE_ACCEPT:
273  c = color_dialog.get_colorsel()->get_current_color();
275  break;
276 
277  default:
278  break;
279 
280  }
281 
282  color_dialog.hide ();
283  ret = true;
284  break;
285 
286  case 1:
287  if (Keyboard::is_edit_event (ev) && group) {
288  /* we'll be editing now ... */
289  ret = true;
290  }
291  break;
292 
293  case 2:
294  val = (*iter)[_columns.is_visible];
295  /* note subtle logic inverse here: we set the new value with
296  "val", rather than !val, because we're using ::set_hidden()
297  not a (non-existent) ::set_visible() call.
298  */
299  group->set_hidden (val, this);
300  ret = true;
301  break;
302 
303 
304  case 3:
305  val = (*iter)[_columns.active_state];
306  group->set_active (!val, this);
307  ret = true;
308  break;
309 
310  case 4:
311  val = (*iter)[_columns.gain];
312  group->set_gain (!val);
313  ret = true;
314  break;
315 
316  case 5:
317  val = (*iter)[_columns.gain_relative];
318  group->set_relative (!val, this);
319  ret = true;
320  break;
321 
322  case 6:
323  val = (*iter)[_columns.mute];
324  group->set_mute (!val);
325  ret = true;
326  break;
327 
328  case 7:
329  val = (*iter)[_columns.solo];
330  group->set_solo (!val);
331  ret = true;
332  break;
333 
334  case 8:
335  val = (*iter)[_columns.record];
336  group->set_recenable (!val);
337  ret = true;
338  break;
339 
340  case 9:
341  val = (*iter)[_columns.monitoring];
342  group->set_monitoring (!val);
343  ret = true;
344  break;
345 
346  case 10:
347  val = (*iter)[_columns.select];
348  group->set_select (!val);
349  ret = true;
350  break;
351 
352  case 11:
353  val = (*iter)[_columns.active_shared];
354  group->set_route_active (!val);
355  ret = true;
356  break;
357 
358  default:
359  break;
360  }
361 
362  return ret;
363 }
364 
365 void
366 EditorRouteGroups::row_change (const Gtk::TreeModel::Path&, const Gtk::TreeModel::iterator& iter)
367 {
368  RouteGroup* group;
369 
370  if (_in_row_change) {
371  return;
372  }
373 
374  if ((group = (*iter)[_columns.routegroup]) == 0) {
375  return;
376  }
377 
378  PropertyList plist;
379  plist.add (Properties::name, string ((*iter)[_columns.text]));
380 
381  bool val = (*iter)[_columns.gain];
382  plist.add (Properties::gain, val);
383  val = (*iter)[_columns.gain_relative];
384  plist.add (Properties::relative, val);
385  val = (*iter)[_columns.mute];
386  plist.add (Properties::mute, val);
387  val = (*iter)[_columns.solo];
388  plist.add (Properties::solo, val);
389  val = (*iter)[_columns.record];
390  plist.add (Properties::recenable, val);
391  val = (*iter)[_columns.monitoring];
392  plist.add (Properties::monitoring, val);
393  val = (*iter)[_columns.select];
394  plist.add (Properties::select, val);
395  val = (*iter)[_columns.active_shared];
396  plist.add (Properties::route_active, val);
397  val = (*iter)[_columns.active_state];
398  plist.add (Properties::active, val);
399  val = (*iter)[_columns.is_visible];
400  plist.add (Properties::hidden, !val);
401 
402  group->apply_changes (plist);
403 
405 }
406 
407 void
409 {
411  bool focus = false;
412 
413  TreeModel::Row row = *(_model->append());
414 
415  row[_columns.gain] = group->is_gain ();
416  row[_columns.gain_relative] = group->is_relative ();
417  row[_columns.mute] = group->is_mute ();
418  row[_columns.solo] = group->is_solo ();
419  row[_columns.record] = group->is_recenable();
420  row[_columns.monitoring] = group->is_monitoring();
421  row[_columns.select] = group->is_select ();
422  row[_columns.active_shared] = group->is_route_active ();
423  row[_columns.active_state] = group->is_active ();
424  row[_columns.is_visible] = !group->is_hidden();
425 
426  Gdk::Color c;
428  row[_columns.gdkcolor] = c;
429 
430  _in_row_change = true;
431 
432  row[_columns.routegroup] = group;
433 
434  if (!group->name().empty()) {
435  row[_columns.text] = group->name();
436  } else {
437  row[_columns.text] = _("unnamed");
438  focus = true;
439  }
440 
442 
443  if (focus) {
444  TreeViewColumn* col = _display.get_column (0);
445  CellRendererText* name_cell = dynamic_cast<CellRendererText*>(_display.get_column_cell_renderer (1));
446  _display.set_cursor (_model->get_path (row), *col, *name_cell, true);
447  }
448 
449  _in_row_change = false;
450 
452 }
453 
454 void
456 {
458 
459  _in_rebuild = true;
460 
461  /* just rebuild the while thing */
462 
463  _model->clear ();
464 
465  if (_session) {
466  _session->foreach_route_group (sigc::mem_fun (*this, &EditorRouteGroups::add));
467  }
468 
469  _in_rebuild = false;
470 }
471 
472 void
474 {
475  assert(group);
476  _in_row_change = true;
477 
478  Gtk::TreeModel::Children children = _model->children();
479 
480  for(Gtk::TreeModel::Children::iterator iter = children.begin(); iter != children.end(); ++iter) {
481  if (group == (*iter)[_columns.routegroup]) {
482 
483  /* we could check the PropertyChange and only set
484  * appropriate fields. but the amount of saved by doing
485  * that is pretty minimal, and this is nice and simple.
486  */
487 
488  (*iter)[_columns.text] = group->name();
489  (*iter)[_columns.gain] = group->is_gain ();
490  (*iter)[_columns.gain_relative] = group->is_relative ();
491  (*iter)[_columns.mute] = group->is_mute ();
492  (*iter)[_columns.solo] = group->is_solo ();
493  (*iter)[_columns.record] = group->is_recenable ();
494  (*iter)[_columns.monitoring] = group->is_monitoring ();
495  (*iter)[_columns.select] = group->is_select ();
496  (*iter)[_columns.active_shared] = group->is_route_active ();
497  (*iter)[_columns.active_state] = group->is_active ();
498  (*iter)[_columns.is_visible] = !group->is_hidden();
499 
500  Gdk::Color c;
502  (*iter)[_columns.gdkcolor] = c;
503 
504  break;
505  }
506  }
507 
508  _in_row_change = false;
509 
510  for (TrackViewList::const_iterator i = _editor->get_track_views().begin(); i != _editor->get_track_views().end(); ++i) {
511  if ((*i)->route_group() == group) {
512  if (group->is_hidden ()) {
514  } else {
516  }
517  }
518  }
519 }
520 
521 void
522 EditorRouteGroups::name_edit (const std::string& path, const std::string& new_text)
523 {
524  RouteGroup* group;
525  TreeIter iter;
526 
527  if ((iter = _model->get_iter (path))) {
528 
529  if ((group = (*iter)[_columns.routegroup]) == 0) {
530  return;
531  }
532 
533  if (new_text != group->name()) {
534  group->set_name (new_text);
535  }
536  }
537 }
538 
539 void
541 {
542  _display.set_model (Glib::RefPtr<Gtk::TreeStore> (0));
543  _model->clear ();
544  _display.set_model (_model);
545 }
546 
547 void
549 {
550  SessionHandlePtr::set_session (s);
551 
552  if (_session) {
553 
555  _session->route_group_removed.connect (
557  );
560  );
561  }
562 
564  pc.add (Properties::select);
565  pc.add (Properties::active);
566 
567  groups_changed ();
568 }
569 
570 void
572 {
573  RouteList rl;
574 
576 }
577 
582 void
583 EditorRouteGroups::row_deleted (Gtk::TreeModel::Path const &)
584 {
586  /* We need to ignore this in cases where we're not doing a drag-and-drop
587  re-order.
588  */
589  return;
590  }
591 
592  /* Re-write the session's route group list so that the new order is preserved */
593 
594  list<RouteGroup*> new_list;
595 
596  Gtk::TreeModel::Children children = _model->children();
597  for (Gtk::TreeModel::Children::iterator i = children.begin(); i != children.end(); ++i) {
598  new_list.push_back ((*i)[_columns.routegroup]);
599  }
600 
601  _session->reorder_route_groups (new_list);
602 }
603 
604 
PBD::Signal0< void > route_groups_reordered
Definition: session.h:508
Gtk::TreeView _display
Glib::RefPtr< Gtk::ListStore > _model
void reorder_route_groups(std::list< RouteGroup * >)
Glib::PropertyProxy< Gdk::Color > property_color()
PBD::Signal1< void, RouteGroup * > route_group_added
Definition: session.h:506
void set_gain(bool yn)
Definition: route_group.cc:299
bool is_mute() const
Definition: route_group.h:70
PBD::ScopedConnectionList _property_changed_connections
PBD::Signal1< void, const PropertyChange & > PropertyChanged
Definition: stateful.h:87
void set_recenable(bool yn)
Definition: route_group.cc:329
void add(ARDOUR::RouteGroup *)
bool is_gain() const
Definition: route_group.h:69
void set_mute(bool yn)
Definition: route_group.cc:309
LIBARDOUR_API PBD::PropertyDescriptor< bool > hidden
Definition: route_group.h:50
bool is_select() const
Definition: route_group.h:73
EditorRoutes * _routes
Definition: editor.h:1862
void set_monitoring(bool yn)
Definition: route_group.cc:379
Definition: ardour_ui.h:130
static ARDOUR_UI * instance()
Definition: ardour_ui.h:187
void property_changed(ARDOUR::RouteGroup *, const PBD::PropertyChange &)
static void set_group_color(ARDOUR::RouteGroup *, uint32_t)
Definition: group_tabs.cc:562
void remove_route_group(RouteGroup &)
Gtk::TreeModelColumn< Gdk::Color > gdkcolor
Definition: Beats.hpp:239
LIBARDOUR_API PBD::PropertyDescriptor< bool > gain
Definition: route_group.cc:44
Gtk::TreeModelColumn< bool > active_state
void set_route_active(bool yn)
Definition: route_group.cc:349
bool is_hidden() const
Definition: route_group.h:68
Gtk::TreeModelColumn< bool > is_visible
Gtk::TreeModelColumn< bool > gain
#define ENSURE_GUI_THREAD(obj, method,...)
Definition: gui_thread.h:34
void set_select(bool yn)
Definition: route_group.cc:339
void row_deleted(Gtk::TreeModel::Path const &)
Gtk::Menu * get_menu(ARDOUR::RouteGroup *g)
Definition: group_tabs.cc:304
#define _(Text)
Definition: i18n.h:11
Gtk::ScrolledWindow _scroller
void set_relative(bool yn, void *src)
Definition: route_group.cc:404
LIBGTKMM2EXT_API uint64_t Keyboard
Definition: debug.cc:23
bool button_press_event(GdkEventButton *ev)
virtual bool apply_changes(PropertyBase const &)
Definition: stateful.cc:337
uint32_t gdk_color_to_rgba(Gdk::Color const &)
Definition: utils.cc:285
void foreach_route_group(boost::function< void(RouteGroup *)> f)
Definition: session.h:510
#define X_(Text)
Definition: i18n.h:13
TrackViewList const & get_track_views()
Definition: editor.h:406
void set_color_from_rgba(Gdk::Color &, uint32_t)
Definition: utils.cc:276
LIBARDOUR_API PBD::PropertyDescriptor< bool > recenable
Definition: route_group.cc:47
Gtk::TreeModelColumn< std::string > text
LIBARDOUR_API PBD::PropertyDescriptor< bool > route_active
Definition: route_group.cc:49
Definition: amp.h:29
virtual bool set_name(const std::string &str)
bool is_active() const
Definition: route_group.h:66
bool is_route_active() const
Definition: route_group.h:74
bool is_recenable() const
Definition: route_group.h:72
#define gui_context()
Definition: gui_thread.h:36
void show_track_in_display(TimeAxisView &)
Gtk::TreeModelColumn< bool > monitoring
bool deletion_in_progress() const
Definition: session.h:179
void set_dirty()
LIBARDOUR_API PBD::PropertyDescriptor< bool > mute
Definition: route_group.cc:45
void set_hidden(bool yn, void *src)
Definition: route_group.cc:415
PBD::ScopedConnectionList _session_connections
LIBARDOUR_API PBD::PropertyDescriptor< bool > active
Definition: route_group.cc:43
void name_edit(const std::string &, const std::string &)
void set_tip(Gtk::Widget &w, const gchar *tip)
void set_solo(bool yn)
Definition: route_group.cc:319
bool is_monitoring() const
Definition: route_group.h:76
bool is_solo() const
Definition: route_group.h:71
Gtk::ColorSelectionDialog color_dialog
static uint32_t group_color(ARDOUR::RouteGroup *)
Definition: group_tabs.cc:616
const char * name
Definition: editor.h:134
void row_change(const Gtk::TreeModel::Path &, const Gtk::TreeModel::iterator &)
LIBARDOUR_API PBD::PropertyDescriptor< bool > solo
Definition: route_group.cc:46
LIBARDOUR_API PBD::PropertyDescriptor< bool > relative
Definition: route_group.cc:42
std::string name() const
bool is_relative() const
Definition: route_group.h:67
LIBARDOUR_API PBD::PropertyDescriptor< bool > select
Definition: route_group.cc:48
Definition: debug.h:30
EditorGroupTabs * _group_tabs
Definition: editor.h:2176
Gtk::TreeModelColumn< ARDOUR::RouteGroup * > routegroup
#define S_(Text)
Definition: i18n.h:18
Gtk::TreeModelColumn< bool > active_shared
Gtk::TreeModelColumn< bool > gain_relative
void set_session(ARDOUR::Session *)
Gtk::TreeModelColumn< bool > mute
Gtk::TreeModelColumn< bool > select
Gtk::TreeModelColumn< bool > solo
#define MISSING_INVALIDATOR
Definition: event_loop.h:86
Gtk::TreeModelColumn< bool > record
std::list< boost::shared_ptr< Route > > RouteList
Definition: types.h:532
void set_active(bool yn, void *src)
Definition: route_group.cc:392
void hide_track_in_display(TimeAxisView *tv, bool apply_to_selection=false)
Definition: editor.cc:5372
bool add(PropertyBase *prop)
void run_new_group_dialog(ARDOUR::RouteList const &)
Definition: group_tabs.cc:419
ARDOUR::Session * _session
void add(PropertyID id)
PBD::Signal0< void > route_group_removed
Definition: session.h:507
LIBARDOUR_API PBD::PropertyDescriptor< bool > monitoring
Definition: route_group.cc:51