Fix bypass crossfade read/write out of bounds
[calf.git] / src / gui.cpp
blobe72dda280b4335e5c726835ce7aae90537ae699c
1 /* Calf DSP Library
2 * GUI functions for a plugin.
3 * Copyright (C) 2007-2011 Krzysztof Foltman
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.
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.
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
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>
27 #include <iostream>
29 using namespace calf_plugins;
30 using namespace std;
32 /******************************** GUI proper ********************************/
34 plugin_gui::plugin_gui(plugin_gui_window *_window)
35 : last_status_serial_no(0)
36 , window(_window)
38 ignore_stack = 0;
39 top_container = NULL;
40 current_control = 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;
51 param_control *plugin_gui::create_control_from_xml(const char *element, const char *attributes[])
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, "keyboard"))
84 return new keyboard_param_control;
85 if (!strcmp(element, "curve"))
86 return new curve_param_control;
87 if (!strcmp(element, "led"))
88 return new led_param_control;
89 if (!strcmp(element, "tube"))
90 return new tube_param_control;
91 if (!strcmp(element, "entry"))
92 return new entry_param_control;
93 if (!strcmp(element, "filechooser"))
94 return new filechooser_param_control;
95 if (!strcmp(element, "listview"))
96 return new listview_param_control;
97 if (!strcmp(element, "notebook"))
98 return new notebook_param_control;
99 return NULL;
102 control_container *plugin_gui::create_container_from_xml(const char *element, const char *attributes[])
104 if (!strcmp(element, "table"))
105 return new table_container;
106 if (!strcmp(element, "vbox"))
107 return new vbox_container;
108 if (!strcmp(element, "hbox"))
109 return new hbox_container;
110 if (!strcmp(element, "align"))
111 return new alignment_container;
112 if (!strcmp(element, "frame"))
113 return new frame_container;
114 if (!strcmp(element, "scrolled"))
115 return new scrolled_container;
116 return NULL;
119 void plugin_gui::xml_element_start(void *data, const char *element, const char *attributes[])
121 plugin_gui *gui = (plugin_gui *)data;
122 gui->xml_element_start(element, attributes);
125 int plugin_gui::get_param_no_by_name(string param_name)
127 int param_no = -1;
128 map<string, int>::iterator it = param_name_map.find(param_name);
129 if (it == param_name_map.end())
130 g_error("Unknown parameter %s", param_name.c_str());
131 else
132 param_no = it->second;
134 return param_no;
137 void plugin_gui::xml_element_start(const char *element, const char *attributes[])
139 if (ignore_stack) {
140 ignore_stack++;
141 return;
143 control_base::xml_attribute_map xam;
144 while(*attributes)
146 xam[attributes[0]] = attributes[1];
147 attributes += 2;
150 if (!strcmp(element, "if"))
152 if (!xam.count("cond") || xam["cond"].empty())
154 g_error("Incorrect <if cond=\"[!]symbol\"> element");
156 string cond = xam["cond"];
157 bool state = true;
158 if (cond.substr(0, 1) == "!") {
159 state = false;
160 cond.erase(0, 1);
162 if (window->environment->check_condition(cond.c_str()) == state)
163 return;
164 ignore_stack = 1;
165 return;
167 control_container *cc = create_container_from_xml(element, attributes);
168 if (cc != NULL)
170 cc->attribs = xam;
171 cc->create(this, element, xam);
172 cc->set_std_properties();
173 gtk_container_set_border_width(cc->container, cc->get_int("border"));
175 container_stack.push_back(cc);
176 current_control = NULL;
177 return;
179 if (!container_stack.empty())
181 current_control = create_control_from_xml(element, attributes);
182 if (current_control)
184 current_control->control_name = element;
185 current_control->attribs = xam;
186 int param_no = -1;
187 if (xam.count("param"))
189 param_no = get_param_no_by_name(xam["param"]);
191 if (param_no != -1)
192 current_control->param_variable = plugin->get_metadata_iface()->get_param_props(param_no)->short_name;
193 current_control->create(this, param_no);
194 current_control->set_std_properties();
195 current_control->init_xml(element);
196 current_control->add_context_menu_handler();
197 if (current_control->is_container()) {
198 gtk_container_set_border_width(current_control->container, current_control->get_int("border"));
199 container_stack.push_back(current_control);
200 control_stack.push_back(current_control);
202 return;
205 g_error("Unxpected element %s in GUI definition\n", element);
208 void plugin_gui::xml_element_end(void *data, const char *element)
210 plugin_gui *gui = (plugin_gui *)data;
211 unsigned int ss = gui->container_stack.size();
212 if (gui->ignore_stack) {
213 gui->ignore_stack--;
214 return;
216 if (!strcmp(element, "if"))
218 return;
220 if (gui->current_control and !gui->current_control->is_container())
222 (*gui->container_stack.rbegin())->add(gui->current_control->widget, gui->current_control);
223 gui->current_control->hook_params();
224 gui->current_control->set();
225 gui->current_control->created();
226 gtk_widget_show_all(gui->current_control->widget);
227 gui->current_control = NULL;
228 return;
230 if (ss > 1) {
231 gui->container_stack[ss - 2]->add(GTK_WIDGET(gui->container_stack[ss - 1]->container), gui->container_stack[ss - 1]);
232 gtk_widget_show_all(GTK_WIDGET(gui->container_stack[ss - 1]->container));
234 else {
235 gui->top_container = gui->container_stack[0];
236 gtk_widget_show_all(GTK_WIDGET(gui->container_stack[0]->container));
238 int css = gui->control_stack.size();
239 if (ss > 1 and gui->container_stack[ss - 1]->is_container() and css) {
240 gui->control_stack[css - 1]->hook_params();
241 gui->control_stack[css - 1]->set();
242 gui->control_stack[css - 1]->created();
244 gui->control_stack.pop_back();
246 gui->container_stack.pop_back();
247 gui->current_control = NULL;
251 GtkWidget *plugin_gui::create_from_xml(plugin_ctl_iface *_plugin, const char *xml)
253 current_control = NULL;
254 top_container = NULL;
255 parser = XML_ParserCreate("UTF-8");
256 plugin = _plugin;
257 container_stack.clear();
258 control_stack.clear();
259 ignore_stack = 0;
261 param_name_map.clear();
262 read_serials.clear();
263 int size = plugin->get_metadata_iface()->get_param_count();
264 read_serials.resize(size);
265 for (int i = 0; i < size; i++)
266 param_name_map[plugin->get_metadata_iface()->get_param_props(i)->short_name] = i;
268 XML_SetUserData(parser, this);
269 XML_SetElementHandler(parser, xml_element_start, xml_element_end);
270 XML_Status status = XML_Parse(parser, xml, strlen(xml), 1);
271 if (status == XML_STATUS_ERROR)
273 g_error("Parse error: %s in XML", XML_ErrorString(XML_GetErrorCode(parser)));
276 XML_ParserFree(parser);
277 last_status_serial_no = plugin->send_status_updates(this, 0);
278 GtkWidget *eventbox = gtk_event_box_new();
279 GtkWidget *decoTable = gtk_table_new(3, 1, FALSE);
281 // decorations
282 // overall background
283 //GtkWidget *bgImg = gtk_image_new_from_file(PKGLIBDIR "/background.png");
284 //gtk_widget_set_size_request(GTK_WIDGET(bgImg), 1, 1);
286 // images for left side
287 GtkWidget *nwImg = gtk_image_new_from_file(PKGLIBDIR "/side_nw.png");
288 GtkWidget *swImg = gtk_image_new_from_file(PKGLIBDIR "/side_sw.png");
289 GtkWidget *wImg = gtk_image_new_from_file(PKGLIBDIR "/side_w.png");
290 gtk_widget_set_size_request(GTK_WIDGET(wImg), 56, 1);
292 // images for right side
293 GtkWidget *neImg = gtk_image_new_from_file(PKGLIBDIR "/side_ne.png");
294 GtkWidget *seImg = gtk_image_new_from_file(PKGLIBDIR "/side_se.png");
295 GtkWidget *eImg = gtk_image_new_from_file(PKGLIBDIR "/side_e.png");
296 GtkWidget *logoImg = gtk_image_new_from_file(PKGLIBDIR "/side_e_logo.png");
297 gtk_widget_set_size_request(GTK_WIDGET(eImg), 56, 1);
299 // pack left box
300 leftBox = gtk_vbox_new(FALSE, 0);
301 gtk_box_pack_start(GTK_BOX(leftBox), GTK_WIDGET(nwImg), FALSE, FALSE, 0);
302 gtk_box_pack_start(GTK_BOX(leftBox), GTK_WIDGET(wImg), TRUE, TRUE, 0);
303 gtk_box_pack_end(GTK_BOX(leftBox), GTK_WIDGET(swImg), FALSE, FALSE, 0);
305 // pack right box
306 rightBox = gtk_vbox_new(FALSE, 0);
307 gtk_box_pack_start(GTK_BOX(rightBox), GTK_WIDGET(neImg), FALSE, FALSE, 0);
308 gtk_box_pack_start(GTK_BOX(rightBox), GTK_WIDGET(eImg), TRUE, TRUE, 0);
309 gtk_box_pack_start(GTK_BOX(rightBox), GTK_WIDGET(logoImg), FALSE, FALSE, 0);
310 gtk_box_pack_end(GTK_BOX(rightBox), GTK_WIDGET(seImg), FALSE, FALSE, 0);
312 //gtk_table_attach(GTK_TABLE(decoTable), GTK_WIDGET(bgImg), 0, 2, 0, 2, (GtkAttachOptions)(GTK_EXPAND | GTK_SHRINK | GTK_FILL), (GtkAttachOptions)(GTK_EXPAND | GTK_SHRINK | GTK_FILL), 0, 0);
313 gtk_table_attach(GTK_TABLE(decoTable), GTK_WIDGET(leftBox), 0, 1, 0, 1, (GtkAttachOptions)(0), (GtkAttachOptions)(GTK_EXPAND | GTK_FILL), 0, 0);
314 gtk_table_attach(GTK_TABLE(decoTable), GTK_WIDGET(rightBox), 2, 3, 0, 1, (GtkAttachOptions)(0), (GtkAttachOptions)(GTK_EXPAND | GTK_FILL), 0, 0);
316 gtk_table_attach(GTK_TABLE(decoTable), GTK_WIDGET(top_container->container), 1, 2, 0, 1, (GtkAttachOptions)(GTK_EXPAND | GTK_FILL), (GtkAttachOptions)(GTK_EXPAND | GTK_FILL), 15, 5);
317 gtk_container_add( GTK_CONTAINER(eventbox), decoTable );
318 gtk_widget_set_name( GTK_WIDGET(eventbox), "Calf-Plugin" );
320 // create window with viewport
321 // GtkWidget *sw = gtk_scrolled_window_new(NULL, NULL);
322 // gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(sw), GTK_POLICY_NEVER, GTK_POLICY_NEVER);
323 // gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(sw), GTK_SHADOW_NONE);
324 // gtk_scrolled_window_add_with_viewport(GTK_SCROLLED_WINDOW(sw), GTK_WIDGET(decoTable));
326 return GTK_WIDGET(eventbox);
329 void plugin_gui::send_configure(const char *key, const char *value)
331 // XXXKF this should really be replaced by a separate list of SCI-capable param controls
332 for (unsigned int i = 0; i < params.size(); i++)
334 assert(params[i] != NULL);
335 send_configure_iface *sci = dynamic_cast<send_configure_iface *>(params[i]);
336 if (sci)
337 sci->send_configure(key, value);
341 void plugin_gui::send_status(const char *key, const char *value)
343 // XXXKF this should really be replaced by a separate list of SUI-capable param controls
344 for (unsigned int i = 0; i < params.size(); i++)
346 assert(params[i] != NULL);
347 send_updates_iface *sui = dynamic_cast<send_updates_iface *>(params[i]);
348 if (sui)
349 sui->send_status(key, value);
353 void plugin_gui::on_idle()
355 set<unsigned> changed;
356 for (unsigned i = 0; i < read_serials.size(); i++)
358 int write_serial = plugin->get_write_serial(i);
359 if (write_serial - read_serials[i] > 0)
361 read_serials[i] = write_serial;
362 changed.insert(i);
365 for (unsigned i = 0; i < params.size(); i++)
367 int param_no = params[i]->param_no;
368 if (param_no != -1)
370 const parameter_properties &props = *plugin->get_metadata_iface()->get_param_props(param_no);
371 bool is_output = (props.flags & PF_PROP_OUTPUT) != 0;
372 if (is_output || (param_no != -1 && changed.count(param_no)))
373 params[i]->set();
375 params[i]->on_idle();
377 last_status_serial_no = plugin->send_status_updates(this, last_status_serial_no);
378 // XXXKF iterate over par2ctl, too...
381 void plugin_gui::refresh()
383 for (unsigned int i = 0; i < params.size(); i++)
384 params[i]->set();
385 plugin->send_configures(this);
386 last_status_serial_no = plugin->send_status_updates(this, last_status_serial_no);
389 void plugin_gui::refresh(int param_no, param_control *originator)
391 std::multimap<int, param_control *>::iterator it = par2ctl.find(param_no);
392 while(it != par2ctl.end() && it->first == param_no)
394 if (it->second != originator)
395 it->second->set();
396 it++;
400 void plugin_gui::set_param_value(int param_no, float value, param_control *originator)
402 plugin->set_param_value(param_no, value);
403 refresh(param_no);
406 /// Get a radio button group (if it exists) for a parameter
407 GSList *plugin_gui::get_radio_group(int param)
409 map<int, GSList *>::const_iterator i = param_radio_groups.find(param);
410 if (i == param_radio_groups.end())
411 return NULL;
412 else
413 return i->second;
416 /// Set a radio button group for a parameter
417 void plugin_gui::set_radio_group(int param, GSList *group)
419 param_radio_groups[param] = group;
422 void plugin_gui::show_rack_ears(bool show)
424 // if hidden, add a no-show-all attribute so that LV2 host doesn't accidentally override
425 // the setting by doing a show_all on the outermost container
426 gtk_widget_set_no_show_all(leftBox, !show);
427 gtk_widget_set_no_show_all(rightBox, !show);
428 if (show)
430 gtk_widget_show(leftBox);
431 gtk_widget_show(rightBox);
433 else
435 gtk_widget_hide(leftBox);
436 gtk_widget_hide(rightBox);
440 void plugin_gui::on_automation_add(GtkWidget *widget, void *user_data)
442 plugin_gui *self = (plugin_gui *)user_data;
443 self->plugin->add_automation(self->context_menu_last_designator, automation_range(0, 1, self->context_menu_param_no));
446 void plugin_gui::on_automation_delete(GtkWidget *widget, void *user_data)
448 automation_menu_entry *ame = (automation_menu_entry *)user_data;
449 ame->gui->plugin->delete_automation(ame->source, ame->gui->context_menu_param_no);
452 void plugin_gui::on_automation_set_lower_or_upper(automation_menu_entry *ame, bool is_upper)
454 const parameter_properties *props = plugin->get_metadata_iface()->get_param_props(context_menu_param_no);
455 float mapped = props->to_01(plugin->get_param_value(context_menu_param_no));
457 automation_map mappings;
458 plugin->get_automation(context_menu_param_no, mappings);
459 automation_map::const_iterator i = mappings.find(ame->source);
460 if (i != mappings.end())
462 if (is_upper)
463 plugin->add_automation(context_menu_last_designator, automation_range(i->second.min_value, mapped, context_menu_param_no));
464 else
465 plugin->add_automation(context_menu_last_designator, automation_range(mapped, i->second.max_value, context_menu_param_no));
469 void plugin_gui::on_automation_set_lower(GtkWidget *widget, void *user_data)
471 automation_menu_entry *ame = (automation_menu_entry *)user_data;
472 ame->gui->on_automation_set_lower_or_upper(ame, false);
475 void plugin_gui::on_automation_set_upper(GtkWidget *widget, void *user_data)
477 automation_menu_entry *ame = (automation_menu_entry *)user_data;
478 ame->gui->on_automation_set_lower_or_upper(ame, true);
481 void plugin_gui::cleanup_automation_entries()
483 for (int i = 0; i < (int)automation_menu_callback_data.size(); i++)
484 delete automation_menu_callback_data[i];
485 automation_menu_callback_data.clear();
488 void plugin_gui::on_control_popup(param_control *ctl, int param_no)
490 cleanup_automation_entries();
491 if (param_no == -1)
492 return;
493 context_menu_param_no = param_no;
494 GtkWidget *menu = gtk_menu_new();
496 multimap<uint32_t, automation_range> mappings;
497 plugin->get_automation(param_no, mappings);
499 context_menu_last_designator = plugin->get_last_automation_source();
501 GtkWidget *item;
502 if (context_menu_last_designator != 0xFFFFFFFF)
504 stringstream ss;
505 ss << "_Bind to: Ch" << (1 + (context_menu_last_designator >> 8)) << ", CC#" << (context_menu_last_designator & 127);
506 item = gtk_menu_item_new_with_mnemonic(ss.str().c_str());
507 g_signal_connect(item, "activate", (GCallback)on_automation_add, this);
508 gtk_menu_shell_append(GTK_MENU_SHELL(menu), item);
510 else
512 item = gtk_menu_item_new_with_label("Send CC to automate");
513 gtk_widget_set_sensitive(item, FALSE);
514 gtk_menu_shell_append(GTK_MENU_SHELL(menu), item);
517 for(multimap<uint32_t, automation_range>::const_iterator i = mappings.begin(); i != mappings.end(); i++)
519 automation_menu_entry *ame = new automation_menu_entry(this, i->first);
520 automation_menu_callback_data.push_back(ame);
521 stringstream ss;
522 ss << "Mapping: Ch" << (1 + (i->first >> 8)) << ", CC#" << (i->first & 127);
523 item = gtk_menu_item_new_with_label(ss.str().c_str());
524 gtk_menu_shell_append(GTK_MENU_SHELL(menu), item);
526 GtkWidget *submenu = gtk_menu_new();
527 gtk_menu_item_set_submenu(GTK_MENU_ITEM(item), submenu);
529 item = gtk_menu_item_new_with_mnemonic("_Delete");
530 g_signal_connect(item, "activate", (GCallback)on_automation_delete, ame);
531 gtk_menu_shell_append(GTK_MENU_SHELL(submenu), item);
533 item = gtk_menu_item_new_with_mnemonic("Set _lower limit");
534 g_signal_connect(item, "activate", (GCallback)on_automation_set_lower, ame);
535 gtk_menu_shell_append(GTK_MENU_SHELL(submenu), item);
537 item = gtk_menu_item_new_with_mnemonic("Set _upper limit");
538 g_signal_connect(item, "activate", (GCallback)on_automation_set_upper, ame);
539 gtk_menu_shell_append(GTK_MENU_SHELL(submenu), item);
540 //g_signal_connect(item, "activate", (GCallback)on_automation_add, this);
544 gtk_widget_show_all(menu);
545 gtk_menu_popup(GTK_MENU(menu), NULL, NULL, NULL, NULL, 3, gtk_get_current_event_time());
548 plugin_gui::~plugin_gui()
550 cleanup_automation_entries();
551 delete preset_access;
552 for (std::vector<param_control *>::iterator i = params.begin(); i != params.end(); i++)
554 delete *i;
558 /***************************** GUI environment ********************************************/
560 bool window_update_controller::check_redraw(GtkWidget *toplevel)
562 GdkWindow *gdkwin = gtk_widget_get_window(toplevel);
563 if (!gdkwin)
564 return false;
566 if (!gdk_window_is_viewable(gdkwin))
567 return false;
568 GdkWindowState state = gdk_window_get_state(gdkwin);
569 if (state & GDK_WINDOW_STATE_ICONIFIED)
571 ++refresh_counter;
572 if (refresh_counter & 15)
573 return false;
575 return true;
578 /***************************** GUI environment ********************************************/
580 gui_environment::gui_environment()
582 keyfile = g_key_file_new();
584 gchar *fn = g_build_filename(getenv("HOME"), ".calfrc", NULL);
585 string filename = fn;
586 g_free(fn);
587 g_key_file_load_from_file(keyfile, filename.c_str(), (GKeyFileFlags)(G_KEY_FILE_KEEP_COMMENTS | G_KEY_FILE_KEEP_TRANSLATIONS), NULL);
589 config_db = new calf_utils::gkeyfile_config_db(keyfile, filename.c_str(), "gui");
590 gui_config.load(config_db);
593 gui_environment::~gui_environment()
595 delete config_db;
596 if (keyfile)
597 g_key_file_free(keyfile);