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