]> Pileus Git - ~andy/gtk/blobdiff - gtk/gtkcoloreditor.c
stylecontext: Do invalidation on first resize container
[~andy/gtk] / gtk / gtkcoloreditor.c
index 99168b5fb3c6bbe7bcec90d31a954124ef74b413..8cbbf21541c48e7141fa7c7253da69d7653243ef 100644 (file)
  * Lesser General Public License for more details.
  *
  * You should have received a copy of the GNU Lesser General Public
- * License along with this library; if not, write to the
- * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
- * Boston, MA 02111-1307, USA.
+ * License along with this library. If not, see <http://www.gnu.org/licenses/>.
  */
 
 /* TODO
- * - move out custom sliders
- * - pop-up entries
+ * - touch
+ * - accessible relations for popups
+ * - saving per-application (?)
+ * - better popup theming
  */
 
 #include "config.h"
 
+#include "gtkcoloreditorprivate.h"
+
 #include "gtkcolorchooserprivate.h"
-#include "gtkcoloreditor.h"
-#include "gtkcolorplane.h"
-#include "gtkcolorswatch.h"
+#include "gtkcolorplaneprivate.h"
+#include "gtkcolorscaleprivate.h"
+#include "gtkcolorswatchprivate.h"
+#include "gtkcolorutils.h"
 #include "gtkgrid.h"
-#include "gtkscale.h"
-#include "gtkaspectframe.h"
-#include "gtkdrawingarea.h"
+#include "gtkorientable.h"
 #include "gtkentry.h"
-#include "gtkhsv.h"
+#include "gtkoverlay.h"
 #include "gtkadjustment.h"
+#include "gtklabel.h"
+#include "gtkspinbutton.h"
 #include "gtkintl.h"
+#include "gtkstylecontext.h"
 
 #include <math.h>
 
 struct _GtkColorEditorPrivate
 {
+  GtkWidget *overlay;
   GtkWidget *grid;
   GtkWidget *swatch;
   GtkWidget *entry;
   GtkWidget *h_slider;
-  GtkWidget *sv_plane;
+  GtkWidget *h_popup;
+  GtkWidget *h_entry;
   GtkWidget *a_slider;
+  GtkWidget *a_popup;
+  GtkWidget *a_entry;
+  GtkWidget *sv_plane;
+  GtkWidget *sv_popup;
+  GtkWidget *s_entry;
+  GtkWidget *v_entry;
+  GtkWidget *current_popup;
+  GtkWidget *popdown_focus;
 
   GtkAdjustment *h_adj;
+  GtkAdjustment *s_adj;
+  GtkAdjustment *v_adj;
   GtkAdjustment *a_adj;
-  cairo_surface_t *h_surface;
-  cairo_surface_t *a_surface;
 
-  GdkRGBA color;
-  gdouble h, s, v;
   guint text_changed : 1;
-  guint show_alpha   : 1;
+  guint use_alpha    : 1;
 };
 
 enum
 {
   PROP_ZERO,
-  PROP_COLOR,
-  PROP_SHOW_ALPHA
+  PROP_RGBA,
+  PROP_USE_ALPHA
 };
 
 static void gtk_color_editor_iface_init (GtkColorChooserInterface *iface);
@@ -82,14 +94,15 @@ scale_round (gdouble value, gdouble scale)
 }
 
 static void
-update_entry (GtkColorEditor *editor)
+entry_set_rgba (GtkColorEditor *editor,
+                const GdkRGBA  *color)
 {
   gchar *text;
 
   text = g_strdup_printf ("#%02X%02X%02X",
-                          scale_round (editor->priv->color.red, 255),
-                          scale_round (editor->priv->color.green, 255),
-                          scale_round (editor->priv->color.blue, 255));
+                          scale_round (color->red, 255),
+                          scale_round (color->green, 255),
+                          scale_round (color->blue, 255));
   gtk_entry_set_text (GTK_ENTRY (editor->priv->entry), text);
   editor->priv->text_changed = FALSE;
   g_free (text);
@@ -108,8 +121,8 @@ entry_apply (GtkWidget      *entry,
   text = gtk_editable_get_chars (GTK_EDITABLE (editor->priv->entry), 0, -1);
   if (gdk_rgba_parse (&color, text))
     {
-      color.alpha = editor->priv->color.alpha;
-      gtk_color_chooser_set_color (GTK_COLOR_CHOOSER (editor), &color);
+      color.alpha = gtk_adjustment_get_value (editor->priv->a_adj);
+      gtk_color_chooser_set_rgba (GTK_COLOR_CHOOSER (editor), &color);
     }
 
   editor->priv->text_changed = FALSE;
@@ -135,296 +148,370 @@ entry_text_changed (GtkWidget      *entry,
 }
 
 static void
-h_changed (GtkAdjustment  *adj,
-           GtkColorEditor *editor)
+hsv_changed (GtkColorEditor *editor)
 {
-  editor->priv->h = gtk_adjustment_get_value (adj);
-  gtk_hsv_to_rgb (editor->priv->h, editor->priv->s, editor->priv->v,
-                  &editor->priv->color.red,
-                  &editor->priv->color.green,
-                  &editor->priv->color.blue);
-  gtk_color_plane_set_h (GTK_COLOR_PLANE (editor->priv->sv_plane), editor->priv->h);
-  update_entry (editor);
-  gtk_color_swatch_set_color (GTK_COLOR_SWATCH (editor->priv->swatch), &editor->priv->color);
-  gtk_widget_queue_draw (editor->priv->a_slider);
-  if (editor->priv->a_surface)
-    {
-      cairo_surface_destroy (editor->priv->a_surface);
-      editor->priv->a_surface = NULL;
-    }
-  g_object_notify (G_OBJECT (editor), "color");
+  GdkRGBA color;
+  gdouble h, s, v, a;
+
+  h = gtk_adjustment_get_value (editor->priv->h_adj);
+  s = gtk_adjustment_get_value (editor->priv->s_adj);
+  v = gtk_adjustment_get_value (editor->priv->v_adj);
+  a = gtk_adjustment_get_value (editor->priv->a_adj);
+
+  gtk_hsv_to_rgb (h, s, v, &color.red, &color.green, &color.blue);
+  color.alpha = a;
+
+  gtk_color_swatch_set_rgba (GTK_COLOR_SWATCH (editor->priv->swatch), &color);
+  gtk_color_scale_set_rgba (GTK_COLOR_SCALE (editor->priv->a_slider), &color);
+  entry_set_rgba (editor, &color);
+
+  g_object_notify (G_OBJECT (editor), "rgba");
 }
 
 static void
-sv_changed (GtkColorPlane  *plane,
-            GtkColorEditor *editor)
+dismiss_current_popup (GtkColorEditor *editor)
 {
-  editor->priv->s = gtk_color_plane_get_s (plane);
-  editor->priv->v = gtk_color_plane_get_v (plane);
-  gtk_hsv_to_rgb (editor->priv->h, editor->priv->s, editor->priv->v,
-                  &editor->priv->color.red,
-                  &editor->priv->color.green,
-                  &editor->priv->color.blue);
-  update_entry (editor);
-  gtk_color_swatch_set_color (GTK_COLOR_SWATCH (editor->priv->swatch), &editor->priv->color);
-  gtk_widget_queue_draw (editor->priv->a_slider);
-  if (editor->priv->a_surface)
+  if (editor->priv->current_popup)
     {
-      cairo_surface_destroy (editor->priv->a_surface);
-      editor->priv->a_surface = NULL;
+      gtk_widget_hide (editor->priv->current_popup);
+      editor->priv->current_popup = NULL;
+      if (editor->priv->popdown_focus)
+        {
+          gtk_widget_grab_focus (editor->priv->popdown_focus);
+          editor->priv->popdown_focus = NULL;
+        }
     }
-  g_object_notify (G_OBJECT (editor), "color");
 }
 
 static void
-a_changed (GtkAdjustment  *adj,
-           GtkColorEditor *editor)
+popup_edit (GtkWidget      *widget,
+            GtkColorEditor *editor)
 {
-  editor->priv->color.alpha = gtk_adjustment_get_value (adj);
-  gtk_color_swatch_set_color (GTK_COLOR_SWATCH (editor->priv->swatch), &editor->priv->color);
-  g_object_notify (G_OBJECT (editor), "color");
-}
+  GtkWidget *popup = NULL;
+  GtkWidget *toplevel;
+  GtkWidget *focus;
 
-static cairo_pattern_t *
-get_checkered_pattern (void)
-{
-  /* need to respect pixman's stride being a multiple of 4 */
-  static unsigned char data[8] = { 0xFF, 0x00, 0x00, 0x00,
-                                   0x00, 0xFF, 0x00, 0x00 };
-  static cairo_surface_t *checkered = NULL;
-  cairo_pattern_t *pattern;
-
-  if (checkered == NULL)
-    checkered = cairo_image_surface_create_for_data (data,
-                                                     CAIRO_FORMAT_A8,
-                                                     2, 2, 4);
-
-  pattern = cairo_pattern_create_for_surface (checkered);
-  cairo_pattern_set_extend (pattern, CAIRO_EXTEND_REPEAT);
-  cairo_pattern_set_filter (pattern, CAIRO_FILTER_NEAREST);
-
-  return pattern;
+  if (widget == editor->priv->sv_plane)
+    {
+      popup = editor->priv->sv_popup;
+      focus = editor->priv->s_entry;
+    }
+  else if (widget == editor->priv->h_slider)
+    {
+      popup = editor->priv->h_popup;
+      focus = editor->priv->h_entry;
+    }
+  else if (widget == editor->priv->a_slider)
+    {
+      popup = editor->priv->a_popup;
+      focus = editor->priv->a_entry;
+    }
+
+  if (popup)
+    {
+      dismiss_current_popup (editor);
+      toplevel = gtk_widget_get_toplevel (GTK_WIDGET (editor));
+      editor->priv->popdown_focus = gtk_window_get_focus (GTK_WINDOW (toplevel));
+      editor->priv->current_popup = popup;
+      gtk_widget_show (popup);
+      gtk_widget_grab_focus (focus);
+    }
 }
 
-static cairo_surface_t *
-create_h_surface (GtkWidget *widget)
+static gboolean
+popup_key_press (GtkWidget      *popup,
+                 GdkEventKey    *event,
+                 GtkColorEditor *editor)
 {
-  cairo_t *cr;
-  cairo_surface_t *surface;
-  gint width, height, stride;
-  cairo_surface_t *tmp;
-  guint red, green, blue;
-  guint32 *data, *p;
-  gdouble h;
-  gdouble r, g, b;
-  gdouble f;
-  gint x, y;
-
-  if (!gtk_widget_get_realized (widget))
-    return NULL;
-
-  width = gtk_widget_get_allocated_width (widget);
-  height = gtk_widget_get_allocated_height (widget);
-
-  surface = gdk_window_create_similar_surface (gtk_widget_get_window (widget),
-                                               CAIRO_CONTENT_COLOR,
-                                               width, height);
-
-  if (width == 1 || height == 1)
-    return surface;
-
-  stride = cairo_format_stride_for_width (CAIRO_FORMAT_RGB24, width);
-
-  data = g_malloc (height * stride);
-
-  f = 1.0 / (height - 1);
-  for (y = 0; y < height; y++)
+  if (event->keyval == GDK_KEY_Escape)
     {
-      h = CLAMP (y * f, 0.0, 1.0);
-      p = data + y * (stride / 4);
-      for (x = 0; x < width; x++)
-        {
-          gtk_hsv_to_rgb (h, 1, 1, &r, &g, &b);
-          red = CLAMP (r * 255, 0, 255);
-          green = CLAMP (g * 255, 0, 255);
-          blue = CLAMP (b * 255, 0, 255);
-          p[x] = (red << 16) | (green << 8) | blue;
-        }
+      dismiss_current_popup (editor);
+      return TRUE;
     }
 
-  tmp = cairo_image_surface_create_for_data ((guchar *)data, CAIRO_FORMAT_RGB24,
-                                             width, height, stride);
-  cr = cairo_create (surface);
-
-  cairo_set_source_surface (cr, tmp, 0, 0);
-  cairo_paint (cr);
-
-  cairo_destroy (cr);
-  cairo_surface_destroy (tmp);
-  g_free (data);
-
-  return surface;
+  return FALSE;
 }
 
 static gboolean
-h_draw (GtkWidget *widget,
-        cairo_t   *cr,
-        GtkColorEditor *editor)
+get_child_position (GtkOverlay     *overlay,
+                    GtkWidget      *widget,
+                    GtkAllocation  *allocation,
+                    GtkColorEditor *editor)
 {
-  cairo_surface_t *surface;
-  gint width, height;
+  GtkRequisition req;
+  GtkAllocation alloc;
+  gint s, e;
 
-  width = gtk_widget_get_allocated_width (widget);
-  height = gtk_widget_get_allocated_height (widget);
+  gtk_widget_get_preferred_size (widget, &req, NULL);
 
-  if (!editor->priv->h_surface)
-    editor->priv->h_surface = create_h_surface (widget);
-  surface = editor->priv->h_surface;
+  allocation->width = req.width;
+  allocation->height = req.height;
 
-  cairo_save (cr);
+  if (widget == editor->priv->sv_popup)
+    {
+      if (gtk_widget_get_direction (GTK_WIDGET (overlay)) == GTK_TEXT_DIR_RTL)
+        allocation->x = 0;
+      else
+        allocation->x = gtk_widget_get_allocated_width (GTK_WIDGET (overlay)) - req.width;
+      allocation->y = req.height / 3;
+    }
+  else if (widget == editor->priv->h_popup)
+    {
+      gtk_widget_get_allocation (editor->priv->h_slider, &alloc);
+      gtk_range_get_slider_range (GTK_RANGE (editor->priv->h_slider), &s, &e);
+
+      if (gtk_widget_get_direction (GTK_WIDGET (overlay)) == GTK_TEXT_DIR_RTL)
+        gtk_widget_translate_coordinates (editor->priv->h_slider,
+                                          gtk_widget_get_parent (editor->priv->grid),
+                                          - req.width, (s + e - req.height) / 2,
+                                          &allocation->x, &allocation->y);
+      else
+        gtk_widget_translate_coordinates (editor->priv->h_slider,
+                                          gtk_widget_get_parent (editor->priv->grid),
+                                          alloc.width, (s + e - req.height) / 2,
+                                          &allocation->x, &allocation->y);
+    }
+  else if (widget == editor->priv->a_popup)
+    {
+      gtk_widget_get_allocation (editor->priv->a_slider, &alloc);
+      gtk_range_get_slider_range (GTK_RANGE (editor->priv->a_slider), &s, &e);
 
-  cairo_rectangle (cr, 1, 1, width - 2, height - 2);
-  cairo_clip (cr);
-  cairo_set_source_surface (cr, surface, 0, 0);
-  cairo_paint (cr);
+      gtk_widget_translate_coordinates (editor->priv->a_slider,
+                                        gtk_widget_get_parent (editor->priv->grid),
+                                        (s + e - req.width) / 2, - req.height,
+                                        &allocation->x, &allocation->y);
+    }
+  else
+    return FALSE;
 
-  cairo_restore (cr);
+  allocation->x = CLAMP (allocation->x, 0, gtk_widget_get_allocated_width (GTK_WIDGET (overlay)) - req.width);
+  allocation->y = CLAMP (allocation->y, 0, gtk_widget_get_allocated_height (GTK_WIDGET (overlay)) - req.height);
 
-  return FALSE;
+  return TRUE;
 }
 
-static cairo_surface_t *
-create_a_surface (GtkWidget *widget,
-                  GdkRGBA   *color)
+static void
+value_changed (GtkAdjustment *a,
+               GtkAdjustment *as)
 {
-  cairo_t *cr;
-  cairo_surface_t *surface;
-  cairo_pattern_t *pattern;
-  cairo_matrix_t matrix;
-  gint width, height;
-
-  if (!gtk_widget_get_realized (widget))
-    return NULL;
+  gdouble scale;
 
-  width = gtk_widget_get_allocated_width (widget);
-  height = gtk_widget_get_allocated_height (widget);
+  scale = gtk_adjustment_get_upper (as) / gtk_adjustment_get_upper (a);
+  g_signal_handlers_block_by_func (as, value_changed, a);
+  gtk_adjustment_set_value (as, gtk_adjustment_get_value (a) * scale);
+  g_signal_handlers_unblock_by_func (as, value_changed, a);
+}
 
-  surface = gdk_window_create_similar_surface (gtk_widget_get_window (widget),
-                                               CAIRO_CONTENT_COLOR,
-                                               width, height);
+static GtkAdjustment *
+scaled_adjustment (GtkAdjustment *a,
+                   gdouble        scale)
+{
+  GtkAdjustment *as;
 
-  if (width == 1 || height == 1)
-    return surface;
+  as = gtk_adjustment_new (gtk_adjustment_get_value (a) * scale,
+                           gtk_adjustment_get_lower (a) * scale,
+                           gtk_adjustment_get_upper (a) * scale,
+                           gtk_adjustment_get_step_increment (a) * scale,
+                           gtk_adjustment_get_page_increment (a) * scale,
+                           gtk_adjustment_get_page_size (a) * scale);
 
-  cr = cairo_create (surface);
+  g_signal_connect (a, "value-changed", G_CALLBACK (value_changed), as);
+  g_signal_connect (as, "value-changed", G_CALLBACK (value_changed), a);
 
-  cairo_set_source_rgb (cr, 0.33, 0.33, 0.33);
-  cairo_paint (cr);
-  cairo_set_source_rgb (cr, 0.66, 0.66, 0.66);
+  return as;
+}
 
-  pattern = get_checkered_pattern ();
-  cairo_matrix_init_scale (&matrix, 0.125, 0.125);
-  cairo_pattern_set_matrix (pattern, &matrix);
-  cairo_mask (cr, pattern);
-  cairo_pattern_destroy (pattern);
+static gboolean
+popup_draw (GtkWidget      *popup,
+            cairo_t        *cr,
+            GtkColorEditor *editor)
+{
+  GtkStyleContext *context;
+  gint width, height;
 
-  pattern = cairo_pattern_create_linear (0, 0, width, 0);
-  cairo_pattern_add_color_stop_rgba (pattern, 0, color->red, color->green, color->blue, 0);
-  cairo_pattern_add_color_stop_rgba (pattern, width, color->red, color->green, color->blue, 1);
-  cairo_set_source (cr, pattern);
-  cairo_paint (cr);
-  cairo_pattern_destroy (pattern);
+  context = gtk_widget_get_style_context (popup);
+  width = gtk_widget_get_allocated_width (popup);
+  height = gtk_widget_get_allocated_height (popup);
 
-  cairo_destroy (cr);
+  gtk_render_background (context, cr, 0, 0, width, height);
+  gtk_render_frame (context, cr, 0, 0, width, height);
 
-  return surface;
+  return FALSE;
 }
 
-static gboolean
-a_draw (GtkWidget *widget,
-        cairo_t   *cr,
-        GtkColorEditor *editor)
+static GtkWidget *
+create_popup (GtkColorEditor *editor,
+              GtkWidget      *attach,
+              GtkWidget      *contents)
 {
-  cairo_surface_t *surface;
-  gint width, height;
+  GtkWidget *popup;
 
-  width = gtk_widget_get_allocated_width (widget);
-  height = gtk_widget_get_allocated_height (widget);
+  g_object_set (contents, "margin", 12, NULL);
 
-  if (!editor->priv->a_surface)
-    editor->priv->a_surface = create_a_surface (widget, &editor->priv->color);
-  surface = editor->priv->a_surface;
+  popup = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0);
+  gtk_style_context_add_class (gtk_widget_get_style_context (popup), GTK_STYLE_CLASS_TOOLTIP);
+  gtk_container_add (GTK_CONTAINER (popup), contents);
 
-  cairo_save (cr);
+  gtk_widget_show_all (contents);
+  gtk_widget_set_no_show_all (popup, TRUE);
 
-  cairo_rectangle (cr, 1, 1, width - 2, height - 2);
-  cairo_clip (cr);
-  cairo_set_source_surface (cr, surface, 0, 0);
-  cairo_paint (cr);
+  g_signal_connect (popup, "draw", G_CALLBACK (popup_draw), editor);
 
-  cairo_restore (cr);
+  gtk_overlay_add_overlay (GTK_OVERLAY (editor->priv->overlay), popup);
+  g_signal_connect (attach, "popup-menu", G_CALLBACK (popup_edit), editor);
 
-  return FALSE;
+  return popup;
 }
 
 static void
 gtk_color_editor_init (GtkColorEditor *editor)
 {
   GtkWidget *grid;
-  GtkAdjustment *adj;
+  GtkWidget *slider;
+  GtkWidget *entry;
+  GtkWidget *swatch;
+  GtkAdjustment *h_adj, *s_adj, *v_adj, *a_adj;
+  AtkObject *atk_obj;
+  GdkRGBA transparent = { 0, 0, 0, 0 };
 
   editor->priv = G_TYPE_INSTANCE_GET_PRIVATE (editor,
                                               GTK_TYPE_COLOR_EDITOR,
                                               GtkColorEditorPrivate);
-  editor->priv->show_alpha = TRUE;
+  editor->priv->use_alpha = TRUE;
 
-  gtk_widget_push_composite_child ();
+  editor->priv->h_adj = h_adj = gtk_adjustment_new (0, 0, 1, 0.01, 0.1, 0);
+  editor->priv->s_adj = s_adj = gtk_adjustment_new (0, 0, 1, 0.01, 0.1, 0);
+  editor->priv->v_adj = v_adj = gtk_adjustment_new (0, 0, 1, 0.01, 0.1, 0);
+  editor->priv->a_adj = a_adj = gtk_adjustment_new (0, 0, 1, 0.01, 0.1, 0);
 
-  editor->priv->grid = grid = gtk_grid_new ();
-  gtk_grid_set_row_spacing (GTK_GRID (grid), 12);
-  gtk_grid_set_column_spacing (GTK_GRID (grid), 12);
+  g_object_ref_sink (h_adj);
+  g_object_ref_sink (s_adj);
+  g_object_ref_sink (v_adj);
+  g_object_ref_sink (a_adj);
+
+  g_signal_connect_swapped (h_adj, "value-changed", G_CALLBACK (hsv_changed), editor);
+  g_signal_connect_swapped (s_adj, "value-changed", G_CALLBACK (hsv_changed), editor);
+  g_signal_connect_swapped (v_adj, "value-changed", G_CALLBACK (hsv_changed), editor);
+  g_signal_connect_swapped (a_adj, "value-changed", G_CALLBACK (hsv_changed), editor);
 
-  editor->priv->swatch = gtk_color_swatch_new ();
-  gtk_widget_set_sensitive (editor->priv->swatch, FALSE);
-  gtk_color_swatch_set_corner_radii (GTK_COLOR_SWATCH (editor->priv->swatch), 2, 2, 2, 2);
-
-  editor->priv->entry = gtk_entry_new ();
-  g_signal_connect (editor->priv->entry, "activate",
-                    G_CALLBACK (entry_apply), editor);
-  g_signal_connect (editor->priv->entry, "notify::text",
-                    G_CALLBACK (entry_text_changed), editor);
-  g_signal_connect (editor->priv->entry, "focus-out-event",
-                    G_CALLBACK (entry_focus_out), editor);
-
-  adj = gtk_adjustment_new (0, 0, 1, 0.01, 0.1, 0);
-  g_signal_connect (adj, "value-changed", G_CALLBACK (h_changed), editor);
-  editor->priv->h_slider = gtk_scale_new (GTK_ORIENTATION_VERTICAL, adj);
-  editor->priv->h_adj = adj;
-  g_signal_connect (editor->priv->h_slider, "draw", G_CALLBACK (h_draw), editor);
-
-  gtk_scale_set_draw_value (GTK_SCALE (editor->priv->h_slider), FALSE);
-  editor->priv->sv_plane = gtk_color_plane_new ();
+  gtk_widget_push_composite_child ();
+
+  /* Construct the main UI */
+  editor->priv->swatch = swatch = gtk_color_swatch_new ();
+  gtk_color_swatch_set_selectable (GTK_COLOR_SWATCH (editor->priv->swatch), FALSE);
+  gtk_widget_set_events (swatch, gtk_widget_get_events (swatch)
+                                 & ~(GDK_BUTTON_PRESS_MASK
+                                     | GDK_BUTTON_RELEASE_MASK
+                                     | GDK_KEY_PRESS_MASK
+                                     | GDK_KEY_RELEASE_MASK));
+  gtk_widget_set_can_focus (swatch, FALSE);
+
+  editor->priv->entry = entry = gtk_entry_new ();
+  atk_obj = gtk_widget_get_accessible (entry);
+  atk_object_set_role (atk_obj, ATK_ROLE_ENTRY);
+  atk_object_set_name (atk_obj, _("Color Name"));
+  g_signal_connect (entry, "activate", G_CALLBACK (entry_apply), editor);
+  g_signal_connect (entry, "notify::text", G_CALLBACK (entry_text_changed), editor);
+  g_signal_connect (entry, "focus-out-event", G_CALLBACK (entry_focus_out), editor);
+
+  editor->priv->h_slider = slider = gtk_color_scale_new (h_adj, GTK_COLOR_SCALE_HUE);
+  gtk_orientable_set_orientation (GTK_ORIENTABLE (slider), GTK_ORIENTATION_VERTICAL);
+  if (gtk_widget_get_direction (slider) == GTK_TEXT_DIR_RTL)
+    gtk_style_context_add_class (gtk_widget_get_style_context (slider),
+                                 GTK_STYLE_CLASS_SCALE_HAS_MARKS_ABOVE);
+  else
+    gtk_style_context_add_class (gtk_widget_get_style_context (slider),
+                                 GTK_STYLE_CLASS_SCALE_HAS_MARKS_BELOW);
+
+  editor->priv->sv_plane = gtk_color_plane_new (h_adj, s_adj, v_adj);
   gtk_widget_set_size_request (editor->priv->sv_plane, 300, 300);
-  gtk_widget_set_hexpand (editor->priv->sv_plane, TRUE);
-  gtk_widget_set_vexpand (editor->priv->sv_plane, TRUE);
 
-  g_signal_connect (editor->priv->sv_plane, "changed", G_CALLBACK (sv_changed), editor);
+  editor->priv->a_slider = slider = gtk_color_scale_new (a_adj, GTK_COLOR_SCALE_ALPHA);
+  gtk_orientable_set_orientation (GTK_ORIENTABLE (slider), GTK_ORIENTATION_HORIZONTAL);
+  gtk_style_context_add_class (gtk_widget_get_style_context (slider),
+                               GTK_STYLE_CLASS_SCALE_HAS_MARKS_ABOVE);
 
-  adj = gtk_adjustment_new (1, 0, 1, 0.01, 0.1, 0);
-  editor->priv->a_slider = gtk_scale_new (GTK_ORIENTATION_HORIZONTAL, adj);
-  g_signal_connect (adj, "value-changed", G_CALLBACK (a_changed), editor);
-  gtk_scale_set_draw_value (GTK_SCALE (editor->priv->a_slider), FALSE);
-  editor->priv->a_adj = adj;
-  g_signal_connect (editor->priv->a_slider, "draw", G_CALLBACK (a_draw), editor);
+  editor->priv->grid = grid = gtk_grid_new ();
+  gtk_grid_set_row_spacing (GTK_GRID (grid), 12);
+  gtk_grid_set_column_spacing (GTK_GRID (grid), 12);
 
   gtk_grid_attach (GTK_GRID (grid), editor->priv->swatch,   1, 0, 1, 1);
   gtk_grid_attach (GTK_GRID (grid), editor->priv->entry,    2, 0, 1, 1);
   gtk_grid_attach (GTK_GRID (grid), editor->priv->h_slider, 0, 1, 1, 1);
   gtk_grid_attach (GTK_GRID (grid), editor->priv->sv_plane, 1, 1, 2, 1);
   gtk_grid_attach (GTK_GRID (grid), editor->priv->a_slider, 1, 2, 2, 1);
-  gtk_widget_show_all (grid);
 
-  gtk_container_add (GTK_CONTAINER (editor), grid);
+  /* This extra margin is necessary so we have room to the sides
+   * to place the popups as desired
+   */
+  gtk_widget_set_margin_left (grid, 30);
+  gtk_widget_set_margin_right (grid, 30);
+
+  editor->priv->overlay = gtk_overlay_new ();
+  gtk_widget_override_background_color (editor->priv->overlay, 0, &transparent);
+  gtk_container_add (GTK_CONTAINER (editor->priv->overlay), grid);
+
+  /* Construct the sv popup */
+  editor->priv->s_entry = entry = gtk_spin_button_new (scaled_adjustment (s_adj, 100), 1, 0);
+  atk_obj = gtk_widget_get_accessible (entry);
+  atk_object_set_name (atk_obj, C_("Color channel", "Saturation"));
+  atk_object_set_role (atk_obj, ATK_ROLE_ENTRY);
+  g_signal_connect (entry, "key-press-event", G_CALLBACK (popup_key_press), editor);
+
+  editor->priv->v_entry = entry = gtk_spin_button_new (scaled_adjustment (v_adj, 100), 1, 0);
+  atk_obj = gtk_widget_get_accessible (entry);
+  atk_object_set_name (atk_obj, C_("Color channel", "Value"));
+  atk_object_set_role (atk_obj, ATK_ROLE_ENTRY);
+  g_signal_connect (entry, "key-press-event", G_CALLBACK (popup_key_press), editor);
+
+  grid = gtk_grid_new ();
+  gtk_grid_set_row_spacing (GTK_GRID (grid), 6);
+  gtk_grid_set_column_spacing (GTK_GRID (grid), 6);
+
+  gtk_grid_attach (GTK_GRID (grid), gtk_label_new (C_("Color channel", "S")), 0, 0, 1, 1);
+  gtk_grid_attach (GTK_GRID (grid), editor->priv->s_entry, 1, 0, 1, 1);
+  gtk_grid_attach (GTK_GRID (grid), gtk_label_new (C_("Color channel", "V")), 0, 1, 1, 1);
+  gtk_grid_attach (GTK_GRID (grid), editor->priv->v_entry, 1, 1, 1, 1);
+
+  editor->priv->sv_popup = create_popup (editor, editor->priv->sv_plane, grid);
+
+  /* Construct the h popup */
+  editor->priv->h_entry = entry = gtk_spin_button_new (scaled_adjustment (h_adj, 100), 1, 0);
+  atk_obj = gtk_widget_get_accessible (entry);
+  atk_object_set_name (atk_obj, C_("Color channel", "Hue"));
+  atk_object_set_role (atk_obj, ATK_ROLE_ENTRY);
+  g_signal_connect (entry, "key-press-event", G_CALLBACK (popup_key_press), editor);
+
+  grid = gtk_grid_new ();
+  gtk_grid_set_column_spacing (GTK_GRID (grid), 6);
+
+  gtk_grid_attach (GTK_GRID (grid), gtk_label_new (C_("Color channel", "H")), 0, 0, 1, 1);
+  gtk_grid_attach (GTK_GRID (grid), editor->priv->h_entry, 1, 0, 1, 1);
+
+  editor->priv->h_popup = create_popup (editor, editor->priv->h_slider, grid);
+
+  /* Construct the a popup */
+  editor->priv->a_entry = entry = gtk_spin_button_new (scaled_adjustment (a_adj, 100), 1, 0);
+  atk_obj = gtk_widget_get_accessible (entry);
+  atk_object_set_name (atk_obj, C_("Color channel", "Alpha"));
+  atk_object_set_role (atk_obj, ATK_ROLE_ENTRY);
+  g_signal_connect (entry, "key-press-event", G_CALLBACK (popup_key_press), editor);
+
+  grid = gtk_grid_new ();
+  gtk_grid_set_column_spacing (GTK_GRID (grid), 6);
+
+  gtk_grid_attach (GTK_GRID (grid), gtk_label_new (C_("Color channel", "A")), 0, 0, 1, 1);
+  gtk_grid_attach (GTK_GRID (grid), editor->priv->a_entry, 1, 0, 1, 1);
+
+  editor->priv->a_popup = create_popup (editor, editor->priv->a_slider, grid);
+
+  /* Hook up popup positioning */
+  g_signal_connect (editor->priv->overlay, "get-child-position", G_CALLBACK (get_child_position), editor);
+  g_signal_connect (editor, "notify::visible", G_CALLBACK (dismiss_current_popup), NULL);
+
+  gtk_widget_show_all (editor->priv->overlay);
+  gtk_container_add (GTK_CONTAINER (editor), editor->priv->overlay);
+
   gtk_widget_pop_composite_child ();
 }
 
@@ -439,14 +526,14 @@ gtk_color_editor_get_property (GObject    *object,
 
   switch (prop_id)
     {
-    case PROP_COLOR:
+    case PROP_RGBA:
       {
         GdkRGBA color;
-        gtk_color_chooser_get_color (cc, &color);
+        gtk_color_chooser_get_rgba (cc, &color);
         g_value_set_boxed (value, &color);
       }
       break;
-    case PROP_SHOW_ALPHA:
+    case PROP_USE_ALPHA:
       g_value_set_boolean (value, gtk_widget_get_visible (ce->priv->a_slider));
       break;
     default:
@@ -456,16 +543,14 @@ gtk_color_editor_get_property (GObject    *object,
 }
 
 static void
-gtk_color_editor_set_show_alpha (GtkColorEditor *editor,
-                                 gboolean        show_alpha)
+gtk_color_editor_set_use_alpha (GtkColorEditor *editor,
+                                gboolean        use_alpha)
 {
-  if (editor->priv->show_alpha != show_alpha)
+  if (editor->priv->use_alpha != use_alpha)
     {
-      editor->priv->show_alpha = show_alpha;
-
-      gtk_widget_set_visible (editor->priv->a_slider, show_alpha);
-
-      gtk_color_swatch_set_show_alpha (GTK_COLOR_SWATCH (editor->priv->swatch), show_alpha);
+      editor->priv->use_alpha = use_alpha;
+      gtk_widget_set_visible (editor->priv->a_slider, use_alpha);
+      gtk_color_swatch_set_use_alpha (GTK_COLOR_SWATCH (editor->priv->swatch), use_alpha);
     }
 }
 
@@ -480,11 +565,11 @@ gtk_color_editor_set_property (GObject      *object,
 
   switch (prop_id)
     {
-    case PROP_COLOR:
-      gtk_color_chooser_set_color (cc, g_value_get_boxed (value));
+    case PROP_RGBA:
+      gtk_color_chooser_set_rgba (cc, g_value_get_boxed (value));
       break;
-    case PROP_SHOW_ALPHA:
-      gtk_color_editor_set_show_alpha (ce, g_value_get_boolean (value));
+    case PROP_USE_ALPHA:
+      gtk_color_editor_set_use_alpha (ce, g_value_get_boolean (value));
       break;
     default:
       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
@@ -497,10 +582,10 @@ gtk_color_editor_finalize (GObject *object)
 {
   GtkColorEditor *editor = GTK_COLOR_EDITOR (object);
 
-  if (editor->priv->h_surface)
-    cairo_surface_destroy (editor->priv->h_surface);
-  if (editor->priv->a_surface)
-    cairo_surface_destroy (editor->priv->a_surface);
+  g_clear_object (&editor->priv->h_adj);
+  g_clear_object (&editor->priv->s_adj);
+  g_clear_object (&editor->priv->v_adj);
+  g_clear_object (&editor->priv->a_adj);
 
   G_OBJECT_CLASS (gtk_color_editor_parent_class)->finalize (object);
 }
@@ -514,63 +599,52 @@ gtk_color_editor_class_init (GtkColorEditorClass *class)
   object_class->get_property = gtk_color_editor_get_property;
   object_class->set_property = gtk_color_editor_set_property;
 
-  g_object_class_override_property (object_class, PROP_COLOR, "color");
-  g_object_class_override_property (object_class, PROP_SHOW_ALPHA, "show-alpha");
+  g_object_class_override_property (object_class, PROP_RGBA, "rgba");
+  g_object_class_override_property (object_class, PROP_USE_ALPHA, "use-alpha");
 
   g_type_class_add_private (class, sizeof (GtkColorEditorPrivate));
 }
 
 static void
-gtk_color_editor_get_color (GtkColorChooser *chooser,
-                            GdkRGBA         *color)
+gtk_color_editor_get_rgba (GtkColorChooser *chooser,
+                           GdkRGBA         *color)
 {
   GtkColorEditor *editor = GTK_COLOR_EDITOR (chooser);
+  gdouble h, s, v;
 
-  color->red = editor->priv->color.red;
-  color->green = editor->priv->color.green;
-  color->blue = editor->priv->color.blue;
-  color->alpha = editor->priv->color.alpha;
+  h = gtk_adjustment_get_value (editor->priv->h_adj);
+  s = gtk_adjustment_get_value (editor->priv->s_adj);
+  v = gtk_adjustment_get_value (editor->priv->v_adj);
+  gtk_hsv_to_rgb (h, s, v, &color->red, &color->green, &color->blue);
+  color->alpha = gtk_adjustment_get_value (editor->priv->a_adj);
 }
 
 static void
-gtk_color_editor_set_color (GtkColorChooser *chooser,
-                            const GdkRGBA   *color)
+gtk_color_editor_set_rgba (GtkColorChooser *chooser,
+                           const GdkRGBA   *color)
 {
   GtkColorEditor *editor = GTK_COLOR_EDITOR (chooser);
   gdouble h, s, v;
 
-  editor->priv->color.red = color->red;
-  editor->priv->color.green = color->green;
-  editor->priv->color.blue = color->blue;
-  editor->priv->color.alpha = color->alpha;
   gtk_rgb_to_hsv (color->red, color->green, color->blue, &h, &s, &v);
-  editor->priv->h = h;
-  editor->priv->s = s;
-  editor->priv->v = v;
-  gtk_color_plane_set_h (GTK_COLOR_PLANE (editor->priv->sv_plane), h);
-  gtk_color_plane_set_s (GTK_COLOR_PLANE (editor->priv->sv_plane), s);
-  gtk_color_plane_set_v (GTK_COLOR_PLANE (editor->priv->sv_plane), v);
-  gtk_color_swatch_set_color (GTK_COLOR_SWATCH (editor->priv->swatch), color);
 
   gtk_adjustment_set_value (editor->priv->h_adj, h);
+  gtk_adjustment_set_value (editor->priv->s_adj, s);
+  gtk_adjustment_set_value (editor->priv->v_adj, v);
   gtk_adjustment_set_value (editor->priv->a_adj, color->alpha);
-  update_entry (editor);
 
-  if (editor->priv->a_surface)
-    {
-      cairo_surface_destroy (editor->priv->a_surface);
-      editor->priv->a_surface = NULL;
-    }
-  gtk_widget_queue_draw (GTK_WIDGET (editor));
+  gtk_color_swatch_set_rgba (GTK_COLOR_SWATCH (editor->priv->swatch), color);
+  gtk_color_scale_set_rgba (GTK_COLOR_SCALE (editor->priv->a_slider), color);
+  entry_set_rgba (editor, color);
 
-  g_object_notify (G_OBJECT (editor), "color");
+  g_object_notify (G_OBJECT (editor), "rgba");
 }
 
 static void
 gtk_color_editor_iface_init (GtkColorChooserInterface *iface)
 {
-  iface->get_color = gtk_color_editor_get_color;
-  iface->set_color = gtk_color_editor_set_color;
+  iface->get_rgba = gtk_color_editor_get_rgba;
+  iface->set_rgba = gtk_color_editor_set_rgba;
 }
 
 GtkWidget *