1 /*
2  * Copyright (C) 2009, 2010 Hermann Meyer, James Warden, Andreas Degert
3  * Copyright (C) 2011 Pete Shorthose
4  *
5  * This program is free software; you can redistribute it and/or modify
6  * it under the terms of the GNU General Public License as published by
7  * the Free Software Foundation; either version 2 of the License, or
8  * (at your option) any later version.
9  *
10  * This program is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  * GNU General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License
16  * along with this program; if not, write to the Free Software
17  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
18  * ---------------------------------------------------------------------------
19  *
20  *    This is the gx_head GUI main class
21  *
22  * ----------------------------------------------------------------------------
23  */
24 
25 #include "guitarix.h"         // NOLINT
26 
27 #include <iomanip>            // NOLINT
28 #include <cstring>            // NOLINT
29 #include <string>             // NOLINT
30 #include <boost/algorithm/string/replace.hpp>
31 #include <gxw/GxControlParameter.h>
32 
33 namespace gx_gui {
34 
35 /* ----- load a top level window from gtk builder file ------ */
36 
load_toplevel(GtkBuilder * builder,const char * filename,const char * windowname)37 GtkWidget *load_toplevel(GtkBuilder *builder, const char* filename, const char* windowname) {
38     string fname = gx_system::get_options().get_builder_filepath(filename);
39     GError *err = NULL;
40     if (!gtk_builder_add_from_file(builder, fname.c_str(), &err)) {
41         g_object_unref(G_OBJECT(builder));
42         gx_print_fatal(_("gtk builder"), err->message);
43         g_error_free(err);
44         return NULL;
45     }
46     GtkWidget *w = GTK_WIDGET(gtk_builder_get_object(builder, windowname));
47     if (!w) {
48         g_object_unref(G_OBJECT(builder));
49         gx_print_fatal(_("gtk builder"), string(windowname)+_(" not found in ")+fname);
50         return NULL;
51     }
52     gtk_builder_connect_signals(builder, 0);
53     return w;
54 }
55 
56 /****************************************************************
57  ** UiBuilder implementation
58  */
59 
60 StackBoxBuilder *UiBuilderImpl::intf = 0;
61 
UiBuilderImpl(PluginDict * i,StackBoxBuilder * b,std::vector<PluginUI * > * pl,PluginUI * pluginui)62 UiBuilderImpl::UiBuilderImpl(PluginDict *i, StackBoxBuilder *b, std::vector<PluginUI*> *pl, PluginUI *pluginui)
63       : UiBuilderBase(), plugin_dict(*i), pluginlist(pl) {
64     intf = b;
65     intf->set_current_plugin(pluginui);
66     openTabBox = openTabBox_;
67     openVerticalBox = openVerticalBox_;
68     openVerticalBox1 = openVerticalBox1_;
69     openVerticalBox2 = openVerticalBox2_;
70     openHorizontalBox = openHorizontalBox_;
71     openHorizontalhideBox = openHorizontalhideBox_;
72     openHorizontalTableBox = openHorizontalTableBox_;
73     openFrameBox = openFrameBox_;
74     openFlipLabelBox = openFlipLabelBox_;
75     openpaintampBox = openpaintampBox_;
76     closeBox = closeBox_;
77     load_glade = load_glade_;
78     load_glade_file = load_glade_file_;
79     create_master_slider = create_master_slider_;
80     create_feedback_slider = create_feedback_slider_;
81     create_mid_rackknob = create_mid_rackknob_;
82     create_small_rackknob = create_small_rackknob_;
83     create_small_rackknobr = create_small_rackknobr_;
84     create_big_rackknob = create_big_rackknob_;
85     create_spin_value = create_spin_value_;
86     create_switch = create_switch_;
87     create_switch_no_caption = create_switch_no_caption_;
88     create_feedback_switch = create_feedback_switch_;
89     create_fload_switch = create_fload_switch_;
90     create_wheel = create_wheel_;
91     create_selector = create_selector_;
92     create_simple_meter = create_simple_meter_;
93     create_simple_c_meter = create_simple_c_meter_;
94     create_selector_no_caption = create_selector_no_caption_;
95     create_port_display = create_port_display_;
96     create_p_display = create_p_display_;
97     create_simple_spin_value = create_simple_spin_value_;
98     create_eq_rackslider_no_caption = create_eq_rackslider_no_caption_;
99     set_next_flags = set_next_flags_;
100     insertSpacer = insertSpacer_;
101 };
102 
~UiBuilderImpl()103 UiBuilderImpl::~UiBuilderImpl() {
104     intf->set_current_plugin(nullptr);
105 }
106 
load_unit(PluginDef * pd)107 bool UiBuilderImpl::load_unit(PluginDef *pd) {
108     if (!pd->load_ui) {
109 	return false;
110     }
111     intf->prepare();
112     plugin = pd;
113     int form = UI_FORM_GLADE|UI_FORM_STACK;
114 #ifndef NDEBUG
115     if (getenv("GUITARIX_FORM_STACK")) {
116         form = UI_FORM_STACK;
117     }
118 #endif
119     pd->load_ui(*this, form);
120     return true;
121 }
122 
openTabBox_(const char * label)123 void UiBuilderImpl::openTabBox_(const char* label) {
124     intf->openTabBox(label);
125 }
126 
openVerticalBox_(const char * label)127 void UiBuilderImpl::openVerticalBox_(const char* label) {
128     intf->openVerticalBox(label);
129 }
130 
openVerticalBox1_(const char * label)131 void UiBuilderImpl::openVerticalBox1_(const char* label) {
132     intf->openVerticalBox1(label);
133 }
134 
openVerticalBox2_(const char * label)135 void UiBuilderImpl::openVerticalBox2_(const char* label) {
136     intf->openVerticalBox2(label);
137 }
138 
openHorizontalhideBox_(const char * label)139 void UiBuilderImpl::openHorizontalhideBox_(const char* label) {
140     intf->openHorizontalhideBox(label);
141 }
142 
openHorizontalTableBox_(const char * label)143 void UiBuilderImpl::openHorizontalTableBox_(const char* label) {
144     intf->openHorizontalTableBox(label);
145 }
146 
openHorizontalBox_(const char * label)147 void UiBuilderImpl::openHorizontalBox_(const char* label) {
148     intf->openHorizontalBox(label);
149 }
150 
openFrameBox_(const char * label)151 void UiBuilderImpl::openFrameBox_(const char* label) {
152     intf->openFrameBox(label);
153 }
154 
openFlipLabelBox_(const char * label)155 void UiBuilderImpl::openFlipLabelBox_(const char* label) {
156     intf->openFlipLabelBox(label);
157 }
158 
openpaintampBox_(const char * label)159 void UiBuilderImpl::openpaintampBox_(const char* label) {
160     intf->openpaintampBox(label);
161 }
162 
insertSpacer_()163 void UiBuilderImpl::insertSpacer_() {
164     intf->insertSpacer();
165 }
166 
set_next_flags_(int flags)167 void UiBuilderImpl::set_next_flags_(int flags) {
168     intf->set_next_flags(flags);
169 }
170 
create_mid_rackknob_(const char * id,const char * label)171 void UiBuilderImpl::create_mid_rackknob_(const char *id, const char *label) {
172     intf->create_mid_rackknob(id, label);
173 }
174 
create_small_rackknob_(const char * id,const char * label)175 void UiBuilderImpl::create_small_rackknob_(const char *id, const char *label) {
176     intf->create_small_rackknob(id, label);
177 }
178 
create_small_rackknobr_(const char * id,const char * label)179 void UiBuilderImpl::create_small_rackknobr_(const char *id, const char *label) {
180     intf->create_small_rackknobr(id, label);
181 }
182 
create_big_rackknob_(const char * id,const char * label)183 void UiBuilderImpl::create_big_rackknob_(const char *id, const char *label) {
184     intf->create_big_rackknob(id, label);
185 }
186 
create_master_slider_(const char * id,const char * label)187 void UiBuilderImpl::create_master_slider_(const char *id, const char *label) {
188     intf->create_master_slider(id, label);
189 }
190 
create_feedback_slider_(const char * id,const char * label)191 void UiBuilderImpl::create_feedback_slider_(const char *id, const char *label) {
192     intf->create_feedback_slider(id, label);
193 }
194 
create_selector_no_caption_(const char * id)195 void UiBuilderImpl::create_selector_no_caption_(const char *id) {
196     intf->create_selector(id, "");
197 }
198 
create_selector_(const char * id,const char * label)199 void UiBuilderImpl::create_selector_(const char *id, const char *label) {
200     intf->create_selector_with_caption(id, label);
201 }
202 
create_simple_meter_(const char * id)203 void UiBuilderImpl::create_simple_meter_(const char *id) {
204     intf->create_simple_meter(id);
205 }
206 
create_simple_c_meter_(const char * id,const char * idl,const char * label)207 void UiBuilderImpl::create_simple_c_meter_(const char *id, const char *idl, const char *label) {
208     intf->create_simple_c_meter(id, idl, label);
209 }
210 
create_spin_value_(const char * id,const char * label)211 void UiBuilderImpl::create_spin_value_(const char *id, const char *label) {
212     intf->create_spin_value(id, label);
213 }
214 
create_switch_no_caption_(const char * sw_type,const char * id)215 void UiBuilderImpl::create_switch_no_caption_(const char *sw_type, const char * id) {
216     intf->create_switch_no_caption(sw_type, id);
217 }
218 
create_feedback_switch_(const char * sw_type,const char * id)219 void UiBuilderImpl::create_feedback_switch_(const char *sw_type, const char * id) {
220     intf->create_feedback_switch(sw_type, id);
221 }
222 
create_fload_switch_(const char * sw_type,const char * id,const char * idf)223 void UiBuilderImpl::create_fload_switch_(const char *sw_type, const char * id, const char * idf) {
224     intf->create_fload_switch(sw_type, id, idf);
225 }
226 
create_switch_(const char * sw_type,const char * id,const char * label)227 void UiBuilderImpl::create_switch_(const char *sw_type, const char * id, const char *label) {
228     intf->create_v_switch(sw_type, id, label);
229 }
230 
create_wheel_(const char * id,const char * label)231 void UiBuilderImpl::create_wheel_(const char * id, const char *label) {
232     intf->create_wheel(id, label);
233 }
234 
create_port_display_(const char * id,const char * label)235 void UiBuilderImpl::create_port_display_(const char *id, const char *label) {
236     intf->create_port_display(id, label);
237 }
238 
create_p_display_(const char * id,const char * idl,const char * idh)239 void UiBuilderImpl::create_p_display_(const char *id, const char *idl, const char *idh) {
240     intf->create_p_display(id, idl, idh);
241 }
242 
create_simple_spin_value_(const char * id)243 void UiBuilderImpl::create_simple_spin_value_(const char *id) {
244     intf->create_simple_spin_value(id);
245 }
246 
create_eq_rackslider_no_caption_(const char * id)247 void UiBuilderImpl::create_eq_rackslider_no_caption_(const char *id) {
248     intf->create_eq_rackslider_no_caption(id);
249 }
250 
closeBox_()251 void UiBuilderImpl::closeBox_() {
252     intf->closeBox();
253 }
254 
load_glade_(const char * data)255 void UiBuilderImpl::load_glade_(const char *data) {
256     intf->loadRackFromGladeData(data);
257 }
258 
load_glade_file_(const char * fname)259 void UiBuilderImpl::load_glade_file_(const char *fname) {
260     intf->loadRackFromGladeFile(fname);
261 }
262 
load(gx_engine::Plugin * p)263 bool UiBuilderImpl::load(gx_engine::Plugin *p) {
264     PluginDef *pd = p->get_pdef();
265     if (!(pd->flags & PGN_GUI) || !(pd->flags & gx_engine::PGNI_DYN_POSITION)) {
266 	return false;
267     }
268     plugin_dict.add_plugin(*pluginlist, pd->id, "");
269     return true;
270 }
271 
272 } /* end of gx_gui namespace */
273 
274 /****************************************************************
275  ** class GxBuilder
276  */
277 
278 // GList(GObject*) helper class must be defined in other namespace
279 namespace Glib { namespace Container_Helpers {
280 template <>
281 struct TypeTraits<GObject*> {
282     typedef GObject *CppType;
283     typedef GObject *CType;
284     typedef GObject *CTypeNonConst;
285 
to_c_typeGlib::Container_Helpers::TypeTraits286     static CType to_c_type(CppType item) { return item; }
to_cpp_typeGlib::Container_Helpers::TypeTraits287     static CppType to_cpp_type(CType item) { return item; }
release_c_typeGlib::Container_Helpers::TypeTraits288     static void release_c_type(CType) {}
289 };
290 }}
291 
292 namespace gx_gui {
293 
294 bool GxBuilder::show_tooltips = true;
295 
296 //static
create_from_file(const std::string & filename,gx_engine::GxMachineBase * pmach,const char * object_id,sigc::signal<void (bool)> * out_ctr)297 Glib::RefPtr<GxBuilder> GxBuilder::create_from_file(
298     const std::string& filename, gx_engine::GxMachineBase* pmach, const char* object_id, sigc::signal<void(bool)> *out_ctr) {
299     Glib::RefPtr<GxBuilder> builder = GxBuilder::create();
300     try {
301 	if (object_id) {
302 	    builder->add_from_file(filename, object_id);
303 	} else {
304 	    builder->add_from_file(filename);
305 	}
306     } catch(const Glib::FileError& ex) {
307         gx_print_fatal("FileError", ex.what());
308     } catch(const Gtk::BuilderError& ex) {
309         gx_print_fatal("Builder Error", ex.what());
310     }
311     if (pmach) {
312 	builder->fixup_controlparameters(*pmach, out_ctr);
313     }
314     return builder;
315 }
316 
317 //static
create_from_file(const std::string & filename,gx_engine::GxMachineBase * pmach,const Glib::StringArrayHandle & object_ids,sigc::signal<void (bool)> * out_ctr)318 Glib::RefPtr<GxBuilder> GxBuilder::create_from_file(
319     const std::string& filename, gx_engine::GxMachineBase* pmach, const Glib::StringArrayHandle& object_ids, sigc::signal<void(bool)> *out_ctr) {
320     Glib::RefPtr<GxBuilder> builder = GxBuilder::create();
321     try {
322 	builder->add_from_file(filename, object_ids);
323     } catch(const Glib::FileError& ex) {
324         gx_print_fatal("FileError", ex.what());
325     } catch(const Gtk::BuilderError& ex) {
326         gx_print_fatal("Builder Error", ex.what());
327     }
328     if (pmach) {
329 	builder->fixup_controlparameters(*pmach, out_ctr);
330     }
331     return builder;
332 }
333 
334 //static
create_from_string(const Glib::ustring & buffer,gx_engine::GxMachineBase * pmach,const char * object_id,sigc::signal<void (bool)> * out_ctr)335 Glib::RefPtr<GxBuilder> GxBuilder::create_from_string(
336     const Glib::ustring& buffer, gx_engine::GxMachineBase* pmach, const char* object_id, sigc::signal<void(bool)> *out_ctr) {
337     Glib::RefPtr<GxBuilder> builder = GxBuilder::create();
338     try {
339 	if (object_id) {
340 	    builder->add_from_string(buffer, object_id);
341 	} else {
342 	    builder->add_from_string(buffer);
343 	}
344     } catch(const Gtk::BuilderError& ex) {
345         gx_print_fatal("Builder Error", ex.what());
346     }
347     if (pmach) {
348 	builder->fixup_controlparameters(*pmach, out_ctr);
349     }
350     return builder;
351 }
352 
353 //static
create_from_string(const Glib::ustring & buffer,gx_engine::GxMachineBase * pmach,const Glib::StringArrayHandle & object_ids,sigc::signal<void (bool)> * out_ctr)354 Glib::RefPtr<GxBuilder> GxBuilder::create_from_string(
355     const Glib::ustring& buffer, gx_engine::GxMachineBase* pmach, const Glib::StringArrayHandle& object_ids, sigc::signal<void(bool)> *out_ctr) {
356     Glib::RefPtr<GxBuilder> builder = GxBuilder::create();
357     try {
358 	builder->add_from_string(buffer, object_ids);
359     } catch(const Gtk::BuilderError& ex) {
360         gx_print_fatal("Builder Error", ex.what());
361     }
362     if (pmach) {
363 	builder->fixup_controlparameters(*pmach, out_ctr);
364     }
365     return builder;
366 }
367 
get_first_window()368 Gtk::Window *GxBuilder::get_first_window() {
369     Glib::SListHandle<GObject*> objs = Glib::SListHandle<GObject*>(
370 	gtk_builder_get_objects(gobj()), Glib::OWNERSHIP_DEEP);
371     for (Glib::SListHandle<GObject*>::iterator i = objs.begin(); i != objs.end(); ++i) {
372 	if (g_type_is_a(G_OBJECT_TYPE(*i), GTK_TYPE_WINDOW)) {
373 	    return Glib::wrap(GTK_WINDOW(*i), false);
374 	}
375     }
376     assert(false);
377     return 0;
378 }
379 
get_cobject(const Glib::ustring & name)380 GObject* GxBuilder::get_cobject(const Glib::ustring& name)
381 {
382     GObject *cobject = gtk_builder_get_object (gobj(), name.c_str());
383     if(!cobject) {
384 	g_critical("gtkmm: object `%s' not found in GtkBuilder file.", name.c_str());
385 	return 0;
386     }
387     return cobject;
388 }
389 
390 /*
391  ** GxBuilder::fixup_controlparameters + helper classes
392  */
393 
check_get_parameter(gx_engine::GxMachineBase & machine,const std::string & id,Gtk::Widget * w)394 gx_engine::Parameter *check_get_parameter(
395     gx_engine::GxMachineBase& machine, const std::string& id, Gtk::Widget *w) {
396     if (!machine.parameter_hasId(id)) {
397         if (w) {
398             w->set_sensitive(0);
399             w->set_tooltip_text(id); // always shown
400         }
401         gx_print_warning(
402             "load dialog",
403             (boost::format("Parameter variable %1% not found") % id).str());
404         return nullptr;
405     }
406     return &machine.get_parameter(id);
407 }
408 
check_get_float_parameter(gx_engine::GxMachineBase & machine,const std::string & id,Gtk::Widget * w)409 gx_engine::FloatParameter *check_get_float_parameter(
410     gx_engine::GxMachineBase& machine, const std::string& id, Gtk::Widget *w) {
411     gx_engine::Parameter *p = check_get_parameter(machine, id, w);
412     if (p) {
413         if (p->isFloat()) {
414             return &machine.get_parameter(id).getFloat();
415         }
416         gx_print_error(
417             "load dialog",
418             Glib::ustring::compose(
419                 "Continuous Parameter variable %1: type not handled", id));
420     }
421     return nullptr;
422 }
423 
424 /**
425  * uiElement: base class for adding signal connections to
426  * sigc::trackable derived objects
427  *
428  * uiElement derived class instances must be allocated with new() and
429  * will be deleted when the destructor of the sigc::trackable object
430  * is called. This removes all signal connections to member functions
431  * of the derived class instance (by uiElement itself being a
432  * sigc::trackable).
433  */
434 class uiElement: public sigc::trackable {
435 private:
436     static void *destroy(void *p);
437 protected:
uiElement(sigc::trackable * obj)438     uiElement(sigc::trackable *obj) { obj->add_destroy_notify_callback(this, destroy); }
~uiElement()439     virtual ~uiElement() {}
440 };
441 
442 //static
destroy(void * p)443 void *uiElement::destroy(void *p) {
444     delete static_cast<uiElement*>(p);
445     return nullptr;
446 }
447 
448 /**
449  * uiConnector: abstract base class
450  *
451  * does midi controller connection and output variable activation
452  *
453  * derived classes do signal connections to parameter variables
454  */
455 class uiConnector: public uiElement {
456 protected:
457     gx_engine::GxMachineBase& machine;
458     const std::string id;
459     void activate_output(bool state, Gtk::Widget *w);
460     void activate_enable_output(bool state, Gtk::Widget *w);
461     uiConnector(gx_engine::GxMachineBase& machine_, const gx_engine::Parameter& p,
462                 Gtk::Widget *w, sigc::signal<void(bool)> *out_ctr, bool disable);
463 };
464 
uiConnector(gx_engine::GxMachineBase & machine_,const gx_engine::Parameter & p,Gtk::Widget * w,sigc::signal<void (bool)> * out_ctr,bool disable)465 uiConnector::uiConnector(gx_engine::GxMachineBase& machine_, const gx_engine::Parameter& p,
466                          Gtk::Widget *w, sigc::signal<void(bool)> *out_ctr, bool disable)
467     : uiElement(w), machine(machine_), id(p.id()) {
468     if (p.isControllable()) {
469         connect_midi_controller(w, id, machine);
470     }
471     if (p.isOutput()) {
472         if (out_ctr) {
473             if (disable) {
474                 out_ctr->connect(
475                     sigc::bind(
476                         sigc::mem_fun(this, &uiConnector::activate_enable_output),
477                         w));
478             } else {
479                 out_ctr->connect(
480                     sigc::bind(
481                         sigc::mem_fun(this, &uiConnector::activate_output),
482                         w));
483             }
484         }
485     }
486 }
487 
activate_output(bool state,Gtk::Widget * w)488 void uiConnector::activate_output(bool state, Gtk::Widget *w) {
489     machine.set_update_parameter(w, id, state);
490 }
491 
activate_enable_output(bool state,Gtk::Widget * w)492 void uiConnector::activate_enable_output(bool state, Gtk::Widget *w) {
493     machine.set_update_parameter(w, id, state);
494     w->set_sensitive(state);
495 }
496 
497 /**
498  * uiSelector: signal connections for Gxw::Selector widgets
499  */
500 template <class T>
501 class uiSelector: public uiConnector {
502 protected:
503     Gtk::Range *rng;
504     void on_value_changed();
505     void set_value(T v);
506     uiSelector(gx_engine::GxMachineBase& machine, Gtk::Range *rng, const gx_engine::Parameter& p,
507                sigc::signal<void(bool)> *out_ctr, bool disable);
508 public:
create(gx_engine::GxMachineBase & machine,Gtk::Range * rng,const gx_engine::Parameter & p,sigc::signal<void (bool)> * out_ctr,bool disable)509     static uiSelector *create(
510         gx_engine::GxMachineBase& machine, Gtk::Range *rng, const gx_engine::Parameter& p,
511         sigc::signal<void(bool)> *out_ctr, bool disable) {
512         return new uiSelector(machine, rng, p, out_ctr, disable);
513     }
514     static uiSelector *create(
515         gx_engine::GxMachineBase& machine, Gtk::Range *rng, const std::string& id,
516         sigc::signal<void(bool)> *out_ctr, bool disable);
517 };
518 
519 template <class T>
uiSelector(gx_engine::GxMachineBase & machine_,Gtk::Range * rng_,const gx_engine::Parameter & p,sigc::signal<void (bool)> * out_ctr,bool disable)520 uiSelector<T>::uiSelector(gx_engine::GxMachineBase& machine_, Gtk::Range *rng_,
521                           const gx_engine::Parameter& p, sigc::signal<void(bool)> *out_ctr,
522                           bool disable)
523     : uiConnector(machine_, p, rng_, out_ctr, disable), rng(rng_) {
524     set_value(machine.get_parameter_value<T>(id));
525     rng->signal_value_changed().connect(
526 	sigc::mem_fun(*this, &uiSelector::on_value_changed));
527     machine.signal_parameter_value<T>(id).connect(
528 	sigc::mem_fun(this, &uiSelector::set_value));
529 }
530 
531 //static
532 template <class T>
create(gx_engine::GxMachineBase & machine_,Gtk::Range * rng_,const std::string & id_,sigc::signal<void (bool)> * out_ctr,bool disable)533 uiSelector<T> *uiSelector<T>::create(
534     gx_engine::GxMachineBase& machine_, Gtk::Range *rng_, const std::string& id_,
535     sigc::signal<void(bool)> *out_ctr, bool disable) {
536     auto p = check_get_parameter(machine_, id_, rng_);
537     if (!p) {
538         return nullptr;
539     }
540     return new uiSelector<T>(machine_, rng_, p, out_ctr, disable);
541 }
542 
543 template <class T>
set_value(T v)544 void uiSelector<T>::set_value(T v) {
545     rng->set_value(v);
546 }
547 
548 template<class T>
on_value_changed()549 void uiSelector<T>::on_value_changed() {
550     machine.set_parameter_value(id, static_cast<T>(rng->get_value()));
551 }
552 
553 /**
554  * uiToggle: signal connections for Gxw::Switch and Gtk::ToggleButton widgets
555  */
556 template<class T>
557 class uiToggle: public uiConnector {
558 protected:
559     Gtk::ToggleButton* button;
560     void on_button_toggled();
561     void on_parameter_changed(T v);
562     uiToggle(gx_engine::GxMachineBase& machine, Gtk::ToggleButton *b,
563              const gx_engine::Parameter& p, sigc::signal<void(bool)> *out_ctr, bool disable);
564 public:
create(gx_engine::GxMachineBase & machine,Gtk::ToggleButton * b,const gx_engine::Parameter & p,sigc::signal<void (bool)> * out_ctr,bool disable)565     static uiToggle *create(gx_engine::GxMachineBase& machine, Gtk::ToggleButton *b,
566                             const gx_engine::Parameter& p, sigc::signal<void(bool)> *out_ctr,
567                             bool disable) {
568         return new uiToggle(machine, b, p, out_ctr, disable);
569     }
570     static uiToggle *create(gx_engine::GxMachineBase& machine, Gtk::ToggleButton *b,
571                             const string& id, sigc::signal<void(bool)> *out_ctr, bool disable);
572 };
573 
574 template<class T>
uiToggle(gx_engine::GxMachineBase & machine_,Gtk::ToggleButton * b,const gx_engine::Parameter & p,sigc::signal<void (bool)> * out_ctr,bool disable)575 uiToggle<T>::uiToggle(gx_engine::GxMachineBase& machine_, Gtk::ToggleButton *b,
576                       const gx_engine::Parameter& p, sigc::signal<void(bool)> *out_ctr, bool disable)
577     : uiConnector(machine_, p, b, out_ctr, disable), button(b) {
578     button->set_active(machine.get_parameter_value<T>(id));
579     button->signal_toggled().connect(sigc::mem_fun(this, &uiToggle<T>::on_button_toggled));
580     machine.signal_parameter_value<T>(id).connect(
581         sigc::mem_fun(this, &uiToggle<T>::on_parameter_changed));
582 }
583 
584 //static
585 template<class T>
create(gx_engine::GxMachineBase & machine_,Gtk::ToggleButton * b,const string & id_,sigc::signal<void (bool)> * out_ctr,bool disable)586 uiToggle<T> *create(gx_engine::GxMachineBase& machine_, Gtk::ToggleButton *b,
587                     const string& id_, sigc::signal<void(bool)> *out_ctr, bool disable) {
588     auto p = check_get_parameter(machine_, id_, b);
589     if (!p) {
590         return nullptr;
591     }
592     return new uiToggle<T>(machine_, b, p, out_ctr, disable);
593 }
594 
595 template<>
on_button_toggled()596 inline void uiToggle<float>::on_button_toggled() {
597     machine.set_parameter_value(id, static_cast<float>(button->get_active()));
598 }
599 
600 template<>
on_button_toggled()601 inline void uiToggle<bool>::on_button_toggled() {
602     machine.set_parameter_value(id, button->get_active());
603 }
604 
605 template<>
on_parameter_changed(float v)606 inline void uiToggle<float>::on_parameter_changed(float v) {
607     button->set_active(v != 0.0);
608 }
609 
610 template<>
on_parameter_changed(bool v)611 inline void uiToggle<bool>::on_parameter_changed(bool v) {
612     button->set_active(v);
613 }
614 
615 /**
616  * uiController: signal connections for output widgets (Gxw::Fastmeter, Gxw::PortDisplay)
617  */
618 class uiController: public uiConnector {
619 protected:
620     Gxw::ControlParameter *elem;
621     uiController(gx_engine::GxMachineBase& machine, const gx_engine::FloatParameter& p,
622                  Gtk::Widget *w, sigc::signal<void(bool)> *out_ctr, bool disable);
623 public:
create(gx_engine::GxMachineBase & machine,const gx_engine::FloatParameter & p,Gtk::Widget * w,sigc::signal<void (bool)> * out_ctr,bool disable)624     static uiController *create(
625         gx_engine::GxMachineBase& machine, const gx_engine::FloatParameter& p,
626         Gtk::Widget *w, sigc::signal<void(bool)> *out_ctr, bool disable) {
627         return new uiController(machine, p, w, out_ctr, disable);
628     }
629     static uiController *create(
630         gx_engine::GxMachineBase& machine, const std::string& id,
631         Gtk::Widget *w, sigc::signal<void(bool)> *out_ctr, bool disable);
632 };
633 
uiController(gx_engine::GxMachineBase & machine_,const gx_engine::FloatParameter & p,Gtk::Widget * w,sigc::signal<void (bool)> * out_ctr,bool disable)634 uiController::uiController(gx_engine::GxMachineBase& machine_, const gx_engine::FloatParameter& p,
635                            Gtk::Widget *w, sigc::signal<void(bool)> *out_ctr, bool disable)
636     : uiConnector(machine_, p, w, out_ctr, disable),
637       elem(dynamic_cast<Gxw::ControlParameter*>(w)) {
638     elem->cp_configure(p.l_group(), p.l_name(), p.getLowerAsFloat(),
639                        p.getUpperAsFloat(), p.is_log_display());
640     elem->cp_set_value(machine.get_parameter_value<float>(id));
641     machine.signal_parameter_value<float>(id).connect(
642 	sigc::mem_fun(elem, &Gxw::ControlParameter::cp_set_value));
643 }
644 
645 //static
create(gx_engine::GxMachineBase & machine_,const std::string & id_,Gtk::Widget * w,sigc::signal<void (bool)> * out_ctr,bool disable)646 uiController *uiController::create(
647     gx_engine::GxMachineBase& machine_, const std::string& id_,
648     Gtk::Widget *w, sigc::signal<void(bool)> *out_ctr, bool disable) {
649     gx_engine::FloatParameter *p = check_get_float_parameter(machine_, id_, w);
650     if (!p) {
651         return nullptr;
652     }
653     return new uiController(machine_, *p, w, out_ctr, disable);
654 }
655 
656 /**
657  * uiAdjustment: signal connections to Gxw::Regler derived widgets
658  */
659 class uiAdjustment: public uiConnector {
660 private:
661     Gtk::Adjustment *adj;
662     bool log;
663     bool blocked;
664 private:
665     void changed();
666     void on_parameter_changed(float v);
667 protected:
668     uiAdjustment(gx_engine::GxMachineBase& machine_, const gx_engine::FloatParameter& p,
669                  Gxw::Regler *regler, sigc::signal<void(bool)> *out_ctr, bool disable);
670 public:
create(gx_engine::GxMachineBase & machine_,const gx_engine::FloatParameter & p,Gxw::Regler * regler,sigc::signal<void (bool)> * out_ctr,bool disable)671     static uiAdjustment *create(
672         gx_engine::GxMachineBase& machine_, const gx_engine::FloatParameter& p,
673         Gxw::Regler *regler, sigc::signal<void(bool)> *out_ctr, bool disable) {
674         return new uiAdjustment(machine_, p, regler, out_ctr, disable);
675     }
676     static uiAdjustment *create(
677         gx_engine::GxMachineBase& machine_, const std::string& id,
678         Gxw::Regler *regler, sigc::signal<void(bool)> *out_ctr, bool disable);
679 };
680 
changed()681 void uiAdjustment::changed() {
682     if (!blocked) {
683         float v = adj->get_value();
684         if (log) {
685             v = pow(10.0, v);
686         }
687         machine.set_parameter_value(id, v);
688     }
689 }
690 
on_parameter_changed(float v)691 void uiAdjustment::on_parameter_changed(float v) {
692     blocked = true;
693     if (log) {
694         v = log10(v);
695     }
696     adj->set_value(v);
697     blocked = false;
698 }
699 
uiAdjustment(gx_engine::GxMachineBase & machine_,const gx_engine::FloatParameter & p,Gxw::Regler * regler,sigc::signal<void (bool)> * out_ctr,bool disable)700 uiAdjustment::uiAdjustment(gx_engine::GxMachineBase& machine_, const gx_engine::FloatParameter& p,
701                            Gxw::Regler *regler, sigc::signal<void(bool)> *out_ctr,
702                            bool disable)
703     : uiConnector(machine_, p, regler, out_ctr, disable), adj(regler->get_adjustment().get()),
704       log(false), blocked(false) {
705     log = p.is_log_display();
706     float v = machine.get_parameter_value<float>(id);
707     if (log) {
708         v = log10(v);
709 	double up = log10(p.getUpperAsFloat());
710 	double step = log10(p.getStepAsFloat());
711 	regler->cp_configure(p.l_group(), p.l_name(), log10(p.getLowerAsFloat()), up, step);
712 	int prec = 0;
713 	float d = log10((p.getStepAsFloat()-1)*p.getUpperAsFloat());
714 	if (up > 0) {
715 	    prec = up;
716 	    if (d < 0) {
717 		prec -= floor(d);
718 	    }
719 	} else if (d < 0) {
720 	    prec = -floor(d);
721 	}
722 	regler->signal_format_value().connect(
723 	    sigc::bind(
724 		sigc::ptr_fun(logarithmic_format_value),
725 		prec));
726 	regler->signal_input_value().connect(
727 	    sigc::ptr_fun(logarithmic_input_value));
728 	regler->cp_set_value(log10(machine.get_parameter_value<float>(id)));
729     } else {
730 	regler->cp_configure(p.l_group(), p.l_name(), p.getLowerAsFloat(),
731                           p.getUpperAsFloat(), p.getStepAsFloat());
732 	regler->cp_set_value(machine.get_parameter_value<float>(id));
733     }
734     adj->set_value(v);
735     machine.signal_parameter_value<float>(id).connect(
736         sigc::mem_fun(this, &uiAdjustment::on_parameter_changed));
737     adj->signal_value_changed().connect(
738         sigc::mem_fun(this, &uiAdjustment::changed));
739 }
740 
741 //static
create(gx_engine::GxMachineBase & machine_,const std::string & id_,Gxw::Regler * regler,sigc::signal<void (bool)> * out_ctr,bool disable)742 uiAdjustment *uiAdjustment::create(
743     gx_engine::GxMachineBase& machine_, const std::string& id_, Gxw::Regler *regler,
744     sigc::signal<void(bool)> *out_ctr, bool disable) {
745     gx_engine::FloatParameter *p = check_get_float_parameter(machine_, id_, regler);
746     if (!p) {
747         return nullptr;
748     }
749     return new uiAdjustment(machine_, *p, regler, out_ctr, disable);
750 }
751 
ui_connect_switch(gx_engine::GxMachineBase & machine,Gtk::ToggleButton * b,const gx_engine::Parameter & p,sigc::signal<void (bool)> * out_ctr,bool disable)752 void ui_connect_switch(
753     gx_engine::GxMachineBase& machine, Gtk::ToggleButton *b,
754     const gx_engine::Parameter& p, sigc::signal<void(bool)> *out_ctr, bool disable) {
755     auto cp = dynamic_cast<Gxw::ControlParameter*>(b);
756     if (cp) {
757         cp->cp_configure(p.l_group(), p.l_name(), 0, 0, 0);
758     }
759     if (p.isFloat()) {
760 	b->set_active(machine.get_parameter_value<float>(p.id()));
761         uiToggle<float>::create(machine, b, p, out_ctr, false);
762     } else if (p.isBool()) {
763 	b->set_active(machine.get_parameter_value<bool>(p.id()));
764         uiToggle<bool>::create(machine, b, p, out_ctr, false);
765     } else {
766 	gx_print_error(
767 	    "load dialog",
768 	    Glib::ustring::compose("Switch Parameter variable %1: type not handled", p.id()));
769     }
770 }
771 
772 /**
773  * parameter signal connections for Gxw::Switch and Gtk::ToggleButton
774  */
ui_connect_switch(gx_engine::GxMachineBase & machine,Gtk::ToggleButton * b,const std::string & id,sigc::signal<void (bool)> * out_ctr,bool disable)775 bool ui_connect_switch(
776     gx_engine::GxMachineBase& machine, Gtk::ToggleButton *b,
777     const std::string& id, sigc::signal<void(bool)> *out_ctr, bool disable) {
778     gx_engine::Parameter *p = check_get_parameter(machine, id, b);
779     if (!p) {
780         return false;
781     }
782     ui_connect_switch(machine, b, *p, out_ctr, disable);
783     return true;
784 }
785 
make_switch_controller(gx_engine::GxMachineBase & machine,Gtk::Widget * w,const gx_engine::Parameter & p,sigc::signal<void (bool)> * out_ctr)786 static void make_switch_controller(gx_engine::GxMachineBase& machine,
787                                    Gtk::Widget *w,
788                                    const gx_engine::Parameter& p,
789                                    sigc::signal<void(bool)> *out_ctr) {
790     Gtk::ToggleButton *t = dynamic_cast<Gtk::ToggleButton*>(w);
791     if (!t) {
792 	gx_print_error(
793 	    "load dialog",
794 	    Glib::ustring::compose(
795                 "Switch Parameter variable %1: Widget must be ToggleButton based", p.id()));
796         return;
797     }
798     ui_connect_switch(machine, t, p, out_ctr, false);
799 }
800 
make_continuous_controller(gx_engine::GxMachineBase & machine,Gtk::Widget * w,gx_engine::Parameter & p,sigc::signal<void (bool)> * out_ctr)801 static void make_continuous_controller(gx_engine::GxMachineBase& machine,
802                                        Gtk::Widget* w,
803                                        gx_engine::Parameter& p,
804                                        sigc::signal<void(bool)> *out_ctr) {
805     std::string var_id = p.id();
806     if (!p.isFloat()) {
807         // show error message
808         check_get_float_parameter(machine, var_id, w);
809 	return;
810     }
811     gx_engine::FloatParameter &fp = p.getFloat();
812     if (dynamic_cast<Gxw::FastMeter*>(w) || dynamic_cast<Gxw::PortDisplay*>(w)) {
813         if (!p.isOutput()) {
814             gx_print_error(
815                 "load dialog",
816                 Glib::ustring::compose(
817                     "Parameter variable %1: no output (register with o or O)", var_id));
818             return;
819         }
820         uiController::create(machine, fp, w, out_ctr, true);
821         return;
822     }
823     Gxw::Regler* r = dynamic_cast<Gxw::Regler*>(w);
824     if (!r) {
825 	gx_print_error(
826 	    "load dialog",
827 	    Glib::ustring::compose(
828                 "Continuous Parameter variable %1: Widget must be Regler based", var_id));
829 	return;
830     }
831     uiAdjustment::create(machine, fp, r, out_ctr, false);
832 }
833 
make_enum_controller(gx_engine::GxMachineBase & machine,Gtk::Widget * w,gx_engine::Parameter & p,sigc::signal<void (bool)> * out_ctr)834 static void make_enum_controller(gx_engine::GxMachineBase& machine,
835                                  Gtk::Widget *w,
836                                  gx_engine::Parameter& p,
837                                  sigc::signal<void(bool)> *out_ctr) {
838     Gxw::Selector *t = dynamic_cast<Gxw::Selector*>(w);
839     if (!t) {
840 	gx_print_warning(
841 	    "load dialog",
842 	    Glib::ustring::compose("Enum Parameter variable %1: no Selector widget", p.id()));
843 	make_continuous_controller(machine, w, p, out_ctr);
844 	return;
845     }
846     Gtk::TreeModelColumn<Glib::ustring> label;
847     Gtk::TreeModelColumnRecord rec;
848     rec.add(label);
849     Glib::RefPtr<Gtk::ListStore> ls = Gtk::ListStore::create(rec);
850     for (const value_pair *vp = p.getValueNames(); vp->value_id; ++vp) {
851 	ls->append()->set_value(0, Glib::ustring(p.value_label(*vp)));
852     }
853     t->set_model(ls);
854     t->cp_configure(p.l_group(), p.l_name(), p.getLowerAsFloat(), p.getUpperAsFloat(), 1.0);
855     if (p.isInt()) {
856 	uiSelector<int>::create(machine, t, p, out_ctr, false);
857     } else if (p.isFloat()) {
858 	uiSelector<float>::create(machine, t, p, out_ctr, false);
859     } else {
860 	gx_print_error(
861 	    "load dialog",
862 	    Glib::ustring::compose("Enum Parameter variable %1: type not handled", p.id()));
863     }
864 }
865 
gx_query_tooltip(GtkWidget * widget,gint x,gint y,gboolean keyboard_mode,GtkTooltip * tooltip,gpointer user_data)866 static gboolean gx_query_tooltip(GtkWidget *widget, gint x, gint y, gboolean keyboard_mode,
867 				 GtkTooltip *tooltip, gpointer user_data) {
868     if (!*static_cast<bool*>(user_data)) {
869 	g_signal_stop_emission_by_name(widget, "query-tooltip");
870 	return FALSE;
871     }
872     return FALSE;
873 }
874 
connect_gx_tooltip_handler(GtkWidget * widget)875 void GxBuilder::connect_gx_tooltip_handler(GtkWidget *widget) {
876     g_signal_connect(widget, "query-tooltip", G_CALLBACK(gx_query_tooltip), &show_tooltips);
877 }
878 
set_tooltip_text_connect_handler(GtkWidget * widget,const char * text)879 void GxBuilder::set_tooltip_text_connect_handler(GtkWidget *widget, const char *text) {
880     gtk_widget_set_tooltip_text(widget, text);
881     connect_gx_tooltip_handler(widget);
882 }
883 
884 /**
885  * connect a widget w to the parameter id.
886  */
ui_connect(gx_engine::GxMachineBase & machine,Gtk::Widget * w,const std::string & id,sigc::signal<void (bool)> * out_ctr)887 bool ui_connect(gx_engine::GxMachineBase& machine, Gtk::Widget *w, const std::string& id,
888                 sigc::signal<void(bool)> *out_ctr) {
889     auto wname = w->get_name();
890     if (wname.empty() || wname.substr(0,7) == "gtkmm__") {
891         string id_css = id;
892         boost::replace_all(id_css, ".", "-");
893         w->set_name(id_css);
894     }
895     gx_engine::Parameter *p = check_get_parameter(machine, id, w);
896     if (!p) {
897         return false;
898     }
899     if (!p->desc().empty()) {
900         GxBuilder::set_tooltip_text_connect_handler(*w, p->l_desc());
901     }
902     auto acc = w->get_accessible();
903     if (acc->get_description().empty()) {
904         acc->set_description(id);
905     }
906     if (acc->get_name().empty()) {
907         Glib::ustring nm = p->l_name();
908         if (nm.empty()) {
909             nm = id.substr(id.find_last_of(".")+1);
910         }
911         acc->set_name(nm);
912     }
913     switch (p->getControlType()) {
914     case gx_engine::Parameter::Continuous:
915         make_continuous_controller(machine, w, *p, out_ctr);
916         break;
917     case gx_engine::Parameter::Switch:
918         make_switch_controller(machine, w, *p, out_ctr);
919         break;
920     case gx_engine::Parameter::Enum:
921         make_enum_controller(machine, w, *p, out_ctr);
922         break;
923     default:
924 	gx_print_error(
925 	    "load dialog",
926 	    Glib::ustring::compose("Parameter variable %1: type not handled", p->id()));
927         break;
928     }
929     return true;
930 }
931 
fixup_controlparameters(gx_engine::GxMachineBase & machine,sigc::signal<void (bool)> * out_ctr)932 void GxBuilder::fixup_controlparameters(gx_engine::GxMachineBase& machine,
933                                         sigc::signal<void(bool)> *out_ctr) {
934     Glib::SListHandle<GObject*> objs = Glib::SListHandle<GObject*>(
935         gtk_builder_get_objects(gobj()), Glib::OWNERSHIP_DEEP);
936     for (Glib::SListHandle<GObject*>::iterator i = objs.begin(); i != objs.end(); ++i) {
937 	const char *wname = nullptr;
938 	GtkWidget *widget = nullptr;
939 	if (g_type_is_a(G_OBJECT_TYPE(*i), GTK_TYPE_WIDGET)) {
940 	    const char *id = gtk_buildable_get_name(GTK_BUILDABLE(*i));
941 	    widget = GTK_WIDGET(*i);
942 	    wname = g_strstr_len(id, -1, ":");
943 	    if (wname) {
944 		gtk_widget_set_name(widget, wname+1);
945 	    }
946 	    if (gtk_widget_get_has_tooltip(widget)) {
947 		connect_gx_tooltip_handler(widget);
948 	    }
949 	}
950         if (!g_type_is_a(G_OBJECT_TYPE(*i), GX_TYPE_CONTROL_PARAMETER)) {
951             continue;
952         }
953         assert(widget);
954         auto w = Glib::wrap(widget);
955         std::string v = dynamic_cast<Gxw::ControlParameter*>(w)->cp_get_var();
956         if (v.empty()) {
957             continue;
958         }
959         ui_connect(machine, w, v, out_ctr);
960     }
961 }
962 
963 } /* end of gx_gui namespace */
964