From 19f70db3e2318a4cd50a594900331ecdb5f79f62 Mon Sep 17 00:00:00 2001 From: David Robillard Date: Sat, 7 Mar 2015 08:44:24 +0000 Subject: Improve preset support. git-svn-id: http://svn.drobilla.net/lad/trunk/jalv@5618 a436a847-0d15-0410-975c-d299462d15a1 --- NEWS | 3 +- src/jalv.c | 1 + src/jalv_gtk.c | 212 ++++++++++++++++++++++++++++++++++++++++++++-------- src/jalv_internal.h | 5 ++ src/state.c | 30 +++++--- wscript | 2 +- 6 files changed, 209 insertions(+), 44 deletions(-) diff --git a/NEWS b/NEWS index 84505da..2f3d185 100644 --- a/NEWS +++ b/NEWS @@ -1,5 +1,6 @@ jalv (1.4.7) unstable; + * Improve preset support * Set Jack port order metadata * Add command prompt to console version for changing controls * Exit on Jack shutdown (Patch from Robin Gareus) @@ -8,7 +9,7 @@ jalv (1.4.7) unstable; * Fix semaphore correctness issues * Use moc-qt4 if present for systems with multiple Qt versions - -- David Robillard Thu, 05 Mar 2015 23:18:11 -0500 + -- David Robillard Sat, 07 Mar 2015 03:44:18 -0500 jalv (1.4.6) stable; diff --git a/src/jalv.c b/src/jalv.c index 86e58af..5eafc5f 100644 --- a/src/jalv.c +++ b/src/jalv.c @@ -1043,6 +1043,7 @@ main(int argc, char** argv) jalv.nodes.midi_MidiEvent = lilv_new_uri(world, LV2_MIDI__MidiEvent); jalv.nodes.pg_group = lilv_new_uri(world, LV2_PORT_GROUPS__group); jalv.nodes.pset_Preset = lilv_new_uri(world, LV2_PRESETS__Preset); + jalv.nodes.pset_bank = lilv_new_uri(world, LV2_PRESETS__bank); jalv.nodes.rdfs_label = lilv_new_uri(world, LILV_NS_RDFS "label"); jalv.nodes.rsz_minimumSize = lilv_new_uri(world, LV2_RESIZE_PORT__minimumSize); jalv.nodes.work_interface = lilv_new_uri(world, LV2_WORKER__interface); diff --git a/src/jalv_gtk.c b/src/jalv_gtk.c index 3e56087..d846265 100644 --- a/src/jalv_gtk.c +++ b/src/jalv_gtk.c @@ -23,6 +23,8 @@ #include "jalv_internal.h" +static GtkCheckMenuItem* active_preset_item = NULL; + typedef struct { Jalv* jalv; LilvNode* property; @@ -180,8 +182,16 @@ symbolify(const char* in) static void on_preset_activate(GtkWidget* widget, gpointer data) { - PresetRecord* record = (PresetRecord*)data; - jalv_apply_preset(record->jalv, record->preset); + if (GTK_CHECK_MENU_ITEM(widget) != active_preset_item) { + PresetRecord* record = (PresetRecord*)data; + jalv_apply_preset(record->jalv, record->preset); + if (active_preset_item) { + gtk_check_menu_item_set_active(active_preset_item, FALSE); + } + + active_preset_item = GTK_CHECK_MENU_ITEM(widget); + gtk_check_menu_item_set_active(active_preset_item, TRUE); + } } static void @@ -192,15 +202,91 @@ on_preset_destroy(gpointer data, GClosure* closure) free(record); } +typedef struct { + GtkMenuItem* item; + char* label; + GtkMenu* menu; + GSequence* banks; +} PresetMenu; + +static PresetMenu* +pset_menu_new(const char* label) +{ + PresetMenu* menu = (PresetMenu*)malloc(sizeof(PresetMenu)); + menu->label = g_strdup(label); + menu->item = GTK_MENU_ITEM(gtk_menu_item_new_with_label(menu->label)); + menu->menu = GTK_MENU(gtk_menu_new()); + menu->banks = NULL; + return menu; +} + +static void +pset_menu_free(PresetMenu* menu) +{ + if (menu->banks) { + for (GSequenceIter* i = g_sequence_get_begin_iter(menu->banks); + !g_sequence_iter_is_end(i); + i = g_sequence_iter_next(i)) { + PresetMenu* bank_menu = (PresetMenu*)g_sequence_get(i); + pset_menu_free(bank_menu); + } + g_sequence_free(menu->banks); + } + + free(menu->label); + free(menu); +} + +static gint +menu_cmp(gconstpointer a, gconstpointer b, gpointer data) +{ + return strcmp(((PresetMenu*)a)->label, ((PresetMenu*)b)->label); +} + +static PresetMenu* +get_bank_menu(Jalv* jalv, PresetMenu* menu, const LilvNode* bank) +{ + LilvNode* label = lilv_world_get( + jalv->world, bank, jalv->nodes.rdfs_label, NULL); + + const char* uri = lilv_node_as_string(bank); + const char* str = label ? lilv_node_as_string(label) : uri; + PresetMenu key = { NULL, (char*)str, NULL, NULL }; + GSequenceIter* i = g_sequence_lookup(menu->banks, &key, menu_cmp, NULL); + if (!i) { + PresetMenu* bank_menu = pset_menu_new(str); + gtk_menu_item_set_submenu(bank_menu->item, GTK_WIDGET(bank_menu->menu)); + g_sequence_insert_sorted(menu->banks, bank_menu, menu_cmp, NULL); + return bank_menu; + } + return g_sequence_get(i); +} + static int add_preset_to_menu(Jalv* jalv, const LilvNode* node, const LilvNode* title, void* data) { - GtkWidget* presets_menu = GTK_WIDGET(data); - const char* label = lilv_node_as_string(title); - GtkWidget* item = gtk_menu_item_new_with_label(label); + PresetMenu* menu = (PresetMenu*)data; + const char* label = lilv_node_as_string(title); + GtkWidget* item = gtk_check_menu_item_new_with_label(label); + gtk_check_menu_item_set_draw_as_radio(GTK_CHECK_MENU_ITEM(item), TRUE); + if (jalv->preset && + lilv_node_equals(lilv_state_get_uri(jalv->preset), node)) { + gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(item), TRUE); + active_preset_item = GTK_CHECK_MENU_ITEM(item); + } + + LilvNode* bank = lilv_world_get( + jalv->world, node, jalv->nodes.pset_bank, NULL); + + if (bank) { + PresetMenu* bank_menu = get_bank_menu(jalv, menu, bank); + gtk_menu_shell_append(GTK_MENU_SHELL(bank_menu->menu), item); + } else { + gtk_menu_shell_append(GTK_MENU_SHELL(menu->menu), item); + } PresetRecord* record = (PresetRecord*)malloc(sizeof(PresetRecord)); record->jalv = jalv; @@ -211,10 +297,43 @@ add_preset_to_menu(Jalv* jalv, record, on_preset_destroy, 0); - gtk_menu_shell_append(GTK_MENU_SHELL(presets_menu), item); return 0; } +static void +finish_menu(PresetMenu* menu) +{ + for (GSequenceIter* i = g_sequence_get_begin_iter(menu->banks); + !g_sequence_iter_is_end(i); + i = g_sequence_iter_next(i)) { + PresetMenu* bank_menu = (PresetMenu*)g_sequence_get(i); + gtk_menu_shell_append(GTK_MENU_SHELL(menu->menu), + GTK_WIDGET(bank_menu->item)); + } + g_sequence_free(menu->banks); +} + +static void +rebuild_preset_menu(Jalv* jalv, GtkContainer* pset_menu) +{ + // Clear current menu + active_preset_item = NULL; + for (GList* items = g_list_nth(gtk_container_get_children(pset_menu), 3); + items; + items = items->next) { + gtk_container_remove(pset_menu, items->data); + } + + // Load presets and build new menu + PresetMenu menu = { + NULL, NULL, GTK_MENU(pset_menu), + g_sequence_new((GDestroyNotify)pset_menu_free) + }; + jalv_load_presets(jalv, add_preset_to_menu, &menu); + finish_menu(&menu); + gtk_widget_show_all(GTK_WIDGET(pset_menu)); +} + static void on_save_preset_activate(GtkWidget* widget, void* ptr) { @@ -257,28 +376,14 @@ on_save_preset_activate(GtkWidget* widget, void* ptr) jalv_save_preset(jalv, dir, (strlen(uri) ? uri : NULL), basename, file); - SerdNode sdir = serd_node_new_file_uri((const uint8_t*)dir, 0, 0, 0); - LilvNode* ldir = lilv_new_uri(jalv->world, (const char*)sdir.buf); - - // Unload all presets and any old information from this bundle - jalv_unload_presets(jalv); - printf("Unload bundle %s\n", lilv_node_as_string(ldir)); + // Reload bundle into the world + LilvNode* ldir = lilv_new_file_uri(jalv->world, NULL, dir); lilv_world_unload_bundle(jalv->world, ldir); - - // Load preset so it is now known to LilvWorld - printf("Load bundle %s\n", lilv_node_as_string(ldir)); lilv_world_load_bundle(jalv->world, ldir); - serd_node_free(&sdir); lilv_node_free(ldir); // Rebuild preset menu - GtkContainer* pset_menu = GTK_CONTAINER(gtk_widget_get_parent(widget)); - GList* items = gtk_container_get_children(pset_menu); - for (items = items->next; items; items = items->next) { - gtk_container_remove(pset_menu, items->data); - } - jalv_load_presets(jalv, add_preset_to_menu, pset_menu); - gtk_widget_show_all(GTK_WIDGET(pset_menu)); + rebuild_preset_menu(jalv, GTK_CONTAINER(gtk_widget_get_parent(widget))); g_free(dir); g_free(file); @@ -291,6 +396,40 @@ on_save_preset_activate(GtkWidget* widget, void* ptr) gtk_widget_destroy(GTK_WIDGET(dialog)); } +static void +on_delete_preset_activate(GtkWidget* widget, void* ptr) +{ + Jalv* jalv = (Jalv*)ptr; + if (!jalv->preset) { + return; + } + + GtkWidget* dialog = gtk_dialog_new_with_buttons( + "Delete Preset?", + (GtkWindow*)jalv->window, + GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT, + GTK_STOCK_CANCEL, GTK_RESPONSE_REJECT, + GTK_STOCK_OK, GTK_RESPONSE_ACCEPT, + NULL); + + char* msg = g_strdup_printf("Delete preset \"%s\" from the file system?", + lilv_state_get_label(jalv->preset)); + + GtkWidget* content = gtk_dialog_get_content_area(GTK_DIALOG(dialog)); + GtkWidget* text = gtk_label_new(msg); + gtk_box_pack_start(GTK_BOX(content), text, TRUE, TRUE, 4); + + gtk_widget_show_all(dialog); + if (gtk_dialog_run(GTK_DIALOG(dialog)) == GTK_RESPONSE_ACCEPT) { + jalv_delete_current_preset(jalv); + rebuild_preset_menu(jalv, GTK_CONTAINER(gtk_widget_get_parent(widget))); + } + + g_free(msg); + gtk_widget_destroy(text); + gtk_widget_destroy(dialog); +} + void jalv_ui_port_event(Jalv* jalv, uint32_t port_index, @@ -802,17 +941,25 @@ build_menu(Jalv* jalv, GtkWidget* window, GtkWidget* vbox) gtk_menu_shell_append(GTK_MENU_SHELL(file_menu), quit); gtk_menu_shell_append(GTK_MENU_SHELL(menu_bar), file); - GtkWidget* presets = gtk_menu_item_new_with_mnemonic("_Presets"); - GtkWidget* presets_menu = gtk_menu_new(); - GtkWidget* save_preset = gtk_menu_item_new_with_mnemonic( + GtkWidget* pset_item = gtk_menu_item_new_with_mnemonic("_Presets"); + GtkWidget* pset_menu = gtk_menu_new(); + GtkWidget* save_preset = gtk_menu_item_new_with_mnemonic( "_Save Preset..."); - gtk_menu_item_set_submenu(GTK_MENU_ITEM(presets), presets_menu); - gtk_menu_shell_append(GTK_MENU_SHELL(presets_menu), save_preset); - gtk_menu_shell_append(GTK_MENU_SHELL(presets_menu), + GtkWidget* delete_preset = gtk_menu_item_new_with_mnemonic( + "_Delete Current Preset..."); + gtk_menu_item_set_submenu(GTK_MENU_ITEM(pset_item), pset_menu); + gtk_menu_shell_append(GTK_MENU_SHELL(pset_menu), save_preset); + gtk_menu_shell_append(GTK_MENU_SHELL(pset_menu), delete_preset); + gtk_menu_shell_append(GTK_MENU_SHELL(pset_menu), gtk_separator_menu_item_new()); - gtk_menu_shell_append(GTK_MENU_SHELL(menu_bar), presets); + gtk_menu_shell_append(GTK_MENU_SHELL(menu_bar), pset_item); - jalv_load_presets(jalv, add_preset_to_menu, presets_menu); + PresetMenu menu = { + NULL, NULL, GTK_MENU(pset_menu), + g_sequence_new((GDestroyNotify)pset_menu_free) + }; + jalv_load_presets(jalv, add_preset_to_menu, &menu); + finish_menu(&menu); g_signal_connect(G_OBJECT(quit), "activate", G_CALLBACK(on_quit_activate), window); @@ -823,6 +970,9 @@ build_menu(Jalv* jalv, GtkWidget* window, GtkWidget* vbox) g_signal_connect(G_OBJECT(save_preset), "activate", G_CALLBACK(on_save_preset_activate), jalv); + g_signal_connect(G_OBJECT(delete_preset), "activate", + G_CALLBACK(on_delete_preset_activate), jalv); + gtk_box_pack_start(GTK_BOX(vbox), menu_bar, FALSE, FALSE, 0); } diff --git a/src/jalv_internal.h b/src/jalv_internal.h index 73bdabe..caf9505 100644 --- a/src/jalv_internal.h +++ b/src/jalv_internal.h @@ -138,6 +138,7 @@ typedef struct { LilvNode* midi_MidiEvent; LilvNode* pg_group; LilvNode* pset_Preset; + LilvNode* pset_bank; LilvNode* rdfs_label; LilvNode* rsz_minimumSize; LilvNode* work_interface; @@ -184,6 +185,7 @@ typedef struct { char* temp_dir; ///< Temporary plugin state directory char* save_dir; ///< Plugin save directory const LilvPlugin* plugin; ///< Plugin class (RDF data) + LilvState* preset; ///< Current preset LilvUIs* uis; ///< All plugin UIs (RDF data) const LilvUI* ui; ///< Plugin UI (RDF data) const LilvNode* ui_type; ///< Plugin UI type (unwrapped) @@ -273,6 +275,9 @@ jalv_unload_presets(Jalv* jalv); int jalv_apply_preset(Jalv* jalv, const LilvNode* preset); +int +jalv_delete_current_preset(Jalv* jalv); + int jalv_save_preset(Jalv* jalv, const char* dir, diff --git a/src/state.c b/src/state.c index dd642f1..4ed4a64 100644 --- a/src/state.c +++ b/src/state.c @@ -49,12 +49,7 @@ jalv_make_path(LV2_State_Make_Path_Handle handle, Jalv* jalv = (Jalv*)handle; // Create in save directory if saving, otherwise use temp directory - const char* dir = (jalv->save_dir) ? jalv->save_dir : jalv->temp_dir; - - char* fullpath = jalv_strjoin(dir, path); - fprintf(stderr, "MAKE PATH `%s' => `%s'\n", path, fullpath); - - return fullpath; + return jalv_strjoin(jalv->save_dir ? jalv->save_dir : jalv->temp_dir, path); } static const void* @@ -203,10 +198,9 @@ jalv_apply_state(Jalv* jalv, LilvState* state) int jalv_apply_preset(Jalv* jalv, const LilvNode* preset) { - LilvState* state = lilv_state_new_from_world( - jalv->world, &jalv->map, preset); - jalv_apply_state(jalv, state); - lilv_state_free(state); + lilv_state_free(jalv->preset); + jalv->preset = lilv_state_new_from_world(jalv->world, &jalv->map, preset); + jalv_apply_state(jalv, jalv->preset); return 0; } @@ -230,7 +224,21 @@ jalv_save_preset(Jalv* jalv, int ret = lilv_state_save( jalv->world, &jalv->map, &jalv->unmap, state, uri, dir, filename); - lilv_state_free(state); + lilv_state_free(jalv->preset); + jalv->preset = state; return ret; } + +int +jalv_delete_current_preset(Jalv* jalv) +{ + if (!jalv->preset) { + return 1; + } + + lilv_state_delete(jalv->world, jalv->preset); + lilv_state_free(jalv->preset); + jalv->preset = NULL; + return 0; +} diff --git a/wscript b/wscript index 6feb190..02d8064 100644 --- a/wscript +++ b/wscript @@ -33,7 +33,7 @@ def configure(conf): autowaf.set_c99_mode(conf) autowaf.display_header('Jalv Configuration') - autowaf.check_pkg(conf, 'lv2', atleast_version='1.8.1', uselib_store='LV2') + autowaf.check_pkg(conf, 'lv2', atleast_version='1.11.1', uselib_store='LV2') autowaf.check_pkg(conf, 'lilv-0', uselib_store='LILV', atleast_version='0.21.0', mandatory=True) autowaf.check_pkg(conf, 'serd-0', uselib_store='SERD', -- cgit v1.2.1