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)
40 current_control
= NULL
;
44 preset_access
= new gui_preset_access(this);
47 param_control
*plugin_gui::create_control_from_xml(const char *element
, const char *attributes
[])
49 if (!strcmp(element
, "knob"))
50 return new knob_param_control
;
51 if (!strcmp(element
, "hscale"))
52 return new hscale_param_control
;
53 if (!strcmp(element
, "vscale"))
54 return new vscale_param_control
;
55 if (!strcmp(element
, "combo"))
56 return new combo_box_param_control
;
57 if (!strcmp(element
, "check"))
58 return new check_param_control
;
59 if (!strcmp(element
, "radio"))
60 return new radio_param_control
;
61 if (!strcmp(element
, "toggle"))
62 return new toggle_param_control
;
63 if (!strcmp(element
, "tap"))
64 return new tap_button_param_control
;
65 if (!strcmp(element
, "spin"))
66 return new spin_param_control
;
67 if (!strcmp(element
, "button"))
68 return new button_param_control
;
69 if (!strcmp(element
, "label"))
70 return new label_param_control
;
71 if (!strcmp(element
, "value"))
72 return new value_param_control
;
73 if (!strcmp(element
, "vumeter"))
74 return new vumeter_param_control
;
75 if (!strcmp(element
, "line-graph"))
76 return new line_graph_param_control
;
77 if (!strcmp(element
, "phase-graph"))
78 return new phase_graph_param_control
;
79 if (!strcmp(element
, "keyboard"))
80 return new keyboard_param_control
;
81 if (!strcmp(element
, "curve"))
82 return new curve_param_control
;
83 if (!strcmp(element
, "led"))
84 return new led_param_control
;
85 if (!strcmp(element
, "tube"))
86 return new tube_param_control
;
87 if (!strcmp(element
, "entry"))
88 return new entry_param_control
;
89 if (!strcmp(element
, "filechooser"))
90 return new filechooser_param_control
;
91 if (!strcmp(element
, "listview"))
92 return new listview_param_control
;
93 if (!strcmp(element
, "notebook"))
94 return new notebook_param_control
;
98 control_container
*plugin_gui::create_container_from_xml(const char *element
, const char *attributes
[])
100 if (!strcmp(element
, "table"))
101 return new table_container
;
102 if (!strcmp(element
, "vbox"))
103 return new vbox_container
;
104 if (!strcmp(element
, "hbox"))
105 return new hbox_container
;
106 if (!strcmp(element
, "align"))
107 return new alignment_container
;
108 if (!strcmp(element
, "frame"))
109 return new frame_container
;
110 if (!strcmp(element
, "scrolled"))
111 return new scrolled_container
;
115 void plugin_gui::xml_element_start(void *data
, const char *element
, const char *attributes
[])
117 plugin_gui
*gui
= (plugin_gui
*)data
;
118 gui
->xml_element_start(element
, attributes
);
121 int plugin_gui::get_param_no_by_name(string param_name
)
124 map
<string
, int>::iterator it
= param_name_map
.find(param_name
);
125 if (it
== param_name_map
.end())
126 g_error("Unknown parameter %s", param_name
.c_str());
128 param_no
= it
->second
;
133 void plugin_gui::xml_element_start(const char *element
, const char *attributes
[])
139 control_base::xml_attribute_map xam
;
142 xam
[attributes
[0]] = attributes
[1];
146 if (!strcmp(element
, "if"))
148 if (!xam
.count("cond") || xam
["cond"].empty())
150 g_error("Incorrect <if cond=\"[!]symbol\"> element");
152 string cond
= xam
["cond"];
154 if (cond
.substr(0, 1) == "!") {
158 if (window
->environment
->check_condition(cond
.c_str()) == state
)
163 control_container
*cc
= create_container_from_xml(element
, attributes
);
167 cc
->create(this, element
, xam
);
168 cc
->set_std_properties();
169 gtk_container_set_border_width(cc
->container
, cc
->get_int("border"));
171 container_stack
.push_back(cc
);
172 current_control
= NULL
;
175 if (!container_stack
.empty())
177 current_control
= create_control_from_xml(element
, attributes
);
180 current_control
->control_name
= element
;
181 current_control
->attribs
= xam
;
183 if (xam
.count("param"))
185 param_no
= get_param_no_by_name(xam
["param"]);
188 current_control
->param_variable
= plugin
->get_metadata_iface()->get_param_props(param_no
)->short_name
;
189 current_control
->create(this, param_no
);
190 current_control
->set_std_properties();
191 current_control
->init_xml(element
);
192 current_control
->add_context_menu_handler();
193 if (current_control
->is_container()) {
194 gtk_container_set_border_width(current_control
->container
, current_control
->get_int("border"));
195 container_stack
.push_back(current_control
);
196 control_stack
.push_back(current_control
);
201 g_error("Unxpected element %s in GUI definition\n", element
);
204 void plugin_gui::xml_element_end(void *data
, const char *element
)
206 plugin_gui
*gui
= (plugin_gui
*)data
;
207 unsigned int ss
= gui
->container_stack
.size();
208 if (gui
->ignore_stack
) {
212 if (!strcmp(element
, "if"))
216 if (gui
->current_control
and !gui
->current_control
->is_container())
218 (*gui
->container_stack
.rbegin())->add(gui
->current_control
->widget
, gui
->current_control
);
219 gui
->current_control
->hook_params();
220 gui
->current_control
->set();
221 gui
->current_control
->created();
222 gtk_widget_show_all(gui
->current_control
->widget
);
223 gui
->current_control
= NULL
;
227 gui
->container_stack
[ss
- 2]->add(GTK_WIDGET(gui
->container_stack
[ss
- 1]->container
), gui
->container_stack
[ss
- 1]);
228 gtk_widget_show_all(GTK_WIDGET(gui
->container_stack
[ss
- 1]->container
));
231 gui
->top_container
= gui
->container_stack
[0];
232 gtk_widget_show_all(GTK_WIDGET(gui
->container_stack
[0]->container
));
234 int css
= gui
->control_stack
.size();
235 if (ss
> 1 and gui
->container_stack
[ss
- 1]->is_container() and css
) {
236 gui
->control_stack
[css
- 1]->hook_params();
237 gui
->control_stack
[css
- 1]->set();
238 gui
->control_stack
[css
- 1]->created();
240 gui
->control_stack
.pop_back();
242 gui
->container_stack
.pop_back();
243 gui
->current_control
= NULL
;
247 GtkWidget
*plugin_gui::create_from_xml(plugin_ctl_iface
*_plugin
, const char *xml
)
249 current_control
= NULL
;
250 top_container
= NULL
;
251 parser
= XML_ParserCreate("UTF-8");
253 container_stack
.clear();
254 control_stack
.clear();
257 param_name_map
.clear();
258 read_serials
.clear();
259 int size
= plugin
->get_metadata_iface()->get_param_count();
260 read_serials
.resize(size
);
261 for (int i
= 0; i
< size
; i
++)
262 param_name_map
[plugin
->get_metadata_iface()->get_param_props(i
)->short_name
] = i
;
264 XML_SetUserData(parser
, this);
265 XML_SetElementHandler(parser
, xml_element_start
, xml_element_end
);
266 XML_Status status
= XML_Parse(parser
, xml
, strlen(xml
), 1);
267 if (status
== XML_STATUS_ERROR
)
269 g_error("Parse error: %s in XML", XML_ErrorString(XML_GetErrorCode(parser
)));
272 XML_ParserFree(parser
);
273 last_status_serial_no
= plugin
->send_status_updates(this, 0);
274 GtkWidget
*eventbox
= gtk_event_box_new();
275 GtkWidget
*decoTable
= gtk_table_new(3, 1, FALSE
);
278 // overall background
279 //GtkWidget *bgImg = gtk_image_new_from_file(PKGLIBDIR "/background.png");
280 //gtk_widget_set_size_request(GTK_WIDGET(bgImg), 1, 1);
282 // images for left side
283 GtkWidget
*nwImg
= gtk_image_new_from_file(PKGLIBDIR
"/side_nw.png");
284 GtkWidget
*swImg
= gtk_image_new_from_file(PKGLIBDIR
"/side_sw.png");
285 GtkWidget
*wImg
= gtk_image_new_from_file(PKGLIBDIR
"/side_w.png");
286 gtk_widget_set_size_request(GTK_WIDGET(wImg
), 56, 1);
288 // images for right side
289 GtkWidget
*neImg
= gtk_image_new_from_file(PKGLIBDIR
"/side_ne.png");
290 GtkWidget
*seImg
= gtk_image_new_from_file(PKGLIBDIR
"/side_se.png");
291 GtkWidget
*eImg
= gtk_image_new_from_file(PKGLIBDIR
"/side_e.png");
292 GtkWidget
*logoImg
= gtk_image_new_from_file(PKGLIBDIR
"/side_e_logo.png");
293 gtk_widget_set_size_request(GTK_WIDGET(eImg
), 56, 1);
296 leftBox
= gtk_vbox_new(FALSE
, 0);
297 gtk_box_pack_start(GTK_BOX(leftBox
), GTK_WIDGET(nwImg
), FALSE
, FALSE
, 0);
298 gtk_box_pack_start(GTK_BOX(leftBox
), GTK_WIDGET(wImg
), TRUE
, TRUE
, 0);
299 gtk_box_pack_end(GTK_BOX(leftBox
), GTK_WIDGET(swImg
), FALSE
, FALSE
, 0);
302 rightBox
= gtk_vbox_new(FALSE
, 0);
303 gtk_box_pack_start(GTK_BOX(rightBox
), GTK_WIDGET(neImg
), FALSE
, FALSE
, 0);
304 gtk_box_pack_start(GTK_BOX(rightBox
), GTK_WIDGET(eImg
), TRUE
, TRUE
, 0);
305 gtk_box_pack_start(GTK_BOX(rightBox
), GTK_WIDGET(logoImg
), FALSE
, FALSE
, 0);
306 gtk_box_pack_end(GTK_BOX(rightBox
), GTK_WIDGET(seImg
), FALSE
, FALSE
, 0);
308 //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);
309 gtk_table_attach(GTK_TABLE(decoTable
), GTK_WIDGET(leftBox
), 0, 1, 0, 1, (GtkAttachOptions
)(0), (GtkAttachOptions
)(GTK_EXPAND
| GTK_FILL
), 0, 0);
310 gtk_table_attach(GTK_TABLE(decoTable
), GTK_WIDGET(rightBox
), 2, 3, 0, 1, (GtkAttachOptions
)(0), (GtkAttachOptions
)(GTK_EXPAND
| GTK_FILL
), 0, 0);
312 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);
313 gtk_container_add( GTK_CONTAINER(eventbox
), decoTable
);
314 gtk_widget_set_name( GTK_WIDGET(eventbox
), "Calf-Plugin" );
316 // create window with viewport
317 // GtkWidget *sw = gtk_scrolled_window_new(NULL, NULL);
318 // gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(sw), GTK_POLICY_NEVER, GTK_POLICY_NEVER);
319 // gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(sw), GTK_SHADOW_NONE);
320 // gtk_scrolled_window_add_with_viewport(GTK_SCROLLED_WINDOW(sw), GTK_WIDGET(decoTable));
322 return GTK_WIDGET(eventbox
);
325 void plugin_gui::send_configure(const char *key
, const char *value
)
327 // XXXKF this should really be replaced by a separate list of SCI-capable param controls
328 for (unsigned int i
= 0; i
< params
.size(); i
++)
330 assert(params
[i
] != NULL
);
331 send_configure_iface
*sci
= dynamic_cast<send_configure_iface
*>(params
[i
]);
333 sci
->send_configure(key
, value
);
337 void plugin_gui::send_status(const char *key
, const char *value
)
339 // XXXKF this should really be replaced by a separate list of SUI-capable param controls
340 for (unsigned int i
= 0; i
< params
.size(); i
++)
342 assert(params
[i
] != NULL
);
343 send_updates_iface
*sui
= dynamic_cast<send_updates_iface
*>(params
[i
]);
345 sui
->send_status(key
, value
);
349 void plugin_gui::on_idle()
351 set
<unsigned> changed
;
352 for (unsigned i
= 0; i
< read_serials
.size(); i
++)
354 int write_serial
= plugin
->get_write_serial(i
);
355 if (write_serial
- read_serials
[i
] > 0)
357 read_serials
[i
] = write_serial
;
361 for (unsigned i
= 0; i
< params
.size(); i
++)
363 int param_no
= params
[i
]->param_no
;
366 const parameter_properties
&props
= *plugin
->get_metadata_iface()->get_param_props(param_no
);
367 bool is_output
= (props
.flags
& PF_PROP_OUTPUT
) != 0;
368 if (is_output
|| (param_no
!= -1 && changed
.count(param_no
)))
371 params
[i
]->on_idle();
373 last_status_serial_no
= plugin
->send_status_updates(this, last_status_serial_no
);
374 // XXXKF iterate over par2ctl, too...
377 void plugin_gui::refresh()
379 for (unsigned int i
= 0; i
< params
.size(); i
++)
381 plugin
->send_configures(this);
382 last_status_serial_no
= plugin
->send_status_updates(this, last_status_serial_no
);
385 void plugin_gui::refresh(int param_no
, param_control
*originator
)
387 std::multimap
<int, param_control
*>::iterator it
= par2ctl
.find(param_no
);
388 while(it
!= par2ctl
.end() && it
->first
== param_no
)
390 if (it
->second
!= originator
)
396 void plugin_gui::set_param_value(int param_no
, float value
, param_control
*originator
)
398 plugin
->set_param_value(param_no
, value
);
402 /// Get a radio button group (if it exists) for a parameter
403 GSList
*plugin_gui::get_radio_group(int param
)
405 map
<int, GSList
*>::const_iterator i
= param_radio_groups
.find(param
);
406 if (i
== param_radio_groups
.end())
412 /// Set a radio button group for a parameter
413 void plugin_gui::set_radio_group(int param
, GSList
*group
)
415 param_radio_groups
[param
] = group
;
418 void plugin_gui::show_rack_ears(bool show
)
420 // if hidden, add a no-show-all attribute so that LV2 host doesn't accidentally override
421 // the setting by doing a show_all on the outermost container
422 gtk_widget_set_no_show_all(leftBox
, !show
);
423 gtk_widget_set_no_show_all(rightBox
, !show
);
426 gtk_widget_show(leftBox
);
427 gtk_widget_show(rightBox
);
431 gtk_widget_hide(leftBox
);
432 gtk_widget_hide(rightBox
);
436 void plugin_gui::on_automation_add(GtkWidget
*widget
, void *user_data
)
438 plugin_gui
*self
= (plugin_gui
*)user_data
;
439 self
->plugin
->add_automation(self
->context_menu_last_designator
, automation_range(0, 1, self
->context_menu_param_no
));
442 void plugin_gui::on_automation_delete(GtkWidget
*widget
, void *user_data
)
444 automation_menu_entry
*ame
= (automation_menu_entry
*)user_data
;
445 ame
->gui
->plugin
->delete_automation(ame
->source
, ame
->gui
->context_menu_param_no
);
448 void plugin_gui::on_automation_set_lower_or_upper(automation_menu_entry
*ame
, bool is_upper
)
450 const parameter_properties
*props
= plugin
->get_metadata_iface()->get_param_props(context_menu_param_no
);
451 float mapped
= props
->to_01(plugin
->get_param_value(context_menu_param_no
));
453 automation_map mappings
;
454 plugin
->get_automation(context_menu_param_no
, mappings
);
455 automation_map::const_iterator i
= mappings
.find(ame
->source
);
456 if (i
!= mappings
.end())
459 plugin
->add_automation(context_menu_last_designator
, automation_range(i
->second
.min_value
, mapped
, context_menu_param_no
));
461 plugin
->add_automation(context_menu_last_designator
, automation_range(mapped
, i
->second
.max_value
, context_menu_param_no
));
465 void plugin_gui::on_automation_set_lower(GtkWidget
*widget
, void *user_data
)
467 automation_menu_entry
*ame
= (automation_menu_entry
*)user_data
;
468 ame
->gui
->on_automation_set_lower_or_upper(ame
, false);
471 void plugin_gui::on_automation_set_upper(GtkWidget
*widget
, void *user_data
)
473 automation_menu_entry
*ame
= (automation_menu_entry
*)user_data
;
474 ame
->gui
->on_automation_set_lower_or_upper(ame
, true);
477 void plugin_gui::cleanup_automation_entries()
479 for (int i
= 0; i
< (int)automation_menu_callback_data
.size(); i
++)
480 delete automation_menu_callback_data
[i
];
481 automation_menu_callback_data
.clear();
484 void plugin_gui::on_control_popup(param_control
*ctl
, int param_no
)
486 cleanup_automation_entries();
489 context_menu_param_no
= param_no
;
490 GtkWidget
*menu
= gtk_menu_new();
492 multimap
<uint32_t, automation_range
> mappings
;
493 plugin
->get_automation(param_no
, mappings
);
495 context_menu_last_designator
= plugin
->get_last_automation_source();
498 if (context_menu_last_designator
!= 0xFFFFFFFF)
501 ss
<< "_Bind to: Ch" << (1 + (context_menu_last_designator
>> 8)) << ", CC#" << (context_menu_last_designator
& 127);
502 item
= gtk_menu_item_new_with_mnemonic(ss
.str().c_str());
503 g_signal_connect(item
, "activate", (GCallback
)on_automation_add
, this);
504 gtk_menu_shell_append(GTK_MENU_SHELL(menu
), item
);
508 item
= gtk_menu_item_new_with_label("Send CC to automate");
509 gtk_widget_set_sensitive(item
, FALSE
);
510 gtk_menu_shell_append(GTK_MENU_SHELL(menu
), item
);
513 for(multimap
<uint32_t, automation_range
>::const_iterator i
= mappings
.begin(); i
!= mappings
.end(); i
++)
515 automation_menu_entry
*ame
= new automation_menu_entry(this, i
->first
);
516 automation_menu_callback_data
.push_back(ame
);
518 ss
<< "Mapping: Ch" << (1 + (i
->first
>> 8)) << ", CC#" << (i
->first
& 127);
519 item
= gtk_menu_item_new_with_label(ss
.str().c_str());
520 gtk_menu_shell_append(GTK_MENU_SHELL(menu
), item
);
522 GtkWidget
*submenu
= gtk_menu_new();
523 gtk_menu_item_set_submenu(GTK_MENU_ITEM(item
), submenu
);
525 item
= gtk_menu_item_new_with_mnemonic("_Delete");
526 g_signal_connect(item
, "activate", (GCallback
)on_automation_delete
, ame
);
527 gtk_menu_shell_append(GTK_MENU_SHELL(submenu
), item
);
529 item
= gtk_menu_item_new_with_mnemonic("Set _lower limit");
530 g_signal_connect(item
, "activate", (GCallback
)on_automation_set_lower
, ame
);
531 gtk_menu_shell_append(GTK_MENU_SHELL(submenu
), item
);
533 item
= gtk_menu_item_new_with_mnemonic("Set _upper limit");
534 g_signal_connect(item
, "activate", (GCallback
)on_automation_set_upper
, ame
);
535 gtk_menu_shell_append(GTK_MENU_SHELL(submenu
), item
);
536 //g_signal_connect(item, "activate", (GCallback)on_automation_add, this);
540 gtk_widget_show_all(menu
);
541 gtk_menu_popup(GTK_MENU(menu
), NULL
, NULL
, NULL
, NULL
, 3, gtk_get_current_event_time());
544 plugin_gui::~plugin_gui()
546 cleanup_automation_entries();
547 delete preset_access
;
548 for (std::vector
<param_control
*>::iterator i
= params
.begin(); i
!= params
.end(); i
++)
554 /***************************** GUI environment ********************************************/
556 bool window_update_controller::check_redraw(GtkWidget
*toplevel
)
558 GdkWindow
*gdkwin
= gtk_widget_get_window(toplevel
);
562 if (!gdk_window_is_viewable(gdkwin
))
564 GdkWindowState state
= gdk_window_get_state(gdkwin
);
565 if (state
& GDK_WINDOW_STATE_ICONIFIED
)
568 if (refresh_counter
& 15)
574 /***************************** GUI environment ********************************************/
576 gui_environment::gui_environment()
578 keyfile
= g_key_file_new();
580 gchar
*fn
= g_build_filename(getenv("HOME"), ".calfrc", NULL
);
581 string filename
= fn
;
583 g_key_file_load_from_file(keyfile
, filename
.c_str(), (GKeyFileFlags
)(G_KEY_FILE_KEEP_COMMENTS
| G_KEY_FILE_KEEP_TRANSLATIONS
), NULL
);
585 config_db
= new calf_utils::gkeyfile_config_db(keyfile
, filename
.c_str(), "gui");
586 gui_config
.load(config_db
);
589 gui_environment::~gui_environment()
593 g_key_file_free(keyfile
);