[gnome-software] Add buttons to control the application review



commit 28be817181fdc762fc394ce84921fca1c53bb9fc
Author: Richard Hughes <richard hughsie com>
Date:   Tue Feb 9 13:34:50 2016 +0000

    Add buttons to control the application review
    
    This allows the community to self-moderate application reviews.

 src/gs-plugin-loader.c        |   12 ++--
 src/gs-plugin-loader.h        |    7 +--
 src/gs-review-row.c           |  133 ++++++++++++++++++++++++++----
 src/gs-review-row.h           |   16 +++-
 src/gs-review-row.ui          |  182 +++++++++++++++++++++++++++++++++--------
 src/gs-review.c               |   38 +++++++++
 src/gs-review.h               |   19 ++++
 src/gs-shell-details.c        |   83 ++++++++++++++-----
 src/plugins/gs-plugin-dummy.c |   38 +++++++--
 9 files changed, 434 insertions(+), 94 deletions(-)
---
diff --git a/src/gs-plugin-loader.c b/src/gs-plugin-loader.c
index 4a5f81b..e5122f6 100644
--- a/src/gs-plugin-loader.c
+++ b/src/gs-plugin-loader.c
@@ -2746,7 +2746,7 @@ void
 gs_plugin_loader_review_action_async (GsPluginLoader *plugin_loader,
                                      GsApp *app,
                                      GsReview *review,
-                                     GsPluginLoaderAction action,
+                                     GsReviewAction action,
                                      GCancellable *cancellable,
                                      GAsyncReadyCallback callback,
                                      gpointer user_data)
@@ -2764,19 +2764,19 @@ gs_plugin_loader_review_action_async (GsPluginLoader *plugin_loader,
        state->review = g_object_ref (review);
 
        switch (action) {
-       case GS_PLUGIN_LOADER_ACTION_REVIEW_SUBMIT:
+       case GS_REVIEW_ACTION_SUBMIT:
                state->function_name = "gs_plugin_review_submit";
                break;
-       case GS_PLUGIN_LOADER_ACTION_REVIEW_UPVOTE:
+       case GS_REVIEW_ACTION_UPVOTE:
                state->function_name = "gs_plugin_review_upvote";
                break;
-       case GS_PLUGIN_LOADER_ACTION_REVIEW_DOWNVOTE:
+       case GS_REVIEW_ACTION_DOWNVOTE:
                state->function_name = "gs_plugin_review_downvote";
                break;
-       case GS_PLUGIN_LOADER_ACTION_REVIEW_REPORT:
+       case GS_REVIEW_ACTION_REPORT:
                state->function_name = "gs_plugin_review_report";
                break;
-       case GS_PLUGIN_LOADER_ACTION_REVIEW_REMOVE:
+       case GS_REVIEW_ACTION_REMOVE:
                state->function_name = "gs_plugin_review_remove";
                break;
        default:
diff --git a/src/gs-plugin-loader.h b/src/gs-plugin-loader.h
index 3ff6399..3925dc9 100644
--- a/src/gs-plugin-loader.h
+++ b/src/gs-plugin-loader.h
@@ -61,11 +61,6 @@ typedef enum {
        GS_PLUGIN_LOADER_ACTION_UPGRADE_TRIGGER,
        GS_PLUGIN_LOADER_ACTION_LAUNCH,
        GS_PLUGIN_LOADER_ACTION_OFFLINE_UPDATE_CANCEL,
-       GS_PLUGIN_LOADER_ACTION_REVIEW_SUBMIT,
-       GS_PLUGIN_LOADER_ACTION_REVIEW_UPVOTE,
-       GS_PLUGIN_LOADER_ACTION_REVIEW_DOWNVOTE,
-       GS_PLUGIN_LOADER_ACTION_REVIEW_REPORT,
-       GS_PLUGIN_LOADER_ACTION_REVIEW_REMOVE,
        GS_PLUGIN_LOADER_ACTION_LAST
 } GsPluginLoaderAction;
 
@@ -217,7 +212,7 @@ gboolean     gs_plugin_loader_app_action_finish     (GsPluginLoader *plugin_loader,
 void            gs_plugin_loader_review_action_async   (GsPluginLoader *plugin_loader,
                                                         GsApp          *app,
                                                         GsReview       *review,
-                                                        GsPluginLoaderAction a,
+                                                        GsReviewAction  action,
                                                         GCancellable   *cancellable,
                                                         GAsyncReadyCallback callback,
                                                         gpointer        user_data);
diff --git a/src/gs-review-row.c b/src/gs-review-row.c
index 2dea624..f6941f5 100644
--- a/src/gs-review-row.c
+++ b/src/gs-review-row.c
@@ -27,7 +27,7 @@
 #include "gs-review-row.h"
 #include "gs-star-widget.h"
 
-struct _GsReviewRow
+typedef struct
 {
        GtkListBoxRow    parent_instance;
 
@@ -37,28 +37,44 @@ struct _GsReviewRow
        GtkWidget       *author_label;
        GtkWidget       *date_label;
        GtkWidget       *text_label;
+       GtkWidget       *button_yes;
+       GtkWidget       *button_no;
+       GtkWidget       *button_report;
+       GtkWidget       *button_remove;
+       GtkWidget       *box_vote_buttons;
+} GsReviewRowPrivate;
+
+enum {
+       SIGNAL_BUTTON_CLICKED,
+       SIGNAL_LAST
 };
 
-G_DEFINE_TYPE (GsReviewRow, gs_review_row, GTK_TYPE_LIST_BOX_ROW)
+static guint signals [SIGNAL_LAST] = { 0 };
+
+G_DEFINE_TYPE_WITH_PRIVATE (GsReviewRow, gs_review_row, GTK_TYPE_LIST_BOX_ROW)
 
 static void
 gs_review_row_refresh (GsReviewRow *row)
 {
+       GsReviewRowPrivate *priv = gs_review_row_get_instance_private (row);
        const gchar *reviewer;
        GDateTime *date;
        g_autofree gchar *text;
 
-       gs_star_widget_set_rating (GS_STAR_WIDGET (row->stars), gs_review_get_rating (row->review));
-       reviewer = gs_review_get_reviewer (row->review);
-       gtk_label_set_text (GTK_LABEL (row->author_label), reviewer ? reviewer : "");
-       date = gs_review_get_date (row->review);
+       gs_star_widget_set_rating (GS_STAR_WIDGET (priv->stars),
+                                  gs_review_get_rating (priv->review));
+       reviewer = gs_review_get_reviewer (priv->review);
+       gtk_label_set_text (GTK_LABEL (priv->author_label), reviewer ? reviewer : "");
+       date = gs_review_get_date (priv->review);
        if (date != NULL)
                text = g_date_time_format (date, "%e %B %Y");
        else
                text = g_strdup ("");
-       gtk_label_set_text (GTK_LABEL (row->date_label), text);
-       gtk_label_set_text (GTK_LABEL (row->summary_label), gs_review_get_summary (row->review));
-       gtk_label_set_text (GTK_LABEL (row->text_label), gs_review_get_text (row->review));
+       gtk_label_set_text (GTK_LABEL (priv->date_label), text);
+       gtk_label_set_text (GTK_LABEL (priv->summary_label),
+                           gs_review_get_summary (priv->review));
+       gtk_label_set_text (GTK_LABEL (priv->text_label),
+                           gs_review_get_text (priv->review));
 }
 
 static gboolean
@@ -91,8 +107,9 @@ static void
 gs_review_row_dispose (GObject *object)
 {
        GsReviewRow *row = GS_REVIEW_ROW (object);
+       GsReviewRowPrivate *priv = gs_review_row_get_instance_private (row);
 
-       g_clear_object (&row->review);
+       g_clear_object (&priv->review);
 
        G_OBJECT_CLASS (gs_review_row_parent_class)->dispose (object);
 }
@@ -105,13 +122,81 @@ gs_review_row_class_init (GsReviewRowClass *klass)
 
        object_class->dispose = gs_review_row_dispose;
 
+       signals [SIGNAL_BUTTON_CLICKED] =
+               g_signal_new ("button-clicked",
+                             G_TYPE_FROM_CLASS (object_class), G_SIGNAL_RUN_LAST,
+                             G_STRUCT_OFFSET (GsReviewRowClass, button_clicked),
+                             NULL, NULL, g_cclosure_marshal_VOID__UINT,
+                             G_TYPE_NONE, 1, G_TYPE_UINT);
+
        gtk_widget_class_set_template_from_resource (widget_class, "/org/gnome/Software/gs-review-row.ui");
 
-       gtk_widget_class_bind_template_child (widget_class, GsReviewRow, stars);
-       gtk_widget_class_bind_template_child (widget_class, GsReviewRow, summary_label);
-       gtk_widget_class_bind_template_child (widget_class, GsReviewRow, author_label);
-       gtk_widget_class_bind_template_child (widget_class, GsReviewRow, date_label);
-       gtk_widget_class_bind_template_child (widget_class, GsReviewRow, text_label);
+       gtk_widget_class_bind_template_child_private (widget_class, GsReviewRow, stars);
+       gtk_widget_class_bind_template_child_private (widget_class, GsReviewRow, summary_label);
+       gtk_widget_class_bind_template_child_private (widget_class, GsReviewRow, author_label);
+       gtk_widget_class_bind_template_child_private (widget_class, GsReviewRow, date_label);
+       gtk_widget_class_bind_template_child_private (widget_class, GsReviewRow, text_label);
+       gtk_widget_class_bind_template_child_private (widget_class, GsReviewRow, button_yes);
+       gtk_widget_class_bind_template_child_private (widget_class, GsReviewRow, button_no);
+       gtk_widget_class_bind_template_child_private (widget_class, GsReviewRow, button_report);
+       gtk_widget_class_bind_template_child_private (widget_class, GsReviewRow, button_remove);
+       gtk_widget_class_bind_template_child_private (widget_class, GsReviewRow, box_vote_buttons);
+}
+
+static void
+gs_review_row_button_clicked_upvote_cb (GtkButton *button, GsReviewRow *row)
+{
+       g_signal_emit (row, signals[SIGNAL_BUTTON_CLICKED], 0,
+                      GS_REVIEW_ACTION_UPVOTE);
+}
+
+static void
+gs_review_row_button_clicked_downvote_cb (GtkButton *button, GsReviewRow *row)
+{
+       g_signal_emit (row, signals[SIGNAL_BUTTON_CLICKED], 0,
+                      GS_REVIEW_ACTION_DOWNVOTE);
+}
+
+static void
+gs_review_row_button_clicked_report_cb (GtkButton *button, GsReviewRow *row)
+{
+       g_signal_emit (row, signals[SIGNAL_BUTTON_CLICKED], 0,
+                      GS_REVIEW_ACTION_REPORT);
+}
+
+static void
+gs_review_row_button_clicked_remove_cb (GtkButton *button, GsReviewRow *row)
+{
+       g_signal_emit (row, signals[SIGNAL_BUTTON_CLICKED], 0,
+                      GS_REVIEW_ACTION_REMOVE);
+}
+
+GsReview *
+gs_review_row_get_review (GsReviewRow *review_row)
+{
+       GsReviewRowPrivate *priv = gs_review_row_get_instance_private (review_row);
+       return priv->review;
+}
+
+void
+gs_review_row_set_actions (GsReviewRow *review_row, guint64 actions)
+{
+       GsReviewRowPrivate *priv = gs_review_row_get_instance_private (review_row);
+
+       if ((actions & (1 << GS_REVIEW_ACTION_UPVOTE |
+                       1 << GS_REVIEW_ACTION_DOWNVOTE)) == 0) {
+               gtk_widget_set_visible (priv->box_vote_buttons, FALSE);
+       } else {
+               gtk_widget_set_visible (priv->box_vote_buttons, TRUE);
+               gtk_widget_set_visible (priv->button_yes,
+                                       actions & 1 << GS_REVIEW_ACTION_UPVOTE);
+               gtk_widget_set_visible (priv->button_no,
+                                       actions & 1 << GS_REVIEW_ACTION_DOWNVOTE);
+       }
+       gtk_widget_set_visible (priv->button_remove,
+                               actions & 1 << GS_REVIEW_ACTION_REMOVE);
+       gtk_widget_set_visible (priv->button_report,
+                               actions & 1 << GS_REVIEW_ACTION_REPORT);
 }
 
 /**
@@ -126,14 +211,28 @@ GtkWidget *
 gs_review_row_new (GsReview *review)
 {
        GsReviewRow *row;
+       GsReviewRowPrivate *priv;
 
        g_return_val_if_fail (GS_IS_REVIEW (review), NULL);
 
        row = g_object_new (GS_TYPE_REVIEW_ROW, NULL);
-       row->review = g_object_ref (review);
-       g_signal_connect_object (row->review, "notify::state",
+       priv = gs_review_row_get_instance_private (row);
+       priv->review = g_object_ref (review);
+       g_signal_connect_object (priv->review, "notify::state",
                                 G_CALLBACK (gs_review_row_notify_props_changed_cb),
                                 row, 0);
+       g_signal_connect_object (priv->button_yes, "clicked",
+                                G_CALLBACK (gs_review_row_button_clicked_upvote_cb),
+                                row, 0);
+       g_signal_connect_object (priv->button_no, "clicked",
+                                G_CALLBACK (gs_review_row_button_clicked_downvote_cb),
+                                row, 0);
+       g_signal_connect_object (priv->button_report, "clicked",
+                                G_CALLBACK (gs_review_row_button_clicked_report_cb),
+                                row, 0);
+       g_signal_connect_object (priv->button_remove, "clicked",
+                                G_CALLBACK (gs_review_row_button_clicked_remove_cb),
+                                row, 0);
        gs_review_row_refresh (row);
 
        return GTK_WIDGET (row);
diff --git a/src/gs-review-row.h b/src/gs-review-row.h
index 3ed6aa7..74e2f01 100644
--- a/src/gs-review-row.h
+++ b/src/gs-review-row.h
@@ -30,9 +30,19 @@ G_BEGIN_DECLS
 
 #define GS_TYPE_REVIEW_ROW (gs_review_row_get_type ())
 
-G_DECLARE_FINAL_TYPE (GsReviewRow, gs_review_row, GS, REVIEW_ROW, GtkListBoxRow)
-
-GtkWidget      *gs_review_row_new      (GsReview *review);
+G_DECLARE_DERIVABLE_TYPE (GsReviewRow, gs_review_row, GS, REVIEW_ROW, GtkListBoxRow)
+
+struct _GsReviewRowClass
+{
+       GtkListBoxRowClass       parent_class;
+       void                    (*button_clicked)       (GsReviewRow    *review_row,
+                                                        GsReviewAction  action);
+};
+
+GtkWidget      *gs_review_row_new              (GsReview       *review);
+GsReview       *gs_review_row_get_review       (GsReviewRow    *review_row);
+void            gs_review_row_set_actions      (GsReviewRow    *review_row,
+                                                guint64         actions);
 
 G_END_DECLS
 
diff --git a/src/gs-review-row.ui b/src/gs-review-row.ui
index 4e281b4..e8e4a31 100644
--- a/src/gs-review-row.ui
+++ b/src/gs-review-row.ui
@@ -3,89 +3,203 @@
   <!-- interface-requires gtk+ 3.10 -->
   <template class="GsReviewRow" parent="GtkListBoxRow">
     <property name="visible">True</property>
+
     <child>
       <object class="GtkGrid" id="grid">
         <property name="visible">True</property>
-        <property name="row-spacing">5</property>
-        <property name="column-spacing">10</property>
-        <property name="margin-top">20</property>
-        <property name="margin-bottom">20</property>
-        <child>
-          <object class="GsStarWidget" id="stars">
-            <property name="visible">True</property>
-            <property name="halign">start</property>
-            <property name="sensitive">False</property>
-          </object>
-          <packing>
-            <property name="left-attach">0</property>
-            <property name="top-attach">0</property>
-            <property name="width">1</property>
-            <property name="height">1</property>
-          </packing>
-        </child>
+        <property name="can_focus">False</property>
+        <property name="margin_top">20</property>
+        <property name="margin_bottom">20</property>
+        <property name="row_spacing">5</property>
+        <property name="column_spacing">10</property>
+        <property name="hexpand">True</property>
+        <property name="vexpand">True</property>
         <child>
           <object class="GtkLabel" id="summary_label">
             <property name="visible">True</property>
-            <property name="expand">True</property>
+            <property name="can_focus">False</property>
             <property name="halign">start</property>
+            <property name="label">Steep learning curve, but worth it</property>
             <property name="ellipsize">end</property>
+            <property name="hexpand">True</property>
             <style>
               <class name="review-summary"/>
             </style>
           </object>
           <packing>
-            <property name="left-attach">1</property>
-            <property name="top-attach">0</property>
-            <property name="width">1</property>
-            <property name="height">1</property>
+            <property name="left_attach">1</property>
+            <property name="top_attach">0</property>
           </packing>
         </child>
         <child>
           <object class="GtkLabel" id="date_label">
             <property name="visible">True</property>
+            <property name="can_focus">False</property>
             <property name="halign">end</property>
+            <property name="label">3 January 2016</property>
+            <property name="hexpand">True</property>
             <style>
               <class name="dim-label"/>
             </style>
           </object>
           <packing>
-            <property name="left-attach">2</property>
-            <property name="top-attach">0</property>
-            <property name="width">1</property>
-            <property name="height">1</property>
+            <property name="left_attach">2</property>
+            <property name="top_attach">0</property>
           </packing>
         </child>
         <child>
           <object class="GtkLabel" id="author_label">
             <property name="visible">True</property>
+            <property name="can_focus">False</property>
             <property name="halign">start</property>
+            <property name="label">Angela Avery</property>
             <style>
               <class name="dim-label"/>
             </style>
           </object>
           <packing>
-            <property name="left-attach">0</property>
-            <property name="top-attach">1</property>
+            <property name="left_attach">0</property>
+            <property name="top_attach">1</property>
             <property name="width">3</property>
-            <property name="height">1</property>
           </packing>
         </child>
         <child>
           <object class="GtkLabel" id="text_label">
             <property name="visible">True</property>
+            <property name="can_focus">False</property>
             <property name="halign">start</property>
+            <property name="margin_top">10</property>
+            <property name="margin_bottom">8</property>
+            <property name="label">Best overall 3D application I've ever used overall 3D application I've 
ever used. Best overall 3D application I've ever used overall 3D application I've ever used. Best overall 3D 
application I've ever used overall 3D application I've ever used. Best overall 3D application I've ever used 
overall 3D application I've ever used.</property>
             <property name="wrap">True</property>
-            <property name="xalign">0</property>
-            <property name="margin-top">10</property>
+            <property name="max_width_chars">80</property>
           </object>
           <packing>
-            <property name="left-attach">0</property>
-            <property name="top-attach">2</property>
+            <property name="left_attach">0</property>
+            <property name="top_attach">2</property>
             <property name="width">3</property>
-            <property name="height">1</property>
+          </packing>
+        </child>
+        <child>
+          <object class="GsStarWidget" id="stars">
+            <property name="visible">True</property>
+            <property name="halign">start</property>
+            <property name="sensitive">False</property>
+          </object>
+          <packing>
+            <property name="left_attach">0</property>
+            <property name="top_attach">0</property>
+          </packing>
+        </child>
+        <child>
+          <object class="GtkBox" id="box_vote_buttons">
+            <property name="visible">True</property>
+            <property name="can_focus">False</property>
+            <property name="spacing">9</property>
+            <child>
+              <object class="GtkLabel">
+                <property name="visible">True</property>
+                <property name="can_focus">False</property>
+                <property name="label" translatable="yes">Was this review useful to you?</property>
+                <style>
+                  <class name="dim-label"/>
+                </style>
+              </object>
+              <packing>
+                <property name="expand">False</property>
+                <property name="fill">False</property>
+                <property name="position">0</property>
+              </packing>
+            </child>
+            <child>
+              <object class="GtkButton" id="button_yes">
+                <property name="label" translatable="yes">Yes</property>
+                <property name="visible">True</property>
+                <property name="can_focus">True</property>
+                <property name="receives_default">True</property>
+              </object>
+              <packing>
+                <property name="expand">False</property>
+                <property name="fill">False</property>
+                <property name="position">1</property>
+              </packing>
+            </child>
+            <child>
+              <object class="GtkButton" id="button_no">
+                <property name="label" translatable="yes">No</property>
+                <property name="visible">True</property>
+                <property name="can_focus">True</property>
+                <property name="receives_default">True</property>
+              </object>
+              <packing>
+                <property name="expand">False</property>
+                <property name="fill">False</property>
+                <property name="position">2</property>
+              </packing>
+            </child>
+          </object>
+          <packing>
+            <property name="left_attach">0</property>
+            <property name="top_attach">3</property>
+            <property name="width">2</property>
+          </packing>
+        </child>
+        <child>
+          <object class="GtkBox" id="box_action_buttons">
+            <property name="visible">True</property>
+            <property name="can_focus">False</property>
+            <property name="spacing">9</property>
+            <property name="halign">end</property>
+            <child>
+              <object class="GtkButton" id="button_report">
+                <property name="label" translatable="yes">Report Abuse</property>
+                <property name="visible">True</property>
+                <property name="can_focus">True</property>
+                <property name="receives_default">True</property>
+                <property name="width_request">150</property>
+              </object>
+              <packing>
+                <property name="expand">False</property>
+                <property name="fill">True</property>
+                <property name="position">0</property>
+              </packing>
+            </child>
+            <child>
+              <object class="GtkButton" id="button_remove">
+                <property name="label" translatable="yes">Remove</property>
+                <property name="visible">True</property>
+                <property name="can_focus">True</property>
+                <property name="receives_default">True</property>
+                <property name="width_request">150</property>
+              </object>
+              <packing>
+                <property name="expand">False</property>
+                <property name="fill">True</property>
+                <property name="position">1</property>
+              </packing>
+            </child>
+          </object>
+          <packing>
+            <property name="left_attach">2</property>
+            <property name="top_attach">3</property>
           </packing>
         </child>
       </object>
     </child>
+
   </template>
+
+  <object class="GtkSizeGroup" id="action_sizegroup">
+    <widgets>
+      <widget name="button_report"/>
+      <widget name="button_remove"/>
+    </widgets>
+  </object>
+  <object class="GtkSizeGroup" id="useful_sizegroup">
+    <widgets>
+      <widget name="button_yes"/>
+      <widget name="button_no"/>
+    </widgets>
+  </object>
+
 </interface>
diff --git a/src/gs-review.c b/src/gs-review.c
index 46d729e..2939b79 100644
--- a/src/gs-review.c
+++ b/src/gs-review.c
@@ -27,6 +27,7 @@ struct _GsReview
 {
        GObject                  parent_instance;
 
+       GsReviewState            state;
        gchar                   *summary;
        gchar                   *text;
        gint                     karma;
@@ -45,6 +46,7 @@ enum {
        PROP_VERSION,
        PROP_REVIEWER,
        PROP_DATE,
+       PROP_STATE,
        PROP_LAST
 };
 
@@ -133,6 +135,26 @@ gs_review_set_rating (GsReview *review, gint rating)
 }
 
 /**
+ * gs_review_get_state:
+ */
+GsReviewState
+gs_review_get_state (GsReview *review)
+{
+       g_return_val_if_fail (GS_IS_REVIEW (review), 0);
+       return review->state;
+}
+
+/**
+ * gs_review_set_state:
+ */
+void
+gs_review_set_state (GsReview *review, GsReviewState state)
+{
+       g_return_if_fail (GS_IS_REVIEW (review));
+       review->state = state;
+}
+
+/**
  * gs_review_get_reviewer:
  **/
 const gchar *
@@ -215,6 +237,9 @@ gs_review_get_property (GObject *object, guint prop_id,
        case PROP_RATING:
                g_value_set_int (value, review->rating);
                break;
+       case PROP_STATE:
+               g_value_set_uint64 (value, review->state);
+               break;
        case PROP_VERSION:
                g_value_set_string (value, review->version);
                break;
@@ -249,6 +274,9 @@ gs_review_set_property (GObject *object, guint prop_id,
        case PROP_RATING:
                gs_review_set_rating (review, g_value_get_int (value));
                break;
+       case PROP_STATE:
+               gs_review_set_state (review, g_value_get_uint64 (value));
+               break;
        case PROP_VERSION:
                gs_review_set_version (review, g_value_get_string (value));
                break;
@@ -329,6 +357,16 @@ gs_review_class_init (GsReviewClass *klass)
        g_object_class_install_property (object_class, PROP_RATING, pspec);
 
        /**
+        * GsApp:state:
+        */
+       pspec = g_param_spec_uint64 ("state", NULL, NULL,
+                                    GS_REVIEW_STATE_NONE,
+                                    GS_REVIEW_STATE_LAST,
+                                    GS_REVIEW_STATE_NONE,
+                                    G_PARAM_READWRITE | G_PARAM_CONSTRUCT);
+       g_object_class_install_property (object_class, PROP_STATE, pspec);
+
+       /**
         * GsApp:version:
         */
        pspec = g_param_spec_string ("version", NULL, NULL,
diff --git a/src/gs-review.h b/src/gs-review.h
index 0a4204f..c47bfff 100644
--- a/src/gs-review.h
+++ b/src/gs-review.h
@@ -30,6 +30,21 @@ G_BEGIN_DECLS
 
 G_DECLARE_FINAL_TYPE (GsReview, gs_review, GS, REVIEW, GObject)
 
+typedef enum {
+       GS_REVIEW_ACTION_SUBMIT,
+       GS_REVIEW_ACTION_UPVOTE,
+       GS_REVIEW_ACTION_DOWNVOTE,
+       GS_REVIEW_ACTION_REPORT,
+       GS_REVIEW_ACTION_REMOVE,
+       GS_REVIEW_ACTION_LAST
+} GsReviewAction;
+
+typedef enum {
+       GS_REVIEW_STATE_NONE = 0,
+       GS_REVIEW_STATE_SELF = 1 << 0,  /* user wrote the review themselves */
+       GS_REVIEW_STATE_LAST
+} GsReviewState;
+
 GsReview       *gs_review_new                          (void);
 
 gint            gs_review_get_karma                    (GsReview       *review);
@@ -60,6 +75,10 @@ GDateTime    *gs_review_get_date                     (GsReview       *review);
 void            gs_review_set_date                     (GsReview       *review,
                                                         GDateTime      *date);
 
+GsReviewState   gs_review_get_state                    (GsReview       *review);
+void            gs_review_set_state                    (GsReview       *review,
+                                                        GsReviewState   state);
+
 G_END_DECLS
 
 #endif /* __GS_REVIEW_H */
diff --git a/src/gs-shell-details.c b/src/gs-shell-details.c
index aa931ba..01b8f8c 100644
--- a/src/gs-shell-details.c
+++ b/src/gs-shell-details.c
@@ -971,11 +971,55 @@ gs_shell_details_refresh_addons (GsShellDetails *self)
        }
 }
 
+/**
+ * gs_shell_details_app_set_review_cb:
+ **/
+static void
+gs_shell_details_app_set_review_cb (GObject *source,
+                               GAsyncResult *res,
+                               gpointer user_data)
+{
+       GsPluginLoader *plugin_loader = GS_PLUGIN_LOADER (source);
+       GsShellDetails *self = GS_SHELL_DETAILS (user_data);
+       g_autoptr(GError) error = NULL;
+
+       if (!gs_plugin_loader_app_action_finish (plugin_loader, res, &error)) {
+               g_warning ("failed to set review %s: %s",
+                          gs_app_get_id (self->app), error->message);
+       }
+}
+
+static void
+gs_shell_details_review_button_clicked_cb (GsReviewRow *row,
+                                          GsReviewAction action,
+                                          GsShellDetails *self)
+{
+       gs_plugin_loader_review_action_async (self->plugin_loader,
+                                             self->app,
+                                             gs_review_row_get_review (row),
+                                             action,
+                                             self->cancellable,
+                                             gs_shell_details_app_set_review_cb,
+                                             self);
+}
+
 static void
 gs_shell_details_refresh_reviews (GsShellDetails *self)
 {
        GPtrArray *reviews;
        guint i;
+       guint64 possible_actions = 0;
+       struct {
+               GsReviewAction action;
+               const gchar *plugin_func;
+       } plugin_vfuncs[] = {
+               { GS_REVIEW_ACTION_UPVOTE,      "gs_plugin_review_upvote" },
+               { GS_REVIEW_ACTION_DOWNVOTE,    "gs_plugin_review_downvote" },
+               { GS_REVIEW_ACTION_REPORT,      "gs_plugin_review_report" },
+               { GS_REVIEW_ACTION_SUBMIT,      "gs_plugin_review_submit" },
+               { GS_REVIEW_ACTION_REMOVE,      "gs_plugin_review_remove" },
+               { GS_REVIEW_ACTION_LAST,        NULL }
+       };
 
        if (!gs_plugin_loader_get_plugin_supported (self->plugin_loader,
                                                    "gs_plugin_review_submit"))
@@ -983,11 +1027,29 @@ gs_shell_details_refresh_reviews (GsShellDetails *self)
 
        gs_container_remove_all (GTK_CONTAINER (self->list_box_reviews));
 
+       /* find what the plugins support */
+       for (i = 0; plugin_vfuncs[i].action != GS_REVIEW_ACTION_LAST; i++) {
+               if (gs_plugin_loader_get_plugin_supported (self->plugin_loader,
+                                                          plugin_vfuncs[i].plugin_func)) {
+                       possible_actions |= 1 << plugin_vfuncs[i].action;
+               }
+       }
+
        /* add all the reviews */
        reviews = gs_app_get_reviews (self->app);
        for (i = 0; i < reviews->len; i++) {
                GsReview *review = g_ptr_array_index (reviews, i);
                GtkWidget *row = gs_review_row_new (review);
+               guint64 actions;
+
+               g_signal_connect (row, "button-clicked",
+                                 G_CALLBACK (gs_shell_details_review_button_clicked_cb), self);
+               if (gs_review_get_state (review) & GS_REVIEW_STATE_SELF) {
+                       actions = possible_actions & 1 << GS_REVIEW_ACTION_REMOVE;
+               } else {
+                       actions = possible_actions & ~(1 << GS_REVIEW_ACTION_REMOVE);
+               }
+               gs_review_row_set_actions (GS_REVIEW_ROW (row), actions);
                gtk_container_add (GTK_CONTAINER (self->list_box_reviews), row);
                gtk_widget_show (row);
        }
@@ -1338,25 +1400,6 @@ gs_shell_details_app_set_ratings_cb (GObject *source,
        }
 }
 
-
-/**
- * gs_shell_details_app_set_review_cb:
- **/
-static void
-gs_shell_details_app_set_review_cb (GObject *source,
-                               GAsyncResult *res,
-                               gpointer user_data)
-{
-       GsPluginLoader *plugin_loader = GS_PLUGIN_LOADER (source);
-       GsShellDetails *self = GS_SHELL_DETAILS (user_data);
-       g_autoptr(GError) error = NULL;
-
-       if (!gs_plugin_loader_app_action_finish (plugin_loader, res, &error)) {
-               g_warning ("failed to set review %s: %s",
-                          gs_app_get_id (self->app), error->message);
-       }
-}
-
 /**
  * gs_shell_details_write_review_cb:
  **/
@@ -1389,7 +1432,7 @@ gs_shell_details_write_review_cb (GtkButton *button,
                gs_plugin_loader_review_action_async (self->plugin_loader,
                                                      self->app,
                                                      review,
-                                                     GS_PLUGIN_LOADER_ACTION_REVIEW_SUBMIT,
+                                                     GS_REVIEW_ACTION_SUBMIT,
                                                      self->cancellable,
                                                      gs_shell_details_app_set_review_cb,
                                                      self);
diff --git a/src/plugins/gs-plugin-dummy.c b/src/plugins/gs-plugin-dummy.c
index cce7a13..020e532 100644
--- a/src/plugins/gs-plugin-dummy.c
+++ b/src/plugins/gs-plugin-dummy.c
@@ -165,15 +165,32 @@ gs_plugin_refine (GsPlugin *plugin,
        /* add fake review */
        if (flags & GS_PLUGIN_REFINE_FLAGS_REQUIRE_REVIEWS) {
                for (l = *list; l != NULL; l = l->next) {
-                       g_autoptr(GsReview) review = NULL;
+                       g_autoptr(GsReview) review1 = NULL;
+                       g_autoptr(GsReview) review2 = NULL;
+                       g_autoptr(GDateTime) dt = NULL;
                        app = GS_APP (l->data);
-                       review = gs_review_new ();
-                       gs_review_set_rating (review, 50);
-                       gs_review_set_reviewer (review, "Angela Avery");
-                       gs_review_set_summary (review, "Steep learning curve, but worth it");
-                       gs_review_set_text (review, "Best overall 3D application I've ever used overall 3D 
application I've ever used. Best overall 3D application I've ever used overall 3D application I've ever used. 
Best overall 3D application I've ever used overall 3D application I've ever used. Best overall 3D application 
I've ever used overall 3D application I've ever used.");
-                       gs_review_set_version (review, "3.16.4");
-                       gs_app_add_review (app, review);
+                       dt = g_date_time_new_now_utc ();
+
+                       /* set first review */
+                       review1 = gs_review_new ();
+                       gs_review_set_rating (review1, 50);
+                       gs_review_set_reviewer (review1, "Angela Avery");
+                       gs_review_set_summary (review1, "Steep learning curve, but worth it");
+                       gs_review_set_text (review1, "Best overall 3D application I've ever used overall 3D 
application I've ever used. Best overall 3D application I've ever used overall 3D application I've ever used. 
Best overall 3D application I've ever used overall 3D application I've ever used. Best overall 3D application 
I've ever used overall 3D application I've ever used.");
+                       gs_review_set_version (review1, "3.16.4");
+                       gs_review_set_date (review1, dt);
+                       gs_app_add_review (app, review1);
+
+                       /* set self review */
+                       review2 = gs_review_new ();
+                       gs_review_set_rating (review2, 100);
+                       gs_review_set_reviewer (review2, "Just Myself");
+                       gs_review_set_summary (review2, "I like this application");
+                       gs_review_set_text (review2, "I'm not very wordy myself.");
+                       gs_review_set_version (review2, "3.16.3");
+                       gs_review_set_date (review2, dt);
+                       gs_review_set_state (review2, GS_REVIEW_STATE_SELF);
+                       gs_app_add_review (app, review2);
                }
        }
 
@@ -251,6 +268,7 @@ gs_plugin_review_submit (GsPlugin *plugin,
                         GCancellable *cancellable,
                         GError **error)
 {
+       g_debug ("Submitting dummy review");
        return TRUE;
 }
 
@@ -264,6 +282,7 @@ gs_plugin_review_report (GsPlugin *plugin,
                         GCancellable *cancellable,
                         GError **error)
 {
+       g_debug ("Reporting dummy review");
        return TRUE;
 }
 
@@ -277,6 +296,7 @@ gs_plugin_review_upvote (GsPlugin *plugin,
                         GCancellable *cancellable,
                         GError **error)
 {
+       g_debug ("Upvoting dummy review");
        return TRUE;
 }
 
@@ -290,6 +310,7 @@ gs_plugin_review_downvote (GsPlugin *plugin,
                           GCancellable *cancellable,
                           GError **error)
 {
+       g_debug ("Downvoting dummy review");
        return TRUE;
 }
 
@@ -303,5 +324,6 @@ gs_plugin_review_remove (GsPlugin *plugin,
                         GCancellable *cancellable,
                         GError **error)
 {
+       g_debug ("Removing dummy self-review");
        return TRUE;
 }


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