[gnome-control-center/wip/feborges/new-users-panel: 36/36] user-accounts: Introduce users list carousel (UmCarousel)



commit 60f88ab62e0dca4da0f0273752c0d3abcd75fc98
Author: Felipe Borges <felipeborges gnome org>
Date:   Wed Jun 22 17:42:24 2016 +0200

    user-accounts: Introduce users list carousel (UmCarousel)

 panels/user-accounts/Makefile.am                  |    4 +
 panels/user-accounts/data/carousel.ui             |   71 +++
 panels/user-accounts/data/user-accounts-dialog.ui |    5 +
 panels/user-accounts/um-arrow-frame.c             |  628 +++++++++++++++++++++
 panels/user-accounts/um-arrow-frame.h             |   37 ++
 panels/user-accounts/um-carousel.c                |  204 +++++++
 panels/user-accounts/um-carousel.h                |   22 +
 panels/user-accounts/um-user-panel.c              |    8 +
 panels/user-accounts/user-accounts.gresource.xml  |    1 +
 9 files changed, 980 insertions(+), 0 deletions(-)
---
diff --git a/panels/user-accounts/Makefile.am b/panels/user-accounts/Makefile.am
index d473bc5..c77359d 100644
--- a/panels/user-accounts/Makefile.am
+++ b/panels/user-accounts/Makefile.am
@@ -36,6 +36,8 @@ libuser_accounts_la_SOURCES =         \
        pw-utils.h                      \
        pw-utils.c                      \
        um-photo-dialog.h               \
+       um-carousel.h                   \
+       um-carousel.c                   \
        um-photo-dialog.c               \
        cc-crop-area.h                  \
        cc-crop-area.c                  \
@@ -46,6 +48,8 @@ libuser_accounts_la_SOURCES =         \
        fingerprint-strings.h           \
        run-passwd.h                    \
        run-passwd.c                    \
+       um-carousel.h                   \
+       um-carousel.c                   \
        um-editable-button.h            \
        um-editable-button.c            \
        um-editable-combo.h             \
diff --git a/panels/user-accounts/data/carousel.ui b/panels/user-accounts/data/carousel.ui
new file mode 100644
index 0000000..dc13fe9
--- /dev/null
+++ b/panels/user-accounts/data/carousel.ui
@@ -0,0 +1,71 @@
+<?xml version="1.0"?>
+<interface>
+  <!-- interface-requires gtk+ 3.8 -->
+  <template class="UmCarousel" parent="GtkGrid">
+    <property name="visible">True</property>
+    <child>
+      <object class="GtkOverlay">
+        <property name="visible">True</property>
+        <property name="expand">True</property>
+        <child>
+          <object class="GtkStack" id="stack">
+            <property name="visible">True</property>
+            <property name="transition_type">slide-left</property>
+            <property name="transition_duration">400</property>
+            <child>
+              <placeholder/>
+            </child>
+          </object>
+        </child>
+        <child type="overlay">
+          <object class="GtkBox">
+            <property name="visible">True</property>
+            <property name="orientation">horizontal</property>
+            <child>
+              <object class="GtkButton" id="go_back_button">
+                <property name="visible">True</property>
+                <property name="can_focus">True</property>
+                <style>
+                  <class name="flat"/>
+                </style>
+                <child>
+                  <object class="GtkImage">
+                    <property name="visible">True</property>
+                    <property name="can_focus">False</property>
+                    <property name="icon-size">3</property>
+                    <property name="icon_name">go-previous-symbolic</property>
+                  </object>
+                </child>
+                <signal name="clicked" handler="um_carousel_go_back_button_clicked" object="UmCarousel" 
swapped="no"/>
+              </object>
+              <packing>
+                <property name="pack_type">start</property>
+              </packing>
+            </child>
+            <child>
+              <object class="GtkButton" id="go_next_button">
+                <property name="visible">True</property>
+                <property name="can_focus">True</property>
+                <style>
+                  <class name="flat"/>
+                </style>
+                <child>
+                  <object class="GtkImage">
+                    <property name="visible">True</property>
+                    <property name="can_focus">False</property>
+                    <property name="icon-size">3</property>
+                    <property name="icon_name">go-next-symbolic</property>
+                  </object>
+                </child>
+                <signal name="clicked" handler="um_carousel_go_next_button_clicked" object="UmCarousel" 
swapped="no"/>
+              </object>
+              <packing>
+                <property name="pack_type">end</property>
+              </packing>
+            </child>
+          </object>
+        </child>
+      </object>
+    </child>
+  </template>
+</interface>
diff --git a/panels/user-accounts/data/user-accounts-dialog.ui 
b/panels/user-accounts/data/user-accounts-dialog.ui
index bb68c97..c481a71 100644
--- a/panels/user-accounts/data/user-accounts-dialog.ui
+++ b/panels/user-accounts/data/user-accounts-dialog.ui
@@ -23,6 +23,11 @@
             <property name="orientation">vertical</property>
             <property name="border_width">12</property>
             <child>
+              <object class="UmCarousel" id="carousel">
+                <property name="visible">True</property>
+              </object>
+            </child>
+            <child>
               <object class="GtkHBox" id="hbox2">
                 <property name="visible">True</property>
                 <property name="spacing">18</property>
diff --git a/panels/user-accounts/um-arrow-frame.c b/panels/user-accounts/um-arrow-frame.c
new file mode 100644
index 0000000..2e2c8dc
--- /dev/null
+++ b/panels/user-accounts/um-arrow-frame.c
@@ -0,0 +1,628 @@
+/* gtd-arrow-frame.c
+ *
+ * Copyright (C) 2015 Georges Basile Stavracas Neto <georges stavracas gmail com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "um-arrow-frame.h"
+
+typedef struct
+{
+  GtkWidget           *row;
+
+  GtkGesture          *pan_gesture;
+
+  GdkWindow           *handle_window;
+  gdouble              offset;
+  gint                 moving : 1;
+} UmArrowFramePrivate;
+
+struct _UmArrowFrame
+{
+  GtkFrame              parent;
+
+  /*<private>*/
+  UmArrowFramePrivate *priv;
+};
+
+#define ARROW_HEIGHT 28
+#define ARROW_WIDTH  8
+#define HANDLE_GAP   (ARROW_WIDTH + 5)
+
+G_DEFINE_TYPE_WITH_PRIVATE (UmArrowFrame, um_arrow_frame, GTK_TYPE_FRAME)
+
+enum {
+  PROP_0,
+  PROP_TASK_ROW,
+  LAST_PROP
+};
+
+GtkWidget*
+um_arrow_frame_new (void)
+{
+  return g_object_new (UM_TYPE_ARROW_FRAME, NULL);
+}
+
+static void
+on_drag_begin_cb (UmArrowFrame *frame,
+                  gdouble        x,
+                  gdouble        y,
+                  GtkGesturePan *gesture)
+{
+  UmArrowFramePrivate *priv = frame->priv;
+  GdkEventSequence *sequence;
+  const GdkEvent *event;
+
+  sequence = gtk_gesture_single_get_current_sequence (GTK_GESTURE_SINGLE (gesture));
+  event = gtk_gesture_get_last_event (GTK_GESTURE (gesture), sequence);
+
+  priv->moving = FALSE;
+
+  if (event->any.window == priv->handle_window)
+    gtk_gesture_set_state (GTK_GESTURE (gesture), GTK_EVENT_SEQUENCE_CLAIMED);
+  else
+    gtk_gesture_set_state (GTK_GESTURE (gesture), GTK_EVENT_SEQUENCE_DENIED);
+}
+
+static void
+on_pan_cb (GtkWidget       *widget,
+           GtkPanDirection  direction,
+           gdouble          offset,
+           GtkGesturePan   *pan)
+{
+  UmArrowFramePrivate *priv;
+  GtkTextDirection dir;
+  gdouble offset_x;
+
+  priv = UM_ARROW_FRAME (widget)->priv;
+  dir = gtk_widget_get_direction (widget);
+
+  priv->moving = TRUE;
+
+  gtk_gesture_drag_get_offset (GTK_GESTURE_DRAG (pan),
+                               &offset_x,
+                               NULL);
+
+  if (dir == GTK_TEXT_DIR_RTL)
+    priv->offset = MAX (0, offset_x);
+  else
+    priv->offset = MAX (0, priv->offset + -1 * offset_x);
+
+
+  gtk_widget_queue_resize (widget);
+}
+
+static void
+on_drag_end_cb (UmArrowFrame *frame,
+                gdouble        x,
+                gdouble        y,
+                GtkGesturePan *pan)
+{
+  UmArrowFramePrivate *priv = frame->priv;
+
+  if (!priv->moving)
+    gtk_gesture_set_state (GTK_GESTURE (pan), GTK_EVENT_SEQUENCE_DENIED);
+}
+
+static void
+um_arrow_frame_finalize (GObject *object)
+{
+  UmArrowFrame *self = (UmArrowFrame *)object;
+  UmArrowFramePrivate *priv = um_arrow_frame_get_instance_private (self);
+
+  g_clear_object (&priv->pan_gesture);
+
+  G_OBJECT_CLASS (um_arrow_frame_parent_class)->finalize (object);
+}
+
+static void
+um_arrow_frame_get_property (GObject    *object,
+                             guint       prop_id,
+                             GValue     *value,
+                             GParamSpec *pspec)
+{
+  UmArrowFrame *self = UM_ARROW_FRAME (object);
+
+  switch (prop_id)
+    {
+    case PROP_TASK_ROW:
+      g_value_set_object (value, self->priv->row);
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+    }
+}
+
+static void
+um_arrow_frame_set_property (GObject      *object,
+                             guint         prop_id,
+                             const GValue *value,
+                             GParamSpec   *pspec)
+{
+  UmArrowFrame *self = UM_ARROW_FRAME (object);
+
+  switch (prop_id)
+    {
+    case PROP_TASK_ROW:
+      um_arrow_frame_set_row (self, g_value_get_object (value));
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+    }
+}
+
+static void
+um_arrow_frame__row_destroyed (UmArrowFrame *frame)
+{
+  um_arrow_frame_set_row (frame, NULL);
+}
+
+static gint
+um_arrow_frame__get_row_y (UmArrowFrame *frame)
+{
+  GtkWidget *widget;
+  GtkWidget *row;
+  gint row_height;
+  gint dest_y;
+
+  widget = GTK_WIDGET (frame);
+
+  if (!frame->priv->row)
+    return gtk_widget_get_allocated_height (widget) / 2;
+
+  row = GTK_WIDGET (frame->priv->row);
+  row_height = gtk_widget_get_allocated_height (row);
+
+  gtk_widget_translate_coordinates (row,
+                                    widget,
+                                    0,
+                                    row_height / 2,
+                                    NULL,
+                                    &dest_y);
+
+  return CLAMP (dest_y,
+                0,
+                gtk_widget_get_allocated_height (widget));
+}
+
+static void
+um_arrow_frame__draw_arrow (UmArrowFrame    *frame,
+                            cairo_t          *cr,
+                            GtkTextDirection  dir)
+{
+  GtkWidget *widget = GTK_WIDGET (frame);
+  GtkStyleContext *context;
+  GtkAllocation alloc;
+  GtkStateFlags state;
+  GtkBorder border;
+  GdkRGBA border_color;
+  gint border_width;
+  gint start_x;
+  gint start_y;
+  gint tip_x;
+  gint tip_y;
+  gint end_x;
+  gint end_y;
+
+  context = gtk_widget_get_style_context (widget);
+  state = gtk_style_context_get_state (context);
+
+  gtk_style_context_get_border (context,
+                                state,
+                                &border);
+
+  gtk_style_context_get (context,
+                         state,
+                         GTK_STYLE_PROPERTY_BORDER_COLOR, &border_color,
+                         NULL);
+
+  /* widget size */
+  gtk_widget_get_allocation (widget, &alloc);
+
+  if (dir == GTK_TEXT_DIR_LTR)
+    {
+      start_x = ARROW_WIDTH + border.left;
+      tip_x = 0;
+      end_x = ARROW_WIDTH + border.left;
+      border_width = border.left;
+    }
+  else
+    {
+      start_x = alloc.width - ARROW_WIDTH - border.right;
+      tip_x = alloc.width;
+      end_x = alloc.width - ARROW_WIDTH - border.right;
+      border_width = border.right;
+    }
+
+  tip_y = um_arrow_frame__get_row_y (frame);
+  start_y = tip_y - (ARROW_HEIGHT / 2);
+  end_y = tip_y + (ARROW_HEIGHT / 2);
+
+  /* draw arrow */
+  cairo_save (cr);
+
+  cairo_set_line_width (cr, 1.0);
+  cairo_move_to (cr, start_x, start_y);
+  cairo_line_to (cr, tip_x,   tip_y);
+  cairo_line_to (cr, end_x,   end_y);
+
+  /*
+   * Don't allow that gtk_render_background renders
+   * anything out of (tip_x, start_y) (end_x, end_y).
+   */
+  cairo_clip (cr);
+
+  /* render the arrow background */
+  gtk_render_background (context,
+                         cr,
+                         0,
+                         0,
+                         alloc.width,
+                         alloc.height);
+
+  /* draw the border */
+  if (border_width > 0)
+    {
+G_GNUC_BEGIN_IGNORE_DEPRECATIONS
+
+      gtk_style_context_get_border_color (context,
+                                          state,
+                                          &border_color);
+
+G_GNUC_END_IGNORE_DEPRECATIONS
+
+      gdk_cairo_set_source_rgba (cr, &border_color);
+
+      cairo_set_line_width (cr, 1);
+      cairo_move_to (cr, start_x, start_y);
+      cairo_line_to (cr, tip_x,   tip_y);
+      cairo_line_to (cr, end_x,   end_y);
+
+      cairo_set_line_width (cr, border_width + 1);
+      cairo_stroke (cr);
+    }
+
+  cairo_restore (cr);
+}
+
+static GtkGesture*
+um_arrow_frame__create_pan_gesture (UmArrowFrame  *frame)
+{
+  GtkGesture *gesture;
+
+  gesture = gtk_gesture_pan_new (GTK_WIDGET (frame), GTK_ORIENTATION_HORIZONTAL);
+
+  gtk_gesture_single_set_touch_only (GTK_GESTURE_SINGLE (gesture), FALSE);
+  gtk_event_controller_set_propagation_phase (GTK_EVENT_CONTROLLER (gesture), GTK_PHASE_CAPTURE);
+
+  g_signal_connect_swapped (gesture,
+                            "drag-begin",
+                            G_CALLBACK (on_drag_begin_cb),
+                            frame);
+  g_signal_connect_swapped (gesture,
+                            "pan",
+                            G_CALLBACK (on_pan_cb),
+                            frame);
+  g_signal_connect_swapped (gesture,
+                            "drag-end",
+                            G_CALLBACK (on_drag_end_cb),
+                            frame);
+
+  return gesture;
+}
+
+static void
+um_arrow_frame__draw_background (UmArrowFrame    *frame,
+                                 cairo_t          *cr,
+                                 GtkTextDirection  dir)
+{
+  GtkWidget *widget = GTK_WIDGET (frame);
+  GtkStyleContext *context;
+  GtkAllocation alloc;
+  GtkStateFlags state;
+  GtkBorder margin;
+  gint start_x;
+  gint start_y;
+  gint start_gap;
+  gint end_x;
+  gint end_y;
+  gint end_gap;
+
+  context = gtk_widget_get_style_context (widget);
+  state = gtk_style_context_get_state (context);
+
+  /* widget size */
+  gtk_widget_get_allocation (widget, &alloc);
+
+  /* margin */
+  gtk_style_context_get_margin (context,
+                                state,
+                                &margin);
+
+  if (dir == GTK_TEXT_DIR_LTR)
+    {
+      start_x = ARROW_WIDTH + margin.left;
+      end_x = alloc.width - margin.right;
+    }
+  else
+    {
+      start_x = margin.left;
+      end_x = alloc.width - margin.right - ARROW_WIDTH;
+    }
+
+
+  start_y = margin.top;
+  end_y = alloc.height - margin.bottom;
+
+  start_gap = ((end_y - start_y - ARROW_HEIGHT) / 2);
+  end_gap = ((end_y - start_y + ARROW_HEIGHT) / 2);
+
+  gtk_render_background (context,
+                         cr,
+                         start_x,
+                         start_y,
+                         end_x,
+                         end_y);
+
+  gtk_render_frame_gap (context,
+                        cr,
+                        start_x,
+                        start_y,
+                        end_x,
+                        end_y,
+                        dir == GTK_TEXT_DIR_LTR ? GTK_POS_LEFT : GTK_POS_RIGHT,
+                        start_gap,
+                        end_gap);
+
+}
+
+static gboolean
+um_arrow_frame_draw (GtkWidget *widget,
+                     cairo_t   *cr)
+{
+  UmArrowFrame *frame = UM_ARROW_FRAME (widget);
+  GtkTextDirection direction;
+  GtkWidget *child;
+
+  direction = gtk_widget_get_direction (widget);
+
+  um_arrow_frame__draw_background (frame,
+                                   cr,
+                                   direction);
+
+  um_arrow_frame__draw_arrow (frame,
+                              cr,
+                              direction);
+
+  child = gtk_bin_get_child (GTK_BIN (widget));
+
+  if (child)
+    {
+      gtk_container_propagate_draw (GTK_CONTAINER (widget),
+                                    child,
+                                    cr);
+    }
+
+  return TRUE;
+}
+
+static void
+um_arrow_frame_compute_child_allocation (GtkFrame      *frame,
+                                         GtkAllocation *allocation)
+{
+  GTK_FRAME_CLASS (um_arrow_frame_parent_class)->compute_child_allocation (frame, allocation);
+
+  allocation->width -= ARROW_WIDTH;
+
+  if (gtk_widget_get_direction (GTK_WIDGET (frame)) != GTK_TEXT_DIR_RTL)
+    {
+      allocation->x += ARROW_WIDTH;
+    }
+}
+
+static void
+um_arrow_frame_get_preferred_width (GtkWidget *widget,
+                                    gint      *minimum_width,
+                                    gint      *natural_width)
+{
+  UmArrowFramePrivate *priv;
+
+  priv = UM_ARROW_FRAME (widget)->priv;
+
+  GTK_WIDGET_CLASS (um_arrow_frame_parent_class)->get_preferred_width (widget,
+                                                                       minimum_width,
+                                                                       natural_width);
+
+  *minimum_width += ARROW_WIDTH;
+  *natural_width += ARROW_WIDTH;
+
+  *natural_width = MAX (*minimum_width, *natural_width + priv->offset);
+}
+
+static void
+um_arrow_frame_realize (GtkWidget *widget)
+{
+  UmArrowFramePrivate *priv;
+  GtkTextDirection dir;
+  GtkAllocation allocation;
+  GdkWindowAttr attributes = { 0 };
+  GdkDisplay *display;
+  GdkWindow *parent_window;
+  gint attributes_mask;
+
+  priv = UM_ARROW_FRAME (widget)->priv;
+  dir = gtk_widget_get_direction (widget);
+  display = gtk_widget_get_display (widget);
+  parent_window = gtk_widget_get_parent_window (widget);
+
+  gtk_widget_set_realized (widget, TRUE);
+
+  gtk_widget_set_window (widget, parent_window);
+  g_object_ref (parent_window);
+
+  gtk_widget_get_allocation (widget, &allocation);
+
+  attributes.window_type = GDK_WINDOW_CHILD;
+  attributes.wclass = GDK_INPUT_ONLY;
+  attributes.x = dir == GTK_TEXT_DIR_LTR ? allocation.x : allocation.height - HANDLE_GAP;
+  attributes.y = allocation.y;
+  attributes.width = HANDLE_GAP;
+  attributes.height = allocation.height;
+  attributes.visual = gtk_widget_get_visual (widget);
+  attributes.cursor = gdk_cursor_new_for_display (display, GDK_SB_H_DOUBLE_ARROW);
+  attributes.event_mask = gtk_widget_get_events (widget);
+  attributes.event_mask |= (GDK_BUTTON_PRESS_MASK |
+                            GDK_BUTTON_RELEASE_MASK |
+                            GDK_ENTER_NOTIFY_MASK |
+                            GDK_LEAVE_NOTIFY_MASK |
+                            GDK_POINTER_MOTION_MASK);
+
+  attributes_mask = GDK_WA_CURSOR | GDK_WA_X | GDK_WA_Y;
+
+  priv->handle_window = gdk_window_new (parent_window,
+                                        &attributes,
+                                        attributes_mask);
+
+  gtk_widget_register_window (widget, priv->handle_window);
+
+  g_clear_object (&attributes.cursor);
+}
+
+static void
+um_arrow_frame_unrealize (GtkWidget *widget)
+{
+  UmArrowFramePrivate *priv;
+
+  priv = UM_ARROW_FRAME (widget)->priv;
+
+  if (priv->handle_window)
+    {
+      gdk_window_hide (priv->handle_window);
+      gtk_widget_unregister_window (widget, priv->handle_window);
+      g_clear_pointer (&priv->handle_window, gdk_window_destroy);
+    }
+
+  GTK_WIDGET_CLASS (um_arrow_frame_parent_class)->unrealize (widget);
+}
+
+static void
+um_arrow_frame_map (GtkWidget *widget)
+{
+  UmArrowFramePrivate *priv;
+
+  priv = UM_ARROW_FRAME (widget)->priv;
+
+  if (priv->handle_window)
+    gdk_window_show (priv->handle_window);
+
+  GTK_WIDGET_CLASS (um_arrow_frame_parent_class)->map (widget);
+}
+
+static void
+um_arrow_frame_unmap (GtkWidget *widget)
+{
+  UmArrowFramePrivate *priv;
+
+  priv = UM_ARROW_FRAME (widget)->priv;
+
+  if (priv->handle_window)
+    gdk_window_hide (priv->handle_window);
+
+  GTK_WIDGET_CLASS (um_arrow_frame_parent_class)->unmap (widget);
+}
+
+static void
+um_arrow_frame_size_allocate (GtkWidget     *widget,
+                              GtkAllocation *allocation)
+{
+  UmArrowFramePrivate *priv;
+  GtkTextDirection dir;
+
+  priv = UM_ARROW_FRAME (widget)->priv;
+  dir = gtk_widget_get_direction (widget);
+
+  GTK_WIDGET_CLASS (um_arrow_frame_parent_class)->size_allocate (widget, allocation);
+
+  gtk_widget_set_allocation (widget, allocation);
+
+  if (gtk_widget_get_realized (widget))
+    {
+      gdk_window_move_resize (priv->handle_window,
+                              dir == GTK_TEXT_DIR_RTL ? allocation->width - HANDLE_GAP : allocation->x,
+                              allocation->y,
+                              HANDLE_GAP,
+                              allocation->height);
+
+      gdk_window_raise (priv->handle_window);
+    }
+}
+
+static void
+um_arrow_frame_class_init (UmArrowFrameClass *klass)
+{
+  GObjectClass *object_class = G_OBJECT_CLASS (klass);
+  GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
+  GtkFrameClass *frame_class = GTK_FRAME_CLASS (klass);
+
+  object_class->finalize = um_arrow_frame_finalize;
+  object_class->get_property = um_arrow_frame_get_property;
+  object_class->set_property = um_arrow_frame_set_property;
+
+  widget_class->draw = um_arrow_frame_draw;
+  widget_class->get_preferred_width = um_arrow_frame_get_preferred_width;
+  widget_class->map = um_arrow_frame_map;
+  widget_class->unmap = um_arrow_frame_unmap;
+  widget_class->realize = um_arrow_frame_realize;
+  widget_class->unrealize = um_arrow_frame_unrealize;
+  widget_class->size_allocate = um_arrow_frame_size_allocate;
+
+  frame_class->compute_child_allocation = um_arrow_frame_compute_child_allocation;
+
+  gtk_widget_class_set_css_name (widget_class, "arrow-frame");
+}
+
+static void
+um_arrow_frame_init (UmArrowFrame *self)
+{
+  self->priv = um_arrow_frame_get_instance_private (self);
+
+  self->priv->pan_gesture = um_arrow_frame__create_pan_gesture (self);
+}
+
+void
+um_arrow_frame_set_row (UmArrowFrame *frame,
+                        GtkWidget     *row)
+{
+  g_return_if_fail (UM_IS_ARROW_FRAME (frame));
+
+  if (frame->priv->row)
+    {
+      g_signal_handlers_disconnect_by_func (frame->priv->row, um_arrow_frame__row_destroyed, frame);
+    }
+
+  frame->priv->row = row;
+
+  if (row)
+    {
+      g_signal_connect_swapped (row,
+                                "destroy",
+                                G_CALLBACK (um_arrow_frame__row_destroyed),
+                                frame);
+
+      gtk_widget_queue_draw (GTK_WIDGET (frame));
+    }
+}
diff --git a/panels/user-accounts/um-arrow-frame.h b/panels/user-accounts/um-arrow-frame.h
new file mode 100644
index 0000000..1008c44
--- /dev/null
+++ b/panels/user-accounts/um-arrow-frame.h
@@ -0,0 +1,37 @@
+/* gtd-arrow-frame.h
+ *
+ * Copyright (C) 2015 Georges Basile Stavracas Neto <georges stavracas gmail com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef UM_ARROW_FRAME_H
+#define UM_ARROW_FRAME_H
+
+#include <gtk/gtk.h>
+
+G_BEGIN_DECLS
+
+#define UM_TYPE_ARROW_FRAME (um_arrow_frame_get_type())
+
+G_DECLARE_FINAL_TYPE (UmArrowFrame, um_arrow_frame, UM, ARROW_FRAME, GtkFrame)
+
+GtkWidget*                      um_arrow_frame_new               (void);
+
+void                            um_arrow_frame_set_row           (UmArrowFrame          *frame,
+                                                                   GtkWidget              *row);
+
+G_END_DECLS
+
+#endif /* UM_ARROW_FRAME_H */
diff --git a/panels/user-accounts/um-carousel.c b/panels/user-accounts/um-carousel.c
new file mode 100644
index 0000000..54c71b0
--- /dev/null
+++ b/panels/user-accounts/um-carousel.c
@@ -0,0 +1,204 @@
+#include "um-carousel.h"
+
+#include <glib-object.h>
+#include <gtk/gtk.h>
+
+#define ITEMS_PER_PAGE 3
+
+typedef struct {
+        GSequence *children;
+        GSequence *pages;
+        gint current_page;
+
+        /* Widgets */
+        GtkStack *stack;
+} UmCarouselPrivate;
+
+struct _UmCarousel {
+        GtkGrid parent;
+
+        UmCarouselPrivate *priv;
+};
+
+G_DEFINE_TYPE_WITH_PRIVATE (UmCarousel, um_carousel, GTK_TYPE_GRID)
+
+void
+um_carousel_insert (UmCarousel *self,
+                    GtkWidget  *child,
+                    gint        position)
+{
+        UmCarouselPrivate *priv;
+        GSequenceIter *iter = NULL;
+        GtkWidget *box;
+        gint n_children;
+
+        priv = self->priv;
+
+        n_children = g_sequence_get_length (priv->children);
+        if (n_children > 0 && (n_children % ITEMS_PER_PAGE == 0)) {
+                gchar *page_name;
+
+                box = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0);
+                page_name = g_strdup_printf ("%d", g_sequence_get_length (priv->pages));
+                gtk_stack_add_named (priv->stack, box, page_name);
+
+                g_sequence_append (priv->pages, box);
+        }
+        else {
+                iter = g_sequence_get_end_iter (priv->pages);
+                iter = g_sequence_iter_prev (iter);
+                box = g_sequence_get (iter);
+        }
+
+        gtk_box_pack_end (GTK_BOX (box), child, TRUE, TRUE, 0);
+        gtk_widget_show_all (box);
+
+        if (position == 0)
+                g_sequence_prepend (priv->children, child);
+        else if (position == -1)
+                g_sequence_append (priv->children, child);
+        else {
+                iter = g_sequence_get_iter_at_pos (priv->children, position);
+                g_sequence_insert_before (iter, child);
+        }
+}
+
+GtkWidget *
+um_carousel_get_child_at_index (UmCarousel *self,
+                                gint        index)
+{
+        UmCarouselPrivate *priv;
+        GSequenceIter *iter;
+
+        priv = self->priv;
+
+        iter = g_sequence_get_iter_at_pos (priv->children, index);
+        if (!g_sequence_iter_is_end (iter))
+                return g_sequence_get (iter);
+
+        return NULL;
+}
+
+static void
+um_carousel_go_back_button_clicked (GtkButton *button,
+                                    gpointer   user_data)
+{
+        UmCarouselPrivate *priv;
+        gchar *page_name;
+
+        priv = UM_CAROUSEL (user_data)->priv;
+
+        priv->current_page--;
+        if (priv->current_page < 0)
+                priv->current_page = g_sequence_get_length (priv->pages) - 1;
+
+        page_name = g_strdup_printf ("%d", priv->current_page);
+
+        gtk_stack_set_visible_child_name (priv->stack, page_name);
+}
+
+static void
+um_carousel_go_next_button_clicked (GtkButton *button,
+                                    gpointer   user_data)
+{
+        UmCarouselPrivate *priv;
+        gchar *page_name;
+
+        priv = UM_CAROUSEL (user_data)->priv;
+
+        priv->current_page++;
+        if (priv->current_page >= g_sequence_get_length (priv->pages))
+                priv->current_page = 0;
+
+        page_name = g_strdup_printf ("%d", priv->current_page);
+        gtk_stack_set_visible_child_name (priv->stack, page_name);
+}
+
+enum {
+        PROP_0,
+        N_PROPS
+};
+
+static GParamSpec *properties [N_PROPS];
+
+UmCarousel *
+um_carousel_new (void)
+{
+        return g_object_new (UM_TYPE_CAROUSEL, NULL);
+}
+
+static void
+um_carousel_finalize (GObject *object)
+{
+        UmCarousel *self = (UmCarousel *)object;
+       UmCarouselPrivate *priv = um_carousel_get_instance_private (self);
+
+       G_OBJECT_CLASS (um_carousel_parent_class)->finalize (object);
+}
+
+static void
+um_carousel_get_property (GObject    *object,
+                          guint       prop_id,
+                          GValue     *value,
+                          GParamSpec *pspec)
+{
+        UmCarousel *self = UM_CAROUSEL (object);
+
+        switch (prop_id) {
+        default:
+                G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+        }
+}
+
+static void
+um_carousel_set_property (GObject      *object,
+                          guint         prop_id,
+                          const GValue *value,
+                          GParamSpec   *pspec)
+{
+        UmCarousel *self = UM_CAROUSEL (object);
+
+        switch (prop_id) {
+        default:
+                G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+        }
+}
+
+static void
+um_carousel_class_init (UmCarouselClass *klass)
+{
+        GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
+        GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+        object_class->finalize = um_carousel_finalize;
+        object_class->get_property = um_carousel_get_property;
+        object_class->set_property = um_carousel_set_property;
+
+        gtk_widget_class_set_template_from_resource (widget_class,
+                                                     "/org/gnome/control-center/user-accounts/carousel.ui");
+
+        gtk_widget_class_bind_template_child_private (widget_class, UmCarousel, stack);
+
+       gtk_widget_class_bind_template_callback (widget_class, um_carousel_go_back_button_clicked);
+       gtk_widget_class_bind_template_callback (widget_class, um_carousel_go_next_button_clicked);
+}
+
+static void
+um_carousel_init (UmCarousel *self)
+{
+        UmCarouselPrivate *priv = um_carousel_get_instance_private (self);
+        GtkWidget *initial_box;
+
+        self->priv = priv;
+
+        priv->children = g_sequence_new (NULL);
+        priv->pages = g_sequence_new (NULL);
+        priv->current_page = 0;
+
+        gtk_widget_init_template (GTK_WIDGET (self));
+
+        initial_box = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0);
+        g_sequence_append (priv->pages, initial_box);
+        gtk_stack_add_named (priv->stack, initial_box, "0");
+        gtk_widget_show_all (GTK_WIDGET (priv->stack));
+}
diff --git a/panels/user-accounts/um-carousel.h b/panels/user-accounts/um-carousel.h
new file mode 100644
index 0000000..3c31979
--- /dev/null
+++ b/panels/user-accounts/um-carousel.h
@@ -0,0 +1,22 @@
+#ifndef UM_CAROUSEL_H
+#define UM_CAROUSEL_H
+
+#include <glib-object.h>
+#include <gtk/gtk.h>
+
+G_BEGIN_DECLS
+
+#define UM_TYPE_CAROUSEL (um_carousel_get_type())
+
+G_DECLARE_FINAL_TYPE (UmCarousel, um_carousel, UM, CAROUSEL, GtkGrid)
+
+UmCarousel *um_carousel_new                (void);
+GtkWidget  *um_carousel_get_child_at_index (UmCarousel *self,
+                                            gint        index);
+void        um_carousel_insert             (UmCarousel *self,
+                                            GtkWidget  *child,
+                                            gint        position);
+
+G_END_DECLS
+
+#endif /* UM_CAROUSEL_H */
diff --git a/panels/user-accounts/um-user-panel.c b/panels/user-accounts/um-user-panel.c
index 2b6ea7e..2f682a1 100644
--- a/panels/user-accounts/um-user-panel.c
+++ b/panels/user-accounts/um-user-panel.c
@@ -45,6 +45,7 @@
 #include "um-editable-combo.h"
 #include "um-user-image.h"
 #include "um-cell-renderer-user-image.h"
+#include "um-carousel.h"
 
 #include "um-account-dialog.h"
 #include "cc-language-chooser.h"
@@ -74,6 +75,7 @@ struct _CcUserPanelPrivate {
         GtkWidget *notification;
         GSettings *login_screen_settings;
 
+        UmCarousel *carousel;
         GtkWidget *main_box;
         GPermission *permission;
         GtkWidget *language_chooser;
@@ -244,6 +246,9 @@ user_added (ActUserManager *um, ActUser *user, CcUserPanelPrivate *d)
                 d->other_iter = gtk_tree_iter_copy (&iter);
                 g_free (title);
         }
+
+        widget = gtk_label_new (get_real_or_user_name (user));
+        um_carousel_insert (d->carousel, widget, -1);
 }
 
 static void
@@ -1742,6 +1747,7 @@ cc_user_panel_init (CcUserPanel *self)
         type = um_editable_combo_get_type ();
         type = um_user_image_get_type ();
         type = um_cell_renderer_user_image_get_type ();
+        type = um_carousel_get_type ();
 
         gtk_widget_set_size_request (GTK_WIDGET (self), -1, 350);
 
@@ -1768,6 +1774,8 @@ cc_user_panel_init (CcUserPanel *self)
         d->history_dialog = um_history_dialog_new ();
         setup_main_window (self);
 
+        d->carousel = UM_CAROUSEL (get_widget (d, "carousel"));
+
         context = gtk_widget_get_style_context (get_widget (d, "list-scrolledwindow"));
         gtk_style_context_set_junction_sides (context, GTK_JUNCTION_BOTTOM);
         context = gtk_widget_get_style_context (get_widget (d, "add-remove-toolbar"));
diff --git a/panels/user-accounts/user-accounts.gresource.xml 
b/panels/user-accounts/user-accounts.gresource.xml
index ad57a8a..4bedc2d 100644
--- a/panels/user-accounts/user-accounts.gresource.xml
+++ b/panels/user-accounts/user-accounts.gresource.xml
@@ -7,6 +7,7 @@
     <file alias="password-dialog.ui" preprocess="xml-stripblanks">data/password-dialog.ui</file>
     <file alias="history-dialog.ui" preprocess="xml-stripblanks">data/history-dialog.ui</file>
     <file alias="user-accounts-dialog.ui" preprocess="xml-stripblanks">data/user-accounts-dialog.ui</file>
+    <file alias="carousel.ui" preprocess="xml-stripblanks">data/carousel.ui</file>
     <file alias="left-index-finger.png">data/icons/left-index-finger.png</file>
     <file alias="left-middle-finger.png">data/icons/left-middle-finger.png</file>
     <file alias="left-little-finger.png">data/icons/left-little-finger.png</file>


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