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>
29 using namespace calf_plugins
;
32 /******************************** GUI proper ********************************/
34 plugin_gui::plugin_gui(plugin_gui_window
*_window
)
35 : last_status_serial_no(0)
43 preset_access
= new gui_preset_access(this);
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
;
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
)
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());
130 param_no
= it
->second
;
135 void plugin_gui::xml_element_start(const char *element
, const char *attributes
[])
141 control_base::xml_attribute_map xam
;
144 xam
[attributes
[0]] = attributes
[1];
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"];
156 if (cond
.substr(0, 1) == "!") {
160 if (window
->environment
->check_condition(cond
.c_str()) == state
)
165 control_base
*cc
= create_widget_from_xml(element
, attributes
);
167 g_error("Unxpected element %s in GUI definition\n", element
);
174 void plugin_gui::xml_element_end(void *data
, const char *element
)
176 plugin_gui
*gui
= (plugin_gui
*)data
;
177 if (gui
->ignore_stack
) {
181 if (!strcmp(element
, "if"))
184 control_base
*control
= gui
->stack
.back();
187 gui
->stack
.pop_back();
188 if (gui
->stack
.empty())
190 gui
->top_container
= control
;
191 gtk_widget_show_all(control
->widget
);
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");
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"));
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");
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
]);
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
]);
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
;
294 par2ctl
.erase(it
, orig
);
300 unsigned last
= params
.size() - 1;
301 for (unsigned i
= 0; i
< params
.size(); ++i
)
303 if (params
[i
] == ctl
)
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
;
324 for (unsigned i
= 0; i
< params
.size(); i
++)
326 int param_no
= params
[i
]->param_no
;
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
)))
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
++)
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
)
359 void plugin_gui::set_param_value(int param_no
, float value
, param_control
*originator
)
361 plugin
->set_param_value(param_no
, value
);
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())
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
);
389 gtk_widget_show(leftBG
);
390 gtk_widget_show(rightBG
);
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())
422 plugin
->add_automation(context_menu_last_designator
, automation_range(i
->second
.min_value
, mapped
, context_menu_param_no
));
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();
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();
461 if (context_menu_last_designator
!= 0xFFFFFFFF)
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
);
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
);
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
);
532 if (!gdk_window_is_viewable(gdkwin
))
534 GdkWindowState state
= gdk_window_get_state(gdkwin
);
535 if (state
& GDK_WINDOW_STATE_ICONIFIED
)
538 if (refresh_counter
& 15)
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
;
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()
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
))
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_
++) {
579 i
[i_
->first
] = create_image(i_
->first
);
582 void image_factory::set_path (string p
) {
586 GdkPixbuf
*image_factory::get (string image
) {
590 i
[image
] = create_image(image
);
593 gboolean
image_factory::available (string image
) {
594 string file
= path
+ "/" + image
+ ".png";
595 if (access(file
.c_str(), F_OK
))
599 image_factory::image_factory (string 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
;
614 i
["side_d_ne"] = NULL
;
615 i
["side_d_nw"] = NULL
;
616 i
["side_d_se"] = NULL
;
617 i
["side_d_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() {