Make Analyzer UI require instance-access
[calf.git] / src / gui.cpp
blob19e8ec768951c723cec30f96573926d4f3e90e58
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 param_count = 0;
41 container = NULL;
42 effect_name = NULL;
43 preset_access = new gui_preset_access(this);
44 optclosed = false;
45 optwidget = NULL;
46 optwindow = NULL;
47 opttitle = NULL;
50 control_base *plugin_gui::create_widget_from_xml(const char *element, const char *attributes[])
52 if (!strcmp(element, "knob"))
53 return new knob_param_control;
54 if (!strcmp(element, "hscale"))
55 return new hscale_param_control;
56 if (!strcmp(element, "vscale"))
57 return new vscale_param_control;
58 if (!strcmp(element, "combo"))
59 return new combo_box_param_control;
60 if (!strcmp(element, "check"))
61 return new check_param_control;
62 if (!strcmp(element, "radio"))
63 return new radio_param_control;
64 if (!strcmp(element, "toggle"))
65 return new toggle_param_control;
66 if (!strcmp(element, "tap"))
67 return new tap_button_param_control;
68 if (!strcmp(element, "spin"))
69 return new spin_param_control;
70 if (!strcmp(element, "button"))
71 return new button_param_control;
72 if (!strcmp(element, "label"))
73 return new label_param_control;
74 if (!strcmp(element, "value"))
75 return new value_param_control;
76 if (!strcmp(element, "vumeter"))
77 return new vumeter_param_control;
78 if (!strcmp(element, "line-graph"))
79 return new line_graph_param_control;
80 if (!strcmp(element, "phase-graph"))
81 return new phase_graph_param_control;
82 if (!strcmp(element, "tuner"))
83 return new tuner_param_control;
84 if (!strcmp(element, "keyboard"))
85 return new keyboard_param_control;
86 if (!strcmp(element, "curve"))
87 return new curve_param_control;
88 if (!strcmp(element, "meterscale"))
89 return new meter_scale_param_control;
90 if (!strcmp(element, "led"))
91 return new led_param_control;
92 if (!strcmp(element, "tube"))
93 return new tube_param_control;
94 if (!strcmp(element, "entry"))
95 return new entry_param_control;
96 if (!strcmp(element, "filechooser"))
97 return new filechooser_param_control;
98 if (!strcmp(element, "listview"))
99 return new listview_param_control;
100 if (!strcmp(element, "notebook"))
101 return new notebook_param_control;
102 if (!strcmp(element, "table"))
103 return new table_container;
104 if (!strcmp(element, "vbox"))
105 return new vbox_container;
106 if (!strcmp(element, "hbox"))
107 return new hbox_container;
108 if (!strcmp(element, "align"))
109 return new alignment_container;
110 if (!strcmp(element, "frame"))
111 return new frame_container;
112 if (!strcmp(element, "scrolled"))
113 return new scrolled_container;
114 return NULL;
117 void plugin_gui::xml_element_start(void *data, const char *element, const char *attributes[])
119 plugin_gui *gui = (plugin_gui *)data;
120 gui->xml_element_start(element, attributes);
123 int plugin_gui::get_param_no_by_name(string param_name)
125 int param_no = -1;
126 map<string, int>::iterator it = param_name_map.find(param_name);
127 if (it == param_name_map.end())
128 g_error("Unknown parameter %s", param_name.c_str());
129 else
130 param_no = it->second;
132 return param_no;
135 void plugin_gui::xml_element_start(const char *element, const char *attributes[])
137 if (ignore_stack) {
138 ignore_stack++;
139 return;
141 control_base::xml_attribute_map xam;
142 while(*attributes)
144 xam[attributes[0]] = attributes[1];
145 attributes += 2;
148 if (!strcmp(element, "if"))
150 if (!xam.count("cond") || xam["cond"].empty())
152 g_error("Incorrect <if cond=\"[!]symbol\"> element");
154 string cond = xam["cond"];
155 bool state = true;
156 if (cond.substr(0, 1) == "!") {
157 state = false;
158 cond.erase(0, 1);
160 if (window->environment->check_condition(cond.c_str()) == state)
161 return;
162 ignore_stack = 1;
163 return;
165 control_base *cc = create_widget_from_xml(element, attributes);
166 if (cc == NULL)
167 g_error("Unxpected element %s in GUI definition\n", element);
169 cc->attribs = xam;
170 cc->create(this);
171 stack.push_back(cc);
174 void plugin_gui::xml_element_end(void *data, const char *element)
176 plugin_gui *gui = (plugin_gui *)data;
177 if (gui->ignore_stack) {
178 gui->ignore_stack--;
179 return;
181 if (!strcmp(element, "if"))
182 return;
184 control_base *control = gui->stack.back();
185 control->created();
187 gui->stack.pop_back();
188 if (gui->stack.empty())
190 gui->top_container = control;
191 gtk_widget_show_all(control->widget);
193 else
194 gui->stack.back()->add(control);
198 GtkWidget *plugin_gui::create_from_xml(plugin_ctl_iface *_plugin, const char *xml)
200 top_container = NULL;
201 parser = XML_ParserCreate("UTF-8");
202 plugin = _plugin;
203 stack.clear();
204 ignore_stack = 0;
206 param_name_map.clear();
207 read_serials.clear();
208 int size = plugin->get_metadata_iface()->get_param_count();
209 read_serials.resize(size);
210 for (int i = 0; i < size; i++)
211 param_name_map[plugin->get_metadata_iface()->get_param_props(i)->short_name] = i;
213 XML_SetUserData(parser, this);
214 XML_SetElementHandler(parser, xml_element_start, xml_element_end);
215 XML_Status status = XML_Parse(parser, xml, strlen(xml), 1);
216 if (status == XML_STATUS_ERROR)
218 g_error("Parse error: %s in XML", XML_ErrorString(XML_GetErrorCode(parser)));
221 XML_ParserFree(parser);
222 last_status_serial_no = plugin->send_status_updates(this, 0);
223 GtkWidget *eventbox = gtk_event_box_new();
224 GtkWidget *decoTable = gtk_table_new(3, 1, FALSE);
226 // images for left side
227 GtkWidget *nwImg = gtk_image_new_from_pixbuf(window->environment->get_image_factory()->get("side_nw"));
228 GtkWidget *swImg = gtk_image_new_from_pixbuf(window->environment->get_image_factory()->get("side_sw"));
230 // images for right side
231 GtkWidget *neImg = gtk_image_new_from_pixbuf(window->environment->get_image_factory()->get("side_ne"));
232 GtkWidget *seImg = gtk_image_new_from_pixbuf(window->environment->get_image_factory()->get("side_se"));
234 // pack left box
235 leftBG = gtk_event_box_new();
236 GtkWidget *leftBox = gtk_vbox_new(FALSE, 0);
237 gtk_container_add(GTK_CONTAINER(leftBG), leftBox);
238 gtk_box_pack_start(GTK_BOX(leftBox), GTK_WIDGET(nwImg), FALSE, FALSE, 0);
239 gtk_box_pack_end(GTK_BOX(leftBox), GTK_WIDGET(swImg), FALSE, FALSE, 0);
240 gtk_widget_set_name(leftBG, "CalfPluginLeft");
242 // pack right box
243 rightBG = gtk_event_box_new();
244 GtkWidget *rightBox = gtk_vbox_new(FALSE, 0);
245 gtk_container_add(GTK_CONTAINER(rightBG), rightBox);
246 gtk_box_pack_start(GTK_BOX(rightBox), GTK_WIDGET(neImg), FALSE, FALSE, 0);
247 gtk_box_pack_end(GTK_BOX(rightBox), GTK_WIDGET(seImg), FALSE, FALSE, 0);
248 gtk_widget_set_name(rightBG, "CalfPluginRight");
250 //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);
251 gtk_table_attach(GTK_TABLE(decoTable), GTK_WIDGET(leftBG), 0, 1, 0, 1, (GtkAttachOptions)(0), (GtkAttachOptions)(GTK_EXPAND | GTK_FILL), 0, 0);
252 gtk_table_attach(GTK_TABLE(decoTable), GTK_WIDGET(rightBG), 2, 3, 0, 1, (GtkAttachOptions)(0), (GtkAttachOptions)(GTK_EXPAND | GTK_FILL), 0, 0);
254 gtk_table_attach(GTK_TABLE(decoTable), top_container->widget, 1, 2, 0, 1, (GtkAttachOptions)(GTK_EXPAND | GTK_FILL), (GtkAttachOptions)(GTK_EXPAND | GTK_FILL), 15, 5);
255 gtk_container_add( GTK_CONTAINER(eventbox), decoTable );
256 gtk_widget_set_name( GTK_WIDGET(eventbox), "Calf-Plugin" );
258 return GTK_WIDGET(eventbox);
261 void plugin_gui::send_configure(const char *key, const char *value)
263 // XXXKF this should really be replaced by a separate list of SCI-capable param controls
264 for (unsigned int i = 0; i < params.size(); i++)
266 assert(params[i] != NULL);
267 send_configure_iface *sci = dynamic_cast<send_configure_iface *>(params[i]);
268 if (sci)
269 sci->send_configure(key, value);
273 void plugin_gui::send_status(const char *key, const char *value)
275 // XXXKF this should really be replaced by a separate list of SUI-capable param controls
276 for (unsigned int i = 0; i < params.size(); i++)
278 assert(params[i] != NULL);
279 send_updates_iface *sui = dynamic_cast<send_updates_iface *>(params[i]);
280 if (sui)
281 sui->send_status(key, value);
285 void plugin_gui::remove_param_ctl(int param, param_control *ctl)
287 std::multimap<int, param_control *>::iterator it = par2ctl.find(param);
288 while(it != par2ctl.end() && it->first == param)
290 if (it->second == ctl)
292 std::multimap<int, param_control *>::iterator orig = it;
293 ++orig;
294 par2ctl.erase(it, orig);
295 it = orig;
297 else
298 ++it;
300 unsigned last = params.size() - 1;
301 for (unsigned i = 0; i < params.size(); ++i)
303 if (params[i] == ctl)
305 if (i != last)
306 std::swap(params[i], params[last]);
307 params.erase(params.begin() + last, params.end());
312 void plugin_gui::on_idle()
314 set<unsigned> changed;
315 for (unsigned i = 0; i < read_serials.size(); i++)
317 int write_serial = plugin->get_write_serial(i);
318 if (write_serial - read_serials[i] > 0)
320 read_serials[i] = write_serial;
321 changed.insert(i);
324 for (unsigned i = 0; i < params.size(); i++)
326 int param_no = params[i]->param_no;
327 if (param_no != -1)
329 const parameter_properties &props = *plugin->get_metadata_iface()->get_param_props(param_no);
330 bool is_output = (props.flags & PF_PROP_OUTPUT) != 0;
331 if (is_output || (param_no != -1 && changed.count(param_no)))
332 params[i]->set();
334 params[i]->on_idle();
336 last_status_serial_no = plugin->send_status_updates(this, last_status_serial_no);
337 // XXXKF iterate over par2ctl, too...
340 void plugin_gui::refresh()
342 for (unsigned int i = 0; i < params.size(); i++)
343 params[i]->set();
344 plugin->send_configures(this);
345 last_status_serial_no = plugin->send_status_updates(this, last_status_serial_no);
348 void plugin_gui::refresh(int param_no, param_control *originator)
350 std::multimap<int, param_control *>::iterator it = par2ctl.find(param_no);
351 while(it != par2ctl.end() && it->first == param_no)
353 if (it->second != originator)
354 it->second->set();
355 ++it;
359 void plugin_gui::set_param_value(int param_no, float value, param_control *originator)
361 plugin->set_param_value(param_no, value);
362 refresh(param_no);
365 /// Get a radio button group (if it exists) for a parameter
366 GSList *plugin_gui::get_radio_group(int param)
368 map<int, GSList *>::const_iterator i = param_radio_groups.find(param);
369 if (i == param_radio_groups.end())
370 return NULL;
371 else
372 return i->second;
375 /// Set a radio button group for a parameter
376 void plugin_gui::set_radio_group(int param, GSList *group)
378 param_radio_groups[param] = group;
381 void plugin_gui::show_rack_ears(bool show)
383 // if hidden, add a no-show-all attribute so that LV2 host doesn't accidentally override
384 // the setting by doing a show_all on the outermost container
385 gtk_widget_set_no_show_all(leftBG, !show);
386 gtk_widget_set_no_show_all(rightBG, !show);
387 if (show)
389 gtk_widget_show(leftBG);
390 gtk_widget_show(rightBG);
392 else
394 gtk_widget_hide(leftBG);
395 gtk_widget_hide(rightBG);
399 void plugin_gui::on_automation_add(GtkWidget *widget, void *user_data)
401 plugin_gui *self = (plugin_gui *)user_data;
402 self->plugin->add_automation(self->context_menu_last_designator, automation_range(0, 1, self->context_menu_param_no));
405 void plugin_gui::on_automation_delete(GtkWidget *widget, void *user_data)
407 automation_menu_entry *ame = (automation_menu_entry *)user_data;
408 ame->gui->plugin->delete_automation(ame->source, ame->gui->context_menu_param_no);
411 void plugin_gui::on_automation_set_lower_or_upper(automation_menu_entry *ame, bool is_upper)
413 const parameter_properties *props = plugin->get_metadata_iface()->get_param_props(context_menu_param_no);
414 float mapped = props->to_01(plugin->get_param_value(context_menu_param_no));
416 automation_map mappings;
417 plugin->get_automation(context_menu_param_no, mappings);
418 automation_map::const_iterator i = mappings.find(ame->source);
419 if (i != mappings.end())
421 if (is_upper)
422 plugin->add_automation(context_menu_last_designator, automation_range(i->second.min_value, mapped, context_menu_param_no));
423 else
424 plugin->add_automation(context_menu_last_designator, automation_range(mapped, i->second.max_value, context_menu_param_no));
428 void plugin_gui::on_automation_set_lower(GtkWidget *widget, void *user_data)
430 automation_menu_entry *ame = (automation_menu_entry *)user_data;
431 ame->gui->on_automation_set_lower_or_upper(ame, false);
434 void plugin_gui::on_automation_set_upper(GtkWidget *widget, void *user_data)
436 automation_menu_entry *ame = (automation_menu_entry *)user_data;
437 ame->gui->on_automation_set_lower_or_upper(ame, true);
440 void plugin_gui::cleanup_automation_entries()
442 for (int i = 0; i < (int)automation_menu_callback_data.size(); i++)
443 delete automation_menu_callback_data[i];
444 automation_menu_callback_data.clear();
447 void plugin_gui::on_control_popup(param_control *ctl, int param_no)
449 cleanup_automation_entries();
450 if (param_no == -1)
451 return;
452 context_menu_param_no = param_no;
453 GtkWidget *menu = gtk_menu_new();
455 multimap<uint32_t, automation_range> mappings;
456 plugin->get_automation(param_no, mappings);
458 context_menu_last_designator = plugin->get_last_automation_source();
460 GtkWidget *item;
461 if (context_menu_last_designator != 0xFFFFFFFF)
463 stringstream ss;
464 ss << "_Bind to: Ch" << (1 + (context_menu_last_designator >> 8)) << ", CC#" << (context_menu_last_designator & 127);
465 item = gtk_menu_item_new_with_mnemonic(ss.str().c_str());
466 g_signal_connect(item, "activate", (GCallback)on_automation_add, this);
467 gtk_menu_shell_append(GTK_MENU_SHELL(menu), item);
469 else
471 item = gtk_menu_item_new_with_label("Send CC to automate");
472 gtk_widget_set_sensitive(item, FALSE);
473 gtk_menu_shell_append(GTK_MENU_SHELL(menu), item);
476 for(multimap<uint32_t, automation_range>::const_iterator i = mappings.begin(); i != mappings.end(); ++i)
478 automation_menu_entry *ame = new automation_menu_entry(this, i->first);
479 automation_menu_callback_data.push_back(ame);
480 stringstream ss;
481 ss << "Mapping: Ch" << (1 + (i->first >> 8)) << ", CC#" << (i->first & 127);
482 item = gtk_menu_item_new_with_label(ss.str().c_str());
483 gtk_menu_shell_append(GTK_MENU_SHELL(menu), item);
485 GtkWidget *submenu = gtk_menu_new();
486 gtk_menu_item_set_submenu(GTK_MENU_ITEM(item), submenu);
488 item = gtk_menu_item_new_with_mnemonic("_Delete");
489 g_signal_connect(item, "activate", (GCallback)on_automation_delete, ame);
490 gtk_menu_shell_append(GTK_MENU_SHELL(submenu), item);
492 item = gtk_menu_item_new_with_mnemonic("Set _lower limit");
493 g_signal_connect(item, "activate", (GCallback)on_automation_set_lower, ame);
494 gtk_menu_shell_append(GTK_MENU_SHELL(submenu), item);
496 item = gtk_menu_item_new_with_mnemonic("Set _upper limit");
497 g_signal_connect(item, "activate", (GCallback)on_automation_set_upper, ame);
498 gtk_menu_shell_append(GTK_MENU_SHELL(submenu), item);
499 //g_signal_connect(item, "activate", (GCallback)on_automation_add, this);
503 gtk_widget_show_all(menu);
504 gtk_menu_popup(GTK_MENU(menu), NULL, NULL, NULL, NULL, 3, gtk_get_current_event_time());
507 void plugin_gui::destroy_child_widgets(GtkWidget *parent)
509 if (parent && GTK_IS_CONTAINER(parent))
511 GList *children = gtk_container_get_children(GTK_CONTAINER(parent));
512 for(GList *p = children; p; p = p->next)
513 gtk_widget_destroy(GTK_WIDGET(p->data));
514 g_list_free(children);
518 plugin_gui::~plugin_gui()
520 cleanup_automation_entries();
521 delete preset_access;
524 /***************************** GUI environment ********************************************/
526 bool window_update_controller::check_redraw(GtkWidget *toplevel)
528 GdkWindow *gdkwin = gtk_widget_get_window(toplevel);
529 if (!gdkwin)
530 return false;
532 if (!gdk_window_is_viewable(gdkwin))
533 return false;
534 GdkWindowState state = gdk_window_get_state(gdkwin);
535 if (state & GDK_WINDOW_STATE_ICONIFIED)
537 ++refresh_counter;
538 if (refresh_counter & 15)
539 return false;
541 return true;
544 /***************************** GUI environment ********************************************/
546 gui_environment::gui_environment()
548 keyfile = g_key_file_new();
550 gchar *fn = g_build_filename(getenv("HOME"), ".calfrc", NULL);
551 string filename = fn;
552 g_free(fn);
553 g_key_file_load_from_file(keyfile, filename.c_str(), (GKeyFileFlags)(G_KEY_FILE_KEEP_COMMENTS | G_KEY_FILE_KEEP_TRANSLATIONS), NULL);
555 config_db = new calf_utils::gkeyfile_config_db(keyfile, filename.c_str(), "gui");
556 gui_config.load(config_db);
557 images = image_factory();
558 images.set_path(PKGLIBDIR "styles/" + get_config()->style);
561 gui_environment::~gui_environment()
563 delete config_db;
564 if (keyfile)
565 g_key_file_free(keyfile);
569 /***************************** Image Factory **************************************/
570 GdkPixbuf *image_factory::create_image (string image) {
571 string file = path + "/" + image + ".png";
572 if (access(file.c_str(), F_OK))
573 return NULL;
574 return gdk_pixbuf_new_from_file(file.c_str(), NULL);
576 void image_factory::recreate_images () {
577 for (map<string, GdkPixbuf*>::iterator i_ = i.begin(); i_ != i.end(); i_++) {
578 if (i[i_->first])
579 i[i_->first] = create_image(i_->first);
582 void image_factory::set_path (string p) {
583 path = p;
584 recreate_images();
586 GdkPixbuf *image_factory::get (string image) {
587 if (!i.count(image))
588 return NULL;
589 if (!i.at(image))
590 i[image] = create_image(image);
591 return i[image];
593 gboolean image_factory::available (string image) {
594 string file = path + "/" + image + ".png";
595 if (access(file.c_str(), F_OK))
596 return false;
597 return true;
599 image_factory::image_factory (string p) {
600 set_path(p);
602 i["combo_arrow"] = NULL;
603 i["light_top"] = NULL;
604 i["light_bottom"] = NULL;
605 i["notebook_screw"] = NULL;
606 i["logo_button"] = NULL;
608 i["knob_1"] = NULL;
609 i["knob_2"] = NULL;
610 i["knob_3"] = NULL;
611 i["knob_4"] = NULL;
612 i["knob_5"] = NULL;
614 i["side_d_ne"] = NULL;
615 i["side_d_nw"] = NULL;
616 i["side_d_se"] = NULL;
617 i["side_d_sw"] = NULL;
619 i["side_ne"] = NULL;
620 i["side_nw"] = NULL;
621 i["side_se"] = NULL;
622 i["side_sw"] = NULL;
623 i["side_e_logo"] = NULL;
625 i["slider_1_horiz"] = NULL;
626 i["slider_1_vert"] = NULL;
627 i["slider_2_horiz"] = NULL;
628 i["slider_2_vert"] = NULL;
630 i["tap_active"] = NULL;
631 i["tap_inactive"] = NULL;
632 i["tap_prelight"] = NULL;
634 i["toggle_0"] = NULL;
635 i["toggle_1"] = NULL;
636 i["toggle_2"] = NULL;
638 i["toggle_2_block"] = NULL;
639 i["toggle_2_bypass"] = NULL;
640 i["toggle_2_bypass2"] = NULL;
641 i["toggle_2_fast"] = NULL;
642 i["toggle_2_listen"] = NULL;
643 i["toggle_2_logarithmic"] = NULL;
644 i["toggle_2_magnetical"] = NULL;
645 i["toggle_2_mono"] = NULL;
646 i["toggle_2_muffle"] = NULL;
647 i["toggle_2_mute"] = NULL;
648 i["toggle_2_phase"] = NULL;
649 i["toggle_2_sc_comp"] = NULL;
650 i["toggle_2_sc_filter"] = NULL;
651 i["toggle_2_softclip"] = NULL;
652 i["toggle_2_solo"] = NULL;
653 i["toggle_2_sync"] = NULL;
654 i["toggle_2_void"] = NULL;
656 image_factory::~image_factory() {