[gnome-builder] transfers: add IdeTransfersButton to workbench headerbar



commit f20a75bd469ec353f680cd81027a10c597589184
Author: Christian Hergert <chergert redhat com>
Date:   Thu Aug 25 19:54:14 2016 -0700

    transfers: add IdeTransfersButton to workbench headerbar
    
    When the transfer manager has active (or historic) transfers, the button
    will be visible. The download progress icon comes from Epiphany and has
    been modified to fit our use-case a bit better.

 data/theme/Adwaita-shared.css                  |   14 +
 data/theme/shared.css                          |    8 +-
 libide/Makefile.am                             |    6 +
 libide/resources/libide.gresource.xml          |    2 +
 libide/transfers/ide-transfer-row.c            |  230 ++++++++++++++++++
 libide/transfers/ide-transfer-row.h            |   39 +++
 libide/transfers/ide-transfer-row.ui           |   80 ++++++
 libide/transfers/ide-transfers-button.c        |  309 ++++++++++++++++++++++++
 libide/transfers/ide-transfers-button.h        |   35 +++
 libide/transfers/ide-transfers-button.ui       |   57 +++++
 libide/transfers/ide-transfers-progress-icon.c |  184 ++++++++++++++
 libide/transfers/ide-transfers-progress-icon.h |   37 +++
 libide/workbench/ide-workbench-header-bar.ui   |   32 ++-
 13 files changed, 1020 insertions(+), 13 deletions(-)
---
diff --git a/data/theme/Adwaita-shared.css b/data/theme/Adwaita-shared.css
index 37289a3..a55a7f6 100644
--- a/data/theme/Adwaita-shared.css
+++ b/data/theme/Adwaita-shared.css
@@ -210,3 +210,17 @@ popover.messagepopover .popover-action-area button:last-child {
 popover.messagepopover .popover-content-area {
   margin: 24px;
 }
+
+
+popover.transfers list {
+  background-color: transparent;
+}
+popover.transfers list row {
+  border-top: 1px solid @borders;
+}
+popover.transfers list row:first-child {
+  border-top: none;
+}
+popover.transfers list row > box {
+  padding: 10px;
+}
diff --git a/data/theme/shared.css b/data/theme/shared.css
index 92d723e..9aa81b3 100644
--- a/data/theme/shared.css
+++ b/data/theme/shared.css
@@ -86,7 +86,7 @@ frame.gb-search-frame > box > grid:first-child > button.close:backdrop image {
 popover list row {
   padding: 6px;
 }
-popover list row button {
+popover list row button:not(.circular) {
   background: transparent;
   border: none;
   box-shadow: none;
@@ -94,11 +94,11 @@ popover list row button {
   padding: 0;
   opacity: 0.25;
 }
-popover list row:selected button,
-popover list row:selected button:hover {
+popover list row:selected button:not(.circular),
+popover list row:selected button:hover:not(.circular) {
   color: @theme_selected_fg_color;
 }
-popover list row button:hover {
+popover list row button:hover:not(.circular) {
   opacity: 1;
 }
 
diff --git a/libide/Makefile.am b/libide/Makefile.am
index 3e23f1b..3f03fb2 100644
--- a/libide/Makefile.am
+++ b/libide/Makefile.am
@@ -135,6 +135,12 @@ libide_1_0_la_public_headers =                            \
        transfers/ide-transfer.h                          \
        transfers/ide-transfer-manager.c                  \
        transfers/ide-transfer-manager.h                  \
+       transfers/ide-transfer-row.c                      \
+       transfers/ide-transfer-row.h                      \
+       transfers/ide-transfers-button.c                  \
+       transfers/ide-transfers-button.h                  \
+       transfers/ide-transfers-progress-icon.c           \
+       transfers/ide-transfers-progress-icon.h           \
        tree/ide-tree-builder.h                           \
        tree/ide-tree-node.h                              \
        tree/ide-tree-types.h                             \
diff --git a/libide/resources/libide.gresource.xml b/libide/resources/libide.gresource.xml
index ecf67b7..7be1d75 100644
--- a/libide/resources/libide.gresource.xml
+++ b/libide/resources/libide.gresource.xml
@@ -76,6 +76,8 @@
     <file compressed="true" 
alias="ide-preferences-spin-button.ui">../preferences/ide-preferences-spin-button.ui</file>
     <file compressed="true" alias="ide-preferences-switch.ui">../preferences/ide-preferences-switch.ui</file>
     <file compressed="true" alias="ide-run-button.ui">../runner/ide-run-button.ui</file>
+    <file compressed="true" alias="ide-transfer-row.ui">../transfers/ide-transfer-row.ui</file>
+    <file compressed="true" alias="ide-transfers-button.ui">../transfers/ide-transfers-button.ui</file>
     <file compressed="true" alias="ide-shortcuts-window.ui">../keybindings/ide-shortcuts-window.ui</file>
     <file compressed="true" 
alias="ide-workbench-header-bar.ui">../workbench/ide-workbench-header-bar.ui</file>
     <file compressed="true" alias="ide-workbench.ui">../workbench/ide-workbench.ui</file>
diff --git a/libide/transfers/ide-transfer-row.c b/libide/transfers/ide-transfer-row.c
new file mode 100644
index 0000000..7d5b248
--- /dev/null
+++ b/libide/transfers/ide-transfer-row.c
@@ -0,0 +1,230 @@
+/* ide-transfer-row.c
+ *
+ * Copyright (C) 2016 Christian Hergert <chergert redhat com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 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/>.
+ */
+
+#define G_LOG_DOMAIN "ide-transfer-row"
+
+#include <egg-binding-group.h>
+
+#include "ide-transfer-row.h"
+
+struct _IdeTransferRow
+{
+  GtkListBoxRow parent_instance;
+
+  IdeTransfer *transfer;
+  EggBindingGroup *bindings;
+
+  GtkLabel *status;
+  GtkLabel *title;
+  GtkImage *image;
+  GtkProgressBar *progress;
+  GtkButton *cancel;
+};
+
+enum {
+  PROP_0,
+  PROP_TRANSFER,
+  N_PROPS
+};
+
+enum {
+  CANCELLED,
+  N_SIGNALS
+};
+
+G_DEFINE_TYPE (IdeTransferRow, ide_transfer_row, GTK_TYPE_LIST_BOX_ROW)
+
+static GParamSpec *properties [N_PROPS];
+static guint signals [N_SIGNALS];
+
+static void
+ide_transfer_row_cancel_clicked (IdeTransferRow *self,
+                                 GtkButton      *button)
+{
+  g_assert (IDE_IS_TRANSFER_ROW (self));
+  g_assert (GTK_IS_BUTTON (button));
+
+  g_signal_emit (self, signals [CANCELLED], 0);
+}
+
+static void
+ide_transfer_row_finalize (GObject *object)
+{
+  IdeTransferRow *self = (IdeTransferRow *)object;
+
+  g_clear_object (&self->transfer);
+  g_clear_object (&self->bindings);
+
+  G_OBJECT_CLASS (ide_transfer_row_parent_class)->finalize (object);
+}
+
+static void
+ide_transfer_row_get_property (GObject    *object,
+                               guint       prop_id,
+                               GValue     *value,
+                               GParamSpec *pspec)
+{
+  IdeTransferRow *self = IDE_TRANSFER_ROW (object);
+
+  switch (prop_id)
+    {
+    case PROP_TRANSFER:
+      g_value_set_object (value, ide_transfer_row_get_transfer (self));
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+    }
+}
+
+static void
+ide_transfer_row_set_property (GObject      *object,
+                               guint         prop_id,
+                               const GValue *value,
+                               GParamSpec   *pspec)
+{
+  IdeTransferRow *self = IDE_TRANSFER_ROW (object);
+
+  switch (prop_id)
+    {
+    case PROP_TRANSFER:
+      ide_transfer_row_set_transfer (self, g_value_get_object (value));
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+    }
+}
+
+static void
+ide_transfer_row_class_init (IdeTransferRowClass *klass)
+{
+  GObjectClass *object_class = G_OBJECT_CLASS (klass);
+  GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
+
+  object_class->finalize = ide_transfer_row_finalize;
+  object_class->get_property = ide_transfer_row_get_property;
+  object_class->set_property = ide_transfer_row_set_property;
+
+  properties [PROP_TRANSFER] =
+    g_param_spec_object ("transfer",
+                         "Transfer",
+                         "Transfer",
+                         IDE_TYPE_TRANSFER,
+                         (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+  g_object_class_install_properties (object_class, N_PROPS, properties);
+
+  /**
+   * IdeTransferRow::cancelled:
+   *
+   * This signal is emitted when the cancel button is clicked.
+   */
+  signals [CANCELLED] =
+    g_signal_new ("cancelled",
+                  G_TYPE_FROM_CLASS (klass),
+                  G_SIGNAL_RUN_LAST,
+                  0, NULL, NULL, NULL,
+                  G_TYPE_NONE, 0);
+
+  gtk_widget_class_set_template_from_resource (widget_class, "/org/gnome/builder/ui/ide-transfer-row.ui");
+  gtk_widget_class_bind_template_child (widget_class, IdeTransferRow, cancel);
+  gtk_widget_class_bind_template_child (widget_class, IdeTransferRow, title);
+  gtk_widget_class_bind_template_child (widget_class, IdeTransferRow, status);
+  gtk_widget_class_bind_template_child (widget_class, IdeTransferRow, progress);
+  gtk_widget_class_bind_template_child (widget_class, IdeTransferRow, image);
+}
+
+static void
+ide_transfer_row_init (IdeTransferRow *self)
+{
+  gtk_widget_init_template (GTK_WIDGET (self));
+
+  g_signal_connect_object (self->cancel,
+                           "clicked",
+                           G_CALLBACK (ide_transfer_row_cancel_clicked),
+                           self,
+                           G_CONNECT_SWAPPED);
+
+  self->bindings = egg_binding_group_new ();
+
+  egg_binding_group_bind (self->bindings,
+                          "title",
+                          self->title,
+                          "label",
+                          G_BINDING_SYNC_CREATE);
+
+  egg_binding_group_bind (self->bindings,
+                          "status",
+                          self->status,
+                          "label",
+                          G_BINDING_SYNC_CREATE);
+
+  egg_binding_group_bind (self->bindings,
+                          "progress",
+                          self->progress,
+                          "fraction",
+                          G_BINDING_SYNC_CREATE);
+
+  egg_binding_group_bind (self->bindings,
+                          "icon-name",
+                          self->image,
+                          "icon-name",
+                          G_BINDING_SYNC_CREATE);
+}
+
+/**
+ * ide_transfer_row_get_transfer:
+ *
+ * Returns: (nullable) (transfer none): An #IdeTransfer or %NULL.
+ */
+IdeTransfer *
+ide_transfer_row_get_transfer (IdeTransferRow *self)
+{
+  g_return_val_if_fail (IDE_IS_TRANSFER_ROW (self), NULL);
+
+  return self->transfer;
+}
+
+void
+ide_transfer_row_set_transfer (IdeTransferRow *self,
+                               IdeTransfer    *transfer)
+{
+  g_return_if_fail (IDE_IS_TRANSFER_ROW (self));
+  g_return_if_fail (!transfer || IDE_IS_TRANSFER (transfer));
+
+  if (g_set_object (&self->transfer, transfer))
+    {
+      egg_binding_group_set_source (self->bindings, transfer);
+      ide_transfer_row_pump (self);
+      g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_TRANSFER]);
+    }
+}
+
+void
+ide_transfer_row_pump (IdeTransferRow *self)
+{
+  gboolean completed;
+
+  g_return_if_fail (IDE_IS_TRANSFER_ROW (self));
+
+  completed = ide_transfer_has_completed (self->transfer);
+
+  gtk_widget_set_visible (GTK_WIDGET (self->cancel), !completed);
+  gtk_widget_set_visible (GTK_WIDGET (self->progress), !completed);
+}
diff --git a/libide/transfers/ide-transfer-row.h b/libide/transfers/ide-transfer-row.h
new file mode 100644
index 0000000..523875d
--- /dev/null
+++ b/libide/transfers/ide-transfer-row.h
@@ -0,0 +1,39 @@
+/* ide-transfer-row.h
+ *
+ * Copyright (C) 2016 Christian Hergert <chergert redhat com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 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 IDE_TRANSFER_ROW_H
+#define IDE_TRANSFER_ROW_H
+
+#include <gtk/gtk.h>
+
+#include "ide-transfer.h"
+
+G_BEGIN_DECLS
+
+#define IDE_TYPE_TRANSFER_ROW (ide_transfer_row_get_type())
+
+G_DECLARE_FINAL_TYPE (IdeTransferRow, ide_transfer_row, IDE, TRANSFER_ROW, GtkListBoxRow)
+
+void         ide_transfer_row_pump         (IdeTransferRow *self) G_GNUC_INTERNAL;
+IdeTransfer *ide_transfer_row_get_transfer (IdeTransferRow *self);
+void         ide_transfer_row_set_transfer (IdeTransferRow *self,
+                                            IdeTransfer    *transfer);
+
+G_END_DECLS
+
+#endif /* IDE_TRANSFER_ROW_H */
diff --git a/libide/transfers/ide-transfer-row.ui b/libide/transfers/ide-transfer-row.ui
new file mode 100644
index 0000000..bdb9c06
--- /dev/null
+++ b/libide/transfers/ide-transfer-row.ui
@@ -0,0 +1,80 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interface>
+  <template class="IdeTransferRow" parent="GtkListBoxRow">
+    <child>
+      <object class="GtkGrid">
+        <property name="row-spacing">6</property>
+        <property name="column-spacing">12</property>
+        <property name="hexpand">true</property>
+        <property name="visible">true</property>
+        <child>
+          <object class="GtkImage" id="image">
+            <property name="hexpand">false</property>
+            <property name="visible">true</property>
+          </object>
+          <packing>
+            <property name="left-attach">0</property>
+            <property name="top-attach">0</property>
+          </packing>
+        </child>
+        <child>
+          <object class="GtkLabel" id="title">
+            <property name="hexpand">true</property>
+            <property name="visible">true</property>
+            <property name="ellipsize">end</property>
+            <property name="xalign">0.0</property>
+          </object>
+          <packing>
+            <property name="left-attach">1</property>
+            <property name="top-attach">0</property>
+          </packing>
+        </child>
+        <child>
+          <object class="GtkProgressBar" id="progress">
+            <property name="visible">true</property>
+          </object>
+          <packing>
+            <property name="left-attach">0</property>
+            <property name="top-attach">1</property>
+            <property name="width">2</property>
+          </packing>
+        </child>
+        <child>
+          <object class="GtkLabel" id="status">
+            <property name="hexpand">true</property>
+            <property name="ellipsize">end</property>
+            <property name="xalign">0.0</property>
+            <property name="visible">true</property>
+          </object>
+          <packing>
+            <property name="left-attach">0</property>
+            <property name="top-attach">2</property>
+            <property name="width">2</property>
+          </packing>
+        </child>
+        <child>
+          <object class="GtkButton" id="cancel">
+            <property name="hexpand">false</property>
+            <property name="halign">center</property>
+            <property name="valign">center</property>
+            <property name="visible">true</property>
+            <style>
+              <class name="circular"/>
+            </style>
+            <child>
+              <object class="GtkImage">
+                <property name="icon-name">window-close-symbolic</property>
+                <property name="visible">true</property>
+              </object>
+            </child>
+          </object>
+          <packing>
+            <property name="left-attach">2</property>
+            <property name="top-attach">0</property>
+            <property name="height">3</property>
+          </packing>
+        </child>
+      </object>
+    </child>
+  </template>
+</interface>
diff --git a/libide/transfers/ide-transfers-button.c b/libide/transfers/ide-transfers-button.c
new file mode 100644
index 0000000..347f5a7
--- /dev/null
+++ b/libide/transfers/ide-transfers-button.c
@@ -0,0 +1,309 @@
+/* ide-transfers-button.c
+ *
+ * Copyright (C) 2016 Christian Hergert <chergert redhat com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 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/>.
+ */
+
+#define G_LOG_DOMAIN "ide-transfers-button"
+
+#include <egg-animation.h>
+
+#include "ide-debug.h"
+#include "ide-context.h"
+
+#include "theatrics/ide-box-theatric.h"
+#include "transfers/ide-transfer.h"
+#include "transfers/ide-transfer-manager.h"
+#include "transfers/ide-transfer-row.h"
+#include "transfers/ide-transfers-button.h"
+#include "transfers/ide-transfers-progress-icon.h"
+#include "util/ide-gtk.h"
+
+struct _IdeTransfersButton
+{
+  GtkMenuButton             parent_instance;
+
+  GtkPopover               *popover;
+  GtkListBox               *list_box;
+  IdeTransfersProgressIcon *icon;
+};
+
+G_DEFINE_TYPE (IdeTransfersButton, ide_transfers_button, GTK_TYPE_MENU_BUTTON)
+
+static void ide_transfers_button_begin_theatrics (IdeTransfersButton *self);
+
+GtkWidget *
+ide_transfers_button_new (void)
+{
+  return g_object_new (IDE_TYPE_TRANSFERS_BUTTON, NULL);
+}
+
+static gboolean
+begin_theatrics_from_main (gpointer user_data)
+{
+  g_autoptr(IdeTransfersButton) self = user_data;
+
+  g_assert (IDE_IS_TRANSFERS_BUTTON (self));
+
+  ide_transfers_button_begin_theatrics (self);
+
+  return G_SOURCE_REMOVE;
+}
+
+static void
+ide_transfers_button_begin_theatrics (IdeTransfersButton *self)
+{
+  g_autoptr(GIcon) icon = NULL;
+  IdeBoxTheatric *theatric;
+  GtkAllocation rect;
+
+  IDE_ENTRY;
+
+  g_assert (IDE_IS_TRANSFERS_BUTTON (self));
+
+  gtk_widget_get_allocation (GTK_WIDGET (self), &rect);
+
+  if (rect.x == -1 && rect.y == -1)
+    {
+      /* Delay this until our widget has been mapped/realized/displayed */
+      g_timeout_add (50, begin_theatrics_from_main, g_object_ref (self));
+      return;
+    }
+
+  rect.x = 0;
+  rect.y = 0;
+
+  icon = g_themed_icon_new ("folder-download-symbolic");
+
+  theatric = g_object_new (IDE_TYPE_BOX_THEATRIC,
+                           "alpha", 1.0,
+                           "height", rect.height,
+                           "icon", icon,
+                           "target", self,
+                           "width", rect.width,
+                           "x", rect.x,
+                           "y", rect.y,
+                           NULL);
+
+  egg_object_animate_full (theatric,
+                           EGG_ANIMATION_EASE_OUT_CUBIC,
+                           750,
+                           gtk_widget_get_frame_clock (GTK_WIDGET (self)),
+                           g_object_unref,
+                           theatric,
+                           "x", rect.x - 60,
+                           "width", rect.width + 120,
+                           "y", rect.y,
+                           "height", rect.height + 120,
+                           "alpha", 0.0,
+                           NULL);
+
+  IDE_EXIT;
+}
+
+static void
+ide_transfers_button_cancel_clicked (IdeTransfersButton *self,
+                                     IdeTransferRow     *row)
+{
+  IdeTransferManager *transfer_manager;
+  IdeTransfer *transfer;
+  IdeContext *context;
+
+  g_assert (IDE_IS_TRANSFERS_BUTTON (self));
+  g_assert (IDE_IS_TRANSFER_ROW (row));
+
+  if (NULL != (transfer = ide_transfer_row_get_transfer (row)) &&
+      NULL != (context = ide_widget_get_context (GTK_WIDGET (self))) &&
+      NULL != (transfer_manager = ide_context_get_transfer_manager (context)))
+    ide_transfer_manager_cancel (transfer_manager, transfer);
+}
+
+static GtkWidget *
+create_transfer_row (gpointer item,
+                     gpointer user_data)
+{
+  IdeTransfersButton *self = user_data;
+  IdeTransfer *transfer = item;
+  IdeTransferRow *row;
+
+  g_assert (IDE_IS_TRANSFER (transfer));
+  g_assert (IDE_IS_TRANSFERS_BUTTON (self));
+
+  row = g_object_new (IDE_TYPE_TRANSFER_ROW,
+                      "selectable", FALSE,
+                      "transfer", transfer,
+                      "visible", TRUE,
+                      NULL);
+
+  g_signal_connect_object (row,
+                           "cancelled",
+                           G_CALLBACK (ide_transfers_button_cancel_clicked),
+                           self,
+                           G_CONNECT_SWAPPED);
+
+  ide_transfers_button_begin_theatrics (self);
+
+  return GTK_WIDGET (row);
+}
+
+static void
+ide_transfers_button_update_visibility (IdeTransfersButton *self)
+{
+  IdeTransferManager *transfer_manager;
+  IdeContext *context;
+  gboolean visible;
+
+  IDE_ENTRY;
+
+  g_assert (IDE_IS_TRANSFERS_BUTTON (self));
+
+  if (NULL != (context = ide_widget_get_context (GTK_WIDGET (self))) &&
+      NULL != (transfer_manager = ide_context_get_transfer_manager (context)))
+    visible = !!g_list_model_get_n_items (G_LIST_MODEL (transfer_manager));
+
+  gtk_widget_set_visible (GTK_WIDGET (self), visible);
+
+  IDE_EXIT;
+}
+
+static void
+find_transfer_row (GtkWidget *widget,
+                   gpointer   user_data)
+{
+  struct {
+    IdeTransfer    *transfer;
+    IdeTransferRow *row;
+  } *lookup = user_data;
+
+  if (lookup->row != NULL)
+    return;
+
+  if (lookup->transfer == ide_transfer_row_get_transfer (IDE_TRANSFER_ROW (widget)))
+    lookup->row = IDE_TRANSFER_ROW (widget);
+}
+
+static void
+ide_transfers_button_transfer_completed (IdeTransfersButton *self,
+                                         IdeTransfer        *transfer,
+                                         IdeTransferManager *transfer_manager)
+{
+  struct {
+    IdeTransfer    *transfer;
+    IdeTransferRow *row;
+  } lookup = { transfer, NULL };
+
+  g_assert (IDE_IS_TRANSFERS_BUTTON (self));
+  g_assert (IDE_IS_TRANSFER (transfer));
+  g_assert (IDE_IS_TRANSFER_MANAGER (transfer_manager));
+
+  gtk_container_foreach (GTK_CONTAINER (self->list_box),
+                         find_transfer_row,
+                         &lookup);
+
+  if (lookup.row != NULL)
+    ide_transfer_row_pump (lookup.row);
+}
+
+static void
+ide_transfers_button_context_set (GtkWidget  *widget,
+                                  IdeContext *context)
+{
+  IdeTransfersButton *self = (IdeTransfersButton *)widget;
+  IdeTransferManager *transfer_manager;
+
+  g_assert (IDE_IS_TRANSFERS_BUTTON (self));
+  g_assert (!context || IDE_IS_CONTEXT (context));
+
+  if (context == NULL)
+    return;
+
+  transfer_manager = ide_context_get_transfer_manager (context);
+
+  g_signal_connect_object (transfer_manager,
+                           "transfer-completed",
+                           G_CALLBACK (ide_transfers_button_transfer_completed),
+                           self,
+                           G_CONNECT_SWAPPED);
+
+  g_object_bind_property (transfer_manager, "progress",
+                          self->icon, "progress",
+                          G_BINDING_SYNC_CREATE);
+
+  g_signal_connect_object (transfer_manager,
+                           "items-changed",
+                           G_CALLBACK (ide_transfers_button_update_visibility),
+                           self,
+                           G_CONNECT_SWAPPED);
+
+  gtk_list_box_bind_model (self->list_box,
+                           G_LIST_MODEL (transfer_manager),
+                           create_transfer_row,
+                           self,
+                           NULL);
+
+  ide_transfers_button_update_visibility (self);
+}
+
+static void
+ide_transfers_button_clear (GSimpleAction *action,
+                            GVariant      *param,
+                            gpointer       user_data)
+{
+  IdeTransfersButton *self = user_data;
+  IdeTransferManager *transfer_manager;
+  IdeContext *context;
+
+  g_assert (G_IS_SIMPLE_ACTION (action));
+
+  gtk_widget_hide (GTK_WIDGET (self->popover));
+
+  if (NULL != (context = ide_widget_get_context (GTK_WIDGET (self))) &&
+      NULL != (transfer_manager = ide_context_get_transfer_manager (context)))
+    ide_transfer_manager_clear (transfer_manager);
+}
+
+static void
+ide_transfers_button_class_init (IdeTransfersButtonClass *klass)
+{
+  GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
+
+  gtk_widget_class_set_template_from_resource (widget_class, 
"/org/gnome/builder/ui/ide-transfers-button.ui");
+  gtk_widget_class_bind_template_child (widget_class, IdeTransfersButton, list_box);
+  gtk_widget_class_bind_template_child (widget_class, IdeTransfersButton, popover);
+  gtk_widget_class_bind_template_child (widget_class, IdeTransfersButton, icon);
+}
+
+static void
+ide_transfers_button_init (IdeTransfersButton *self)
+{
+  g_autoptr(GSimpleActionGroup) actions = NULL;
+  static const GActionEntry entries[] = {
+    { "clear", ide_transfers_button_clear },
+  };
+
+  gtk_widget_init_template (GTK_WIDGET (self));
+
+  ide_widget_set_context_handler (GTK_WIDGET (self),
+                                  ide_transfers_button_context_set);
+
+  actions = g_simple_action_group_new ();
+  g_action_map_add_action_entries (G_ACTION_MAP (actions),
+                                   entries,
+                                   G_N_ELEMENTS (entries),
+                                   self);
+  gtk_widget_insert_action_group (GTK_WIDGET (self),
+                                  "transfers",
+                                  G_ACTION_GROUP (actions));
+}
diff --git a/libide/transfers/ide-transfers-button.h b/libide/transfers/ide-transfers-button.h
new file mode 100644
index 0000000..b9c2616
--- /dev/null
+++ b/libide/transfers/ide-transfers-button.h
@@ -0,0 +1,35 @@
+/* ide-transfers-button.h
+ *
+ * Copyright (C) 2016 Christian Hergert <chergert redhat com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 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 IDE_TRANSFERS_BUTTON_H
+#define IDE_TRANSFERS_BUTTON_H
+
+#include <gtk/gtk.h>
+
+G_BEGIN_DECLS
+
+#define IDE_TYPE_TRANSFERS_BUTTON (ide_transfers_button_get_type())
+
+G_DECLARE_FINAL_TYPE (IdeTransfersButton, ide_transfers_button, IDE, TRANSFERS_BUTTON, GtkMenuButton)
+
+GtkWidget *ide_transfers_button_new (void);
+
+G_END_DECLS
+
+#endif /* IDE_TRANSFERS_BUTTON_H */
+
diff --git a/libide/transfers/ide-transfers-button.ui b/libide/transfers/ide-transfers-button.ui
new file mode 100644
index 0000000..049686f
--- /dev/null
+++ b/libide/transfers/ide-transfers-button.ui
@@ -0,0 +1,57 @@
+<?xml version="1.0"?>
+<interface>
+  <object class="GtkPopover" id="popover">
+    <property name="border-width">12</property>
+    <style>
+      <class name="transfers"/>
+    </style>
+    <child>
+      <object class="GtkBox">
+        <property name="orientation">vertical</property>
+        <property name="spacing">24</property>
+        <property name="visible">true</property>
+        <child>
+          <object class="GtkScrolledWindow">
+            <property name="min-content-width">275</property>
+            <property name="min-content-height">300</property>
+            <property name="max-content-height">300</property>
+            <property name="visible">true</property>
+            <child>
+              <object class="GtkListBox" id="list_box">
+                <property name="visible">true</property>
+              </object>
+            </child>
+          </object>
+        </child>
+        <child>
+          <object class="GtkBox">
+            <property name="orientation">horizontal</property>
+            <property name="visible">true</property>
+            <child>
+              <object class="GtkButton">
+                <property name="action-name">transfers.clear</property>
+                <property name="label" translatable="yes">Clear _All</property>
+                <property name="use-underline">true</property>
+                <property name="visible">true</property>
+              </object>
+              <packing>
+                <property name="pack-type">end</property>
+              </packing>
+            </child>
+          </object>
+        </child>
+      </object>
+    </child>
+  </object>
+  <template class="IdeTransfersButton" parent="GtkMenuButton">
+    <property name="popover">popover</property>
+    <style>
+      <class name="image-button"/>
+    </style>
+    <child>
+      <object class="IdeTransfersProgressIcon" id="icon">
+        <property name="visible">true</property>
+      </object>
+    </child>
+  </template>
+</interface>
diff --git a/libide/transfers/ide-transfers-progress-icon.c b/libide/transfers/ide-transfers-progress-icon.c
new file mode 100644
index 0000000..8216b6b
--- /dev/null
+++ b/libide/transfers/ide-transfers-progress-icon.c
@@ -0,0 +1,184 @@
+/* -*- Mode: C; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/*
+ *  Copyright © 2015 Igalia S.L.
+ *  Copyright © 2016 Christian Hergert <chergert redhat com>
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 2, or (at your option)
+ *  any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#define G_LOG_DOMAIN "ide-transfers-progress-icon"
+
+#include "ide-transfers-progress-icon.h"
+
+struct _IdeTransfersProgressIcon
+{
+  GtkDrawingArea parent_instance;
+  gdouble        progress;
+};
+
+G_DEFINE_TYPE (IdeTransfersProgressIcon, ide_transfers_progress_icon, GTK_TYPE_DRAWING_AREA)
+
+enum {
+  PROP_0,
+  PROP_PROGRESS,
+  N_PROPS
+};
+
+static GParamSpec *properties [N_PROPS];
+
+static gboolean
+ide_transfers_progress_icon_draw (GtkWidget *widget,
+                                  cairo_t   *cr)
+{
+  IdeTransfersProgressIcon *self = (IdeTransfersProgressIcon *)widget;
+  GtkStyleContext *style_context;
+  GdkRGBA color;
+  gdouble progress;
+  gint width;
+  gint height;
+
+  g_assert (IDE_IS_TRANSFERS_PROGRESS_ICON (self));
+  g_assert (cr != NULL);
+
+  width = gtk_widget_get_allocated_width (widget);
+  height = gtk_widget_get_allocated_height (widget);
+
+  progress = ide_transfers_progress_icon_get_progress (self);
+
+  style_context = gtk_widget_get_style_context (widget);
+  gtk_style_context_get_color (style_context, gtk_widget_get_state_flags (widget), &color);
+  color.alpha *= progress == 1 ? 1 : 0.2;
+
+  gdk_cairo_set_source_rgba (cr, &color);
+  cairo_move_to (cr, width / 4., 0);
+  cairo_line_to (cr, width - (width / 4.), 0);
+  cairo_line_to (cr, width - (width / 4.), height / 2.);
+  cairo_line_to (cr, width, height / 2.);
+  cairo_line_to (cr, width / 2., height);
+  cairo_line_to (cr, 0, height / 2.);
+  cairo_line_to (cr, width / 4., height / 2.);
+  cairo_line_to (cr, width / 4., 0);
+  cairo_fill_preserve (cr);
+
+  if (progress > 0 && progress < 1)
+    {
+      cairo_clip (cr);
+      color.alpha = 1;
+      gdk_cairo_set_source_rgba (cr, &color);
+      cairo_rectangle (cr, 0, 0, width, height * progress);
+      cairo_fill (cr);
+    }
+
+  return GDK_EVENT_PROPAGATE;
+}
+
+static void
+ide_transfers_progress_icon_get_property (GObject    *object,
+                                          guint       prop_id,
+                                          GValue     *value,
+                                          GParamSpec *pspec)
+{
+  IdeTransfersProgressIcon *self = IDE_TRANSFERS_PROGRESS_ICON (object);
+
+  switch (prop_id)
+    {
+    case PROP_PROGRESS:
+      g_value_set_double (value, ide_transfers_progress_icon_get_progress (self));
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+    }
+}
+
+static void
+ide_transfers_progress_icon_set_property (GObject      *object,
+                                          guint         prop_id,
+                                          const GValue *value,
+                                          GParamSpec   *pspec)
+{
+  IdeTransfersProgressIcon *self = IDE_TRANSFERS_PROGRESS_ICON (object);
+
+  switch (prop_id)
+    {
+    case PROP_PROGRESS:
+      ide_transfers_progress_icon_set_progress (self, g_value_get_double (value));
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+    }
+}
+
+static void
+ide_transfers_progress_icon_class_init (IdeTransfersProgressIconClass *klass)
+{
+  GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
+  GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+  object_class->get_property = ide_transfers_progress_icon_get_property;
+  object_class->set_property = ide_transfers_progress_icon_set_property;
+
+  widget_class->draw = ide_transfers_progress_icon_draw;
+
+  properties [PROP_PROGRESS] =
+    g_param_spec_double ("progress",
+                         "Progress",
+                         "Progress",
+                         0.0,
+                         1.0,
+                         0.0,
+                         (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+  g_object_class_install_properties (object_class, N_PROPS, properties);
+}
+
+static void
+ide_transfers_progress_icon_init (IdeTransfersProgressIcon *icon)
+{
+  g_object_set (icon, "width-request", 16, "height-request", 16, NULL);
+  gtk_widget_set_valign (GTK_WIDGET (icon), GTK_ALIGN_CENTER);
+  gtk_widget_set_halign (GTK_WIDGET (icon), GTK_ALIGN_CENTER);
+}
+
+GtkWidget *
+ide_transfers_progress_icon_new (void)
+{
+  return g_object_new (IDE_TYPE_TRANSFERS_PROGRESS_ICON, NULL);
+}
+
+gdouble
+ide_transfers_progress_icon_get_progress (IdeTransfersProgressIcon *self)
+{
+  g_return_val_if_fail (IDE_IS_TRANSFERS_PROGRESS_ICON (self), 0.0);
+
+  return self->progress;
+}
+
+void
+ide_transfers_progress_icon_set_progress (IdeTransfersProgressIcon *self,
+                                          gdouble                   progress)
+{
+  g_return_if_fail (IDE_IS_TRANSFERS_PROGRESS_ICON (self));
+
+  progress = CLAMP (progress, 0.0, 1.0);
+
+  if (self->progress != progress)
+    {
+      self->progress = progress;
+      g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_PROGRESS]);
+      gtk_widget_queue_draw (GTK_WIDGET (self));
+    }
+}
diff --git a/libide/transfers/ide-transfers-progress-icon.h b/libide/transfers/ide-transfers-progress-icon.h
new file mode 100644
index 0000000..d43b18d
--- /dev/null
+++ b/libide/transfers/ide-transfers-progress-icon.h
@@ -0,0 +1,37 @@
+/* ide-transfers-progress-icon.h
+ *
+ * Copyright (C) 2016 Christian Hergert <chergert redhat com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 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 IDE_TRANSFERS_PROGRESS_ICON_H
+#define IDE_TRANSFERS_PROGRESS_ICON_H
+
+#include <gtk/gtk.h>
+
+G_BEGIN_DECLS
+
+#define IDE_TYPE_TRANSFERS_PROGRESS_ICON (ide_transfers_progress_icon_get_type())
+
+G_DECLARE_FINAL_TYPE (IdeTransfersProgressIcon, ide_transfers_progress_icon, IDE, TRANSFERS_PROGRESS_ICON, 
GtkDrawingArea)
+
+GtkWidget *ide_transfers_progress_icon_new          (void);
+gdouble    ide_transfers_progress_icon_get_progress (IdeTransfersProgressIcon *self);
+void       ide_transfers_progress_icon_set_progress (IdeTransfersProgressIcon *self,
+                                                     gdouble                   progress);
+
+G_END_DECLS
+
+#endif /* IDE_TRANSFERS_PROGRESS_ICON_H */
diff --git a/libide/workbench/ide-workbench-header-bar.ui b/libide/workbench/ide-workbench-header-bar.ui
index 4d4b0e4..20cc662 100644
--- a/libide/workbench/ide-workbench-header-bar.ui
+++ b/libide/workbench/ide-workbench-header-bar.ui
@@ -34,23 +34,37 @@
           </packing>
         </child>
         <child>
-          <object class="GtkMenuButton" id="menu_button">
-            <property name="tooltip-text" translatable="yes">Show workbench menu</property>
-            <property name="focus-on-click">false</property>
+          <object class="GtkBox">
+            <property name="orientation">horizontal</property>
+            <property name="spacing">6</property>
             <property name="visible">true</property>
-            <style>
-              <class name="image-button"/>
-            </style>
             <child>
-              <object class="GtkImage">
-                <property name="icon-name">open-menu-symbolic</property>
+              <object class="IdeTransfersButton" id="transfers_button">
+                <property name="tooltip-text" translatable="yes">Transfers</property>
+                <property name="focus-on-click">false</property>
                 <property name="visible">true</property>
               </object>
             </child>
+            <child>
+              <object class="GtkMenuButton" id="menu_button">
+                <property name="tooltip-text" translatable="yes">Show workbench menu</property>
+                <property name="focus-on-click">false</property>
+                <property name="visible">true</property>
+                <style>
+                  <class name="image-button"/>
+                </style>
+                <child>
+                  <object class="GtkImage">
+                    <property name="icon-name">open-menu-symbolic</property>
+                    <property name="visible">true</property>
+                  </object>
+                </child>
+              </object>
+            </child>
           </object>
           <packing>
-            <property name="pack-type">end</property>
             <property name="priority">-100000</property>
+            <property name="pack-type">end</property>
           </packing>
         </child>
         <child>



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