Fri, 10 Mar 2023 01:16:40 -0600
Add a PurpleRequestFieldString subclass
This also does an `hg cp`, though with all the renaming of the parameter names, maybe that wasn't as useful for tracking the diff.
Note, I didn't bother re-indenting some of the blocks, because they'll all eventually be moved when everything is subclassed.
Testing Done:
Compiled and opened Request Fields from the Demo protocol.
Reviewed at https://reviews.imfreedom.org/r/2324/
/* * Pidgin - Internet Messenger * Copyright (C) Pidgin Developers <devel@pidgin.im> * * Pidgin is the legal property of its developers, whose names are too numerous * to list here. Please refer to the COPYRIGHT file distributed with this * source distribution. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, see <https://www.gnu.org/licenses/>. */ #ifdef HAVE_CONFIG_H #include <config.h> #endif #include <math.h> #include <glib/gi18n-lib.h> #include <purple.h> #include <adwaita.h> #include "pidginvvprefs.h" #include "pidgincore.h" #include "pidginprefsinternal.h" struct _PidginVVPrefs { AdwPreferencesPage parent; struct { PidginPrefCombo input; PidginPrefCombo output; GtkWidget *threshold_row; GtkWidget *threshold; GtkWidget *volume; GtkWidget *test; GtkWidget *level; GtkWidget *drop; GstElement *pipeline; } voice; struct { PidginPrefCombo input; PidginPrefCombo output; GtkWidget *frame; GtkWidget *test; GstElement *pipeline; } video; }; G_DEFINE_TYPE(PidginVVPrefs, pidgin_vv_prefs, ADW_TYPE_PREFERENCES_PAGE) /* Keep in sync with voice.level's GtkLevelBar::max-value in the * pidgin/resources/Prefs.vv.ui builder file. */ #define MAX_AUDIO_LEVEL (19.0) /****************************************************************************** * Helpers *****************************************************************************/ static void populate_vv_device_menuitems(PurpleMediaElementType type, GtkListStore *store) { PurpleMediaManager *manager = NULL; GList *devices; gtk_list_store_clear(store); manager = purple_media_manager_get(); devices = purple_media_manager_enumerate_elements(manager, type); for (; devices; devices = g_list_delete_link(devices, devices)) { PurpleMediaElementInfo *info = devices->data; GtkTreeIter iter; char *name, *id; name = purple_media_element_info_get_name(info); id = purple_media_element_info_get_id(info); gtk_list_store_append(store, &iter); gtk_list_store_set(store, &iter, PIDGIN_PREF_COMBO_TEXT, name, PIDGIN_PREF_COMBO_VALUE, id, -1); g_free(name); g_free(id); g_object_unref(info); } } static GstElement * create_test_element(PurpleMediaElementType type) { PurpleMediaElementInfo *element_info; element_info = purple_media_manager_get_active_element(purple_media_manager_get(), type); g_return_val_if_fail(element_info, NULL); return purple_media_element_info_call_create(element_info, NULL, NULL, NULL); } static GstElement * create_voice_pipeline(PidginVVPrefs *prefs) { GstElement *pipeline; GstElement *src, *sink; GstElement *volume; GstElement *level; GstElement *valve; pipeline = gst_pipeline_new("voicetest"); src = create_test_element(PURPLE_MEDIA_ELEMENT_AUDIO | PURPLE_MEDIA_ELEMENT_SRC); sink = create_test_element(PURPLE_MEDIA_ELEMENT_AUDIO | PURPLE_MEDIA_ELEMENT_SINK); volume = gst_element_factory_make("volume", "volume"); level = gst_element_factory_make("level", "level"); valve = gst_element_factory_make("valve", "valve"); g_object_set(volume, "volume", gtk_scale_button_get_value(GTK_SCALE_BUTTON(prefs->voice.volume)) / 100.0, NULL); gst_bin_add_many(GST_BIN(pipeline), src, volume, level, valve, sink, NULL); gst_element_link_many(src, volume, level, valve, sink, NULL); purple_debug_info("gtkprefs", "create_voice_pipeline: setting pipeline " "state to GST_STATE_PLAYING - it may hang here on win32\n"); gst_element_set_state(GST_ELEMENT(pipeline), GST_STATE_PLAYING); purple_debug_info("gtkprefs", "create_voice_pipeline: state is set\n"); return pipeline; } static void on_volume_change_cb(GtkWidget *w, G_GNUC_UNUSED gdouble value, gpointer data) { PidginVVPrefs *prefs = PIDGIN_VV_PREFS(data); GstElement *volume; if (!prefs->voice.pipeline) { return; } volume = gst_bin_get_by_name(GST_BIN(prefs->voice.pipeline), "volume"); g_object_set(volume, "volume", gtk_scale_button_get_value(GTK_SCALE_BUTTON(w)) / 100.0, NULL); } static gdouble gst_msg_db_to_percent(GstMessage *msg, gchar *value_name) { const GValue *list; const GValue *value; gdouble value_db; gdouble percent; list = gst_structure_get_value(gst_message_get_structure(msg), value_name); G_GNUC_BEGIN_IGNORE_DEPRECATIONS value = g_value_array_get_nth(g_value_get_boxed(list), 0); G_GNUC_END_IGNORE_DEPRECATIONS value_db = g_value_get_double(value); percent = pow(10, value_db / 20); return (percent > 1.0) ? 1.0 : percent; } static gboolean gst_bus_cb(G_GNUC_UNUSED GstBus *bus, GstMessage *msg, gpointer data) { PidginVVPrefs *prefs = PIDGIN_VV_PREFS(data); if (GST_MESSAGE_TYPE(msg) == GST_MESSAGE_ELEMENT && gst_structure_has_name(gst_message_get_structure(msg), "level")) { GstElement *src = GST_ELEMENT(GST_MESSAGE_SRC(msg)); gchar *name = gst_element_get_name(src); if (purple_strequal(name, "level")) { gdouble percent; gdouble threshold; gboolean drop; GstElement *valve; percent = gst_msg_db_to_percent(msg, "rms"); gtk_level_bar_set_value(GTK_LEVEL_BAR(prefs->voice.level), percent * MAX_AUDIO_LEVEL); percent = gst_msg_db_to_percent(msg, "decay"); threshold = gtk_range_get_value(GTK_RANGE( prefs->voice.threshold)) / 100.0; drop = percent < threshold; valve = gst_bin_get_by_name(GST_BIN(GST_ELEMENT_PARENT(src)), "valve"); g_object_set(valve, "drop", drop, NULL); gtk_label_set_text(GTK_LABEL(prefs->voice.drop), drop ? _("DROP") : ""); } g_free(name); } return TRUE; } static void voice_test_destroy_cb(G_GNUC_UNUSED GtkWidget *w, gpointer data) { PidginVVPrefs *prefs = PIDGIN_VV_PREFS(data); if (!prefs->voice.pipeline) { return; } gst_element_set_state(prefs->voice.pipeline, GST_STATE_NULL); g_clear_pointer(&prefs->voice.pipeline, gst_object_unref); } static void enable_voice_test(PidginVVPrefs *prefs) { GstBus *bus; prefs->voice.pipeline = create_voice_pipeline(prefs); bus = gst_pipeline_get_bus(GST_PIPELINE(prefs->voice.pipeline)); gst_bus_add_signal_watch(bus); g_signal_connect(bus, "message", G_CALLBACK(gst_bus_cb), prefs); gst_object_unref(bus); } static void toggle_voice_test_cb(GtkToggleButton *test, gpointer data) { PidginVVPrefs *prefs = PIDGIN_VV_PREFS(data); if (gtk_toggle_button_get_active(test)) { enable_voice_test(prefs); g_signal_connect(prefs->voice.volume, "value-changed", G_CALLBACK(on_volume_change_cb), prefs); g_signal_connect(test, "destroy", G_CALLBACK(voice_test_destroy_cb), prefs); } else { gtk_level_bar_set_value(GTK_LEVEL_BAR(prefs->voice.level), 0.0); gtk_label_set_text(GTK_LABEL(prefs->voice.drop), ""); g_object_disconnect(prefs->voice.volume, "any-signal::value-changed", G_CALLBACK(on_volume_change_cb), prefs, NULL); g_object_disconnect(test, "any-signal::destroy", G_CALLBACK(voice_test_destroy_cb), prefs, NULL); voice_test_destroy_cb(NULL, prefs); } } static void volume_changed_cb(G_GNUC_UNUSED GtkScaleButton *button, gdouble value, G_GNUC_UNUSED gpointer data) { purple_prefs_set_int("/purple/media/audio/volume/input", value * 100); } static void threshold_value_changed_cb(GtkScale *scale, gpointer data) { PidginVVPrefs *prefs = data; int value; char *tmp; value = (int)gtk_range_get_value(GTK_RANGE(scale)); tmp = g_strdup_printf(_("Silence threshold: %d%%"), value); adw_preferences_row_set_title(ADW_PREFERENCES_ROW(prefs->voice.threshold_row), tmp); g_free(tmp); gtk_level_bar_add_offset_value(GTK_LEVEL_BAR(prefs->voice.level), GTK_LEVEL_BAR_OFFSET_LOW, value / 100.0 * MAX_AUDIO_LEVEL); purple_prefs_set_int("/purple/media/audio/silence_threshold", value); } static void bind_voice_test(PidginVVPrefs *prefs) { char *tmp; gtk_scale_button_set_value(GTK_SCALE_BUTTON(prefs->voice.volume), purple_prefs_get_int("/purple/media/audio/volume/input") / 100.0); tmp = g_strdup_printf(_("Silence threshold: %d%%"), purple_prefs_get_int("/purple/media/audio/silence_threshold")); adw_preferences_row_set_title(ADW_PREFERENCES_ROW(prefs->voice.threshold_row), tmp); g_free(tmp); /* Move the default high levels to the end (low is set by * threshold_value_changed_cb when set below.) */ gtk_level_bar_add_offset_value(GTK_LEVEL_BAR(prefs->voice.level), GTK_LEVEL_BAR_OFFSET_HIGH, MAX_AUDIO_LEVEL); gtk_level_bar_add_offset_value(GTK_LEVEL_BAR(prefs->voice.level), GTK_LEVEL_BAR_OFFSET_FULL, MAX_AUDIO_LEVEL); gtk_range_set_value(GTK_RANGE(prefs->voice.threshold), purple_prefs_get_int("/purple/media/audio/silence_threshold")); } static GstElement * create_video_pipeline(void) { GstElement *pipeline; GstElement *src, *sink; GstElement *videoconvert; GstElement *videoscale; pipeline = gst_pipeline_new("videotest"); src = create_test_element(PURPLE_MEDIA_ELEMENT_VIDEO | PURPLE_MEDIA_ELEMENT_SRC); sink = create_test_element(PURPLE_MEDIA_ELEMENT_VIDEO | PURPLE_MEDIA_ELEMENT_SINK); videoconvert = gst_element_factory_make("videoconvert", NULL); videoscale = gst_element_factory_make("videoscale", NULL); g_object_set_data(G_OBJECT(pipeline), "sink", sink); gst_bin_add_many(GST_BIN(pipeline), src, videoconvert, videoscale, sink, NULL); gst_element_link_many(src, videoconvert, videoscale, sink, NULL); return pipeline; } static void video_test_destroy_cb(G_GNUC_UNUSED GtkWidget *w, gpointer data) { PidginVVPrefs *prefs = PIDGIN_VV_PREFS(data); if (!prefs->video.pipeline) { return; } gst_element_set_state(prefs->video.pipeline, GST_STATE_NULL); g_clear_pointer(&prefs->video.pipeline, gst_object_unref); } static void enable_video_test(PidginVVPrefs *prefs) { GtkWidget *video = NULL; GstElement *sink = NULL; prefs->video.pipeline = create_video_pipeline(); sink = g_object_get_data(G_OBJECT(prefs->video.pipeline), "sink"); g_object_get(sink, "widget", &video, NULL); gtk_widget_show(video); gtk_widget_set_size_request(prefs->video.frame, 400, 300); gtk_aspect_frame_set_child(GTK_ASPECT_FRAME(prefs->video.frame), video); gst_element_set_state(GST_ELEMENT(prefs->video.pipeline), GST_STATE_PLAYING); g_object_unref(video); } static void toggle_video_test_cb(GtkToggleButton *test, gpointer data) { PidginVVPrefs *prefs = PIDGIN_VV_PREFS(data); if (gtk_toggle_button_get_active(test)) { enable_video_test(prefs); g_signal_connect(test, "destroy", G_CALLBACK(video_test_destroy_cb), prefs); } else { g_object_disconnect(test, "any-signal::destroy", G_CALLBACK(video_test_destroy_cb), prefs, NULL); video_test_destroy_cb(NULL, prefs); } } static void vv_device_changed_cb(const gchar *name, G_GNUC_UNUSED PurplePrefType type, gconstpointer value, gpointer data) { PidginVVPrefs *prefs = PIDGIN_VV_PREFS(data); PurpleMediaManager *manager; PurpleMediaElementInfo *info; manager = purple_media_manager_get(); info = purple_media_manager_get_element_info(manager, value); purple_media_manager_set_active_element(manager, info); /* Refresh test viewers */ if (strstr(name, "audio") && prefs->voice.pipeline) { voice_test_destroy_cb(NULL, prefs); enable_voice_test(prefs); } else if (strstr(name, "video") && prefs->video.pipeline) { video_test_destroy_cb(NULL, prefs); enable_video_test(prefs); } } static const char * purple_media_type_to_preference_key(PurpleMediaElementType type) { if (type & PURPLE_MEDIA_ELEMENT_AUDIO) { if (type & PURPLE_MEDIA_ELEMENT_SRC) { return PIDGIN_PREFS_ROOT "/vvconfig/audio/src/device"; } else if (type & PURPLE_MEDIA_ELEMENT_SINK) { return PIDGIN_PREFS_ROOT "/vvconfig/audio/sink/device"; } } else if (type & PURPLE_MEDIA_ELEMENT_VIDEO) { if (type & PURPLE_MEDIA_ELEMENT_SRC) { return PIDGIN_PREFS_ROOT "/vvconfig/video/src/device"; } else if (type & PURPLE_MEDIA_ELEMENT_SINK) { return PIDGIN_PREFS_ROOT "/vvconfig/video/sink/device"; } } return NULL; } static void bind_vv_dropdown(PidginPrefCombo *combo, PurpleMediaElementType element_type) { const gchar *preference_key; GtkTreeModel *model; preference_key = purple_media_type_to_preference_key(element_type); model = gtk_combo_box_get_model(GTK_COMBO_BOX(combo->combo)); populate_vv_device_menuitems(element_type, GTK_LIST_STORE(model)); combo->type = PURPLE_PREF_STRING; combo->key = preference_key; pidgin_prefs_bind_dropdown(combo); } static void bind_vv_frame(PidginVVPrefs *prefs, PidginPrefCombo *combo, PurpleMediaElementType type) { bind_vv_dropdown(combo, type); purple_prefs_connect_callback(combo->combo, purple_media_type_to_preference_key(type), vv_device_changed_cb, prefs); g_signal_connect_swapped(combo->combo, "destroy", G_CALLBACK(purple_prefs_disconnect_by_handle), combo->combo); g_object_set_data(G_OBJECT(combo->combo), "vv_media_type", (gpointer)type); g_object_set_data(G_OBJECT(combo->combo), "vv_combo", combo); } static void device_list_changed_cb(G_GNUC_UNUSED PurpleMediaManager *manager, GtkWidget *widget) { PidginPrefCombo *combo; PurpleMediaElementType media_type; const gchar *preference_key; guint signal_id; GtkTreeModel *model; combo = g_object_get_data(G_OBJECT(widget), "vv_combo"); media_type = (PurpleMediaElementType)GPOINTER_TO_INT(g_object_get_data( G_OBJECT(widget), "vv_media_type")); preference_key = purple_media_type_to_preference_key(media_type); /* Block signals so pref doesn't get re-saved while changing UI. */ signal_id = g_signal_lookup("changed", GTK_TYPE_COMBO_BOX); g_signal_handlers_block_matched(combo->combo, G_SIGNAL_MATCH_ID, signal_id, 0, NULL, NULL, NULL); model = gtk_combo_box_get_model(GTK_COMBO_BOX(combo->combo)); populate_vv_device_menuitems(media_type, GTK_LIST_STORE(model)); gtk_combo_box_set_active_id(GTK_COMBO_BOX(combo->combo), purple_prefs_get_string(preference_key)); g_signal_handlers_unblock_matched(combo->combo, G_SIGNAL_MATCH_ID, signal_id, 0, NULL, NULL, NULL); } /****************************************************************************** * GObject Implementation *****************************************************************************/ static void pidgin_vv_prefs_class_init(PidginVVPrefsClass *klass) { GtkWidgetClass *widget_class = GTK_WIDGET_CLASS(klass); gtk_widget_class_set_template_from_resource( widget_class, "/im/pidgin/Pidgin3/Prefs/vv.ui" ); gtk_widget_class_bind_template_child(widget_class, PidginVVPrefs, voice.input.combo); gtk_widget_class_bind_template_child(widget_class, PidginVVPrefs, voice.output.combo); gtk_widget_class_bind_template_child(widget_class, PidginVVPrefs, voice.volume); gtk_widget_class_bind_template_child(widget_class, PidginVVPrefs, voice.threshold_row); gtk_widget_class_bind_template_child(widget_class, PidginVVPrefs, voice.threshold); gtk_widget_class_bind_template_child(widget_class, PidginVVPrefs, voice.level); gtk_widget_class_bind_template_child(widget_class, PidginVVPrefs, voice.drop); gtk_widget_class_bind_template_child(widget_class, PidginVVPrefs, voice.test); gtk_widget_class_bind_template_callback(widget_class, volume_changed_cb); gtk_widget_class_bind_template_callback(widget_class, threshold_value_changed_cb); gtk_widget_class_bind_template_callback(widget_class, toggle_voice_test_cb); gtk_widget_class_bind_template_child(widget_class, PidginVVPrefs, video.input.combo); gtk_widget_class_bind_template_child(widget_class, PidginVVPrefs, video.output.combo); gtk_widget_class_bind_template_child(widget_class, PidginVVPrefs, video.frame); gtk_widget_class_bind_template_child(widget_class, PidginVVPrefs, video.test); gtk_widget_class_bind_template_callback(widget_class, toggle_video_test_cb); } static void pidgin_vv_prefs_init(PidginVVPrefs *prefs) { PurpleMediaManager *manager = NULL; gtk_widget_init_template(GTK_WIDGET(prefs)); manager = purple_media_manager_get(); bind_vv_frame(prefs, &prefs->voice.input, PURPLE_MEDIA_ELEMENT_AUDIO | PURPLE_MEDIA_ELEMENT_SRC); g_signal_connect_object(manager, "elements-changed::audiosrc", G_CALLBACK(device_list_changed_cb), prefs->voice.input.combo, 0); bind_vv_frame(prefs, &prefs->voice.output, PURPLE_MEDIA_ELEMENT_AUDIO | PURPLE_MEDIA_ELEMENT_SINK); g_signal_connect_object(manager, "elements-changed::audiosink", G_CALLBACK(device_list_changed_cb), prefs->voice.output.combo, 0); bind_voice_test(prefs); bind_vv_frame(prefs, &prefs->video.input, PURPLE_MEDIA_ELEMENT_VIDEO | PURPLE_MEDIA_ELEMENT_SRC); g_signal_connect_object(manager, "elements-changed::videosrc", G_CALLBACK(device_list_changed_cb), prefs->video.input.combo, 0); bind_vv_frame(prefs, &prefs->video.output, PURPLE_MEDIA_ELEMENT_VIDEO | PURPLE_MEDIA_ELEMENT_SINK); g_signal_connect_object(manager, "elements-changed::videosink", G_CALLBACK(device_list_changed_cb), prefs->video.output.combo, 0); } /****************************************************************************** * API *****************************************************************************/ GtkWidget * pidgin_vv_prefs_new(void) { return GTK_WIDGET(g_object_new(PIDGIN_TYPE_VV_PREFS, NULL)); } void pidgin_vv_prefs_disable_test_pipelines(PidginVVPrefs *prefs) { g_return_if_fail(PIDGIN_IS_VV_PREFS(prefs)); gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(prefs->voice.test), FALSE); gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(prefs->video.test), FALSE); }