diff --git a/panels/common/cc-value-object.c b/panels/common/cc-value-object.c new file mode 100644 index 0000000000000000000000000000000000000000..c779b3a0be9a7c84c92edba93c37785590ac4b7e --- /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 0000000000000000000000000000000000000000..b39c2061ca42fa32f84587cb5fb03a4279112733 --- /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 7d15e64f3c77110f5c2b482b85d3d87ff75c211e..118a842571689f2c4b6fc41caf99a554bfec005b 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', ) diff --git a/panels/display/cc-display-arrangement.c b/panels/display/cc-display-arrangement.c index 630c72e8d1e5d8a90e0935fbbff18c4f298622d1..adbbcbc281f28f9a54df4f1023ee89ef09e0a070 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; @@ -857,6 +835,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 @@ -877,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", @@ -911,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; } @@ -928,6 +954,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]); } diff --git a/panels/display/cc-display-arrangement.h b/panels/display/cc-display-arrangement.h index 8d0e39bc0fff8b8475c81f88232fe9aafffc4c0b..9494c484b8769d7ac315834811047ac67aa49269 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); diff --git a/panels/display/cc-display-config-dbus.c b/panels/display/cc-display-config-dbus.c index b74160fdc1751cb4967a55e5492bc50f45d45903..efb91b346b9a82dd7b07deaddacd6199fbfbde27 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; diff --git a/panels/display/cc-display-config.c b/panels/display/cc-display-config.c index e2b6d991ce1f9160daeeb0657552ab193fd85a96..3bed67a493d9129571cecf9761e70e472a271202 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, @@ -369,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) { @@ -436,6 +458,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 @@ -491,6 +515,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 e744227054bb879b8b1f20e4a062f2708889446d..faeae8e34724431a474d8ef1f1856911b5ad0308 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); @@ -220,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, diff --git a/panels/display/cc-display-panel.c b/panels/display/cc-display-panel.c index 39002decf5d99b7cf6e881c6ee806903f593d2f6..bb574c5c9dbe9386653ee2a869669ea7325522a6 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 @@ -35,6 +38,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 @@ -44,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 { @@ -61,14 +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; @@ -87,40 +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 const gchar * -get_resolution_string (CcDisplayMode *mode); -static const gchar * -get_frequency_string (CcDisplayMode *mode); -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 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) @@ -270,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); @@ -282,1761 +455,403 @@ cc_display_panel_dispose (GObject *object) } 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) -{ - return "help:gnome-help/prefs-display"; -} - -static void -cc_display_panel_class_init (CcDisplayPanelClass *klass) -{ - GObjectClass *object_class = G_OBJECT_CLASS (klass); - CcPanelClass *panel_class = CC_PANEL_CLASS (klass); - - panel_class->get_help_uri = cc_display_panel_get_help_uri; - - object_class->constructed = cc_display_panel_constructed; - object_class->dispose = cc_display_panel_dispose; - - 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); -} - -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) -{ - 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; -} - -static void -make_list_transparent (GtkWidget *listbox) -{ - g_autoptr(GtkCssProvider) provider = NULL; - - 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); -} - -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; -} - -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); -} - -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) +on_arrangement_selected_ouptut_changed_cb (CcDisplayPanel *panel) { - 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; + set_current_output (panel, cc_display_arrangement_get_selected_output (panel->arrangement), FALSE); } static void -refresh_rate_row_activated (CcDisplayPanel *panel, - GtkListBoxRow *row) +on_monitor_settings_updated_cb (CcDisplayPanel *panel, + CcDisplayMonitor *monitor, + CcDisplaySettings *settings) { - CcDisplayMode *mode = g_object_get_data (G_OBJECT (row), "mode"); - - cc_display_monitor_set_mode (panel->current_output, mode); + if (monitor) + cc_display_config_snap_output (panel->current_config, monitor); 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) -{ - 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; -} - -static GtkWidget * -make_single_output_ui (CcDisplayPanel *panel) -{ - GtkWidget *vbox, *frame, *ui; - - panel->rows_size_group = gtk_size_group_new (GTK_SIZE_GROUP_BOTH); - - 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); - - ui = make_output_ui (panel); - gtk_widget_show (ui); - gtk_container_add (GTK_CONTAINER (frame), ui); - - 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); -} - -static void -arrangement_notify_selected_ouptut_cb (CcDisplayPanel *panel, - GParamSpec *pspec, - CcDisplayArrangement *arr) -{ - CcDisplayMonitor *output = cc_display_arrangement_get_selected_output (arr); - - if (output && output != panel->current_output) - set_current_output (panel, output); -} - -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) -{ - 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); - - 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)); - - return row; -} - -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"); - - cc_display_monitor_set_primary (output, TRUE); - update_apply_button (panel); -} - -static GtkWidget * -make_primary_chooser_popover (CcDisplayPanel *panel) -{ - 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); - } - - return make_list_popover (listbox); -} - -static GtkWidget * -make_primary_chooser_row (CcDisplayPanel *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; -} - -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) -{ - 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); - - row = make_arrangement_row (panel); - gtk_widget_show (row); - gtk_container_add (GTK_CONTAINER (listbox), row); - - row = make_primary_chooser_row (panel); - gtk_widget_show (row); - gtk_container_add (GTK_CONTAINER (listbox), row); - - return frame; -} - -static void -two_output_chooser_active (CcDisplayPanel *panel, - GParamSpec *pspec, - GtkWidget *button) -{ - CcDisplayMonitor *output = g_object_get_data (G_OBJECT (button), "output"); - - if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (button))) - set_current_output (panel, output); -} - -static void -two_output_chooser_sync (GtkWidget *box, - CcDisplayPanel *panel) -{ - g_autoptr(GList) children = NULL; - GList *l; - - children = gtk_container_get_children (GTK_CONTAINER (box)); - for (l = children; l; l = l->next) - { - 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); - } -} - -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); - - group = NULL; - for (l = outputs; l; l = l->next) - { - 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); - } - - 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); - - 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), - frame, 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 (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (button))) - { - GList *outputs, *l; - - cc_display_monitor_set_active (output, TRUE); - - outputs = cc_display_config_get_monitors (panel->current_config); - for (l = outputs; l; l = l->next) - { - CcDisplayMonitor *other = l->data; - if (other != output) - cc_display_monitor_set_active (other, FALSE); - } - - update_apply_button (panel); - } -} - -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); -} - -static GtkWidget * -make_two_single_ui (CcDisplayPanel *panel) +static void +on_config_type_toggled_cb (CcDisplayPanel *panel, + GtkRadioButton *btn) { - 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); + CcDisplayConfigType type; - frame = make_frame (NULL, NULL); - gtk_widget_show (frame); - gtk_widget_set_margin_top (frame, HEADING_PADDING); - gtk_container_add (GTK_CONTAINER (vbox), frame); + if (panel->rebuilding) + return; - 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), - frame, G_CONNECT_SWAPPED); + if (!panel->current_config) + return; - ui = make_night_light_widget (panel); - gtk_widget_show (ui); - gtk_container_add (GTK_CONTAINER (vbox), ui); + if (!gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (btn))) + return; - g_clear_object (&panel->rows_size_group); - return vbox; + type = cc_panel_get_selected_type (panel); + config_ensure_of_type (panel, type); } static void -set_mode_on_all_outputs (CcDisplayConfig *config, - CcDisplayMode *mode) +on_night_light_list_box_row_activated_cb (CcDisplayPanel *panel) { - 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); - } + 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 -mirror_resolution_row_activated (CcDisplayPanel *panel, - GtkListBoxRow *row) +on_output_enabled_active_changed_cb (CcDisplayPanel *panel) { - CcDisplayMode *mode = g_object_get_data (G_OBJECT (row), "mode"); + gboolean active; - set_mode_on_all_outputs (panel->current_config, mode); - update_apply_button (panel); + if (!panel->current_output) + return; + + active = gtk_switch_get_active (panel->output_enabled_switch); + + if (cc_display_monitor_is_active (panel->current_output) == active) + return; + + /* Changing the active state requires a UI rebuild. */ + cc_display_monitor_set_active (panel->current_output, active); + rebuild_ui (panel); } -static GtkWidget * -make_mirror_resolution_popover (CcDisplayPanel *panel) +static void +on_output_selection_combo_changed_cb (CcDisplayPanel *panel) { - GtkWidget *listbox; - GList *resolutions, *l; + GtkTreeIter iter; + g_autoptr(CcDisplayMonitor) output = NULL; - resolutions = g_object_get_data (G_OBJECT (panel->current_config), "mirror-res-list"); + if (!panel->current_config) + return; - listbox = make_list_box (); - gtk_widget_show (listbox); - g_object_set (listbox, "margin", 12, NULL); + if (!gtk_combo_box_get_active_iter (panel->output_selection_combo, &iter)) + return; - 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); - } + gtk_tree_model_get (GTK_TREE_MODEL (panel->output_selection_list), &iter, + 1, &output, + -1); - return make_list_popover (listbox); + set_current_output (panel, output, FALSE); } -static GtkWidget * -make_mirror_resolution_row (CcDisplayPanel *panel, - CcDisplayMonitor *output) +static void +on_output_selection_two_toggled_cb (CcDisplayPanel *panel, GtkRadioButton *btn) { - 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; + if (!gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (btn))) + return; + + set_current_output (panel, g_object_get_data (G_OBJECT (btn), "display"), FALSE); } static void -ensure_mirror_res_list (CcDisplayConfig *config) +on_primary_display_selected_index_changed_cb (CcDisplayPanel *panel) { - GHashTable *res_set; - GList *resolutions, *l; + gint idx = hdy_combo_row_get_selected_index (panel->primary_display_row); + g_autoptr(CcDisplayMonitor) output = NULL; - if (g_object_get_data (G_OBJECT (config), "mirror-res-list")) + if (idx < 0) return; - res_set = g_hash_table_new (g_str_hash, g_str_equal); - - resolutions = cc_display_config_get_cloning_modes (config); - for (l = resolutions; l; l = l->next) - { - 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); + output = g_list_model_get_item (G_LIST_MODEL (panel->primary_display_list), idx); - resolutions = g_list_sort (resolutions, (GCompareFunc) sort_modes_by_area_desc); + if (cc_display_monitor_is_primary (output)) + return; - g_object_set_data_full (G_OBJECT (config), "mirror-res-list", - resolutions, (GDestroyNotify) g_list_free); + cc_display_monitor_set_primary (output, TRUE); + update_apply_button (panel); } -static GtkWidget * -make_two_mirror_ui (CcDisplayPanel *panel) +static void +cc_display_panel_constructed (GObject *object) { - GtkWidget *vbox, *listbox, *frame, *row, *ui; - - ensure_mirror_res_list (panel->current_config); - if (!cc_display_config_is_cloning (panel->current_config)) - { - 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))); - } + g_signal_connect_object (cc_panel_get_shell (CC_PANEL (object)), "notify::active-panel", + G_CALLBACK (active_panel_changed), object, 0); - panel->rows_size_group = gtk_size_group_new (GTK_SIZE_GROUP_BOTH); + G_OBJECT_CLASS (cc_display_panel_parent_class)->constructed (object); +} - vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 0); - 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); +static const char * +cc_display_panel_get_help_uri (CcPanel *panel) +{ + return "help:gnome-help/prefs-display"; +} - 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); - } +static void +cc_display_panel_class_init (CcDisplayPanelClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + CcPanelClass *panel_class = CC_PANEL_CLASS (klass); + GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); - row = make_mirror_resolution_row (panel, panel->current_output); - gtk_widget_show (row); - gtk_container_add (GTK_CONTAINER (listbox), row); + panel_class->get_help_uri = cc_display_panel_get_help_uri; - ui = make_night_light_widget (panel); - gtk_widget_show (ui); - gtk_container_add (GTK_CONTAINER (vbox), ui); + object_class->constructed = cc_display_panel_constructed; + object_class->dispose = cc_display_panel_dispose; - g_clear_object (&panel->rows_size_group); - return vbox; + 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_visible_child_changed (CcDisplayPanel *panel, - GParamSpec *pspec, - GtkWidget *stack) +set_current_output (CcDisplayPanel *panel, + CcDisplayMonitor *output, + gboolean force) { - GtkWidget *bin, *ui; - g_autoptr(GList) children = NULL; - GList *l; + GtkTreeIter iter; + gboolean changed; - reset_current_config (panel); + /* Note, this function is also called if the internal UI needs updating after a rebuild. */ + changed = (output != panel->current_output); + + if (!changed && !force) + return; - children = gtk_container_get_children (GTK_CONTAINER (stack)); - for (l = children; l; l = l->next) + if (changed && cc_panel_get_selected_type (panel) == CC_DISPLAY_CONFIG_SINGLE) { - GtkWidget *ui = gtk_bin_get_child (GTK_BIN (l->data)); - if (ui) - gtk_widget_destroy (ui); + if (output) + cc_display_monitor_set_active (output, TRUE); + if (panel->current_output) + cc_display_monitor_set_active (panel->current_output, FALSE); } - bin = gtk_stack_get_visible_child (GTK_STACK (stack)); + panel->current_output = output; - if (g_str_equal (gtk_stack_get_visible_child_name (GTK_STACK (stack)), "mirror")) + if (panel->current_output) { - ui = make_two_mirror_ui (panel); + 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 { - 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); - else - ui = make_two_join_ui (panel); + gtk_label_set_text (panel->current_output_label, ""); + gtk_switch_set_active (panel->output_enabled_switch, FALSE); } - gtk_widget_show (ui); - gtk_container_add (GTK_CONTAINER (bin), ui); + 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); - ensure_monitor_labels (panel); - update_apply_button (panel); -} + 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)) + { + g_autoptr(CcDisplayMonitor) o = NULL; -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; + gtk_tree_model_get (GTK_TREE_MODEL (panel->output_selection_list), &iter, + 1, &o, + -1); - g_value_set_boolean (to_value, visible_child == button_child); - return TRUE; -} + if (o == panel->current_output) + { + gtk_combo_box_set_active_iter (panel->output_selection_combo, &iter); + break; + } -static gboolean -transform_button_to_stack (GBinding *binding, - const GValue *from_value, - GValue *to_value, - gpointer user_data) -{ - GtkWidget *button_child = user_data; + gtk_tree_model_iter_next (GTK_TREE_MODEL (panel->output_selection_list), &iter); + } - if (g_value_get_boolean (from_value)) - g_value_set_object (to_value, button_child); - return TRUE; + 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 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) +rebuild_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"); - else - gtk_stack_set_visible_child_name (GTK_STACK (stack), "single"); + guint n_outputs, n_active_outputs, n_usable_outputs; + GList *outputs, *l; - return make_scrollable (vbox); -} + panel->rebuilding = TRUE; -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); -} + g_list_store_remove_all (panel->primary_display_list); + gtk_list_store_clear (panel->output_selection_list); -static void -output_switch_sync (GtkWidget *button, - CcDisplayMonitor *output) -{ - gtk_switch_set_active (GTK_SWITCH (button), cc_display_monitor_is_active (output)); -} + 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_output_switch (CcDisplayPanel *panel) -{ - GtkWidget *button = gtk_switch_new (); + if (!cc_display_monitor_is_usable (output)) + continue; - 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); + n_usable_outputs += 1; - 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); + if (n_usable_outputs == 1) + { + 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); + } - return button; -} + 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); -static void -replace_output_switch (GtkWidget *frame, - CcDisplayPanel *panel) -{ - GtkWidget *sw; + 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); + } + } - 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); -} + /* Sync the rebuild lists/buttons */ + set_current_output (panel, panel->current_output, TRUE); -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); -} + n_outputs = g_list_length (outputs); -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)); + /* 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) + { + CcDisplayConfigType types, type; - gtk_label_set_text (GTK_LABEL (label), text); -} + gtk_widget_set_visible (panel->config_type_switcher_frame, TRUE); + type = cc_panel_get_selected_type (panel); + types = config_find_types (panel); -static GtkWidget * -make_output_chooser_button (CcDisplayPanel *panel) -{ - GtkWidget *listbox, *button, *popover, *label; - GList *outputs, *l; + if (!(type & types)) + cc_panel_set_selected_type (panel, config_select_type (panel)); + } + else + { + 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 + cc_panel_set_selected_type (panel, CC_DISPLAY_CONFIG_JOIN); + } - outputs = cc_display_config_get_ui_sorted_monitors (panel->current_config); - listbox = make_list_box (); - gtk_widget_show (listbox); + gtk_widget_set_visible (panel->arrangement_frame, cc_panel_get_selected_type (panel) == CC_DISPLAY_CONFIG_JOIN); - for (l = outputs; l; l = l->next) + if (panel->lid_is_closed) { - 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 <= 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 + { + 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); - - 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), - frame, 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); + 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); - if (!cc_display_monitor_is_useful (output)) - continue; + g_clear_object (&old); - panel->current_output = output; - break; - } + 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 @@ -2136,139 +951,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) -{ - 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) { @@ -2329,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; } @@ -2342,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 "); } @@ -2413,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, @@ -2459,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; } @@ -2475,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)); + + self->arrangement = cc_display_arrangement_new (NULL); - 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); + 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)); + + 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); - label = gtk_label_new (_("Could not get screen information")); - gtk_widget_show (label); - gtk_stack_add_named (GTK_STACK (self->stack), - label, - "error"); + 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); - gtk_container_add (GTK_CONTAINER (self), self->stack); + 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 0000000000000000000000000000000000000000..f76b4fed1be29fbed35b32cfa6e25905f440d148 --- /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/cc-display-settings.c b/panels/display/cc-display-settings.c new file mode 100644 index 0000000000000000000000000000000000000000..58a308e2b4014cabb3a5d0e84bd654e9288d756d --- /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 0000000000000000000000000000000000000000..58709ddf7d896bdb398fbe5084797702203131a8 --- /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 0000000000000000000000000000000000000000..6e04265f29a53842e92bab40a4fbeb671e8b7c7e --- /dev/null +++ b/panels/display/cc-display-settings.ui @@ -0,0 +1,64 @@ + + + + + + + diff --git a/panels/display/cc-night-light-dialog.c b/panels/display/cc-night-light-dialog.c index 8ab9d4ca0b242d7987f414010009e3fda3c63fef..42c1353d1cdb2ad2b8cda4c07c0f21c4488f1b2c 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); diff --git a/panels/display/display.gresource.xml b/panels/display/display.gresource.xml index 5ecf8c59680be7ace86c880401507589201545f1..04600346b218965d10340811718fb5c460e3dec3 100644 --- a/panels/display/display.gresource.xml +++ b/panels/display/display.gresource.xml @@ -1,6 +1,8 @@ + cc-display-panel.ui + 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 bd4352b9bbbf5208fd2a9c6b997280688e14c444..d448e822c8cde15328dfb3ae9cf94c602a2bee7f 100644 --- a/panels/display/meson.build +++ b/panels/display/meson.build @@ -24,11 +24,14 @@ 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-panel.ui', + 'cc-display-settings.ui', + 'cc-night-light-dialog.ui', ) sources += gnome.compile_resources(