ef012935750974111f88470a5e9cec8efadabfea
[ardour.git] / gtk2_ardour / group_tabs.cc
1 /*
2     Copyright (C) 2009 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 <gtkmm/stock.h>
21
22 #include "ardour/session.h"
23 #include "ardour/route_group.h"
24 #include "ardour/route.h"
25 #include "ardour/vca_manager.h"
26 #include "ardour/vca.h"
27
28 #include "gtkmm2ext/doi.h"
29
30 #include "gui_thread.h"
31 #include "route_group_dialog.h"
32 #include "group_tabs.h"
33 #include "keyboard.h"
34 #include "pbd/i18n.h"
35 #include "ardour_ui.h"
36 #include "rgb_macros.h"
37 #include "ui_config.h"
38 #include "utils.h"
39
40 using namespace std;
41 using namespace Gtk;
42 using namespace ARDOUR;
43 using namespace ARDOUR_UI_UTILS;
44 using Gtkmm2ext::Keyboard;
45
46 list<Gdk::Color> GroupTabs::_used_colors;
47
48 GroupTabs::GroupTabs ()
49         : _menu (0)
50         , _dragging (0)
51         , _dragging_new_tab (0)
52 {
53         add_events (Gdk::BUTTON_PRESS_MASK|Gdk::BUTTON_RELEASE_MASK|Gdk::POINTER_MOTION_MASK);
54         UIConfiguration::instance().ColorsChanged.connect (sigc::mem_fun (*this, &GroupTabs::queue_draw));
55 }
56
57 GroupTabs::~GroupTabs ()
58 {
59         delete _menu;
60 }
61
62 void
63 GroupTabs::set_session (Session* s)
64 {
65         SessionHandlePtr::set_session (s);
66
67         if (_session) {
68                 _session->RouteGroupPropertyChanged.connect (
69                         _session_connections, invalidator (*this), boost::bind (&GroupTabs::route_group_property_changed, this, _1), gui_context()
70                         );
71                 _session->RouteAddedToRouteGroup.connect (
72                         _session_connections, invalidator (*this), boost::bind (&GroupTabs::route_added_to_route_group, this, _1, _2), gui_context()
73                         );
74                 _session->RouteRemovedFromRouteGroup.connect (
75                         _session_connections, invalidator (*this), boost::bind (&GroupTabs::route_removed_from_route_group, this, _1, _2), gui_context()
76                         );
77
78                 _session->route_group_removed.connect (_session_connections, invalidator (*this), boost::bind (&GroupTabs::set_dirty, this, (cairo_rectangle_t*)0), gui_context());
79         }
80 }
81
82
83 /** Handle a size request.
84  *  @param req GTK requisition
85  */
86 void
87 GroupTabs::on_size_request (Gtk::Requisition *req)
88 {
89         /* Use a dummy, small width and the actual height that we want */
90         req->width = 16;
91         req->height = 16;
92 }
93
94 bool
95 GroupTabs::on_button_press_event (GdkEventButton* ev)
96 {
97         using namespace Menu_Helpers;
98
99         double const p = primary_coordinate (ev->x, ev->y);
100
101         list<Tab>::iterator prev;
102         list<Tab>::iterator next;
103         Tab* t = click_to_tab (p, &prev, &next);
104
105         _drag_min = prev != _tabs.end() ? prev->to : 0;
106         _drag_max = next != _tabs.end() ? next->from : extent ();
107
108         if (ev->button == 1) {
109
110                 if (t == 0) {
111                         Tab n;
112                         n.from = n.to = p;
113                         _dragging_new_tab = true;
114
115                         if (next == _tabs.end()) {
116                                 _tabs.push_back (n);
117                                 t = &_tabs.back ();
118                         } else {
119                                 list<Tab>::iterator j = _tabs.insert (next, n);
120                                 t = &(*j);
121                         }
122
123                 } else {
124                         _dragging_new_tab = false;
125                         _initial_dragging_routes = routes_for_tab (t);
126                 }
127
128                 _dragging = t;
129                 _drag_moved = false;
130                 _drag_first = p;
131
132                 double const h = (t->from + t->to) / 2;
133                 if (p < h) {
134                         _drag_moving = t->from;
135                         _drag_fixed = t->to;
136                         _drag_offset = p - t->from;
137                 } else {
138                         _drag_moving = t->to;
139                         _drag_fixed = t->from;
140                         _drag_offset = p - t->to;
141                 }
142
143         } else if (ev->button == 3) {
144
145                 RouteGroup* g = t ? t->group : 0;
146
147                 if (Keyboard::modifier_state_equals (ev->state, Keyboard::TertiaryModifier) && g) {
148                         remove_group (g);
149                 } else if (Keyboard::modifier_state_equals (ev->state, Keyboard::PrimaryModifier) && g) {
150                         edit_group (g);
151                 } else {
152                         Menu* m = get_menu (g, true);
153                         if (m) {
154                                 m->popup (ev->button, ev->time);
155                         }
156                 }
157         }
158
159         return true;
160 }
161
162
163 bool
164 GroupTabs::on_motion_notify_event (GdkEventMotion* ev)
165 {
166         if (_dragging == 0) {
167                 return false;
168         }
169
170         double const p = primary_coordinate (ev->x, ev->y);
171
172         if (p != _drag_first) {
173                 _drag_moved = true;
174         }
175
176         _drag_moving = p - _drag_offset;
177
178         _dragging->from = min (_drag_moving, _drag_fixed);
179         _dragging->to = max (_drag_moving, _drag_fixed);
180
181         _dragging->from = max (_dragging->from, _drag_min);
182         _dragging->to = min (_dragging->to, _drag_max);
183
184         set_dirty ();
185         queue_draw ();
186
187         gdk_event_request_motions(ev);
188
189         return true;
190 }
191
192
193 bool
194 GroupTabs::on_button_release_event (GdkEventButton*)
195 {
196         if (_dragging == 0) {
197                 return false;
198         }
199
200         if (!_drag_moved) {
201
202                 if (_dragging->group) {
203                         /* toggle active state */
204                         _dragging->group->set_active (!_dragging->group->is_active (), this);
205                 }
206
207         } else {
208                 /* finish drag */
209                 RouteList routes = routes_for_tab (_dragging);
210
211                 if (!routes.empty()) {
212                         if (_dragging_new_tab) {
213                                 run_new_group_dialog (&routes, false);
214                         } else {
215                                 boost::shared_ptr<RouteList> r = _session->get_routes ();
216                                 for (RouteList::iterator i = r->begin(); i != r->end(); ++i) {
217
218                                         bool const was_in_tab = find (
219                                                 _initial_dragging_routes.begin(), _initial_dragging_routes.end(), *i
220                                                 ) != _initial_dragging_routes.end ();
221
222                                         bool const now_in_tab = find (routes.begin(), routes.end(), *i) != routes.end();
223
224                                         if (was_in_tab && !now_in_tab) {
225                                                 _dragging->group->remove (*i);
226                                         } else if (!was_in_tab && now_in_tab) {
227                                                 _dragging->group->add (*i);
228                                         }
229                                 }
230                         }
231                 }
232
233                 set_dirty ();
234                 queue_draw ();
235         }
236
237         _dragging = 0;
238         _initial_dragging_routes.clear ();
239
240         return true;
241 }
242
243 void
244 GroupTabs::render (Cairo::RefPtr<Cairo::Context> const& ctx, cairo_rectangle_t*)
245 {
246         cairo_t* cr = ctx->cobj();
247         if (_dragging == 0) {
248                 _tabs = compute_tabs ();
249         }
250
251         /* background */
252
253         Gdk::Color c = get_style()->get_base (Gtk::STATE_NORMAL);
254
255         cairo_set_source_rgb (cr, c.get_red_p(), c.get_green_p(), c.get_blue_p());
256         cairo_rectangle (cr, 0, 0, get_width(), get_height());
257         cairo_fill (cr);
258
259         /* tabs */
260
261         for (list<Tab>::const_iterator i = _tabs.begin(); i != _tabs.end(); ++i) {
262                 draw_tab (cr, *i);
263         }
264 }
265
266
267 /** Convert a click position to a tab.
268  *  @param c Click position.
269  *  @param prev Filled in with the previous tab to the click, or _tabs.end().
270  *  @param next Filled in with the next tab after the click, or _tabs.end().
271  *  @return Tab under the click, or 0.
272  */
273
274 GroupTabs::Tab *
275 GroupTabs::click_to_tab (double c, list<Tab>::iterator* prev, list<Tab>::iterator* next)
276 {
277         *prev = *next = _tabs.end ();
278         Tab* under = 0;
279
280         list<Tab>::iterator i = _tabs.begin ();
281         while (i != _tabs.end()) {
282
283                 if (i->from > c) {
284                         *next = i;
285                         break;
286                 }
287
288                 if (i->to < c) {
289                         *prev = i;
290                         ++i;
291                         continue;
292                 }
293
294                 if (i->from <= c && c < i->to) {
295                         under = &(*i);
296                 }
297
298                 ++i;
299         }
300
301         return under;
302 }
303
304 void
305 GroupTabs::add_new_from_items (Menu_Helpers::MenuList& items)
306 {
307         using namespace Menu_Helpers;
308         Menu *new_from;
309
310         new_from = manage (new Menu);
311         {
312                 MenuList& f = new_from->items ();
313                 f.push_back (MenuElem (_("Selection..."), sigc::bind (sigc::mem_fun (*this, &GroupTabs::new_from_selection), false)));
314                 f.push_back (MenuElem (_("Record Enabled..."), sigc::bind (sigc::mem_fun (*this, &GroupTabs::new_from_rec_enabled), false)));
315                 f.push_back (MenuElem (_("Soloed..."), sigc::bind (sigc::mem_fun (*this, &GroupTabs::new_from_soloed), false)));
316         }
317         items.push_back (MenuElem (_("Create New Group From..."), *new_from));
318
319         new_from = manage (new Menu);
320         {
321                 MenuList& f = new_from->items ();
322                 f.push_back (MenuElem (_("Selection..."), sigc::bind (sigc::mem_fun (*this, &GroupTabs::new_from_selection), true)));
323                 f.push_back (MenuElem (_("Record Enabled..."), sigc::bind (sigc::mem_fun (*this, &GroupTabs::new_from_rec_enabled), true)));
324                 f.push_back (MenuElem (_("Soloed..."), sigc::bind (sigc::mem_fun (*this, &GroupTabs::new_from_soloed), true)));
325         }
326         items.push_back (MenuElem (_("Create New Group with Master From..."), *new_from));
327 }
328
329 Gtk::Menu*
330 GroupTabs::get_menu (RouteGroup* g, bool in_tab_area)
331 {
332         using namespace Menu_Helpers;
333
334         delete _menu;
335
336         _menu = new Menu;
337         _menu->set_name ("ArdourContextMenu");
338
339         MenuList& items = _menu->items();
340         Menu* vca_menu;
341
342         const VCAList vcas = _session->vca_manager().vcas ();
343
344         if (!in_tab_area) {
345                 /* context menu is not for a group tab, show the "create new
346                    from" items here
347                 */
348                 add_new_from_items (items);
349         }
350
351         if (g) {
352                 items.push_back (SeparatorElem());
353                 items.push_back (MenuElem (_("Edit Group..."), sigc::bind (sigc::mem_fun (*this, &GroupTabs::edit_group), g)));
354                 items.push_back (MenuElem (_("Collect Group"), sigc::bind (sigc::mem_fun (*this, &GroupTabs::collect), g)));
355                 items.push_back (MenuElem (_("Remove Group"), sigc::bind (sigc::mem_fun (*this, &GroupTabs::remove_group), g)));
356
357                 items.push_back (SeparatorElem());
358
359                 vca_menu = manage (new Menu);
360                 MenuList& f (vca_menu->items());
361                 f.push_back (MenuElem ("New", sigc::bind (sigc::mem_fun (*this, &GroupTabs::assign_group_to_master), 0, g, true)));
362
363                 for (VCAList::const_iterator v = vcas.begin(); v != vcas.end(); ++v) {
364                         f.push_back (MenuElem ((*v)->name().empty() ? string_compose ("VCA %1", (*v)->number()) : (*v)->name(), sigc::bind (sigc::mem_fun (*this, &GroupTabs::assign_group_to_master), (*v)->number(), g, true)));
365                 }
366                 items.push_back (MenuElem (_("Assign Group to VCA..."), *vca_menu));
367
368
369                 items.push_back (SeparatorElem());
370
371                 if (g->has_subgroup()) {
372                         items.push_back (MenuElem (_("Remove Subgroup Bus"), sigc::bind (sigc::mem_fun (*this, &GroupTabs::un_subgroup), g)));
373                 } else {
374                         items.push_back (MenuElem (_("Add New Subgroup Bus"), sigc::bind (sigc::mem_fun (*this, &GroupTabs::subgroup), g, false, PreFader)));
375                 }
376                 items.push_back (MenuElem (_("Add New Aux Bus (pre-fader)"), sigc::bind (sigc::mem_fun (*this, &GroupTabs::subgroup), g, true, PreFader)));
377                 items.push_back (MenuElem (_("Add New Aux Bus (post-fader)"), sigc::bind (sigc::mem_fun (*this, &GroupTabs::subgroup), g, true, PostFader)));
378                 items.push_back (SeparatorElem());
379
380         }
381
382         add_menu_items (_menu, g);
383
384         if (in_tab_area) {
385                 /* context menu is for a group tab, show the "create new
386                    from" items here
387                 */
388                 add_new_from_items (items);
389         }
390
391         items.push_back (SeparatorElem());
392
393         vca_menu = manage (new Menu);
394         {
395                 MenuList& f (vca_menu->items());
396                 f.push_back (MenuElem ("New", sigc::bind (sigc::mem_fun (*this, &GroupTabs::assign_selection_to_master), 0)));
397                 for (VCAList::const_iterator v = vcas.begin(); v != vcas.end(); ++v) {
398                         f.push_back (MenuElem ((*v)->name().empty() ? string_compose ("VCA %1", (*v)->number()) : (*v)->name(), sigc::bind (sigc::mem_fun (*this, &GroupTabs::assign_selection_to_master), (*v)->number())));
399                 }
400         }
401
402         items.push_back (MenuElem (_("Assign Selection to VCA..."), *vca_menu));
403
404         vca_menu = manage (new Menu);
405         {
406                 MenuList& f (vca_menu->items());
407                 f.push_back (MenuElem ("New", sigc::bind (sigc::mem_fun (*this, &GroupTabs::assign_recenabled_to_master), 0)));
408                 for (VCAList::const_iterator v = vcas.begin(); v != vcas.end(); ++v) {
409                         f.push_back (MenuElem ((*v)->name().empty() ? string_compose ("VCA %1", (*v)->number()) : (*v)->name(), sigc::bind (sigc::mem_fun (*this, &GroupTabs::assign_recenabled_to_master), (*v)->number())));
410                 }
411
412         }
413         items.push_back (MenuElem (_("Assign Record Enabled to VCA..."), *vca_menu));
414
415         vca_menu = manage (new Menu);
416         {
417                 MenuList& f (vca_menu->items());
418                 f.push_back (MenuElem ("New", sigc::bind (sigc::mem_fun (*this, &GroupTabs::assign_soloed_to_master), 0)));
419                 for (VCAList::const_iterator v = vcas.begin(); v != vcas.end(); ++v) {
420                         f.push_back (MenuElem ((*v)->name().empty() ? string_compose ("VCA %1", (*v)->number()) : (*v)->name(), sigc::bind (sigc::mem_fun (*this, &GroupTabs::assign_soloed_to_master), (*v)->number())));
421                 }
422
423         }
424         items.push_back (MenuElem (_("Assign Soloed to VCA..."), *vca_menu));
425
426         items.push_back (SeparatorElem());
427         items.push_back (MenuElem (_("Enable All Groups"), sigc::mem_fun(*this, &GroupTabs::activate_all)));
428         items.push_back (MenuElem (_("Disable All Groups"), sigc::mem_fun(*this, &GroupTabs::disable_all)));
429
430         return _menu;
431 }
432
433 void
434 GroupTabs::assign_group_to_master (uint32_t which, RouteGroup* group, bool rename_master) const
435 {
436         if (!_session || !group) {
437                 return;
438         }
439
440         boost::shared_ptr<VCA> master;
441
442         if (which == 0) {
443                 if (_session->vca_manager().create_vca (1)) {
444                         /* error */
445                         return;
446                 }
447
448                 /* Get most recently created VCA... */
449                 which = _session->vca_manager().vcas().back()->number();
450         }
451
452         master = _session->vca_manager().vca_by_number (which);
453
454         if (!master) {
455                 /* should never happen; if it does, basically something deeply
456                    odd happened, no reason to tell user because there's no
457                    sensible explanation.
458                 */
459                 return;
460         }
461
462         group->assign_master (master);
463
464         if (rename_master){
465                 master->set_name (group->name());
466         }
467 }
468
469 void
470 GroupTabs::assign_some_to_master (uint32_t which, RouteList rl)
471 {
472         if (!_session) {
473                 return;
474         }
475
476         boost::shared_ptr<VCA> master;
477
478         if (which == 0) {
479                 if (_session->vca_manager().create_vca (1)) {
480                         /* error */
481                         return;
482                 }
483
484                 /* Get most recently created VCA... */
485                 which = _session->vca_manager().vcas().back()->number();
486         }
487
488         master = _session->vca_manager().vca_by_number (which);
489
490         if (!master) {
491                 /* should never happen; if it does, basically something deeply
492                    odd happened, no reason to tell user because there's no
493                    sensible explanation.
494                 */
495                 return;
496         }
497
498
499         if (rl.empty()) {
500                 return;
501         }
502
503         for (RouteList::iterator r = rl.begin(); r != rl.end(); ++r) {
504                 (*r)->assign (master, false);
505         }
506 }
507
508 RouteList
509 GroupTabs::get_rec_enabled ()
510 {
511         RouteList rec_enabled;
512
513         if (!_session) {
514                 return rec_enabled;
515         }
516
517         boost::shared_ptr<RouteList> rl = _session->get_routes ();
518
519         for (RouteList::iterator i = rl->begin(); i != rl->end(); ++i) {
520                 boost::shared_ptr<Track> trk (boost::dynamic_pointer_cast<Track> (*i));
521                 if (trk && trk->rec_enable_control()->get_value()) {
522                         rec_enabled.push_back (*i);
523                 }
524         }
525
526         return rec_enabled;
527 }
528
529
530 RouteList
531 GroupTabs::get_soloed ()
532 {
533         boost::shared_ptr<RouteList> rl = _session->get_routes ();
534
535         RouteList soloed;
536
537         for (RouteList::iterator i = rl->begin(); i != rl->end(); ++i) {
538                 if (!(*i)->is_master() && (*i)->soloed()) {
539                         soloed.push_back (*i);
540                 }
541         }
542
543         return soloed;
544 }
545
546 void
547 GroupTabs::assign_selection_to_master (uint32_t which)
548 {
549         assign_some_to_master (which, selected_routes ());
550 }
551
552 void
553 GroupTabs::assign_recenabled_to_master (uint32_t which)
554 {
555         assign_some_to_master (which, get_rec_enabled());
556 }
557
558 void
559 GroupTabs::assign_soloed_to_master (uint32_t which)
560 {
561         assign_some_to_master (which, get_soloed());
562 }
563
564 void
565 GroupTabs::new_from_selection (bool with_master)
566 {
567         RouteList rl (selected_routes());
568         run_new_group_dialog (&rl, with_master);
569 }
570
571 void
572 GroupTabs::new_from_rec_enabled (bool with_master)
573 {
574         RouteList rl (get_rec_enabled());
575         run_new_group_dialog (&rl, with_master);
576 }
577
578 void
579 GroupTabs::new_from_soloed (bool with_master)
580 {
581         RouteList rl (get_soloed());
582         run_new_group_dialog (&rl, with_master);
583 }
584
585 void
586 GroupTabs::run_new_group_dialog (RouteList const * rl, bool with_master)
587 {
588         if (rl && rl->empty()) {
589                 return;
590         }
591
592         RouteGroup* g = new RouteGroup (*_session, "");
593         RouteGroupDialog* d = new RouteGroupDialog (g, true);
594
595         d->signal_response().connect (sigc::bind (sigc::mem_fun (*this, &GroupTabs::new_group_dialog_finished), d, rl ? new RouteList (*rl): 0, with_master));
596         d->present ();
597 }
598
599 void
600 GroupTabs::new_group_dialog_finished (int r, RouteGroupDialog* d, RouteList const * rl, bool with_master) const
601 {
602         if (r == RESPONSE_OK) {
603
604                 if (!d->name_check()) {
605                         return;
606                 }
607
608                 _session->add_route_group (d->group());
609
610                 if (rl) {
611                         for (RouteList::const_iterator i = rl->begin(); i != rl->end(); ++i) {
612                                 d->group()->add (*i);
613                         }
614
615                         if (with_master) {
616                                 assign_group_to_master (0, d->group(), true); /* zero => new master */
617                         }
618                 }
619         } else {
620                 delete d->group ();
621         }
622
623         delete rl;
624         delete_when_idle (d);
625 }
626
627 void
628 GroupTabs::edit_group (RouteGroup* g)
629 {
630         RouteGroupDialog* d = new RouteGroupDialog (g, false);
631         d->signal_response().connect (sigc::bind (sigc::mem_fun (*this, &GroupTabs::edit_group_dialog_finished), d));
632         d->present ();
633 }
634
635 void
636 GroupTabs::edit_group_dialog_finished (int r, RouteGroupDialog* d) const
637 {
638         delete_when_idle (d);
639 }
640
641 void
642 GroupTabs::subgroup (RouteGroup* g, bool aux, Placement placement)
643 {
644         g->make_subgroup (aux, placement);
645 }
646
647 void
648 GroupTabs::un_subgroup (RouteGroup* g)
649 {
650         g->destroy_subgroup ();
651 }
652
653 /** Collect all members of a RouteGroup so that they are together in the Editor or Mixer.
654  *  @param g Group to collect.
655  */
656 void
657 GroupTabs::collect (RouteGroup* g)
658 {
659         boost::shared_ptr<RouteList> group_routes = g->route_list ();
660         group_routes->sort (Stripable::PresentationOrderSorter());
661         int const N = group_routes->size ();
662
663         RouteList::iterator i = group_routes->begin ();
664         boost::shared_ptr<RouteList> routes = _session->get_routes ();
665         routes->sort (Stripable::PresentationOrderSorter());
666         RouteList::const_iterator j = routes->begin ();
667
668         int diff = 0;
669         int coll = -1;
670
671         PresentationInfo::ChangeSuspender cs;
672
673         while (i != group_routes->end() && j != routes->end()) {
674
675                 PresentationInfo::order_t const k = (*j)->presentation_info ().order();
676
677                 if (*i == *j) {
678
679                         if (coll == -1) {
680                                 coll = k;
681                                 diff = N - 1;
682                         } else {
683                                 --diff;
684                         }
685
686                         (*j)->set_presentation_order (coll);
687
688                         ++coll;
689                         ++i;
690
691                 } else {
692
693                         (*j)->set_presentation_order (k + diff);
694
695                 }
696
697                 ++j;
698         }
699 }
700
701 void
702 GroupTabs::activate_all ()
703 {
704         _session->foreach_route_group (
705                 sigc::bind (sigc::mem_fun (*this, &GroupTabs::set_activation), true)
706                 );
707 }
708
709 void
710 GroupTabs::disable_all ()
711 {
712         _session->foreach_route_group (
713                 sigc::bind (sigc::mem_fun (*this, &GroupTabs::set_activation), false)
714                 );
715 }
716
717 void
718 GroupTabs::set_activation (RouteGroup* g, bool a)
719 {
720         g->set_active (a, this);
721 }
722
723 void
724 GroupTabs::remove_group (RouteGroup* g)
725 {
726         RouteList rl (*(g->route_list().get()));
727         _session->remove_route_group (*g);
728
729         PresentationInfo::ChangeSuspender cs;
730
731         for (RouteList::iterator i = rl.begin(); i != rl.end(); ++i) {
732                 (*i)->presentation_info().PropertyChanged (Properties::color);
733         }
734 }
735
736 /** Set the color of the tab of a route group */
737 void
738 GroupTabs::set_group_color (RouteGroup* group, uint32_t color)
739 {
740         assert (group);
741         uint32_t r, g, b, a;
742
743         UINT_TO_RGBA (color, &r, &g, &b, &a);
744
745         /* Hack to disallow black route groups; force a dark grey instead */
746         const uint32_t dark_gray = 25;
747
748         if (r < dark_gray && g < dark_gray && b < dark_gray) {
749                 r = dark_gray;
750                 g = dark_gray;
751                 b = dark_gray;
752         }
753
754         GUIObjectState& gui_state = *ARDOUR_UI::instance()->gui_object_state;
755
756         char buf[64];
757
758         /* for historical reasons the colors must be stored as 16 bit color
759          * values. Ugh.
760          */
761
762         snprintf (buf, sizeof (buf), "%d:%d:%d", (r<<8), (g<<8), (b<<8));
763         gui_state.set_property (group_gui_id (group), "color", buf);
764
765         /* the group color change notification */
766
767         PBD::PropertyChange change;
768         change.add (Properties::color);
769         group->PropertyChanged (change);
770
771         /* This is a bit of a hack, but this might change
772            our route's effective color, so emit gui_changed
773            for our routes.
774         */
775
776         emit_gui_changed_for_members (group);
777 }
778
779 /** @return the ID string to use for the GUI state of a route group */
780 string
781 GroupTabs::group_gui_id (RouteGroup* group)
782 {
783         assert (group);
784
785         char buf[64];
786         snprintf (buf, sizeof (buf), "route_group %s", group->id().to_s().c_str ());
787
788         return buf;
789 }
790
791 /** @return the color to use for a route group tab */
792 uint32_t
793 GroupTabs::group_color (RouteGroup* group)
794 {
795         assert (group);
796
797         GUIObjectState& gui_state = *ARDOUR_UI::instance()->gui_object_state;
798         string const gui_id = group_gui_id (group);
799         bool empty;
800         string const color = gui_state.get_string (gui_id, "color", &empty);
801
802         if (empty) {
803                 /* no color has yet been set, so use a random one */
804                 uint32_t c = gdk_color_to_rgba (unique_random_color (_used_colors));
805                 set_group_color (group, c);
806                 return c;
807         }
808
809         int r, g, b;
810
811         /* for historical reasons, colors are stored as 16 bit values.
812          */
813
814         sscanf (color.c_str(), "%d:%d:%d", &r, &g, &b);
815
816         r /= 256;
817         g /= 256;
818         b /= 256;
819
820         return RGBA_TO_UINT (r, g, b, 255);
821 }
822
823 void
824 GroupTabs::route_group_property_changed (RouteGroup* rg)
825 {
826         /* This is a bit of a hack, but this might change
827            our route's effective color, so emit gui_changed
828            for our routes.
829         */
830
831         emit_gui_changed_for_members (rg);
832
833         set_dirty ();
834 }
835
836 void
837 GroupTabs::route_added_to_route_group (RouteGroup*, boost::weak_ptr<Route> w)
838 {
839         /* Similarly-spirited hack as in route_group_property_changed */
840
841         boost::shared_ptr<Route> r = w.lock ();
842         if (!r) {
843                 return;
844         }
845
846         r->presentation_info().PropertyChanged (Properties::color);
847
848         set_dirty ();
849 }
850
851 void
852 GroupTabs::route_removed_from_route_group (RouteGroup*, boost::weak_ptr<Route> w)
853 {
854         /* Similarly-spirited hack as in route_group_property_changed */
855
856         boost::shared_ptr<Route> r = w.lock ();
857         if (!r) {
858                 return;
859         }
860
861         r->presentation_info().PropertyChanged (Properties::color);
862
863         set_dirty ();
864 }
865
866 void
867 GroupTabs::emit_gui_changed_for_members (RouteGroup* rg)
868 {
869         PresentationInfo::ChangeSuspender cs;
870
871         for (RouteList::iterator i = rg->route_list()->begin(); i != rg->route_list()->end(); ++i) {
872                 (*i)->presentation_info().PropertyChanged (Properties::color);
873         }
874 }