1 /*
2  * Copyright (C) 2009, 2010 Hermann Meyer, James Warden
3  * Copyright (C) 2011 Pete Shorthose
4  * Copyright (C) 2012 Andreas Degert
5  *
6  * This program is free software; you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License as published by
8  * the Free Software Foundation; either version 2 of the License, or
9  * (at your option) any later version.
10  *
11  * This program is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with this program; if not, write to the Free Software
18  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
19  * ---------------------------------------------------------------------------
20  *
21  * ----------------------------------------------------------------------------
22  */
23 
24 #include <guitarix.h>
25 #include <boost/algorithm/string/replace.hpp>
26 
27 /****************************************************************
28  ** class PluginUI
29  **
30  ** This class represents a rack unit. It refers to an engine
31  ** plugin. The user interface in the rack is loaded on demand.
32  **
33  ** It is responsible for reflecting any changes done to display
34  ** parameter variables (box_visible, flat/expanded format, ordering).
35  **
36  ** Registering with an GxUI is done in PluginDict.
37  **
38  ** When a preset load is in progress re-ordering is blocked.
39  ** MainWindow connects RackContainer::check_order() to
40  ** GxSettings::signal_selection_changed so that ordering will be done
41  ** when the load is finished.
42  **
43  */
44 
PluginUI(PluginDict & plugin_dict_,const char * name,const Glib::ustring & tooltip_)45 PluginUI::PluginUI(PluginDict& plugin_dict_, const char *name,
46                    const Glib::ustring& tooltip_)
47     : action(),
48       group(),
49       toolitem(),
50       rackbox(),
51       hidden(false),
52       active(false),
53       output_widget_state(),
54       output_widgets_active(false),
55       tooltip(tooltip_),
56       shortname(),
57       plugin(plugin_dict_.get_machine().pluginlist_lookup_plugin(name)),
58       plugin_dict(plugin_dict_) {
59     if (plugin->get_pdef()->description && tooltip.empty()) {
60         tooltip = plugin->get_pdef()->description;
61     }
62     plugin->get_pdef()->flags |= gx_engine::PGNI_UI_REG;
63     active = !has_gui();
64 }
65 
~PluginUI()66 PluginUI::~PluginUI() {
67     output_widget_state.clear();
68     delete rackbox;
69     if (toolitem) {
70         if (group) {
71             group->remove(*toolitem);
72         }
73         delete toolitem;
74     }
75     plugin->get_pdef()->flags &= ~gx_engine::PGNI_UI_REG;
76 }
77 
on_plugin_preset_popup()78 void PluginUI::on_plugin_preset_popup() {
79     plugin_dict.plugin_preset_popup(plugin->get_pdef());
80 }
81 
is_registered(gx_engine::GxMachineBase & m,const char * name)82 bool PluginUI::is_registered(gx_engine::GxMachineBase& m, const char *name) {
83     return m.pluginlist_lookup_plugin(name)->get_pdef()->flags & gx_engine::PGNI_UI_REG;
84 }
85 
compress(bool state)86 void PluginUI::compress(bool state) {
87     plugin->set_plug_visible(state);
88     if (rackbox) {
89         if (rackbox->can_compress()) {
90             rackbox->swtch(state);
91         }
92     }
93 }
94 
set_action(Glib::RefPtr<ToggleAction> & act)95 void PluginUI::set_action(Glib::RefPtr<ToggleAction>& act)
96 {
97     action = act;
98     action->signal_toggled().connect(sigc::mem_fun(*this, &PluginUI::on_action_toggled));
99 }
100 
on_action_toggled()101 void PluginUI::on_action_toggled() {
102     if (rackbox && action->get_active() == active) {
103         return;
104     }
105     if (action->get_active()) {
106         plugin_dict.activate(get_id(), "", true);
107     } else {
108         plugin_dict.deactivate(get_id(), true);
109     }
110 }
111 
hide(bool animate)112 void PluginUI::hide(bool animate) {
113     rackbox->display(false, animate);
114 }
115 
show(bool animate)116 void PluginUI::show(bool animate) {
117     hidden = plugin_dict.get_plugins_hidden();
118     if (hidden) {
119         rackbox->hide();
120     } else {
121         rackbox->display(true, animate);
122     }
123 }
124 
activate(bool animate,const string & before)125 void PluginUI::activate(bool animate, const string& before) {
126     if (!has_gui() || active) {
127         return;
128     }
129     active = true;
130     hidden = plugin_dict.get_plugins_hidden();
131     toolitem->hide();
132     bool plug = plugin->get_plug_visible();
133     if (rackbox) {
134         rackbox->swtch(plug);
135     } else {
136         rackbox = plugin_dict.add_rackbox(*this, plug, -1, animate);
137     }
138     plugin->set_box_visible(true);
139     rackbox->set_config_mode(plugin_dict.get_config_mode());
140     set_update_state(get_update_cond());
141     if (hidden) {
142         rackbox->display(false, false);
143     } else {
144         rackbox->display(true, animate);
145     }
146     set_active(true);
147 }
148 
deactivate(bool animate)149 void PluginUI::deactivate(bool animate) {
150     if (!has_gui() || !active) {
151         return;
152     }
153     active = false;
154     plugin->set_box_visible(false);
155     toolitem->show();
156     hide(animate);
157     set_active(false);
158 }
159 
set_config_mode(bool state)160 void PluginUI::set_config_mode(bool state) {
161     if (active) {
162         on_state_change();
163         rackbox->set_config_mode(state);
164     }
165 }
166 
update_rackbox()167 void PluginUI::update_rackbox() {
168     if (!rackbox) {
169         return;
170     }
171     bool reorder = true;
172     RackContainer *container = NULL;
173     string before;
174     if (!plugin->get_box_visible()) {
175         delete rackbox;
176         rackbox = 0;
177         reorder = false;
178     } else {
179         container = rackbox->get_parent();
180         RackContainer::rackbox_list l = container->get_children();
181         for (RackContainer::rackbox_list::iterator i = l.begin(); i != l.end(); ++i) {
182             if (*i == rackbox) {
183                 if (++i != l.end()) {
184                     before = (*i)->get_id();
185                 }
186                 break;
187             }
188         }
189         if (rackbox) {
190             hide(false);
191             delete rackbox;
192             rackbox = 0;
193         }
194     }
195     rackbox = plugin_dict.add_rackbox(*this, plugin->get_plug_visible(), -1, false);
196     show(false);
197     if (reorder) container->reorder(get_id(), before);
198 }
199 
plugins_by_name_less(PluginUI * a,PluginUI * b)200 bool plugins_by_name_less(PluginUI *a, PluginUI *b) {
201     int res = a->get_type() - b->get_type();
202     if (res == 0) {
203         gchar *an = g_utf8_casefold(a->get_shortname(), 1);
204         gchar *bn = g_utf8_casefold(b->get_shortname(), 1);
205         res = g_utf8_collate(an, bn);
206         g_free(an);
207         g_free(bn);
208     }
209     return res < 0;
210 }
211 
on_rack_handle_press(GdkEventButton * ev)212 bool PluginUI::on_rack_handle_press(GdkEventButton* ev) {
213     if (ev->type == GDK_2BUTTON_PRESS && ev->button == 1) {
214         plugin_dict.deactivate(get_id(), true);
215         group->set_collapsed(false);
216         return true;
217     }
218     return false;
219 }
220 
on_ti_drag_begin(const Glib::RefPtr<Gdk::DragContext> & context)221 void PluginUI::on_ti_drag_begin(const Glib::RefPtr<Gdk::DragContext>& context) {
222     plugin_dict.drag_icon = new DragIcon(*this, context, plugin_dict.get_options());
223 }
224 
on_ti_drag_data_get(const Glib::RefPtr<Gdk::DragContext> & context,Gtk::SelectionData & selection,int info,int timestamp)225 void PluginUI::on_ti_drag_data_get(const Glib::RefPtr<Gdk::DragContext>& context, Gtk::SelectionData& selection, int info, int timestamp) {
226     selection.set(*context->list_targets().begin(), get_id());
227 }
228 
on_ti_drag_data_delete(const Glib::RefPtr<Gdk::DragContext> & context)229 void PluginUI::on_ti_drag_data_delete(const Glib::RefPtr<Gdk::DragContext>& context) {
230     toolitem->hide();
231 }
232 
on_ti_button_press(GdkEventButton * ev)233 bool PluginUI::on_ti_button_press(GdkEventButton *ev) {
234     if (ev->type == GDK_2BUTTON_PRESS) {
235         plugin_dict.activate(get_id(), "", true);
236         return true;
237     }
238     return false;
239 }
240 
on_ti_drag_end(const Glib::RefPtr<Gdk::DragContext> & context)241 void PluginUI::on_ti_drag_end(const Glib::RefPtr<Gdk::DragContext>& context) {
242     if (plugin_dict.drag_icon) {
243         delete plugin_dict.drag_icon;
244         plugin_dict.drag_icon = nullptr;
245     }
246 }
247 
on_my_leave_out(GdkEventCrossing * focus)248 bool PluginUI::on_my_leave_out(GdkEventCrossing *focus) {
249     Glib::RefPtr<Gdk::Window> wind = toolitem->get_window();
250     wind->set_cursor();
251     return true;
252 }
253 
on_my_enter_in(GdkEventCrossing * focus)254 bool PluginUI::on_my_enter_in(GdkEventCrossing *focus) {
255     Glib::RefPtr<Gdk::Window> wind = toolitem->get_window();
256     Glib::RefPtr<Gdk::Display> disp = toolitem->get_display();
257     Glib::RefPtr<Gdk::Cursor> cursor(Gdk::Cursor::create(disp, Gdk::HAND1));
258     wind->set_cursor(cursor);
259     return true;
260 }
261 
get_displayname(bool useshort) const262 Glib::ustring PluginUI::get_displayname(bool useshort) const {
263     Glib::ustring name = useshort ? get_shortname() : get_name();
264     if (get_type() == PLUGIN_TYPE_STEREO) {
265         name = "◗◖ " + name; //♾⚮⦅◗◖⦆⚭ ⧓ Ꝏꝏ ⦅◉⦆● ▷◁ ▶◀
266     }
267     if (plugin->get_pdef()->flags & gx_engine::PGNI_IS_LV2) {
268         name += " (LV2)";
269     } else if (plugin->get_pdef()->flags & gx_engine::PGNI_IS_LADSPA) {
270         name += " (LADSPA)";
271     }
272     return name;
273 }
274 
add_toolitem(Gtk::ToolItemGroup * gw)275 void PluginUI::add_toolitem(Gtk::ToolItemGroup *gw) {
276     Gtk::ToolItem *tb = new Gtk::ToolItem();
277     tb->set_use_drag_window(true);
278     tb->signal_drag_begin().connect(sigc::mem_fun(*this, &PluginUI::on_ti_drag_begin));
279     tb->signal_drag_end().connect(sigc::mem_fun(*this, &PluginUI::on_ti_drag_end));
280     tb->signal_drag_data_delete().connect(sigc::mem_fun(*this, &PluginUI::on_ti_drag_data_delete));
281     tb->signal_button_press_event().connect(sigc::mem_fun(*this, &PluginUI::on_ti_button_press));
282     tb->add_events(Gdk::ENTER_NOTIFY_MASK|Gdk::LEAVE_NOTIFY_MASK);
283     tb->signal_leave_notify_event().connect(sigc::mem_fun(*this, &PluginUI::on_my_leave_out));
284     tb->signal_enter_notify_event().connect(sigc::mem_fun(*this, &PluginUI::on_my_enter_in));
285     std::vector<Gtk::TargetEntry> listTargets;
286     if (get_type() == PLUGIN_TYPE_MONO) {
287         listTargets.push_back(Gtk::TargetEntry("application/x-gtk-tool-palette-item-mono", Gtk::TARGET_SAME_APP, 0));
288     } else {
289         listTargets.push_back(Gtk::TargetEntry("application/x-gtk-tool-palette-item-stereo", Gtk::TARGET_SAME_APP, 0));
290     }
291     tb->drag_source_set(listTargets, Gdk::BUTTON1_MASK, Gdk::ACTION_MOVE);
292     tb->signal_drag_data_get().connect(sigc::mem_fun(*this, &PluginUI::on_ti_drag_data_get));
293     Gtk::Label *img = new Gtk::Label(get_displayname(true));
294     img->set_xalign(0);
295     img->set_halign(Gtk::ALIGN_END);
296     if (!tooltip.empty()) {
297         gx_gui::GxBuilder::set_tooltip_text_connect_handler(*img, tooltip);
298     }
299     tb->add(*manage(img));
300     tb->show_all();
301     toolitem = tb;
302     gw->add(*manage(tb));
303     group = gw;
304 }
305 
get_update_cond()306 bool PluginUI::get_update_cond() {
307     return active && !plugin_dict.get_config_mode() && plugin->get_on_off() /*&& !plugin->get_plug_visible()*/;
308 }
309 
set_update_state(bool state)310 void PluginUI::set_update_state(bool state) {
311     output_widgets_active = state;
312     output_widget_state(state);
313 }
314 
on_state_change()315 void PluginUI::on_state_change() {
316     int i = get_update_cond();
317     if (i != output_widgets_active) {
318         set_update_state(i);
319     }
320 }
321 
322 // define if memory leaks in atk, pango, etc. are closed (valgrind --leak-check=full guitarix)
323 //#define LEAKS_OK
324 
dispose_rackbox()325 void PluginUI::dispose_rackbox() {
326     if (plugin->get_box_visible()) {
327         rackbox->hide(); // dnd operation, just hide
328     } else {
329 #ifdef LEAKS_OK
330         delete rackbox;
331         rackbox = nullptr;
332 #else
333         rackbox->hide();
334 #endif
335     }
336 }
337 
animate_vanish()338 bool PluginUI::animate_vanish() {
339     rackbox->anim_height -= rackbox->anim_step;
340     if (rackbox->anim_height > 0) {
341         rackbox->set_size_request(-1, rackbox->anim_height);
342         return true;
343     }
344     rackbox->set_visibility(true);
345     rackbox->set_size_request(-1,-1);
346     dispose_rackbox();
347     return false;
348 }
349 
350 #define ANIMATE_STEPS 5
351 #define ANIMATE_TIME 20
352 #define AUTOSCROLL_TIMEOUT 50
353 
remove(bool animate)354 void PluginUI::remove(bool animate) {
355     if (!animate || !plugin_dict.use_animations()) {
356         dispose_rackbox();
357     } else {
358         if (rackbox->anim_tag.connected()) {
359             rackbox->anim_tag.disconnect();
360             rackbox->set_size_request(-1,-1);
361             rackbox->show();
362         }
363         gint min_height, natural_height;
364         rackbox->get_preferred_height(min_height, natural_height);
365         rackbox->anim_height = min_height;
366         rackbox->set_size_request(-1, rackbox->anim_height);
367         rackbox->set_visibility(false);
368         rackbox->anim_step = rackbox->anim_height / ANIMATE_STEPS;
369         rackbox->anim_tag = Glib::signal_timeout().connect(
370                                 sigc::mem_fun(*this, &PluginUI::animate_vanish),
371                                 ANIMATE_TIME);
372     }
373 }
374 
375 
376 /****************************************************************
377  ** class PluginDict
378  */
379 
PluginDict(gx_engine::GxMachineBase & machine_,gx_system::CmdlineOptions & options_,Gtk::ToolPalette & toolpalette_,gx_gui::StackBoxBuilder & boxbuilder_,UIManager & uimanager_)380 PluginDict::PluginDict(gx_engine::GxMachineBase& machine_, gx_system::CmdlineOptions& options_,
381                        Gtk::ToolPalette& toolpalette_, gx_gui::StackBoxBuilder& boxbuilder_,
382                        UIManager& uimanager_)
383     : monorackcontainer(*this),
384       stereorackcontainer(*this),
385       groupmap(),
386       monotargets(),
387       stereotargets(),
388       boxbuilder(boxbuilder_),
389       machine(machine_),
390       options(options_),
391       toolpalette(toolpalette_),
392       uimanager(uimanager_),
393       config_mode(false),
394       plugins_hidden(false),
395       drag_icon(0) {
396     std::vector<Gtk::TargetEntry> listTargets;
397     // the tool palette accepts drags from the racks
398     listTargets.push_back(Gtk::TargetEntry("application/x-guitarix-mono", Gtk::TARGET_SAME_APP, 0));
399     listTargets.push_back(Gtk::TargetEntry("application/x-guitarix-stereo", Gtk::TARGET_SAME_APP, 1));
400     toolpalette.drag_dest_set(listTargets, Gtk::DEST_DEFAULT_ALL, Gdk::ACTION_MOVE);
401     toolpalette.signal_drag_data_received().connect(sigc::mem_fun(*this, &PluginDict::on_tp_drag_data_received));
402     // the racks additionally accept drags from the tool palette
403     listTargets.push_back(Gtk::TargetEntry("application/x-gtk-tool-palette-item-mono", Gtk::TARGET_SAME_APP, 2));
404     listTargets.push_back(Gtk::TargetEntry("application/x-gtk-tool-palette-item-stereo", Gtk::TARGET_SAME_APP, 3));
405     monotargets.push_back("application/x-guitarix-mono");
406     monotargets.push_back("application/x-gtk-tool-palette-item-mono");
407     stereotargets.push_back("application/x-guitarix-stereo");
408     stereotargets.push_back("application/x-gtk-tool-palette-item-stereo");
409     monorackcontainer.set_list_targets(listTargets, monotargets, stereotargets);
410     stereorackcontainer.set_list_targets(listTargets, stereotargets, monotargets);
411     machine.signal_selection_changed().connect(
412         sigc::mem_fun0(this, &PluginDict::check_order));
413     machine.signal_rack_unit_order_changed().connect(
414         sigc::mem_fun(this, &PluginDict::unit_order_changed));
415     machine.signal_plugin_changed().connect(
416         sigc::mem_fun(this, &PluginDict::on_plugin_changed));
417     fill_pluginlist();
418 }
419 
activate(const string & id,const string & before,bool animate)420 PluginUI *PluginDict::activate(const string& id, const string& before, bool animate) {
421     PluginUI *p = at(id);
422     if (!p->has_gui() || p->active) {
423         return p;
424     }
425     p->plugin->set_plug_visible(false);
426     p->activate(animate, before);
427     machine.insert_rack_unit(id, before, p->get_type());
428     return p;
429 }
430 
deactivate(const string & id,bool animate)431 PluginUI *PluginDict::deactivate(const string& id, bool animate) {
432     PluginUI *p = at(id);
433     if (!p->has_gui() || !p->active) {
434         return p;
435     }
436     p->plugin->set_on_off(false);
437     p->deactivate(animate);
438     machine.remove_rack_unit(id, p->get_type());
439     return p;
440 }
441 
stop_at(RackContainer * container,double off,double step_size,double page_size)442 double PluginDict::stop_at(RackContainer *container, double off, double step_size, double page_size) {
443     if (container == &monorackcontainer) {
444         return stereorackcontainer.stop_at_bottom(off, step_size, page_size);
445     } else {
446         assert(container == &stereorackcontainer);
447         return monorackcontainer.stop_at_top(off, step_size);
448     }
449 }
450 
check_order()451 void PluginDict::check_order() {
452     check_order(PLUGIN_TYPE_MONO, false);
453     check_order(PLUGIN_TYPE_STEREO, false);
454 }
455 
check_order(PluginType tp,bool animate)456 void PluginDict::check_order(PluginType tp, bool animate) {
457     RackContainer& container = (tp == PLUGIN_TYPE_STEREO) ? stereorackcontainer : monorackcontainer;
458     const std::vector<std::string> ol = machine.get_rack_unit_order(tp);
459     bool in_order = true;
460     int pos = 0;
461     unsigned int post_pre = 1;
462     bool need_renumber = false;
463     std::set<std::string> unit_set(ol.begin(), ol.end());
464     RackContainer::rackbox_list l = container.get_children();
465     std::vector<std::string>::const_iterator oi = ol.begin();
466     for (RackContainer::rackbox_list::iterator c = l.begin(); c != l.end(); ++c) {
467         string id = (*c)->get_id();
468         PluginUI *p = at(id);
469         if (!p->active) {
470             continue;
471         }
472         if (unit_set.find(id) == unit_set.end()) {
473             deactivate(id, animate);
474             continue;
475         }
476         if (!in_order) {
477             continue;
478         }
479         if (oi == ol.end()) {
480             in_order = false;
481             continue;
482         }
483         if (*oi != id) {
484             in_order = false;
485             continue;
486         }
487         if (id == "ampstack") {
488             pos = 0;
489             post_pre = 0;
490             continue;
491         }
492         if (!need_renumber && !(*c)->compare_position(pos, post_pre)) {
493             need_renumber = true;
494         }
495         ++oi;
496     }
497     if (oi != ol.end()) {
498         in_order = false;
499     }
500     if (!in_order) {
501         int n = 0;
502         for (std::vector<std::string>::const_iterator oi = ol.begin(); oi != ol.end(); ++oi) {
503             PluginUI *p = at(*oi);
504             p->activate(animate, "");
505             if (p->rackbox) {
506                 container.reorder_child(*p->rackbox, n++);
507             }
508         }
509     }
510     if (!in_order || need_renumber) {
511         container.renumber();
512     }
513     container.set_child_count(ol.size());
514 }
515 
unit_order_changed(bool stereo)516 void PluginDict::unit_order_changed(bool stereo) {
517     check_order(stereo ? PLUGIN_TYPE_STEREO : PLUGIN_TYPE_MONO, true);
518 }
519 
reorder(RackContainer * container,const std::string & name,const std::string & before)520 void PluginDict::reorder(RackContainer *container, const std::string& name, const std::string& before) {
521     PluginType tp = (container == &monorackcontainer ? PLUGIN_TYPE_MONO : PLUGIN_TYPE_STEREO);
522     machine.insert_rack_unit(name, before, tp);
523 }
524 
add(PluginUI * p)525 void PluginDict::add(PluginUI *p) {
526     insert(pair<std::string, PluginUI*>(p->get_id(), p));
527 }
528 
remove(PluginUI * p)529 void PluginDict::remove(PluginUI *p) {
530     std::map<std::string, PluginUI*>::iterator i = find(p->get_id());
531     assert(i != end());
532     erase(i);
533 }
534 
cleanup()535 void PluginDict::cleanup() {
536     for (std::map<std::string, PluginUI*>::iterator i = begin(); i != end(); ++i) {
537         delete i->second;
538     }
539     for (std::map<Glib::ustring, Gtk::ToolItemGroup*>::iterator i = groupmap.begin(); i != groupmap.end(); ++i) {
540         delete i->second;
541     }
542     clear();
543 }
544 
~PluginDict()545 PluginDict::~PluginDict() {
546     cleanup();
547 }
548 
compress(bool state)549 void PluginDict::compress(bool state) {
550     for (std::map<std::string, PluginUI*>::iterator i = begin(); i != end(); ++i) {
551         i->second->compress(state);
552     }
553 }
554 
set_config_mode(bool state)555 void PluginDict::set_config_mode(bool state) {
556     config_mode = state;
557     for (std::map<std::string, PluginUI*>::iterator i = begin(); i != end(); ++i) {
558         i->second->set_config_mode(state);
559     }
560 }
561 
show_entries()562 void PluginDict::show_entries() {
563     plugins_hidden = false;
564     for (std::map<std::string, PluginUI*>::iterator i = begin(); i != end(); ++i) {
565         i->second->hidden = false;
566         if (i->second->active) {
567             RackBox *r = i->second->rackbox;
568             if (r) {
569                 r->show();
570             }
571         }
572     }
573 }
574 
hide_entries()575 void PluginDict::hide_entries() {
576     plugins_hidden = true;
577     for (std::map<std::string, PluginUI*>::iterator i = begin(); i != end(); ++i) {
578         i->second->hidden = true;
579         RackBox *r = i->second->rackbox;
580         if (r) {
581             if (r->can_compress()) {
582                 r->hide();
583             }
584         }
585     }
586 }
587 
on_tp_drag_data_received(const Glib::RefPtr<Gdk::DragContext> & context,int x,int y,const Gtk::SelectionData & data,int info,int timestamp)588 void PluginDict::on_tp_drag_data_received(const Glib::RefPtr<Gdk::DragContext>& context, int x, int y, const Gtk::SelectionData& data, int info, int timestamp) {
589     Glib::ustring id = data.get_data_as_string();
590     PluginUI *p = deactivate(id, false);
591     p->group->set_collapsed(false);
592 }
593 
add_plugin(std::vector<PluginUI * > & p,const char * id,const Glib::ustring & tooltip)594 void PluginDict::add_plugin(std::vector<PluginUI*>& p, const char *id, const Glib::ustring& tooltip) {
595     if (PluginUI::is_registered(machine, id)) {
596         return;
597     }
598     p.push_back(new PluginUI(*this, id, tooltip));
599 }
600 
add_bare(const char * id,Gtk::Container * box)601 void PluginDict::add_bare(const char * id, Gtk::Container *box) {
602     PluginUI *plugin = new PluginUI(*this, id);
603     add(plugin);
604     plugin->rackbox = add_rackbox_internal(*plugin, 0, 0, false, -1, false, box);
605 }
606 
add_rackbox_internal(PluginUI & plugin,Gtk::Widget * mainwidget,Gtk::Widget * miniwidget,bool mini,int pos,bool animate,Gtk::Widget * bare)607 RackBox *PluginDict::add_rackbox_internal(PluginUI& plugin, Gtk::Widget *mainwidget, Gtk::Widget *miniwidget,
608         bool mini, int pos, bool animate, Gtk::Widget *bare) {
609     RackBox *r = new RackBox(plugin, *this, bare);
610     r->swtch(mini);
611     r->pack(mainwidget, miniwidget);
612     if (plugin.get_type() == PLUGIN_TYPE_MONO) {
613         monorackcontainer.add(*manage(r), pos);
614     } else {
615         stereorackcontainer.add(*manage(r), pos);
616     }
617     if (animate) {
618         r->animate_insert();
619     }
620     return r;
621 }
622 
add_rackbox(PluginUI & pl,bool mini,int pos,bool animate)623 RackBox *PluginDict::add_rackbox(PluginUI& pl, bool mini, int pos, bool animate) {
624     gx_gui::UiBuilderImpl builder(this, &boxbuilder, nullptr, &pl);
625     Gtk::Widget *mainwidget = 0;
626     Gtk::Widget *miniwidget = 0;
627     if (machine.load_unit(builder, pl.plugin->get_pdef())) {
628         boxbuilder.fetch(mainwidget, miniwidget);
629     }
630     return add_rackbox_internal(pl, mainwidget, miniwidget, mini, pos, animate);
631 }
632 
plugin_preset_popup(const PluginDef * pdef)633 void PluginDict::plugin_preset_popup(const PluginDef *pdef) {
634     new PluginPresetPopup(pdef, machine);
635 }
636 
plugin_preset_popup(const PluginDef * pdef,const Glib::ustring & name)637 void PluginDict::plugin_preset_popup(const PluginDef *pdef, const Glib::ustring& name) {
638     new PluginPresetPopup(pdef, machine, name);
639 }
640 
pluginlist_append(std::vector<PluginUI * > & p)641 void PluginDict::pluginlist_append(std::vector<PluginUI*>& p) {
642     gx_gui::UiBuilderImpl builder(this, &boxbuilder, &p, nullptr);
643     machine.pluginlist_append_rack(builder);
644 }
645 
on_plugin_changed(gx_engine::Plugin * pl,gx_engine::PluginChange::pc c)646 void PluginDict::on_plugin_changed(gx_engine::Plugin *pl, gx_engine::PluginChange::pc c) {
647     if (!pl) { // end of update sequence
648         return;
649     }
650     if (c == gx_engine::PluginChange::add) {
651         register_plugin(new PluginUI(*this, pl->get_pdef()->id, ""));
652         return;
653     }
654     PluginUI *pui = at(pl->get_pdef()->id);
655     if (c == gx_engine::PluginChange::remove) {
656         Glib::ustring actname = pui->get_action()->get_name();
657         remove_plugin_menu_entry(pui);
658         uimanager.get_action_group()->remove(actname);
659         machine.remove_rack_unit(pui->get_id(), pui->get_type());
660         std::string group_id = pui->get_category();
661         remove(pui);
662         delete pui;
663         Gtk::ToolItemGroup * group = groupmap[group_id];
664         if (group->get_n_items() == 0) {
665             // removal is optional since empty toolitem groups and
666             // menus are already hidden.
667             uimanager.remove_item(category_id(pui->get_category(), false));
668             uimanager.remove_item(category_id(pui->get_category(), true));
669             groupmap.erase(group_id);
670             delete group;
671         }
672     } else {
673         assert(c == gx_engine::PluginChange::update ||
674                c == gx_engine::PluginChange::update_category);
675         bool state =  pui->plugin->get_on_off();
676         pui->update_rackbox();
677         pui->plugin->set_on_off(state);
678         if (c == gx_engine::PluginChange::update_category) {
679             pui->group = add_plugin_category(pui->get_category());
680             // if the toolitem group becomes empty, it will be hidden automatically
681             pui->toolitem->reparent(*pui->group);
682             remove_plugin_menu_entry(pui);
683             add_plugin_menu_entry(pui);
684         }
685     }
686 }
687 
category_id(const std::string & group,bool stereo)688 Glib::ustring PluginDict::category_id(const std::string& group, bool stereo) {
689     const char *tp = stereo ? "Stereo" : "Mono";
690     std::string group_id = group;
691     boost::replace_all(group_id, " ", ""); // FIXME: replace all invalid chars
692     return Glib::ustring::compose("Plugin%1Category_%2", tp, group_id);
693 }
694 
add_plugin_category(const char * group,bool collapse)695 Gtk::ToolItemGroup *PluginDict::add_plugin_category(const char *group, bool collapse) {
696     std::map<Glib::ustring, Gtk::ToolItemGroup*>::iterator it = groupmap.find(group);
697     if (it != groupmap.end()) {
698         return it->second;
699     }
700     // add category menus (mono and stereo)
701     Glib::ustring display = gettext(group);
702     Glib::RefPtr<Gio::MenuItem> item;
703     Glib::ustring action;
704     action = category_id(group, false);
705     item = Gio::MenuItem::create(display, action);
706     item->set_submenu(Gio::Menu::create());
707     uimanager.get_linked_menu("MonoPlugins")->append_item(item);
708     uimanager.find_item(action)->hide();
709     action = category_id(group, true);
710     item = Gio::MenuItem::create(display, action);
711     item->set_submenu(Gio::Menu::create());
712     uimanager.get_linked_menu("StereoPlugins")->append_item(item);
713     uimanager.find_item(action)->hide();
714     // toolitem group
715     Gtk::ToolItemGroup *gw = new Gtk::ToolItemGroup(gettext(group));
716     groupmap[group] = gw;
717     gw->set_collapsed(collapse);
718     toolpalette.add(*manage(gw));
719     toolpalette.set_exclusive(*gw, true);
720     toolpalette.set_expand(*gw, true);
721     // make groups label left aligned (not settable in CSS)
722     gw->get_label_widget()->set_halign(Gtk::ALIGN_START);
723     return gw;
724 }
725 
add_plugin_menu_entry(PluginUI * pui)726 Glib::ustring PluginDict::add_plugin_menu_entry(PluginUI *pui) {
727     Glib::ustring cat_id = category_id(
728                                pui->get_category(), pui->get_type() == PLUGIN_TYPE_STEREO);
729     Glib::RefPtr<Gio::Menu> menu = uimanager.get_linked_menu(cat_id);
730     uimanager.find_item(cat_id)->show();
731     Glib::ustring actname = Glib::ustring::compose("Plugin_%1", pui->get_id());
732     auto a = Gio::MenuItem::create(pui->get_name(), actname);
733     menu->append_item(a);
734     return actname;
735 }
736 
remove_plugin_menu_entry(PluginUI * pui)737 void PluginDict::remove_plugin_menu_entry(PluginUI *pui) {
738     Glib::ustring action = pui->get_action()->get_name();
739     auto *menu = dynamic_cast<Gtk::Menu*>(uimanager.find_item(action)->get_parent());
740     uimanager.remove_item(action);
741     if (menu->get_children().size() == 0) {
742         menu->get_attach_widget()->hide();
743     }
744 }
745 
register_plugin(PluginUI * pui)746 void PluginDict::register_plugin(PluginUI *pui) {
747     add(pui);
748     Gtk::ToolItemGroup *gw = add_plugin_category(pui->get_category());
749     Glib::ustring actionname = add_plugin_menu_entry(pui);
750     pui->add_toolitem(gw);
751     Glib::RefPtr<ToggleAction> act = uimanager.add_toggle_action(
752                                          actionname, pui->plugin->get_box_visible());
753     pui->set_action(act);
754     if (pui->plugin->get_box_visible()) {
755         act->set_active(true);
756     }
757 }
758 
759 class JConvPluginUI: public PluginUI {
760 private:
761     virtual void on_plugin_preset_popup();
762 public:
JConvPluginUI(PluginDict & plugin_dict,const char * id,const Glib::ustring & tooltip="")763     JConvPluginUI(PluginDict& plugin_dict, const char* id,
764                   const Glib::ustring& tooltip="")
765         : PluginUI(plugin_dict, id, tooltip) {
766     }
767 };
768 
on_plugin_preset_popup()769 void JConvPluginUI::on_plugin_preset_popup() {
770     gx_engine::JConvParameter *jcp = dynamic_cast<gx_engine::JConvParameter*>(
771                                          &plugin_dict.get_machine().get_parameter(std::string(get_id())+".convolver"));
772     assert(jcp);
773     Glib::ustring name = jcp->get_value().getIRFile();
774     Glib::ustring::size_type n = name.find_last_of('.');
775     if (n != Glib::ustring::npos) {
776         name.erase(n);
777     }
778     plugin_dict.plugin_preset_popup(plugin->get_pdef(), name);
779 }
780 
fill_pluginlist()781 void PluginDict::fill_pluginlist() {
782     // define order of categories by registering
783     // them first
784     add_plugin_category(N_("Tone Control"), false);
785     add_plugin_category(N_("Distortion"));
786     add_plugin_category(N_("Fuzz"));
787     add_plugin_category(N_("Reverb"));
788     add_plugin_category(N_("Echo / Delay"));
789     add_plugin_category(N_("Modulation"));
790     add_plugin_category(N_("Guitar Effects"));
791     add_plugin_category(N_("Misc"));
792 
793     std::vector<PluginUI*> p;
794     p.push_back(new JConvPluginUI(*this, "jconv"));
795     p.push_back(new JConvPluginUI(*this, "jconv_mono"));
796     pluginlist_append(p);
797     std::sort(p.begin(), p.end(), plugins_by_name_less);
798     for (std::vector<PluginUI*>::iterator v = p.begin(); v != p.end(); ++v) {
799         register_plugin(*v);
800     }
801 }
802 
803 
804 /****************************************************************
805  ** class DragIcon
806  */
807 
DragIcon(PluginUI & plugin,Glib::RefPtr<Gdk::DragContext> context,gx_system::CmdlineOptions & options,int xoff)808 DragIcon::DragIcon(PluginUI& plugin, Glib::RefPtr<Gdk::DragContext> context, gx_system::CmdlineOptions& options, int xoff)
809     : window(), drag_icon_pixbuf() {
810     Glib::RefPtr<Gdk::Screen> screen = context->get_source_window()->get_screen();
811     Glib::RefPtr<Gdk::Visual> rgba;
812     if (screen->is_composited()) {
813         rgba = screen->get_rgba_visual();
814         window = new Gtk::Window(Gtk::WINDOW_POPUP);
815         if (rgba) { // else will look ugly..
816             gtk_widget_set_visual(GTK_WIDGET(window->gobj()), rgba->gobj());
817         }
818     }
819     create_drag_icon_pixbuf(plugin, rgba, options);
820     int w = drag_icon_pixbuf->get_width();
821     int h = drag_icon_pixbuf->get_height();
822     int h2 = (h/2)-2;
823     int w2 = std::min(std::max(0, xoff), w-gradient_length/2) - 4;
824     if (window) {
825         window->set_size_request(w, h);
826         window->signal_draw().connect(sigc::mem_fun(*this, &DragIcon::icon_draw));
827         //context->set_icon_widget(window, w2, h2);
828         gtk_drag_set_icon_widget(context->gobj(), GTK_WIDGET(window->gobj()), w2, h2);
829     } else {
830         context->set_icon(drag_icon_pixbuf, w2, h2);
831     }
832 }
833 
~DragIcon()834 DragIcon::~DragIcon() {
835     delete window;
836 }
837 
icon_draw(const Cairo::RefPtr<Cairo::Context> & cr)838 bool DragIcon::icon_draw(const Cairo::RefPtr<Cairo::Context> &cr) {
839     Gdk::Cairo::set_source_pixbuf(cr, drag_icon_pixbuf, 0, 0);
840     cr->paint();
841     return true;
842 }
843 
create_drag_icon_pixbuf(const PluginUI & plugin,Glib::RefPtr<Gdk::Visual> rgba,gx_system::CmdlineOptions & options)844 void DragIcon::create_drag_icon_pixbuf(const PluginUI& plugin, Glib::RefPtr<Gdk::Visual> rgba, gx_system::CmdlineOptions& options) {
845     Gtk::OffscreenWindow w;
846     w.signal_draw().connect(sigc::bind(sigc::mem_fun(*this, &DragIcon::window_draw), sigc::ref(w)));
847     if (rgba) {
848         gtk_widget_set_visual(GTK_WIDGET(w.gobj()), rgba->gobj());
849     }
850     Gtk::Widget *r = RackBox::create_drag_widget(plugin, options);
851     w.add(*r);
852     w.show_all();
853     int busy_wait = 0;
854     while (Gtk::Main::events_pending() && busy_wait < 25) { // needed for style updates
855         Gtk::Main::iteration(false);
856         ++busy_wait;
857     }
858     w.get_window()->process_updates(true);
859     drag_icon_pixbuf = w.get_pixbuf();
860 }
861 
window_draw(const Cairo::RefPtr<Cairo::Context> & cr,Gtk::OffscreenWindow & widget)862 bool DragIcon::window_draw(const Cairo::RefPtr<Cairo::Context> &cr, Gtk::OffscreenWindow& widget) {
863     Gtk::Widget *child = widget.get_child();
864     if (child) {
865         widget.propagate_draw(*child, cr);
866     }
867     if (!window) {
868         return true;
869     }
870     int w = widget.get_window()->get_width();
871     int h = widget.get_window()->get_height();
872     Cairo::RefPtr<Cairo::LinearGradient> grad = Cairo::LinearGradient::create(w, 0, w-gradient_length, 0);
873     grad->add_color_stop_rgba(0, 1, 1, 1, 1);
874     grad->add_color_stop_rgba(1, 1, 1, 1, 0);
875     cr->rectangle(w-gradient_length, 0, gradient_length, h);
876     cr->mask(grad);
877     return true;
878 }
879 
880 
881 /****************************************************************
882  ** class MiniRackBox
883  */
884 
885 Glib::RefPtr<Gtk::SizeGroup> MiniRackBox::szg_label;
886 
make_delete_button(RackBox & rb)887 Gtk::Widget *MiniRackBox::make_delete_button(RackBox& rb) {
888     Gtk::Widget *w;
889     if (rb.has_delete()) {
890         Gtk::Label *l = new Gtk::Label("\u2a2f");
891         l->show();
892         Gtk::Button *b = new Gtk::Button();
893         b->set_focus_on_click(false);
894         b->add(*manage(l));
895         b->signal_clicked().connect(
896             sigc::hide_return(
897                 sigc::bind(
898                     sigc::mem_fun(rb.plugin.plugin_dict, &PluginDict::deactivate),
899                     rb.get_id(), true)));
900         w = b;
901     } else {
902         w = new Gtk::Alignment();
903     }
904     w->set_size_request(20, 15);
905     return w;
906 }
907 
on_my_leave_out(GdkEventCrossing * focus)908 bool MiniRackBox::on_my_leave_out(GdkEventCrossing *focus) {
909     if (!mconbox.get_visible()) {
910         Glib::RefPtr<Gdk::Window> window = this->get_window();
911         window->set_cursor();
912     }
913     return true;
914 }
915 
on_my_enter_in(GdkEventCrossing * focus)916 bool MiniRackBox::on_my_enter_in(GdkEventCrossing *focus) {
917     if (!mconbox.get_visible()) {
918         Glib::RefPtr<Gdk::Window> window = this->get_window();
919         Glib::RefPtr<Gdk::Display> disp = this->get_display();
920         Glib::RefPtr<Gdk::Cursor> cursor(Gdk::Cursor::create(disp, Gdk::HAND1));
921         window->set_cursor(cursor);
922     }
923     return true;
924 }
925 
MiniRackBox(RackBox & rb,gx_system::CmdlineOptions & options)926 MiniRackBox::MiniRackBox(RackBox& rb, gx_system::CmdlineOptions& options)
927     : Gtk::HBox(),
928       evbox(),
929       mconbox(false, 4),
930       mb_expand_button(),
931       mb_delete_button(),
932       preset_button(),
933       on_off_switch("switchit") {
934     gx_gui::ui_connect_switch(rb.plugin_dict.get_machine(), &on_off_switch,
935                               rb.plugin.plugin->id_on_off(), nullptr, false);
936     if (strcmp(rb.plugin.get_id(), "ampstack") != 0) { // FIXME
937         if (!szg_label) {
938             szg_label = Gtk::SizeGroup::create(Gtk::SIZE_GROUP_HORIZONTAL);
939         }
940         evbox.set_visible_window(false);
941         evbox.signal_leave_notify_event().connect(sigc::mem_fun(*this, &MiniRackBox::on_my_leave_out));
942         evbox.signal_enter_notify_event().connect(sigc::mem_fun(*this, &MiniRackBox::on_my_enter_in));
943         add(evbox);
944 
945         Gtk::Alignment *al = new Gtk::Alignment();
946         al->set_padding(0, 4, 0, 0);
947         al->set_border_width(0);
948         al->get_style_context()->add_class("minibox-title");
949 
950         evbox.add(*manage(al));
951 
952         Gtk::HBox *box = new Gtk::HBox();
953         Gtk::HBox *top = new Gtk::HBox();
954         al->add(*manage(box));
955 
956         this->set_spacing(0);
957         this->set_border_width(0);
958 
959         box->set_spacing(0);
960         box->set_border_width(0);
961 
962         top->set_spacing(4);
963         top->set_border_width(0);
964         top->set_name("rack_unit_title_bar");
965 
966         box->pack_start(*manage(rb.wrap_bar()), Gtk::PACK_SHRINK);
967         box->pack_start(*manage(top));
968         box->pack_start(*manage(rb.wrap_bar()), Gtk::PACK_SHRINK);
969 
970         top->pack_start(on_off_switch, Gtk::PACK_SHRINK);
971         on_off_switch.set_name("effect_on_off");
972         Gtk::Widget *effect_label = RackBox::make_label(rb.plugin, options);
973         szg_label->add_widget(*manage(effect_label));
974         top->pack_start(*manage(effect_label), Gtk::PACK_SHRINK);
975 
976         top->pack_start(mconbox, Gtk::PACK_EXPAND_WIDGET);
977 
978         mb_expand_button = rb.make_expand_button(true);
979         top->pack_end(*manage(mb_expand_button), Gtk::PACK_SHRINK);
980         if (!(rb.plugin.plugin->get_pdef()->flags & PGN_NO_PRESETS)) {
981             preset_button = rb.make_preset_button();
982             top->pack_end(*manage(preset_button), Gtk::PACK_SHRINK);
983         }
984         mb_delete_button = make_delete_button(rb);
985         mb_delete_button->set_no_show_all(true);
986         top->pack_end(*manage(mb_delete_button), Gtk::PACK_SHRINK);
987     } else { // special minibox for main amp in config mode
988         if (!szg_label) {
989             szg_label = Gtk::SizeGroup::create(Gtk::SIZE_GROUP_HORIZONTAL);
990         }
991         evbox.set_visible_window(false);
992         evbox.signal_leave_notify_event().connect(sigc::mem_fun(*this, &MiniRackBox::on_my_leave_out));
993         evbox.signal_enter_notify_event().connect(sigc::mem_fun(*this, &MiniRackBox::on_my_enter_in));
994         add(evbox);
995 
996         Gtk::Alignment *al = new Gtk::Alignment();
997         al->set_padding(0, 4, 0, 0);
998         al->set_border_width(0);
999 
1000         Gtk::HBox *box = new Gtk::HBox();
1001         Gtk::HBox *top = new Gtk::HBox();
1002         evbox.add(*manage(box));
1003 
1004         top->set_name("rack_unit_title_bar");
1005         Gtk::Widget *effect_label = RackBox::make_label(rb.plugin, options);
1006         szg_label->add_widget(*manage(effect_label));
1007         top->pack_start(*manage(effect_label), Gtk::PACK_SHRINK);
1008         top->pack_start(mconbox, Gtk::PACK_EXPAND_WIDGET);
1009         box->pack_start(*manage(al), Gtk::PACK_SHRINK);
1010         box->pack_start(*manage(top));
1011     }
1012     show_all();
1013 }
1014 
pack(Gtk::Widget * w)1015 void MiniRackBox::pack(Gtk::Widget *w) {
1016     if (w) {
1017         mconbox.pack_start(*manage(w), Gtk::PACK_SHRINK, 4);
1018     }
1019 }
1020 
set_config_mode(bool mode,PluginUI & plugin)1021 void MiniRackBox::set_config_mode(bool mode, PluginUI& plugin) {
1022     evbox.set_above_child(mode);
1023     if (mode) {
1024         mconbox.hide();
1025         if (preset_button) {
1026             preset_button->hide();
1027         }
1028         if (mb_expand_button) {
1029             mb_expand_button->hide();
1030         }
1031         if (mb_delete_button) {
1032             // force event handling of the button to be above the evbox
1033             mb_delete_button->hide();
1034             Glib::signal_idle().connect_once(
1035                 sigc::mem_fun(mb_delete_button,&Gtk::Widget::show),
1036                 Glib::PRIORITY_HIGH_IDLE);
1037             evbox_button = evbox.signal_button_press_event().connect(
1038                                sigc::mem_fun(plugin, &PluginUI::on_rack_handle_press));
1039         }
1040     } else {
1041         mconbox.show();
1042         if (preset_button) {
1043             preset_button->show();
1044         }
1045         if (mb_expand_button) {
1046             mb_expand_button->show();
1047         }
1048         if (mb_delete_button) {
1049             evbox_button.disconnect();
1050             mb_delete_button->hide();
1051         }
1052     }
1053 }
1054 
1055 
1056 /****************************************************************
1057  ** class PluginPresetPopup
1058  */
1059 
1060 /*
1061 ** InputWindow
1062 */
1063 
1064 class InputWindow: public Gtk::Window {
1065 private:
1066     Glib::ustring name;
1067     void on_cancel();
1068     void on_ok(Gtk::Entry *e);
1069     virtual bool on_key_press_event(GdkEventKey *event);
1070     static InputWindow* create_from_builder(
1071         BaseObjectType* cobject, Glib::RefPtr<gx_gui::GxBuilder> bld, const Glib::ustring& save_name_default);
1072     InputWindow(BaseObjectType* cobject, Glib::RefPtr<gx_gui::GxBuilder> bld, const Glib::ustring& save_name_default);
1073 public:
1074     ~InputWindow();
1075     static InputWindow *create(const gx_system::CmdlineOptions& options, const Glib::ustring& save_name_default);
1076     void run();
get_name()1077     Glib::ustring& get_name() {
1078         return name;
1079     }
1080 };
1081 
create_from_builder(BaseObjectType * cobject,Glib::RefPtr<gx_gui::GxBuilder> bld,const Glib::ustring & save_name_default)1082 InputWindow *InputWindow::create_from_builder(BaseObjectType* cobject, Glib::RefPtr<gx_gui::GxBuilder> bld,
1083         const Glib::ustring& save_name_default) {
1084     return new InputWindow(cobject, bld, save_name_default);
1085 }
1086 
~InputWindow()1087 InputWindow::~InputWindow() {
1088 }
1089 
create(const gx_system::CmdlineOptions & options,const Glib::ustring & save_name_default)1090 InputWindow *InputWindow::create(const gx_system::CmdlineOptions& options, const Glib::ustring& save_name_default) {
1091     Glib::RefPtr<gx_gui::GxBuilder> bld = gx_gui::GxBuilder::create_from_file(options.get_builder_filepath("pluginpreset_inputwindow.glade"));
1092     InputWindow *w;
1093     bld->get_toplevel_derived(
1094         "PluginPresetInputWindow", w,
1095         sigc::bind(sigc::ptr_fun(InputWindow::create_from_builder), bld, save_name_default));
1096     return w;
1097 }
1098 
on_key_press_event(GdkEventKey * event)1099 bool InputWindow::on_key_press_event(GdkEventKey *event) {
1100     if (event->keyval == GDK_KEY_Escape && (event->state & Gtk::AccelGroup::get_default_mod_mask()) == 0) {
1101         hide();
1102         return true;
1103     }
1104     return Gtk::Window::on_key_press_event(event);
1105 }
1106 
on_ok(Gtk::Entry * e)1107 void InputWindow::on_ok(Gtk::Entry *e) {
1108     name = e->get_text();
1109     hide();
1110 }
1111 
InputWindow(BaseObjectType * cobject,Glib::RefPtr<gx_gui::GxBuilder> bld,const Glib::ustring & save_name_default)1112 InputWindow::InputWindow(BaseObjectType* cobject, Glib::RefPtr<gx_gui::GxBuilder> bld,
1113                          const Glib::ustring& save_name_default)
1114     : Gtk::Window(cobject), name() {
1115     Gtk::Button *b;
1116     bld->find_widget("cancelbutton", b);
1117     b->signal_clicked().connect(
1118         sigc::mem_fun(*this, &InputWindow::hide));
1119     bld->find_widget("okbutton", b);
1120     Gtk::Entry *e;
1121     bld->find_widget("entry", e);
1122     e->set_text(save_name_default);
1123     e->select_region(0, -1);
1124     b->signal_clicked().connect(
1125         sigc::bind(sigc::mem_fun(*this, &InputWindow::on_ok), e));
1126 }
1127 
run()1128 void InputWindow::run() {
1129     Gtk::Main::run(*this);
1130 }
1131 
1132 /*
1133 ** PluginPresetListWindow
1134 */
1135 
1136 class TextListStore: public Gtk::ListStore {
1137 public:
1138     class TextListColumns : public Gtk::TreeModel::ColumnRecord {
1139     public:
1140         Gtk::TreeModelColumn<Glib::ustring> name;
TextListColumns()1141         TextListColumns() {
1142             add(name);
1143         }
1144     } col;
1145 private:
TextListStore()1146     TextListStore(): Gtk::ListStore(), col() {
1147         set_column_types(col);
1148     }
1149 public:
create()1150     static Glib::RefPtr<TextListStore> create() {
1151         return Glib::RefPtr<TextListStore>(new TextListStore);
1152     }
1153 };
1154 
1155 class PluginPresetListWindow: public Gtk::Window {
1156 private:
1157     Glib::RefPtr<TextListStore> textliststore;
1158     PluginPresetPopup& presetlist;
1159     //
1160     Gtk::TreeView *treeview;
1161     Gtk::Button *removebutton;
1162     using Gtk::Window::on_remove;
1163     void on_remove();
1164     void on_selection_changed();
1165     virtual bool on_key_press_event(GdkEventKey *event);
1166     static PluginPresetListWindow* create_from_builder(
1167         BaseObjectType* cobject, Glib::RefPtr<gx_gui::GxBuilder> bld, PluginPresetPopup& p);
1168     PluginPresetListWindow(BaseObjectType* cobject, Glib::RefPtr<gx_gui::GxBuilder> bld, PluginPresetPopup& p);
1169 public:
1170     ~PluginPresetListWindow();
1171     static PluginPresetListWindow *create(const gx_system::CmdlineOptions& options, PluginPresetPopup& p);
1172     void run();
1173 };
1174 
create_from_builder(BaseObjectType * cobject,Glib::RefPtr<gx_gui::GxBuilder> bld,PluginPresetPopup & p)1175 PluginPresetListWindow *PluginPresetListWindow::create_from_builder(
1176     BaseObjectType* cobject, Glib::RefPtr<gx_gui::GxBuilder> bld, PluginPresetPopup& p) {
1177     return new PluginPresetListWindow(cobject, bld, p);
1178 }
1179 
~PluginPresetListWindow()1180 PluginPresetListWindow::~PluginPresetListWindow() {
1181 }
1182 
create(const gx_system::CmdlineOptions & options,PluginPresetPopup & p)1183 PluginPresetListWindow *PluginPresetListWindow::create(
1184     const gx_system::CmdlineOptions& options, PluginPresetPopup& p) {
1185     Glib::RefPtr<gx_gui::GxBuilder> bld = gx_gui::GxBuilder::create_from_file(
1186             options.get_builder_filepath("pluginpreset_listwindow.glade"));
1187     PluginPresetListWindow *w;
1188     bld->get_toplevel_derived(
1189         "PluginPresetListWindow", w,
1190         sigc::bind(sigc::ptr_fun(PluginPresetListWindow::create_from_builder), bld, sigc::ref(p)));
1191     return w;
1192 }
1193 
on_key_press_event(GdkEventKey * event)1194 bool PluginPresetListWindow::on_key_press_event(GdkEventKey *event) {
1195     if (event->keyval == GDK_KEY_Escape && (event->state & Gtk::AccelGroup::get_default_mod_mask()) == 0) {
1196         hide();
1197         return true;
1198     }
1199     return Gtk::Window::on_key_press_event(event);
1200 }
1201 
on_remove()1202 void PluginPresetListWindow::on_remove() {
1203     Gtk::TreeIter it = treeview->get_selection()->get_selected();
1204     if (it) {
1205         presetlist.get_machine().plugin_preset_list_remove(
1206             presetlist.get_pdef(), it->get_value(textliststore->col.name));
1207         textliststore->erase(it);
1208     }
1209 }
1210 
PluginPresetListWindow(BaseObjectType * cobject,Glib::RefPtr<gx_gui::GxBuilder> bld,PluginPresetPopup & p)1211 PluginPresetListWindow::PluginPresetListWindow(
1212     BaseObjectType* cobject, Glib::RefPtr<gx_gui::GxBuilder> bld, PluginPresetPopup& p)
1213     : Gtk::Window(cobject),
1214       textliststore(TextListStore::create()),
1215       presetlist(p) {
1216     Gtk::Button *b;
1217     bld->find_widget("closebutton", b);
1218     b->signal_clicked().connect(
1219         sigc::mem_fun(*this, &PluginPresetListWindow::hide));
1220     bld->find_widget("removebutton", removebutton);
1221     removebutton->signal_clicked().connect(
1222         sigc::mem_fun0(*this, &PluginPresetListWindow::on_remove));
1223     bld->find_widget("treeview", treeview);
1224     for (gx_preset::UnitPresetList::const_iterator i = presetlist.begin(); i != presetlist.end(); ++i) {
1225         if (i->name.empty()) {
1226             break;
1227         }
1228         textliststore->append()->set_value(textliststore->col.name, i->name);
1229     }
1230     treeview->set_model(textliststore);
1231     removebutton->set_sensitive(false);
1232     Glib::RefPtr<Gtk::TreeSelection> sel = treeview->get_selection();
1233     sel->signal_changed().connect(
1234         sigc::mem_fun(*this, &PluginPresetListWindow::on_selection_changed));
1235 }
1236 
on_selection_changed()1237 void PluginPresetListWindow::on_selection_changed() {
1238     removebutton->set_sensitive(treeview->get_selection()->get_selected());
1239 }
1240 
run()1241 void PluginPresetListWindow::run() {
1242     Gtk::Main::run(*this);
1243 }
1244 
1245 /*
1246 ** PluginPresetPopup
1247 */
1248 
set_plugin_preset(bool factory,const Glib::ustring & name)1249 void PluginPresetPopup::set_plugin_preset(bool factory, const Glib::ustring& name) {
1250     if(strcmp(pdef->id,"seq")==0) {
1251         machine.plugin_preset_list_sync_set(pdef, factory, name);
1252     } else {
1253         machine.plugin_preset_list_set(pdef, factory, name);
1254     }
1255 }
1256 
set_plugin_std_preset()1257 void PluginPresetPopup::set_plugin_std_preset() {
1258     machine.reset_unit(pdef);
1259 }
1260 
save_plugin_preset()1261 void PluginPresetPopup::save_plugin_preset() {
1262     InputWindow *w = InputWindow::create(machine.get_options(), save_name_default);
1263     w->run();
1264     if (!w->get_name().empty()) {
1265         // save loop file to plugin preset name
1266         if(strcmp(pdef->id,"dubber")==0) {
1267             Glib::ustring name = "";
1268             machine.set_parameter_value("dubber.filename", name);
1269             machine.set_parameter_value("dubber.savefile", true);
1270             machine.set_parameter_value("dubber.filename", w->get_name());
1271         }
1272         machine.plugin_preset_list_save(pdef, w->get_name());
1273         if(strcmp(pdef->id,"seq")==0) {
1274             Glib::ustring id = "seq." + w->get_name();
1275             if (!machine.parameter_hasId(id)) {
1276                 machine.insert_param("seq", w->get_name());
1277             }
1278         }
1279     }
1280     delete w;
1281 }
1282 
remove_plugin_preset()1283 void PluginPresetPopup::remove_plugin_preset() {
1284     PluginPresetListWindow *w = PluginPresetListWindow::create(machine.get_options(), *this);
1285     w->run();
1286     delete w;
1287 }
1288 
add_plugin_preset_list(bool * found)1289 bool PluginPresetPopup::add_plugin_preset_list(bool *found) {
1290     *found = false;
1291     bool found_presets = false;
1292     bool factory = false;
1293     for (gx_preset::UnitPresetList::iterator i = presetnames.begin(); i != presetnames.end(); ++i) {
1294         if (i->name.empty()) {
1295             factory = true;
1296             if (found_presets) {
1297                 append(*manage(new Gtk::SeparatorMenuItem()));
1298                 *found = true;
1299                 found_presets = false;
1300             }
1301             continue;
1302         } else {
1303             found_presets = true;
1304         }
1305         Gtk::CheckMenuItem *c = new Gtk::CheckMenuItem(i->name);
1306         if (i->is_set) {
1307             c->set_active(true);
1308         }
1309         c->signal_activate().connect(
1310             sigc::bind(sigc::mem_fun(this, &PluginPresetPopup::set_plugin_preset), factory, i->name));
1311         append(*manage(c));
1312     }
1313     return found_presets;
1314 }
1315 
delete_plugin_preset_popup(PluginPresetPopup * p)1316 static bool delete_plugin_preset_popup(PluginPresetPopup *p) {
1317     delete p;
1318     return false;
1319 }
1320 
on_selection_done()1321 void PluginPresetPopup::on_selection_done() {
1322     Glib::signal_idle().connect(
1323         sigc::bind(
1324             sigc::ptr_fun(delete_plugin_preset_popup),
1325             this));
1326 }
1327 
PluginPresetPopup(const PluginDef * pdef_,gx_engine::GxMachineBase & machine_,const Glib::ustring & save_name_default_)1328 PluginPresetPopup::PluginPresetPopup(const PluginDef *pdef_, gx_engine::GxMachineBase& machine_,
1329                                      const Glib::ustring& save_name_default_)
1330     : Gtk::Menu(),
1331       pdef(pdef_),
1332       machine(machine_),
1333       save_name_default(save_name_default_),
1334       presetnames() {
1335     machine.plugin_preset_list_load(pdef, presetnames);
1336     bool found_presets;
1337     if (!add_plugin_preset_list(&found_presets)) {
1338         Gtk::CheckMenuItem *c = new Gtk::CheckMenuItem(_("standard"));
1339         if (machine.parameter_unit_has_std_values(pdef)) {
1340             c->set_active(true);
1341         }
1342         c->signal_activate().connect(
1343             sigc::mem_fun(this, &PluginPresetPopup::set_plugin_std_preset));
1344         append(*manage(c));
1345     }
1346     append(*manage(new Gtk::SeparatorMenuItem()));
1347     Gtk::MenuItem *mi = new Gtk::MenuItem(_("save..."));
1348     append(*manage(mi));
1349     mi->signal_activate().connect(
1350         sigc::mem_fun(this, &PluginPresetPopup::save_plugin_preset));
1351     if (found_presets) {
1352         mi = new Gtk::MenuItem(_("remove..."));
1353         append(*manage(mi));
1354         mi->signal_activate().connect(
1355             sigc::mem_fun(this, &PluginPresetPopup::remove_plugin_preset));
1356     }
1357     show_all();
1358     popup_at_pointer(nullptr);
1359 }
1360 
1361 
1362 /****************************************************************
1363  ** class RackBox
1364  */
1365 
set_paintbox_unit_shrink(Gxw::PaintBox & pb,PluginType tp)1366 void RackBox::set_paintbox_unit_shrink(Gxw::PaintBox& pb, PluginType tp) {
1367     pb.get_style_context()->add_class("rackmini");
1368     pb.set_border_width(0);
1369 }
1370 
set_paintbox_unit(Gxw::PaintBox & pb,const PluginUI & plugin)1371 void RackBox::set_paintbox_unit(Gxw::PaintBox& pb, const PluginUI& plugin) {
1372     pb.set_border_width(0);
1373 }
1374 
set_paintbox(Gxw::PaintBox & pb,PluginType tp)1375 void RackBox::set_paintbox(Gxw::PaintBox& pb, PluginType tp) {
1376     pb.set_name("rackbox");
1377     // pb.property_paint_func().set_value("rectangle_skin_color_expose");
1378     pb.set_border_width(0);
1379 }
1380 
make_label(const PluginUI & plugin,gx_system::CmdlineOptions & options,bool useshort)1381 Gtk::Widget *RackBox::make_label(const PluginUI& plugin, gx_system::CmdlineOptions& options, bool useshort) {
1382     Gtk::Label *effect_label = new Gtk::Label(plugin.get_displayname(useshort));
1383     effect_label->set_alignment(0.0, 0.5);
1384     effect_label->get_style_context()->add_class("effect_title");
1385     return effect_label;
1386 }
1387 
make_bar(int left,int right,bool sens)1388 Gtk::Widget *RackBox::make_bar(int left, int right, bool sens) {
1389     Gtk::Alignment *al = new Gtk::Alignment(0, 0, 1.0, 1.0);
1390     //al->set_padding(4, 4, left, right);
1391     Gtk::Button *button = new Gtk::Button();
1392     button->set_size_request(32,-1);
1393     //button->set_name("effect_reset");
1394     gx_gui::GxBuilder::set_tooltip_text_connect_handler(*button, _("Drag'n' Drop Handle"));
1395     button->set_relief(Gtk::RELIEF_NONE);
1396     button->set_sensitive(sens);
1397     al->add(*manage(button));
1398     return al;
1399 }
1400 
on_my_leave_out(GdkEventCrossing * focus)1401 bool RackBox::on_my_leave_out(GdkEventCrossing *focus) {
1402     Glib::RefPtr<Gdk::Window> window = this->get_window();
1403     window->set_cursor();
1404     return true;
1405 }
1406 
on_my_enter_in(GdkEventCrossing * focus)1407 bool RackBox::on_my_enter_in(GdkEventCrossing *focus) {
1408     Glib::RefPtr<Gdk::Window> window = this->get_window();
1409     Glib::RefPtr<Gdk::Display> disp = this->get_display();
1410     Glib::RefPtr<Gdk::Cursor> cursor(Gdk::Cursor::create(disp, Gdk::HAND1));
1411     window->set_cursor(cursor);
1412     return true;
1413 }
1414 
wrap_bar(int left,int right,bool sens)1415 Gtk::Widget *RackBox::wrap_bar(int left, int right, bool sens) {
1416     Gtk::EventBox *ev = new Gtk::EventBox;
1417     ev->set_visible_window(false);
1418     ev->set_above_child(true);
1419     ev->add(*manage(make_bar(left, right, sens)));
1420     ev->signal_leave_notify_event().connect(sigc::mem_fun(*this, &RackBox::on_my_leave_out));
1421     ev->signal_enter_notify_event().connect(sigc::mem_fun(*this, &RackBox::on_my_enter_in));
1422     ev->signal_button_press_event().connect(sigc::mem_fun(plugin, &PluginUI::on_rack_handle_press));
1423     ev->signal_drag_begin().connect(sigc::mem_fun(*this, &RackBox::on_my_drag_begin));
1424     ev->signal_drag_end().connect(sigc::mem_fun(*this, &RackBox::on_my_drag_end));
1425     ev->signal_drag_failed().connect(sigc::mem_fun(*this, &RackBox::on_my_drag_failed));
1426     ev->signal_drag_data_get().connect(sigc::mem_fun(*this, &RackBox::on_my_drag_data_get));
1427     std::vector<Gtk::TargetEntry> listTargets;
1428     listTargets.push_back(Gtk::TargetEntry(target, Gtk::TARGET_SAME_APP, 0));
1429     ev->drag_source_set(listTargets, Gdk::BUTTON1_MASK, Gdk::ACTION_MOVE);
1430     return ev;
1431 }
1432 
create_icon_widget(const PluginUI & plugin,gx_system::CmdlineOptions & options)1433 Gtk::Widget *RackBox::create_icon_widget(const PluginUI& plugin, gx_system::CmdlineOptions& options) {
1434     Gxw::PaintBox *pb = new Gxw::PaintBox(Gtk::ORIENTATION_HORIZONTAL);
1435     RackBox::set_paintbox(*pb, plugin.get_type());
1436     Gtk::Widget *effect_label = RackBox::make_label(plugin, options);
1437     Gtk::Alignment *al = new Gtk::Alignment(0.0, 0.0, 1.0, 1.0);
1438     al->set_padding(0,2,2,0);
1439     al->add(*manage(effect_label));
1440     pb->pack_start(*manage(al), Gtk::PACK_SHRINK);
1441     pb->show_all();
1442     return pb;
1443 }
1444 
create_drag_widget(const PluginUI & plugin,gx_system::CmdlineOptions & options)1445 Gtk::Widget *RackBox::create_drag_widget(const PluginUI& plugin, gx_system::CmdlineOptions& options) {
1446     Gxw::PaintBox *pb = new Gxw::PaintBox(Gtk::ORIENTATION_HORIZONTAL);
1447     RackBox::set_paintbox_unit_shrink(*pb, plugin.get_type());
1448     pb->set_name("drag_widget");
1449     Gtk::Widget *effect_label = RackBox::make_label(plugin, options);
1450     if (plugin.plugin->get_pdef()->flags & gx_engine::PGNI_IS_LV2) {
1451         pb->get_style_context()->add_class("PL-LV2");
1452         pb->set_name("rack_unit_title_bar");
1453     } else if (plugin.plugin->get_pdef()->flags & gx_engine::PGNI_IS_LADSPA) {
1454         pb->get_style_context()->add_class("PL-LADSPA");
1455         pb->set_name("rack_unit_title_bar");
1456     }
1457     Gtk::Alignment *al = new Gtk::Alignment(0.0, 0.0, 0.0, 0.0);
1458     al->set_padding(0,0,4,20);
1459     al->add(*manage(RackBox::make_bar(4, 4, true))); // FIXME: fix style and remove sens parameter
1460     pb->pack_start(*manage(al), Gtk::PACK_SHRINK);
1461     //pb->pack_start(*manage(swtch), Gtk::PACK_SHRINK);
1462     pb->pack_start(*manage(effect_label), Gtk::PACK_SHRINK);
1463     al = new Gtk::Alignment(0.0, 0.0, 0.0, 0.0);
1464     al->set_size_request(70,30);
1465     pb->pack_start(*manage(al), Gtk::PACK_SHRINK);
1466     pb->show_all();
1467     return pb;
1468 }
1469 
display(bool v,bool animate)1470 void RackBox::display(bool v, bool animate) {
1471     if (v) {
1472         if (animate) {
1473             animate_insert();
1474         } else {
1475             show();
1476         }
1477     } else {
1478         plugin.remove(animate);
1479     }
1480 }
1481 
RackBox(PluginUI & plugin_,PluginDict & tl,Gtk::Widget * bare)1482 RackBox::RackBox(PluginUI& plugin_, PluginDict& tl, Gtk::Widget* bare)
1483     : Gtk::VBox(),
1484       plugin(plugin_),
1485       plugin_dict(tl),
1486       anim_tag(),
1487       compress(true),
1488       delete_button(true),
1489       mbox(Gtk::ORIENTATION_HORIZONTAL),
1490       minibox(0),
1491       fbox(0),
1492       target(),
1493       anim_height(0),
1494       anim_step(),
1495       drag_icon(),
1496       target_height(0),
1497       box(Gtk::ORIENTATION_HORIZONTAL, 2),
1498       on_off_switch("switchit") {
1499     gx_gui::ui_connect_switch(tl.get_machine(), &on_off_switch,
1500                               plugin.plugin->id_on_off(), nullptr, false);
1501     if (bare) {
1502         compress = false;
1503         delete_button = false;
1504     }
1505     set_paintbox_unit_shrink(mbox, plugin.get_type());
1506     init_dnd();
1507     minibox = new MiniRackBox(*this, tl.get_options());
1508     mbox.pack_start(*manage(minibox));
1509     pack_start(mbox, Gtk::PACK_EXPAND_WIDGET);
1510     if (bare) {
1511         add(*manage(bare));
1512         fbox = bare;
1513     } else {
1514         Gxw::PaintBox *pb = new Gxw::PaintBox(Gtk::ORIENTATION_HORIZONTAL);
1515         pb->show();
1516         set_paintbox_unit(*pb, plugin);
1517         pb->pack_start(*manage(make_full_box(tl.get_options())));
1518         pack_start(*manage(pb), Gtk::PACK_SHRINK);
1519         fbox = pb;
1520     }
1521     fbox->get_style_context()->add_class("rackmain");
1522 
1523     string css_id = plugin.plugin->get_pdef()->id;
1524     boost::replace_all(css_id, ".", "-");
1525     set_name("PLUI-" + css_id);
1526     Glib::RefPtr<Gtk::StyleContext> context = get_style_context();
1527     context->add_class("rackbox");
1528     if (plugin.plugin->get_pdef()->flags & gx_engine::PGNI_IS_LV2) {
1529         context->add_class("PL-LV2");
1530         fbox->get_style_context()->add_class("PL-LV2");
1531         mbox.get_style_context()->add_class("PL-LV2");
1532     } else if (plugin.plugin->get_pdef()->flags & gx_engine::PGNI_IS_LADSPA) {
1533         context->add_class("PL-LADSPA");
1534         fbox->get_style_context()->add_class("PL-LADSPA");
1535         mbox.get_style_context()->add_class("PL-LADSPA");
1536     }
1537     on_off_switch.signal_toggled().connect(
1538         sigc::mem_fun(plugin, &PluginUI::on_state_change));
1539     show();
1540 }
1541 
init_dnd()1542 void RackBox::init_dnd() {
1543     target = "application/x-guitarix-";
1544     if (plugin.get_type() == PLUGIN_TYPE_MONO) {
1545         target += "mono";
1546     } else {
1547         target += "stereo";
1548     }
1549     mbox.signal_drag_begin().connect(sigc::mem_fun(*this, &RackBox::on_my_drag_begin));
1550     mbox.signal_drag_end().connect(sigc::mem_fun(*this, &RackBox::on_my_drag_end));
1551     mbox.signal_drag_failed().connect(sigc::mem_fun(*this, &RackBox::on_my_drag_failed));
1552     mbox.signal_drag_data_get().connect(sigc::mem_fun(*this, &RackBox::on_my_drag_data_get));
1553 }
1554 
enable_drag(bool v)1555 void RackBox::enable_drag(bool v) {
1556     if (v) {
1557         std::vector<Gtk::TargetEntry> listTargets;
1558         listTargets.push_back(Gtk::TargetEntry(target, Gtk::TARGET_SAME_APP, 0));
1559         mbox.drag_source_set(listTargets, Gdk::BUTTON1_MASK, Gdk::ACTION_MOVE);
1560     } else {
1561         mbox.drag_source_unset();
1562     }
1563 }
1564 
get_parent()1565 RackContainer *RackBox::get_parent() {
1566     return dynamic_cast<RackContainer*>(Gtk::VBox::get_parent());
1567 }
1568 
on_my_drag_begin(const Glib::RefPtr<Gdk::DragContext> & context)1569 void RackBox::on_my_drag_begin(const Glib::RefPtr<Gdk::DragContext>& context) {
1570     int x, y;
1571     get_pointer(x, y);
1572     drag_icon = new DragIcon(plugin, context, plugin_dict.get_options(), x);
1573     plugin.remove(true);
1574     get_parent()->change_child_count(-1);
1575 }
1576 
animate_create()1577 bool RackBox::animate_create() {
1578     bool ret = true;
1579     anim_height += anim_step;
1580     if (anim_height >= target_height) {
1581         set_visibility(true);
1582         set_size_request(-1,-1);
1583         ret = false;
1584     } else {
1585         set_size_request(-1, anim_height);
1586     }
1587     get_parent()->ensure_visible(*this);
1588     return ret;
1589 }
1590 
animate_insert()1591 void RackBox::animate_insert() {
1592     if (!plugin_dict.use_animations()) {
1593         show();
1594         get_parent()->ensure_visible(*this);
1595     } else {
1596         if (anim_tag.connected()) {
1597             hide();
1598             anim_tag.disconnect();
1599             set_size_request(-1,-1);
1600         }
1601         show();
1602         gint min_height, natural_height;
1603         get_preferred_height(min_height, natural_height); // needs show and visibility true
1604         target_height = min_height;
1605         set_size_request(-1,0);
1606         set_visibility(false);
1607         anim_height = 0;
1608         anim_step = target_height / ANIMATE_STEPS;
1609         anim_tag = Glib::signal_timeout().connect(mem_fun(*this, &RackBox::animate_create), ANIMATE_TIME);
1610     }
1611 }
1612 
on_my_drag_end(const Glib::RefPtr<Gdk::DragContext> & context)1613 void RackBox::on_my_drag_end(const Glib::RefPtr<Gdk::DragContext>& context) {
1614     if (drag_icon) {
1615         delete drag_icon;
1616         drag_icon = 0;
1617     }
1618     if (plugin.plugin->get_box_visible()) {
1619         animate_insert();
1620     }
1621 }
1622 
on_my_drag_failed(const Glib::RefPtr<Gdk::DragContext> & context,Gtk::DragResult result)1623 bool RackBox::on_my_drag_failed(const Glib::RefPtr<Gdk::DragContext>& context, Gtk::DragResult result) {
1624     get_parent()->change_child_count(1);
1625     return false;
1626 }
1627 
on_my_drag_data_get(const Glib::RefPtr<Gdk::DragContext> & context,Gtk::SelectionData & selection,int info,int timestamp)1628 void RackBox::on_my_drag_data_get(const Glib::RefPtr<Gdk::DragContext>& context, Gtk::SelectionData& selection, int info, int timestamp) {
1629     selection.set(target, plugin.get_id());
1630 }
1631 
vis_switch(Gtk::Widget & a,Gtk::Widget & b)1632 void RackBox::vis_switch(Gtk::Widget& a, Gtk::Widget& b) {
1633     a.hide();
1634     b.show();
1635 }
1636 
set_visibility(bool v)1637 void RackBox::set_visibility(bool v) {
1638     bool config_mode = plugin_dict.get_config_mode();
1639     if (config_mode || get_plug_visible()) {
1640         mbox.set_visible(v);
1641         set_config_mode(plugin_dict.get_config_mode());
1642     } else {
1643         fbox->set_visible(v);
1644     }
1645 }
1646 
swtch(bool mini)1647 void RackBox::swtch(bool mini) {
1648     plugin.plugin->set_plug_visible(mini);
1649     plugin.on_state_change();
1650     if (mini || plugin_dict.get_config_mode()) {
1651         vis_switch(*fbox, mbox);
1652     } else {
1653         vis_switch(mbox, *fbox);
1654     }
1655 }
1656 
set_config_mode(bool mode)1657 void RackBox::set_config_mode(bool mode) {
1658     if (!can_compress() || !get_plug_visible()) {
1659         if (mode) {
1660             vis_switch(*fbox, mbox);
1661             if (strcmp(plugin.get_id(), "ampstack") == 0) { // FIXME
1662                 return;
1663             }
1664         } else {
1665             vis_switch(mbox, *fbox);
1666         }
1667     }
1668     minibox->set_config_mode(mode, plugin);
1669     enable_drag(mode);
1670 }
1671 
set_position(int pos,int post_pre)1672 void RackBox::set_position(int pos, int post_pre) {
1673     plugin.plugin->set_position(pos);
1674     if (plugin.get_type() == PLUGIN_TYPE_MONO) {
1675         plugin.plugin->set_effect_post_pre(post_pre);
1676     }
1677 }
1678 
compare_position(int pos,int post_pre)1679 bool RackBox::compare_position(int pos, int post_pre) {
1680     if (plugin.plugin->get_position() != pos) {
1681         return false;
1682     }
1683     if (plugin.get_type() == PLUGIN_TYPE_MONO && plugin.plugin->get_effect_post_pre() != post_pre) {
1684         return false;
1685     }
1686     return true;
1687 }
1688 
do_expand()1689 void RackBox::do_expand() {
1690     swtch(false);
1691     Glib::signal_idle().connect_once(
1692         sigc::bind(
1693             sigc::mem_fun(get_parent(), &RackContainer::ensure_visible),
1694             sigc::ref(*this)));
1695 }
1696 
make_expand_button(bool expand)1697 Gtk::Button *RackBox::make_expand_button(bool expand) {
1698     const gchar *t;
1699     Gtk::Button *b = new Gtk::Button();
1700     //b->set_relief(Gtk::RELIEF_NONE);
1701     if (expand) {
1702         t = "rack_expand";
1703         gx_gui::GxBuilder::set_tooltip_text_connect_handler(*b, _("expand effect unit"));
1704     } else {
1705         t = "rack_shrink";
1706         gx_gui::GxBuilder::set_tooltip_text_connect_handler(*b, _("shrink effect unit"));
1707     }
1708     Gtk::Image *l = new Gtk::Image();
1709     l->Gtk::Image::set_from_icon_name(t, Gtk::ICON_SIZE_BUTTON);
1710     b->set_focus_on_click(false);
1711     b->add(*manage(l));
1712     b->set_name("effect_on_off");
1713     if (expand) {
1714         b->signal_clicked().connect(
1715             sigc::mem_fun(*this, &RackBox::do_expand));
1716     } else {
1717         b->signal_clicked().connect(
1718             sigc::bind(sigc::mem_fun(*this, &RackBox::swtch), true));
1719     }
1720     return b;
1721 }
1722 
make_preset_button()1723 Gtk::Button *RackBox::make_preset_button() {
1724     Gtk::Button *p = new Gtk::Button();
1725     //p->set_relief(Gtk::RELIEF_NONE);
1726     Gtk::Image *l = new Gtk::Image();
1727     l->Gtk::Image::set_from_icon_name("rack_preset", Gtk::ICON_SIZE_BUTTON);
1728     p->add(*manage(l));
1729     p->set_can_default(false);
1730     p->set_can_focus(false);
1731     gx_gui::GxBuilder::set_tooltip_text_connect_handler(*p, _("manage effect unit presets"));
1732     p->set_name("effect_on_off");
1733     p->signal_clicked().connect(
1734         sigc::mem_fun(plugin, &PluginUI::on_plugin_preset_popup));
1735     return p;
1736 }
1737 
pack(Gtk::Widget * main,Gtk::Widget * mini)1738 void RackBox::pack(Gtk::Widget *main, Gtk::Widget *mini) {
1739     if (!main) {
1740         return;
1741     }
1742     box.pack_start(*manage(main));
1743     minibox->pack(mini);
1744 }
1745 
make_full_box(gx_system::CmdlineOptions & options)1746 Gtk::HBox *RackBox::make_full_box(gx_system::CmdlineOptions& options) {
1747     Gtk::HBox *bx = new Gtk::HBox();
1748     Gtk::Widget *effect_label = make_label(plugin, options, false);
1749 
1750     // overall hbox: drag-button - center vbox - drag button
1751     Gtk::HBox *main   = new Gtk::HBox();
1752     // center vbox containing title bar and widgets
1753     Gtk::VBox *center = new Gtk::VBox();
1754     // title vbox on top
1755     Gtk::HBox *top    = new Gtk::HBox();
1756 
1757     // spacing for bottom shadow
1758     Gtk::Alignment *al = new Gtk::Alignment();
1759     al->set_padding(0, 4, 0, 0);
1760     al->add(*manage(main));
1761 
1762     main->set_spacing(0);
1763 
1764     center->set_name("rack_unit_center");
1765     center->set_border_width(0);
1766     center->set_spacing(4);
1767     center->pack_start(*manage(top), Gtk::PACK_SHRINK);
1768     center->pack_start(box, Gtk::PACK_EXPAND_WIDGET);
1769 
1770     top->set_spacing(4);
1771     top->set_name("rack_unit_title_bar");
1772 
1773     top->pack_start(on_off_switch, Gtk::PACK_SHRINK);
1774     on_off_switch.set_name("effect_on_off");
1775     top->pack_start(*manage(effect_label), Gtk::PACK_SHRINK);
1776     top->pack_end(*manage(make_expand_button(false)), Gtk::PACK_SHRINK);
1777     if (!(plugin.plugin->get_pdef()->flags & PGN_NO_PRESETS))
1778         top->pack_end(*manage(make_preset_button()), Gtk::PACK_SHRINK);
1779 
1780     main->pack_start(*manage(wrap_bar()), Gtk::PACK_SHRINK);
1781     main->pack_start(*manage(center), Gtk::PACK_EXPAND_WIDGET);
1782     main->pack_end(*manage(wrap_bar()), Gtk::PACK_SHRINK);
1783 
1784     main->set_name(plugin.get_id());
1785     bx->pack_start(*manage(al), Gtk::PACK_EXPAND_WIDGET);
1786     //al->show_all();
1787     bx->show_all();
1788     return bx;
1789 }
1790 
1791 
1792 /****************************************************************
1793  ** class RackContainer
1794  */
1795 
1796 static const int min_containersize = 40;
1797 
RackContainer(PluginDict & plugin_dict_)1798 RackContainer::RackContainer(PluginDict& plugin_dict_)
1799     : Gtk::VBox(),
1800       plugin_dict(plugin_dict_),
1801       in_drag(-2),
1802       count(0),
1803       targets(),
1804       othertargets(),
1805       highlight_connection(),
1806       autoscroll_connection() {
1807     set_size_request(-1, min_containersize);
1808     show_all();
1809 }
1810 
set_list_targets(const std::vector<Gtk::TargetEntry> & listTargets,const std::vector<std::string> & targets_,const std::vector<std::string> & othertargets_)1811 void RackContainer::set_list_targets(const std::vector<Gtk::TargetEntry>& listTargets,
1812                                      const std::vector<std::string>& targets_,
1813                                      const std::vector<std::string>& othertargets_) {
1814     targets = targets_;
1815     othertargets = othertargets_;
1816     drag_dest_set(listTargets, Gtk::DEST_DEFAULT_DROP, Gdk::ACTION_MOVE);
1817 }
1818 
drag_highlight_draw(const Cairo::RefPtr<Cairo::Context> & cr,int y0)1819 bool RackContainer::drag_highlight_draw(const Cairo::RefPtr<Cairo::Context> &cr, int y0) {
1820     if (!get_is_drawable()) {
1821         return false;
1822     }
1823     int x, y, width, height;
1824     if (!get_has_window()) {
1825         Gtk::Allocation a = get_allocation();
1826         x      = 0;
1827         y      = 0;
1828         width  = a.get_width();
1829         height = a.get_height();
1830     } else {
1831         get_window()->get_geometry(x, y, width, height);
1832         x = 0;
1833         y = 0;
1834     }
1835     Glib::RefPtr<Gdk::Pixbuf> pb_ = Gtk::IconTheme::get_default()->load_icon("insert", -1, Gtk::ICON_LOOKUP_GENERIC_FALLBACK);
1836     if (pb_) {
1837         Gdk::Cairo::set_source_pixbuf(cr, pb_, x, y);
1838         cr->get_source()->set_extend(Cairo::EXTEND_REPEAT);
1839         if (y0 < 0) {
1840             cr->set_line_width(4.0);
1841             cr->rectangle(x, max(0, y), width, height);
1842             cr->stroke();
1843         } else {
1844             cr->rectangle(x, max(y, y0 - 3), width, 2);
1845             cr->fill();
1846         }
1847     }
1848     return false;
1849 }
1850 
1851 struct childpos {
1852     int y0, y1, pos;
childposchildpos1853     childpos(int y0_, int y1_, int pos_): y0(y0_), y1(y1_), pos(pos_) {}
operator <childpos1854     bool operator<(const childpos& p) {
1855         return y0 < p.y0;
1856     }
1857 };
1858 
find_index(int x,int y,string * before,int * ypos)1859 void RackContainer::find_index(int x, int y, string *before, int *ypos) {
1860     std::list<childpos> l;
1861     std::vector<RackBox*> children = get_children();
1862     int mpos = -1;
1863     for (std::vector<RackBox*>::iterator ch = children.begin(); ch != children.end(); ++ch) {
1864         ++mpos;
1865         if (!(*ch)->get_visible()) {
1866             continue;
1867         }
1868         Gtk::Allocation a = (*ch)->get_allocation();
1869         l.push_back(childpos(a.get_y(), a.get_y()+a.get_height(), mpos));
1870     }
1871     if (l.empty()) {
1872         if (before) {
1873             *before = "";
1874         }
1875         *ypos = -1;
1876         return;
1877     }
1878     Gtk::Allocation a0 = get_allocation();
1879     y += a0.get_y();
1880     int sy = l.begin()->y0;
1881     for (std::list<childpos>::iterator cp = l.begin(); cp != l.end(); ++cp) {
1882         if (y < (cp->y0 + cp->y1) / 2) {
1883             if (before) {
1884                 *before = children[cp->pos]->get_id();
1885             }
1886             *ypos = (cp->y0+sy)/2 - a0.get_y();
1887             return;
1888         }
1889         sy = cp->y1;
1890     }
1891     if (before) {
1892         *before = "";
1893     }
1894     *ypos = sy - a0.get_y();
1895 }
1896 
check_targets(const std::vector<std::string> & tgts1,const std::vector<std::string> & tgts2)1897 bool RackContainer::check_targets(const std::vector<std::string>& tgts1, const std::vector<std::string>& tgts2) {
1898     for (std::vector<std::string>::const_iterator t1 = tgts1.begin(); t1 != tgts1.end(); ++t1) {
1899         for (std::vector<std::string>::const_iterator t2 = tgts2.begin(); t2 != tgts2.end(); ++t2) {
1900             if (*t1 == *t2) {
1901                 return true;
1902             }
1903         }
1904     }
1905     return false;
1906 }
1907 
on_drag_motion(const Glib::RefPtr<Gdk::DragContext> & context,int x,int y,guint timestamp)1908 bool RackContainer::on_drag_motion(const Glib::RefPtr<Gdk::DragContext>& context, int x, int y, guint timestamp) {
1909     const std::vector<std::string>& tg = context->list_targets();
1910     if (!check_targets(tg, targets)) {
1911         if (check_targets(tg, othertargets)) {
1912             if (!autoscroll_connection.connected()) {
1913                 autoscroll_connection = Glib::signal_timeout().connect(
1914                                             sigc::mem_fun(*this, &RackContainer::scrollother_timeout), AUTOSCROLL_TIMEOUT);
1915             }
1916             context->drag_status(Gdk::DragAction(0), timestamp);
1917             return true;
1918         }
1919         return false;
1920     }
1921     context->drag_status(Gdk::ACTION_MOVE, timestamp);
1922     int ind;
1923     find_index(x, y, nullptr, &ind);
1924     if (in_drag == ind) {
1925         return true;
1926     }
1927     if (in_drag > -2) {
1928         highlight_connection.disconnect();
1929     }
1930     highlight_connection = signal_draw().connect(sigc::bind(sigc::mem_fun(*this, &RackContainer::drag_highlight_draw), ind), true);
1931     queue_draw();
1932     in_drag = ind;
1933     if (!autoscroll_connection.connected()) {
1934         autoscroll_connection = Glib::signal_timeout().connect(
1935                                     sigc::mem_fun(*this, &RackContainer::scroll_timeout), AUTOSCROLL_TIMEOUT);
1936     }
1937     return true;
1938 }
1939 
ensure_visible(RackBox & child)1940 void RackContainer::ensure_visible(RackBox& child) {
1941     Gtk::Allocation alloc = child.get_allocation();
1942     Gtk::Viewport *p = dynamic_cast<Gtk::Viewport*>(get_ancestor(GTK_TYPE_VIEWPORT));
1943     p->get_vadjustment()->clamp_page(alloc.get_y(), alloc.get_y()+alloc.get_height());
1944 }
1945 
1946 static const double scroll_edge_size = 60.0;
1947 static const int step_size = 20;
1948 
scrollother_timeout()1949 bool RackContainer::scrollother_timeout() {
1950     Gtk::Viewport *p = dynamic_cast<Gtk::Viewport*>(get_ancestor(GTK_TYPE_VIEWPORT));
1951     Glib::RefPtr<Gtk::Adjustment> a = p->get_vadjustment();
1952     double off = a->get_value();
1953     Gtk::Allocation alloc = get_allocation();
1954     int x, y;
1955     get_pointer(x, y);
1956     y -= alloc.get_height();
1957     double step;
1958     if (y < -scroll_edge_size) {
1959         step = step_size;
1960     } else {
1961         step = step_size * exp(-(y+scroll_edge_size)/(1.0*scroll_edge_size));
1962         if (step < 1.5) {
1963             return false;
1964         }
1965     }
1966     off = plugin_dict.stop_at(this, off, step_size, a->get_page_size());
1967     if (off < a->get_lower()) {
1968         off = a->get_lower();
1969     }
1970     if (off > a->get_upper() - a->get_page_size()) {
1971         off = a->get_upper() - a->get_page_size();
1972     }
1973     a->set_value(off);
1974     return true;
1975 }
1976 
scroll_timeout()1977 bool RackContainer::scroll_timeout() {
1978     Gtk::Viewport *p = dynamic_cast<Gtk::Viewport*>(get_ancestor(GTK_TYPE_VIEWPORT));
1979     Glib::RefPtr<Gtk::Adjustment> a = p->get_vadjustment();
1980     double off = a->get_value();
1981     Gtk::Allocation alloc = get_allocation();
1982     int x, y;
1983     get_pointer(x, y);
1984     double sez = scroll_edge_size;
1985     if (sez > a->get_page_size() / 3) {
1986         sez = a->get_page_size() / 3;
1987     }
1988     double yw = y + alloc.get_y() - off;
1989     double step;
1990     if (yw <= sez) {
1991         step = step_size * (sez-yw) / sez;
1992         off = max(double(alloc.get_y()), off-step);
1993     } else {
1994         yw = a->get_page_size() - yw;
1995         if (yw <= sez) {
1996             step = step_size * (sez-yw) / sez;
1997             off = min(alloc.get_y()+alloc.get_height()-a->get_page_size(), off+step);
1998         } else {
1999             return true;
2000         }
2001     }
2002     if (off < a->get_lower()) {
2003         off = a->get_lower();
2004     }
2005     if (off > a->get_upper() - a->get_page_size()) {
2006         off = a->get_upper() - a->get_page_size();
2007     }
2008     a->set_value(off);
2009     return true;
2010 }
2011 
stop_at_bottom(double off,double step_size,double pagesize)2012 double RackContainer::stop_at_bottom(double off, double step_size, double pagesize) {
2013     Gtk::Allocation alloc = get_allocation();
2014     double lim = alloc.get_y() + alloc.get_height() - pagesize;
2015     if (off >= lim) {
2016         return off;
2017     }
2018     return min(off+step_size, lim);
2019 }
2020 
stop_at_top(double off,double step_size)2021 double RackContainer::stop_at_top(double off, double step_size) {
2022     Gtk::Allocation alloc = get_allocation();
2023     if (off < alloc.get_y()) {
2024         return off;
2025     }
2026     return max(off-step_size, double(alloc.get_y()));
2027 }
2028 
on_drag_leave(const Glib::RefPtr<Gdk::DragContext> & context,guint timestamp)2029 void RackContainer::on_drag_leave(const Glib::RefPtr<Gdk::DragContext>& context, guint timestamp) {
2030     if (in_drag > -2) {
2031         highlight_connection.disconnect();
2032         queue_draw();
2033         in_drag = -2;
2034     }
2035     autoscroll_connection.disconnect();
2036 }
2037 
on_drag_data_received(const Glib::RefPtr<Gdk::DragContext> & context,int x,int y,const Gtk::SelectionData & data,guint info,guint timestamp)2038 void RackContainer::on_drag_data_received(const Glib::RefPtr<Gdk::DragContext>& context, int x, int y, const Gtk::SelectionData& data, guint info, guint timestamp) {
2039     int ind;
2040     string before;
2041     find_index(x, y, &before, &ind);
2042     string id = data.get_data_as_string();
2043     std::string dtype = data.get_data_type();
2044     if (dtype == "application/x-gtk-tool-palette-item-mono" || dtype == "application/x-gtk-tool-palette-item-stereo") {
2045         plugin_dict.activate(id, before, true);
2046     } else {
2047         reorder(id, before);
2048         plugin_dict[id]->show(true);
2049     }
2050 }
2051 
on_add(Widget * ch)2052 void RackContainer::on_add(Widget *ch) {
2053     assert(false);
2054 }
2055 
add(RackBox & r,int pos)2056 void RackContainer::add(RackBox& r, int pos) {
2057     pack_start(r, false, true);
2058     if (pos != -1) {
2059         reorder_child(r, pos);
2060     }
2061 }
2062 
set_child_count(int n)2063 void RackContainer::set_child_count(int n) {
2064     if (n == count) {
2065         return;
2066     }
2067     count = n;
2068     if (!count) {
2069         set_size_request(-1, min_containersize);
2070     } else {
2071         set_size_request(-1, -1);
2072     }
2073 }
2074 
renumber()2075 void RackContainer::renumber() {
2076     rackbox_list l = get_children();
2077     int pos = 0;
2078     unsigned int post_pre = 1;
2079     for (rackbox_list::iterator c = l.begin(); c != l.end(); ++c, ++pos) {
2080         if (strcmp((*c)->get_id(), "ampstack") == 0) { // FIXME
2081             pos = 0;
2082             post_pre = 0;
2083             continue;
2084         }
2085         (*c)->set_position(pos, post_pre);
2086     }
2087 }
2088