Move DPIReset and ColorsChanged signals into UIConfiguration
[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 #include "ardour/session.h"
22 #include "ardour/route_group.h"
23 #include "ardour/route.h"
24
25 #include "gui_thread.h"
26 #include "route_group_dialog.h"
27 #include "group_tabs.h"
28 #include "keyboard.h"
29 #include "i18n.h"
30 #include "ardour_ui.h"
31 #include "rgb_macros.h"
32 #include "utils.h"
33
34 using namespace std;
35 using namespace Gtk;
36 using namespace ARDOUR;
37 using namespace ARDOUR_UI_UTILS;
38 using Gtkmm2ext::Keyboard;
39
40 list<Gdk::Color> GroupTabs::_used_colors;
41
42 GroupTabs::GroupTabs ()
43         : _menu (0)
44         , _dragging (0)
45         , _dragging_new_tab (0)
46 {
47         add_events (Gdk::BUTTON_PRESS_MASK|Gdk::BUTTON_RELEASE_MASK|Gdk::POINTER_MOTION_MASK);
48         UIConfiguration::ColorsChanged.connect (sigc::mem_fun (*this, &GroupTabs::queue_draw));
49 }
50
51 GroupTabs::~GroupTabs ()
52 {
53         delete _menu;
54 }
55
56 void
57 GroupTabs::set_session (Session* s)
58 {
59         SessionHandlePtr::set_session (s);
60
61         if (_session) {
62                 _session->RouteGroupPropertyChanged.connect (
63                         _session_connections, invalidator (*this), boost::bind (&GroupTabs::route_group_property_changed, this, _1), gui_context()
64                         );
65                 _session->RouteAddedToRouteGroup.connect (
66                         _session_connections, invalidator (*this), boost::bind (&GroupTabs::route_added_to_route_group, this, _1, _2), gui_context()
67                         );
68                 _session->RouteRemovedFromRouteGroup.connect (
69                         _session_connections, invalidator (*this), boost::bind (&GroupTabs::route_removed_from_route_group, this, _1, _2), gui_context()
70                         );
71                 
72                 _session->route_group_removed.connect (_session_connections, invalidator (*this), boost::bind (&GroupTabs::set_dirty, this), gui_context());
73         }
74 }
75
76
77 /** Handle a size request.
78  *  @param req GTK requisition
79  */
80 void
81 GroupTabs::on_size_request (Gtk::Requisition *req)
82 {
83         /* Use a dummy, small width and the actual height that we want */
84         req->width = 16;
85         req->height = 16;
86 }
87
88 bool
89 GroupTabs::on_button_press_event (GdkEventButton* ev)
90 {
91         using namespace Menu_Helpers;
92
93         double const p = primary_coordinate (ev->x, ev->y);
94
95         list<Tab>::iterator prev;
96         list<Tab>::iterator next;
97         Tab* t = click_to_tab (p, &prev, &next);
98
99         _drag_min = prev != _tabs.end() ? prev->to : 0;
100         _drag_max = next != _tabs.end() ? next->from : extent ();
101
102         if (ev->button == 1) {
103
104                 if (t == 0) {
105                         Tab n;
106                         n.from = n.to = p;
107                         _dragging_new_tab = true;
108
109                         if (next == _tabs.end()) {
110                                 _tabs.push_back (n);
111                                 t = &_tabs.back ();
112                         } else {
113                                 list<Tab>::iterator j = _tabs.insert (next, n);
114                                 t = &(*j);
115                         }
116
117                 } else {
118                         _dragging_new_tab = false;
119                         _initial_dragging_routes = routes_for_tab (t);
120                 }
121
122                 _dragging = t;
123                 _drag_moved = false;
124                 _drag_first = p;
125
126                 double const h = (t->from + t->to) / 2;
127                 if (p < h) {
128                         _drag_moving = t->from;
129                         _drag_fixed = t->to;
130                         _drag_offset = p - t->from;
131                 } else {
132                         _drag_moving = t->to;
133                         _drag_fixed = t->from;
134                         _drag_offset = p - t->to;
135                 }
136
137         } else if (ev->button == 3) {
138
139                 RouteGroup* g = t ? t->group : 0;
140                 
141                 if (Keyboard::modifier_state_equals (ev->state, Keyboard::PrimaryModifier) && g) {
142                         /* edit */
143                         RouteGroupDialog d (g, false);
144                         d.do_run ();
145                 } else {
146                         Menu* m = get_menu (g);
147                         if (m) {
148                                 m->popup (ev->button, ev->time);
149                         }
150                 }
151         }
152
153         return true;
154 }
155
156
157 bool
158 GroupTabs::on_motion_notify_event (GdkEventMotion* ev)
159 {
160         if (_dragging == 0) {
161                 return false;
162         }
163
164         double const p = primary_coordinate (ev->x, ev->y);
165
166         if (p != _drag_first) {
167                 _drag_moved = true;
168         }
169
170         _drag_moving = p - _drag_offset;
171
172         _dragging->from = min (_drag_moving, _drag_fixed);
173         _dragging->to = max (_drag_moving, _drag_fixed);
174
175         _dragging->from = max (_dragging->from, _drag_min);
176         _dragging->to = min (_dragging->to, _drag_max);
177
178         set_dirty ();
179         queue_draw ();
180
181         gdk_event_request_motions(ev);
182
183         return true;
184 }
185
186
187 bool
188 GroupTabs::on_button_release_event (GdkEventButton*)
189 {
190         if (_dragging == 0) {
191                 return false;
192         }
193         
194         if (!_drag_moved) {
195                 
196                 if (_dragging->group) {
197                         /* toggle active state */
198                         _dragging->group->set_active (!_dragging->group->is_active (), this);
199                 }
200                 
201         } else {
202                 /* finish drag */
203                 RouteList routes = routes_for_tab (_dragging);
204                 
205                 if (!routes.empty()) {
206                         if (_dragging_new_tab) {
207                                 RouteGroup* g = create_and_add_group ();
208                                 if (g) {
209                                         for (RouteList::iterator i = routes.begin(); i != routes.end(); ++i) {
210                                                 g->add (*i);
211                                         }
212                                 }
213                         } else {
214                                 boost::shared_ptr<RouteList> r = _session->get_routes ();
215                                 for (RouteList::iterator i = r->begin(); i != r->end(); ++i) {
216                                         
217                                         bool const was_in_tab = find (
218                                                 _initial_dragging_routes.begin(), _initial_dragging_routes.end(), *i
219                                                 ) != _initial_dragging_routes.end ();
220                                         
221                                         bool const now_in_tab = find (routes.begin(), routes.end(), *i) != routes.end();
222                                         
223                                         if (was_in_tab && !now_in_tab) {
224                                                 _dragging->group->remove (*i);
225                                         } else if (!was_in_tab && now_in_tab) {
226                                                 _dragging->group->add (*i);
227                                         }
228                                 }
229                         }
230                 }
231                 
232                 set_dirty ();
233                 queue_draw ();
234         }
235         
236         _dragging = 0;
237         _initial_dragging_routes.clear ();
238
239         return true;
240 }
241
242 void
243 GroupTabs::render (cairo_t* cr, cairo_rectangle_t*)
244 {
245         if (_dragging == 0) {
246                 _tabs = compute_tabs ();
247         }
248
249         /* background */
250
251         Gdk::Color c = get_style()->get_base (Gtk::STATE_NORMAL);
252
253         cairo_set_source_rgb (cr, c.get_red_p(), c.get_green_p(), c.get_blue_p());
254         cairo_rectangle (cr, 0, 0, get_width(), get_height());
255         cairo_fill (cr);
256         
257         /* tabs */
258
259         for (list<Tab>::const_iterator i = _tabs.begin(); i != _tabs.end(); ++i) {
260                 draw_tab (cr, *i);
261         }
262 }
263
264
265 /** Convert a click position to a tab.
266  *  @param c Click position.
267  *  @param prev Filled in with the previous tab to the click, or _tabs.end().
268  *  @param next Filled in with the next tab after the click, or _tabs.end().
269  *  @return Tab under the click, or 0.
270  */
271
272 GroupTabs::Tab *
273 GroupTabs::click_to_tab (double c, list<Tab>::iterator* prev, list<Tab>::iterator* next)
274 {
275         *prev = *next = _tabs.end ();
276         Tab* under = 0;
277
278         list<Tab>::iterator i = _tabs.begin ();
279         while (i != _tabs.end()) {
280
281                 if (i->from > c) {
282                         *next = i;
283                         break;
284                 }
285
286                 if (i->to < c) {
287                         *prev = i;
288                         ++i;
289                         continue;
290                 }
291
292                 if (i->from <= c && c < i->to) {
293                         under = &(*i);
294                 }
295
296                 ++i;
297         }
298
299         return under;
300 }
301
302 Gtk::Menu*
303 GroupTabs::get_menu (RouteGroup* g)
304 {
305         using namespace Menu_Helpers;
306
307         delete _menu;
308
309         Menu* new_from = new Menu;
310         MenuList& f = new_from->items ();
311         f.push_back (MenuElem (_("Selection..."), sigc::mem_fun (*this, &GroupTabs::new_from_selection)));
312         f.push_back (MenuElem (_("Record Enabled..."), sigc::mem_fun (*this, &GroupTabs::new_from_rec_enabled)));
313         f.push_back (MenuElem (_("Soloed..."), sigc::mem_fun (*this, &GroupTabs::new_from_soloed)));
314
315         _menu = new Menu;
316         _menu->set_name ("ArdourContextMenu");
317         MenuList& items = _menu->items();
318
319         items.push_back (MenuElem (_("Create New Group ..."), hide_return (sigc::mem_fun(*this, &GroupTabs::create_and_add_group))));
320         items.push_back (MenuElem (_("Create New Group From"), *new_from));
321
322         if (g) {
323                 items.push_back (MenuElem (_("Edit Group..."), sigc::bind (sigc::mem_fun (*this, &GroupTabs::edit_group), g)));
324                 items.push_back (MenuElem (_("Collect Group"), sigc::bind (sigc::mem_fun (*this, &GroupTabs::collect), g)));
325                 items.push_back (MenuElem (_("Remove Group"), sigc::bind (sigc::mem_fun (*this, &GroupTabs::remove_group), g)));
326                 items.push_back (SeparatorElem());
327                 if (g->has_subgroup()) {
328                         items.push_back (MenuElem (_("Remove Subgroup Bus"), sigc::bind (sigc::mem_fun (*this, &GroupTabs::un_subgroup), g)));
329                 } else {
330                         items.push_back (MenuElem (_("Add New Subgroup Bus"), sigc::bind (sigc::mem_fun (*this, &GroupTabs::subgroup), g, false, PreFader)));
331                 }
332                 items.push_back (MenuElem (_("Add New Aux Bus (pre-fader)"), sigc::bind (sigc::mem_fun (*this, &GroupTabs::subgroup), g, true, PreFader)));
333                 items.push_back (MenuElem (_("Add New Aux Bus (post-fader)"), sigc::bind (sigc::mem_fun (*this, &GroupTabs::subgroup), g, true, PostFader)));
334         }
335
336         add_menu_items (_menu, g);
337
338         items.push_back (SeparatorElem());
339         items.push_back (MenuElem (_("Enable All Groups"), sigc::mem_fun(*this, &GroupTabs::activate_all)));
340         items.push_back (MenuElem (_("Disable All Groups"), sigc::mem_fun(*this, &GroupTabs::disable_all)));
341
342         return _menu;
343
344 }
345
346 void
347 GroupTabs::new_from_selection ()
348 {
349         RouteList rl = selected_routes ();
350         if (rl.empty()) {
351                 return;
352         }
353
354         run_new_group_dialog (rl);
355 }
356
357 void
358 GroupTabs::new_from_rec_enabled ()
359 {
360         boost::shared_ptr<RouteList> rl = _session->get_routes ();
361
362         RouteList rec_enabled;
363
364         for (RouteList::iterator i = rl->begin(); i != rl->end(); ++i) {
365                 if ((*i)->record_enabled()) {
366                         rec_enabled.push_back (*i);
367                 }
368         }
369
370         if (rec_enabled.empty()) {
371                 return;
372         }
373
374         run_new_group_dialog (rec_enabled);
375 }
376
377 void
378 GroupTabs::new_from_soloed ()
379 {
380         boost::shared_ptr<RouteList> rl = _session->get_routes ();
381
382         RouteList soloed;
383
384         for (RouteList::iterator i = rl->begin(); i != rl->end(); ++i) {
385                 if (!(*i)->is_master() && (*i)->soloed()) {
386                         soloed.push_back (*i);
387                 }
388         }
389
390         if (soloed.empty()) {
391                 return;
392         }
393
394         run_new_group_dialog (soloed);
395 }
396
397 PBD::PropertyList
398 GroupTabs::default_properties () const
399 {
400         PBD::PropertyList plist;
401
402         plist.add (Properties::route_active, true);
403         plist.add (Properties::active, true);
404         plist.add (Properties::gain, true);
405         plist.add (Properties::relative, true);
406         plist.add (Properties::color, true);
407         plist.add (Properties::monitoring, true);
408         plist.add (Properties::select, true);
409         plist.add (Properties::mute, true);
410         plist.add (Properties::solo, true);
411         plist.add (Properties::gain, true);
412         plist.add (Properties::recenable, true);
413
414         return plist;
415 }
416
417 void
418 GroupTabs::run_new_group_dialog (RouteList const & rl)
419 {
420         RouteGroup* g = new RouteGroup (*_session, "");
421         g->apply_changes (default_properties ());
422
423         RouteGroupDialog d (g, true);
424
425         if (d.do_run ()) {
426                 delete g;
427         } else {
428                 _session->add_route_group (g);
429                 for (RouteList::const_iterator i = rl.begin(); i != rl.end(); ++i) {
430                         g->add (*i);
431                 }
432         }
433 }
434
435 RouteGroup *
436 GroupTabs::create_and_add_group () const
437 {
438         RouteGroup* g = new RouteGroup (*_session, "");
439
440         g->apply_changes (default_properties ());
441
442         RouteGroupDialog d (g, true);
443
444         if (d.do_run ()) {
445                 delete g;
446                 return 0;
447         }
448
449         _session->add_route_group (g);
450         return g;
451 }
452
453 void
454 GroupTabs::edit_group (RouteGroup* g)
455 {
456         RouteGroupDialog d (g, false);
457         d.do_run ();
458 }
459
460 void
461 GroupTabs::subgroup (RouteGroup* g, bool aux, Placement placement)
462 {
463         g->make_subgroup (aux, placement);
464 }
465
466 void
467 GroupTabs::un_subgroup (RouteGroup* g)
468 {
469         g->destroy_subgroup ();
470 }
471
472 struct CollectSorter {
473         bool operator () (boost::shared_ptr<Route> a, boost::shared_ptr<Route> b) {
474                 return a->order_key () < b->order_key ();
475         }
476 };
477
478 struct OrderSorter {
479         bool operator() (boost::shared_ptr<Route> a, boost::shared_ptr<Route> b) {
480                 return a->order_key () < b->order_key ();
481         }
482 };
483
484 /** Collect all members of a RouteGroup so that they are together in the Editor or Mixer.
485  *  @param g Group to collect.
486  */
487 void
488 GroupTabs::collect (RouteGroup* g)
489 {
490         boost::shared_ptr<RouteList> group_routes = g->route_list ();
491         group_routes->sort (CollectSorter ());
492         int const N = group_routes->size ();
493
494         RouteList::iterator i = group_routes->begin ();
495         boost::shared_ptr<RouteList> routes = _session->get_routes ();
496         routes->sort (OrderSorter ());
497         RouteList::const_iterator j = routes->begin ();
498
499         int diff = 0;
500         int coll = -1;
501         while (i != group_routes->end() && j != routes->end()) {
502
503                 int const k = (*j)->order_key ();
504
505                 if (*i == *j) {
506
507                         if (coll == -1) {
508                                 coll = k;
509                                 diff = N - 1;
510                         } else {
511                                 --diff;
512                         }
513
514                         (*j)->set_order_key (coll);
515
516                         ++coll;
517                         ++i;
518
519                 } else {
520
521                         (*j)->set_order_key (k + diff);
522
523                 }
524
525                 ++j;
526         }
527
528         sync_order_keys ();
529 }
530
531 void
532 GroupTabs::activate_all ()
533 {
534         _session->foreach_route_group (
535                 sigc::bind (sigc::mem_fun (*this, &GroupTabs::set_activation), true)
536                 );
537 }
538
539 void
540 GroupTabs::disable_all ()
541 {
542         _session->foreach_route_group (
543                 sigc::bind (sigc::mem_fun (*this, &GroupTabs::set_activation), false)
544                 );
545 }
546
547 void
548 GroupTabs::set_activation (RouteGroup* g, bool a)
549 {
550         g->set_active (a, this);
551 }
552
553 void
554 GroupTabs::remove_group (RouteGroup* g)
555 {
556         _session->remove_route_group (*g);
557 }
558
559 /** Set the color of the tab of a route group */
560 void
561 GroupTabs::set_group_color (RouteGroup* group, uint32_t color)
562 {
563         assert (group);
564         uint32_t r, g, b, a;
565
566         UINT_TO_RGBA (color, &r, &g, &b, &a);
567
568         /* Hack to disallow black route groups; force a dark grey instead */
569
570         if (r == 0 && g == 0 && b == 0) {
571                 r = 25;
572                 g = 25;
573                 b = 25;
574         }
575         
576         GUIObjectState& gui_state = *ARDOUR_UI::instance()->gui_object_state;
577
578         char buf[64];
579         
580         /* for historical reasons the colors must be stored as 16 bit color
581          * values. Ugh.
582          */
583
584         snprintf (buf, sizeof (buf), "%d:%d:%d", (r<<8), (g<<8), (b<<8));
585         gui_state.set_property (group_gui_id (group), "color", buf);
586         
587         /* the group color change notification */
588         
589         PBD::PropertyChange change;
590         change.add (Properties::color);
591         group->PropertyChanged (change);
592
593         /* This is a bit of a hack, but this might change
594            our route's effective color, so emit gui_changed
595            for our routes.
596         */
597
598         emit_gui_changed_for_members (group);
599 }
600
601 /** @return the ID string to use for the GUI state of a route group */
602 string
603 GroupTabs::group_gui_id (RouteGroup* group)
604 {
605         assert (group);
606
607         char buf[64];
608         snprintf (buf, sizeof (buf), "route_group %s", group->id().to_s().c_str ());
609
610         return buf;
611 }
612
613 /** @return the color to use for a route group tab */
614 uint32_t
615 GroupTabs::group_color (RouteGroup* group)
616 {
617         assert (group);
618         
619         GUIObjectState& gui_state = *ARDOUR_UI::instance()->gui_object_state;
620         string const gui_id = group_gui_id (group);
621         bool empty;
622         string const color = gui_state.get_string (gui_id, "color", &empty);
623
624         if (empty) {
625                 /* no color has yet been set, so use a random one */
626                 uint32_t c = gdk_color_to_rgba (unique_random_color (_used_colors));
627                 set_group_color (group, c);
628                 return c;
629         }
630
631         int r, g, b;
632
633         /* for historical reasons, colors are stored as 16 bit values.
634          */
635
636         sscanf (color.c_str(), "%d:%d:%d", &r, &g, &b);
637
638         r /= 256;
639         g /= 256;
640         b /= 256;
641
642         return RGBA_TO_UINT (r, g, b, 255);
643 }
644
645 void
646 GroupTabs::route_group_property_changed (RouteGroup* rg)
647 {
648         /* This is a bit of a hack, but this might change
649            our route's effective color, so emit gui_changed
650            for our routes.
651         */
652
653         emit_gui_changed_for_members (rg);
654         
655         set_dirty ();
656 }
657
658 void
659 GroupTabs::route_added_to_route_group (RouteGroup*, boost::weak_ptr<Route> w)
660 {
661         /* Similarly-spirited hack as in route_group_property_changed */
662
663         boost::shared_ptr<Route> r = w.lock ();
664         if (!r) {
665                 return;
666         }
667
668         r->gui_changed (X_("color"), 0);
669
670         set_dirty ();
671 }
672
673 void
674 GroupTabs::route_removed_from_route_group (RouteGroup*, boost::weak_ptr<Route> w)
675 {
676         /* Similarly-spirited hack as in route_group_property_changed */
677
678         boost::shared_ptr<Route> r = w.lock ();
679         if (!r) {
680                 return;
681         }
682
683         r->gui_changed (X_("color"), 0);
684
685         set_dirty ();
686 }
687
688 void
689 GroupTabs::emit_gui_changed_for_members (RouteGroup* rg)
690 {
691         for (RouteList::iterator i = rg->route_list()->begin(); i != rg->route_list()->end(); ++i) {
692                 (*i)->gui_changed (X_("color"), 0);
693         }
694 }