[gnome-software] Show addons on the details page
- From: Kalev Lember <klember src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [gnome-software] Show addons on the details page
- Date: Tue, 24 Jun 2014 13:33:42 +0000 (UTC)
commit 3cc3e2ca6d72e01010b16a6c4a30353d6ff2fad2
Author: Kalev Lember <kalevlember gmail com>
Date: Tue Jun 24 16:00:21 2014 +0300
Show addons on the details page
po/POTFILES.in | 1 +
src/Makefile.am | 3 +
src/gnome-software.gresource.xml | 1 +
src/gs-app-addon-row.c | 298 ++++++++++++++++++++++++++++++++++++
src/gs-app-addon-row.h | 69 +++++++++
src/gs-app-addon-row.ui | 102 ++++++++++++
src/gs-app.c | 63 ++++++++
src/gs-app.h | 7 +
src/gs-plugin-loader.c | 71 +++++++++-
src/gs-plugin.h | 1 +
src/gs-shell-details.c | 186 +++++++++++++++++++++--
src/gs-shell-details.ui | 65 ++++++++-
src/plugins/gs-plugin-appstream.c | 40 +++++
src/plugins/gs-plugin-packagekit.c | 42 ++++-
14 files changed, 924 insertions(+), 25 deletions(-)
---
diff --git a/po/POTFILES.in b/po/POTFILES.in
index 34ebddc..3d3e737 100644
--- a/po/POTFILES.in
+++ b/po/POTFILES.in
@@ -7,6 +7,7 @@ src/gnome-software-local-file.desktop.in
[type: gettext/glade]src/gnome-software.ui
src/gs-app-folder-dialog.c
src/gs-application.c
+src/gs-app-addon-row.c
src/gs-app-row.c
src/gs-app-tile.c
src/gs-category.c
diff --git a/src/Makefile.am b/src/Makefile.am
index 0f9526c..34e8091 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -31,6 +31,7 @@ UI_FILES = \
category-tile.ui \
feature-tile.ui \
gnome-software.ui \
+ gs-app-addon-row.ui \
gs-app-row.ui \
gs-history-dialog.ui \
gs-shell-category.ui \
@@ -95,6 +96,8 @@ gnome_software_SOURCES = \
gs-app.h \
gs-category.c \
gs-category.h \
+ gs-app-addon-row.c \
+ gs-app-addon-row.h \
gs-app-row.c \
gs-app-row.h \
gs-star-widget.c \
diff --git a/src/gnome-software.gresource.xml b/src/gnome-software.gresource.xml
index 70cee3a..92a1866 100644
--- a/src/gnome-software.gresource.xml
+++ b/src/gnome-software.gresource.xml
@@ -9,6 +9,7 @@
<file preprocess="xml-stripblanks">app-tile.ui</file>
<file preprocess="xml-stripblanks">app-folder-dialog.ui</file>
<file preprocess="xml-stripblanks">screenshot-image.ui</file>
+ <file preprocess="xml-stripblanks">gs-app-addon-row.ui</file>
<file preprocess="xml-stripblanks">gs-app-row.ui</file>
<file preprocess="xml-stripblanks">gs-history-dialog.ui</file>
<file preprocess="xml-stripblanks">gs-shell-category.ui</file>
diff --git a/src/gs-app-addon-row.c b/src/gs-app-addon-row.c
new file mode 100644
index 0000000..91fa471
--- /dev/null
+++ b/src/gs-app-addon-row.c
@@ -0,0 +1,298 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-
+ *
+ * Copyright (C) 2012-2013 Richard Hughes <richard hughsie com>
+ * Copyright (C) 2013 Matthias Clasen <mclasen redhat com>
+ *
+ * Licensed under the GNU General Public License Version 2
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h"
+
+#include <glib/gi18n.h>
+#include <gtk/gtk.h>
+
+#include "gs-app-addon-row.h"
+#include "gs-utils.h"
+
+struct _GsAppAddonRowPrivate
+{
+ GsApp *app;
+ GtkWidget *name_box;
+ GtkWidget *name_label;
+ GtkWidget *description_label;
+ GtkWidget *spinner;
+ GtkWidget *label;
+ GtkWidget *checkbox;
+};
+
+G_DEFINE_TYPE_WITH_PRIVATE (GsAppAddonRow, gs_app_addon_row, GTK_TYPE_LIST_BOX_ROW)
+
+enum {
+ PROP_ZERO,
+ PROP_SELECTED
+};
+
+/**
+ * gs_app_addon_row_get_summary:
+ *
+ * Return value: PangoMarkup
+ **/
+static GString *
+gs_app_addon_row_get_summary (GsAppAddonRow *row)
+{
+ GsAppAddonRowPrivate *priv = row->priv;
+ GString *str = NULL;
+ const gchar *tmp = NULL;
+ gchar *escaped;
+
+ /* try all these things in order */
+ if (gs_app_get_kind (priv->app) == GS_APP_KIND_MISSING)
+ tmp = gs_app_get_summary_missing (priv->app);
+ if (tmp == NULL || (tmp != NULL && tmp[0] == '\0'))
+ tmp = gs_app_get_summary (priv->app);
+ if (tmp == NULL || (tmp != NULL && tmp[0] == '\0'))
+ tmp = gs_app_get_description (priv->app);
+
+ escaped = g_markup_escape_text (tmp, -1);
+ str = g_string_new (escaped);
+ g_free (escaped);
+
+ return str;
+}
+
+void
+gs_app_addon_row_refresh (GsAppAddonRow *row)
+{
+ GsAppAddonRowPrivate *priv = row->priv;
+ GString *str;
+
+ if (row->priv->app == NULL)
+ return;
+
+ /* join the lines */
+ str = gs_app_addon_row_get_summary (row);
+ gs_string_replace (str, "\n", " ");
+ gtk_label_set_markup (GTK_LABEL (priv->description_label), str->str);
+ g_string_free (str, TRUE);
+
+ gtk_label_set_label (GTK_LABEL (priv->name_label),
+ gs_app_get_name (priv->app));
+
+ /* update the state label */
+ switch (gs_app_get_state (row->priv->app)) {
+ case GS_APP_STATE_QUEUED:
+ gtk_widget_set_visible (priv->label, TRUE);
+ gtk_label_set_label (GTK_LABEL (priv->label), _("Pending"));
+ break;
+ case GS_APP_STATE_UPDATABLE:
+ case GS_APP_STATE_INSTALLED:
+ gtk_widget_set_visible (priv->label, TRUE);
+ gtk_label_set_label (GTK_LABEL (priv->label), _("Installed"));
+ break;
+ case GS_APP_STATE_INSTALLING:
+ gtk_widget_set_visible (priv->label, TRUE);
+ gtk_label_set_label (GTK_LABEL (priv->label), _("Installing"));
+ break;
+ case GS_APP_STATE_REMOVING:
+ gtk_widget_set_visible (priv->label, TRUE);
+ gtk_label_set_label (GTK_LABEL (priv->label), _("Removing"));
+ break;
+ default:
+ gtk_widget_set_visible (priv->label, FALSE);
+ break;
+ }
+
+ /* update the spinner */
+ switch (gs_app_get_state (row->priv->app)) {
+ case GS_APP_STATE_INSTALLING:
+ case GS_APP_STATE_REMOVING:
+ gtk_spinner_start (GTK_SPINNER (priv->spinner));
+ gtk_widget_set_visible (priv->spinner, TRUE);
+ break;
+ default:
+ gtk_spinner_stop (GTK_SPINNER (priv->spinner));
+ gtk_widget_set_visible (priv->spinner, FALSE);
+ break;
+ }
+
+ /* update the checkbox */
+ switch (gs_app_get_state (row->priv->app)) {
+ case GS_APP_STATE_QUEUED:
+ gtk_widget_set_sensitive (priv->checkbox, TRUE);
+ gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (row->priv->checkbox), TRUE);
+ break;
+ case GS_APP_STATE_AVAILABLE:
+ case GS_APP_STATE_LOCAL:
+ gtk_widget_set_sensitive (priv->checkbox, TRUE);
+ gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (row->priv->checkbox), FALSE);
+ break;
+ case GS_APP_STATE_UPDATABLE:
+ case GS_APP_STATE_INSTALLED:
+ gtk_widget_set_sensitive (priv->checkbox, TRUE);
+ gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (row->priv->checkbox), TRUE);
+ break;
+ case GS_APP_STATE_INSTALLING:
+ gtk_widget_set_sensitive (priv->checkbox, FALSE);
+ gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (row->priv->checkbox), TRUE);
+ break;
+ case GS_APP_STATE_REMOVING:
+ gtk_widget_set_sensitive (priv->checkbox, FALSE);
+ gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (row->priv->checkbox), FALSE);
+ break;
+ default:
+ gtk_widget_set_sensitive (priv->checkbox, FALSE);
+ gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (row->priv->checkbox), FALSE);
+ break;
+ }
+}
+
+GsApp *
+gs_app_addon_row_get_addon (GsAppAddonRow *row)
+{
+ g_return_val_if_fail (GS_IS_APP_ADDON_ROW (row), NULL);
+ return row->priv->app;
+}
+
+static void
+gs_app_addon_row_notify_props_changed_cb (GsApp *app,
+ GParamSpec *pspec,
+ GsAppAddonRow *row)
+{
+ gs_app_addon_row_refresh (row);
+}
+
+void
+gs_app_addon_row_set_addon (GsAppAddonRow *row, GsApp *app)
+{
+ g_return_if_fail (GS_IS_APP_ADDON_ROW (row));
+ g_return_if_fail (GS_IS_APP (app));
+
+ row->priv->app = g_object_ref (app);
+
+ g_signal_connect_object (row->priv->app, "notify::state",
+ G_CALLBACK (gs_app_addon_row_notify_props_changed_cb),
+ row, 0);
+ gs_app_addon_row_refresh (row);
+}
+
+static void
+gs_app_addon_row_destroy (GtkWidget *object)
+{
+ GsAppAddonRow *row = GS_APP_ADDON_ROW (object);
+ GsAppAddonRowPrivate *priv = row->priv;
+
+ g_clear_object (&priv->app);
+
+ GTK_WIDGET_CLASS (gs_app_addon_row_parent_class)->destroy (object);
+}
+
+static void
+gs_app_addon_row_set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec)
+{
+ GsAppAddonRow *row = GS_APP_ADDON_ROW (object);
+
+ switch (prop_id) {
+ case PROP_SELECTED:
+ gs_app_addon_row_set_selected (row, g_value_get_boolean (value));
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+gs_app_addon_row_get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec)
+{
+ GsAppAddonRow *row = GS_APP_ADDON_ROW (object);
+
+ switch (prop_id) {
+ case PROP_SELECTED:
+ g_value_set_boolean (value, gs_app_addon_row_get_selected (row));
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+gs_app_addon_row_class_init (GsAppAddonRowClass *klass)
+{
+ GParamSpec *pspec;
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
+
+ object_class->set_property = gs_app_addon_row_set_property;
+ object_class->get_property = gs_app_addon_row_get_property;
+
+ widget_class->destroy = gs_app_addon_row_destroy;
+
+ pspec = g_param_spec_boolean ("selected", NULL, NULL,
+ FALSE, G_PARAM_READWRITE);
+ g_object_class_install_property (object_class, PROP_SELECTED, pspec);
+
+ gtk_widget_class_set_template_from_resource (widget_class, "/org/gnome/software/gs-app-addon-row.ui");
+
+ gtk_widget_class_bind_template_child_private (widget_class, GsAppAddonRow, name_box);
+ gtk_widget_class_bind_template_child_private (widget_class, GsAppAddonRow, name_label);
+ gtk_widget_class_bind_template_child_private (widget_class, GsAppAddonRow, description_label);
+ gtk_widget_class_bind_template_child_private (widget_class, GsAppAddonRow, spinner);
+ gtk_widget_class_bind_template_child_private (widget_class, GsAppAddonRow, label);
+ gtk_widget_class_bind_template_child_private (widget_class, GsAppAddonRow, checkbox);
+}
+
+static void
+checkbox_toggled (GtkWidget *widget, GsAppAddonRow *row)
+{
+ g_object_notify (G_OBJECT (row), "selected");
+}
+
+static void
+gs_app_addon_row_init (GsAppAddonRow *row)
+{
+ GsAppAddonRowPrivate *priv;
+
+ priv = gs_app_addon_row_get_instance_private (row);
+ row->priv = priv;
+
+ gtk_widget_set_has_window (GTK_WIDGET (row), FALSE);
+ gtk_widget_init_template (GTK_WIDGET (row));
+
+ g_signal_connect (priv->checkbox, "toggled",
+ G_CALLBACK (checkbox_toggled), row);
+}
+
+void
+gs_app_addon_row_set_selected (GsAppAddonRow *row, gboolean selected)
+{
+ gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (row->priv->checkbox), selected);
+}
+
+gboolean
+gs_app_addon_row_get_selected (GsAppAddonRow *row)
+{
+ return gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (row->priv->checkbox));
+}
+
+GtkWidget *
+gs_app_addon_row_new (void)
+{
+ return g_object_new (GS_TYPE_APP_ADDON_ROW, NULL);
+}
+
+/* vim: set noexpandtab: */
diff --git a/src/gs-app-addon-row.h b/src/gs-app-addon-row.h
new file mode 100644
index 0000000..601de6c
--- /dev/null
+++ b/src/gs-app-addon-row.h
@@ -0,0 +1,69 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-
+ *
+ * Copyright (C) 2012 Richard Hughes <richard hughsie com>
+ *
+ * Licensed under the GNU General Public License Version 2
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef GS_APP_ADDON_ROW_H
+#define GS_APP_ADDON_ROW_H
+
+#include <gtk/gtk.h>
+
+#include "gs-app.h"
+
+#define GS_TYPE_APP_ADDON_ROW (gs_app_addon_row_get_type())
+#define GS_APP_ADDON_ROW(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), GS_TYPE_APP_ADDON_ROW,
GsAppAddonRow))
+#define GS_APP_ADDON_ROW_CLASS(cls) (G_TYPE_CHECK_CLASS_CAST((cls), GS_TYPE_APP_ADDON_ROW,
GsAppAddonRowClass))
+#define GS_IS_APP_ADDON_ROW(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), GS_TYPE_APP_ADDON_ROW))
+#define GS_IS_APP_ADDON_ROW_CLASS(cls) (G_TYPE_CHECK_CLASS_TYPE((cls), GS_TYPE_APP_ADDON_ROW))
+#define GS_APP_ADDON_ROW_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj), GS_TYPE_APP_ADDON_ROW,
GsAppAddonRowClass))
+
+G_BEGIN_DECLS
+
+typedef struct _GsAppAddonRow GsAppAddonRow;
+typedef struct _GsAppAddonRowClass GsAppAddonRowClass;
+typedef struct _GsAppAddonRowPrivate GsAppAddonRowPrivate;
+
+struct _GsAppAddonRow
+{
+ GtkListBoxRow parent;
+
+ /*< private >*/
+ GsAppAddonRowPrivate *priv;
+};
+
+struct _GsAppAddonRowClass
+{
+ GtkListBoxRowClass parent_class;
+};
+
+GType gs_app_addon_row_get_type (void);
+GtkWidget *gs_app_addon_row_new (void);
+void gs_app_addon_row_refresh (GsAppAddonRow *row);
+void gs_app_addon_row_set_selected (GsAppAddonRow *row,
+ gboolean selected);
+gboolean gs_app_addon_row_get_selected (GsAppAddonRow *row);
+GsApp *gs_app_addon_row_get_addon (GsAppAddonRow *row);
+void gs_app_addon_row_set_addon (GsAppAddonRow *row,
+ GsApp *app);
+
+G_END_DECLS
+
+#endif /* GS_APP_ADDON_ROW_H */
+
+/* vim: set noexpandtab: */
diff --git a/src/gs-app-addon-row.ui b/src/gs-app-addon-row.ui
new file mode 100644
index 0000000..f518d45
--- /dev/null
+++ b/src/gs-app-addon-row.ui
@@ -0,0 +1,102 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interface>
+ <!-- interface-requires gtk+ 3.10 -->
+ <template class="GsAppAddonRow" parent="GtkListBoxRow">
+ <property name="visible">True</property>
+ <child>
+ <object class="GtkBox" id="box">
+ <property name="visible">True</property>
+ <property name="margin-top">12</property>
+ <property name="margin-bottom">12</property>
+ <property name="margin-start">18</property>
+ <property name="margin-end">18</property>
+ <property name="orientation">horizontal</property>
+ <child>
+ <object class="GtkCheckButton" id="checkbox">
+ <property name="visible">True</property>
+ <property name="valign">center</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkBox" id="name_box">
+ <property name="visible">True</property>
+ <property name="margin_start">12</property>
+ <property name="orientation">vertical</property>
+ <property name="valign">start</property>
+ <child>
+ <object class="GtkLabel" id="name_label">
+ <property name="visible">True</property>
+ <property name="max_width_chars">20</property>
+ <property name="xalign">0.0</property>
+ <property name="yalign">0.5</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="description_label">
+ <property name="visible">True</property>
+ <property name="xalign">0</property>
+ <property name="yalign">0.5</property>
+ <property name="ellipsize">end</property>
+ <style>
+ <class name="dim-label"/>
+ </style>
+ </object>
+ <packing>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkBox" id="end_box">
+ <property name="visible">True</property>
+ <property name="orientation">horizontal</property>
+ <property name="width_request">180</property>
+ <property name="halign">end</property>
+ <property name="valign">center</property>
+ <child>
+ <object class="GtkSpinner" id="spinner">
+ <property name="margin_start">12</property>
+ <property name="halign">end</property>
+ </object>
+ <packing>
+ <property name="pack_type">end</property>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="label">
+ <property name="margin_start">12</property>
+ <property name="halign">end</property>
+ </object>
+ <packing>
+ <property name="pack_type">end</property>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="pack_type">end</property>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ </template>
+</interface>
diff --git a/src/gs-app.c b/src/gs-app.c
index 3ead682..e2d84f7 100644
--- a/src/gs-app.c
+++ b/src/gs-app.c
@@ -89,11 +89,14 @@ struct GsAppPrivate
GHashTable *metadata;
GdkPixbuf *pixbuf;
GdkPixbuf *featured_pixbuf;
+ GPtrArray *addons; /* of GsApp */
+ GHashTable *addons_hash; /* of "id" */
GPtrArray *related; /* of GsApp */
GHashTable *related_hash; /* of "id-source" */
GPtrArray *history; /* of GsApp */
guint64 install_date;
guint64 kudos;
+ gboolean to_be_installed;
};
enum {
@@ -1484,6 +1487,39 @@ gs_app_set_metadata (GsApp *app, const gchar *key, const gchar *value)
}
/**
+ * gs_app_get_addons:
+ */
+GPtrArray *
+gs_app_get_addons (GsApp *app)
+{
+ g_return_val_if_fail (GS_IS_APP (app), NULL);
+ return app->priv->addons;
+}
+
+/**
+ * gs_app_add_addon:
+ */
+void
+gs_app_add_addon (GsApp *app, GsApp *addon)
+{
+ gpointer found;
+ const gchar *id;
+
+ g_return_if_fail (GS_IS_APP (app));
+ g_return_if_fail (GS_IS_APP (addon));
+
+ id = gs_app_get_id (addon);
+ found = g_hash_table_lookup (app->priv->addons_hash, id);
+ if (found != NULL) {
+ g_debug ("Already added %s as an addon", id);
+ return;
+ }
+ g_hash_table_insert (app->priv->addons_hash, g_strdup (id), GINT_TO_POINTER (1));
+
+ g_ptr_array_add (app->priv->addons, g_object_ref (addon));
+}
+
+/**
* gs_app_get_related:
*/
GPtrArray *
@@ -1701,6 +1737,26 @@ gs_app_get_kudos_percentage (GsApp *app)
}
/**
+ * gs_app_get_to_be_installed:
+ */
+gboolean
+gs_app_get_to_be_installed (GsApp *app)
+{
+ GsAppPrivate *priv = app->priv;
+ return priv->to_be_installed;
+}
+
+/**
+ * gs_app_set_to_be_installed:
+ */
+void
+gs_app_set_to_be_installed (GsApp *app, gboolean to_be_installed)
+{
+ GsAppPrivate *priv = app->priv;
+ priv->to_be_installed = to_be_installed;
+}
+
+/**
* gs_app_subsume:
*
* Imports all the useful data from @other into @app.
@@ -1962,6 +2018,7 @@ gs_app_init (GsApp *app)
app->priv->sources = g_ptr_array_new_with_free_func (g_free);
app->priv->source_ids = g_ptr_array_new_with_free_func (g_free);
app->priv->categories = g_ptr_array_new_with_free_func (g_free);
+ app->priv->addons = g_ptr_array_new_with_free_func ((GDestroyNotify) g_object_unref);
app->priv->related = g_ptr_array_new_with_free_func ((GDestroyNotify) g_object_unref);
app->priv->history = g_ptr_array_new_with_free_func ((GDestroyNotify) g_object_unref);
app->priv->screenshots = g_ptr_array_new_with_free_func ((GDestroyNotify) g_object_unref);
@@ -1969,6 +2026,10 @@ gs_app_init (GsApp *app)
g_str_equal,
g_free,
g_free);
+ app->priv->addons_hash = g_hash_table_new_full (g_str_hash,
+ g_str_equal,
+ g_free,
+ NULL);
app->priv->related_hash = g_hash_table_new_full (g_str_hash,
g_str_equal,
g_free,
@@ -2011,6 +2072,8 @@ gs_app_finalize (GObject *object)
g_free (priv->update_details);
g_free (priv->management_plugin);
g_hash_table_unref (priv->metadata);
+ g_hash_table_unref (priv->addons_hash);
+ g_ptr_array_unref (priv->addons);
g_hash_table_unref (priv->related_hash);
g_ptr_array_unref (priv->related);
g_ptr_array_unref (priv->history);
diff --git a/src/gs-app.h b/src/gs-app.h
index 43bcba5..fe73838 100644
--- a/src/gs-app.h
+++ b/src/gs-app.h
@@ -87,6 +87,7 @@ typedef enum {
GS_APP_ID_KIND_FONT,
GS_APP_ID_KIND_CODEC,
GS_APP_ID_KIND_WEBAPP,
+ GS_APP_ID_KIND_ADDON,
GS_APP_ID_KIND_LAST
} GsAppIdKind;
@@ -246,6 +247,9 @@ void gs_app_set_rating_kind (GsApp *app,
guint64 gs_app_get_size (GsApp *app);
void gs_app_set_size (GsApp *app,
guint64 size);
+GPtrArray *gs_app_get_addons (GsApp *app);
+void gs_app_add_addon (GsApp *app,
+ GsApp *addon);
GPtrArray *gs_app_get_related (GsApp *app);
void gs_app_add_related (GsApp *app,
GsApp *app2);
@@ -270,6 +274,9 @@ void gs_app_add_kudo (GsApp *app,
guint64 gs_app_get_kudos (GsApp *app);
guint gs_app_get_kudos_weight (GsApp *app);
guint gs_app_get_kudos_percentage (GsApp *app);
+gboolean gs_app_get_to_be_installed (GsApp *app);
+void gs_app_set_to_be_installed (GsApp *app,
+ gboolean to_be_installed);
G_END_DECLS
diff --git a/src/gs-plugin-loader.c b/src/gs-plugin-loader.c
index f338332..3f4b952 100644
--- a/src/gs-plugin-loader.c
+++ b/src/gs-plugin-loader.c
@@ -225,7 +225,9 @@ gs_plugin_loader_run_refine (GsPluginLoader *plugin_loader,
GError **error)
{
GList *l;
+ GList *addons_list = NULL;
GList *related_list = NULL;
+ GPtrArray *addons;
GPtrArray *related;
GsApp *app;
GsPlugin *plugin;
@@ -254,6 +256,32 @@ gs_plugin_loader_run_refine (GsPluginLoader *plugin_loader,
goto out;
}
+ /* refine addons one layer deep */
+ if ((flags & GS_PLUGIN_REFINE_FLAGS_REQUIRE_ADDONS) > 0) {
+ flags &= ~GS_PLUGIN_REFINE_FLAGS_REQUIRE_ADDONS;
+ for (l = *list; l != NULL; l = l->next) {
+ app = GS_APP (l->data);
+ addons = gs_app_get_addons (app);
+ for (i = 0; i < addons->len; i++) {
+ GsApp *addon = g_ptr_array_index (addons, i);
+ g_debug ("refining app %s addon %s",
+ gs_app_get_id (app),
+ gs_app_get_id (addon));
+ gs_plugin_add_app (&addons_list, addon);
+ }
+ }
+ if (addons_list != NULL) {
+ ret = gs_plugin_loader_run_refine (plugin_loader,
+ function_name_parent,
+ &addons_list,
+ flags,
+ cancellable,
+ error);
+ if (!ret)
+ goto out;
+ }
+ }
+
/* also do related packages one layer deep */
if ((flags & GS_PLUGIN_REFINE_FLAGS_REQUIRE_RELATED) > 0) {
flags &= ~GS_PLUGIN_REFINE_FLAGS_REQUIRE_RELATED;
@@ -287,6 +315,7 @@ gs_plugin_loader_run_refine (GsPluginLoader *plugin_loader,
/* dedupe applications we already know about */
gs_plugin_loader_list_dedupe (plugin_loader, *list);
out:
+ gs_plugin_list_free (addons_list);
gs_plugin_list_free (related_list);
gs_plugin_list_free (freeze_list);
return ret;
@@ -1888,7 +1917,9 @@ gs_plugin_loader_app_action_thread_cb (GTask *task,
GError *error = NULL;
GsPluginLoaderAsyncState *state = (GsPluginLoaderAsyncState *) task_data;
GsPluginLoader *plugin_loader = GS_PLUGIN_LOADER (object);
+ GPtrArray *addons;
gboolean ret;
+ guint i;
guint id;
/* add to list */
@@ -1905,11 +1936,28 @@ gs_plugin_loader_app_action_thread_cb (GTask *task,
cancellable,
&error);
if (ret) {
- if (state->state_success != GS_APP_STATE_UNKNOWN)
+ if (state->state_success != GS_APP_STATE_UNKNOWN) {
gs_app_set_state (state->app, state->state_success);
+ addons = gs_app_get_addons (state->app);
+ for (i = 0; i < addons->len; i++) {
+ GsApp *addon = g_ptr_array_index (addons, i);
+ if (gs_app_get_to_be_installed (addon)) {
+ gs_app_set_state (addon, state->state_success);
+ gs_app_set_to_be_installed (addon, FALSE);
+ }
+ }
+ }
g_task_return_boolean (task, TRUE);
} else {
gs_app_set_state (state->app, state->state_failure);
+ addons = gs_app_get_addons (state->app);
+ for (i = 0; i < addons->len; i++) {
+ GsApp *addon = g_ptr_array_index (addons, i);
+ if (gs_app_get_to_be_installed (addon)) {
+ gs_app_set_state (addon, state->state_failure);
+ gs_app_set_to_be_installed (addon, FALSE);
+ }
+ }
g_task_return_error (task, error);
}
@@ -2029,9 +2077,11 @@ save_install_queue (GsPluginLoader *plugin_loader)
static void
add_app_to_install_queue (GsPluginLoader *plugin_loader, GsApp *app)
{
+ GPtrArray *addons;
+ guint i;
guint id;
- /* FIXME: persist this */
+ /* queue the app itself */
g_mutex_lock (&plugin_loader->priv->pending_apps_mutex);
g_ptr_array_add (plugin_loader->priv->pending_apps, g_object_ref (app));
g_mutex_unlock (&plugin_loader->priv->pending_apps_mutex);
@@ -2040,12 +2090,22 @@ add_app_to_install_queue (GsPluginLoader *plugin_loader, GsApp *app)
id = g_idle_add (emit_pending_apps_idle, g_object_ref (plugin_loader));
g_source_set_name_by_id (id, "[gnome-software] emit_pending_apps_idle");
save_install_queue (plugin_loader);
+
+ /* recursively queue any addons */
+ addons = gs_app_get_addons (app);
+ for (i = 0; i < addons->len; i++) {
+ GsApp *addon = g_ptr_array_index (addons, i);
+ if (gs_app_get_to_be_installed (addon))
+ add_app_to_install_queue (plugin_loader, addon);
+ }
}
static gboolean
remove_app_from_install_queue (GsPluginLoader *plugin_loader, GsApp *app)
{
+ GPtrArray *addons;
gboolean ret;
+ guint i;
guint id;
g_mutex_lock (&plugin_loader->priv->pending_apps_mutex);
@@ -2057,6 +2117,13 @@ remove_app_from_install_queue (GsPluginLoader *plugin_loader, GsApp *app)
id = g_idle_add (emit_pending_apps_idle, g_object_ref (plugin_loader));
g_source_set_name_by_id (id, "[gnome-software] emit_pending_apps_idle");
save_install_queue (plugin_loader);
+
+ /* recursively remove any queued addons */
+ addons = gs_app_get_addons (app);
+ for (i = 0; i < addons->len; i++) {
+ GsApp *addon = g_ptr_array_index (addons, i);
+ remove_app_from_install_queue (plugin_loader, addon);
+ }
}
return ret;
diff --git a/src/gs-plugin.h b/src/gs-plugin.h
index 76d4a48..c09a9ac 100644
--- a/src/gs-plugin.h
+++ b/src/gs-plugin.h
@@ -96,6 +96,7 @@ typedef enum {
GS_PLUGIN_REFINE_FLAGS_REQUIRE_ORIGIN = 1 << 10,
GS_PLUGIN_REFINE_FLAGS_REQUIRE_RELATED = 1 << 11,
GS_PLUGIN_REFINE_FLAGS_REQUIRE_MENU_PATH = 1 << 12,
+ GS_PLUGIN_REFINE_FLAGS_REQUIRE_ADDONS = 1 << 13,
GS_PLUGIN_REFINE_FLAGS_LAST
} GsPluginRefineFlags;
diff --git a/src/gs-shell-details.c b/src/gs-shell-details.c
index fc8f309..5edef28 100644
--- a/src/gs-shell-details.c
+++ b/src/gs-shell-details.c
@@ -29,6 +29,7 @@
#include "gs-utils.h"
#include "gs-shell-details.h"
+#include "gs-app-addon-row.h"
#include "gs-history-dialog.h"
#include "gs-screenshot-image.h"
#include "gs-star-widget.h"
@@ -55,6 +56,7 @@ struct GsShellDetailsPrivate
GtkWidget *application_details_icon;
GtkWidget *application_details_summary;
GtkWidget *application_details_title;
+ GtkWidget *box_addons;
GtkWidget *box_details;
GtkWidget *box_details_description;
GtkWidget *box_details_header;
@@ -69,6 +71,7 @@ struct GsShellDetailsPrivate
GtkWidget *infobar_details_package_baseos;
GtkWidget *infobar_details_repo;
GtkWidget *infobar_details_webapp;
+ GtkWidget *label_addons_uninstalled_app;
GtkWidget *label_details_category_value;
GtkWidget *label_details_developer_title;
GtkWidget *label_details_developer_value;
@@ -78,6 +81,7 @@ struct GsShellDetailsPrivate
GtkWidget *label_details_size_value;
GtkWidget *label_details_updated_value;
GtkWidget *label_details_version_value;
+ GtkWidget *list_box_addons;
GtkWidget *scrolledwindow_details;
GtkWidget *spinner_details;
GtkWidget *stack_details;
@@ -513,6 +517,7 @@ gs_shell_details_refresh_all (GsShellDetails *shell_details)
GError *error = NULL;
GPtrArray *history;
GdkPixbuf *pixbuf = NULL;
+ GList *addons, *l;
GsShellDetailsPrivate *priv = shell_details->priv;
GtkWidget *widget2;
const gchar *tmp;
@@ -774,6 +779,83 @@ gs_shell_details_refresh_all (GsShellDetails *shell_details)
gtk_widget_set_visible (priv->infobar_details_webapp, FALSE);
break;
}
+
+ /* only show the "select addons" string if the app isn't yet installed */
+ switch (gs_app_get_state (priv->app)) {
+ case GS_APP_STATE_INSTALLED:
+ case GS_APP_STATE_UPDATABLE:
+ gtk_widget_set_visible (priv->label_addons_uninstalled_app, FALSE);
+ break;
+ default:
+ gtk_widget_set_visible (priv->label_addons_uninstalled_app, TRUE);
+ break;
+ }
+
+ addons = gtk_container_get_children (GTK_CONTAINER (priv->list_box_addons));
+ gtk_widget_set_visible (priv->box_addons, addons != NULL);
+ for (l = addons; l; l = l->next) {
+ /* show checkboxes in front of addons if the app isn't yet installed */
+ switch (gs_app_get_state (priv->app)) {
+ case GS_APP_STATE_INSTALLED:
+ case GS_APP_STATE_UPDATABLE:
+ break;
+ default:
+ break;
+ }
+ }
+ g_list_free (addons);
+}
+
+static void
+list_header_func (GtkListBoxRow *row,
+ GtkListBoxRow *before,
+ gpointer user_data)
+{
+ GtkWidget *header = NULL;
+ if (before != NULL)
+ header = gtk_separator_new (GTK_ORIENTATION_HORIZONTAL);
+ gtk_list_box_row_set_header (row, header);
+}
+
+static gint
+list_sort_func (GtkListBoxRow *a,
+ GtkListBoxRow *b,
+ gpointer user_data)
+{
+ GsApp *a1 = gs_app_addon_row_get_addon (GS_APP_ADDON_ROW (a));
+ GsApp *a2 = gs_app_addon_row_get_addon (GS_APP_ADDON_ROW (b));
+
+ return g_strcmp0 (gs_app_get_name (a1),
+ gs_app_get_name (a2));
+}
+
+static void gs_shell_details_addon_selected_cb (GsAppAddonRow *row, GParamSpec *pspec, GsShellDetails
*shell_details);
+
+static void
+gs_shell_details_refresh_addons (GsShellDetails *shell_details)
+{
+ GsShellDetailsPrivate *priv = shell_details->priv;
+ GPtrArray *addons;
+ guint i;
+
+ gs_container_remove_all (GTK_CONTAINER (priv->list_box_addons));
+
+ addons = gs_app_get_addons (priv->app);
+ for (i = 0; i < addons->len; i++) {
+ GsApp *addon;
+ GtkWidget *row;
+
+ addon = g_ptr_array_index (addons, i);
+ row = gs_app_addon_row_new ();
+
+ gs_app_addon_row_set_addon (GS_APP_ADDON_ROW (row), addon);
+ gtk_container_add (GTK_CONTAINER (priv->list_box_addons), row);
+ gtk_widget_show (row);
+
+ g_signal_connect (row, "notify::selected",
+ G_CALLBACK (gs_shell_details_addon_selected_cb),
+ shell_details);
+ }
}
/**
@@ -800,6 +882,7 @@ gs_shell_details_app_refine_cb (GObject *source,
g_error_free (error);
return;
}
+ gs_shell_details_refresh_addons (shell_details);
gs_shell_details_refresh_all (shell_details);
gs_shell_details_set_state (shell_details, GS_SHELL_DETAILS_STATE_READY);
}
@@ -848,6 +931,7 @@ gs_shell_details_filename_to_app_cb (GObject *source,
/* change widgets */
gs_shell_details_refresh (shell_details);
gs_shell_details_refresh_screenshots (shell_details);
+ gs_shell_details_refresh_addons (shell_details);
gs_shell_details_refresh_all (shell_details);
gs_shell_details_set_state (shell_details, GS_SHELL_DETAILS_STATE_READY);
}
@@ -894,7 +978,8 @@ gs_shell_details_set_app (GsShellDetails *shell_details, GsApp *app)
GS_PLUGIN_REFINE_FLAGS_REQUIRE_SETUP_ACTION |
GS_PLUGIN_REFINE_FLAGS_REQUIRE_ORIGIN |
GS_PLUGIN_REFINE_FLAGS_REQUIRE_MENU_PATH |
- GS_PLUGIN_REFINE_FLAGS_REQUIRE_URL,
+ GS_PLUGIN_REFINE_FLAGS_REQUIRE_URL |
+ GS_PLUGIN_REFINE_FLAGS_REQUIRE_ADDONS,
priv->cancellable,
gs_shell_details_app_refine_cb,
shell_details);
@@ -1004,10 +1089,10 @@ gs_shell_details_app_removed_cb (GObject *source,
}
/**
- * gs_shell_details_app_remove_button_cb:
+ * gs_shell_details_app_remove
**/
static void
-gs_shell_details_app_remove_button_cb (GtkWidget *widget, GsShellDetails *shell_details)
+gs_shell_details_app_remove (GsShellDetails *shell_details, GsApp *app)
{
GsShellDetailsHelper *helper;
GsShellDetailsPrivate *priv = shell_details->priv;
@@ -1022,7 +1107,7 @@ gs_shell_details_app_remove_button_cb (GtkWidget *widget, GsShellDetails *shell_
/* TRANSLATORS: this is a prompt message, and
* '%s' is an application summary, e.g. 'GNOME Clocks' */
_("Are you sure you want to remove %s?"),
- gs_app_get_name (priv->app));
+ gs_app_get_name (app));
g_string_prepend (markup, "<b>");
g_string_append (markup, "</b>");
dialog = gtk_message_dialog_new (window,
@@ -1034,20 +1119,20 @@ gs_shell_details_app_remove_button_cb (GtkWidget *widget, GsShellDetails *shell_
gtk_message_dialog_format_secondary_markup (GTK_MESSAGE_DIALOG (dialog),
/* TRANSLATORS: longer dialog text */
_("%s will be removed, and you will have to install it to
use it again."),
- gs_app_get_name (priv->app));
+ gs_app_get_name (app));
/* TRANSLATORS: this is button text to remove the application */
gtk_dialog_add_button (GTK_DIALOG (dialog), _("Remove"), GTK_RESPONSE_OK);
- if (gs_app_get_state (priv->app) == GS_APP_STATE_INSTALLED)
+ if (gs_app_get_state (app) == GS_APP_STATE_INSTALLED)
response = gtk_dialog_run (GTK_DIALOG (dialog));
else
response = GTK_RESPONSE_OK; /* pending install */
if (response == GTK_RESPONSE_OK) {
- g_debug ("remove %s", gs_app_get_id (priv->app));
+ g_debug ("remove %s", gs_app_get_id (app));
helper = g_new0 (GsShellDetailsHelper, 1);
helper->shell_details = g_object_ref (shell_details);
- helper->app = g_object_ref (priv->app);
+ helper->app = g_object_ref (app);
gs_plugin_loader_app_action_async (priv->plugin_loader,
- priv->app,
+ app,
GS_PLUGIN_LOADER_ACTION_REMOVE,
priv->cancellable,
gs_shell_details_app_removed_cb,
@@ -1058,18 +1143,30 @@ gs_shell_details_app_remove_button_cb (GtkWidget *widget, GsShellDetails *shell_
}
/**
- * gs_shell_details_app_install_button_cb:
+ * gs_shell_details_app_remove_button_cb:
**/
static void
-gs_shell_details_app_install_button_cb (GtkWidget *widget, GsShellDetails *shell_details)
+gs_shell_details_app_remove_button_cb (GtkWidget *widget, GsShellDetails *shell_details)
+{
+ GsShellDetailsPrivate *priv = shell_details->priv;
+
+ gs_shell_details_app_remove (shell_details, priv->app);
+}
+
+/**
+ * gs_shell_details_app_install:
+ **/
+static void
+gs_shell_details_app_install (GsShellDetails *shell_details, GsApp *app)
{
GsShellDetailsPrivate *priv = shell_details->priv;
GsShellDetailsHelper *helper;
+
helper = g_new0 (GsShellDetailsHelper, 1);
helper->shell_details = g_object_ref (shell_details);
- helper->app = g_object_ref (priv->app);
+ helper->app = g_object_ref (app);
gs_plugin_loader_app_action_async (priv->plugin_loader,
- priv->app,
+ app,
GS_PLUGIN_LOADER_ACTION_INSTALL,
priv->cancellable,
gs_shell_details_app_installed_cb,
@@ -1077,6 +1174,59 @@ gs_shell_details_app_install_button_cb (GtkWidget *widget, GsShellDetails *shell
}
/**
+ * gs_shell_details_app_install_button_cb:
+ **/
+static void
+gs_shell_details_app_install_button_cb (GtkWidget *widget, GsShellDetails *shell_details)
+{
+ GsShellDetailsPrivate *priv = shell_details->priv;
+ GList *addons, *l;
+
+ /* Mark ticked addons to be installed together with the app */
+ addons = gtk_container_get_children (GTK_CONTAINER (priv->list_box_addons));
+ for (l = addons; l; l = l->next) {
+ if (gs_app_addon_row_get_selected (l->data)) {
+ GsApp *addon = gs_app_addon_row_get_addon (l->data);
+
+ if (gs_app_get_state (addon) == GS_APP_STATE_AVAILABLE)
+ gs_app_set_to_be_installed (addon, TRUE);
+ }
+ }
+ g_list_free (addons);
+
+ gs_shell_details_app_install (shell_details, priv->app);
+}
+
+/**
+ * gs_shell_details_addon_selected_cb:
+ **/
+static void
+gs_shell_details_addon_selected_cb (GsAppAddonRow *row,
+ GParamSpec *pspec,
+ GsShellDetails *shell_details)
+{
+ GsShellDetailsPrivate *priv = shell_details->priv;
+ GsApp *addon;
+
+ addon = gs_app_addon_row_get_addon (row);
+
+ /* If the main app is already installed, ticking the addon checkbox
+ * triggers an immediate install. Otherwise we'll install the addon
+ * together with the main app. */
+ switch (gs_app_get_state (priv->app)) {
+ case GS_APP_STATE_INSTALLED:
+ case GS_APP_STATE_UPDATABLE:
+ if (gs_app_addon_row_get_selected (row))
+ gs_shell_details_app_install (shell_details, addon);
+ else
+ gs_shell_details_app_remove (shell_details, addon);
+ break;
+ default:
+ break;
+ }
+}
+
+/**
* gs_shell_details_app_launch_button_cb:
**/
static void
@@ -1247,6 +1397,7 @@ gs_shell_details_class_init (GsShellDetailsClass *klass)
gtk_widget_class_bind_template_child_private (widget_class, GsShellDetails, application_details_icon);
gtk_widget_class_bind_template_child_private (widget_class, GsShellDetails,
application_details_summary);
gtk_widget_class_bind_template_child_private (widget_class, GsShellDetails,
application_details_title);
+ gtk_widget_class_bind_template_child_private (widget_class, GsShellDetails, box_addons);
gtk_widget_class_bind_template_child_private (widget_class, GsShellDetails, box_details);
gtk_widget_class_bind_template_child_private (widget_class, GsShellDetails, box_details_description);
gtk_widget_class_bind_template_child_private (widget_class, GsShellDetails, box_details_header);
@@ -1261,6 +1412,7 @@ gs_shell_details_class_init (GsShellDetailsClass *klass)
gtk_widget_class_bind_template_child_private (widget_class, GsShellDetails,
infobar_details_package_baseos);
gtk_widget_class_bind_template_child_private (widget_class, GsShellDetails, infobar_details_repo);
gtk_widget_class_bind_template_child_private (widget_class, GsShellDetails, infobar_details_webapp);
+ gtk_widget_class_bind_template_child_private (widget_class, GsShellDetails,
label_addons_uninstalled_app);
gtk_widget_class_bind_template_child_private (widget_class, GsShellDetails,
label_details_category_value);
gtk_widget_class_bind_template_child_private (widget_class, GsShellDetails,
label_details_developer_title);
gtk_widget_class_bind_template_child_private (widget_class, GsShellDetails,
label_details_developer_value);
@@ -1270,6 +1422,7 @@ gs_shell_details_class_init (GsShellDetailsClass *klass)
gtk_widget_class_bind_template_child_private (widget_class, GsShellDetails, label_details_size_value);
gtk_widget_class_bind_template_child_private (widget_class, GsShellDetails,
label_details_updated_value);
gtk_widget_class_bind_template_child_private (widget_class, GsShellDetails,
label_details_version_value);
+ gtk_widget_class_bind_template_child_private (widget_class, GsShellDetails, list_box_addons);
gtk_widget_class_bind_template_child_private (widget_class, GsShellDetails, scrolledwindow_details);
gtk_widget_class_bind_template_child_private (widget_class, GsShellDetails, spinner_details);
gtk_widget_class_bind_template_child_private (widget_class, GsShellDetails, stack_details);
@@ -1297,6 +1450,13 @@ gs_shell_details_init (GsShellDetails *shell_details)
soup_session_add_feature_by_type (priv->session,
SOUP_TYPE_PROXY_RESOLVER_DEFAULT);
}
+
+ gtk_list_box_set_header_func (GTK_LIST_BOX (priv->list_box_addons),
+ list_header_func,
+ shell_details, NULL);
+ gtk_list_box_set_sort_func (GTK_LIST_BOX (priv->list_box_addons),
+ list_sort_func,
+ shell_details, NULL);
}
/**
diff --git a/src/gs-shell-details.ui b/src/gs-shell-details.ui
index c7a070b..7cb3f99 100644
--- a/src/gs-shell-details.ui
+++ b/src/gs-shell-details.ui
@@ -602,7 +602,7 @@
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="margin_top">28</property>
- <property name="margin_bottom">24</property>
+ <property name="margin_bottom">12</property>
<property name="halign">start</property>
<property name="valign">start</property>
<property name="hexpand">True</property>
@@ -888,6 +888,69 @@
<property name="position">10</property>
</packing>
</child>
+
+ <child>
+ <object class="GtkBox" id="box_addons">
+ <property name="visible">True</property>
+ <property name="orientation">vertical</property>
+ <property name="margin_top">28</property>
+
+ <child>
+ <object class="GtkBox" id="box_addons_title">
+ <property name="visible">True</property>
+ <property name="orientation">vertical</property>
+ <property name="margin_bottom">12</property>
+ <child>
+ <object class="GtkLabel" id="label_addons_title">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="halign">start</property>
+ <property name="valign">start</property>
+ <property name="hexpand">True</property>
+ <property name="xalign">0</property>
+ <property name="label" translatable="yes">Addons</property>
+ <style>
+ <class name="application-details-title"/>
+ </style>
+ </object>
+ </child>
+ <child>
+ <object class="GtkLabel" id="label_addons_uninstalled_app">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="xalign">0</property>
+ <property name="label" translatable="yes">Selected addons will be installed
with the application.</property>
+ </object>
+ </child>
+ </object>
+ </child>
+
+ <child>
+ <object class="GtkFrame" id="box_addons_frame">
+ <property name="visible">True</property>
+ <property name="shadow_type">in</property>
+ <property name="halign">fill</property>
+ <property name="valign">start</property>
+ <style>
+ <class name="view"/>
+ </style>
+ <child>
+ <object class="GtkListBox" id="list_box_addons">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="selection_mode">none</property>
+ </object>
+ </child>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">11</property>
+ </packing>
+ </child>
+
</object>
</child>
</object>
diff --git a/src/plugins/gs-plugin-appstream.c b/src/plugins/gs-plugin-appstream.c
index 4d1261f..0802714 100644
--- a/src/plugins/gs-plugin-appstream.c
+++ b/src/plugins/gs-plugin-appstream.c
@@ -35,6 +35,8 @@ struct GsPluginPrivate {
gsize done_init;
};
+static gboolean gs_plugin_refine_item (GsPlugin *plugin, GsApp *app, AsApp *item, GError **error);
+
/**
* gs_plugin_get_name:
*/
@@ -276,6 +278,38 @@ out:
}
/**
+ * gs_plugin_refine_add_addons:
+ */
+static void
+gs_plugin_refine_add_addons (GsPlugin *plugin, GsApp *app, AsApp *item)
+{
+ GPtrArray *addons;
+ guint i;
+
+ addons = as_app_get_addons (item);
+ if (addons == NULL)
+ return;
+
+ for (i = 0; i < addons->len; i++) {
+ AsApp *as_addon = g_ptr_array_index (addons, i);
+ GsApp *addon;
+ GError *error = NULL;
+ gboolean ret;
+
+ addon = gs_app_new (as_app_get_id_full (as_addon));
+
+ /* add all the data we can */
+ ret = gs_plugin_refine_item (plugin, addon, as_addon, &error);
+ if (!ret) {
+ g_warning ("failed to refine addon: %s", error->message);
+ g_error_free (error);
+ continue;
+ }
+
+ gs_app_add_addon (app, addon);
+ }
+}
+/**
* gs_plugin_refine_add_screenshots:
*/
static void
@@ -516,6 +550,9 @@ gs_plugin_refine_item (GsPlugin *plugin,
case AS_ID_KIND_SOURCE:
/* handled above */
break;
+ case AS_ID_KIND_ADDON:
+ gs_app_set_id_kind (app, GS_APP_ID_KIND_ADDON);
+ break;
default:
g_warning ("Unhandled AsIdKind '%s'", as_id_kind_to_string (as_app_get_id_kind
(item)));
break;
@@ -527,6 +564,9 @@ gs_plugin_refine_item (GsPlugin *plugin,
if (pkgnames->len > 0 && gs_app_get_sources(app)->len == 0)
gs_app_set_sources (app, pkgnames);
+ /* set addons */
+ gs_plugin_refine_add_addons (plugin, app, item);
+
/* set screenshots */
gs_plugin_refine_add_screenshots (app, item);
diff --git a/src/plugins/gs-plugin-packagekit.c b/src/plugins/gs-plugin-packagekit.c
index 6d2d20e..cf29fe9 100644
--- a/src/plugins/gs-plugin-packagekit.c
+++ b/src/plugins/gs-plugin-packagekit.c
@@ -283,15 +283,15 @@ gs_plugin_app_install (GsPlugin *plugin,
GCancellable *cancellable,
GError **error)
{
- GPtrArray *array = NULL;
+ GPtrArray *addons;
+ GPtrArray *array_package_ids = NULL;
GPtrArray *source_ids;
PkError *error_code = NULL;
PkResults *results = NULL;
const gchar *package_id;
gboolean ret = TRUE;
gchar **package_ids = NULL;
- guint i;
- guint cnt = 0;
+ guint i, j;
/* only process this app if was created by this plugin */
if (g_strcmp0 (gs_app_get_management_plugin (app), "PackageKit") != 0)
@@ -310,14 +310,32 @@ gs_plugin_app_install (GsPlugin *plugin,
"installing not available");
goto out;
}
- package_ids = g_new0 (gchar *, source_ids->len + 1);
+ array_package_ids = g_ptr_array_new_with_free_func (g_free);
for (i = 0; i < source_ids->len; i++) {
package_id = g_ptr_array_index (source_ids, i);
if (g_strstr_len (package_id, -1, ";installed") != NULL)
continue;
- package_ids[cnt++] = g_strdup (package_id);
+ g_ptr_array_add (array_package_ids, g_strdup (package_id));
}
- if (cnt == 0) {
+
+ addons = gs_app_get_addons (app);
+ for (i = 0; i < addons->len; i++) {
+ GsApp *addon = g_ptr_array_index (addons, i);
+
+ if (!gs_app_get_to_be_installed (addon))
+ continue;
+
+ source_ids = gs_app_get_source_ids (addon);
+ for (j = 0; j < source_ids->len; j++) {
+ package_id = g_ptr_array_index (source_ids, j);
+ if (g_strstr_len (package_id, -1, ";installed") != NULL)
+ continue;
+ g_ptr_array_add (array_package_ids, g_strdup (package_id));
+ }
+ }
+ g_ptr_array_add (array_package_ids, NULL);
+
+ if (array_package_ids->len == 0) {
ret = FALSE;
g_set_error_literal (error,
GS_PLUGIN_ERROR,
@@ -326,8 +344,14 @@ gs_plugin_app_install (GsPlugin *plugin,
goto out;
}
gs_app_set_state (app, GS_APP_STATE_INSTALLING);
+ addons = gs_app_get_addons (app);
+ for (i = 0; i < addons->len; i++) {
+ GsApp *addon = g_ptr_array_index (addons, i);
+ if (gs_app_get_to_be_installed (addon))
+ gs_app_set_state (addon, GS_APP_STATE_INSTALLING);
+ }
results = pk_task_install_packages_sync (plugin->priv->task,
- package_ids,
+ (gchar **) array_package_ids->pdata,
cancellable,
gs_plugin_packagekit_progress_cb, plugin,
error);
@@ -384,8 +408,8 @@ out:
g_strfreev (package_ids);
if (error_code != NULL)
g_object_unref (error_code);
- if (array != NULL)
- g_ptr_array_unref (array);
+ if (array_package_ids != NULL)
+ g_ptr_array_unref (array_package_ids);
if (results != NULL)
g_object_unref (results);
return ret;
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]