1 /* Calf DSP Library
2  * GUI functions for a plugin.
3  * Copyright (C) 2007-2011 Krzysztof Foltman
4  *
5  * This program is free software; you can redistribute it and/or
6  * modify it under the terms of the GNU Lesser General Public
7  * License as published by the Free Software Foundation; either
8  * version 2 of the License, or (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 GNU
13  * Lesser General Public License for more details.
14  *
15  * You should have received a copy of the GNU Lesser General
16  * Public License along with this program; if not, write to the
17  * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
18  * Boston, MA  02110-1301  USA
19  */
20 
21 #include <calf/gui_config.h>
22 #include <calf/gui_controls.h>
23 #include <calf/preset.h>
24 #include <calf/preset_gui.h>
25 #include <gdk/gdk.h>
26 
27 #include <iostream>
28 
29 using namespace calf_plugins;
30 using namespace std;
31 
32 /******************************** GUI proper ********************************/
33 
plugin_gui(plugin_gui_widget * _window)34 plugin_gui::plugin_gui(plugin_gui_widget *_window)
35 : last_status_serial_no(0)
36 , window(_window)
37 {
38     plugin = NULL;
39     ignore_stack = 0;
40     top_container = NULL;
41     param_count = 0;
42     container = NULL;
43     effect_name = NULL;
44     preset_access = new gui_preset_access(this);
45     optclosed = false;
46     optwidget = NULL;
47     optwindow = NULL;
48     opttitle = NULL;
49 }
50 
create_widget_from_xml(const char * element,const char * attributes[])51 control_base *plugin_gui::create_widget_from_xml(const char *element, const char *attributes[])
52 {
53     if (!strcmp(element, "knob"))
54         return new knob_param_control;
55     if (!strcmp(element, "hscale"))
56         return new hscale_param_control;
57     if (!strcmp(element, "vscale"))
58         return new vscale_param_control;
59     if (!strcmp(element, "combo"))
60         return new combo_box_param_control;
61     if (!strcmp(element, "check"))
62         return new check_param_control;
63     if (!strcmp(element, "radio"))
64         return new radio_param_control;
65     if (!strcmp(element, "toggle"))
66         return new toggle_param_control;
67     if (!strcmp(element, "tap"))
68         return new tap_button_param_control;
69     if (!strcmp(element, "spin"))
70         return new spin_param_control;
71     if (!strcmp(element, "button"))
72         return new button_param_control;
73     if (!strcmp(element, "label"))
74         return new label_param_control;
75     if (!strcmp(element, "value"))
76         return new value_param_control;
77     if (!strcmp(element, "vumeter"))
78         return new vumeter_param_control;
79     if (!strcmp(element, "line-graph"))
80         return new line_graph_param_control;
81     if (!strcmp(element, "phase-graph"))
82         return new phase_graph_param_control;
83     if (!strcmp(element, "tuner"))
84         return new tuner_param_control;
85     if (!strcmp(element, "pattern"))
86         return new pattern_param_control;
87     if (!strcmp(element, "keyboard"))
88         return new keyboard_param_control;
89     if (!strcmp(element, "curve"))
90         return new curve_param_control;
91     if (!strcmp(element, "meterscale"))
92         return new meter_scale_param_control;
93     if (!strcmp(element, "led"))
94         return new led_param_control;
95     if (!strcmp(element, "tube"))
96         return new tube_param_control;
97     if (!strcmp(element, "entry"))
98         return new entry_param_control;
99     if (!strcmp(element, "filechooser"))
100         return new filechooser_param_control;
101     if (!strcmp(element, "listview"))
102         return new listview_param_control;
103     if (!strcmp(element, "notebook"))
104         return new notebook_param_control;
105     if (!strcmp(element, "table"))
106         return new table_container;
107     if (!strcmp(element, "vbox"))
108         return new vbox_container;
109     if (!strcmp(element, "hbox"))
110         return new hbox_container;
111     if (!strcmp(element, "align"))
112         return new alignment_container;
113     if (!strcmp(element, "frame"))
114         return new frame_container;
115     if (!strcmp(element, "scrolled"))
116         return new scrolled_container;
117     return NULL;
118 }
119 
xml_element_start(void * data,const char * element,const char * attributes[])120 void plugin_gui::xml_element_start(void *data, const char *element, const char *attributes[])
121 {
122     plugin_gui *gui = (plugin_gui *)data;
123     gui->xml_element_start(element, attributes);
124 }
125 
get_param_no_by_name(string param_name)126 int plugin_gui::get_param_no_by_name(string param_name)
127 {
128     int param_no = -1;
129     map<string, int>::iterator it = param_name_map.find(param_name);
130     if (it == param_name_map.end())
131         g_error("Unknown parameter %s", param_name.c_str());
132     else
133         param_no = it->second;
134 
135     return param_no;
136 }
137 
xml_element_start(const char * element,const char * attributes[])138 void plugin_gui::xml_element_start(const char *element, const char *attributes[])
139 {
140     if (ignore_stack) {
141         ignore_stack++;
142         return;
143     }
144     control_base::xml_attribute_map xam;
145     while(*attributes)
146     {
147         xam[attributes[0]] = attributes[1];
148         attributes += 2;
149     }
150 
151     if (!strcmp(element, "if"))
152     {
153         if (!xam.count("cond") || xam["cond"].empty())
154         {
155             g_error("Incorrect <if cond=\"[!]symbol\"> element");
156         }
157         string cond = xam["cond"];
158         bool state = true;
159         if (cond.substr(0, 1) == "!") {
160             state = false;
161             cond.erase(0, 1);
162         }
163         if (window->get_environment()->check_condition(cond.c_str()) == state)
164             return;
165         ignore_stack = 1;
166         return;
167     }
168     control_base *cc = create_widget_from_xml(element, attributes);
169     if (cc == NULL)
170         g_error("Unxpected element %s in GUI definition\n", element);
171 
172     cc->attribs = xam;
173     cc->create(this);
174     stack.push_back(cc);
175 }
176 
xml_element_end(void * data,const char * element)177 void plugin_gui::xml_element_end(void *data, const char *element)
178 {
179     plugin_gui *gui = (plugin_gui *)data;
180     if (gui->ignore_stack) {
181         gui->ignore_stack--;
182         return;
183     }
184     if (!strcmp(element, "if"))
185         return;
186 
187     control_base *control = gui->stack.back();
188     control->created();
189 
190     gui->stack.pop_back();
191     if (gui->stack.empty())
192     {
193         gui->top_container = control;
194         gtk_widget_show_all(control->widget);
195     }
196     else
197         gui->stack.back()->add(control);
198 }
199 
200 
create_from_xml(plugin_ctl_iface * _plugin,const char * xml)201 GtkWidget *plugin_gui::create_from_xml(plugin_ctl_iface *_plugin, const char *xml)
202 {
203     top_container = NULL;
204     parser = XML_ParserCreate("UTF-8");
205     plugin = _plugin;
206     stack.clear();
207     ignore_stack = 0;
208 
209     param_name_map.clear();
210     read_serials.clear();
211     int size = plugin->get_metadata_iface()->get_param_count();
212     read_serials.resize(size);
213     for (int i = 0; i < size; i++)
214         param_name_map[plugin->get_metadata_iface()->get_param_props(i)->short_name] = i;
215 
216     XML_SetUserData(parser, this);
217     XML_SetElementHandler(parser, xml_element_start, xml_element_end);
218     XML_Status status = XML_Parse(parser, xml, strlen(xml), 1);
219     if (status == XML_STATUS_ERROR)
220     {
221         g_error("Parse error: %s in XML", XML_ErrorString(XML_GetErrorCode(parser)));
222     }
223 
224     XML_ParserFree(parser);
225     last_status_serial_no = plugin->send_status_updates(this, 0);
226     return top_container->widget;
227 }
228 
send_configure(const char * key,const char * value)229 void plugin_gui::send_configure(const char *key, const char *value)
230 {
231     // XXXKF this should really be replaced by a separate list of SCI-capable param controls
232     for (unsigned int i = 0; i < params.size(); i++)
233     {
234         assert(params[i] != NULL);
235         send_configure_iface *sci = dynamic_cast<send_configure_iface *>(params[i]);
236         if (sci)
237             sci->send_configure(key, value);
238     }
239 }
240 
send_status(const char * key,const char * value)241 void plugin_gui::send_status(const char *key, const char *value)
242 {
243     // XXXKF this should really be replaced by a separate list of SUI-capable param controls
244     for (unsigned int i = 0; i < params.size(); i++)
245     {
246         assert(params[i] != NULL);
247         send_updates_iface *sui = dynamic_cast<send_updates_iface *>(params[i]);
248         if (sui)
249             sui->send_status(key, value);
250     }
251 }
252 
remove_param_ctl(int param,param_control * ctl)253 void plugin_gui::remove_param_ctl(int param, param_control *ctl)
254 {
255     std::multimap<int, param_control *>::iterator it = par2ctl.find(param);
256     while(it != par2ctl.end() && it->first == param)
257     {
258         if (it->second == ctl)
259         {
260             std::multimap<int, param_control *>::iterator orig = it;
261             ++orig;
262             par2ctl.erase(it, orig);
263             it = orig;
264         }
265         else
266             ++it;
267     }
268     unsigned last = params.size() - 1;
269     for (unsigned i = 0; i < params.size(); ++i)
270     {
271         if (params[i] == ctl)
272         {
273             if (i != last)
274                 std::swap(params[i], params[last]);
275             params.erase(params.begin() + last, params.end());
276             last--;
277         }
278     }
279 }
280 
on_idle()281 void plugin_gui::on_idle()
282 {
283     set<unsigned> changed;
284     for (unsigned i = 0; i < read_serials.size(); i++)
285     {
286         int write_serial = plugin->get_write_serial(i);
287         if (write_serial - read_serials[i] > 0)
288         {
289             read_serials[i] = write_serial;
290             changed.insert(i);
291         }
292     }
293     for (unsigned i = 0; i < params.size(); i++)
294     {
295         int param_no = params[i]->param_no;
296         if (param_no != -1)
297         {
298             const parameter_properties &props = *plugin->get_metadata_iface()->get_param_props(param_no);
299             bool is_output = (props.flags & PF_PROP_OUTPUT) != 0;
300             if (is_output || (param_no != -1 && changed.count(param_no)))
301                 params[i]->set();
302         }
303         params[i]->on_idle();
304     }
305     last_status_serial_no = plugin->send_status_updates(this, last_status_serial_no);
306     // XXXKF iterate over par2ctl, too...
307 }
308 
refresh()309 void plugin_gui::refresh()
310 {
311     for (unsigned int i = 0; i < params.size(); i++)
312         params[i]->set();
313     plugin->send_configures(this);
314     last_status_serial_no = plugin->send_status_updates(this, last_status_serial_no);
315 }
316 
refresh(int param_no,param_control * originator)317 void plugin_gui::refresh(int param_no, param_control *originator)
318 {
319     std::multimap<int, param_control *>::iterator it = par2ctl.find(param_no);
320     while(it != par2ctl.end() && it->first == param_no)
321     {
322         if (it->second != originator)
323             it->second->set();
324         ++it;
325     }
326 }
327 
set_param_value(int param_no,float value,param_control * originator)328 void plugin_gui::set_param_value(int param_no, float value, param_control *originator)
329 {
330     plugin->set_param_value(param_no, value);
331 
332     main_window_iface *main = window->get_main_window();
333     if (main)
334         main->refresh_plugin_param(plugin, param_no);
335     else
336         refresh(param_no);
337 }
338 
339 /// Get a radio button group (if it exists) for a parameter
get_radio_group(int param)340 GSList *plugin_gui::get_radio_group(int param)
341 {
342     map<int, GSList *>::const_iterator i = param_radio_groups.find(param);
343     if (i == param_radio_groups.end())
344         return NULL;
345     else
346         return i->second;
347 }
348 
349 /// Set a radio button group for a parameter
set_radio_group(int param,GSList * group)350 void plugin_gui::set_radio_group(int param, GSList *group)
351 {
352     param_radio_groups[param] = group;
353 }
354 
on_automation_add(GtkWidget * widget,void * user_data)355 void plugin_gui::on_automation_add(GtkWidget *widget, void *user_data)
356 {
357     plugin_gui *self = (plugin_gui *)user_data;
358     self->plugin->add_automation(self->context_menu_last_designator, automation_range(0, 1, self->context_menu_param_no));
359 }
360 
on_automation_delete(GtkWidget * widget,void * user_data)361 void plugin_gui::on_automation_delete(GtkWidget *widget, void *user_data)
362 {
363     automation_menu_entry *ame = (automation_menu_entry *)user_data;
364     ame->gui->plugin->delete_automation(ame->source, ame->gui->context_menu_param_no);
365 }
366 
on_automation_set_lower_or_upper(automation_menu_entry * ame,bool is_upper)367 void plugin_gui::on_automation_set_lower_or_upper(automation_menu_entry *ame, bool is_upper)
368 {
369     const parameter_properties *props = plugin->get_metadata_iface()->get_param_props(context_menu_param_no);
370     float mapped = props->to_01(plugin->get_param_value(context_menu_param_no));
371 
372     automation_map mappings;
373     plugin->get_automation(context_menu_param_no, mappings);
374     automation_map::const_iterator i = mappings.find(ame->source);
375     if (i != mappings.end())
376     {
377         if (is_upper)
378             plugin->add_automation(context_menu_last_designator, automation_range(i->second.min_value, mapped, context_menu_param_no));
379         else
380             plugin->add_automation(context_menu_last_designator, automation_range(mapped, i->second.max_value, context_menu_param_no));
381     }
382 }
383 
on_automation_set_lower(GtkWidget * widget,void * user_data)384 void plugin_gui::on_automation_set_lower(GtkWidget *widget, void *user_data)
385 {
386     automation_menu_entry *ame = (automation_menu_entry *)user_data;
387     ame->gui->on_automation_set_lower_or_upper(ame, false);
388 }
389 
on_automation_set_upper(GtkWidget * widget,void * user_data)390 void plugin_gui::on_automation_set_upper(GtkWidget *widget, void *user_data)
391 {
392     automation_menu_entry *ame = (automation_menu_entry *)user_data;
393     ame->gui->on_automation_set_lower_or_upper(ame, true);
394 }
395 
cleanup_automation_entries()396 void plugin_gui::cleanup_automation_entries()
397 {
398     for (int i = 0; i < (int)automation_menu_callback_data.size(); i++)
399         delete automation_menu_callback_data[i];
400     automation_menu_callback_data.clear();
401 }
402 
on_control_popup(param_control * ctl,int param_no)403 void plugin_gui::on_control_popup(param_control *ctl, int param_no)
404 {
405     cleanup_automation_entries();
406     if (param_no == -1)
407         return;
408     context_menu_param_no = param_no;
409     GtkWidget *menu = gtk_menu_new();
410 
411     multimap<uint32_t, automation_range> mappings;
412     plugin->get_automation(param_no, mappings);
413 
414     context_menu_last_designator = plugin->get_last_automation_source();
415 
416     GtkWidget *item;
417     if (context_menu_last_designator != 0xFFFFFFFF)
418     {
419         stringstream ss;
420         ss << "_Bind to: Ch" << (1 + (context_menu_last_designator >> 8)) << ", CC#" << (context_menu_last_designator & 127);
421         item = gtk_menu_item_new_with_mnemonic(ss.str().c_str());
422         g_signal_connect(item, "activate", (GCallback)on_automation_add, this);
423         gtk_menu_shell_append(GTK_MENU_SHELL(menu), item);
424     }
425     else
426     {
427         item = gtk_menu_item_new_with_label("Send CC to automate");
428         gtk_widget_set_sensitive(item, FALSE);
429         gtk_menu_shell_append(GTK_MENU_SHELL(menu), item);
430     }
431 
432     for(multimap<uint32_t, automation_range>::const_iterator i = mappings.begin(); i != mappings.end(); ++i)
433     {
434         automation_menu_entry *ame = new automation_menu_entry(this, i->first);
435         automation_menu_callback_data.push_back(ame);
436         stringstream ss;
437         ss << "Mapping: Ch" << (1 + (i->first >> 8)) << ", CC#" << (i->first & 127);
438         item = gtk_menu_item_new_with_label(ss.str().c_str());
439         gtk_menu_shell_append(GTK_MENU_SHELL(menu), item);
440 
441         GtkWidget *submenu = gtk_menu_new();
442         gtk_menu_item_set_submenu(GTK_MENU_ITEM(item), submenu);
443 
444         item = gtk_menu_item_new_with_mnemonic("_Delete");
445         g_signal_connect(item, "activate", (GCallback)on_automation_delete, ame);
446         gtk_menu_shell_append(GTK_MENU_SHELL(submenu), item);
447 
448         item = gtk_menu_item_new_with_mnemonic("Set _lower limit");
449         g_signal_connect(item, "activate", (GCallback)on_automation_set_lower, ame);
450         gtk_menu_shell_append(GTK_MENU_SHELL(submenu), item);
451 
452         item = gtk_menu_item_new_with_mnemonic("Set _upper limit");
453         g_signal_connect(item, "activate", (GCallback)on_automation_set_upper, ame);
454         gtk_menu_shell_append(GTK_MENU_SHELL(submenu), item);
455         //g_signal_connect(item, "activate", (GCallback)on_automation_add, this);
456 
457     }
458 
459     gtk_widget_show_all(menu);
460     gtk_menu_popup(GTK_MENU(menu), NULL, NULL, NULL, NULL, 3, gtk_get_current_event_time());
461 }
462 
destroy_child_widgets(GtkWidget * parent)463 void plugin_gui::destroy_child_widgets(GtkWidget *parent)
464 {
465     if (parent && GTK_IS_CONTAINER(parent))
466     {
467         GList *children = gtk_container_get_children(GTK_CONTAINER(parent));
468         for(GList *p = children; p; p = p->next)
469             gtk_widget_destroy(GTK_WIDGET(p->data));
470         g_list_free(children);
471     }
472 }
473 
~plugin_gui()474 plugin_gui::~plugin_gui()
475 {
476     cleanup_automation_entries();
477     delete preset_access;
478 }
479 
480 /***************************** GUI environment ********************************************/
481 
check_redraw(GtkWidget * toplevel)482 bool window_update_controller::check_redraw(GtkWidget *toplevel)
483 {
484     GdkWindow *gdkwin = gtk_widget_get_window(toplevel);
485     if (!gdkwin)
486         return false;
487 
488     if (!gdk_window_is_viewable(gdkwin))
489         return false;
490     GdkWindowState state = gdk_window_get_state(gdkwin);
491     if (state & GDK_WINDOW_STATE_ICONIFIED)
492     {
493         ++refresh_counter;
494         if (refresh_counter & 15)
495             return false;
496     }
497     return true;
498 }
499 
500 /***************************** GUI environment ********************************************/
501 
gui_environment()502 gui_environment::gui_environment()
503 {
504     keyfile = g_key_file_new();
505 
506     gchar *fn = g_build_filename(getenv("HOME"), ".calfrc", NULL);
507     string filename = fn;
508     g_free(fn);
509     g_key_file_load_from_file(keyfile, filename.c_str(), (GKeyFileFlags)(G_KEY_FILE_KEEP_COMMENTS | G_KEY_FILE_KEEP_TRANSLATIONS), NULL);
510 
511     config_db = new calf_utils::gkeyfile_config_db(keyfile, filename.c_str(), "gui");
512     gui_config.load(config_db);
513     images = image_factory();
514     images.set_path(PKGLIBDIR "styles/" + get_config()->style);
515 }
516 
~gui_environment()517 gui_environment::~gui_environment()
518 {
519     delete config_db;
520     if (keyfile)
521         g_key_file_free(keyfile);
522 }
523 
524 
525 /***************************** Image Factory **************************************/
create_image(string image)526 GdkPixbuf *image_factory::create_image (string image) {
527     string file = path + "/" + image + ".png";
528     if (access(file.c_str(), F_OK))
529         return NULL;
530     return gdk_pixbuf_new_from_file(file.c_str(), NULL);
531 }
recreate_images()532 void image_factory::recreate_images () {
533     for (map<string, GdkPixbuf*>::iterator i_ = i.begin(); i_ != i.end(); i_++) {
534         if (i[i_->first])
535             i[i_->first] = create_image(i_->first);
536     }
537 }
set_path(string p)538 void image_factory::set_path (string p) {
539     path = p;
540     recreate_images();
541 }
get(string image)542 GdkPixbuf *image_factory::get (string image) {
543     if (!i.count(image))
544         return NULL;
545     if (!i.at(image))
546         i[image] = create_image(image);
547     return i[image];
548 }
available(string image)549 gboolean image_factory::available (string image) {
550     string file = path + "/" + image + ".png";
551     if (access(file.c_str(), F_OK))
552         return false;
553     return true;
554 }
image_factory(string p)555 image_factory::image_factory (string p) {
556     set_path(p);
557 
558     i["combo_arrow"]              = NULL;
559     i["light_top"]                = NULL;
560     i["light_bottom"]             = NULL;
561     i["notebook_screw"]           = NULL;
562     i["logo_button"]              = NULL;
563 
564     i["knob_1"]                    = NULL;
565     i["knob_2"]                    = NULL;
566     i["knob_3"]                    = NULL;
567     i["knob_4"]                    = NULL;
568     i["knob_5"]                    = NULL;
569 
570     i["side_d_ne"]                = NULL;
571     i["side_d_nw"]                = NULL;
572     i["side_d_se"]                = NULL;
573     i["side_d_sw"]                = NULL;
574 
575     i["side_ne"]                  = NULL;
576     i["side_nw"]                  = NULL;
577     i["side_se"]                  = NULL;
578     i["side_sw"]                  = NULL;
579     i["side_e_logo"]              = NULL;
580 
581     i["slider_1_horiz"]            = NULL;
582     i["slider_1_vert"]             = NULL;
583     i["slider_2_horiz"]            = NULL;
584     i["slider_2_vert"]             = NULL;
585 
586     i["tap_active"]               = NULL;
587     i["tap_inactive"]             = NULL;
588     i["tap_prelight"]             = NULL;
589 
590     i["toggle_0"]                 = NULL;
591     i["toggle_1"]                 = NULL;
592     i["toggle_2"]                 = NULL;
593 
594     i["toggle_2_block"]           = NULL;
595     i["toggle_2_bypass"]          = NULL;
596     i["toggle_2_bypass2"]         = NULL;
597     i["toggle_2_fast"]            = NULL;
598     i["toggle_2_listen"]          = NULL;
599     i["toggle_2_logarithmic"]     = NULL;
600     i["toggle_2_magnetical"]      = NULL;
601     i["toggle_2_mono"]            = NULL;
602     i["toggle_2_muffle"]          = NULL;
603     i["toggle_2_mute"]            = NULL;
604     i["toggle_2_phase"]           = NULL;
605     i["toggle_2_sc_comp"]         = NULL;
606     i["toggle_2_sc_filter"]       = NULL;
607     i["toggle_2_softclip"]        = NULL;
608     i["toggle_2_solo"]            = NULL;
609     i["toggle_2_sync"]            = NULL;
610     i["toggle_2_void"]            = NULL;
611     i["toggle_2_gui"]             = NULL;
612     i["toggle_2_connect"]         = NULL;
613     i["toggle_2_pauseplay"]       = NULL;
614 }
~image_factory()615 image_factory::~image_factory() {
616 
617 }
618