From d9aab366096a55d6f7a8378da7ad00d455e53af1 Mon Sep 17 00:00:00 2001 From: Benjamin Berg Date: Tue, 22 Jan 2019 13:34:22 +0100 Subject: [PATCH 01/13] display: Compare configurations without monitor offset When comparing configurations, the monitor positions are compared directly. This comparison will not work properly if one of the configurations has an offset. This results in the "Apply" button to show up incorrectly after moving the top/left monitor position. --- panels/display/cc-display-config-dbus.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/panels/display/cc-display-config-dbus.c b/panels/display/cc-display-config-dbus.c index b74160fdc1..efb91b346b 100644 --- a/panels/display/cc-display-config-dbus.c +++ b/panels/display/cc-display-config-dbus.c @@ -1049,6 +1049,9 @@ cc_display_config_dbus_equal (CcDisplayConfig *pself, CcDisplayConfigDBus *other = CC_DISPLAY_CONFIG_DBUS (pother); GList *l; + cc_display_config_dbus_ensure_non_offset_coords (self); + cc_display_config_dbus_ensure_non_offset_coords (other); + for (l = self->monitors; l != NULL; l = l->next) { CcDisplayMonitorDBus *m1 = l->data; -- GitLab From 41717253c867530991fcd1acedbf4e7db4953cc6 Mon Sep 17 00:00:00 2001 From: Benjamin Berg Date: Sun, 20 Jan 2019 20:45:50 +0100 Subject: [PATCH 02/13] display: Fix leak in night light dialog --- panels/display/cc-night-light-dialog.c | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/panels/display/cc-night-light-dialog.c b/panels/display/cc-night-light-dialog.c index 8ab9d4ca0b..42c1353d1c 100644 --- a/panels/display/cc-night-light-dialog.c +++ b/panels/display/cc-night-light-dialog.c @@ -638,14 +638,15 @@ cc_night_light_dialog_init (CcNightLightDialog *self) { g_autoptr(GtkCssProvider) provider = NULL; g_autoptr(GError) error = NULL; - g_autofree gchar *text = NULL; + g_autofree gchar *text_low = NULL; + g_autofree gchar *text_high = NULL; gtk_widget_init_template (GTK_WIDGET (self)); - text = g_strdup_printf ("%s", "More Warm"); + text_low = g_strdup_printf ("%s", "More Warm"); gtk_scale_add_mark (GTK_SCALE (self->scale_color_temperature), 3000, GTK_POS_BOTTOM, - text); + text_low); gtk_scale_add_mark (GTK_SCALE (self->scale_color_temperature), 4000, GTK_POS_BOTTOM, @@ -655,10 +656,10 @@ cc_night_light_dialog_init (CcNightLightDialog *self) 5000, GTK_POS_BOTTOM, NULL); - text = g_strdup_printf ("%s", "Less Warm"); + text_high = g_strdup_printf ("%s", "Less Warm"); gtk_scale_add_mark (GTK_SCALE (self->scale_color_temperature), 6000, GTK_POS_BOTTOM, - text); + text_high); self->cancellable = g_cancellable_new (); self->settings_display = g_settings_new (DISPLAY_SCHEMA); -- GitLab From 713e209783a14b31aaaeefeec87b5b26a0543f03 Mon Sep 17 00:00:00 2001 From: Benjamin Berg Date: Sun, 20 Jan 2019 21:42:04 +0100 Subject: [PATCH 03/13] display: Add missing finalize handler for CcDisplayMonitor This caused the UI related strings to be leaked. --- panels/display/cc-display-config.c | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/panels/display/cc-display-config.c b/panels/display/cc-display-config.c index e2b6d991ce..626f3f5f45 100644 --- a/panels/display/cc-display-config.c +++ b/panels/display/cc-display-config.c @@ -150,9 +150,25 @@ cc_display_monitor_init (CcDisplayMonitor *self) priv->is_usable = TRUE; } +static void +cc_display_monitor_finalize (GObject *object) +{ + CcDisplayMonitor *self = CC_DISPLAY_MONITOR (object); + CcDisplayMonitorPrivate *priv = CC_DISPLAY_MONITOR_GET_PRIVATE (self); + + g_clear_pointer (&priv->ui_name, g_free); + g_clear_pointer (&priv->ui_number_name, g_free); + + G_OBJECT_CLASS (cc_display_monitor_parent_class)->finalize (object); +} + static void cc_display_monitor_class_init (CcDisplayMonitorClass *klass) { + GObjectClass *gobject_class = G_OBJECT_CLASS (klass); + + gobject_class->finalize = cc_display_monitor_finalize; + g_signal_new ("rotation", CC_TYPE_DISPLAY_MONITOR, G_SIGNAL_RUN_LAST, -- GitLab From e520ab8952684e18a850e035fd2b18e3e04f8437 Mon Sep 17 00:00:00 2001 From: Benjamin Berg Date: Sun, 20 Jan 2019 21:43:04 +0100 Subject: [PATCH 04/13] display: Chain up CcDisplayArrangement finalize handler --- panels/display/cc-display-arrangement.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/panels/display/cc-display-arrangement.c b/panels/display/cc-display-arrangement.c index 630c72e8d1..dc30c72054 100644 --- a/panels/display/cc-display-arrangement.c +++ b/panels/display/cc-display-arrangement.c @@ -857,6 +857,8 @@ cc_display_arrangement_finalize (GObject *object) CcDisplayArrangement *self = CC_DISPLAY_ARRANGEMENT (object); g_clear_object (&self->config); + + G_OBJECT_CLASS (cc_display_arrangement_parent_class)->finalize (object); } static void -- GitLab From 5b53f026c85ef8c656b17ef313eb0398c5e68776 Mon Sep 17 00:00:00 2001 From: Benjamin Berg Date: Sun, 20 Jan 2019 21:43:31 +0100 Subject: [PATCH 05/13] display: Chain up CcDisplayConfig finalize handler --- panels/display/cc-display-config.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/panels/display/cc-display-config.c b/panels/display/cc-display-config.c index 626f3f5f45..6670edb004 100644 --- a/panels/display/cc-display-config.c +++ b/panels/display/cc-display-config.c @@ -452,6 +452,8 @@ cc_display_config_finalize (GObject *object) CcDisplayConfigPrivate *priv = CC_DISPLAY_CONFIG_GET_PRIVATE (self); g_list_free (priv->ui_sorted_monitors); + + G_OBJECT_CLASS (cc_display_config_parent_class)->finalize (object); } static void -- GitLab From be3fbd8beff2149ef424e179826965681693648b Mon Sep 17 00:00:00 2001 From: Benjamin Berg Date: Sat, 19 Jan 2019 23:33:43 +0100 Subject: [PATCH 06/13] display: Redraw arrangement widget after monitor selection change --- panels/display/cc-display-arrangement.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/panels/display/cc-display-arrangement.c b/panels/display/cc-display-arrangement.c index dc30c72054..8c8c2691dd 100644 --- a/panels/display/cc-display-arrangement.c +++ b/panels/display/cc-display-arrangement.c @@ -930,6 +930,8 @@ cc_display_arrangement_set_selected_output (CcDisplayArrangement *self, /* XXX: Could check that it actually belongs to the right config object. */ self->selected_output = output; + gtk_widget_queue_draw (GTK_WIDGET (self)); + g_object_notify_by_pspec (G_OBJECT (self), props[PROP_SELECTED_OUTPUT]); } -- GitLab From 4360288c7466e3f24adb8c2b52379480294173a3 Mon Sep 17 00:00:00 2001 From: Benjamin Berg Date: Fri, 18 Jan 2019 11:49:21 +0100 Subject: [PATCH 07/13] display: Move setter for cloning mode into CcDisplayConfig It will be used from multiple files in the future. --- panels/display/cc-display-config.c | 15 +++++++++++++++ panels/display/cc-display-config.h | 4 ++++ panels/display/cc-display-panel.c | 20 +++----------------- 3 files changed, 22 insertions(+), 17 deletions(-) diff --git a/panels/display/cc-display-config.c b/panels/display/cc-display-config.c index 6670edb004..cf45ff33a7 100644 --- a/panels/display/cc-display-config.c +++ b/panels/display/cc-display-config.c @@ -509,6 +509,21 @@ cc_display_config_is_applicable (CcDisplayConfig *self) return CC_DISPLAY_CONFIG_GET_CLASS (self)->is_applicable (self); } +void +cc_display_config_set_mode_on_all_outputs (CcDisplayConfig *config, + CcDisplayMode *mode) +{ + GList *outputs, *l; + + outputs = cc_display_config_get_monitors (config); + for (l = outputs; l; l = l->next) + { + CcDisplayMonitor *output = l->data; + cc_display_monitor_set_mode (output, mode); + cc_display_monitor_set_position (output, 0, 0); + } +} + gboolean cc_display_config_equal (CcDisplayConfig *self, CcDisplayConfig *other) diff --git a/panels/display/cc-display-config.h b/panels/display/cc-display-config.h index e744227054..d4dcc5998b 100644 --- a/panels/display/cc-display-config.h +++ b/panels/display/cc-display-config.h @@ -169,6 +169,10 @@ gboolean cc_display_config_is_cloning (CcDisplayConfig void cc_display_config_set_cloning (CcDisplayConfig *config, gboolean clone); GList* cc_display_config_get_cloning_modes (CcDisplayConfig *config); + +void cc_display_config_set_mode_on_all_outputs (CcDisplayConfig *config, + CcDisplayMode *mode); + gboolean cc_display_config_is_layout_logical (CcDisplayConfig *self); const char* cc_display_monitor_get_display_name (CcDisplayMonitor *monitor); diff --git a/panels/display/cc-display-panel.c b/panels/display/cc-display-panel.c index 39002decf5..d3d9d982ff 100644 --- a/panels/display/cc-display-panel.c +++ b/panels/display/cc-display-panel.c @@ -1478,27 +1478,13 @@ make_two_single_ui (CcDisplayPanel *panel) return vbox; } -static void -set_mode_on_all_outputs (CcDisplayConfig *config, - CcDisplayMode *mode) -{ - GList *outputs, *l; - outputs = cc_display_config_get_monitors (config); - for (l = outputs; l; l = l->next) - { - CcDisplayMonitor *output = l->data; - cc_display_monitor_set_mode (output, mode); - cc_display_monitor_set_position (output, 0, 0); - } -} - static void mirror_resolution_row_activated (CcDisplayPanel *panel, GtkListBoxRow *row) { CcDisplayMode *mode = g_object_get_data (G_OBJECT (row), "mode"); - set_mode_on_all_outputs (panel->current_config, mode); + cc_display_config_set_mode_on_all_outputs (panel->current_config, mode); update_apply_button (panel); } @@ -1596,8 +1582,8 @@ make_two_mirror_ui (CcDisplayPanel *panel) GList *modes; cc_display_config_set_cloning (panel->current_config, TRUE); modes = g_object_get_data (G_OBJECT (panel->current_config), "mirror-res-list"); - set_mode_on_all_outputs (panel->current_config, - CC_DISPLAY_MODE (g_list_nth_data (modes, 0))); + cc_display_config_set_mode_on_all_outputs (panel->current_config, + CC_DISPLAY_MODE (g_list_nth_data (modes, 0))); } panel->rows_size_group = gtk_size_group_new (GTK_SIZE_GROUP_BOTH); -- GitLab From 9025f79b5fe5b59b89bb9f2d7c89d5f4c5da909c Mon Sep 17 00:00:00 2001 From: Benjamin Berg Date: Sat, 19 Jan 2019 23:32:00 +0100 Subject: [PATCH 08/13] display: Add helper that returns the monitor name as new string --- panels/display/cc-display-config.c | 6 ++++++ panels/display/cc-display-config.h | 1 + 2 files changed, 7 insertions(+) diff --git a/panels/display/cc-display-config.c b/panels/display/cc-display-config.c index cf45ff33a7..3bed67a493 100644 --- a/panels/display/cc-display-config.c +++ b/panels/display/cc-display-config.c @@ -385,6 +385,12 @@ cc_display_monitor_get_ui_number_name (CcDisplayMonitor *self) return CC_DISPLAY_MONITOR_GET_PRIVATE (self)->ui_number_name; } +char * +cc_display_monitor_dup_ui_number_name (CcDisplayMonitor *self) +{ + return g_strdup (CC_DISPLAY_MONITOR_GET_PRIVATE (self)->ui_number_name); +} + static void cc_display_monitor_set_ui_info (CcDisplayMonitor *self, gint ui_number, gchar *ui_name) { diff --git a/panels/display/cc-display-config.h b/panels/display/cc-display-config.h index d4dcc5998b..faeae8e347 100644 --- a/panels/display/cc-display-config.h +++ b/panels/display/cc-display-config.h @@ -224,6 +224,7 @@ void cc_display_monitor_set_usable (CcDisplayMonitor * int cc_display_monitor_get_ui_number (CcDisplayMonitor *monitor); const char* cc_display_monitor_get_ui_name (CcDisplayMonitor *monitor); const char* cc_display_monitor_get_ui_number_name (CcDisplayMonitor *monitor); +char* cc_display_monitor_dup_ui_number_name (CcDisplayMonitor *monitor); void cc_display_mode_get_resolution (CcDisplayMode *mode, int *width, -- GitLab From afe8661704542db012be71beeaeac5345a0c5485 Mon Sep 17 00:00:00 2001 From: Benjamin Berg Date: Sat, 19 Jan 2019 23:33:18 +0100 Subject: [PATCH 09/13] display: Allow config to be modified in arrangement widget --- panels/display/cc-display-arrangement.c | 94 ++++++++++++++++--------- panels/display/cc-display-arrangement.h | 4 ++ 2 files changed, 63 insertions(+), 35 deletions(-) diff --git a/panels/display/cc-display-arrangement.c b/panels/display/cc-display-arrangement.c index 8c8c2691dd..adbbcbc281 100644 --- a/panels/display/cc-display-arrangement.c +++ b/panels/display/cc-display-arrangement.c @@ -524,36 +524,6 @@ on_output_changed_cb (CcDisplayArrangement *self, gtk_widget_queue_draw (GTK_WIDGET (self)); } -static void -cc_display_arrangement_set_config (CcDisplayArrangement *self, - CcDisplayConfig *config) -{ - const gchar *signals[] = { "rotation", "mode", "primary", "active", "scale", "position-changed", "is-usable" }; - GList *outputs, *l; - guint i; - - g_assert (self->config == NULL); - - self->config = g_object_ref (config); - - /* Listen to all the signals */ - if (self->config) - { - outputs = cc_display_config_get_monitors (self->config); - for (l = outputs; l; l = l->next) - { - CcDisplayMonitor *output = l->data; - - for (i = 0; i < G_N_ELEMENTS (signals); ++i) - g_signal_connect_object (output, signals[i], G_CALLBACK (on_output_changed_cb), self, G_CONNECT_SWAPPED); - } - } - - cc_display_arrangement_set_selected_output (self, NULL); - - g_object_notify_by_pspec (G_OBJECT (self), props[PROP_CONFIG]); -} - static gboolean cc_display_arrangement_draw (GtkWidget *widget, cairo_t *cr) @@ -563,7 +533,8 @@ cc_display_arrangement_draw (GtkWidget *widget, g_autoptr(GList) outputs = NULL; GList *l; - g_return_val_if_fail (self->config, FALSE); + if (!self->config) + return FALSE; cc_display_arrangement_update_matrices (self); @@ -694,6 +665,9 @@ cc_display_arrangement_button_press_event (GtkWidget *widget, gdouble event_x, event_y; gint mon_x, mon_y; + if (!self->config) + return FALSE; + /* Only handle normal presses of the left mouse button. */ if (event->button != 1 || event->type != GDK_BUTTON_PRESS) return FALSE; @@ -729,6 +703,9 @@ cc_display_arrangement_button_release_event (GtkWidget *widget, CcDisplayArrangement *self = CC_DISPLAY_ARRANGEMENT (widget); CcDisplayMonitor *output; + if (!self->config) + return FALSE; + /* Only handle left mouse button */ if (event->button != 1) return FALSE; @@ -758,7 +735,8 @@ cc_display_arrangement_motion_notify_event (GtkWidget *widget, gint mon_x, mon_y; SnapData snap_data; - g_return_val_if_fail (self->config, FALSE); + if (!self->config) + return FALSE; if (cc_display_config_count_useful_monitors (self->config) <= 1) return FALSE; @@ -879,7 +857,7 @@ cc_display_arrangement_class_init (CcDisplayArrangementClass *klass) props[PROP_CONFIG] = g_param_spec_object ("config", "Display Config", "The display configuration to work with", CC_TYPE_DISPLAY_CONFIG, - G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS); + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); props[PROP_SELECTED_OUTPUT] = g_param_spec_object ("selected-output", "Selected Output", "The output that is currently selected on the configuration", @@ -913,11 +891,57 @@ cc_display_arrangement_new (CcDisplayConfig *config) return g_object_new (CC_TYPE_DISPLAY_ARRANGEMENT, "config", config, NULL); } -CcDisplayMonitor* -cc_display_arrangement_get_selected_output (CcDisplayArrangement *self) +CcDisplayConfig* +cc_display_arrangement_get_config (CcDisplayArrangement *self) +{ + return self->config; +} + +void +cc_display_arrangement_set_config (CcDisplayArrangement *self, + CcDisplayConfig *config) { + const gchar *signals[] = { "rotation", "mode", "primary", "active", "scale", "position-changed", "is-usable" }; + GList *outputs, *l; + guint i; + + if (self->config) + { + outputs = cc_display_config_get_monitors (self->config); + for (l = outputs; l; l = l->next) + { + CcDisplayMonitor *output = l->data; + + g_signal_handlers_disconnect_by_data (output, self); + } + } + g_clear_object (&self->config); + self->drag_active = FALSE; + /* Listen to all the signals */ + if (config) + { + self->config = g_object_ref (config); + + outputs = cc_display_config_get_monitors (self->config); + for (l = outputs; l; l = l->next) + { + CcDisplayMonitor *output = l->data; + + for (i = 0; i < G_N_ELEMENTS (signals); ++i) + g_signal_connect_object (output, signals[i], G_CALLBACK (on_output_changed_cb), self, G_CONNECT_SWAPPED); + } + } + + cc_display_arrangement_set_selected_output (self, NULL); + + g_object_notify_by_pspec (G_OBJECT (self), props[PROP_CONFIG]); +} + +CcDisplayMonitor* +cc_display_arrangement_get_selected_output (CcDisplayArrangement *self) +{ return self->selected_output; } diff --git a/panels/display/cc-display-arrangement.h b/panels/display/cc-display-arrangement.h index 8d0e39bc0f..9494c484b8 100644 --- a/panels/display/cc-display-arrangement.h +++ b/panels/display/cc-display-arrangement.h @@ -30,6 +30,10 @@ G_DECLARE_FINAL_TYPE (CcDisplayArrangement, cc_display_arrangement, CC, DISPLAY_ CcDisplayArrangement* cc_display_arrangement_new (CcDisplayConfig *config); +CcDisplayConfig* cc_display_arrangement_get_config (CcDisplayArrangement *self); +void cc_display_arrangement_set_config (CcDisplayArrangement *self, + CcDisplayConfig *config); + CcDisplayMonitor* cc_display_arrangement_get_selected_output (CcDisplayArrangement *arr); void cc_display_arrangement_set_selected_output (CcDisplayArrangement *arr, CcDisplayMonitor *output); -- GitLab From b789a1cda2a65427c1b8b77739011badcb557afa Mon Sep 17 00:00:00 2001 From: Benjamin Berg Date: Wed, 23 Jan 2019 00:15:37 +0100 Subject: [PATCH 10/13] common: Add CcValueObject until HdyValueObject is available libhandy now has HdyValueObject, this simply copies the class over as it will only become available in libhandy 0.8. --- panels/common/cc-value-object.c | 269 ++++++++++++++++++++++++++++++++ panels/common/cc-value-object.h | 30 ++++ panels/common/meson.build | 2 + 3 files changed, 301 insertions(+) create mode 100644 panels/common/cc-value-object.c create mode 100644 panels/common/cc-value-object.h diff --git a/panels/common/cc-value-object.c b/panels/common/cc-value-object.c new file mode 100644 index 0000000000..c779b3a0be --- /dev/null +++ b/panels/common/cc-value-object.c @@ -0,0 +1,269 @@ +/* + * Copyright (C) 2019 Red Hat Inc. + * + * SPDX-License-Identifier: LGPL-2.1+ + * + * This is a copy of CcValueObject from libhandy 0.8. + * Modified to change the prefix and to not translate the property nick and + * description. + */ + +#include +#include "cc-value-object.h" + +/** + * SECTION:cc-value-object + * @short_description: An object representing a #GValue. + * @Title: CcValueObject + * + * The #CcValueObject object represents a #GValue, allowing it to be + * used with #GListModel. + * + * Since: 0.0.8 + */ + +struct _CcValueObject +{ + GObject parent_instance; + + GValue value; +}; + +G_DEFINE_TYPE (CcValueObject, cc_value_object, G_TYPE_OBJECT) + +enum { + PROP_0, + PROP_VALUE, + N_PROPS +}; + +static GParamSpec *props [N_PROPS]; + +/** + * cc_value_object_new: + * @value: the #GValue to store + * + * Create a new #CcValueObject. + * + * Returns: a new #CcValueObject + * Since: 0.0.8 + */ +CcValueObject * +cc_value_object_new (const GValue *value) +{ + return g_object_new (CC_TYPE_VALUE_OBJECT, + "value", value, + NULL); +} + +/** + * cc_value_object_new_collect: (skip) + * @type: the #GType of the value + * @...: the value to store + * + * Creates a new #CcValueObject. This is a convenience method which uses + * the G_VALUE_COLLECT() macro internally. + * + * Returns: a new #CcValueObject + * Since: 0.0.8 + */ +CcValueObject* +cc_value_object_new_collect (GType type, ...) +{ + g_auto(GValue) value = G_VALUE_INIT; + g_autofree gchar *error = NULL; + va_list var_args; + + va_start (var_args, type); + + G_VALUE_COLLECT_INIT (&value, type, var_args, 0, &error); + + va_end (var_args); + + if (error) + g_critical ("%s: %s", G_STRFUNC, error); + + return g_object_new (CC_TYPE_VALUE_OBJECT, + "value", &value, + NULL); +} + +/** + * cc_value_object_new_string: (skip) + * @string: (transfer none): the string to store + * + * Creates a new #CcValueObject. This is a convenience method to create a + * #CcValueObject that stores a string. + * + * Returns: a new #CcValueObject + * Since: 0.0.8 + */ +CcValueObject* +cc_value_object_new_string (const gchar *string) +{ + g_auto(GValue) value = G_VALUE_INIT; + + g_value_init (&value, G_TYPE_STRING); + g_value_set_string (&value, string); + return cc_value_object_new (&value); +} + +/** + * cc_value_object_new_take_string: (skip) + * @string: (transfer full): the string to store + * + * Creates a new #CcValueObject. This is a convenience method to create a + * #CcValueObject that stores a string taking ownership of it. + * + * Returns: a new #CcValueObject + * Since: 0.0.8 + */ +CcValueObject* +cc_value_object_new_take_string (gchar *string) +{ + g_auto(GValue) value = G_VALUE_INIT; + + g_value_init (&value, G_TYPE_STRING); + g_value_take_string (&value, string); + return cc_value_object_new (&value); +} + +static void +cc_value_object_finalize (GObject *object) +{ + CcValueObject *self = CC_VALUE_OBJECT (object); + + g_value_unset (&self->value); + + G_OBJECT_CLASS (cc_value_object_parent_class)->finalize (object); +} + +static void +cc_value_object_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + CcValueObject *self = CC_VALUE_OBJECT (object); + + switch (prop_id) + { + case PROP_VALUE: + g_value_set_boxed (value, &self->value); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + } +} + +static void +cc_value_object_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + CcValueObject *self = CC_VALUE_OBJECT (object); + GValue *real_value; + + switch (prop_id) + { + case PROP_VALUE: + /* construct only */ + real_value = g_value_get_boxed (value); + g_value_init (&self->value, real_value->g_type); + g_value_copy (real_value, &self->value); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + } +} + +static void +cc_value_object_class_init (CcValueObjectClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->finalize = cc_value_object_finalize; + object_class->get_property = cc_value_object_get_property; + object_class->set_property = cc_value_object_set_property; + + props[PROP_VALUE] = + g_param_spec_boxed ("value", "Value", + "The contained value", + G_TYPE_VALUE, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS); + + g_object_class_install_properties (object_class, + N_PROPS, + props); +} + +static void +cc_value_object_init (CcValueObject *self) +{ +} + +/** + * cc_value_object_get_value: + * @value: the #CcValueObject + * + * Return the contained value. + * + * Returns: (transfer none): the contained #GValue + * Since: 0.0.8 + */ +const GValue* +cc_value_object_get_value (CcValueObject *value) +{ + return &value->value; +} + +/** + * cc_value_object_copy_value: + * @value: the #CcValueObject + * @dest: #GValue with correct type to copy into + * + * Copy data from the contained #GValue into @dest. + * + * Since: 0.0.8 + */ +void +cc_value_object_copy_value (CcValueObject *value, + GValue *dest) +{ + g_value_copy (&value->value, dest); +} + +/** + * cc_value_object_get_string: + * @value: the #CcValueObject + * + * Returns the contained string if the value is of type #G_TYPE_STRING. + * + * Returns: (transfer none): the contained string + * Since: 0.0.8 + */ +const gchar* +cc_value_object_get_string (CcValueObject *value) +{ + return g_value_get_string (&value->value); +} + +/** + * cc_value_object_dup_string: + * @value: the #CcValueObject + * + * Returns a copy of the contained string if the value is of type + * #G_TYPE_STRING. + * + * Returns: (transfer full): a copy of the contained string + * Since: 0.0.8 + */ +gchar* +cc_value_object_dup_string (CcValueObject *value) +{ + return g_value_dup_string (&value->value); +} + diff --git a/panels/common/cc-value-object.h b/panels/common/cc-value-object.h new file mode 100644 index 0000000000..b39c2061ca --- /dev/null +++ b/panels/common/cc-value-object.h @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2019 Red Hat Inc. + * + * SPDX-License-Identifier: LGPL-2.1+ + */ + +#pragma once + +#include +#include + +G_BEGIN_DECLS + +#define CC_TYPE_VALUE_OBJECT (cc_value_object_get_type()) + +G_DECLARE_FINAL_TYPE (CcValueObject, cc_value_object, CC, VALUE_OBJECT, GObject) + +CcValueObject *cc_value_object_new (const GValue *value); +CcValueObject *cc_value_object_new_collect (GType type, + ...); +CcValueObject *cc_value_object_new_string (const gchar *string); +CcValueObject *cc_value_object_new_take_string (gchar *string); + +const GValue* cc_value_object_get_value (CcValueObject *value); +void cc_value_object_copy_value (CcValueObject *value, + GValue *dest); +const gchar* cc_value_object_get_string (CcValueObject *value); +gchar* cc_value_object_dup_string (CcValueObject *value); + +G_END_DECLS diff --git a/panels/common/meson.build b/panels/common/meson.build index 7d15e64f3c..118a842571 100644 --- a/panels/common/meson.build +++ b/panels/common/meson.build @@ -26,6 +26,8 @@ common_sources += gnome.mkenums( sources = files( 'cc-hostname-entry.c', + # Remove once we depend on libhandy 0.8 + 'cc-value-object.c', 'hostname-helper.c', 'list-box-helper.c', ) -- GitLab From 8503dab53000bd138d85250342bf32c2286c5826 Mon Sep 17 00:00:00 2001 From: Benjamin Berg Date: Thu, 17 Jan 2019 16:10:51 +0100 Subject: [PATCH 11/13] display: Add settings widget for output configuration This widget now uses libhandy for the rows. --- panels/display/cc-display-settings.c | 799 ++++++++++++++++++++++++++ panels/display/cc-display-settings.h | 44 ++ panels/display/cc-display-settings.ui | 64 +++ panels/display/display.gresource.xml | 1 + panels/display/meson.build | 4 +- 5 files changed, 911 insertions(+), 1 deletion(-) create mode 100644 panels/display/cc-display-settings.c create mode 100644 panels/display/cc-display-settings.h create mode 100644 panels/display/cc-display-settings.ui diff --git a/panels/display/cc-display-settings.c b/panels/display/cc-display-settings.c new file mode 100644 index 0000000000..58a308e2b4 --- /dev/null +++ b/panels/display/cc-display-settings.c @@ -0,0 +1,799 @@ +/* cc-display-settings.c + * + * Copyright (C) 2007, 2008, 2018, 2019 Red Hat, Inc. + * Copyright (C) 2013 Intel, Inc. + * + * Written by: Benjamin Berg + * + * 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, 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 . + */ + +#define HANDY_USE_UNSTABLE_API 1 +#include +#include +#include +#include "list-box-helper.h" +#include "cc-display-settings.h" +#include "cc-display-config.h" +#include "cc-value-object.h" + +/* The minimum supported size a monitor may have */ +#define MINIMUM_WIDTH 740 +#define MINIMUM_HEIGHT 530 + +struct _CcDisplaySettings +{ + GtkDrawingArea object; + + gboolean updating; + guint idle_udpate_id; + + gboolean has_accelerometer; + CcDisplayConfig *config; + CcDisplayMonitor *selected_output; + + GListStore *orientation_list; + GListStore *refresh_rate_list; + GListStore *resolution_list; + GListStore *scale_list; + + GtkWidget *orientation_row; + GtkWidget *refresh_rate_row; + GtkWidget *resolution_row; + GtkWidget *scale_bbox; + GtkWidget *scale_row; + GtkWidget *underscanning_row; + GtkWidget *underscanning_switch; +}; + +typedef struct _CcDisplaySettings CcDisplaySettings; + +enum { + PROP_0, + PROP_HAS_ACCELEROMETER, + PROP_CONFIG, + PROP_SELECTED_OUTPUT, + PROP_LAST +}; + +G_DEFINE_TYPE (CcDisplaySettings, cc_display_settings, GTK_TYPE_LIST_BOX) + +static GParamSpec *props[PROP_LAST]; + +static gboolean +should_show_rotation (CcDisplaySettings *self) +{ + gboolean supports_rotation; + + supports_rotation = cc_display_monitor_supports_rotation (self->selected_output, + CC_DISPLAY_ROTATION_90 | + CC_DISPLAY_ROTATION_180 | + CC_DISPLAY_ROTATION_270); + + /* Doesn't support rotation at all */ + if (!supports_rotation) + return FALSE; + + /* We can always rotate displays that aren't builtin */ + if (!cc_display_monitor_is_builtin (self->selected_output)) + return TRUE; + + /* Only offer rotation if there's no accelerometer */ + return !self->has_accelerometer; +} + +static const gchar * +string_for_rotation (CcDisplayRotation rotation) +{ + switch (rotation) + { + case CC_DISPLAY_ROTATION_NONE: + case CC_DISPLAY_ROTATION_180_FLIPPED: + return C_("Display rotation", "Landscape"); + case CC_DISPLAY_ROTATION_90: + case CC_DISPLAY_ROTATION_270_FLIPPED: + return C_("Display rotation", "Portrait Right"); + case CC_DISPLAY_ROTATION_270: + case CC_DISPLAY_ROTATION_90_FLIPPED: + return C_("Display rotation", "Portrait Left"); + case CC_DISPLAY_ROTATION_180: + case CC_DISPLAY_ROTATION_FLIPPED: + return C_("Display rotation", "Landscape (flipped)"); + } + return ""; +} + +static const gchar * +make_aspect_string (gint width, + gint height) +{ + int ratio; + const gchar *aspect = NULL; + + /* We use a number of Unicode characters below: + * ∶ is U+2236 RATIO + *   is U+2009 THIN SPACE, + * × is U+00D7 MULTIPLICATION SIGN + */ + if (width && height) { + if (width > height) + ratio = width * 10 / height; + else + ratio = height * 10 / width; + + switch (ratio) { + case 13: + aspect = "4∶3"; + break; + case 16: + aspect = "16∶10"; + break; + case 17: + aspect = "16∶9"; + break; + case 23: + aspect = "21∶9"; + break; + case 12: + aspect = "5∶4"; + break; + /* This catches 1.5625 as well (1600x1024) when maybe it shouldn't. */ + case 15: + aspect = "3∶2"; + break; + case 18: + aspect = "9∶5"; + break; + case 10: + aspect = "1∶1"; + break; + } + } + + return aspect; +} + +static char * +make_resolution_string (CcDisplayMode *mode) +{ + const char *interlaced = cc_display_mode_is_interlaced (mode) ? "i" : ""; + const char *aspect; + int width, height; + + cc_display_mode_get_resolution (mode, &width, &height); + aspect = make_aspect_string (width, height); + + if (aspect != NULL) + return g_strdup_printf ("%d × %d%s (%s)", width, height, interlaced, aspect); + else + return g_strdup_printf ("%d × %d%s", width, height, interlaced); +} + +static gchar * +get_frequency_string (CcDisplayMode *mode) +{ + return g_strdup_printf (_("%.2lf Hz"), cc_display_mode_get_freq_f (mode)); +} + +static gboolean +display_mode_supported_at_scale (CcDisplayMode *mode, double scale) +{ + int width, height; + + cc_display_mode_get_resolution (mode, &width, &height); + + return round (width / scale) >= MINIMUM_WIDTH && round (height / scale) >= MINIMUM_HEIGHT; +} + +static double +round_scale_for_ui (double scale) +{ + /* Keep in sync with mutter */ + return round (scale*4)/4; +} + +static gchar * +get_scale_string (CcValueObject *obj) +{ + gdouble scale = g_value_get_double (cc_value_object_get_value (obj)); + + return g_strdup_printf (" %d %% ", (int) (round_scale_for_ui (scale)*100)); +} + +static gint +sort_modes_by_area_desc (CcDisplayMode *a, CcDisplayMode *b) +{ + gint wa, ha, wb, hb; + gint res; + + cc_display_mode_get_resolution (a, &wa, &ha); + cc_display_mode_get_resolution (b, &wb, &hb); + + /* Prefer wide screen if the size is equal */ + res = wb*hb - wa*ha; + if (res == 0) + return wb - wa; + return res; +} + +static gint +sort_modes_by_freq_desc (CcDisplayMode *a, CcDisplayMode *b) +{ + double delta = (cc_display_mode_get_freq_f (b) - cc_display_mode_get_freq_f (a))*1000.; + return delta; +} + +static gboolean +cc_display_settings_rebuild_ui (CcDisplaySettings *self) +{ + GList *modes; + GList *item; + gint width, height; + CcDisplayMode *current_mode; + + self->idle_udpate_id = 0; + + if (!self->config || !self->selected_output) + { + gtk_widget_set_visible (self->orientation_row, FALSE); + gtk_widget_set_visible (self->refresh_rate_row, FALSE); + gtk_widget_set_visible (self->resolution_row, FALSE); + gtk_widget_set_visible (self->scale_row, FALSE); + gtk_widget_set_visible (self->underscanning_row, FALSE); + + return G_SOURCE_REMOVE; + } + + g_object_freeze_notify ((GObject*) self->orientation_row); + g_object_freeze_notify ((GObject*) self->refresh_rate_row); + g_object_freeze_notify ((GObject*) self->resolution_row); + g_object_freeze_notify ((GObject*) self->scale_row); + g_object_freeze_notify ((GObject*) self->underscanning_switch); + + cc_display_monitor_get_geometry (self->selected_output, NULL, NULL, &width, &height); + + /* Selecte the first mode we can find if the monitor is disabled. */ + current_mode = cc_display_monitor_get_mode (self->selected_output); + if (current_mode == NULL) + current_mode = cc_display_monitor_get_preferred_mode (self->selected_output); + + if (should_show_rotation (self)) + { + guint i; + CcDisplayRotation rotations[] = { CC_DISPLAY_ROTATION_NONE, + CC_DISPLAY_ROTATION_90, + CC_DISPLAY_ROTATION_270, + CC_DISPLAY_ROTATION_180 }; + + gtk_widget_set_visible (self->orientation_row, TRUE); + + g_list_store_remove_all (self->orientation_list); + for (i = 0; i < G_N_ELEMENTS (rotations); i++) + { + g_autoptr(CcValueObject) obj = NULL; + + if (!cc_display_monitor_supports_rotation (self->selected_output, rotations[i])) + continue; + + obj = cc_value_object_new_collect (G_TYPE_STRING, string_for_rotation (rotations[i])); + g_list_store_append (self->orientation_list, obj); + g_object_set_data (G_OBJECT (obj), "rotation-value", GINT_TO_POINTER (rotations[i])); + + if (cc_display_monitor_get_rotation (self->selected_output) == rotations[i]) + hdy_combo_row_set_selected_index (HDY_COMBO_ROW (self->orientation_row), + g_list_model_get_n_items (G_LIST_MODEL (self->orientation_list)) - 1); + } + } + else + { + gtk_widget_set_visible (self->orientation_row, FALSE); + } + + /* Only show refresh rate if we are not in cloning mode. */ + if (!cc_display_config_is_cloning (self->config)) + { + GList *item; + gdouble freq; + + freq = cc_display_mode_get_freq_f (current_mode); + + modes = cc_display_monitor_get_modes (self->selected_output); + + g_list_store_remove_all (self->refresh_rate_list); + + for (item = modes; item != NULL; item = item->next) + { + gint w, h; + guint new; + CcDisplayMode *mode = CC_DISPLAY_MODE (item->data); + + cc_display_mode_get_resolution (mode, &w, &h); + if (w != width || h != height) + continue; + + /* At some point we used to filter very close resolutions, + * but we don't anymore these days. + */ + new = g_list_store_insert_sorted (self->refresh_rate_list, + mode, + (GCompareDataFunc) sort_modes_by_freq_desc, + NULL); + if (freq == cc_display_mode_get_freq_f (mode)) + hdy_combo_row_set_selected_index (HDY_COMBO_ROW (self->refresh_rate_row), new); + } + + /* Show if we have more than one frequency to choose from. */ + gtk_widget_set_visible (self->refresh_rate_row, + g_list_model_get_n_items (G_LIST_MODEL (self->refresh_rate_list)) > 1); + } + else + { + gtk_widget_set_visible (self->refresh_rate_row, FALSE); + } + + + /* Resolutions are always shown. */ + gtk_widget_set_visible (self->resolution_row, TRUE); + if (cc_display_config_is_cloning (self->config)) + modes = cc_display_config_get_cloning_modes (self->config); + else + modes = cc_display_monitor_get_modes (self->selected_output); + + g_list_store_remove_all (self->resolution_list); + g_list_store_append (self->resolution_list, current_mode); + hdy_combo_row_set_selected_index (HDY_COMBO_ROW (self->resolution_row), 0); + for (item = modes; item != NULL; item = item->next) + { + gint ins; + gint w, h; + CcDisplayMode *mode = CC_DISPLAY_MODE (item->data); + + /* Exclude unusable low resolutions */ + if (!display_mode_supported_at_scale (mode, 1.0)) + continue; + + cc_display_mode_get_resolution (mode, &w, &h); + + /* Find the appropriate insertion point. */ + for (ins = 0; ins < g_list_model_get_n_items (G_LIST_MODEL (self->resolution_list)); ins++) + { + g_autoptr(CcDisplayMode) m = NULL; + gint cmp; + + m = g_list_model_get_item (G_LIST_MODEL (self->resolution_list), ins); + + cmp = sort_modes_by_area_desc (mode, m); + /* Next item is smaller, insert at this point. */ + if (cmp < 0) + break; + + /* Don't insert if it is already in the list */ + if (cmp == 0) + { + ins = -1; + break; + } + } + + if (ins >= 0) + g_list_store_insert (self->resolution_list, ins, mode); + } + + + /* Update scale row. */ + g_list_store_remove_all (self->scale_list); + if (!cc_display_config_is_cloning (self->config)) + { + const gdouble *scales, *scale; + + scales = cc_display_mode_get_supported_scales (current_mode); + for (scale = scales; *scale != 0.0; scale++) + { + g_autoptr(CcValueObject) obj = NULL; + + if (!display_mode_supported_at_scale (current_mode, *scale) && + cc_display_monitor_get_scale (self->selected_output) != *scale) + continue; + + obj = cc_value_object_new_collect (G_TYPE_DOUBLE, *scale); + g_list_store_append (self->scale_list, obj); + + if (cc_display_monitor_get_scale (self->selected_output) == *scale) + hdy_combo_row_set_selected_index (HDY_COMBO_ROW (self->scale_row), + g_list_model_get_n_items (G_LIST_MODEL (self->scale_list)) - 1); + } + + gtk_widget_set_visible (self->scale_row, g_list_model_get_n_items (G_LIST_MODEL (self->scale_list)) > 1); + } + else + { + gtk_widget_set_visible (self->scale_row, FALSE); + } + + gtk_widget_set_visible (self->underscanning_row, + cc_display_monitor_supports_underscanning (self->selected_output) && + !cc_display_config_is_cloning (self->config)); + gtk_switch_set_active (GTK_SWITCH (self->underscanning_switch), + cc_display_monitor_get_underscanning (self->selected_output)); + + self->updating = TRUE; + g_object_thaw_notify ((GObject*) self->orientation_row); + g_object_thaw_notify ((GObject*) self->refresh_rate_row); + g_object_thaw_notify ((GObject*) self->resolution_row); + g_object_thaw_notify ((GObject*) self->scale_row); + g_object_thaw_notify ((GObject*) self->underscanning_switch); + self->updating = FALSE; + + return G_SOURCE_REMOVE; +} + +static void +on_output_changed_cb (CcDisplaySettings *self, + GParamSpec *pspec, + CcDisplayMonitor *output) +{ + /* Do this frmo an idle handler, because otherwise we may create an + * infinite loop triggering the notify::selected-index from the + * combo rows. */ + if (self->idle_udpate_id) + return; + + self->idle_udpate_id = g_idle_add ((GSourceFunc) cc_display_settings_rebuild_ui, self); +} + +static void +on_orientation_selection_changed_cb (GtkWidget *widget, + GParamSpec *pspec, + CcDisplaySettings *self) +{ + gint idx; + g_autoptr(CcValueObject) obj = NULL; + + if (self->updating) + return; + + idx = hdy_combo_row_get_selected_index (HDY_COMBO_ROW (self->orientation_row)); + obj = g_list_model_get_item (G_LIST_MODEL (self->orientation_list), idx); + + cc_display_monitor_set_rotation (self->selected_output, + GPOINTER_TO_INT (g_object_get_data (G_OBJECT (obj), "rotation-value"))); + + g_signal_emit_by_name (G_OBJECT (self), "updated", self->selected_output); +} + +static void +on_refresh_rate_selection_changed_cb (GtkWidget *widget, + GParamSpec *pspec, + CcDisplaySettings *self) +{ + gint idx; + g_autoptr(CcDisplayMode) mode = NULL; + + if (self->updating) + return; + + idx = hdy_combo_row_get_selected_index (HDY_COMBO_ROW (self->refresh_rate_row)); + mode = g_list_model_get_item (G_LIST_MODEL (self->refresh_rate_list), idx); + + cc_display_monitor_set_mode (self->selected_output, mode); + + g_signal_emit_by_name (G_OBJECT (self), "updated", self->selected_output); +} + +static void +on_resolution_selection_changed_cb (GtkWidget *widget, + GParamSpec *pspec, + CcDisplaySettings *self) +{ + gint idx; + g_autoptr(CcDisplayMode) mode = NULL; + + if (self->updating) + return; + + idx = hdy_combo_row_get_selected_index (HDY_COMBO_ROW (self->resolution_row)); + mode = g_list_model_get_item (G_LIST_MODEL (self->resolution_list), idx); + + /* This is the only row that can be changed when in cloning mode. */ + if (!cc_display_config_is_cloning (self->config)) + cc_display_monitor_set_mode (self->selected_output, mode); + else + cc_display_config_set_mode_on_all_outputs (self->config, mode); + + g_signal_emit_by_name (G_OBJECT (self), "updated", self->selected_output); +} + +static void +on_scale_selection_changed_cb (GtkWidget *widget, + GParamSpec *pspec, + CcDisplaySettings *self) +{ + gint idx; + g_autoptr(CcValueObject) obj = NULL; + + if (self->updating) + return; + + idx = hdy_combo_row_get_selected_index (HDY_COMBO_ROW (self->scale_row)); + obj = g_list_model_get_item (G_LIST_MODEL (self->scale_list), idx); + + cc_display_monitor_set_scale (self->selected_output, + g_value_get_double (cc_value_object_get_value (obj))); + + g_signal_emit_by_name (G_OBJECT (self), "updated", self->selected_output); +} + +static void +on_underscanning_switch_active_changed_cb (GtkWidget *widget, + GParamSpec *pspec, + CcDisplaySettings *self) +{ + if (self->updating) + return; + + cc_display_monitor_set_underscanning (self->selected_output, + gtk_switch_get_active (GTK_SWITCH (self->underscanning_switch))); + + g_signal_emit_by_name (G_OBJECT (self), "updated", self->selected_output); +} + +static void +cc_display_settings_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + CcDisplaySettings *self = CC_DISPLAY_SETTINGS (object); + + switch (prop_id) + { + case PROP_HAS_ACCELEROMETER: + g_value_set_boolean (value, cc_display_settings_get_has_accelerometer (self)); + break; + + case PROP_CONFIG: + g_value_set_object (value, self->config); + break; + + case PROP_SELECTED_OUTPUT: + g_value_set_object (value, self->selected_output); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + } +} + +static void +cc_display_settings_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + CcDisplaySettings *self = CC_DISPLAY_SETTINGS (object); + + switch (prop_id) + { + case PROP_HAS_ACCELEROMETER: + cc_display_settings_set_has_accelerometer (self, g_value_get_boolean (value)); + break; + + case PROP_CONFIG: + cc_display_settings_set_config (self, g_value_get_object (value)); + break; + + case PROP_SELECTED_OUTPUT: + cc_display_settings_set_selected_output (self, g_value_get_object (value)); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + } +} + +static void +cc_display_settings_finalize (GObject *object) +{ + CcDisplaySettings *self = CC_DISPLAY_SETTINGS (object); + + g_clear_object (&self->config); + + g_clear_object (&self->orientation_list); + g_clear_object (&self->refresh_rate_list); + g_clear_object (&self->resolution_list); + g_clear_object (&self->scale_list); + + if (self->idle_udpate_id) + g_source_remove (self->idle_udpate_id); + self->idle_udpate_id = 0; + + G_OBJECT_CLASS (cc_display_settings_parent_class)->finalize (object); +} + +static void +cc_display_settings_class_init (CcDisplaySettingsClass *klass) +{ + GObjectClass *gobject_class = G_OBJECT_CLASS (klass); + GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); + + gobject_class->finalize = cc_display_settings_finalize; + gobject_class->get_property = cc_display_settings_get_property; + gobject_class->set_property = cc_display_settings_set_property; + + gtk_widget_class_set_template_from_resource (widget_class, "/org/gnome/control-center/display/cc-display-settings.ui"); + + props[PROP_HAS_ACCELEROMETER] = + g_param_spec_boolean ("has-accelerometer", "Has Accelerometer", + "If an accelerometre is available for the builtin display", + FALSE, + G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS); + + props[PROP_CONFIG] = + g_param_spec_object ("config", "Display Config", + "The display configuration to work with", + CC_TYPE_DISPLAY_CONFIG, + G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS); + + props[PROP_SELECTED_OUTPUT] = + g_param_spec_object ("selected-output", "Selected Output", + "The output that is currently selected on the configuration", + CC_TYPE_DISPLAY_MONITOR, + G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS); + + g_object_class_install_properties (gobject_class, + PROP_LAST, + props); + + g_signal_new ("updated", + CC_TYPE_DISPLAY_SETTINGS, + G_SIGNAL_RUN_LAST, + 0, NULL, NULL, NULL, + G_TYPE_NONE, 1, CC_TYPE_DISPLAY_MONITOR); + + gtk_widget_class_bind_template_child (widget_class, CcDisplaySettings, orientation_row); + gtk_widget_class_bind_template_child (widget_class, CcDisplaySettings, refresh_rate_row); + gtk_widget_class_bind_template_child (widget_class, CcDisplaySettings, resolution_row); + gtk_widget_class_bind_template_child (widget_class, CcDisplaySettings, scale_row); + gtk_widget_class_bind_template_child (widget_class, CcDisplaySettings, underscanning_row); + gtk_widget_class_bind_template_child (widget_class, CcDisplaySettings, underscanning_switch); + + gtk_widget_class_bind_template_callback (widget_class, on_orientation_selection_changed_cb); + gtk_widget_class_bind_template_callback (widget_class, on_refresh_rate_selection_changed_cb); + gtk_widget_class_bind_template_callback (widget_class, on_resolution_selection_changed_cb); + gtk_widget_class_bind_template_callback (widget_class, on_scale_selection_changed_cb); + gtk_widget_class_bind_template_callback (widget_class, on_underscanning_switch_active_changed_cb); +} + +static void +cc_display_settings_init (CcDisplaySettings *self) +{ + gtk_widget_init_template (GTK_WIDGET (self)); + + gtk_list_box_set_header_func (GTK_LIST_BOX (self), + cc_list_box_update_header_func, + NULL, NULL); + + self->orientation_list = g_list_store_new (CC_TYPE_VALUE_OBJECT); + self->refresh_rate_list = g_list_store_new (CC_TYPE_DISPLAY_MODE); + self->resolution_list = g_list_store_new (CC_TYPE_DISPLAY_MODE); + self->scale_list = g_list_store_new (CC_TYPE_VALUE_OBJECT); + + self->updating = TRUE; + + hdy_combo_row_bind_name_model (HDY_COMBO_ROW (self->orientation_row), + G_LIST_MODEL (self->orientation_list), + (HdyComboRowGetNameFunc) cc_value_object_dup_string, + NULL, NULL); + hdy_combo_row_bind_name_model (HDY_COMBO_ROW (self->refresh_rate_row), + G_LIST_MODEL (self->refresh_rate_list), + (HdyComboRowGetNameFunc) get_frequency_string, + NULL, NULL); + hdy_combo_row_bind_name_model (HDY_COMBO_ROW (self->resolution_row), + G_LIST_MODEL (self->resolution_list), + (HdyComboRowGetNameFunc) make_resolution_string, + NULL, NULL); + hdy_combo_row_bind_name_model (HDY_COMBO_ROW (self->scale_row), + G_LIST_MODEL (self->scale_list), + (HdyComboRowGetNameFunc) get_scale_string, + NULL, NULL); + + self->updating = FALSE; +} + +CcDisplaySettings* +cc_display_settings_new (void) +{ + return g_object_new (CC_TYPE_DISPLAY_SETTINGS, + NULL); +} + +gboolean +cc_display_settings_get_has_accelerometer (CcDisplaySettings *settings) +{ + return settings->has_accelerometer; +} + +void +cc_display_settings_set_has_accelerometer (CcDisplaySettings *self, + gboolean has_accelerometer) +{ + self->has_accelerometer = has_accelerometer; + + cc_display_settings_rebuild_ui (self); + g_object_notify_by_pspec (G_OBJECT (self), props[PROP_CONFIG]); +} + +CcDisplayConfig* +cc_display_settings_get_config (CcDisplaySettings *self) +{ + return self->config; +} + +void +cc_display_settings_set_config (CcDisplaySettings *self, + CcDisplayConfig *config) +{ + const gchar *signals[] = { "rotation", "mode", "scale", "is-usable", "active" }; + GList *outputs, *l; + guint i; + + if (self->config) + { + outputs = cc_display_config_get_monitors (self->config); + for (l = outputs; l; l = l->next) + { + CcDisplayMonitor *output = l->data; + + g_signal_handlers_disconnect_by_data (output, self); + } + } + g_clear_object (&self->config); + + self->config = g_object_ref (config); + + /* Listen to all the signals */ + if (self->config) + { + outputs = cc_display_config_get_monitors (self->config); + for (l = outputs; l; l = l->next) + { + CcDisplayMonitor *output = l->data; + + for (i = 0; i < G_N_ELEMENTS (signals); ++i) + g_signal_connect_object (output, signals[i], G_CALLBACK (on_output_changed_cb), self, G_CONNECT_SWAPPED); + } + } + + cc_display_settings_set_selected_output (self, NULL); + + g_object_notify_by_pspec (G_OBJECT (self), props[PROP_CONFIG]); +} + +CcDisplayMonitor* +cc_display_settings_get_selected_output (CcDisplaySettings *self) +{ + return self->selected_output; +} + +void +cc_display_settings_set_selected_output (CcDisplaySettings *self, + CcDisplayMonitor *output) +{ + self->selected_output = output; + + cc_display_settings_rebuild_ui (self); + + g_object_notify_by_pspec (G_OBJECT (self), props[PROP_SELECTED_OUTPUT]); +} + diff --git a/panels/display/cc-display-settings.h b/panels/display/cc-display-settings.h new file mode 100644 index 0000000000..58709ddf7d --- /dev/null +++ b/panels/display/cc-display-settings.h @@ -0,0 +1,44 @@ +/* -*- mode: c; style: linux -*- + * + * Copyright (C) 2019 Red Hat, Inc. + * + * Written by: Benjamin Berg + * + * 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, 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 . + */ + +#pragma once + +#include +#include "cc-display-config.h" + +G_BEGIN_DECLS + +#define CC_TYPE_DISPLAY_SETTINGS cc_display_settings_get_type () +G_DECLARE_FINAL_TYPE (CcDisplaySettings, cc_display_settings, CC, DISPLAY_SETTINGS, GtkListBox); + +CcDisplaySettings* cc_display_settings_new (void); + +gboolean cc_display_settings_get_has_accelerometer (CcDisplaySettings *settings); +void cc_display_settings_set_has_accelerometer (CcDisplaySettings *settings, + gboolean has_accelerometer); +CcDisplayConfig* cc_display_settings_get_config (CcDisplaySettings *settings); +void cc_display_settings_set_config (CcDisplaySettings *settings, + CcDisplayConfig *config); +CcDisplayMonitor* cc_display_settings_get_selected_output (CcDisplaySettings *settings); +void cc_display_settings_set_selected_output (CcDisplaySettings *settings, + CcDisplayMonitor *output); + +G_END_DECLS + diff --git a/panels/display/cc-display-settings.ui b/panels/display/cc-display-settings.ui new file mode 100644 index 0000000000..6e04265f29 --- /dev/null +++ b/panels/display/cc-display-settings.ui @@ -0,0 +1,64 @@ + + + + + + + diff --git a/panels/display/display.gresource.xml b/panels/display/display.gresource.xml index 5ecf8c5968..70eedbaa9a 100644 --- a/panels/display/display.gresource.xml +++ b/panels/display/display.gresource.xml @@ -1,6 +1,7 @@ + cc-display-settings.ui cc-night-light-dialog.ui display-arrangement.css night-light.css diff --git a/panels/display/meson.build b/panels/display/meson.build index bd4352b9bb..a90572b1e9 100644 --- a/panels/display/meson.build +++ b/panels/display/meson.build @@ -24,11 +24,13 @@ sources = files( 'cc-display-config-dbus.c', 'cc-display-config-manager-dbus.c', 'cc-display-config-manager.c', + 'cc-display-settings.c', 'cc-night-light-dialog.c', ) resource_data = files( - 'cc-night-light-dialog.ui' + 'cc-display-settings.ui', + 'cc-night-light-dialog.ui', ) sources += gnome.compile_resources( -- GitLab From e5925f5eb6e95e6d0a79734d39ff637555f2deaf Mon Sep 17 00:00:00 2001 From: Benjamin Berg Date: Fri, 18 Jan 2019 11:13:01 +0100 Subject: [PATCH 12/13] display: Hookup new CcDisplaySettings --- panels/display/cc-display-panel.c | 942 +++--------------------------- 1 file changed, 87 insertions(+), 855 deletions(-) diff --git a/panels/display/cc-display-panel.c b/panels/display/cc-display-panel.c index d3d9d982ff..f42b2f7032 100644 --- a/panels/display/cc-display-panel.c +++ b/panels/display/cc-display-panel.c @@ -35,6 +35,7 @@ #include "cc-display-arrangement.h" #include "cc-night-light-dialog.h" #include "cc-display-resources.h" +#include "cc-display-settings.h" /* The minimum supported size for the panel */ #define MINIMUM_WIDTH 740 @@ -62,6 +63,7 @@ struct _CcDisplayPanel CcDisplayMonitor *current_output; CcDisplayArrangement *arrangement; + CcDisplaySettings *settings; guint focus_id; @@ -106,15 +108,8 @@ enum }; static guint panel_signals[LAST_PANEL_SIGNAL] = { 0 }; -static const gchar * -get_resolution_string (CcDisplayMode *mode); -static const gchar * -get_frequency_string (CcDisplayMode *mode); -static GtkWidget * +static GtkWidget* make_night_light_widget (CcDisplayPanel *panel); -static gboolean -should_show_rotation (CcDisplayPanel *panel, - CcDisplayMonitor *output); static void update_apply_button (CcDisplayPanel *panel); static void @@ -281,6 +276,16 @@ cc_display_panel_dispose (GObject *object) G_OBJECT_CLASS (cc_display_panel_parent_class)->dispose (object); } +static void +on_monitor_settings_updated_cb (CcDisplayPanel *panel, + CcDisplayMonitor *monitor, + CcDisplaySettings *settings) +{ + if (monitor) + cc_display_config_snap_output (panel->current_config, monitor); + update_apply_button (panel); +} + static void cc_display_panel_constructed (GObject *object) { @@ -525,587 +530,11 @@ make_popover_label (const gchar *text) NULL); } -static const gchar * -string_for_rotation (CcDisplayRotation rotation) -{ - switch (rotation) - { - case CC_DISPLAY_ROTATION_NONE: - case CC_DISPLAY_ROTATION_180_FLIPPED: - return C_("Display rotation", "Landscape"); - case CC_DISPLAY_ROTATION_90: - case CC_DISPLAY_ROTATION_270_FLIPPED: - return C_("Display rotation", "Portrait Right"); - case CC_DISPLAY_ROTATION_270: - case CC_DISPLAY_ROTATION_90_FLIPPED: - return C_("Display rotation", "Portrait Left"); - case CC_DISPLAY_ROTATION_180: - case CC_DISPLAY_ROTATION_FLIPPED: - return C_("Display rotation", "Landscape (flipped)"); - } - return ""; -} - -static void -orientation_row_activated (CcDisplayPanel *panel, - GtkListBoxRow *row) -{ - CcDisplayRotation rotation = GPOINTER_TO_UINT (g_object_get_data (G_OBJECT (row), "rotation")); - - cc_display_monitor_set_rotation (panel->current_output, rotation); - cc_display_config_snap_output (panel->current_config, panel->current_output); - update_apply_button (panel); -} - -static GtkWidget * -make_orientation_popover (CcDisplayPanel *panel) -{ - GtkWidget *listbox; - CcDisplayRotation rotations[] = { CC_DISPLAY_ROTATION_NONE, - CC_DISPLAY_ROTATION_90, - CC_DISPLAY_ROTATION_270, - CC_DISPLAY_ROTATION_180 }; - guint i = 0; - - listbox = make_list_box (); - gtk_widget_show (listbox); - g_object_set (listbox, "margin", 12, NULL); - - for (i = 0; i < G_N_ELEMENTS (rotations); ++i) - { - CcDisplayRotation rotation = rotations[i]; - if (cc_display_monitor_supports_rotation (panel->current_output, rotation)) - { - GtkWidget *label, *row; - - label = make_popover_label (string_for_rotation (rotation)); - gtk_widget_show (label); - row = g_object_new (CC_TYPE_LIST_BOX_ROW, - "child", label, - NULL); - gtk_widget_show (row); - g_object_set_data (G_OBJECT (row), "rotation", GUINT_TO_POINTER (rotation)); - - g_signal_connect_object (row, "activated", G_CALLBACK (orientation_row_activated), - panel, G_CONNECT_SWAPPED); - - gtk_container_add (GTK_CONTAINER (listbox), row); - } - } - - return make_list_popover (listbox); -} - -static void -orientation_row_sync (GtkPopover *popover, - CcDisplayMonitor *output) -{ - gtk_label_set_text (GTK_LABEL (gtk_popover_get_relative_to (popover)), - string_for_rotation (cc_display_monitor_get_rotation (output))); -} - -static GtkWidget * -make_orientation_row (CcDisplayPanel *panel, CcDisplayMonitor *output) -{ - GtkWidget *row, *label, *heading_label, *popover; - - label = gtk_label_new (string_for_rotation (cc_display_monitor_get_rotation (output))); - gtk_widget_show (label); - popover = make_orientation_popover (panel); - gtk_popover_set_relative_to (GTK_POPOVER (popover), label); - - heading_label = gtk_label_new (_("Orientation")); - gtk_widget_show (heading_label); - row = make_row (panel->rows_size_group, heading_label, label); - g_signal_connect_object (row, "activated", G_CALLBACK (gtk_popover_popup), - popover, G_CONNECT_SWAPPED); - g_signal_connect_object (output, "rotation", G_CALLBACK (orientation_row_sync), - popover, G_CONNECT_SWAPPED); - return row; -} - -static gboolean -display_mode_supported_at_scale (CcDisplayMode *mode, double scale) -{ - gint width, height; - gint scaled_width, scaled_height; - - cc_display_mode_get_resolution (mode, &width, &height); - - scaled_width = round (width / scale); - scaled_height = round (height / scale); - - return scaled_width >= MINIMUM_WIDTH && scaled_height >= MINIMUM_HEIGHT; -} - -static void -resolution_row_activated (CcDisplayPanel *panel, - GtkListBoxRow *row) -{ - CcDisplayMode *mode = g_object_get_data (G_OBJECT (row), "mode"); - double scale = cc_display_monitor_get_scale (panel->current_output); - - cc_display_monitor_set_mode (panel->current_output, mode); - - /* Restore 1.0 scaling if the previous scale is not supported at the - * new resolution. */ - if (!display_mode_supported_at_scale (mode, scale)) - cc_display_monitor_set_scale (panel->current_output, 1.0); - - cc_display_config_snap_output (panel->current_config, panel->current_output); - update_apply_button (panel); -} - -static GtkWidget * -make_resolution_popover (CcDisplayPanel *panel) -{ - GtkWidget *listbox; - GList *resolutions, *l; - - resolutions = g_object_get_data (G_OBJECT (panel->current_output), "res-list"); - - listbox = make_list_box (); - gtk_widget_show (listbox); - g_object_set (listbox, "margin", 12, NULL); - - for (l = resolutions; l; l = l->next) - { - CcDisplayMode *mode = l->data; - GtkWidget *row; - GtkWidget *child; - - /* Exclude unusable low resolutions */ - if (!display_mode_supported_at_scale (mode, 1.0)) - continue; - - child = make_popover_label (get_resolution_string (mode)); - gtk_widget_show (child); - - row = g_object_new (CC_TYPE_LIST_BOX_ROW, - "child", child, - NULL); - gtk_widget_show (row); - g_object_set_data (G_OBJECT (row), "mode", mode); - - g_signal_connect_object (row, "activated", G_CALLBACK (resolution_row_activated), - panel, G_CONNECT_SWAPPED); - gtk_container_add (GTK_CONTAINER (listbox), row); - } - - return make_list_popover (listbox); -} - static void -resolution_row_sync (GtkPopover *popover, - CcDisplayMonitor *output) -{ - gtk_label_set_text (GTK_LABEL (gtk_popover_get_relative_to (popover)), - get_resolution_string (cc_display_monitor_get_mode (output))); -} - -static GtkWidget * -make_resolution_row (CcDisplayPanel *panel, CcDisplayMonitor *output) -{ - GtkWidget *row, *label, *heading_label, *popover; - - label = gtk_label_new (get_resolution_string (cc_display_monitor_get_mode (output))); - gtk_widget_show (label); - popover = make_resolution_popover (panel); - gtk_popover_set_relative_to (GTK_POPOVER (popover), label); - - heading_label = gtk_label_new (_("Resolution")); - gtk_widget_show (heading_label); - row = make_row (panel->rows_size_group, heading_label, label); - g_signal_connect_object (row, "activated", G_CALLBACK (gtk_popover_popup), - popover, G_CONNECT_SWAPPED); - g_signal_connect_object (output, "mode", G_CALLBACK (resolution_row_sync), - popover, G_CONNECT_SWAPPED); - return row; -} - -static void -refresh_rate_row_activated (CcDisplayPanel *panel, - GtkListBoxRow *row) -{ - CcDisplayMode *mode = g_object_get_data (G_OBJECT (row), "mode"); - - cc_display_monitor_set_mode (panel->current_output, mode); - update_apply_button (panel); -} - -static GtkWidget * -make_refresh_rate_popover (CcDisplayPanel *panel) -{ - GtkWidget *listbox; - GHashTable *res_freqs; - GList *freqs, *l; - - res_freqs = g_object_get_data (G_OBJECT (panel->current_output), "res-freqs"); - freqs = g_hash_table_lookup (res_freqs, - get_resolution_string (cc_display_monitor_get_mode (panel->current_output))); - - listbox = make_list_box (); - gtk_widget_show (listbox); - g_object_set (listbox, "margin", 12, NULL); - - for (l = freqs; l; l = l->next) - { - CcDisplayMode *mode = l->data; - GtkWidget *label, *row; - - label = make_popover_label (get_frequency_string (mode)); - gtk_widget_show (label); - row = g_object_new (CC_TYPE_LIST_BOX_ROW, - "child", label, - NULL); - gtk_widget_show (row); - g_object_set_data (G_OBJECT (row), "mode", mode); - - g_signal_connect_object (row, "activated", G_CALLBACK (refresh_rate_row_activated), - panel, G_CONNECT_SWAPPED); - gtk_container_add (GTK_CONTAINER (listbox), row); - } - - return make_list_popover (listbox); -} - -static void -refresh_rate_row_sync (GtkPopover *popover, - CcDisplayMonitor *output) -{ - gtk_label_set_text (GTK_LABEL (gtk_popover_get_relative_to (popover)), - get_frequency_string (cc_display_monitor_get_mode (output))); -} - -static gboolean -should_show_refresh_rate (CcDisplayMonitor *output) -{ - GHashTable *res_freqs = g_object_get_data (G_OBJECT (output), "res-freqs"); - const gchar *resolution = get_resolution_string (cc_display_monitor_get_mode (output)); - GList *freqs = g_hash_table_lookup (res_freqs, resolution); - - return g_list_length (freqs) > 1; -} - -static void -refresh_rate_row_sync_visibility (GtkWidget *row, - CcDisplayMonitor *output) -{ - if (!should_show_refresh_rate (output)) - gtk_widget_hide (row); - else - gtk_widget_show (row); -} - -static GtkWidget * -make_refresh_rate_row (CcDisplayPanel *panel, CcDisplayMonitor *output) -{ - GtkWidget *row, *label, *heading_label, *popover; - - label = gtk_label_new (get_frequency_string (cc_display_monitor_get_mode (output))); - gtk_widget_show (label); - popover = make_refresh_rate_popover (panel); - gtk_popover_set_relative_to (GTK_POPOVER (popover), label); - - heading_label = gtk_label_new (_("Refresh Rate")); - gtk_widget_show (heading_label); - row = make_row (panel->rows_size_group, heading_label, label); - g_signal_connect_object (row, "activated", G_CALLBACK (gtk_popover_popup), - popover, G_CONNECT_SWAPPED); - g_signal_connect_object (output, "mode", G_CALLBACK (refresh_rate_row_sync), - popover, G_CONNECT_SWAPPED); - - g_signal_connect_object (output, "mode", G_CALLBACK (refresh_rate_row_sync_visibility), - row, G_CONNECT_SWAPPED); - refresh_rate_row_sync_visibility (row, output); - - return row; -} - -static guint -n_supported_scales (CcDisplayMode *mode) -{ - const double *scales = cc_display_mode_get_supported_scales (mode); - guint n = 0; - - while (scales[n] != 0.0 && display_mode_supported_at_scale (mode, scales[n])) - n++; - - return n; -} - -static gboolean -should_show_scale_row (CcDisplayMonitor *output) -{ - CcDisplayMode *mode = cc_display_monitor_get_mode (output); - return mode ? n_supported_scales (mode) > 1 : FALSE; -} - -static void -scale_row_sync_visibility (GtkWidget *row, - CcDisplayMonitor *output) -{ - if (!should_show_scale_row (output)) - gtk_widget_hide (row); - else - gtk_widget_show (row); -} - -static void -scale_buttons_active (CcDisplayPanel *panel, - GParamSpec *pspec, - GtkWidget *button) -{ - double scale = *(double*) g_object_get_data (G_OBJECT (button), "scale"); - - if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (button))) - { - cc_display_monitor_set_scale (panel->current_output, scale); - cc_display_config_snap_output (panel->current_config, panel->current_output); - update_apply_button (panel); - } -} - -static double -round_scale_for_ui (double scale) -{ - /* Keep in sync with mutter */ - return round (scale*4)/4; -} - -static GtkWidget * -make_label_for_scale (double scale) -{ - g_autofree gchar *text = g_strdup_printf (" %d %% ", (int) (round_scale_for_ui (scale)*100)); - return gtk_label_new (text); -} - -static void -scale_buttons_sync (GtkWidget *bbox, - CcDisplayMonitor *output) -{ - g_autoptr(GList) children; - GList *l; - - children = gtk_container_get_children (GTK_CONTAINER (bbox)); - for (l = children; l; l = l->next) - { - GtkWidget *button = l->data; - double scale = *(double*) g_object_get_data (G_OBJECT (button), "scale"); - if (scale == cc_display_monitor_get_scale (output)) - gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (button), TRUE); - } -} - -#define MAX_N_SCALES 5 -static void -setup_scale_buttons (GtkWidget *bbox, - CcDisplayMonitor *output) -{ - CcDisplayPanel *panel; - GtkRadioButton *group; - CcDisplayMode *mode; - const double *scales, *scale; - guint i; - - panel = g_object_get_data (G_OBJECT (bbox), "panel"); - - gtk_container_foreach (GTK_CONTAINER (bbox), (GtkCallback) gtk_widget_destroy, NULL); - - mode = cc_display_monitor_get_mode (output); - if (!mode) - return; - - scales = cc_display_mode_get_supported_scales (mode); - group = NULL; - for (scale = scales, i = 0; *scale != 0.0 && i < MAX_N_SCALES; scale++, i++) - { - GtkWidget *button, *label; - - if (!display_mode_supported_at_scale (mode, *scale)) - continue; - - button = gtk_radio_button_new_from_widget (group); - gtk_widget_show (button); - label = make_label_for_scale (*scale); - gtk_widget_show (label); - gtk_button_set_image (GTK_BUTTON (button), label); - gtk_toggle_button_set_mode (GTK_TOGGLE_BUTTON (button), FALSE); - - g_object_set_data_full (G_OBJECT (button), "scale", g_memdup (scale, sizeof (double)), g_free); - - g_signal_connect_object (button, "notify::active", G_CALLBACK (scale_buttons_active), - panel, G_CONNECT_SWAPPED); - gtk_container_add (GTK_CONTAINER (bbox), button); - group = GTK_RADIO_BUTTON (button); - } - - scale_buttons_sync (bbox, output); - - gtk_widget_show (bbox); -} -#undef MAX_N_SCALES - -static GtkWidget * -make_scale_row (CcDisplayPanel *panel, CcDisplayMonitor *output) -{ - GtkWidget *row, *bbox, *label; - - label = gtk_label_new (_("Scale")); - gtk_widget_show (label); - - bbox = gtk_button_box_new (GTK_ORIENTATION_HORIZONTAL); - gtk_widget_show (bbox); - gtk_widget_set_valign (bbox, GTK_ALIGN_CENTER); - gtk_button_box_set_layout (GTK_BUTTON_BOX (bbox), GTK_BUTTONBOX_EXPAND); - - row = make_row (panel->rows_size_group, label, bbox); - gtk_widget_set_margin_top (gtk_bin_get_child (GTK_BIN (row)), 0); - gtk_widget_set_margin_bottom (gtk_bin_get_child (GTK_BIN (row)), 0); - gtk_list_box_row_set_activatable (GTK_LIST_BOX_ROW (row), FALSE); - - g_object_set_data (G_OBJECT (bbox), "panel", panel); - g_signal_connect_object (output, "mode", G_CALLBACK (setup_scale_buttons), - bbox, G_CONNECT_SWAPPED); - g_signal_connect_object (output, "scale", G_CALLBACK (scale_buttons_sync), - bbox, G_CONNECT_SWAPPED); - setup_scale_buttons (bbox, output); - - g_signal_connect_object (output, "mode", G_CALLBACK (scale_row_sync_visibility), - row, G_CONNECT_SWAPPED); - scale_row_sync_visibility (row, output); - - return row; -} - -static void -underscanning_switch_active (CcDisplayPanel *panel, - GParamSpec *pspec, - GtkWidget *button) -{ - cc_display_monitor_set_underscanning (panel->current_output, - gtk_switch_get_active (GTK_SWITCH (button))); - cc_display_config_snap_output (panel->current_config, panel->current_output); - update_apply_button (panel); -} - -static GtkWidget * -make_underscanning_row (CcDisplayPanel *panel, - CcDisplayMonitor *output) -{ - GtkWidget *button, *label; - - button = gtk_switch_new (); - gtk_widget_show (button); - gtk_switch_set_active (GTK_SWITCH (button), - cc_display_monitor_get_underscanning (output)); - g_signal_connect_object (button, "notify::active", G_CALLBACK (underscanning_switch_active), - panel, G_CONNECT_SWAPPED); - - label = gtk_label_new (_("Adjust for TV")); - gtk_widget_show (label); - - return make_row (panel->rows_size_group, label, button); -} - -static gint -sort_modes_by_area_desc (CcDisplayMode *a, CcDisplayMode *b) -{ - int wa, ha, wb, hb; - - cc_display_mode_get_resolution (a, &wa, &ha); - cc_display_mode_get_resolution (b, &wb, &hb); - - return wb*hb - wa*ha; -} - -static gint -sort_modes_by_freq_desc (CcDisplayMode *a, CcDisplayMode *b) -{ - double delta = (cc_display_mode_get_freq_f (b) - cc_display_mode_get_freq_f (a))*1000.; - return delta; -} - -static void -ensure_res_freqs (CcDisplayMonitor *output) -{ - GHashTable *res_freqs; - GHashTableIter iter; - GList *resolutions, *modes, *m; - - if (g_object_get_data (G_OBJECT (output), "res-freqs")) - return; - - res_freqs = g_hash_table_new_full (g_str_hash, g_str_equal, - NULL, (GDestroyNotify) g_list_free); - resolutions = NULL; - - modes = cc_display_monitor_get_modes (output); - for (m = modes; m; m = m->next) - { - CcDisplayMode *mode = m->data; - const gchar *resolution = get_resolution_string (mode); - GList *l, *exist; - - exist = l = g_hash_table_lookup (res_freqs, resolution); - l = g_list_append (l, mode); - if (!exist) - g_hash_table_insert (res_freqs, (gpointer) resolution, l); - } - - g_hash_table_iter_init (&iter, res_freqs); - while (g_hash_table_iter_next (&iter, NULL, (gpointer *) &modes)) - { - modes = g_list_copy (modes); - modes = g_list_sort (modes, (GCompareFunc) sort_modes_by_freq_desc); - g_hash_table_iter_replace (&iter, modes); - - resolutions = g_list_prepend (resolutions, g_list_nth_data (modes, 0)); - } - - resolutions = g_list_sort (resolutions, (GCompareFunc) sort_modes_by_area_desc); - - g_object_set_data_full (G_OBJECT (output), "res-freqs", - res_freqs, (GDestroyNotify) g_hash_table_destroy); - g_object_set_data_full (G_OBJECT (output), "res-list", - resolutions, (GDestroyNotify) g_list_free); -} - -static GtkWidget * -make_output_ui (CcDisplayPanel *panel) +udpate_display_settings (GtkWidget *frame, + CcDisplayPanel *panel) { - GtkWidget *listbox, *row; - - ensure_res_freqs (panel->current_output); - - listbox = make_list_box (); - - if (should_show_rotation (panel, panel->current_output)) - { - row = make_orientation_row (panel, panel->current_output); - gtk_widget_show (row); - gtk_container_add (GTK_CONTAINER (listbox), - row); - } - - row = make_resolution_row (panel, panel->current_output); - gtk_widget_show (row); - gtk_container_add (GTK_CONTAINER (listbox), row); - - row = make_scale_row (panel, panel->current_output); - gtk_container_add (GTK_CONTAINER (listbox), row); - - row = make_refresh_rate_row (panel, panel->current_output); - gtk_container_add (GTK_CONTAINER (listbox), row); - - if (cc_display_monitor_supports_underscanning (panel->current_output)) - { - row = make_underscanning_row (panel, panel->current_output); - gtk_widget_show (row); - gtk_container_add (GTK_CONTAINER (listbox), row); - } - - return listbox; + cc_display_settings_set_selected_output (panel->settings, panel->current_output); } static GtkWidget * @@ -1122,9 +551,17 @@ make_single_output_ui (CcDisplayPanel *panel) gtk_widget_show (frame); gtk_container_add (GTK_CONTAINER (vbox), frame); - ui = make_output_ui (panel); - gtk_widget_show (ui); - gtk_container_add (GTK_CONTAINER (frame), ui); + panel->settings = cc_display_settings_new (); + cc_display_settings_set_config (panel->settings, panel->current_config); + cc_display_settings_set_has_accelerometer (panel->settings, panel->has_accelerometer); + cc_display_settings_set_selected_output (panel->settings, panel->current_output); + gtk_widget_show (GTK_WIDGET (panel->settings)); + gtk_container_add (GTK_CONTAINER (frame), GTK_WIDGET (panel->settings)); + g_signal_connect_object (panel, "current-output", G_CALLBACK (udpate_display_settings), + frame, G_CONNECT_SWAPPED); + g_signal_connect_object (panel->settings, "updated", + G_CALLBACK (on_monitor_settings_updated_cb), panel, + G_CONNECT_SWAPPED); ui = make_night_light_widget (panel); gtk_widget_show (ui); @@ -1271,23 +708,6 @@ make_primary_chooser_row (CcDisplayPanel *panel) return row; } -static void -replace_current_output_ui (GtkWidget *frame, - CcDisplayPanel *panel) -{ - GtkWidget *ui; - - panel->rows_size_group = gtk_size_group_new (GTK_SIZE_GROUP_BOTH); - - gtk_widget_destroy (gtk_bin_get_child (GTK_BIN (frame))); - ui = make_output_ui (panel); - gtk_widget_show (ui); - gtk_container_add (GTK_CONTAINER (frame), ui); - gtk_widget_show (frame); - - g_clear_object (&panel->rows_size_group); -} - static GtkWidget * make_arrangement_ui (CcDisplayPanel *panel) { @@ -1398,11 +818,17 @@ make_two_join_ui (CcDisplayPanel *panel) gtk_widget_set_margin_top (frame, HEADING_PADDING); gtk_container_add (GTK_CONTAINER (vbox), frame); - ui = make_output_ui (panel); - gtk_widget_show (ui); - gtk_container_add (GTK_CONTAINER (frame), ui); - g_signal_connect_object (panel, "current-output", G_CALLBACK (replace_current_output_ui), + panel->settings = cc_display_settings_new (); + cc_display_settings_set_config (panel->settings, panel->current_config); + cc_display_settings_set_has_accelerometer (panel->settings, panel->has_accelerometer); + cc_display_settings_set_selected_output (panel->settings, panel->current_output); + gtk_widget_show (GTK_WIDGET (panel->settings)); + gtk_container_add (GTK_CONTAINER (frame), GTK_WIDGET (panel->settings)); + g_signal_connect_object (panel, "current-output", G_CALLBACK (udpate_display_settings), frame, G_CONNECT_SWAPPED); + g_signal_connect_object (panel->settings, "updated", + G_CALLBACK (on_monitor_settings_updated_cb), panel, + G_CONNECT_SWAPPED); ui = make_night_light_widget (panel); gtk_widget_show (ui); @@ -1464,11 +890,17 @@ make_two_single_ui (CcDisplayPanel *panel) gtk_widget_set_margin_top (frame, HEADING_PADDING); gtk_container_add (GTK_CONTAINER (vbox), frame); - ui = make_output_ui (panel); - gtk_widget_show (ui); - gtk_container_add (GTK_CONTAINER (frame), ui); - g_signal_connect_object (panel, "current-output", G_CALLBACK (replace_current_output_ui), + panel->settings = cc_display_settings_new (); + cc_display_settings_set_config (panel->settings, panel->current_config); + cc_display_settings_set_has_accelerometer (panel->settings, panel->has_accelerometer); + cc_display_settings_set_selected_output (panel->settings, panel->current_output); + gtk_widget_show (GTK_WIDGET (panel->settings)); + gtk_container_add (GTK_CONTAINER (frame), GTK_WIDGET (panel->settings)); + g_signal_connect_object (panel, "current-output", G_CALLBACK (udpate_display_settings), frame, G_CONNECT_SWAPPED); + g_signal_connect_object (panel->settings, "updated", + G_CALLBACK (on_monitor_settings_updated_cb), panel, + G_CONNECT_SWAPPED); ui = make_night_light_widget (panel); gtk_widget_show (ui); @@ -1478,112 +910,33 @@ make_two_single_ui (CcDisplayPanel *panel) return vbox; } -static void -mirror_resolution_row_activated (CcDisplayPanel *panel, - GtkListBoxRow *row) -{ - CcDisplayMode *mode = g_object_get_data (G_OBJECT (row), "mode"); - - cc_display_config_set_mode_on_all_outputs (panel->current_config, mode); - update_apply_button (panel); -} - static GtkWidget * -make_mirror_resolution_popover (CcDisplayPanel *panel) -{ - GtkWidget *listbox; - GList *resolutions, *l; - - resolutions = g_object_get_data (G_OBJECT (panel->current_config), "mirror-res-list"); - - listbox = make_list_box (); - gtk_widget_show (listbox); - g_object_set (listbox, "margin", 12, NULL); - - for (l = resolutions; l; l = l->next) - { - CcDisplayMode *mode = l->data; - GtkWidget *label, *row; - - label = make_popover_label (get_resolution_string (mode)); - gtk_widget_show (label); - row = g_object_new (CC_TYPE_LIST_BOX_ROW, - "child", label, - NULL); - gtk_widget_show (row); - g_object_set_data (G_OBJECT (row), "mode", mode); - - g_signal_connect_object (row, "activated", G_CALLBACK (mirror_resolution_row_activated), - panel, G_CONNECT_SWAPPED); - gtk_container_add (GTK_CONTAINER (listbox), row); - } - - return make_list_popover (listbox); -} - -static GtkWidget * -make_mirror_resolution_row (CcDisplayPanel *panel, - CcDisplayMonitor *output) -{ - GtkWidget *row, *label, *heading_label, *popover; - - label = gtk_label_new (get_resolution_string (cc_display_monitor_get_mode (output))); - gtk_widget_show (label); - popover = make_mirror_resolution_popover (panel); - gtk_popover_set_relative_to (GTK_POPOVER (popover), label); - - heading_label = gtk_label_new (_("Resolution")); - gtk_widget_show (heading_label); - row = make_row (panel->rows_size_group, heading_label, label); - g_signal_connect_object (row, "activated", G_CALLBACK (gtk_popover_popup), - popover, G_CONNECT_SWAPPED); - g_signal_connect_object (output, "mode", G_CALLBACK (resolution_row_sync), - popover, G_CONNECT_SWAPPED); - return row; -} - -static void -ensure_mirror_res_list (CcDisplayConfig *config) +make_two_mirror_ui (CcDisplayPanel *panel) { - GHashTable *res_set; - GList *resolutions, *l; - - if (g_object_get_data (G_OBJECT (config), "mirror-res-list")) - return; - - res_set = g_hash_table_new (g_str_hash, g_str_equal); + GtkWidget *vbox, *frame, *ui; - resolutions = cc_display_config_get_cloning_modes (config); - for (l = resolutions; l; l = l->next) + if (!cc_display_config_is_cloning (panel->current_config)) { - CcDisplayMode *mode = l->data; - const gchar *resolution = get_resolution_string (mode); - if (!g_hash_table_contains (res_set, resolution)) - g_hash_table_insert (res_set, (gpointer) resolution, mode); - } - - resolutions = g_hash_table_get_values (res_set); - g_hash_table_destroy (res_set); + GList *modes = cc_display_config_get_cloning_modes (panel->current_config); + gint bw, bh; + CcDisplayMode *best = NULL; - resolutions = g_list_sort (resolutions, (GCompareFunc) sort_modes_by_area_desc); + while (modes) + { + CcDisplayMode *mode = modes->data; + gint w, h; - g_object_set_data_full (G_OBJECT (config), "mirror-res-list", - resolutions, (GDestroyNotify) g_list_free); -} + cc_display_mode_get_resolution (mode, &w, &h); + if (best == NULL || (bw*bh < w*h)) + { + best = mode; + cc_display_mode_get_resolution (best, &bw, &bh); + } -static GtkWidget * -make_two_mirror_ui (CcDisplayPanel *panel) -{ - GtkWidget *vbox, *listbox, *frame, *row, *ui; - - ensure_mirror_res_list (panel->current_config); - if (!cc_display_config_is_cloning (panel->current_config)) - { - GList *modes; + modes = modes->next; + } cc_display_config_set_cloning (panel->current_config, TRUE); - modes = g_object_get_data (G_OBJECT (panel->current_config), "mirror-res-list"); - cc_display_config_set_mode_on_all_outputs (panel->current_config, - CC_DISPLAY_MODE (g_list_nth_data (modes, 0))); + cc_display_config_set_mode_on_all_outputs (panel->current_config, best); } panel->rows_size_group = gtk_size_group_new (GTK_SIZE_GROUP_BOTH); @@ -1592,20 +945,17 @@ make_two_mirror_ui (CcDisplayPanel *panel) frame = make_frame (NULL, NULL); gtk_widget_show (frame); gtk_container_add (GTK_CONTAINER (vbox), frame); - listbox = make_list_box (); - gtk_widget_show (listbox); - gtk_container_add (GTK_CONTAINER (frame), listbox); - - if (should_show_rotation (panel, panel->current_output)) - { - ui = make_orientation_row (panel, panel->current_output); - gtk_widget_show (ui); - gtk_container_add (GTK_CONTAINER (listbox), ui); - } - - row = make_mirror_resolution_row (panel, panel->current_output); - gtk_widget_show (row); - gtk_container_add (GTK_CONTAINER (listbox), row); + panel->settings = cc_display_settings_new (); + cc_display_settings_set_config (panel->settings, panel->current_config); + cc_display_settings_set_has_accelerometer (panel->settings, panel->has_accelerometer); + cc_display_settings_set_selected_output (panel->settings, panel->current_output); + gtk_widget_show (GTK_WIDGET (panel->settings)); + gtk_container_add (GTK_CONTAINER (frame), GTK_WIDGET (panel->settings)); + g_signal_connect_object (panel, "current-output", G_CALLBACK (udpate_display_settings), + frame, G_CONNECT_SWAPPED); + g_signal_connect_object (panel->settings, "updated", + G_CALLBACK (on_monitor_settings_updated_cb), panel, + G_CONNECT_SWAPPED); ui = make_night_light_widget (panel); gtk_widget_show (ui); @@ -1925,11 +1275,17 @@ make_multi_output_ui (CcDisplayPanel *panel) gtk_widget_set_margin_top (frame, HEADING_PADDING); gtk_container_add (GTK_CONTAINER (vbox), frame); - ui = make_output_ui (panel); - gtk_widget_show (ui); - gtk_container_add (GTK_CONTAINER (frame), ui); - g_signal_connect_object (panel, "current-output", G_CALLBACK (replace_current_output_ui), + panel->settings = cc_display_settings_new (); + cc_display_settings_set_config (panel->settings, panel->current_config); + cc_display_settings_set_has_accelerometer (panel->settings, panel->has_accelerometer); + cc_display_settings_set_selected_output (panel->settings, panel->current_output); + gtk_widget_show (GTK_WIDGET (panel->settings)); + gtk_container_add (GTK_CONTAINER (frame), GTK_WIDGET (panel->settings)); + g_signal_connect_object (panel, "current-output", G_CALLBACK (udpate_display_settings), frame, G_CONNECT_SWAPPED); + g_signal_connect_object (panel->settings, "updated", + G_CALLBACK (on_monitor_settings_updated_cb), panel, + G_CONNECT_SWAPPED); ui = make_night_light_widget (panel); gtk_widget_show (ui); @@ -2122,130 +1478,6 @@ apply_current_configuration (CcDisplayPanel *self) g_warning ("Error applying configuration: %s", error->message); } -static const gchar * -make_aspect_string (gint width, - gint height) -{ - int ratio; - const gchar *aspect = NULL; - - /* We use a number of Unicode characters below: - * ∶ is U+2236 RATIO - *   is U+2009 THIN SPACE, - * × is U+00D7 MULTIPLICATION SIGN - */ - if (width && height) { - if (width > height) - ratio = width * 10 / height; - else - ratio = height * 10 / width; - - switch (ratio) { - case 13: - aspect = "4∶3"; - break; - case 16: - aspect = "16∶10"; - break; - case 17: - aspect = "16∶9"; - break; - case 23: - aspect = "21∶9"; - break; - case 12: - aspect = "5∶4"; - break; - /* This catches 1.5625 as well (1600x1024) when maybe it shouldn't. */ - case 15: - aspect = "3∶2"; - break; - case 18: - aspect = "9∶5"; - break; - case 10: - aspect = "1∶1"; - break; - } - } - - return aspect; -} - -static char * -make_resolution_string (CcDisplayMode *mode) -{ - const char *interlaced = cc_display_mode_is_interlaced (mode) ? "i" : ""; - const char *aspect; - int width, height; - - cc_display_mode_get_resolution (mode, &width, &height); - aspect = make_aspect_string (width, height); - - if (aspect != NULL) - return g_strdup_printf ("%d × %d%s (%s)", width, height, interlaced, aspect); - else - return g_strdup_printf ("%d × %d%s", width, height, interlaced); -} - -static const gchar * -get_resolution_string (CcDisplayMode *mode) -{ - char *resolution; - - if (!mode) - return ""; - - resolution = g_object_get_data (G_OBJECT (mode), "resolution"); - if (resolution) - return resolution; - - resolution = make_resolution_string (mode); - g_object_set_data_full (G_OBJECT (mode), "resolution", resolution, g_free); - return resolution; -} - -static const gchar * -get_frequency_string (CcDisplayMode *mode) -{ - char *frequency; - - if (!mode) - return ""; - - frequency = g_object_get_data (G_OBJECT (mode), "frequency"); - if (frequency) - return frequency; - - frequency = g_strdup_printf (_("%.2lf Hz"), cc_display_mode_get_freq_f (mode)); - - g_object_set_data_full (G_OBJECT (mode), "frequency", frequency, g_free); - return frequency; -} - -static gboolean -should_show_rotation (CcDisplayPanel *panel, - CcDisplayMonitor *output) -{ - gboolean supports_rotation; - - supports_rotation = cc_display_monitor_supports_rotation (output, - CC_DISPLAY_ROTATION_90 | - CC_DISPLAY_ROTATION_180 | - CC_DISPLAY_ROTATION_270); - - /* Doesn't support rotation at all */ - if (!supports_rotation) - return FALSE; - - /* We can always rotate displays that aren't builtin */ - if (!cc_display_monitor_is_builtin (output)) - return TRUE; - - /* Only offer rotation if there's no accelerometer */ - return !panel->has_accelerometer; -} - static void cc_display_panel_night_light_activated (CcDisplayPanel *panel) { -- GitLab From d9a7c849054f5ce2c3f81b15b61b51081193f091 Mon Sep 17 00:00:00 2001 From: Benjamin Berg Date: Sat, 19 Jan 2019 23:36:24 +0100 Subject: [PATCH 13/13] display: Rewrite as a template widget --- panels/display/cc-display-panel.c | 1543 +++++++++----------------- panels/display/cc-display-panel.ui | 538 +++++++++ panels/display/display.gresource.xml | 1 + panels/display/meson.build | 1 + 4 files changed, 1042 insertions(+), 1041 deletions(-) create mode 100644 panels/display/cc-display-panel.ui diff --git a/panels/display/cc-display-panel.c b/panels/display/cc-display-panel.c index f42b2f7032..bb574c5c9d 100644 --- a/panels/display/cc-display-panel.c +++ b/panels/display/cc-display-panel.c @@ -26,6 +26,9 @@ #include #include +#define HANDY_USE_UNSTABLE_API 1 +#include + #include "shell/cc-object-storage.h" #include "list-box-helper.h" #include @@ -45,14 +48,12 @@ #define SECTION_PADDING 32 #define HEADING_PADDING 12 -enum -{ - DISPLAY_MODE_PRIMARY, - DISPLAY_MODE_SECONDARY, - /* DISPLAY_MODE_PRESENTATION, */ - DISPLAY_MODE_MIRROR, - DISPLAY_MODE_OFF -}; +typedef enum { + /*< flags >*/ + CC_DISPLAY_CONFIG_SINGLE = 0x1, + CC_DISPLAY_CONFIG_JOIN = 0x2, + CC_DISPLAY_CONFIG_CLONE = 0x4, +} CcDisplayConfigType; struct _CcDisplayPanel { @@ -62,15 +63,13 @@ struct _CcDisplayPanel CcDisplayConfig *current_config; CcDisplayMonitor *current_output; + gboolean rebuilding; + CcDisplayArrangement *arrangement; CcDisplaySettings *settings; guint focus_id; - GtkSizeGroup *main_size_group; - GtkSizeGroup *rows_size_group; - GtkWidget *stack; - CcNightLightDialog *night_light_dialog; GSettings *settings_color; @@ -89,33 +88,213 @@ struct _CcDisplayPanel GtkWidget *apply_titlebar; GtkWidget *apply_titlebar_apply; GtkWidget *apply_titlebar_warning; + + GListStore *primary_display_list; + GtkListStore *output_selection_list; + + GtkWidget *arrangement_frame; + GtkAlignment *arrangement_bin; + GtkRadioButton *config_type_join; + GtkRadioButton *config_type_mirror; + GtkRadioButton *config_type_single; + GtkWidget *config_type_switcher_frame; + GtkLabel *current_output_label; + GtkWidget *display_settings_frame; + GtkLabel *night_light_status_label; + GtkSwitch *output_enabled_switch; + GtkComboBox *output_selection_combo; + GtkStack *output_selection_stack; + GtkButtonBox *output_selection_two_first; + GtkButtonBox *output_selection_two_second; + HdyComboRow *primary_display_row; }; CC_PANEL_REGISTER (CcDisplayPanel, cc_display_panel) -typedef struct -{ - int grab_x; - int grab_y; - int output_x; - int output_y; -} GrabInfo; - -enum -{ - CURRENT_OUTPUT, - LAST_PANEL_SIGNAL -}; -static guint panel_signals[LAST_PANEL_SIGNAL] = { 0 }; - -static GtkWidget* -make_night_light_widget (CcDisplayPanel *panel); static void update_apply_button (CcDisplayPanel *panel); static void apply_current_configuration (CcDisplayPanel *self); static void reset_current_config (CcDisplayPanel *panel); +static void +rebuild_ui (CcDisplayPanel *panel); +static void +set_current_output (CcDisplayPanel *panel, + CcDisplayMonitor *output, + gboolean force); + + +static CcDisplayConfigType +config_find_types (CcDisplayPanel *panel) +{ + CcDisplayConfigType types = 0; + guint n_outputs, n_active_outputs; + GList *outputs, *l; + + outputs = cc_display_config_get_ui_sorted_monitors (panel->current_config); + n_outputs = g_list_length (outputs); + n_active_outputs = 0; + for (l = outputs; l; l = l->next) + { + CcDisplayMonitor *output = l->data; + + if (cc_display_monitor_is_useful (output)) + n_active_outputs += 1; + } + + if (n_outputs > (panel->lid_is_closed ? 2 : 1)) + { + if (cc_display_config_is_cloning (panel->current_config)) + types |= CC_DISPLAY_CONFIG_CLONE; + else + { + types |= CC_DISPLAY_CONFIG_JOIN; + + if (n_active_outputs == 1) + types |= CC_DISPLAY_CONFIG_SINGLE; + } + } + else + { + types |= CC_DISPLAY_CONFIG_SINGLE; + } + + return types; +} + +static CcDisplayConfigType +config_select_type (CcDisplayPanel *panel) +{ + CcDisplayConfigType types = config_find_types (panel); + + if (types & CC_DISPLAY_CONFIG_CLONE) + return CC_DISPLAY_CONFIG_CLONE; + if (types & CC_DISPLAY_CONFIG_SINGLE) + return CC_DISPLAY_CONFIG_SINGLE; + + return CC_DISPLAY_CONFIG_JOIN; +} + +static CcDisplayConfigType +cc_panel_get_selected_type (CcDisplayPanel *panel) +{ + if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (panel->config_type_join))) + return CC_DISPLAY_CONFIG_JOIN; + else if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (panel->config_type_mirror))) + return CC_DISPLAY_CONFIG_CLONE; + else if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (panel->config_type_single))) + return CC_DISPLAY_CONFIG_SINGLE; + else + g_assert_not_reached (); +} + +static void +cc_panel_set_selected_type (CcDisplayPanel *panel, CcDisplayConfigType type) +{ + switch (type) + { + case CC_DISPLAY_CONFIG_JOIN: + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (panel->config_type_join), TRUE); + break; + case CC_DISPLAY_CONFIG_CLONE: + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (panel->config_type_mirror), TRUE); + break; + case CC_DISPLAY_CONFIG_SINGLE: + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (panel->config_type_single), TRUE); + break; + default: + g_assert_not_reached (); + } +} + +static void +config_ensure_of_type (CcDisplayPanel *panel, CcDisplayConfigType type) +{ + CcDisplayConfigType types = config_find_types (panel); + GList *outputs, *l; + + if (type == CC_DISPLAY_CONFIG_SINGLE && (types & CC_DISPLAY_CONFIG_SINGLE)) + return; + + /* Already compatible and not just "single" mode. */ + if (!(types & CC_DISPLAY_CONFIG_SINGLE) && (types & type)) + return; + + reset_current_config (panel); + + outputs = cc_display_config_get_ui_sorted_monitors (panel->current_config); + + switch (type) + { + case CC_DISPLAY_CONFIG_SINGLE: + g_debug ("Creating new single config"); + /* Disable all but the current primary output */ + cc_display_config_set_cloning (panel->current_config, FALSE); + for (l = outputs; l; l = l->next) + { + CcDisplayMonitor *output = l->data; + + /* Select the current primary output as the active one */ + if (cc_display_monitor_is_primary (output)) + { + cc_display_monitor_set_active (output, TRUE); + set_current_output (panel, output, FALSE); + } + else + { + cc_display_monitor_set_active (output, FALSE); + } + } + break; + + case CC_DISPLAY_CONFIG_JOIN: + g_debug ("Creating new join config"); + /* Enable all usable outputs */ + cc_display_config_set_cloning (panel->current_config, FALSE); + for (l = outputs; l; l = l->next) + { + CcDisplayMonitor *output = l->data; + + cc_display_monitor_set_active (output, cc_display_monitor_is_usable (output)); + break; + } + break; + + case CC_DISPLAY_CONFIG_CLONE: + { + g_debug ("Creating new clone config"); + GList *modes = cc_display_config_get_cloning_modes (panel->current_config); + gint bw, bh; + CcDisplayMode *best = NULL; + + /* Turn on cloning and select the best mode we can find by default */ + cc_display_config_set_cloning (panel->current_config, TRUE); + + while (modes) + { + CcDisplayMode *mode = modes->data; + gint w, h; + + cc_display_mode_get_resolution (mode, &w, &h); + if (best == NULL || (bw*bh < w*h)) + { + best = mode; + cc_display_mode_get_resolution (best, &bw, &bh); + } + + modes = modes->next; + } + cc_display_config_set_mode_on_all_outputs (panel->current_config, best); + } + break; + + default: + g_assert_not_reached (); + } + + rebuild_ui (panel); +} static void monitor_labeler_hide (CcDisplayPanel *self) @@ -265,7 +444,6 @@ cc_display_panel_dispose (GObject *object) g_clear_object (&self->current_config); g_clear_object (&self->up_client); g_clear_object (&self->settings_color); - g_clear_object (&self->main_size_group); g_cancellable_cancel (self->shell_cancellable); g_clear_object (&self->shell_cancellable); @@ -276,6 +454,12 @@ cc_display_panel_dispose (GObject *object) G_OBJECT_CLASS (cc_display_panel_parent_class)->dispose (object); } +static void +on_arrangement_selected_ouptut_changed_cb (CcDisplayPanel *panel) +{ + set_current_output (panel, cc_display_arrangement_get_selected_output (panel->arrangement), FALSE); +} + static void on_monitor_settings_updated_cb (CcDisplayPanel *panel, CcDisplayMonitor *monitor, @@ -287,1098 +471,387 @@ on_monitor_settings_updated_cb (CcDisplayPanel *panel, } static void -cc_display_panel_constructed (GObject *object) -{ - g_signal_connect_object (cc_panel_get_shell (CC_PANEL (object)), "notify::active-panel", - G_CALLBACK (active_panel_changed), object, 0); - - G_OBJECT_CLASS (cc_display_panel_parent_class)->constructed (object); -} - -static const char * -cc_display_panel_get_help_uri (CcPanel *panel) +on_config_type_toggled_cb (CcDisplayPanel *panel, + GtkRadioButton *btn) { - return "help:gnome-help/prefs-display"; -} + CcDisplayConfigType type; -static void -cc_display_panel_class_init (CcDisplayPanelClass *klass) -{ - GObjectClass *object_class = G_OBJECT_CLASS (klass); - CcPanelClass *panel_class = CC_PANEL_CLASS (klass); + if (panel->rebuilding) + return; - panel_class->get_help_uri = cc_display_panel_get_help_uri; + if (!panel->current_config) + return; - object_class->constructed = cc_display_panel_constructed; - object_class->dispose = cc_display_panel_dispose; + if (!gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (btn))) + return; - panel_signals[CURRENT_OUTPUT] = - g_signal_new ("current-output", - CC_TYPE_DISPLAY_PANEL, - G_SIGNAL_RUN_LAST, - 0, NULL, NULL, NULL, - G_TYPE_NONE, 0); + type = cc_panel_get_selected_type (panel); + config_ensure_of_type (panel, type); } static void -set_current_output (CcDisplayPanel *panel, - CcDisplayMonitor *output) -{ - panel->current_output = output; - g_signal_emit (panel, panel_signals[CURRENT_OUTPUT], 0); -} - -static GtkWidget * -make_bin (void) -{ - return g_object_new (GTK_TYPE_FRAME, "shadow-type", GTK_SHADOW_NONE, NULL); -} - -static GtkWidget * -wrap_in_boxes (GtkWidget *widget) -{ - GtkWidget *box, *bin; - - box = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, PANEL_PADDING); - bin = make_bin (); - gtk_widget_show (bin); - gtk_box_pack_start (GTK_BOX (box), bin, TRUE, TRUE, 0); - gtk_box_pack_start (GTK_BOX (box), widget, TRUE, TRUE, 0); - bin = make_bin (); - gtk_widget_show (bin); - gtk_box_pack_start (GTK_BOX (box), bin, TRUE, TRUE, 0); - return box; -} - -static GtkWidget * -make_scrollable (GtkWidget *widget) +on_night_light_list_box_row_activated_cb (CcDisplayPanel *panel) { - GtkWidget *sw, *box; - sw = g_object_new (GTK_TYPE_SCROLLED_WINDOW, - "hscrollbar-policy", GTK_POLICY_NEVER, - "min-content-height", 450, - "propagate-natural-height", TRUE, - NULL); - box = wrap_in_boxes (widget); - gtk_widget_show (box); - gtk_container_add (GTK_CONTAINER (sw), box); - return sw; -} - -static GtkWidget * -make_bold_label (const gchar *text) -{ - g_autoptr(GtkCssProvider) provider = NULL; - GtkWidget *label = gtk_label_new (text); - - provider = gtk_css_provider_new (); - gtk_css_provider_load_from_data (GTK_CSS_PROVIDER (provider), - "label { font-weight: bold; }", -1, NULL); - gtk_style_context_add_provider (gtk_widget_get_style_context (label), - GTK_STYLE_PROVIDER (provider), - GTK_STYLE_PROVIDER_PRIORITY_APPLICATION); - - return label; -} - -static GtkWidget * -make_main_vbox (GtkSizeGroup *size_group) -{ - GtkWidget *vbox; - - vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 0); - gtk_widget_set_margin_top (vbox, PANEL_PADDING); - gtk_widget_set_margin_bottom (vbox, PANEL_PADDING); - - if (size_group) - gtk_size_group_add_widget (size_group, vbox); - - return vbox; -} - -static GtkWidget * -make_row (GtkSizeGroup *size_group, - GtkWidget *start_widget, - GtkWidget *end_widget) -{ - GtkWidget *row, *box; - - row = g_object_new (CC_TYPE_LIST_BOX_ROW, NULL); - - box = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 50); - gtk_widget_show (box); - gtk_widget_set_margin_start (box, 20); - gtk_widget_set_margin_end (box, 20); - gtk_widget_set_margin_top (box, 20); - gtk_widget_set_margin_bottom (box, 20); - - if (start_widget) - { - gtk_widget_set_halign (start_widget, GTK_ALIGN_START); - gtk_box_pack_start (GTK_BOX (box), start_widget, FALSE, FALSE, 0); - } - if (end_widget) - { - gtk_widget_set_halign (end_widget, GTK_ALIGN_END); - gtk_box_pack_end (GTK_BOX (box), end_widget, FALSE, FALSE, 0); - } - - gtk_container_add (GTK_CONTAINER (row), box); - - if (size_group) - gtk_size_group_add_widget (size_group, row); - - return row; -} - -static GtkWidget * -make_frame (const gchar *title, const gchar *subtitle) -{ - GtkWidget *frame; - - frame = gtk_frame_new (NULL); - gtk_frame_set_shadow_type (GTK_FRAME (frame), GTK_SHADOW_IN); - - if (title) - { - GtkWidget *vbox, *label; - - vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, HEADING_PADDING/2); - gtk_widget_show (vbox); - gtk_widget_set_margin_bottom (vbox, HEADING_PADDING); - - label = make_bold_label (title); - gtk_widget_show (label); - gtk_widget_set_halign (label, GTK_ALIGN_START); - gtk_container_add (GTK_CONTAINER (vbox), label); - - if (subtitle) - { - label = gtk_label_new (subtitle); - gtk_widget_show (label); - gtk_label_set_line_wrap (GTK_LABEL (label), TRUE); - gtk_label_set_xalign (GTK_LABEL (label), 0.0); - gtk_widget_set_halign (label, GTK_ALIGN_START); - gtk_container_add (GTK_CONTAINER (vbox), label); - gtk_style_context_add_class (gtk_widget_get_style_context (label), - GTK_STYLE_CLASS_DIM_LABEL); - } - - gtk_frame_set_label_widget (GTK_FRAME (frame), vbox); - gtk_frame_set_label_align (GTK_FRAME (frame), 0.0, 1.0); - } - - return frame; -} - -static GtkWidget * -make_list_box (void) -{ - GtkWidget *listbox; - - listbox = g_object_new (CC_TYPE_LIST_BOX, NULL); - gtk_list_box_set_selection_mode (GTK_LIST_BOX (listbox), GTK_SELECTION_NONE); - gtk_list_box_set_header_func (GTK_LIST_BOX (listbox), - cc_list_box_update_header_func, - NULL, NULL); - return listbox; + GtkWindow *toplevel; + toplevel = GTK_WINDOW (cc_shell_get_toplevel (cc_panel_get_shell (CC_PANEL (panel)))); + gtk_window_set_transient_for (GTK_WINDOW (panel->night_light_dialog), toplevel); + gtk_window_present (GTK_WINDOW (panel->night_light_dialog)); } static void -make_list_transparent (GtkWidget *listbox) +on_output_enabled_active_changed_cb (CcDisplayPanel *panel) { - g_autoptr(GtkCssProvider) provider = NULL; + gboolean active; - provider = gtk_css_provider_new (); - gtk_css_provider_load_from_data (GTK_CSS_PROVIDER (provider), - "list { border-style: none; background-color: transparent; }", -1, NULL); - gtk_style_context_add_provider (gtk_widget_get_style_context (listbox), - GTK_STYLE_PROVIDER (provider), - GTK_STYLE_PROVIDER_PRIORITY_APPLICATION); -} + if (!panel->current_output) + return; -static GtkWidget * -make_list_popover (GtkWidget *listbox) -{ - GtkWidget *popover = g_object_new (GTK_TYPE_POPOVER, - "position", GTK_POS_BOTTOM, - NULL); - GtkWidget *sw = g_object_new (GTK_TYPE_SCROLLED_WINDOW, - "hscrollbar-policy", GTK_POLICY_NEVER, - "max-content-height", 400, - "propagate-natural-height", TRUE, - NULL); - gtk_widget_show (sw); - make_list_transparent (listbox); - gtk_container_add (GTK_CONTAINER (sw), listbox); - - gtk_container_add (GTK_CONTAINER (popover), sw); - g_signal_connect_object (listbox, "row-activated", G_CALLBACK (gtk_widget_hide), - popover, G_CONNECT_SWAPPED); - return popover; -} + active = gtk_switch_get_active (panel->output_enabled_switch); -static GtkWidget * -make_popover_label (const gchar *text) -{ - return g_object_new (GTK_TYPE_LABEL, - "label", text, - "margin", 12, - "xalign", 0.0, - "width-chars", 20, - "max-width-chars", 20, - NULL); -} + if (cc_display_monitor_is_active (panel->current_output) == active) + return; -static void -udpate_display_settings (GtkWidget *frame, - CcDisplayPanel *panel) -{ - cc_display_settings_set_selected_output (panel->settings, panel->current_output); + /* Changing the active state requires a UI rebuild. */ + cc_display_monitor_set_active (panel->current_output, active); + rebuild_ui (panel); } -static GtkWidget * -make_single_output_ui (CcDisplayPanel *panel) +static void +on_output_selection_combo_changed_cb (CcDisplayPanel *panel) { - GtkWidget *vbox, *frame, *ui; - - panel->rows_size_group = gtk_size_group_new (GTK_SIZE_GROUP_BOTH); + GtkTreeIter iter; + g_autoptr(CcDisplayMonitor) output = NULL; - vbox = make_main_vbox (panel->main_size_group); - gtk_widget_show (vbox); - - frame = make_frame (cc_display_monitor_get_ui_name (panel->current_output), NULL); - gtk_widget_show (frame); - gtk_container_add (GTK_CONTAINER (vbox), frame); + if (!panel->current_config) + return; - panel->settings = cc_display_settings_new (); - cc_display_settings_set_config (panel->settings, panel->current_config); - cc_display_settings_set_has_accelerometer (panel->settings, panel->has_accelerometer); - cc_display_settings_set_selected_output (panel->settings, panel->current_output); - gtk_widget_show (GTK_WIDGET (panel->settings)); - gtk_container_add (GTK_CONTAINER (frame), GTK_WIDGET (panel->settings)); - g_signal_connect_object (panel, "current-output", G_CALLBACK (udpate_display_settings), - frame, G_CONNECT_SWAPPED); - g_signal_connect_object (panel->settings, "updated", - G_CALLBACK (on_monitor_settings_updated_cb), panel, - G_CONNECT_SWAPPED); + if (!gtk_combo_box_get_active_iter (panel->output_selection_combo, &iter)) + return; - ui = make_night_light_widget (panel); - gtk_widget_show (ui); - gtk_container_add (GTK_CONTAINER (vbox), ui); + gtk_tree_model_get (GTK_TREE_MODEL (panel->output_selection_list), &iter, + 1, &output, + -1); - g_clear_object (&panel->rows_size_group); - return make_scrollable (vbox); + set_current_output (panel, output, FALSE); } static void -arrangement_notify_selected_ouptut_cb (CcDisplayPanel *panel, - GParamSpec *pspec, - CcDisplayArrangement *arr) +on_output_selection_two_toggled_cb (CcDisplayPanel *panel, GtkRadioButton *btn) { - CcDisplayMonitor *output = cc_display_arrangement_get_selected_output (arr); + if (!gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (btn))) + return; - if (output && output != panel->current_output) - set_current_output (panel, output); + set_current_output (panel, g_object_get_data (G_OBJECT (btn), "display"), FALSE); } static void -arrangement_update_selected_output (CcDisplayArrangement *arr, - CcDisplayPanel *panel) -{ - cc_display_arrangement_set_selected_output (arr, panel->current_output); -} - -static GtkWidget * -make_arrangement_row (CcDisplayPanel *panel) +on_primary_display_selected_index_changed_cb (CcDisplayPanel *panel) { - GtkWidget *row; - CcDisplayArrangement *arr; - - arr = cc_display_arrangement_new (panel->current_config); - gtk_widget_show (GTK_WIDGET (arr)); - cc_display_arrangement_set_selected_output (arr, panel->current_output); - g_signal_connect_object (arr, "updated", - G_CALLBACK (update_apply_button), panel, - G_CONNECT_SWAPPED); - g_signal_connect_object (arr, "notify::selected-output", - G_CALLBACK (arrangement_notify_selected_ouptut_cb), panel, - G_CONNECT_SWAPPED); - g_signal_connect_object (panel, "current-output", - G_CALLBACK (arrangement_update_selected_output), arr, - G_CONNECT_SWAPPED); - - gtk_widget_set_size_request (GTK_WIDGET (arr), 400, 175); + gint idx = hdy_combo_row_get_selected_index (panel->primary_display_row); + g_autoptr(CcDisplayMonitor) output = NULL; - row = g_object_new (CC_TYPE_LIST_BOX_ROW, NULL); - gtk_list_box_row_set_activatable (GTK_LIST_BOX_ROW (row), FALSE); - gtk_container_add (GTK_CONTAINER (row), GTK_WIDGET (arr)); + if (idx < 0) + return; - return row; -} + output = g_list_model_get_item (G_LIST_MODEL (panel->primary_display_list), idx); -static void -primary_chooser_sync (GtkPopover *popover, - CcDisplayConfig *config) -{ - GtkWidget *label; - GList *outputs, *l; - - label = gtk_popover_get_relative_to (popover); - outputs = cc_display_config_get_monitors (config); - for (l = outputs; l; l = l->next) - { - CcDisplayMonitor *output = l->data; - if (cc_display_monitor_is_primary (output)) - { - const gchar *text = cc_display_monitor_get_ui_number_name (output); - gtk_label_set_text (GTK_LABEL (label), text); - return; - } - } -} - -static void -primary_chooser_row_activated (CcDisplayPanel *panel, - GtkListBoxRow *row) -{ - CcDisplayMonitor *output = g_object_get_data (G_OBJECT (row), "output"); + if (cc_display_monitor_is_primary (output)) + return; cc_display_monitor_set_primary (output, TRUE); update_apply_button (panel); } -static GtkWidget * -make_primary_chooser_popover (CcDisplayPanel *panel) +static void +cc_display_panel_constructed (GObject *object) { - GtkWidget *listbox; - GList *outputs, *l; - - outputs = cc_display_config_get_ui_sorted_monitors (panel->current_config); - - listbox = make_list_box (); - gtk_widget_show (listbox); - g_object_set (listbox, "margin", 12, NULL); - - for (l = outputs; l; l = l->next) - { - CcDisplayMonitor *output = l->data; - GtkWidget *label, *row; - const gchar *text; - - if (!cc_display_monitor_is_useful (output)) - continue; - - text = cc_display_monitor_get_ui_number_name (output); - label = make_popover_label (text); - gtk_widget_show (label); - row = g_object_new (CC_TYPE_LIST_BOX_ROW, - "child", label, - NULL); - gtk_widget_show (row); - g_object_set_data (G_OBJECT (row), "output", output); - - g_signal_connect_object (row, "activated", G_CALLBACK (primary_chooser_row_activated), - panel, G_CONNECT_SWAPPED); - gtk_container_add (GTK_CONTAINER (listbox), row); - } + g_signal_connect_object (cc_panel_get_shell (CC_PANEL (object)), "notify::active-panel", + G_CALLBACK (active_panel_changed), object, 0); - return make_list_popover (listbox); + G_OBJECT_CLASS (cc_display_panel_parent_class)->constructed (object); } -static GtkWidget * -make_primary_chooser_row (CcDisplayPanel *panel) +static const char * +cc_display_panel_get_help_uri (CcPanel *panel) { - GtkWidget *row, *label, *heading_label, *popover; - - label = gtk_label_new (NULL); - gtk_widget_show (label); - popover = make_primary_chooser_popover (panel); - gtk_popover_set_relative_to (GTK_POPOVER (popover), label); - - heading_label = gtk_label_new (_("Primary Display")); - gtk_widget_show (heading_label); - row = make_row (panel->rows_size_group, heading_label, label); - g_signal_connect_object (row, "activated", G_CALLBACK (gtk_popover_popup), - popover, G_CONNECT_SWAPPED); - g_signal_connect_object (panel->current_config, "primary", G_CALLBACK (primary_chooser_sync), - popover, G_CONNECT_SWAPPED); - primary_chooser_sync (GTK_POPOVER (popover), panel->current_config); - - return row; + return "help:gnome-help/prefs-display"; } -static GtkWidget * -make_arrangement_ui (CcDisplayPanel *panel) +static void +cc_display_panel_class_init (CcDisplayPanelClass *klass) { - GtkWidget *frame, *listbox, *row; - - frame = make_frame (_("Display Arrangement"), - _("Drag displays to match your setup. The top bar is placed on the primary display.")); - listbox = make_list_box (); - gtk_widget_show (listbox); - gtk_container_add (GTK_CONTAINER (frame), listbox); + GObjectClass *object_class = G_OBJECT_CLASS (klass); + CcPanelClass *panel_class = CC_PANEL_CLASS (klass); + GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); - row = make_arrangement_row (panel); - gtk_widget_show (row); - gtk_container_add (GTK_CONTAINER (listbox), row); + panel_class->get_help_uri = cc_display_panel_get_help_uri; - row = make_primary_chooser_row (panel); - gtk_widget_show (row); - gtk_container_add (GTK_CONTAINER (listbox), row); + object_class->constructed = cc_display_panel_constructed; + object_class->dispose = cc_display_panel_dispose; - return frame; + gtk_widget_class_set_template_from_resource (widget_class, "/org/gnome/control-center/display/cc-display-panel.ui"); + + gtk_widget_class_bind_template_child (widget_class, CcDisplayPanel, arrangement_frame); + gtk_widget_class_bind_template_child (widget_class, CcDisplayPanel, arrangement_bin); + gtk_widget_class_bind_template_child (widget_class, CcDisplayPanel, config_type_switcher_frame); + gtk_widget_class_bind_template_child (widget_class, CcDisplayPanel, config_type_join); + gtk_widget_class_bind_template_child (widget_class, CcDisplayPanel, config_type_mirror); + gtk_widget_class_bind_template_child (widget_class, CcDisplayPanel, config_type_single); + gtk_widget_class_bind_template_child (widget_class, CcDisplayPanel, current_output_label); + gtk_widget_class_bind_template_child (widget_class, CcDisplayPanel, display_settings_frame); + gtk_widget_class_bind_template_child (widget_class, CcDisplayPanel, night_light_status_label); + gtk_widget_class_bind_template_child (widget_class, CcDisplayPanel, output_enabled_switch); + gtk_widget_class_bind_template_child (widget_class, CcDisplayPanel, output_selection_combo); + gtk_widget_class_bind_template_child (widget_class, CcDisplayPanel, output_selection_stack); + gtk_widget_class_bind_template_child (widget_class, CcDisplayPanel, output_selection_two_first); + gtk_widget_class_bind_template_child (widget_class, CcDisplayPanel, output_selection_two_second); + gtk_widget_class_bind_template_child (widget_class, CcDisplayPanel, primary_display_row); + + gtk_widget_class_bind_template_callback (widget_class, on_config_type_toggled_cb); + gtk_widget_class_bind_template_callback (widget_class, on_night_light_list_box_row_activated_cb); + gtk_widget_class_bind_template_callback (widget_class, on_output_enabled_active_changed_cb); + gtk_widget_class_bind_template_callback (widget_class, on_output_selection_combo_changed_cb); + gtk_widget_class_bind_template_callback (widget_class, on_output_selection_two_toggled_cb); + gtk_widget_class_bind_template_callback (widget_class, on_primary_display_selected_index_changed_cb); } static void -two_output_chooser_active (CcDisplayPanel *panel, - GParamSpec *pspec, - GtkWidget *button) +set_current_output (CcDisplayPanel *panel, + CcDisplayMonitor *output, + gboolean force) { - CcDisplayMonitor *output = g_object_get_data (G_OBJECT (button), "output"); + GtkTreeIter iter; + gboolean changed; - if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (button))) - set_current_output (panel, output); -} + /* Note, this function is also called if the internal UI needs updating after a rebuild. */ + changed = (output != panel->current_output); -static void -two_output_chooser_sync (GtkWidget *box, - CcDisplayPanel *panel) -{ - g_autoptr(GList) children = NULL; - GList *l; + if (!changed && !force) + return; - children = gtk_container_get_children (GTK_CONTAINER (box)); - for (l = children; l; l = l->next) + if (changed && cc_panel_get_selected_type (panel) == CC_DISPLAY_CONFIG_SINGLE) { - GtkWidget *button = l->data; - CcDisplayMonitor *output = g_object_get_data (G_OBJECT (button), "output"); - if (panel->current_output == output) - gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (button), TRUE); + if (output) + cc_display_monitor_set_active (output, TRUE); + if (panel->current_output) + cc_display_monitor_set_active (panel->current_output, FALSE); } -} -static GtkWidget * -make_two_output_chooser (CcDisplayPanel *panel) -{ - GtkWidget *box; - GtkRadioButton *group; - GList *outputs, *l; - - outputs = cc_display_config_get_ui_sorted_monitors (panel->current_config); - - box = gtk_button_box_new (GTK_ORIENTATION_HORIZONTAL); - gtk_button_box_set_layout (GTK_BUTTON_BOX (box), GTK_BUTTONBOX_EXPAND); - gtk_style_context_add_class (gtk_widget_get_style_context (box), GTK_STYLE_CLASS_LINKED); + panel->current_output = output; - group = NULL; - for (l = outputs; l; l = l->next) + if (panel->current_output) { - CcDisplayMonitor *output = l->data; - GtkWidget *button = gtk_radio_button_new_from_widget (group); - - gtk_widget_show (button); - gtk_button_set_label (GTK_BUTTON (button), cc_display_monitor_get_ui_name (output)); - gtk_toggle_button_set_mode (GTK_TOGGLE_BUTTON (button), FALSE); - - g_object_set_data (G_OBJECT (button), "output", output); - g_signal_connect_object (button, "notify::active", G_CALLBACK (two_output_chooser_active), - panel, G_CONNECT_SWAPPED); - gtk_container_add (GTK_CONTAINER (box), button); - group = GTK_RADIO_BUTTON (button); + gtk_label_set_text (panel->current_output_label, cc_display_monitor_get_ui_name (panel->current_output)); + gtk_switch_set_active (panel->output_enabled_switch, cc_display_monitor_is_active (panel->current_output)); + } + else + { + gtk_label_set_text (panel->current_output_label, ""); + gtk_switch_set_active (panel->output_enabled_switch, FALSE); } - g_signal_connect_object (panel, "current-output", G_CALLBACK (two_output_chooser_sync), - box, G_CONNECT_SWAPPED); - two_output_chooser_sync (box, panel); - - return box; -} - -static GtkWidget * -make_two_join_ui (CcDisplayPanel *panel) -{ - GtkWidget *vbox, *ui, *frame, *box; - - panel->rows_size_group = gtk_size_group_new (GTK_SIZE_GROUP_BOTH); - - vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 0); - - ui = make_arrangement_ui (panel); - gtk_widget_show (ui); - gtk_container_add (GTK_CONTAINER (vbox), ui); - - box = make_two_output_chooser (panel); - gtk_widget_show (box); - gtk_widget_set_margin_top (box, SECTION_PADDING); - gtk_container_add (GTK_CONTAINER (vbox), box); - - frame = make_frame (NULL, NULL); - gtk_widget_show (frame); - gtk_widget_set_margin_top (frame, HEADING_PADDING); - gtk_container_add (GTK_CONTAINER (vbox), frame); - - panel->settings = cc_display_settings_new (); - cc_display_settings_set_config (panel->settings, panel->current_config); - cc_display_settings_set_has_accelerometer (panel->settings, panel->has_accelerometer); - cc_display_settings_set_selected_output (panel->settings, panel->current_output); - gtk_widget_show (GTK_WIDGET (panel->settings)); - gtk_container_add (GTK_CONTAINER (frame), GTK_WIDGET (panel->settings)); - g_signal_connect_object (panel, "current-output", G_CALLBACK (udpate_display_settings), - frame, G_CONNECT_SWAPPED); - g_signal_connect_object (panel->settings, "updated", - G_CALLBACK (on_monitor_settings_updated_cb), panel, - G_CONNECT_SWAPPED); - - ui = make_night_light_widget (panel); - gtk_widget_show (ui); - gtk_container_add (GTK_CONTAINER (vbox), ui); - - g_clear_object (&panel->rows_size_group); - return vbox; -} - -static void -two_output_chooser_activate_output (CcDisplayPanel *panel, - GParamSpec *pspec, - GtkWidget *button) -{ - CcDisplayMonitor *output = g_object_get_data (G_OBJECT (button), "output"); + if (g_object_get_data (G_OBJECT (panel->output_selection_two_first), "display") == output) + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (panel->output_selection_two_first), TRUE); + if (g_object_get_data (G_OBJECT (panel->output_selection_two_second), "display") == output) + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (panel->output_selection_two_second), TRUE); - if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (button))) + gtk_tree_model_get_iter_first (GTK_TREE_MODEL (panel->output_selection_list), &iter); + while (gtk_list_store_iter_is_valid (panel->output_selection_list, &iter)) { - GList *outputs, *l; + g_autoptr(CcDisplayMonitor) o = NULL; - cc_display_monitor_set_active (output, TRUE); + gtk_tree_model_get (GTK_TREE_MODEL (panel->output_selection_list), &iter, + 1, &o, + -1); - outputs = cc_display_config_get_monitors (panel->current_config); - for (l = outputs; l; l = l->next) + if (o == panel->current_output) { - CcDisplayMonitor *other = l->data; - if (other != output) - cc_display_monitor_set_active (other, FALSE); + gtk_combo_box_set_active_iter (panel->output_selection_combo, &iter); + break; } - update_apply_button (panel); + gtk_tree_model_iter_next (GTK_TREE_MODEL (panel->output_selection_list), &iter); } -} -static void -connect_activate_output (GtkWidget *button, - gpointer panel) -{ - g_signal_connect_object (button, "notify::active", G_CALLBACK (two_output_chooser_activate_output), - panel, G_CONNECT_SWAPPED); + if (changed) + { + cc_display_settings_set_selected_output (panel->settings, panel->current_output); + cc_display_arrangement_set_selected_output (panel->arrangement, panel->current_output); + } } -static GtkWidget * -make_two_single_ui (CcDisplayPanel *panel) +static void +rebuild_ui (CcDisplayPanel *panel) { - GtkWidget *vbox, *frame, *ui, *box; - - panel->rows_size_group = gtk_size_group_new (GTK_SIZE_GROUP_BOTH); - - vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 0); - - box = make_two_output_chooser (panel); - gtk_widget_show (box); - gtk_container_foreach (GTK_CONTAINER (box), connect_activate_output, panel); - gtk_container_add (GTK_CONTAINER (vbox), box); - - frame = make_frame (NULL, NULL); - gtk_widget_show (frame); - gtk_widget_set_margin_top (frame, HEADING_PADDING); - gtk_container_add (GTK_CONTAINER (vbox), frame); + guint n_outputs, n_active_outputs, n_usable_outputs; + GList *outputs, *l; - panel->settings = cc_display_settings_new (); - cc_display_settings_set_config (panel->settings, panel->current_config); - cc_display_settings_set_has_accelerometer (panel->settings, panel->has_accelerometer); - cc_display_settings_set_selected_output (panel->settings, panel->current_output); - gtk_widget_show (GTK_WIDGET (panel->settings)); - gtk_container_add (GTK_CONTAINER (frame), GTK_WIDGET (panel->settings)); - g_signal_connect_object (panel, "current-output", G_CALLBACK (udpate_display_settings), - frame, G_CONNECT_SWAPPED); - g_signal_connect_object (panel->settings, "updated", - G_CALLBACK (on_monitor_settings_updated_cb), panel, - G_CONNECT_SWAPPED); + panel->rebuilding = TRUE; - ui = make_night_light_widget (panel); - gtk_widget_show (ui); - gtk_container_add (GTK_CONTAINER (vbox), ui); + g_list_store_remove_all (panel->primary_display_list); + gtk_list_store_clear (panel->output_selection_list); - g_clear_object (&panel->rows_size_group); - return vbox; -} + n_active_outputs = 0; + n_usable_outputs = 0; + outputs = cc_display_config_get_ui_sorted_monitors (panel->current_config); + for (l = outputs; l; l = l->next) + { + GtkTreeIter iter; + CcDisplayMonitor *output = l->data; -static GtkWidget * -make_two_mirror_ui (CcDisplayPanel *panel) -{ - GtkWidget *vbox, *frame, *ui; + if (!cc_display_monitor_is_usable (output)) + continue; - if (!cc_display_config_is_cloning (panel->current_config)) - { - GList *modes = cc_display_config_get_cloning_modes (panel->current_config); - gint bw, bh; - CcDisplayMode *best = NULL; + n_usable_outputs += 1; - while (modes) + if (n_usable_outputs == 1) { - CcDisplayMode *mode = modes->data; - gint w, h; + gtk_button_set_label (GTK_BUTTON (panel->output_selection_two_first), + cc_display_monitor_get_ui_name (output)); + g_object_set_data (G_OBJECT (panel->output_selection_two_first), + "display", + output); + } + else if (n_usable_outputs == 2) + { + gtk_button_set_label (GTK_BUTTON (panel->output_selection_two_second), + cc_display_monitor_get_ui_name (output)); + g_object_set_data (G_OBJECT (panel->output_selection_two_second), + "display", + output); + } - cc_display_mode_get_resolution (mode, &w, &h); - if (best == NULL || (bw*bh < w*h)) - { - best = mode; - cc_display_mode_get_resolution (best, &bw, &bh); - } + gtk_list_store_append (panel->output_selection_list, &iter); + gtk_list_store_set (panel->output_selection_list, + &iter, + 0, cc_display_monitor_get_ui_number_name (output), + 1, output, + -1); - modes = modes->next; + if (cc_display_monitor_is_active (output)) + { + n_active_outputs += 1; + + g_list_store_append (panel->primary_display_list, output); + if (cc_display_monitor_is_primary (output)) + hdy_combo_row_set_selected_index (panel->primary_display_row, + g_list_model_get_n_items (G_LIST_MODEL (panel->primary_display_list)) - 1); + + /* Ensure that an output is selected; note that this doesn't ensure + * the selected output is any useful (i.e. when switching types). + */ + if (!panel->current_output) + set_current_output (panel, output, FALSE); } - cc_display_config_set_cloning (panel->current_config, TRUE); - cc_display_config_set_mode_on_all_outputs (panel->current_config, best); } - panel->rows_size_group = gtk_size_group_new (GTK_SIZE_GROUP_BOTH); - - vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 0); - frame = make_frame (NULL, NULL); - gtk_widget_show (frame); - gtk_container_add (GTK_CONTAINER (vbox), frame); - panel->settings = cc_display_settings_new (); - cc_display_settings_set_config (panel->settings, panel->current_config); - cc_display_settings_set_has_accelerometer (panel->settings, panel->has_accelerometer); - cc_display_settings_set_selected_output (panel->settings, panel->current_output); - gtk_widget_show (GTK_WIDGET (panel->settings)); - gtk_container_add (GTK_CONTAINER (frame), GTK_WIDGET (panel->settings)); - g_signal_connect_object (panel, "current-output", G_CALLBACK (udpate_display_settings), - frame, G_CONNECT_SWAPPED); - g_signal_connect_object (panel->settings, "updated", - G_CALLBACK (on_monitor_settings_updated_cb), panel, - G_CONNECT_SWAPPED); - - ui = make_night_light_widget (panel); - gtk_widget_show (ui); - gtk_container_add (GTK_CONTAINER (vbox), ui); - - g_clear_object (&panel->rows_size_group); - return vbox; -} + /* Sync the rebuild lists/buttons */ + set_current_output (panel, panel->current_output, TRUE); -static void -two_output_visible_child_changed (CcDisplayPanel *panel, - GParamSpec *pspec, - GtkWidget *stack) -{ - GtkWidget *bin, *ui; - g_autoptr(GList) children = NULL; - GList *l; - - reset_current_config (panel); + n_outputs = g_list_length (outputs); - children = gtk_container_get_children (GTK_CONTAINER (stack)); - for (l = children; l; l = l->next) + /* We only show the top chooser with two outputs (and never if the lid is closed). + * And only in that mode do we allow mirroring. */ + if (n_outputs == 2 && !panel->lid_is_closed) { - GtkWidget *ui = gtk_bin_get_child (GTK_BIN (l->data)); - if (ui) - gtk_widget_destroy (ui); - } + CcDisplayConfigType types, type; - bin = gtk_stack_get_visible_child (GTK_STACK (stack)); + gtk_widget_set_visible (panel->config_type_switcher_frame, TRUE); + type = cc_panel_get_selected_type (panel); + types = config_find_types (panel); - if (g_str_equal (gtk_stack_get_visible_child_name (GTK_STACK (stack)), "mirror")) - { - ui = make_two_mirror_ui (panel); + if (!(type & types)) + cc_panel_set_selected_type (panel, config_select_type (panel)); } else { - gboolean single; - GList *outputs, *l; - - if (cc_display_config_is_cloning (panel->current_config)) - { - cc_display_config_set_cloning (panel->current_config, FALSE); - } - single = g_str_equal (gtk_stack_get_visible_child_name (GTK_STACK (stack)), "single"); - outputs = cc_display_config_get_monitors (panel->current_config); - for (l = outputs; l; l = l->next) - { - CcDisplayMonitor *output = l->data; - cc_display_monitor_set_active (output, (!single || output == panel->current_output)); - } - - if (single) - ui = make_two_single_ui (panel); + gtk_widget_set_visible (panel->config_type_switcher_frame, FALSE); + if (n_outputs == (panel->lid_is_closed ? 2 : 1)) + cc_panel_set_selected_type (panel, CC_DISPLAY_CONFIG_SINGLE); else - ui = make_two_join_ui (panel); + cc_panel_set_selected_type (panel, CC_DISPLAY_CONFIG_JOIN); } - gtk_widget_show (ui); - gtk_container_add (GTK_CONTAINER (bin), ui); - - ensure_monitor_labels (panel); - update_apply_button (panel); -} - -static gboolean -transform_stack_to_button (GBinding *binding, - const GValue *from_value, - GValue *to_value, - gpointer user_data) -{ - GtkWidget *visible_child = g_value_get_object (from_value); - GtkWidget *button_child = user_data; - - g_value_set_boolean (to_value, visible_child == button_child); - return TRUE; -} - -static gboolean -transform_button_to_stack (GBinding *binding, - const GValue *from_value, - GValue *to_value, - gpointer user_data) -{ - GtkWidget *button_child = user_data; - if (g_value_get_boolean (from_value)) - g_value_set_object (to_value, button_child); - return TRUE; -} + gtk_widget_set_visible (panel->arrangement_frame, cc_panel_get_selected_type (panel) == CC_DISPLAY_CONFIG_JOIN); -static void -add_two_output_page (GtkWidget *switcher, - GtkWidget *stack, - const gchar *name, - const gchar *title, - const gchar *icon) -{ - GtkWidget *button, *bin, *image; - - bin = make_bin (); - gtk_widget_show (bin); - gtk_stack_add_named (GTK_STACK (stack), bin, name); - image = gtk_image_new_from_icon_name (icon, GTK_ICON_SIZE_LARGE_TOOLBAR); - gtk_widget_show (image); - g_object_set (G_OBJECT (image), "margin", HEADING_PADDING, NULL); - button = g_object_new (GTK_TYPE_TOGGLE_BUTTON, - "image", image, - "image-position", GTK_POS_LEFT, - "always-show-image", TRUE, - "label", title, - NULL); - gtk_widget_show (button); - gtk_container_add (GTK_CONTAINER (switcher), button); - g_object_bind_property_full (stack, "visible-child", button, "active", G_BINDING_BIDIRECTIONAL, - transform_stack_to_button, transform_button_to_stack, bin, NULL); -} - -static GtkWidget * -make_two_output_ui (CcDisplayPanel *panel) -{ - GtkWidget *vbox, *switcher, *stack, *bin, *label; - gboolean show_mirror; - - show_mirror = g_list_length (cc_display_config_get_cloning_modes (panel->current_config)) > 0; - - vbox = make_main_vbox (panel->main_size_group); - gtk_widget_show (vbox); - - label = make_bold_label (_("Display Mode")); - gtk_widget_show (label); - gtk_widget_set_halign (label, GTK_ALIGN_START); - gtk_widget_set_margin_bottom (label, HEADING_PADDING); - gtk_container_add (GTK_CONTAINER (vbox), label); - - switcher = gtk_button_box_new (GTK_ORIENTATION_HORIZONTAL); - gtk_widget_show (switcher); - gtk_widget_set_margin_bottom (switcher, SECTION_PADDING); - gtk_button_box_set_layout (GTK_BUTTON_BOX (switcher), GTK_BUTTONBOX_EXPAND); - gtk_container_add (GTK_CONTAINER (vbox), switcher); - - stack = gtk_stack_new (); - gtk_widget_show (stack); - gtk_container_add (GTK_CONTAINER (vbox), stack); - /* Add a dummy first stack page so that setting the visible child - * below triggers a visible-child-name notification. */ - bin = make_bin (); - gtk_widget_show (bin); - gtk_stack_add_named (GTK_STACK (stack), bin, "dummy"); - - add_two_output_page (switcher, stack, "join", _("Join Displays"), - "video-joined-displays-symbolic"); - if (show_mirror) - add_two_output_page (switcher, stack, "mirror", _("Mirror"), - "view-mirror-symbolic"); - - add_two_output_page (switcher, stack, "single", _("Single Display"), - "video-single-display-symbolic"); - - g_signal_connect_object (stack, "notify::visible-child-name", - G_CALLBACK (two_output_visible_child_changed), - panel, G_CONNECT_SWAPPED); - - if (cc_display_config_is_cloning (panel->current_config) && show_mirror) - gtk_stack_set_visible_child_name (GTK_STACK (stack), "mirror"); - else if (cc_display_config_count_useful_monitors (panel->current_config) > 1) - gtk_stack_set_visible_child_name (GTK_STACK (stack), "join"); + if (panel->lid_is_closed) + { + if (n_outputs <= 2 || cc_panel_get_selected_type (panel) == CC_DISPLAY_CONFIG_CLONE) + gtk_stack_set_visible_child_name (panel->output_selection_stack, "no-selection"); + else + gtk_stack_set_visible_child_name (panel->output_selection_stack, "multi-selection"); + } else - gtk_stack_set_visible_child_name (GTK_STACK (stack), "single"); - - return make_scrollable (vbox); -} - -static void -output_switch_active (CcDisplayPanel *panel, - GParamSpec *pspec, - GtkWidget *button) -{ - cc_display_monitor_set_active (panel->current_output, - gtk_switch_get_active (GTK_SWITCH (button))); - cc_display_config_snap_output (panel->current_config, panel->current_output); - update_apply_button (panel); -} - -static void -output_switch_sync (GtkWidget *button, - CcDisplayMonitor *output) -{ - gtk_switch_set_active (GTK_SWITCH (button), cc_display_monitor_is_active (output)); -} - -static GtkWidget * -make_output_switch (CcDisplayPanel *panel) -{ - GtkWidget *button = gtk_switch_new (); - - g_signal_connect_object (button, "notify::active", G_CALLBACK (output_switch_active), - panel, G_CONNECT_SWAPPED); - g_signal_connect_object (panel->current_output, "active", G_CALLBACK (output_switch_sync), - button, G_CONNECT_SWAPPED); - output_switch_sync (button, panel->current_output); - - if ((cc_display_config_count_useful_monitors (panel->current_config) < 2 && cc_display_monitor_is_active (panel->current_output)) || - !cc_display_monitor_is_usable (panel->current_output)) - gtk_widget_set_sensitive (button, FALSE); - - return button; -} - -static void -replace_output_switch (GtkWidget *frame, - CcDisplayPanel *panel) -{ - GtkWidget *sw; - - gtk_widget_destroy (gtk_bin_get_child (GTK_BIN (frame))); - sw = make_output_switch (panel); - gtk_widget_show (sw); - gtk_container_add (GTK_CONTAINER (frame), sw); -} - -static void -output_chooser_row_activated (CcDisplayPanel *panel, - GtkWidget *row) -{ - CcDisplayMonitor *output = g_object_get_data (G_OBJECT (row), "output"); - set_current_output (panel, output); -} - -static void -output_chooser_sync (GtkWidget *button, - CcDisplayPanel *panel) -{ - const gchar *text = cc_display_monitor_get_ui_number_name (panel->current_output); - GtkWidget *label = gtk_bin_get_child (GTK_BIN (button)); - - gtk_label_set_text (GTK_LABEL (label), text); -} - -static GtkWidget * -make_output_chooser_button (CcDisplayPanel *panel) -{ - GtkWidget *listbox, *button, *popover, *label; - GList *outputs, *l; - - outputs = cc_display_config_get_ui_sorted_monitors (panel->current_config); - - listbox = make_list_box (); - gtk_widget_show (listbox); - - for (l = outputs; l; l = l->next) { - CcDisplayMonitor *output = l->data; - GtkWidget *label, *row; - const gchar *text; - - text = cc_display_monitor_get_ui_number_name (output); - label = make_popover_label (text); - gtk_widget_show (label); - row = g_object_new (CC_TYPE_LIST_BOX_ROW, - "child", label, - NULL); - gtk_widget_show (row); - g_object_set_data (G_OBJECT (row), "output", output); - - g_signal_connect_object (row, "activated", G_CALLBACK (output_chooser_row_activated), - panel, G_CONNECT_SWAPPED); - gtk_container_add (GTK_CONTAINER (listbox), row); + if (n_outputs == 1 || cc_panel_get_selected_type (panel) == CC_DISPLAY_CONFIG_CLONE) + gtk_stack_set_visible_child_name (panel->output_selection_stack, "no-selection"); + else if (n_outputs == 2) + gtk_stack_set_visible_child_name (panel->output_selection_stack, "two-selection"); + else + gtk_stack_set_visible_child_name (panel->output_selection_stack, "multi-selection"); } - popover = make_list_popover (listbox); - button = gtk_menu_button_new (); - label = make_bold_label (NULL); - gtk_widget_show (label); - gtk_container_add (GTK_CONTAINER (button), label); - gtk_menu_button_set_popover (GTK_MENU_BUTTON (button), popover); - g_signal_connect_object (panel, "current-output", G_CALLBACK (output_chooser_sync), - button, G_CONNECT_SWAPPED); - output_chooser_sync (button, panel); - - return button; -} - -static GtkWidget * -make_multi_output_ui (CcDisplayPanel *panel) -{ - GtkWidget *vbox, *ui, *frame, *sw, *hbox, *button; - - panel->rows_size_group = gtk_size_group_new (GTK_SIZE_GROUP_BOTH); - - vbox = make_main_vbox (panel->main_size_group); - gtk_widget_show (vbox); - - ui = make_arrangement_ui (panel); - gtk_widget_show (ui); - gtk_container_add (GTK_CONTAINER (vbox), ui); - - hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0); - gtk_widget_show (hbox); - gtk_widget_set_margin_top (hbox, SECTION_PADDING); - gtk_container_add (GTK_CONTAINER (vbox), hbox); - - button = make_output_chooser_button (panel); - gtk_widget_show (button); - gtk_box_pack_start (GTK_BOX (hbox), button, FALSE, FALSE, 0); - - frame = make_bin (); - gtk_widget_show (frame); - gtk_box_pack_end (GTK_BOX (hbox), frame, FALSE, FALSE, 0); - sw = make_output_switch (panel); - gtk_widget_show (sw); - gtk_container_add (GTK_CONTAINER (frame), sw); - g_signal_connect_object (panel, "current-output", G_CALLBACK (replace_output_switch), - frame, G_CONNECT_SWAPPED); - - frame = make_frame (NULL, NULL); - gtk_widget_show (frame); - gtk_widget_set_margin_top (frame, HEADING_PADDING); - gtk_container_add (GTK_CONTAINER (vbox), frame); - - panel->settings = cc_display_settings_new (); - cc_display_settings_set_config (panel->settings, panel->current_config); - cc_display_settings_set_has_accelerometer (panel->settings, panel->has_accelerometer); - cc_display_settings_set_selected_output (panel->settings, panel->current_output); - gtk_widget_show (GTK_WIDGET (panel->settings)); - gtk_container_add (GTK_CONTAINER (frame), GTK_WIDGET (panel->settings)); - g_signal_connect_object (panel, "current-output", G_CALLBACK (udpate_display_settings), - frame, G_CONNECT_SWAPPED); - g_signal_connect_object (panel->settings, "updated", - G_CALLBACK (on_monitor_settings_updated_cb), panel, - G_CONNECT_SWAPPED); - - ui = make_night_light_widget (panel); - gtk_widget_show (ui); - gtk_container_add (GTK_CONTAINER (vbox), ui); - - g_clear_object (&panel->rows_size_group); - return make_scrollable (vbox); + panel->rebuilding = FALSE; + update_apply_button (panel); } static void reset_current_config (CcDisplayPanel *panel) { - GList *outputs, *l; CcDisplayConfig *current; + CcDisplayConfig *old; + GList *outputs, *l; + + g_debug ("Resetting current config!"); - g_clear_object (&panel->current_config); + /* We need to hold on to the config until all display references are dropped. */ + old = panel->current_config; panel->current_output = NULL; current = cc_display_config_manager_get_current (panel->manager); - if (!current) - return; - panel->current_config = current; - outputs = cc_display_config_get_ui_sorted_monitors (panel->current_config); - for (l = outputs; l; l = l->next) + g_list_store_remove_all (panel->primary_display_list); + gtk_list_store_clear (panel->output_selection_list); + + if (panel->current_config) { - CcDisplayMonitor *output = l->data; + outputs = cc_display_config_get_ui_sorted_monitors (panel->current_config); + for (l = outputs; l; l = l->next) + { + CcDisplayMonitor *output = l->data; - /* Mark any builtin monitor as unusable if the lid is closed. */ - if (cc_display_monitor_is_builtin (output) && panel->lid_is_closed) - cc_display_monitor_set_usable (output, FALSE); + /* Mark any builtin monitor as unusable if the lid is closed. */ + if (cc_display_monitor_is_builtin (output) && panel->lid_is_closed) + cc_display_monitor_set_usable (output, FALSE); + } + } - if (!cc_display_monitor_is_useful (output)) - continue; + cc_display_arrangement_set_config (panel->arrangement, panel->current_config); + cc_display_settings_set_config (panel->settings, panel->current_config); + set_current_output (panel, NULL, FALSE); - panel->current_output = output; - break; - } + g_clear_object (&old); + + update_apply_button (panel); } static void on_screen_changed (CcDisplayPanel *panel) { - GtkWidget *main_widget; - GList *outputs; - guint n_outputs; - if (!panel->manager) return; reset_titlebar (panel); - main_widget = gtk_stack_get_child_by_name (GTK_STACK (panel->stack), "main"); - if (main_widget) - gtk_widget_destroy (main_widget); - reset_current_config (panel); + rebuild_ui (panel); if (!panel->current_config) - goto show_error; + return; ensure_monitor_labels (panel); - - if (!panel->current_output) - goto show_error; - - outputs = cc_display_config_get_ui_sorted_monitors (panel->current_config); - n_outputs = g_list_length (outputs); - if (panel->lid_is_closed) - { - if (n_outputs <= 2) - main_widget = make_single_output_ui (panel); - else - main_widget = make_multi_output_ui (panel); - } - else - { - if (n_outputs == 1) - main_widget = make_single_output_ui (panel); - else if (n_outputs == 2) - main_widget = make_two_output_ui (panel); - else - main_widget = make_multi_output_ui (panel); - } - - gtk_widget_show (main_widget); - gtk_stack_add_named (GTK_STACK (panel->stack), main_widget, "main"); - gtk_stack_set_visible_child (GTK_STACK (panel->stack), main_widget); - return; - - show_error: - gtk_stack_set_visible_child_name (GTK_STACK (panel->stack), "error"); } static gboolean @@ -1478,15 +951,6 @@ apply_current_configuration (CcDisplayPanel *self) g_warning ("Error applying configuration: %s", error->message); } -static void -cc_display_panel_night_light_activated (CcDisplayPanel *panel) -{ - GtkWindow *toplevel; - toplevel = GTK_WINDOW (cc_shell_get_toplevel (cc_panel_get_shell (CC_PANEL (panel)))); - gtk_window_set_transient_for (GTK_WINDOW (panel->night_light_dialog), toplevel); - gtk_window_present (GTK_WINDOW (panel->night_light_dialog)); -} - static void mapped_cb (CcDisplayPanel *panel) { @@ -1547,6 +1011,7 @@ update_has_accel (CcDisplayPanel *self) { g_debug ("Has no accelerometer"); self->has_accelerometer = FALSE; + cc_display_settings_set_has_accelerometer (self->settings, self->has_accelerometer); return; } @@ -1560,6 +1025,8 @@ update_has_accel (CcDisplayPanel *self) self->has_accelerometer = FALSE; } + cc_display_settings_set_has_accelerometer (self->settings, self->has_accelerometer); + g_debug ("Has %saccelerometer", self->has_accelerometer ? "" : "no "); } @@ -1631,38 +1098,6 @@ settings_color_changed_cb (GSettings *settings, gchar *key, GtkWidget *label) night_light_sync_label (label, settings); } -static GtkWidget * -make_night_light_widget (CcDisplayPanel *self) -{ - GtkWidget *frame, *row, *label, *state_label; - GtkWidget *night_light_listbox; - - frame = make_frame (NULL, NULL); - night_light_listbox = make_list_box (); - gtk_widget_show (night_light_listbox); - gtk_container_add (GTK_CONTAINER (frame), night_light_listbox); - - label = gtk_label_new (_("_Night Light")); - gtk_widget_show (label); - gtk_label_set_use_underline (GTK_LABEL (label), TRUE); - - state_label = gtk_label_new (""); - gtk_widget_show (state_label); - g_signal_connect_object (self->settings_color, "changed", - G_CALLBACK (settings_color_changed_cb), state_label, 0); - night_light_sync_label (state_label, self->settings_color); - - row = make_row (self->rows_size_group, label, state_label); - gtk_widget_show (row); - gtk_container_add (GTK_CONTAINER (night_light_listbox), row); - g_signal_connect_object (row, "activated", - G_CALLBACK (cc_display_panel_night_light_activated), - self, G_CONNECT_SWAPPED); - - gtk_widget_set_margin_top (frame, SECTION_PADDING); - return frame; -} - static void session_bus_ready (GObject *source, GAsyncResult *res, @@ -1677,7 +1112,6 @@ session_bus_ready (GObject *source, if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) { g_warning ("Failed to get session bus: %s", error->message); - gtk_stack_set_visible_child_name (GTK_STACK (self->stack), "error"); } return; } @@ -1693,31 +1127,58 @@ static void cc_display_panel_init (CcDisplayPanel *self) { g_autoptr (GtkCssProvider) provider = NULL; - GtkWidget *bin, *label; + GtkCellRenderer *renderer; g_resources_register (cc_display_get_resource ()); - self->stack = gtk_stack_new (); - gtk_widget_show (self->stack); + gtk_widget_init_template (GTK_WIDGET (self)); - bin = make_bin (); - gtk_widget_show (bin); - gtk_widget_set_size_request (bin, 500, -1); - gtk_stack_add_named (GTK_STACK (self->stack), bin, "main-size-group"); - self->main_size_group = gtk_size_group_new (GTK_SIZE_GROUP_HORIZONTAL); - gtk_size_group_add_widget (self->main_size_group, bin); + self->arrangement = cc_display_arrangement_new (NULL); - label = gtk_label_new (_("Could not get screen information")); - gtk_widget_show (label); - gtk_stack_add_named (GTK_STACK (self->stack), - label, - "error"); + gtk_widget_show (GTK_WIDGET (self->arrangement)); + gtk_widget_set_size_request (GTK_WIDGET (self->arrangement), 400, 175); + gtk_container_add (GTK_CONTAINER (self->arrangement_bin), GTK_WIDGET (self->arrangement)); - gtk_container_add (GTK_CONTAINER (self), self->stack); + g_signal_connect_object (self->arrangement, "updated", + G_CALLBACK (update_apply_button), self, + G_CONNECT_SWAPPED); + g_signal_connect_object (self->arrangement, "notify::selected-output", + G_CALLBACK (on_arrangement_selected_ouptut_changed_cb), self, + G_CONNECT_SWAPPED); + + self->settings = cc_display_settings_new (); + gtk_widget_show (GTK_WIDGET (self->settings)); + gtk_container_add (GTK_CONTAINER (self->display_settings_frame), GTK_WIDGET (self->settings)); + g_signal_connect_object (self->settings, "updated", + G_CALLBACK (on_monitor_settings_updated_cb), self, + G_CONNECT_SWAPPED); + + self->primary_display_list = g_list_store_new (CC_TYPE_DISPLAY_MONITOR); + hdy_combo_row_bind_name_model (self->primary_display_row, + G_LIST_MODEL (self->primary_display_list), + (HdyComboRowGetNameFunc) cc_display_monitor_dup_ui_number_name, + NULL, NULL); + + self->output_selection_list = gtk_list_store_new (2, G_TYPE_STRING, CC_TYPE_DISPLAY_MONITOR); + gtk_combo_box_set_model (self->output_selection_combo, GTK_TREE_MODEL (self->output_selection_list)); + gtk_cell_layout_clear (GTK_CELL_LAYOUT (self->output_selection_combo)); + renderer = gtk_cell_renderer_text_new (); + gtk_cell_layout_pack_start (GTK_CELL_LAYOUT (self->output_selection_combo), + renderer, + TRUE); + gtk_cell_layout_add_attribute (GTK_CELL_LAYOUT (self->output_selection_combo), + renderer, + "text", + 0); + gtk_cell_renderer_set_visible (renderer, TRUE); self->night_light_dialog = cc_night_light_dialog_new (); self->settings_color = g_settings_new ("org.gnome.settings-daemon.plugins.color"); + g_signal_connect_object (self->settings_color, "changed", + G_CALLBACK (settings_color_changed_cb), self->night_light_status_label, 0); + night_light_sync_label (GTK_WIDGET (self->night_light_status_label), self->settings_color); + self->up_client = up_client_new (); if (up_client_get_lid_is_present (self->up_client)) { diff --git a/panels/display/cc-display-panel.ui b/panels/display/cc-display-panel.ui new file mode 100644 index 0000000000..f76b4fed1b --- /dev/null +++ b/panels/display/cc-display-panel.ui @@ -0,0 +1,538 @@ + + + + + + + True + False + 12 + 12 + 12 + 12 + video-joined-displays-symbolic + 3 + + + True + False + 12 + 12 + 12 + 12 + view-mirror-symbolic + 3 + + + True + False + 12 + 12 + 12 + 12 + video-single-display-symbolic + 3 + + + diff --git a/panels/display/display.gresource.xml b/panels/display/display.gresource.xml index 70eedbaa9a..04600346b2 100644 --- a/panels/display/display.gresource.xml +++ b/panels/display/display.gresource.xml @@ -1,6 +1,7 @@ + cc-display-panel.ui cc-display-settings.ui cc-night-light-dialog.ui display-arrangement.css diff --git a/panels/display/meson.build b/panels/display/meson.build index a90572b1e9..d448e822c8 100644 --- a/panels/display/meson.build +++ b/panels/display/meson.build @@ -29,6 +29,7 @@ sources = files( ) resource_data = files( + 'cc-display-panel.ui', 'cc-display-settings.ui', 'cc-night-light-dialog.ui', ) -- GitLab