[gnome-control-center] display: Add natural light UI to the display panel



commit ef4dfe88964da393dc9cf2bca72e5da83f43df5b
Author: Richard Hughes <richard hughsie com>
Date:   Mon Feb 6 14:37:42 2017 +0000

    display: Add natural light UI to the display panel
    
    https://bugzilla.gnome.org/show_bug.cgi?id=778326

 configure.ac                             |    1 +
 panels/display/Makefile.am               |   19 +-
 panels/display/cc-display-panel.c        |   81 ++++
 panels/display/cc-natural-light-dialog.c |  683 ++++++++++++++++++++++++++++++
 panels/display/cc-natural-light-dialog.h |   37 ++
 panels/display/cc-natural-light-widget.c |  285 +++++++++++++
 panels/display/cc-natural-light-widget.h |   41 ++
 panels/display/display.gresource.xml     |    8 +
 panels/display/display.ui                |  549 ++++++++++++++++++++++++
 panels/display/icons/16x16/sunrise.png   |  Bin 0 -> 348 bytes
 panels/display/icons/16x16/sunset.png    |  Bin 0 -> 361 bytes
 11 files changed, 1703 insertions(+), 1 deletions(-)
---
diff --git a/configure.ac b/configure.ac
index 01dd4a8..bb87390 100644
--- a/configure.ac
+++ b/configure.ac
@@ -129,6 +129,7 @@ PKG_CHECK_MODULES(DATETIME_PANEL, $COMMON_MODULES
                   polkit-gobject-1 >= $POLKIT_REQUIRED_VERSION
                   gdk-pixbuf-2.0 >= $GDKPIXBUF_REQUIRED_VERSION)
 PKG_CHECK_MODULES(DISPLAY_PANEL, $COMMON_MODULES gnome-desktop-3.0 >= 3.1.0
+                  colord >= $COLORD_REQUIRED_VERSION
                   upower-glib >= 0.99.0)
 PKG_CHECK_MODULES(INFO_PANEL, $COMMON_MODULES libgtop-2.0
                  polkit-gobject-1 >= $POLKIT_REQUIRED_VERSION)
diff --git a/panels/display/Makefile.am b/panels/display/Makefile.am
index c03696f..a9b32a6 100644
--- a/panels/display/Makefile.am
+++ b/panels/display/Makefile.am
@@ -3,14 +3,29 @@ cappletname = display
 
 noinst_LTLIBRARIES = libdisplay.la
 
+BUILT_SOURCES =                        \
+       cc-display-resources.h  \
+       cc-display-resources.c
+
 libdisplay_la_SOURCES =                \
+       $(BUILT_SOURCES)        \
        cc-display-panel.c      \
        cc-display-panel.h      \
+       cc-natural-light-dialog.c \
+       cc-natural-light-dialog.h \
+       cc-natural-light-widget.c \
+       cc-natural-light-widget.h \
        scrollarea.c            \
        scrollarea.h
 
 libdisplay_la_LIBADD = $(PANEL_LIBS) $(DISPLAY_PANEL_LIBS) $(LIBM)
 
+resource_files = $(shell glib-compile-resources --sourcedir=$(srcdir) --sourcedir=$(srcdir)/icons/16x16 
--generate-dependencies $(srcdir)/display.gresource.xml)
+cc-display-resources.c: display.gresource.xml $(resource_files)
+       $(AM_V_GEN) glib-compile-resources --target=$@ --sourcedir=$(srcdir) 
--sourcedir=$(srcdir)/icons/16x16 --generate-source --c-name cc_display $<
+cc-display-resources.h: display.gresource.xml $(resource_files)
+       $(AM_V_GEN) glib-compile-resources --target=$@ --sourcedir=$(srcdir) 
--sourcedir=$(srcdir)/icons/16x16 --generate-header --c-name cc_display $<
+
 # You will need a recent intltool or the patch from this bug
 # http://bugzilla.gnome.org/show_bug.cgi?id=462312
 @INTLTOOL_POLICY_RULE@
@@ -34,9 +49,11 @@ desktop_DATA = $(Desktop_in_files:.desktop.in=.desktop)
 
 AM_CPPFLAGS   = $(PANEL_CFLAGS) \
             $(DISPLAY_PANEL_CFLAGS) \
+            -DDATADIR="\"$(datadir)\""\
             -DGNOMELOCALEDIR="\"$(datadir)/locale\""
 
-CLEANFILES = $(Desktop_in_files) $(desktop_DATA)
+CLEANFILES = $(Desktop_in_files) $(desktop_DATA) $(BUILT_SOURCES)
+EXTRA_DIST = $(resource_files) display.gresource.xml
 
 if MAINTAINER_MODE
 gtk_update_icon_cache = gtk-update-icon-cache -f -t $(datadir)/icons/hicolor
diff --git a/panels/display/cc-display-panel.c b/panels/display/cc-display-panel.c
index 026e4ba..b132850 100644
--- a/panels/display/cc-display-panel.c
+++ b/panels/display/cc-display-panel.c
@@ -34,6 +34,9 @@
 #include "shell/list-box-helper.h"
 #include <libupower-glib/upower.h>
 
+#include "cc-natural-light-dialog.h"
+#include "cc-display-resources.h"
+
 CC_PANEL_REGISTER (CcDisplayPanel, cc_display_panel)
 
 #define DISPLAY_PANEL_PRIVATE(o) \
@@ -84,6 +87,10 @@ struct _CcDisplayPanelPrivate
   GtkWidget *rotate_right_button;
   GtkWidget *dialog;
   GtkWidget *config_grid;
+  GtkWidget *natural_light_filter_label;
+  CcNaturalLightDialog *natural_light_dialog;
+
+  GSettings *settings_color;
 
   UpClient *up_client;
   gboolean lid_is_closed;
@@ -241,6 +248,8 @@ cc_display_panel_dispose (GObject *object)
   g_clear_object (&priv->up_client);
   g_clear_object (&priv->background);
   g_clear_object (&priv->thumbnail_factory);
+  g_clear_object (&priv->settings_color);
+  g_clear_object (&priv->natural_light_dialog);
 
   if (priv->dialog)
     {
@@ -2604,6 +2613,17 @@ show_setup_dialog (CcDisplayPanel *panel)
 }
 
 static void
+cc_display_panel_natural_light_activated (GtkListBox     *listbox,
+                                          GtkWidget      *row,
+                                          CcDisplayPanel *panel)
+{
+  CcDisplayPanelPrivate *priv = panel->priv;
+  GtkWindow *toplevel;
+  toplevel = GTK_WINDOW (cc_shell_get_toplevel (cc_panel_get_shell (CC_PANEL (panel))));
+  cc_natural_light_dialog_present (priv->natural_light_dialog, toplevel);
+}
+
+static void
 cc_display_panel_box_row_activated (GtkListBox     *listbox,
                                     GtkWidget      *row,
                                     CcDisplayPanel *panel)
@@ -2756,13 +2776,38 @@ sensor_proxy_vanished_cb (GDBusConnection *connection,
 }
 
 static void
+natural_light_enabled_recheck (CcDisplayPanel *self)
+{
+  CcDisplayPanelPrivate *priv = DISPLAY_PANEL_PRIVATE (self);
+  gboolean ret = g_settings_get_boolean (priv->settings_color,
+                                         "natural-light-enabled");
+  gtk_label_set_label (GTK_LABEL (priv->natural_light_filter_label),
+                       /* TRANSLATORS: the state of the natural light setting */
+                       ret ? _("On") : _("Off"));
+}
+
+static void
+settings_color_changed_cb (GSettings *settings, gchar *key, gpointer user_data)
+{
+  CcDisplayPanel *self = CC_DISPLAY_PANEL (user_data);
+  if (g_strcmp0 (key, "natural-light-enabled") == 0)
+    natural_light_enabled_recheck (self);
+}
+
+static void
 cc_display_panel_init (CcDisplayPanel *self)
 {
   CcDisplayPanelPrivate *priv;
   GtkWidget *frame, *vbox;
+  GtkListBoxRow *row;
+  GtkWidget *box;
+  GtkWidget *label;
+  GtkWidget *natural_light_listbox;
   GError *error = NULL;
   GSettings *settings;
 
+  g_resources_register (cc_display_get_resource ());
+
   priv = self->priv = DISPLAY_PANEL_PRIVATE (self);
 
   settings = g_settings_new ("org.gnome.desktop.background");
@@ -2772,6 +2817,7 @@ cc_display_panel_init (CcDisplayPanel *self)
 
   priv->thumbnail_factory = gnome_desktop_thumbnail_factory_new (GNOME_DESKTOP_THUMBNAIL_SIZE_NORMAL);
 
+  priv->natural_light_dialog = cc_natural_light_dialog_new ();
 
   priv->screen = gnome_rr_screen_new (gdk_screen_get_default (), &error);
   if (!priv->screen)
@@ -2814,6 +2860,36 @@ cc_display_panel_init (CcDisplayPanel *self)
   gtk_widget_set_halign (priv->arrange_button, GTK_ALIGN_CENTER);
 
   gtk_container_add (GTK_CONTAINER (vbox), priv->arrange_button);
+
+  /* natural light section */
+  frame = gtk_frame_new (NULL);
+  gtk_frame_set_shadow_type (GTK_FRAME (frame), GTK_SHADOW_IN);
+  natural_light_listbox = gtk_list_box_new ();
+  gtk_list_box_set_selection_mode (GTK_LIST_BOX (natural_light_listbox),
+                                   GTK_SELECTION_NONE);
+  gtk_container_add (GTK_CONTAINER (frame), natural_light_listbox);
+  g_signal_connect (natural_light_listbox, "row-activated",
+                    G_CALLBACK (cc_display_panel_natural_light_activated),
+                    self);
+  row = GTK_LIST_BOX_ROW (gtk_list_box_row_new ());
+  box = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 50);
+  gtk_container_add (GTK_CONTAINER (row), box);
+  gtk_container_add (GTK_CONTAINER (natural_light_listbox), GTK_WIDGET (row));
+  label = gtk_label_new (_("_Natural Light Filter"));
+  gtk_widget_set_halign (label, GTK_ALIGN_START);
+  gtk_label_set_use_underline (GTK_LABEL (label), TRUE);
+  gtk_widget_set_margin_start (label, 20);
+  gtk_widget_set_margin_end (label, 20);
+  gtk_widget_set_margin_top (label, 6);
+  gtk_widget_set_margin_bottom (label, 6);
+  gtk_box_pack_start (GTK_BOX (box), label, TRUE, TRUE, 0);
+  priv->natural_light_filter_label = label = gtk_label_new ("");
+  gtk_widget_set_halign (label, GTK_ALIGN_END);
+  gtk_widget_set_margin_start (label, 24);
+  gtk_widget_set_margin_end (label, 24);
+  gtk_box_pack_start (GTK_BOX (box), label, FALSE, TRUE, 0);
+  gtk_container_add (GTK_CONTAINER (vbox), frame);
+
   gtk_widget_show_all (vbox);
 
   on_screen_changed (self);
@@ -2838,6 +2914,11 @@ cc_display_panel_init (CcDisplayPanel *self)
   else
     g_clear_object (&self->priv->up_client);
 
+  priv->settings_color = g_settings_new ("org.gnome.settings-daemon.plugins.color");
+  g_signal_connect (priv->settings_color, "changed",
+                    G_CALLBACK (settings_color_changed_cb), self);
+  natural_light_enabled_recheck (self);
+
   g_signal_connect (self, "map", G_CALLBACK (mapped_cb), NULL);
 
   self->priv->shell_cancellable = g_cancellable_new ();
diff --git a/panels/display/cc-natural-light-dialog.c b/panels/display/cc-natural-light-dialog.c
new file mode 100644
index 0000000..9147698
--- /dev/null
+++ b/panels/display/cc-natural-light-dialog.c
@@ -0,0 +1,683 @@
+/*
+ * Copyright (C) 2017 Richard Hughes <richard hughsie com>
+ *
+ * Licensed under the GNU General Public License Version 2
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h"
+
+#include <gdesktop-enums.h>
+#include <gtk/gtk.h>
+#include <math.h>
+
+#include "cc-natural-light-dialog.h"
+#include "cc-natural-light-widget.h"
+
+struct _CcNaturalLightDialog {
+  GObject              parent;
+  GtkBuilder          *builder;
+  GSettings           *settings_display;
+  GSettings           *settings_clock;
+  GDBusProxy          *proxy_color;
+  GDBusProxy          *proxy_color_props;
+  GCancellable        *cancellable;
+  GtkWidget           *natural_light_widget;
+  GtkWidget           *main_window;
+  gboolean             ignore_value_changed;
+  guint                timer_id;
+  GDesktopClockFormat  clock_format;
+};
+
+G_DEFINE_TYPE (CcNaturalLightDialog, cc_natural_light_dialog, G_TYPE_OBJECT);
+
+#define CLOCK_SCHEMA     "org.gnome.desktop.interface"
+#define DISPLAY_SCHEMA   "org.gnome.settings-daemon.plugins.color"
+#define CLOCK_FORMAT_KEY "clock-format"
+
+void
+cc_natural_light_dialog_present (CcNaturalLightDialog *self, GtkWindow *parent)
+{
+  GtkWindow *window = GTK_WINDOW (self->main_window);
+  if (parent != NULL)
+    {
+      gtk_window_set_transient_for (window, parent);
+      gtk_window_set_modal (window, TRUE);
+    }
+  gtk_window_present (window);
+}
+
+static void
+cc_natural_light_dialog_finalize (GObject *object)
+{
+  CcNaturalLightDialog *self = CC_NATURAL_LIGHT_DIALOG (object);
+
+  if (self->cancellable != NULL)
+    {
+      g_cancellable_cancel (self->cancellable);
+      g_clear_object (&self->cancellable);
+    }
+
+  if (self->main_window)
+    {
+      gtk_widget_destroy (self->main_window);
+      self->main_window = NULL;
+    }
+
+  g_object_unref (self->builder);
+  g_object_unref (self->proxy_color);
+  g_object_unref (self->proxy_color_props);
+  g_object_unref (self->settings_display);
+  g_object_unref (self->settings_clock);
+  if (self->timer_id > 0)
+    g_source_remove (self->timer_id);
+
+  G_OBJECT_CLASS (cc_natural_light_dialog_parent_class)->finalize (object);
+}
+
+static void
+cc_natural_light_dialog_class_init (CcNaturalLightDialogClass *klass)
+{
+  GObjectClass *object_class = G_OBJECT_CLASS (klass);
+  object_class->finalize = cc_natural_light_dialog_finalize;
+}
+
+static gdouble
+frac_day_from_dt (GDateTime *dt)
+{
+  return g_date_time_get_hour (dt) +
+          (gdouble) g_date_time_get_minute (dt) / 60.f +
+          (gdouble) g_date_time_get_second (dt) / 3600.f;
+}
+
+static void
+dialog_adjustments_set_frac_hours (CcNaturalLightDialog *self,
+                            gdouble value,
+                            const gchar *id_hours,
+                            const gchar *id_mins,
+                            const gchar *id_stack)
+{
+  GtkAdjustment *adj;
+  gdouble hours;
+  gdouble mins = 0.f;
+  gboolean is_pm = FALSE;
+  gboolean is_24h;
+  GtkWidget *widget;
+
+  /* display the right thing for AM/PM */
+  is_24h = self->clock_format == G_DESKTOP_CLOCK_FORMAT_24H;
+  hours = floor (value);
+  if (!is_24h)
+    {
+      if (hours > 12)
+        {
+          hours -= 12;
+          is_pm = TRUE;
+        }
+      else if (hours < 1.0)
+        {
+          hours += 12;
+          is_pm = FALSE;
+        }
+      else if (hours == 12.f)
+        {
+          is_pm = TRUE;
+        }
+    }
+  if (value > 0.f)
+    {
+      mins = fmod (value, hours) * 60.f;
+      mins = fmod (mins, 60.f);
+    }
+  g_debug ("setting adjustment %.3f to %.0f:%02.0f", value, hours, mins);
+
+  self->ignore_value_changed = TRUE;
+  adj = GTK_ADJUSTMENT (gtk_builder_get_object (self->builder, id_hours));
+  gtk_adjustment_set_value (GTK_ADJUSTMENT (adj), hours);
+  adj = GTK_ADJUSTMENT (gtk_builder_get_object (self->builder, id_mins));
+  gtk_adjustment_set_value (GTK_ADJUSTMENT (adj), mins);
+  self->ignore_value_changed = FALSE;
+
+  widget = GTK_WIDGET (gtk_builder_get_object (self->builder, id_stack));
+  if (is_24h)
+    gtk_stack_set_visible_child_name (GTK_STACK (widget), "blank");
+  else
+    gtk_stack_set_visible_child_name (GTK_STACK (widget), is_pm ? "pm" : "am");
+}
+
+static void
+dialog_update_state (CcNaturalLightDialog *self)
+{
+  GtkWidget *widget;
+  gboolean automatic;
+  gboolean disabled_until_tomorrow = FALSE;
+  gboolean enabled;
+  gdouble value = 0.f;
+  g_autoptr(GDateTime) dt = g_date_time_new_now_local ();
+
+  /* only show the infobar if we are disabled */
+  if (self->proxy_color != NULL)
+    {
+      g_autoptr(GVariant) disabled = NULL;
+      disabled = g_dbus_proxy_get_cached_property (self->proxy_color,
+                                                   "DisabledUntilTomorrow");
+      if (disabled != NULL)
+        disabled_until_tomorrow = g_variant_get_boolean (disabled);
+    }
+  widget = GTK_WIDGET (gtk_builder_get_object (self->builder, "infobar_disabled"));
+  gtk_widget_set_visible (widget, disabled_until_tomorrow);
+
+  /* make things insensitive if the switch is disabled */
+  widget = GTK_WIDGET (gtk_builder_get_object (self->builder, "switch_enable"));
+  enabled = gtk_switch_get_state (GTK_SWITCH (widget));
+  widget = GTK_WIDGET (gtk_builder_get_object (self->builder, "radio_automatic"));
+  gtk_widget_set_sensitive (widget, enabled);
+  automatic = gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (widget));
+  widget = GTK_WIDGET (gtk_builder_get_object (self->builder, "radio_manual"));
+  gtk_widget_set_sensitive (widget, enabled);
+  widget = GTK_WIDGET (gtk_builder_get_object (self->builder, "box_manual"));
+  gtk_widget_set_sensitive (widget, enabled && !automatic);
+
+  /* set from */
+  if (automatic && self->proxy_color != NULL)
+    {
+      g_autoptr(GVariant) sunset = NULL;
+      sunset = g_dbus_proxy_get_cached_property (self->proxy_color, "Sunset");
+      if (sunset != NULL)
+        {
+          value = g_variant_get_double (sunset);
+        }
+      else
+        {
+          value = 16.0f;
+          g_warning ("no sunset data, using %02.2f", value);
+        }
+    }
+  else
+    {
+      value = g_settings_get_double (self->settings_display, "natural-light-schedule-from");
+      value = fmod (value, 24.f);
+    }
+  dialog_adjustments_set_frac_hours (self, value,
+                                    "adjustment_from_hours",
+                                    "adjustment_from_minutes",
+                                    "stack_from");
+  cc_natural_light_widget_set_from (CC_NATURAL_LIGHT_WIDGET (self->natural_light_widget), value);
+
+  /* set to */
+  if (automatic && self->proxy_color != NULL)
+    {
+      g_autoptr(GVariant) sunset = NULL;
+      sunset = g_dbus_proxy_get_cached_property (self->proxy_color, "Sunrise");
+      if (sunset != NULL)
+        {
+          value = g_variant_get_double (sunset);
+        }
+      else
+        {
+          value = 8.0f;
+          g_warning ("no sunrise data, using %02.2f", value);
+        }
+    }
+  else
+    {
+      value = g_settings_get_double (self->settings_display, "natural-light-schedule-to");
+      value = fmod (value, 24.f);
+    }
+  dialog_adjustments_set_frac_hours (self, value,
+                                     "adjustment_to_hours",
+                                     "adjustment_to_minutes",
+                                     "stack_to");
+  cc_natural_light_widget_set_to (CC_NATURAL_LIGHT_WIDGET (self->natural_light_widget), value);
+
+  /* set new time */
+  cc_natural_light_widget_set_now (CC_NATURAL_LIGHT_WIDGET (self->natural_light_widget),
+                                   frac_day_from_dt (dt));
+}
+
+static gboolean
+dialog_tick_cb (gpointer user_data)
+{
+  CcNaturalLightDialog *self = (CcNaturalLightDialog *) user_data;
+  dialog_update_state (self);
+  return G_SOURCE_CONTINUE;
+}
+
+static void
+dialog_enabled_notify_cb (GtkSwitch *sw, GParamSpec *pspec, CcNaturalLightDialog *self)
+{
+  g_settings_set_boolean (self->settings_display, "natural-light-enabled",
+                          gtk_switch_get_active (sw));
+  dialog_update_state (self);
+}
+
+static void
+dialog_mode_changed_cb (GtkToggleButton *togglebutton, CcNaturalLightDialog *self)
+{
+  GtkWidget *widget;
+  gboolean ret;
+
+  widget = GTK_WIDGET (gtk_builder_get_object (self->builder, "radio_automatic"));
+  ret = gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (widget));
+  g_settings_set_boolean (self->settings_display, "natural-light-schedule-automatic", ret);
+
+  dialog_update_state (self);
+}
+
+static void
+dialog_undisable_call_cb (GObject *source_object, GAsyncResult *res, gpointer user_data)
+{
+  CcNaturalLightDialog *self = (CcNaturalLightDialog *) user_data;
+  g_autoptr(GVariant) val = NULL;
+  g_autoptr(GError) error = NULL;
+
+  val = g_dbus_proxy_call_finish (G_DBUS_PROXY (source_object),
+                                  res, &error);
+  if (val == NULL)
+    {
+      g_warning ("failed to undisable: %s", error->message);
+      return;
+    }
+  dialog_update_state (self);
+}
+
+static void
+dialog_undisable_clicked_cb (GtkButton *button, CcNaturalLightDialog *self)
+{
+  g_dbus_proxy_call (self->proxy_color_props,
+                     "Set",
+                     g_variant_new ("(ssv)",
+                                    "org.gnome.SettingsDaemon.Color",
+                                    "DisabledUntilTomorrow",
+                                    g_variant_new_boolean (FALSE)),
+                     G_DBUS_CALL_FLAGS_NONE,
+                     5000,
+                     self->cancellable,
+                     dialog_undisable_call_cb,
+                     self);
+}
+
+static gdouble
+dialog_adjustments_get_frac_hours (CcNaturalLightDialog *self,
+                                   const gchar *id_hours,
+                                   const gchar *id_mins,
+                                   const gchar *id_stack)
+{
+  GtkAdjustment *adj;
+  GtkWidget *widget;
+  gdouble value;
+  adj = GTK_ADJUSTMENT (gtk_builder_get_object (self->builder, id_hours));
+  value = gtk_adjustment_get_value (adj);
+  adj = GTK_ADJUSTMENT (gtk_builder_get_object (self->builder, id_mins));
+  value += gtk_adjustment_get_value (adj) / 60.0f;
+  widget = GTK_WIDGET (gtk_builder_get_object (self->builder, id_stack));
+  if (g_strcmp0 (gtk_stack_get_visible_child_name (GTK_STACK (widget)), "pm") == 0)
+    value += 12.f;
+  return value;
+}
+
+static void
+dialog_time_from_value_changed_cb (GtkAdjustment *adjustment, CcNaturalLightDialog *self)
+{
+  gdouble value;
+
+  if (self->ignore_value_changed)
+    return;
+
+  value = dialog_adjustments_get_frac_hours (self,
+                                      "adjustment_from_hours",
+                                      "adjustment_from_minutes",
+                                      "stack_from");
+  if (value >= 24.f)
+    value = fmod (value, 24);
+  g_debug ("new value = %.3f", value);
+  g_settings_set_double (self->settings_display, "natural-light-schedule-from", value);
+  dialog_update_state (self);
+}
+
+static void
+dialog_time_to_value_changed_cb (GtkAdjustment *adjustment, CcNaturalLightDialog *self)
+{
+  gdouble value;
+
+  if (self->ignore_value_changed)
+    return;
+
+  value = dialog_adjustments_get_frac_hours (self,
+                                      "adjustment_to_hours",
+                                      "adjustment_to_minutes",
+                                      "stack_to");
+  if (value >= 24.f)
+    value = fmod (value, 24);
+  g_debug ("new value = %.3f", value);
+  g_settings_set_double (self->settings_display, "natural-light-schedule-to", value);
+  dialog_update_state (self);
+}
+
+static void
+dialog_got_proxy_cb (GObject *source_object, GAsyncResult *res, gpointer user_data)
+{
+  CcNaturalLightDialog *self = (CcNaturalLightDialog *) user_data;
+  g_autoptr(GError) error = NULL;
+  self->proxy_color = g_dbus_proxy_new_for_bus_finish (res, &error);
+  if (self->proxy_color == NULL)
+    {
+      g_warning ("failed to connect to g-s-d: %s", error->message);
+      return;
+    }
+  dialog_update_state (self);
+  self->timer_id = g_timeout_add_seconds (10, dialog_tick_cb, self);
+}
+
+static void
+dialog_got_proxy_props_cb (GObject *source_object, GAsyncResult *res, gpointer user_data)
+{
+  CcNaturalLightDialog *self = (CcNaturalLightDialog *) user_data;
+  g_autoptr(GError) error = NULL;
+  self->proxy_color_props = g_dbus_proxy_new_for_bus_finish (res, &error);
+  if (self->proxy_color_props == NULL)
+    {
+      g_warning ("failed to connect to g-s-d: %s", error->message);
+      return;
+    }
+}
+
+static gboolean
+dialog_format_minutes_combobox (GtkSpinButton *spin, CcNaturalLightDialog *self)
+{
+  GtkAdjustment *adjustment;
+  g_autofree gchar *text = NULL;
+  adjustment = gtk_spin_button_get_adjustment (spin);
+  text = g_strdup_printf ("%02.0f", gtk_adjustment_get_value (adjustment));
+  gtk_entry_set_text (GTK_ENTRY (spin), text);
+  return TRUE;
+}
+
+static gboolean
+dialog_format_hours_combobox (GtkSpinButton *spin, CcNaturalLightDialog *self)
+{
+  GtkAdjustment *adjustment;
+  g_autofree gchar *text = NULL;
+  adjustment = gtk_spin_button_get_adjustment (spin);
+  if (self->clock_format == G_DESKTOP_CLOCK_FORMAT_12H)
+    text = g_strdup_printf ("%.0f", gtk_adjustment_get_value (adjustment));
+  else
+    text = g_strdup_printf ("%02.0f", gtk_adjustment_get_value (adjustment));
+  gtk_entry_set_text (GTK_ENTRY (spin), text);
+  return TRUE;
+}
+
+static void
+dialog_update_adjustments (CcNaturalLightDialog *self)
+{
+  GtkAdjustment *adj;
+  GtkWidget *widget;
+
+  /* from */
+  adj = GTK_ADJUSTMENT (gtk_builder_get_object (self->builder, "adjustment_from_hours"));
+  if (self->clock_format == G_DESKTOP_CLOCK_FORMAT_24H)
+    {
+      gtk_adjustment_set_lower (adj, 0);
+      gtk_adjustment_set_upper (adj, 23);
+    }
+  else
+    {
+      if (gtk_adjustment_get_value (adj) > 12)
+        {
+          widget = GTK_WIDGET (gtk_builder_get_object (self->builder, "stack_from"));
+          gtk_stack_set_visible_child_name (GTK_STACK (widget), "pm");
+        }
+      gtk_adjustment_set_lower (adj, 1);
+      gtk_adjustment_set_upper (adj, 12);
+    }
+
+  /* to */
+  adj = GTK_ADJUSTMENT (gtk_builder_get_object (self->builder, "adjustment_to_hours"));
+  if (self->clock_format == G_DESKTOP_CLOCK_FORMAT_24H)
+    {
+      gtk_adjustment_set_lower (adj, 0);
+      gtk_adjustment_set_upper (adj, 23);
+    }
+  else
+    {
+      if (gtk_adjustment_get_value (adj) > 12)
+        {
+          widget = GTK_WIDGET (gtk_builder_get_object (self->builder, "stack_to"));
+          gtk_stack_set_visible_child_name (GTK_STACK (widget), "pm");
+        }
+      gtk_adjustment_set_lower (adj, 1);
+      gtk_adjustment_set_upper (adj, 12);
+    }
+}
+
+static void
+dialog_clock_settings_changed_cb (GSettings *settings_display, gchar *key, CcNaturalLightDialog *self)
+{
+  GtkAdjustment *adj;
+  GtkWidget *widget;
+
+  self->clock_format = g_settings_get_enum (settings_display, CLOCK_FORMAT_KEY);
+
+  /* uncontionally widen this to avoid truncation */
+  adj = GTK_ADJUSTMENT (gtk_builder_get_object (self->builder, "adjustment_from_hours"));
+  gtk_adjustment_set_lower (adj, 0);
+  gtk_adjustment_set_upper (adj, 23);
+  adj = GTK_ADJUSTMENT (gtk_builder_get_object (self->builder, "adjustment_to_hours"));
+  gtk_adjustment_set_lower (adj, 0);
+  gtk_adjustment_set_upper (adj, 23);
+
+  /* update spinbuttons */
+  widget = GTK_WIDGET (gtk_builder_get_object (self->builder, "spinbutton_from_hours"));
+  gtk_spin_button_update (GTK_SPIN_BUTTON (widget));
+  widget = GTK_WIDGET (gtk_builder_get_object (self->builder, "spinbutton_to_hours"));
+  gtk_spin_button_update (GTK_SPIN_BUTTON (widget));
+
+  /* update UI */
+  dialog_update_state (self);
+  dialog_update_adjustments (self);
+}
+
+static void
+dialog_am_pm_from_button_clicked_cb (GtkButton *button, CcNaturalLightDialog *self)
+{
+  gdouble value;
+  value = g_settings_get_double (self->settings_display, "natural-light-schedule-from");
+  if (value > 12.f)
+    value -= 12.f;
+  else
+    value += 12.f;
+  if (value >= 24.f)
+    value = fmod (value, 24);
+  g_settings_set_double (self->settings_display, "natural-light-schedule-from", value);
+  g_debug ("new value = %.3f", value);
+  dialog_update_state (self);
+}
+
+static void
+dialog_am_pm_to_button_clicked_cb (GtkButton *button, CcNaturalLightDialog *self)
+{
+  gdouble value;
+  value = g_settings_get_double (self->settings_display, "natural-light-schedule-to");
+  if (value > 12.f)
+    value -= 12.f;
+  else
+    value += 12.f;
+  if (value >= 24.f)
+    value = fmod (value, 24);
+  g_settings_set_double (self->settings_display, "natural-light-schedule-to", value);
+  g_debug ("new value = %.3f", value);
+  dialog_update_state (self);
+}
+
+static gboolean
+dialog_delete_event_cb (GtkWidget *widget,
+                        GdkEvent *event,
+                        CcNaturalLightDialog *self)
+{
+  gtk_widget_hide (widget);
+  return TRUE;
+}
+
+static void
+cc_natural_light_dialog_init (CcNaturalLightDialog *self)
+{
+  GdkScreen *screen;
+  GtkAdjustment *adj;
+  GtkBox *box;
+  GtkWidget *sw;
+  GtkWidget *widget;
+  g_autoptr(GError) error = NULL;
+  g_autoptr(GtkCssProvider) provider = NULL;
+
+  self->cancellable = g_cancellable_new ();
+  self->settings_display = g_settings_new (DISPLAY_SCHEMA);
+
+  self->builder = gtk_builder_new ();
+  gtk_builder_add_from_resource (self->builder,
+                                 "/org/gnome/control-center/display/display.ui",
+                                 &error);
+
+  if (error != NULL)
+    {
+      g_critical ("Could not load interface file: %s", error->message);
+      g_error_free (error);
+      return;
+    }
+
+  /* connect widgets */
+  sw = GTK_WIDGET (gtk_builder_get_object (self->builder, "switch_enable"));
+  gtk_switch_set_active (GTK_SWITCH (sw),
+                         g_settings_get_boolean (self->settings_display, "natural-light-enabled"));
+  g_signal_connect (sw, "notify::active",
+                    G_CALLBACK (dialog_enabled_notify_cb), self);
+  g_settings_bind_writable (self->settings_display, "natural-light-enabled",
+                            sw, "sensitive",
+                            FALSE);
+  widget = GTK_WIDGET (gtk_builder_get_object (self->builder, "radio_automatic"));
+  g_signal_connect (widget, "toggled",
+                    G_CALLBACK (dialog_mode_changed_cb), self);
+  widget = GTK_WIDGET (gtk_builder_get_object (self->builder, "radio_manual"));
+  g_signal_connect (widget, "toggled",
+                    G_CALLBACK (dialog_mode_changed_cb), self);
+  adj = GTK_ADJUSTMENT (gtk_builder_get_object (self->builder, "adjustment_from_hours"));
+  g_signal_connect (adj, "value-changed",
+                    G_CALLBACK (dialog_time_from_value_changed_cb), self);
+  adj = GTK_ADJUSTMENT (gtk_builder_get_object (self->builder, "adjustment_from_minutes"));
+  g_signal_connect (adj, "value-changed",
+                    G_CALLBACK (dialog_time_from_value_changed_cb), self);
+  adj = GTK_ADJUSTMENT (gtk_builder_get_object (self->builder, "adjustment_to_hours"));
+  g_signal_connect (adj, "value-changed",
+                    G_CALLBACK (dialog_time_to_value_changed_cb), self);
+  adj = GTK_ADJUSTMENT (gtk_builder_get_object (self->builder, "adjustment_to_minutes"));
+  g_signal_connect (adj, "value-changed",
+                    G_CALLBACK (dialog_time_to_value_changed_cb), self);
+  widget = GTK_WIDGET (gtk_builder_get_object (self->builder, "button_undisable"));
+  g_signal_connect (widget, "clicked",
+                    G_CALLBACK (dialog_undisable_clicked_cb), self);
+
+  widget = GTK_WIDGET (gtk_builder_get_object (self->builder, "button_from_pm"));
+  g_signal_connect (widget, "clicked",
+                    G_CALLBACK (dialog_am_pm_from_button_clicked_cb), self);
+  widget = GTK_WIDGET (gtk_builder_get_object (self->builder, "button_from_am"));
+  g_signal_connect (widget, "clicked",
+                    G_CALLBACK (dialog_am_pm_from_button_clicked_cb), self);
+  widget = GTK_WIDGET (gtk_builder_get_object (self->builder, "button_to_pm"));
+  g_signal_connect (widget, "clicked",
+                    G_CALLBACK (dialog_am_pm_to_button_clicked_cb), self);
+  widget = GTK_WIDGET (gtk_builder_get_object (self->builder, "button_to_am"));
+  g_signal_connect (widget, "clicked",
+                    G_CALLBACK (dialog_am_pm_to_button_clicked_cb), self);
+
+  self->main_window = GTK_WIDGET (gtk_builder_get_object (self->builder, "window_natural_light"));
+  g_signal_connect (self->main_window, "delete-event",
+                    G_CALLBACK (dialog_delete_event_cb), self);
+
+  /* use custom CSS */
+  provider = gtk_css_provider_new ();
+  if (!gtk_css_provider_load_from_data (provider,
+                                        ".padded-spinbutton {\n"
+                                        "    font-size: 110%;\n"
+                                        "    min-width: 50px;\n"
+                                        "}\n"
+                                        ".unpadded-button {\n"
+                                        "    padding: 6px;\n"
+                                        "}\n",
+                                        -1,
+                                        &error))
+    {
+      g_error ("Failed to load CSS: %s", error->message);
+    }
+  widget = GTK_WIDGET (gtk_builder_get_object (self->builder, "spinbutton_from_hours"));
+  screen = gtk_widget_get_screen (widget);
+  gtk_style_context_add_provider_for_screen (screen, GTK_STYLE_PROVIDER (provider),
+                                             GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);
+
+  widget = GTK_WIDGET (gtk_builder_get_object (self->builder, "spinbutton_from_hours"));
+  g_signal_connect (widget, "output",
+                    G_CALLBACK (dialog_format_hours_combobox), self);
+  widget = GTK_WIDGET (gtk_builder_get_object (self->builder, "spinbutton_from_minutes"));
+  g_signal_connect (widget, "output",
+                    G_CALLBACK (dialog_format_minutes_combobox), self);
+  widget = GTK_WIDGET (gtk_builder_get_object (self->builder, "spinbutton_to_hours"));
+  g_signal_connect (widget, "output",
+                    G_CALLBACK (dialog_format_hours_combobox), self);
+  widget = GTK_WIDGET (gtk_builder_get_object (self->builder, "spinbutton_to_minutes"));
+  g_signal_connect (widget, "output",
+                    G_CALLBACK (dialog_format_minutes_combobox), self);
+
+  /* add custom widget */
+  self->natural_light_widget = cc_natural_light_widget_new ();
+  gtk_widget_set_size_request (self->natural_light_widget, -1, 34);
+  box = GTK_BOX (gtk_builder_get_object (self->builder, "box_content"));
+  gtk_box_pack_start (box, self->natural_light_widget, FALSE, FALSE, 0);
+  gtk_widget_show (self->natural_light_widget);
+
+  g_dbus_proxy_new_for_bus (G_BUS_TYPE_SESSION,
+                            G_DBUS_PROXY_FLAGS_NONE,
+                            NULL,
+                            "org.gnome.SettingsDaemon.Color",
+                            "/org/gnome/SettingsDaemon/Color",
+                            "org.gnome.SettingsDaemon.Color",
+                            self->cancellable,
+                            dialog_got_proxy_cb,
+                            self);
+
+  g_dbus_proxy_new_for_bus (G_BUS_TYPE_SESSION,
+                            G_DBUS_PROXY_FLAGS_NONE,
+                            NULL,
+                            "org.gnome.SettingsDaemon.Color",
+                            "/org/gnome/SettingsDaemon/Color",
+                            "org.freedesktop.DBus.Properties",
+                            self->cancellable,
+                            dialog_got_proxy_props_cb,
+                            self);
+
+  /* clock settings_display */
+  self->settings_clock = g_settings_new (CLOCK_SCHEMA);
+  self->clock_format = g_settings_get_enum (self->settings_clock, CLOCK_FORMAT_KEY);
+  dialog_update_adjustments (self);
+  g_signal_connect (self->settings_clock, "changed::" CLOCK_FORMAT_KEY,
+                    G_CALLBACK (dialog_clock_settings_changed_cb), self);
+
+  dialog_update_state (self);
+}
+
+CcNaturalLightDialog *
+cc_natural_light_dialog_new (void)
+{
+  return g_object_new (CC_TYPE_NATURAL_LIGHT_DIALOG, NULL);
+}
+
diff --git a/panels/display/cc-natural-light-dialog.h b/panels/display/cc-natural-light-dialog.h
new file mode 100644
index 0000000..8f5802a
--- /dev/null
+++ b/panels/display/cc-natural-light-dialog.h
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2017 Richard Hughes <richard hughsie com>
+ *
+ * Licensed under the GNU General Public License Version 2
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef __CC_NATURAL_LIGHT_DIALOG_H__
+#define __CC_NATURAL_LIGHT_DIALOG_H__
+
+#include <gtk/gtk.h>
+
+G_BEGIN_DECLS
+
+#define CC_TYPE_NATURAL_LIGHT_DIALOG (cc_natural_light_dialog_get_type ())
+G_DECLARE_FINAL_TYPE (CcNaturalLightDialog, cc_natural_light_dialog, CC, NATURAL_LIGHT_DIALOG, GObject)
+
+CcNaturalLightDialog  *cc_natural_light_dialog_new      (void);
+void                   cc_natural_light_dialog_present  (CcNaturalLightDialog *self,
+                                                         GtkWindow            *parent);
+
+G_END_DECLS
+
+#endif
diff --git a/panels/display/cc-natural-light-widget.c b/panels/display/cc-natural-light-widget.c
new file mode 100644
index 0000000..cb9be5a
--- /dev/null
+++ b/panels/display/cc-natural-light-widget.c
@@ -0,0 +1,285 @@
+/*
+ * Copyright (C) 2017 Richard Hughes <richard hughsie com>
+ *
+ * Licensed under the GNU General Public License Version 2
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h"
+
+#include <gtk/gtk.h>
+#include <colord.h>
+
+#include "cc-natural-light-widget.h"
+#include "cc-display-resources.h"
+
+struct _CcNaturalLightWidget {
+  GtkDrawingArea   parent;
+  gdouble          to;
+  gdouble          from;
+  gdouble          now;
+  cairo_surface_t *surface_sunrise;
+  cairo_surface_t *surface_sunset;
+};
+
+G_DEFINE_TYPE (CcNaturalLightWidget, cc_natural_light_widget, GTK_TYPE_DRAWING_AREA);
+
+static gboolean cc_natural_light_widget_draw (GtkWidget *widget, cairo_t *cr);
+
+void
+cc_natural_light_widget_set_to (CcNaturalLightWidget *self, gdouble to)
+{
+  self->to = to;
+  gtk_widget_queue_draw (GTK_WIDGET (self));
+}
+
+void
+cc_natural_light_widget_set_from (CcNaturalLightWidget *self, gdouble from)
+{
+  self->from = from;
+  gtk_widget_queue_draw (GTK_WIDGET (self));
+}
+
+void
+cc_natural_light_widget_set_now (CcNaturalLightWidget *self, gdouble now)
+{
+  self->now = now;
+  gtk_widget_queue_draw (GTK_WIDGET (self));
+}
+
+
+static void
+cc_natural_light_widget_finalize (GObject *object)
+{
+  CcNaturalLightWidget *self = CC_NATURAL_LIGHT_WIDGET (object);
+
+  g_clear_pointer (&self->surface_sunrise, (GDestroyNotify) cairo_surface_destroy);
+  g_clear_pointer (&self->surface_sunset, (GDestroyNotify) cairo_surface_destroy);
+
+  G_OBJECT_CLASS (cc_natural_light_widget_parent_class)->finalize (object);
+}
+
+static void
+cc_natural_light_widget_class_init (CcNaturalLightWidgetClass *klass)
+{
+  GObjectClass *object_class = G_OBJECT_CLASS (klass);
+  GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
+  object_class->finalize = cc_natural_light_widget_finalize;
+  widget_class->draw = cc_natural_light_widget_draw;
+}
+
+static cairo_status_t
+_png_read_func (void *closure, unsigned char *data, unsigned int length)
+{
+  GInputStream *stream = G_INPUT_STREAM (closure);
+  gssize read;
+  g_autoptr(GError) error = NULL;
+  read = g_input_stream_read (stream, data, (gsize) length, NULL, &error);
+  if (read < 0)
+    {
+      g_warning ("failed to read form stream: %s", error->message);
+      return CAIRO_STATUS_READ_ERROR;
+    }
+  return CAIRO_STATUS_SUCCESS;
+}
+
+static cairo_surface_t *
+read_surface_from_resource (const gchar *path)
+{
+  g_autoptr(GError) error = NULL;
+  g_autoptr(GInputStream) stream = NULL;
+  stream = g_resource_open_stream (cc_display_get_resource (), path,
+                                   G_RESOURCE_LOOKUP_FLAGS_NONE, &error);
+  if (stream == NULL)
+    {
+      g_error ("failed to load PNG data: %s", error->message);
+      return NULL;
+    }
+  return cairo_image_surface_create_from_png_stream (_png_read_func, stream);
+}
+
+static void
+cc_natural_light_widget_init (CcNaturalLightWidget *self)
+{
+  self->to = 8;
+  self->from = 16;
+  self->now = 11;
+  self->surface_sunrise = read_surface_from_resource ("/org/gnome/control-center/display/sunrise.png");
+  self->surface_sunset = read_surface_from_resource ("/org/gnome/control-center/display/sunset.png");
+}
+
+static gboolean
+is_frac_day_between (gdouble value, gdouble to, gdouble from)
+{
+  /* wraparound to the next day */
+  if (from < to)
+    from += 24;
+
+  /* wraparound to the previous day */
+  if (value < from && value < to)
+    value += 24;
+
+  /* test limits */
+  return value > to && value <= from;
+}
+
+
+static void
+rounded_rectangle (cairo_t *cr,
+                   gdouble  x,
+                   gdouble  y,
+                   gdouble  radius,
+                   gdouble  width,
+                   gdouble  height)
+{
+  gdouble degrees = G_PI / 180.0;
+
+  cairo_new_sub_path (cr);
+  cairo_arc (cr,
+             x + width - radius,
+             y + radius,
+             radius,
+             -90 * degrees,
+             0 * degrees);
+  cairo_arc (cr,
+             x + width - radius,
+             y + height - radius,
+             radius,
+             0 * degrees,
+             90 * degrees);
+  cairo_arc (cr,
+             x + radius,
+             y + height - radius,
+             radius,
+             90 * degrees,
+             180 * degrees);
+  cairo_arc (cr,
+             x + radius,
+             y + radius,
+             radius,
+             180 * degrees,
+             270 * degrees);
+  cairo_close_path (cr);
+}
+
+static gboolean
+cc_natural_light_widget_draw (GtkWidget *widget, cairo_t *cr)
+{
+  CdColorRGB color;
+  CdColorRGB color_temperature;
+  CdColorRGB color_unity;
+  GtkAllocation rect;
+  const guint arrow_sz = 5; /* px */
+  const guint icon_sz = 16; /* px */
+  guint line_x = 0; /* px */
+  const guint bar_voffset = arrow_sz + icon_sz;
+
+  CcNaturalLightWidget *self = (CcNaturalLightWidget*) widget;
+  g_return_val_if_fail (self != NULL, FALSE);
+  g_return_val_if_fail (CC_IS_NATURAL_LIGHT_WIDGET (self), FALSE);
+
+  cd_color_rgb_set (&color_temperature, 0.992, 0.796, 0.612);
+  cd_color_rgb_set (&color_unity, 0.773, 0.862, 0.953);
+
+  gtk_widget_get_allocation (widget, &rect);
+
+  /* clip to a rounded rectangle */
+  cairo_save (cr);
+  cairo_set_line_width (cr, 1);
+  rounded_rectangle (cr, 0, bar_voffset, 6,
+                     rect.width, rect.height - bar_voffset - 1);
+  cairo_clip (cr);
+
+  /* draw each color line */
+  cairo_set_line_width (cr, 1);
+  gdouble subsect = 24.f / (gdouble) rect.width;
+  for (guint x = 0; x < rect.width; x += 1)
+    {
+      gdouble frac_hour = subsect * x;
+      if (frac_hour > self->now && line_x == 0)
+        {
+          cd_color_rgb_set (&color, 0.333, 0.333, 0.333);
+          line_x = x;
+        }
+      else if (is_frac_day_between (frac_hour, self->to - 1, self->to))
+        {
+          gdouble frac = 1.f - (self->to - frac_hour);
+          cd_color_rgb_interpolate (&color_temperature,
+                                    &color_unity,
+                                    frac,
+                                    &color);
+        }
+      else if (is_frac_day_between (frac_hour, self->from - 1, self->from))
+        {
+          gdouble frac = self->from - frac_hour;
+          cd_color_rgb_interpolate (&color_temperature,
+                                    &color_unity,
+                                    frac,
+                                    &color);
+        }
+      else if (is_frac_day_between (frac_hour, self->to, self->from))
+        {
+          cd_color_rgb_copy (&color_unity, &color);
+        }
+      else
+        {
+          cd_color_rgb_copy (&color_temperature, &color);
+        }
+      cairo_set_source_rgb (cr, color.R, color.G, color.B);
+      cairo_move_to (cr, x + 0.5, bar_voffset);
+      cairo_line_to (cr, x + 0.5, bar_voffset + rect.height);
+      cairo_stroke (cr);
+    }
+
+  /* apply border */
+  rounded_rectangle (cr, 0, bar_voffset, 6,
+                     rect.width, rect.height - bar_voffset - 1);
+  cairo_set_source_rgb (cr, 0.65, 0.65, 0.65);
+  cairo_set_line_width (cr, 1);
+  cairo_stroke (cr);
+  cairo_restore (cr);
+
+  /* apply arrow */
+  cairo_move_to (cr, line_x - arrow_sz + 0.5, bar_voffset - arrow_sz);
+  cairo_line_to (cr, line_x + arrow_sz + 0.5, bar_voffset - arrow_sz);
+  cairo_line_to (cr, line_x + 0.5, arrow_sz + bar_voffset - arrow_sz);
+  cairo_close_path (cr);
+  cairo_set_source_rgb (cr, 0.333, 0.333, 0.333);
+  cairo_fill (cr);
+
+  /* draw icons */
+  if (self->to <= 0)
+    line_x = rect.width - icon_sz;
+  else
+    line_x = MIN (MAX ((self->to / subsect) - (icon_sz / 2), 0), rect.width - icon_sz);
+  cairo_set_source_surface (cr, self->surface_sunrise, line_x, 0);
+  cairo_paint (cr);
+  if (self->from <= 0)
+    line_x = rect.width - icon_sz;
+  else
+    line_x = MIN (MAX ((self->from / subsect) - (icon_sz / 2), 0), rect.width - icon_sz);
+  cairo_set_source_surface (cr, self->surface_sunset, line_x, 0);
+  cairo_paint (cr);
+
+  return FALSE;
+}
+
+GtkWidget *
+cc_natural_light_widget_new (void)
+{
+  return g_object_new (CC_TYPE_NATURAL_LIGHT_WIDGET, NULL);
+}
+
diff --git a/panels/display/cc-natural-light-widget.h b/panels/display/cc-natural-light-widget.h
new file mode 100644
index 0000000..022fe2f
--- /dev/null
+++ b/panels/display/cc-natural-light-widget.h
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2017 Richard Hughes <richard hughsie com>
+ *
+ * Licensed under the GNU General Public License Version 2
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef __CC_NATURAL_LIGHT_WIDGET_H__
+#define __CC_NATURAL_LIGHT_WIDGET_H__
+
+#include <gtk/gtk.h>
+
+G_BEGIN_DECLS
+
+#define CC_TYPE_NATURAL_LIGHT_WIDGET (cc_natural_light_widget_get_type ())
+G_DECLARE_FINAL_TYPE (CcNaturalLightWidget, cc_natural_light_widget, CC, NATURAL_LIGHT_WIDGET, 
GtkDrawingArea)
+
+GtkWidget   *cc_natural_light_widget_new      (void);
+void         cc_natural_light_widget_set_to   (CcNaturalLightWidget *self,
+                                               gdouble               to);
+void         cc_natural_light_widget_set_from (CcNaturalLightWidget *self,
+                                               gdouble               from);
+void         cc_natural_light_widget_set_now  (CcNaturalLightWidget *self,
+                                               gdouble               now);
+
+G_END_DECLS
+
+#endif
diff --git a/panels/display/display.gresource.xml b/panels/display/display.gresource.xml
new file mode 100644
index 0000000..342ed1e
--- /dev/null
+++ b/panels/display/display.gresource.xml
@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<gresources>
+  <gresource prefix="/org/gnome/control-center/display">
+    <file preprocess="xml-stripblanks">display.ui</file>
+    <file>sunrise.png</file>
+    <file>sunset.png</file>
+  </gresource>
+</gresources>
diff --git a/panels/display/display.ui b/panels/display/display.ui
new file mode 100644
index 0000000..ee4d6a7
--- /dev/null
+++ b/panels/display/display.ui
@@ -0,0 +1,549 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Generated with glade 3.20.0 -->
+<interface>
+  <requires lib="gtk+" version="3.20"/>
+  <object class="GtkSizeGroup">
+    <property name="mode">both</property>
+  </object>
+  <object class="GtkAdjustment" id="adjustment_from_hours">
+    <property name="upper">23</property>
+    <property name="step_increment">1</property>
+    <property name="page_increment">10</property>
+  </object>
+  <object class="GtkAdjustment" id="adjustment_from_minutes">
+    <property name="upper">59</property>
+    <property name="step_increment">1</property>
+    <property name="page_increment">10</property>
+  </object>
+  <object class="GtkAdjustment" id="adjustment_to_hours">
+    <property name="upper">23</property>
+    <property name="step_increment">1</property>
+    <property name="page_increment">10</property>
+  </object>
+  <object class="GtkAdjustment" id="adjustment_to_minutes">
+    <property name="upper">59</property>
+    <property name="step_increment">1</property>
+    <property name="page_increment">10</property>
+  </object>
+  <object class="GtkDialog" id="window_natural_light">
+    <property name="can_focus">False</property>
+    <property name="title" translatable="yes" comments="This is the redshift functionality where we supress 
blue night when the sun has gone down">Natural Light Filter</property>
+    <property name="resizable">False</property>
+    <property name="modal">True</property>
+    <property name="destroy_with_parent">True</property>
+    <property name="type_hint">dialog</property>
+    <child internal-child="vbox">
+      <object class="GtkBox">
+        <property name="can_focus">False</property>
+        <property name="orientation">vertical</property>
+        <property name="spacing">2</property>
+        <child internal-child="action_area">
+          <object class="GtkButtonBox">
+            <property name="can_focus">False</property>
+            <property name="layout_style">end</property>
+          </object>
+          <packing>
+            <property name="expand">False</property>
+            <property name="fill">False</property>
+            <property name="position">0</property>
+          </packing>
+        </child>
+        <child>
+          <object class="GtkBox">
+            <property name="visible">True</property>
+            <property name="can_focus">False</property>
+            <property name="orientation">vertical</property>
+            <child>
+              <object class="GtkInfoBar" id="infobar_disabled">
+                <property name="name">infobar_disabled</property>
+                <property name="visible">True</property>
+                <property name="can_focus">False</property>
+                <property name="border_width">0</property>
+                <property name="orientation">vertical</property>
+                <property name="spacing">12</property>
+                <child internal-child="action_area">
+                  <object class="GtkButtonBox">
+                    <property name="can_focus">False</property>
+                    <property name="border_width">12</property>
+                    <property name="spacing">6</property>
+                    <property name="layout_style">end</property>
+                    <child>
+                      <object class="GtkButton" id="button_undisable">
+                        <property name="label" translatable="yes" comments="This cancels the redshift 
inhibit.">Restart Filter</property>
+                        <property name="name">button_undisable</property>
+                        <property name="visible">True</property>
+                        <property name="can_focus">True</property>
+                        <property name="receives_default">True</property>
+                      </object>
+                      <packing>
+                        <property name="expand">False</property>
+                        <property name="fill">False</property>
+                        <property name="pack_type">end</property>
+                        <property name="position">0</property>
+                      </packing>
+                    </child>
+                  </object>
+                  <packing>
+                    <property name="expand">False</property>
+                    <property name="fill">False</property>
+                    <property name="pack_type">end</property>
+                    <property name="position">0</property>
+                  </packing>
+                </child>
+                <child internal-child="content_area">
+                  <object class="GtkBox">
+                    <property name="can_focus">False</property>
+                    <property name="spacing">16</property>
+                    <child>
+                      <object class="GtkLabel">
+                        <property name="visible">True</property>
+                        <property name="can_focus">False</property>
+                        <property name="halign">start</property>
+                        <property name="margin_left">12</property>
+                        <property name="hexpand">False</property>
+                        <property name="label" translatable="yes" comments="Inhibit the redshift 
functionality until the next day starts">Temporarily Disabled Until Tommorow</property>
+                        <attributes>
+                          <attribute name="weight" value="bold"/>
+                        </attributes>
+                      </object>
+                      <packing>
+                        <property name="expand">True</property>
+                        <property name="fill">True</property>
+                        <property name="position">0</property>
+                      </packing>
+                    </child>
+                  </object>
+                  <packing>
+                    <property name="expand">False</property>
+                    <property name="fill">False</property>
+                    <property name="position">0</property>
+                  </packing>
+                </child>
+              </object>
+              <packing>
+                <property name="expand">False</property>
+                <property name="fill">True</property>
+                <property name="position">0</property>
+              </packing>
+            </child>
+            <child>
+              <object class="GtkBox" id="box_content">
+                <property name="visible">True</property>
+                <property name="can_focus">False</property>
+                <property name="border_width">36</property>
+                <property name="orientation">vertical</property>
+                <property name="spacing">32</property>
+                <child>
+                  <object class="GtkBox">
+                    <property name="visible">True</property>
+                    <property name="can_focus">False</property>
+                    <property name="margin_bottom">4</property>
+                    <child>
+                      <object class="GtkLabel">
+                        <property name="visible">True</property>
+                        <property name="can_focus">False</property>
+                        <property name="label" translatable="yes">Natural Light Filter</property>
+                        <attributes>
+                          <attribute name="weight" value="bold"/>
+                        </attributes>
+                      </object>
+                      <packing>
+                        <property name="expand">False</property>
+                        <property name="fill">True</property>
+                        <property name="position">0</property>
+                      </packing>
+                    </child>
+                    <child>
+                      <object class="GtkSwitch" id="switch_enable">
+                        <property name="visible">True</property>
+                        <property name="can_focus">True</property>
+                      </object>
+                      <packing>
+                        <property name="expand">False</property>
+                        <property name="fill">True</property>
+                        <property name="pack_type">end</property>
+                        <property name="position">1</property>
+                      </packing>
+                    </child>
+                  </object>
+                  <packing>
+                    <property name="expand">False</property>
+                    <property name="fill">True</property>
+                    <property name="position">0</property>
+                  </packing>
+                </child>
+                <child>
+                  <object class="GtkBox">
+                    <property name="visible">True</property>
+                    <property name="can_focus">False</property>
+                    <property name="spacing">42</property>
+                    <child>
+                      <object class="GtkRadioButton" id="radio_automatic">
+                        <property name="label" translatable="yes" comments="When the sun comes up in the 
morning to the time the sun goes down in the evening">Sunset to sunrise</property>
+                        <property name="visible">True</property>
+                        <property name="can_focus">True</property>
+                        <property name="receives_default">False</property>
+                        <property name="active">True</property>
+                        <property name="draw_indicator">True</property>
+                        <property name="group">radio_manual</property>
+                      </object>
+                      <packing>
+                        <property name="expand">False</property>
+                        <property name="fill">True</property>
+                        <property name="position">0</property>
+                      </packing>
+                    </child>
+                    <child>
+                      <object class="GtkRadioButton" id="radio_manual">
+                        <property name="label" translatable="yes" comments="This allows the user to schedule 
when the redshift functionality is triggered">Manual schedule</property>
+                        <property name="visible">True</property>
+                        <property name="can_focus">True</property>
+                        <property name="receives_default">False</property>
+                        <property name="active">True</property>
+                        <property name="draw_indicator">True</property>
+                      </object>
+                      <packing>
+                        <property name="expand">False</property>
+                        <property name="fill">True</property>
+                        <property name="position">1</property>
+                      </packing>
+                    </child>
+                  </object>
+                  <packing>
+                    <property name="expand">False</property>
+                    <property name="fill">True</property>
+                    <property name="position">1</property>
+                  </packing>
+                </child>
+                <child>
+                  <object class="GtkBox" id="box_manual">
+                    <property name="visible">True</property>
+                    <property name="can_focus">False</property>
+                    <property name="spacing">42</property>
+                    <child>
+                      <object class="GtkBox">
+                        <property name="visible">True</property>
+                        <property name="can_focus">False</property>
+                        <property name="orientation">vertical</property>
+                        <property name="spacing">12</property>
+                        <child>
+                          <object class="GtkLabel">
+                            <property name="visible">True</property>
+                            <property name="can_focus">False</property>
+                            <property name="halign">start</property>
+                            <property name="label" translatable="yes">From</property>
+                            <style>
+                              <class name="dim-label"/>
+                            </style>
+                          </object>
+                          <packing>
+                            <property name="expand">False</property>
+                            <property name="fill">False</property>
+                            <property name="position">0</property>
+                          </packing>
+                        </child>
+                        <child>
+                          <object class="GtkBox">
+                            <property name="visible">True</property>
+                            <property name="can_focus">False</property>
+                            <property name="spacing">6</property>
+                            <child>
+                              <object class="GtkSpinButton" id="spinbutton_from_hours">
+                                <property name="visible">True</property>
+                                <property name="can_focus">True</property>
+                                <property name="max_width_chars">2</property>
+                                <property name="orientation">vertical</property>
+                                <property name="adjustment">adjustment_from_hours</property>
+                                <property name="numeric">True</property>
+                                <property name="wrap">True</property>
+                                <property name="value">4</property>
+                                <style>
+                                  <class name="padded-spinbutton"/>
+                                </style>
+                              </object>
+                              <packing>
+                                <property name="expand">False</property>
+                                <property name="fill">True</property>
+                                <property name="position">0</property>
+                              </packing>
+                            </child>
+                            <child>
+                              <object class="GtkLabel">
+                                <property name="visible">True</property>
+                                <property name="can_focus">False</property>
+                                <property name="label" translatable="yes">:</property>
+                              </object>
+                              <packing>
+                                <property name="expand">False</property>
+                                <property name="fill">True</property>
+                                <property name="position">1</property>
+                              </packing>
+                            </child>
+                            <child>
+                              <object class="GtkSpinButton" id="spinbutton_from_minutes">
+                                <property name="visible">True</property>
+                                <property name="can_focus">True</property>
+                                <property name="max_width_chars">2</property>
+                                <property name="orientation">vertical</property>
+                                <property name="adjustment">adjustment_from_minutes</property>
+                                <property name="numeric">True</property>
+                                <property name="wrap">True</property>
+                                <style>
+                                  <class name="padded-spinbutton"/>
+                                </style>
+                              </object>
+                              <packing>
+                                <property name="expand">False</property>
+                                <property name="fill">True</property>
+                                <property name="position">2</property>
+                              </packing>
+                            </child>
+                            <child>
+                              <object class="GtkStack" id="stack_from">
+                                <property name="visible">True</property>
+                                <property name="can_focus">False</property>
+                                <property name="margin_left">6</property>
+                                <child>
+                                  <object class="GtkButton" id="button_from_am">
+                                    <property name="label" translatable="yes" comments="This is the short 
form for the time period in the morning">AM</property>
+                                    <property name="visible">True</property>
+                                    <property name="can_focus">True</property>
+                                    <property name="receives_default">True</property>
+                                    <property name="valign">center</property>
+                                    <style>
+                                      <class name="unpadded-button"/>
+                                    </style>
+                                  </object>
+                                  <packing>
+                                    <property name="name">am</property>
+                                    <property name="title">page0</property>
+                                  </packing>
+                                </child>
+                                <child>
+                                  <object class="GtkButton" id="button_from_pm">
+                                    <property name="label" translatable="yes" comments="This is the short 
form for the time period in the afternoon">PM</property>
+                                    <property name="visible">True</property>
+                                    <property name="can_focus">True</property>
+                                    <property name="receives_default">True</property>
+                                    <property name="valign">center</property>
+                                    <style>
+                                      <class name="unpadded-button"/>
+                                    </style>
+                                  </object>
+                                  <packing>
+                                    <property name="name">pm</property>
+                                    <property name="title">page1</property>
+                                    <property name="position">1</property>
+                                  </packing>
+                                </child>
+                                <child>
+                                  <object class="GtkLabel">
+                                    <property name="visible">True</property>
+                                    <property name="can_focus">False</property>
+                                  </object>
+                                  <packing>
+                                    <property name="name">blank</property>
+                                    <property name="title">page0</property>
+                                    <property name="position">2</property>
+                                  </packing>
+                                </child>
+                              </object>
+                              <packing>
+                                <property name="expand">False</property>
+                                <property name="fill">True</property>
+                                <property name="position">3</property>
+                              </packing>
+                            </child>
+                          </object>
+                          <packing>
+                            <property name="expand">False</property>
+                            <property name="fill">True</property>
+                            <property name="position">1</property>
+                          </packing>
+                        </child>
+                      </object>
+                      <packing>
+                        <property name="expand">False</property>
+                        <property name="fill">True</property>
+                        <property name="position">0</property>
+                      </packing>
+                    </child>
+                    <child>
+                      <object class="GtkBox">
+                        <property name="visible">True</property>
+                        <property name="can_focus">False</property>
+                        <property name="orientation">vertical</property>
+                        <property name="spacing">12</property>
+                        <child>
+                          <object class="GtkLabel">
+                            <property name="visible">True</property>
+                            <property name="can_focus">False</property>
+                            <property name="halign">start</property>
+                            <property name="label" translatable="yes">To</property>
+                            <style>
+                              <class name="dim-label"/>
+                            </style>
+                          </object>
+                          <packing>
+                            <property name="expand">False</property>
+                            <property name="fill">False</property>
+                            <property name="position">0</property>
+                          </packing>
+                        </child>
+                        <child>
+                          <object class="GtkBox">
+                            <property name="visible">True</property>
+                            <property name="can_focus">False</property>
+                            <property name="spacing">6</property>
+                            <child>
+                              <object class="GtkSpinButton" id="spinbutton_to_hours">
+                                <property name="visible">True</property>
+                                <property name="can_focus">True</property>
+                                <property name="max_width_chars">2</property>
+                                <property name="text" translatable="yes">4</property>
+                                <property name="input_purpose">number</property>
+                                <property name="orientation">vertical</property>
+                                <property name="adjustment">adjustment_to_hours</property>
+                                <property name="numeric">True</property>
+                                <property name="wrap">True</property>
+                                <property name="value">4</property>
+                                <style>
+                                  <class name="padded-spinbutton"/>
+                                </style>
+                              </object>
+                              <packing>
+                                <property name="expand">False</property>
+                                <property name="fill">True</property>
+                                <property name="position">0</property>
+                              </packing>
+                            </child>
+                            <child>
+                              <object class="GtkLabel">
+                                <property name="visible">True</property>
+                                <property name="can_focus">False</property>
+                                <property name="label" translatable="yes">:</property>
+                              </object>
+                              <packing>
+                                <property name="expand">False</property>
+                                <property name="fill">True</property>
+                                <property name="position">1</property>
+                              </packing>
+                            </child>
+                            <child>
+                              <object class="GtkSpinButton" id="spinbutton_to_minutes">
+                                <property name="visible">True</property>
+                                <property name="can_focus">True</property>
+                                <property name="max_width_chars">2</property>
+                                <property name="text" translatable="yes">0</property>
+                                <property name="orientation">vertical</property>
+                                <property name="adjustment">adjustment_to_minutes</property>
+                                <property name="numeric">True</property>
+                                <property name="wrap">True</property>
+                                <style>
+                                  <class name="padded-spinbutton"/>
+                                </style>
+                              </object>
+                              <packing>
+                                <property name="expand">False</property>
+                                <property name="fill">True</property>
+                                <property name="position">2</property>
+                              </packing>
+                            </child>
+                            <child>
+                              <object class="GtkStack" id="stack_to">
+                                <property name="visible">True</property>
+                                <property name="can_focus">False</property>
+                                <property name="margin_left">6</property>
+                                <child>
+                                  <object class="GtkButton" id="button_to_am">
+                                    <property name="label" translatable="yes">AM</property>
+                                    <property name="visible">True</property>
+                                    <property name="can_focus">True</property>
+                                    <property name="receives_default">True</property>
+                                    <property name="valign">center</property>
+                                    <style>
+                                      <class name="unpadded-button"/>
+                                    </style>
+                                  </object>
+                                  <packing>
+                                    <property name="name">am</property>
+                                    <property name="title">page0</property>
+                                  </packing>
+                                </child>
+                                <child>
+                                  <object class="GtkButton" id="button_to_pm">
+                                    <property name="label" translatable="yes">PM</property>
+                                    <property name="visible">True</property>
+                                    <property name="can_focus">True</property>
+                                    <property name="receives_default">True</property>
+                                    <property name="valign">center</property>
+                                    <style>
+                                      <class name="unpadded-button"/>
+                                    </style>
+                                  </object>
+                                  <packing>
+                                    <property name="name">pm</property>
+                                    <property name="title">page1</property>
+                                    <property name="position">1</property>
+                                  </packing>
+                                </child>
+                                <child>
+                                  <object class="GtkLabel">
+                                    <property name="visible">True</property>
+                                    <property name="can_focus">False</property>
+                                  </object>
+                                  <packing>
+                                    <property name="name">blank</property>
+                                    <property name="title">page0</property>
+                                    <property name="position">2</property>
+                                  </packing>
+                                </child>
+                              </object>
+                              <packing>
+                                <property name="expand">False</property>
+                                <property name="fill">True</property>
+                                <property name="position">3</property>
+                              </packing>
+                            </child>
+                          </object>
+                          <packing>
+                            <property name="expand">False</property>
+                            <property name="fill">True</property>
+                            <property name="position">1</property>
+                          </packing>
+                        </child>
+                      </object>
+                      <packing>
+                        <property name="expand">False</property>
+                        <property name="fill">True</property>
+                        <property name="position">1</property>
+                      </packing>
+                    </child>
+                  </object>
+                  <packing>
+                    <property name="expand">False</property>
+                    <property name="fill">True</property>
+                    <property name="position">2</property>
+                  </packing>
+                </child>
+              </object>
+              <packing>
+                <property name="expand">False</property>
+                <property name="fill">True</property>
+                <property name="position">1</property>
+              </packing>
+            </child>
+          </object>
+          <packing>
+            <property name="expand">False</property>
+            <property name="fill">True</property>
+            <property name="position">1</property>
+          </packing>
+        </child>
+      </object>
+    </child>
+    <child>
+      <placeholder/>
+    </child>
+  </object>
+</interface>
diff --git a/panels/display/icons/16x16/sunrise.png b/panels/display/icons/16x16/sunrise.png
new file mode 100644
index 0000000..a659b1d
Binary files /dev/null and b/panels/display/icons/16x16/sunrise.png differ
diff --git a/panels/display/icons/16x16/sunset.png b/panels/display/icons/16x16/sunset.png
new file mode 100644
index 0000000..bbdee49
Binary files /dev/null and b/panels/display/icons/16x16/sunset.png differ


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