[gnome-control-center/wip/benzea/display-settings-widget: 8/9] display: Add settings widget for output configuration



commit 3c83c2be04edbc55f15aa9bfb3673d6f5045bb00
Author: Benjamin Berg <bberg redhat com>
Date:   Thu Jan 17 16:10:51 2019 +0100

    display: Add settings widget for output configuration
    
    This widget now uses libhandy for the rows.

 panels/display/cc-display-settings.c  | 765 ++++++++++++++++++++++++++++++++++
 panels/display/cc-display-settings.h  |  39 ++
 panels/display/cc-display-settings.ui |  64 +++
 panels/display/cc-value-object.c      | 155 +++++++
 panels/display/cc-value-object.h      |  21 +
 panels/display/display.gresource.xml  |   1 +
 panels/display/meson.build            |   5 +-
 7 files changed, 1049 insertions(+), 1 deletion(-)
---
diff --git a/panels/display/cc-display-settings.c b/panels/display/cc-display-settings.c
new file mode 100644
index 000000000..582481376
--- /dev/null
+++ b/panels/display/cc-display-settings.c
@@ -0,0 +1,765 @@
+/* cc-display-settings.c
+ *
+ * Copyright (C) 2007, 2008, 2018, 2019  Red Hat, Inc.
+ * Copyright (C) 2013 Intel, Inc.
+ *
+ * Written by: Benjamin Berg <bberg redhat com>
+ *
+ * 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 <http://www.gnu.org/licenses/>.
+ */
+
+#define HANDY_USE_UNSTABLE_API 1
+#include <handy.h>
+#include <glib/gi18n.h>
+#include <math.h>
+#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;
+
+  self->idle_udpate_id = 0;
+
+  if (!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);
+
+  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++)
+        {
+          CcValueObject *obj;
+
+          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))
+    {
+      CcDisplayMode *current_mode;
+      GList *item;
+      gdouble freq;
+
+      current_mode = cc_display_monitor_get_mode (self->selected_output);
+      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,
+                       cc_display_monitor_get_mode (self->selected_output));
+  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++)
+        {
+          CcDisplayMode *m;
+          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))
+    {
+      CcDisplayMode *mode = cc_display_monitor_get_mode (self->selected_output);
+      const gdouble *scales, *scale;
+
+      scales = cc_display_mode_get_supported_scales (mode);
+      for (scale = scales; *scale != 0.0; scale++)
+        {
+          CcValueObject *obj;
+          if (!display_mode_supported_at_scale (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;
+  CcValueObject *obj;
+
+  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;
+  CcDisplayMode *mode;
+
+  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;
+  CcDisplayMode *mode;
+
+  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;
+  CcValueObject *obj;
+
+  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_set_config (CcDisplaySettings *self,
+                                CcDisplayConfig      *config)
+{
+  const gchar *signals[] = { "rotation", "mode", "scale", "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_settings_set_selected_output (self, NULL);
+
+  g_object_notify_by_pspec (G_OBJECT (self), props[PROP_CONFIG]);
+}
+
+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, self->has_accelerometer);
+      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:
+      self->has_accelerometer = 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;
+}
+
+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_CONSTRUCT_ONLY | 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_CONSTRUCT_ONLY | 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 (CcDisplayConfig *config,
+                         gboolean         has_accelerometer)
+{
+  return g_object_new (CC_TYPE_DISPLAY_SETTINGS,
+                       "config", config,
+                       "has-accelerometer", has_accelerometer,
+                       NULL);
+}
+
+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 000000000..0910ccce3
--- /dev/null
+++ b/panels/display/cc-display-settings.h
@@ -0,0 +1,39 @@
+/* -*- mode: c; style: linux -*-
+ *
+ * Copyright (C) 2019 Red Hat, Inc.
+ *
+ * Written by: Benjamin Berg <bberg redhat com>
+ *
+ * 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 <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include <gtk/gtk.h>
+#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                 (CcDisplayConfig      *config,
+                                                             gboolean              has_accelerometer);
+
+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 000000000..14c5bdba4
--- /dev/null
+++ b/panels/display/cc-display-settings.ui
@@ -0,0 +1,64 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Generated with glade 3.22.0 -->
+<interface>
+  <requires lib="gtk+" version="3.20"/>
+  <requires lib="libhandy" version="0.0"/>
+  <template class="CcDisplaySettings" parent="GtkListBox">
+    <property name="visible">True</property>
+    <property name="can_focus">True</property>
+    <property name="selection_mode">none</property>
+    <child>
+      <object class="HdyComboRow" id="orientation_row">
+        <property name="width_request">100</property>
+        <property name="visible">True</property>
+        <property name="can_focus">True</property>
+        <property name="title">Orientation</property>
+        <signal name="notify::selected-index" handler="on_orientation_selection_changed_cb" swapped="no"/>
+      </object>
+    </child>
+    <child>
+      <object class="HdyComboRow" id="resolution_row">
+        <property name="width_request">100</property>
+        <property name="visible">True</property>
+        <property name="can_focus">True</property>
+        <property name="title">Resolution</property>
+        <signal name="notify::selected-index" handler="on_resolution_selection_changed_cb" swapped="no"/>
+      </object>
+    </child>
+    <child>
+      <object class="HdyComboRow" id="refresh_rate_row">
+        <property name="width_request">100</property>
+        <property name="visible">True</property>
+        <property name="can_focus">False</property>
+        <property name="title">Refresh Rate</property>
+        <signal name="notify::selected-index" handler="on_refresh_rate_selection_changed_cb" swapped="no"/>
+      </object>
+    </child>
+    <child>
+      <object class="HdyActionRow" id="underscanning_row">
+        <property name="width_request">100</property>
+        <property name="visible">True</property>
+        <property name="can_focus">True</property>
+        <property name="title">Adjust for TV</property>
+        <child type="action">
+          <object class="GtkSwitch" id="underscanning_switch">
+            <property name="visible">True</property>
+            <property name="can_focus">False</property>
+            <property name="halign">end</property>
+            <property name="valign">center</property>
+            <signal name="notify::active" handler="on_underscanning_switch_active_changed_cb" swapped="no"/>
+          </object>
+        </child>
+      </object>
+    </child>
+    <child>
+      <object class="HdyComboRow" id="scale_row">
+        <property name="width_request">100</property>
+        <property name="visible">True</property>
+        <property name="can_focus">True</property>
+        <property name="title">Scale</property>
+        <signal name="notify::selected-index" handler="on_scale_selection_changed_cb" swapped="no"/>
+      </object>
+    </child>
+  </template>
+</interface>
diff --git a/panels/display/cc-value-object.c b/panels/display/cc-value-object.c
new file mode 100644
index 000000000..a0a8a3e4d
--- /dev/null
+++ b/panels/display/cc-value-object.c
@@ -0,0 +1,155 @@
+#include "cc-value-object.h"
+#include <gobject/gvaluecollector.h>
+
+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];
+
+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)
+ *
+ */
+CcValueObject*
+cc_value_object_new_collect (GType type, ...)
+{
+  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);
+}
+
+static void
+cc_value_object_finalize (GObject *object)
+{
+  CcValueObject *self = (CcValueObject *)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)
+{
+}
+
+const GValue*
+cc_value_object_get_value (CcValueObject *self)
+{
+  return &self->value;
+}
+
+void
+cc_value_object_copy_value (CcValueObject *self,
+                            GValue        *dest_value)
+{
+  g_value_copy (&self->value, dest_value);
+}
+
+const gchar*
+cc_value_object_get_string (CcValueObject *self)
+{
+  return g_value_get_string (&self->value);
+}
+
+gchar*
+cc_value_object_dup_string (CcValueObject *self)
+{
+  return g_value_dup_string (&self->value);
+}
+
diff --git a/panels/display/cc-value-object.h b/panels/display/cc-value-object.h
new file mode 100644
index 000000000..392d93283
--- /dev/null
+++ b/panels/display/cc-value-object.h
@@ -0,0 +1,21 @@
+#pragma once
+
+#include <glib-object.h>
+
+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, ...);
+
+const GValue   *cc_value_object_get_value  (CcValueObject *self);
+void            cc_value_object_copy_value (CcValueObject *self,
+                                            GValue        *dest_value);
+
+const gchar    *cc_value_object_get_string  (CcValueObject *self);
+gchar          *cc_value_object_dup_string  (CcValueObject *self);
+
+G_END_DECLS
diff --git a/panels/display/display.gresource.xml b/panels/display/display.gresource.xml
index 5ecf8c596..70eedbaa9 100644
--- a/panels/display/display.gresource.xml
+++ b/panels/display/display.gresource.xml
@@ -1,6 +1,7 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <gresources>
   <gresource prefix="/org/gnome/control-center/display">
+    <file preprocess="xml-stripblanks">cc-display-settings.ui</file>
     <file preprocess="xml-stripblanks">cc-night-light-dialog.ui</file>
     <file>display-arrangement.css</file>
     <file>night-light.css</file>
diff --git a/panels/display/meson.build b/panels/display/meson.build
index bd4352b9b..8805f1f90 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',
+  'cc-value-object.c',
 )
 
 resource_data = files(
-  'cc-night-light-dialog.ui'
+  'cc-display-settings.ui',
+  'cc-night-light-dialog.ui',
 )
 
 sources += gnome.compile_resources(


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]