[gnome-software] Add a banner designer utility
- From: Richard Hughes <rhughes src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [gnome-software] Add a banner designer utility
- Date: Thu, 11 May 2017 11:34:35 +0000 (UTC)
commit 603ba3923b74399b5b90c2db0f33512f80f55863
Author: Richard Hughes <richard hughsie com>
Date: Wed May 3 19:15:05 2017 +0100
Add a banner designer utility
This allows designers to use just CSS and export an AppStream file.
contrib/gnome-software.spec.in | 8 +-
meson.build | 2 +-
po/POTFILES.in | 2 +
src/gnome-software-editor.gresource.xml | 12 +
src/gnome-software-editor.xml | 66 ++
src/gs-editor.c | 1232 ++++++++++++++++++++++++++++++
src/gs-editor.ui | 697 +++++++++++++++++
src/gs-feature-tile.c | 6 +-
src/gs-upgrade-banner.c | 6 +-
src/gs-upgrade-banner.ui | 4 +-
src/meson.build | 61 ++-
src/org.gnome.Software.Editor.desktop.in | 17 +
12 files changed, 2101 insertions(+), 12 deletions(-)
---
diff --git a/contrib/gnome-software.spec.in b/contrib/gnome-software.spec.in
index 0b7600e..49467f7 100644
--- a/contrib/gnome-software.spec.in
+++ b/contrib/gnome-software.spec.in
@@ -142,7 +142,8 @@ glib-compile-schemas %{_datadir}/glib-2.0/schemas &> /dev/null || :
%doc AUTHORS README
%license COPYING
%{_bindir}/gnome-software
-%{_datadir}/applications/*.desktop
+%{_datadir}/applications/gnome-software-local-file.desktop
+%{_datadir}/applications/org.gnome.Software.desktop
%dir %{_datadir}/gnome-software
%{_datadir}/gnome-software/*.png
%{_datadir}/appdata/*.appdata.xml
@@ -207,6 +208,11 @@ glib-compile-schemas %{_datadir}/glib-2.0/schemas &> /dev/null || :
%{_libexecdir}/gnome-software-cmd
%{_libexecdir}/gnome-software-restarter
+# optional editor
+%{_datadir}/applications/org.gnome.Software.Editor.desktop
+%{_bindir}/gnome-software-editor
+%{_mandir}/man1/gnome-software-editor.1.gz
+
%files devel
%{_libdir}/pkgconfig/gnome-software.pc
%dir %{_includedir}/gnome-software
diff --git a/meson.build b/meson.build
index 4e27615..132b659 100644
--- a/meson.build
+++ b/meson.build
@@ -89,7 +89,7 @@ add_global_link_arguments(
language: 'c'
)
-appstream_glib = dependency('appstream-glib', version : '>= 0.6.5')
+appstream_glib = dependency('appstream-glib', version : '>= 0.6.13')
gdk_pixbuf = dependency('gdk-pixbuf-2.0', version : '>= 2.31.5')
gio_unix = dependency('gio-unix-2.0')
gmodule = dependency('gmodule-2.0')
diff --git a/po/POTFILES.in b/po/POTFILES.in
index a18019f..a0726e1 100644
--- a/po/POTFILES.in
+++ b/po/POTFILES.in
@@ -20,6 +20,8 @@ src/gs-content-rating.c
src/gs-dbus-helper.c
src/gs-details-page.c
src/gs-details-page.ui
+src/gs-editor.c
+src/gs-editor.ui
src/gs-extras-page.c
src/gs-extras-page.ui
src/gs-feature-tile.c
diff --git a/src/gnome-software-editor.gresource.xml b/src/gnome-software-editor.gresource.xml
new file mode 100644
index 0000000..508aa9f
--- /dev/null
+++ b/src/gnome-software-editor.gresource.xml
@@ -0,0 +1,12 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<gresources>
+ <gresource prefix="/org/gnome/Software/Editor">
+ <file preprocess="xml-stripblanks">gs-editor.ui</file>
+ </gresource>
+ <gresource prefix="/org/gnome/Software">
+ <file preprocess="xml-stripblanks">gs-feature-tile.ui</file>
+ <file preprocess="xml-stripblanks">gs-summary-tile.ui</file>
+ <file preprocess="xml-stripblanks">gs-upgrade-banner.ui</file>
+ <file preprocess="xml-stripblanks">gs-star-widget.ui</file>
+ </gresource>
+</gresources>
diff --git a/src/gnome-software-editor.xml b/src/gnome-software-editor.xml
new file mode 100644
index 0000000..ad7ddc4
--- /dev/null
+++ b/src/gnome-software-editor.xml
@@ -0,0 +1,66 @@
+<?xml version='1.0'?>
+<!DOCTYPE refentry PUBLIC "-//OASIS//DTD DocBook XML V4.2//EN"
+ "http://www.oasis-open.org/docbook/xml/4.2/docbookx.dtd">
+
+<refentry id="gnome-software">
+
+ <refentryinfo>
+ <title>gnome-software-editor</title>
+ <productname>GNOME</productname>
+ <author>
+ <contrib>Maintainer</contrib>
+ <firstname>Richard</firstname>
+ <surname>Hughes</surname>
+ <email>richard hughsie com</email>
+ </author>
+ <copyright>
+ <year>2017</year>
+ <holder>Richard Hughes</holder>
+ </copyright>
+ </refentryinfo>
+
+ <refmeta>
+ <refentrytitle>gnome-software-editor</refentrytitle>
+ <manvolnum>1</manvolnum>
+ <refmiscinfo class="manual">User Commands</refmiscinfo>
+ </refmeta>
+
+ <refnamediv>
+ <refname>gnome-software-editor</refname>
+ <refpurpose>Design the featured banners for GNOME Software</refpurpose>
+ </refnamediv>
+
+ <refsynopsisdiv>
+ <cmdsynopsis>
+ <command>gnome-software-editor</command>
+ <arg choice="opt" rep="repeat">OPTION</arg>
+ </cmdsynopsis>
+ </refsynopsisdiv>
+
+ <refsect1>
+ <title>Description</title>
+ <para>
+ This manual page documents briefly the <command>gnome-software-editor</command> command.
+ </para>
+ <para>
+ <command>gnome-software-editor</command> allows you to design featured
+ banners for the GNOME Software overview page.
+ </para>
+ </refsect1>
+
+ <refsect1>
+ <title>Options</title>
+ <variablelist>
+ <varlistentry>
+ <term><option>-?</option>, <option>--help</option></term>
+ <listitem><para>Prints a short help text and exits.</para></listitem>
+ </varlistentry>
+ </variablelist>
+ </refsect1>
+
+ <refsect1>
+ <title>Author</title>
+ <para>This manual page was written by Richard Hughes <email>richard hughsie com</email>.
+ </para>
+ </refsect1>
+</refentry>
diff --git a/src/gs-editor.c b/src/gs-editor.c
new file mode 100644
index 0000000..e6dc3a2
--- /dev/null
+++ b/src/gs-editor.c
@@ -0,0 +1,1232 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-
+ *
+ * Copyright (C) 2017 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.
+ */
+
+#include "config.h"
+
+#include <glib/gi18n.h>
+#include <gtk/gtk.h>
+#include <locale.h>
+
+#include "gs-common.h"
+#include "gs-feature-tile.h"
+#include "gs-summary-tile.h"
+#include "gs-upgrade-banner.h"
+
+typedef struct {
+ GCancellable *cancellable;
+ GtkApplication *application;
+ GtkBuilder *builder;
+ GtkWidget *featured_tile1;
+ GtkWidget *upgrade_banner;
+ AsStore *store;
+ AsStore *store_global;
+ AsApp *selected_item;
+ AsApp *deleted_item;
+ gboolean is_in_refresh;
+ gboolean pending_changes;
+ guint refresh_details_delayed_id;
+} GsEditor;
+
+static gchar *
+gs_editor_css_download_resources (GsEditor *self, const gchar *css, GError **error)
+{
+ g_autoptr(GsPlugin) plugin = NULL;
+ g_autoptr(GString) css2 = NULL;
+ g_autoptr(SoupSession) soup_session = NULL;
+
+ /* replace keywords */
+ css2 = g_string_new (css);
+ as_utils_string_replace (css2, "@datadir@", DATADIR);
+
+ /* make remote URIs local */
+ plugin = gs_plugin_new ();
+ gs_plugin_set_name (plugin, "editor");
+ soup_session = soup_session_new_with_options (SOUP_SESSION_USER_AGENT, gs_user_agent (),
+ SOUP_SESSION_TIMEOUT, 10,
+ NULL);
+ gs_plugin_set_soup_session (plugin, soup_session);
+ return gs_plugin_download_rewrite_resource (plugin, css2->str, NULL, error);
+}
+
+typedef struct {
+ GsEditor *self;
+ GError **error;
+} GsDesignErrorHelper;
+
+static void
+gs_design_css_parsing_error_cb (GtkCssProvider *provider,
+ GtkCssSection *section,
+ GError *error,
+ gpointer user_data)
+{
+ GsDesignErrorHelper *helper = (GsDesignErrorHelper *) user_data;
+ if (*(helper->error) != NULL) {
+ g_warning ("ignoring parse error %u:%u: %s",
+ gtk_css_section_get_start_line (section),
+ gtk_css_section_get_start_position (section),
+ error->message);
+ return;
+ }
+ *(helper->error) = g_error_copy (error);
+}
+
+static gboolean
+gs_design_validate_css (GsEditor *self, const gchar *css, GError **error)
+{
+ GsDesignErrorHelper helper;
+ g_autofree gchar *css_new = NULL;
+ g_autoptr(GString) str = NULL;
+ g_autoptr(GtkCssProvider) provider = NULL;
+
+ /* nothing set */
+ if (css == NULL)
+ return TRUE;
+
+ /* remove custom class if NULL */
+ str = g_string_new (NULL);
+ g_string_append (str, ".themed-widget {");
+ css_new = gs_editor_css_download_resources (self, css, error);
+ if (css_new == NULL)
+ return FALSE;
+ g_string_append (str, css_new);
+ g_string_append (str, "}");
+
+ /* set up custom provider */
+ helper.self = self;
+ helper.error = error;
+ provider = gtk_css_provider_new ();
+ g_signal_connect (provider, "parsing-error",
+ G_CALLBACK (gs_design_css_parsing_error_cb), &helper);
+ gtk_style_context_add_provider_for_screen (gdk_screen_get_default (),
+ GTK_STYLE_PROVIDER (provider),
+ GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);
+ gtk_css_provider_load_from_data (provider, str->str, -1, NULL);
+ return *(helper.error) == NULL;
+}
+
+static void
+gs_editor_refine_app_pixbuf (GsApp *app)
+{
+ GPtrArray *icons;
+ if (gs_app_get_pixbuf (app) != NULL)
+ return;
+ icons = gs_app_get_icons (app);
+ for (guint i = 0; i < icons->len; i++) {
+ AsIcon *ic = g_ptr_array_index (icons, i);
+ g_autoptr(GError) error = NULL;
+ if (as_icon_get_kind (ic) == AS_ICON_KIND_STOCK) {
+
+ g_autoptr(GdkPixbuf) pb = NULL;
+ pb = gtk_icon_theme_load_icon (gtk_icon_theme_get_default (),
+ as_icon_get_name (ic),
+ 64,
+ GTK_ICON_LOOKUP_FORCE_SIZE,
+ &error);
+ if (pb == NULL) {
+ g_warning ("failed to load icon: %s", error->message);
+ continue;
+ }
+ gs_app_set_pixbuf (app, pb);
+ } else {
+ if (!as_icon_load (ic, AS_ICON_LOAD_FLAG_SEARCH_SIZE, &error)) {
+ g_warning ("failed to load icon: %s", error->message);
+ continue;
+ }
+ gs_app_set_pixbuf (app, as_icon_get_pixbuf (ic));
+ }
+ break;
+ }
+}
+
+static GsApp *
+gs_editor_convert_app (GsEditor *self, AsApp *item)
+{
+ AsApp *item_global;
+ AsAppState item_state;
+ GsApp *app;
+ const gchar *keys[] = {
+ "GnomeSoftware::AppTile-css",
+ "GnomeSoftware::FeatureTile-css",
+ "GnomeSoftware::UpgradeBanner-css",
+ NULL };
+
+ /* copy name, summary and description */
+ app = gs_app_new (as_app_get_id (item));
+ item_global = as_store_get_app_by_id (self->store_global, as_app_get_id (item));
+ if (item_global == NULL) {
+ const gchar *tmp;
+ g_autoptr(AsIcon) ic = NULL;
+ g_debug ("no app found for %s, using fallback", as_app_get_id (item));
+
+ /* copy from AsApp, falling back to something sane */
+ tmp = as_app_get_name (item, NULL);
+ if (tmp == NULL)
+ tmp = "Application";
+ gs_app_set_name (app, GS_APP_QUALITY_NORMAL, tmp);
+ tmp = as_app_get_comment (item, NULL);
+ if (tmp == NULL)
+ tmp = "Description";
+ gs_app_set_summary (app, GS_APP_QUALITY_NORMAL, tmp);
+ tmp = as_app_get_description (item, NULL);
+ if (tmp == NULL)
+ tmp = "A multiline description";
+ gs_app_set_description (app, GS_APP_QUALITY_NORMAL, tmp);
+ ic = as_icon_new ();
+ as_icon_set_kind (ic, AS_ICON_KIND_STOCK);
+ as_icon_set_name (ic, "application-x-executable");
+ gs_app_add_icon (app, ic);
+ item_state = as_app_get_state (item);
+ } else {
+ GPtrArray *icons;
+ g_debug ("found global app for %s", as_app_get_id (item));
+ gs_app_set_name (app, GS_APP_QUALITY_NORMAL,
+ as_app_get_name (item_global, NULL));
+ gs_app_set_summary (app, GS_APP_QUALITY_NORMAL,
+ as_app_get_comment (item_global, NULL));
+ gs_app_set_description (app, GS_APP_QUALITY_NORMAL,
+ as_app_get_description (item_global, NULL));
+ icons = as_app_get_icons (item_global);
+ for (guint i = 0; i < icons->len; i++) {
+ AsIcon *icon = g_ptr_array_index (icons, i);
+ gs_app_add_icon (app, icon);
+ }
+ item_state = as_app_get_state (item_global);
+ }
+
+ /* copy state */
+ if (item_state == AS_APP_STATE_UNKNOWN)
+ item_state = AS_APP_STATE_AVAILABLE;
+ gs_app_set_state (app, item_state);
+
+ /* copy version */
+ gs_app_set_version (app, "3.28");
+
+ /* load pixbuf */
+ gs_editor_refine_app_pixbuf (app);
+
+ /* copy metadata */
+ for (guint i = 0; keys[i] != NULL; i++) {
+ g_autoptr(GError) error = NULL;
+ const gchar *css = as_app_get_metadata_item (item, keys[i]);
+ if (css != NULL) {
+ g_autofree gchar *css_new = NULL;
+ css_new = gs_editor_css_download_resources (self, css, &error);
+ if (css_new == NULL) {
+ g_warning ("%s", error->message);
+ gs_app_set_metadata (app, keys[i], css);
+ } else {
+ gs_app_set_metadata (app, keys[i], css_new);
+ }
+ } else {
+ gs_app_set_metadata (app, keys[i], NULL);
+ }
+ }
+ return app;
+}
+
+static void
+gs_editor_refresh_details (GsEditor *self)
+{
+ AsAppKind app_kind = AS_APP_KIND_UNKNOWN;
+ GtkWidget *widget;
+ const gchar *css = NULL;
+ g_autoptr(GError) error = NULL;
+ g_autoptr(GsApp) app = NULL;
+
+ /* ignore changed events */
+ self->is_in_refresh = TRUE;
+
+ /* create a GsApp for the AsApp */
+ if (self->selected_item != NULL) {
+ app = gs_editor_convert_app (self, self->selected_item);
+ g_debug ("refreshing details for %s", gs_app_get_id (app));
+ }
+
+ /* get kind */
+ if (self->selected_item != NULL)
+ app_kind = as_app_get_kind (self->selected_item);
+
+ /* feature tiles */
+ if (app_kind != AS_APP_KIND_OS_UPGRADE) {
+ if (self->selected_item != NULL) {
+ gs_app_tile_set_app (GS_APP_TILE (self->featured_tile1), app);
+ gtk_widget_set_sensitive (self->featured_tile1, TRUE);
+ } else {
+ gtk_widget_set_sensitive (self->featured_tile1, FALSE);
+ }
+ gtk_widget_set_visible (self->featured_tile1, TRUE);
+ } else {
+ gtk_widget_set_visible (self->featured_tile1, FALSE);
+ }
+
+ /* upgrade banner */
+ if (app_kind == AS_APP_KIND_OS_UPGRADE) {
+ if (self->selected_item != NULL) {
+ gs_upgrade_banner_set_app (GS_UPGRADE_BANNER (self->upgrade_banner), app);
+ gtk_widget_set_sensitive (self->upgrade_banner, TRUE);
+ } else {
+ gtk_widget_set_sensitive (self->upgrade_banner, FALSE);
+ }
+ gtk_widget_set_visible (self->upgrade_banner, TRUE);
+ } else {
+ gtk_widget_set_visible (self->upgrade_banner, FALSE);
+ }
+
+ /* name */
+ widget = GTK_WIDGET (gtk_builder_get_object (self->builder, "box_name"));
+ if (self->selected_item != NULL) {
+ const gchar *tmp;
+ gtk_widget_set_visible (widget, app_kind == AS_APP_KIND_OS_UPGRADE);
+ widget = GTK_WIDGET (gtk_builder_get_object (self->builder, "entry_name"));
+ tmp = as_app_get_name (self->selected_item, NULL);
+ if (tmp != NULL)
+ gtk_entry_set_text (GTK_ENTRY (widget), tmp);
+ } else {
+ gtk_widget_set_visible (widget, FALSE);
+ }
+
+ /* summary */
+ widget = GTK_WIDGET (gtk_builder_get_object (self->builder, "box_summary"));
+ if (self->selected_item != NULL) {
+ const gchar *tmp;
+ gtk_widget_set_visible (widget, app_kind == AS_APP_KIND_OS_UPGRADE);
+ widget = GTK_WIDGET (gtk_builder_get_object (self->builder, "entry_summary"));
+ tmp = as_app_get_comment (self->selected_item, NULL);
+ if (tmp != NULL)
+ gtk_entry_set_text (GTK_ENTRY (widget), tmp);
+ } else {
+ gtk_widget_set_visible (widget, FALSE);
+ }
+
+ /* kudos */
+ widget = GTK_WIDGET (gtk_builder_get_object (self->builder, "box_kudos"));
+ if (self->selected_item != NULL) {
+ gtk_widget_set_visible (widget, app_kind != AS_APP_KIND_OS_UPGRADE);
+ } else {
+ gtk_widget_set_visible (widget, TRUE);
+ }
+
+ /* category featured */
+ widget = GTK_WIDGET (gtk_builder_get_object (self->builder, "checkbutton_category_featured"));
+ if (self->selected_item != NULL) {
+ gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (widget),
+ as_app_has_category (self->selected_item,
+ "Featured"));
+ gtk_widget_set_sensitive (widget, TRUE);
+ } else {
+ gtk_widget_set_sensitive (widget, FALSE);
+ }
+
+ /* kudo popular */
+ widget = GTK_WIDGET (gtk_builder_get_object (self->builder, "checkbutton_editors_pick"));
+ if (self->selected_item != NULL) {
+ gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (widget),
+ as_app_has_kudo (self->selected_item,
+ "GnomeSoftware::popular"));
+ gtk_widget_set_sensitive (widget, TRUE);
+ } else {
+ gtk_widget_set_sensitive (widget, FALSE);
+ }
+
+ /* featured */
+ widget = GTK_WIDGET (gtk_builder_get_object (self->builder, "textview_css"));
+ if (self->selected_item != NULL) {
+ GtkTextBuffer *buffer;
+ GtkTextIter iter_end;
+ GtkTextIter iter_start;
+ g_autofree gchar *css_existing = NULL;
+
+ css = as_app_get_metadata_item (self->selected_item,
+ "GnomeSoftware::FeatureTile-css");
+ if (css == NULL)
+ css = "";
+ buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (widget));
+ gtk_text_buffer_get_bounds (buffer, &iter_start, &iter_end);
+ css_existing = gtk_text_buffer_get_text (buffer, &iter_start, &iter_end, FALSE);
+ if (g_strcmp0 (css_existing, css) != 0)
+ gtk_text_buffer_set_text (buffer, css, -1);
+ gtk_widget_set_sensitive (widget, TRUE);
+ } else {
+ gtk_widget_set_sensitive (widget, FALSE);
+ }
+
+ /* desktop ID */
+ widget = GTK_WIDGET (gtk_builder_get_object (self->builder, "entry_desktop_id"));
+ if (self->selected_item != NULL) {
+ const gchar *id = as_app_get_id (self->selected_item);
+ if (id == NULL)
+ id = "";
+ gtk_entry_set_text (GTK_ENTRY (widget), id);
+ gtk_widget_set_sensitive (widget, TRUE);
+ } else {
+ gtk_entry_set_text (GTK_ENTRY (widget), "");
+ gtk_widget_set_sensitive (widget, FALSE);
+ }
+
+ /* validate CSS */
+ if (css == NULL) {
+ widget = GTK_WIDGET (gtk_builder_get_object (self->builder, "label_infobar_css"));
+ gtk_label_set_label (GTK_LABEL (widget), "");
+ widget = GTK_WIDGET (gtk_builder_get_object (self->builder, "infobar_css"));
+ gtk_info_bar_set_message_type (GTK_INFO_BAR (widget), GTK_MESSAGE_OTHER);
+ } else if (!gs_design_validate_css (self, css, &error)) {
+ g_autofree gchar *msg = g_strdup (error->message);
+ g_strdelimit (msg, "\n\r<>", '\0');
+ widget = GTK_WIDGET (gtk_builder_get_object (self->builder, "label_infobar_css"));
+ gtk_label_set_label (GTK_LABEL (widget), msg);
+ widget = GTK_WIDGET (gtk_builder_get_object (self->builder, "infobar_css"));
+ gtk_info_bar_set_message_type (GTK_INFO_BAR (widget), GTK_MESSAGE_WARNING);
+ } else {
+ widget = GTK_WIDGET (gtk_builder_get_object (self->builder, "label_infobar_css"));
+ gtk_label_set_label (GTK_LABEL (widget), _("CSS validated OK!"));
+ widget = GTK_WIDGET (gtk_builder_get_object (self->builder, "infobar_css"));
+ gtk_info_bar_set_message_type (GTK_INFO_BAR (widget), GTK_MESSAGE_OTHER);
+ }
+
+ /* do not ignore changed events */
+ self->is_in_refresh = FALSE;
+}
+
+static gboolean
+gs_design_dialog_refresh_details_delayed_cb (gpointer user_data)
+{
+ GsEditor *self = (GsEditor *) user_data;
+ gs_editor_refresh_details (self);
+ self->refresh_details_delayed_id = 0;
+ return FALSE;
+}
+
+static void
+gs_design_dialog_refresh_details_delayed (GsEditor *self)
+{
+ if (self->refresh_details_delayed_id != 0)
+ g_source_remove (self->refresh_details_delayed_id);
+ self->refresh_details_delayed_id = g_timeout_add (500,
+ gs_design_dialog_refresh_details_delayed_cb, self);
+}
+
+static void
+gs_design_dialog_buffer_changed_cb (GtkTextBuffer *buffer, GsEditor *self)
+{
+ GtkTextIter iter_end;
+ GtkTextIter iter_start;
+ g_autofree gchar *css = NULL;
+
+ /* ignore, self change */
+ if (self->is_in_refresh)
+ return;
+
+ gtk_text_buffer_get_bounds (buffer, &iter_start, &iter_end);
+ css = gtk_text_buffer_get_text (buffer, &iter_start, &iter_end, FALSE);
+ g_debug ("CSS now '%s'", css);
+ as_app_add_metadata (self->selected_item, "GnomeSoftware::FeatureTile-css", NULL);
+ as_app_add_metadata (self->selected_item, "GnomeSoftware::FeatureTile-css", css);
+ self->pending_changes = TRUE;
+ gs_design_dialog_refresh_details_delayed (self);
+}
+
+static void
+gs_editor_set_page (GsEditor *self, const gchar *name)
+{
+ GtkWidget *widget;
+
+ widget = GTK_WIDGET (gtk_builder_get_object (self->builder, "stack_main"));
+ gtk_stack_set_visible_child_name (GTK_STACK (widget), name);
+
+ if (g_strcmp0 (name, "none") == 0) {
+ widget = GTK_WIDGET (gtk_builder_get_object (self->builder, "button_back"));
+ gtk_widget_set_visible (widget, FALSE);
+ widget = GTK_WIDGET (gtk_builder_get_object (self->builder, "button_new"));
+ gtk_widget_set_visible (widget, TRUE);
+ widget = GTK_WIDGET (gtk_builder_get_object (self->builder, "button_import"));
+ gtk_widget_set_visible (widget, TRUE);
+ widget = GTK_WIDGET (gtk_builder_get_object (self->builder, "button_save"));
+ gtk_widget_set_visible (widget, TRUE);
+ widget = GTK_WIDGET (gtk_builder_get_object (self->builder, "button_search"));
+ gtk_widget_set_visible (widget, FALSE);
+ widget = GTK_WIDGET (gtk_builder_get_object (self->builder, "button_remove"));
+ gtk_widget_set_visible (widget, FALSE);
+
+ } else if (g_strcmp0 (name, "choice") == 0) {
+ widget = GTK_WIDGET (gtk_builder_get_object (self->builder, "button_back"));
+ gtk_widget_set_visible (widget, FALSE);
+ widget = GTK_WIDGET (gtk_builder_get_object (self->builder, "button_new"));
+ gtk_widget_set_visible (widget, TRUE);
+ widget = GTK_WIDGET (gtk_builder_get_object (self->builder, "button_import"));
+ gtk_widget_set_visible (widget, TRUE);
+ widget = GTK_WIDGET (gtk_builder_get_object (self->builder, "button_save"));
+ gtk_widget_set_visible (widget, TRUE);
+ widget = GTK_WIDGET (gtk_builder_get_object (self->builder, "button_search"));
+ gtk_widget_set_visible (widget, TRUE);
+ widget = GTK_WIDGET (gtk_builder_get_object (self->builder, "button_remove"));
+ gtk_widget_set_visible (widget, FALSE);
+
+ } else if (g_strcmp0 (name, "details") == 0) {
+ widget = GTK_WIDGET (gtk_builder_get_object (self->builder, "button_back"));
+ gtk_widget_set_visible (widget, TRUE);
+ widget = GTK_WIDGET (gtk_builder_get_object (self->builder, "button_new"));
+ gtk_widget_set_visible (widget, FALSE);
+ widget = GTK_WIDGET (gtk_builder_get_object (self->builder, "button_import"));
+ gtk_widget_set_visible (widget, FALSE);
+ widget = GTK_WIDGET (gtk_builder_get_object (self->builder, "button_save"));
+ gtk_widget_set_visible (widget, FALSE);
+ widget = GTK_WIDGET (gtk_builder_get_object (self->builder, "button_search"));
+ gtk_widget_set_visible (widget, FALSE);
+ widget = GTK_WIDGET (gtk_builder_get_object (self->builder, "button_remove"));
+ gtk_widget_set_visible (widget, TRUE);
+ }
+}
+
+static void
+gs_editor_app_tile_clicked_cb (GsAppTile *tile, GsEditor *self)
+{
+ GsApp *app = gs_app_tile_get_app (tile);
+ AsApp *item = as_store_get_app_by_id (self->store, gs_app_get_id (app));
+ if (item == NULL) {
+ g_warning ("failed to find %s", gs_app_get_id (app));
+ return;
+ }
+ g_set_object (&self->selected_item, item);
+
+ gs_editor_refresh_details (self);
+ gs_editor_set_page (self, "details");
+}
+
+static void
+gs_editor_refresh_choice (GsEditor *self)
+{
+ GPtrArray *apps;
+ GtkContainer *container;
+
+ /* add all apps */
+ container = GTK_CONTAINER (gtk_builder_get_object (self->builder,
+ "flowbox_main"));
+ gs_container_remove_all (GTK_CONTAINER (container));
+ apps = as_store_get_apps (self->store);
+ for (guint i = 0; i < apps->len; i++) {
+ AsApp *item = g_ptr_array_index (apps, i);
+ GtkWidget *tile = NULL;
+ g_autoptr(GsApp) app = NULL;
+
+ app = gs_editor_convert_app (self, item);
+ tile = gs_summary_tile_new (app);
+ g_signal_connect (tile, "clicked",
+ G_CALLBACK (gs_editor_app_tile_clicked_cb),
+ self);
+ gtk_widget_set_visible (tile, TRUE);
+ gtk_widget_set_vexpand (tile, FALSE);
+ gtk_widget_set_hexpand (tile, FALSE);
+ gtk_widget_set_size_request (tile, 300, 50);
+ gtk_widget_set_valign (tile, GTK_ALIGN_START);
+ gtk_container_add (GTK_CONTAINER (container), tile);
+ }
+}
+
+static void
+gs_editor_error_message (GsEditor *self, const gchar *title, const gchar *message)
+{
+ GtkWidget *dialog;
+ GtkWindow *window;
+ window = GTK_WINDOW (gtk_builder_get_object (self->builder, "window_main"));
+ dialog = gtk_message_dialog_new (window,
+ GTK_DIALOG_MODAL |
+ GTK_DIALOG_DESTROY_WITH_PARENT,
+ GTK_MESSAGE_WARNING,
+ GTK_BUTTONS_OK,
+ "%s", title);
+ gtk_message_dialog_format_secondary_text (GTK_MESSAGE_DIALOG (dialog),
+ "%s", message);
+ gtk_dialog_run (GTK_DIALOG (dialog));
+ gtk_widget_destroy (dialog);
+}
+
+static void
+gs_editor_button_back_clicked_cb (GtkWidget *widget, GsEditor *self)
+{
+ gs_editor_set_page (self, as_store_get_size (self->store) == 0 ? "none" : "choice");
+}
+
+static void
+gs_editor_button_menu_clicked_cb (GtkWidget *widget, GsEditor *self)
+{
+ widget = GTK_WIDGET (gtk_builder_get_object (self->builder, "popover_menu"));
+ gtk_popover_popup (GTK_POPOVER (widget));
+}
+
+static void
+gs_editor_refresh_file (GsEditor *self, GFile *file)
+{
+ GtkWidget *widget;
+
+ /* set subtitle */
+ widget = GTK_WIDGET (gtk_builder_get_object (self->builder, "headerbar_main"));
+ if (file != NULL) {
+ g_autofree gchar *basename = g_file_get_basename (file);
+ gtk_header_bar_set_subtitle (GTK_HEADER_BAR (widget), basename);
+ } else {
+ gtk_header_bar_set_subtitle (GTK_HEADER_BAR (widget), NULL);
+ }
+}
+
+static void
+gs_editor_button_import_file (GsEditor *self, GFile *file)
+{
+ g_autoptr(GError) error = NULL;
+
+ /* load new file */
+ if (!as_store_from_file (self->store, file, NULL, NULL, &error)) {
+ /* TRANSLATORS: error dialog title */
+ gs_editor_error_message (self, _("Failed to load file"), error->message);
+ return;
+ }
+
+ /* update listview */
+ gs_editor_refresh_choice (self);
+ gs_editor_refresh_file (self, file);
+
+ /* set the appropriate page */
+ gs_editor_set_page (self, as_store_get_size (self->store) == 0 ? "none" : "choice");
+
+ /* reset */
+ self->pending_changes = FALSE;
+}
+
+static gchar *
+gs_editor_get_default_save_location (void)
+{
+ gchar *xmlfn = g_build_filename (g_get_user_data_dir (),
+ "app-info",
+ "xmls",
+ NULL);
+ g_mkdir_with_parents (xmlfn, 0777);
+ return xmlfn;
+}
+
+static void
+gs_editor_button_import_clicked_cb (GtkApplication *application, GsEditor *self)
+{
+ GtkFileFilter *filter;
+ GtkWindow *window;
+ GtkWidget *dialog;
+ gint res;
+ g_autoptr(GFile) file = NULL;
+ g_autofree gchar *appinfo_xml = NULL;
+
+ /* import warning */
+ window = GTK_WINDOW (gtk_builder_get_object (self->builder,
+ "window_main"));
+ if (as_store_get_size (self->store) > 0) {
+ dialog = gtk_message_dialog_new (window,
+ GTK_DIALOG_MODAL |
+ GTK_DIALOG_DESTROY_WITH_PARENT,
+ GTK_MESSAGE_WARNING,
+ GTK_BUTTONS_CANCEL,
+ /* TRANSLATORS: window title */
+ _("Unsaved changes"));
+ gtk_message_dialog_format_secondary_text (GTK_MESSAGE_DIALOG (dialog),
+ _("The application list is already loaded."));
+
+ gtk_dialog_add_button (GTK_DIALOG (dialog),
+ /* TRANSLATORS: button text */
+ _("Merge documents"),
+ GTK_RESPONSE_ACCEPT);
+ gtk_dialog_add_button (GTK_DIALOG (dialog),
+ /* TRANSLATORS: button text */
+ _("Throw away changes"),
+ GTK_RESPONSE_YES);
+ res = gtk_dialog_run (GTK_DIALOG (dialog));
+ gtk_widget_destroy (dialog);
+
+ if (res == GTK_RESPONSE_CANCEL)
+ return;
+ if (res == GTK_RESPONSE_YES)
+ as_store_remove_all (self->store);
+ }
+
+ /* import the new file */
+ dialog = gtk_file_chooser_dialog_new (_("Open AppStream File"),
+ window,
+ GTK_FILE_CHOOSER_ACTION_OPEN,
+ _("_Cancel"), GTK_RESPONSE_CANCEL,
+ _("_Open"), GTK_RESPONSE_ACCEPT,
+ NULL);
+ filter = gtk_file_filter_new ();
+ gtk_file_filter_add_pattern (filter, "*.xml");
+ gtk_file_chooser_set_filter (GTK_FILE_CHOOSER (dialog), filter);
+ appinfo_xml = gs_editor_get_default_save_location ();
+ gtk_file_chooser_set_current_folder (GTK_FILE_CHOOSER (dialog), appinfo_xml);
+ res = gtk_dialog_run (GTK_DIALOG (dialog));
+ if (res != GTK_RESPONSE_ACCEPT) {
+ gtk_widget_destroy (dialog);
+ return;
+ }
+ file = gtk_file_chooser_get_file (GTK_FILE_CHOOSER (dialog));
+ gs_editor_button_import_file (self, file);
+ gtk_widget_destroy (dialog);
+}
+
+static void
+gs_editor_button_save_clicked_cb (GtkApplication *application, GsEditor *self)
+{
+ GtkFileFilter *filter;
+ GtkWidget *dialog;
+ GtkWindow *window;
+ gint res;
+ g_autofree gchar *appinfo_xml = NULL;
+ g_autoptr(GError) error = NULL;
+ g_autoptr(GFile) file = NULL;
+
+ /* export a new file */
+ window = GTK_WINDOW (gtk_builder_get_object (self->builder,
+ "window_main"));
+ dialog = gtk_file_chooser_dialog_new (_("Open AppStream File"),
+ window,
+ GTK_FILE_CHOOSER_ACTION_SAVE,
+ _("_Cancel"), GTK_RESPONSE_CANCEL,
+ _("_Save"), GTK_RESPONSE_ACCEPT,
+ NULL);
+ filter = gtk_file_filter_new ();
+ gtk_file_filter_add_pattern (filter, "*.xml");
+ gtk_file_chooser_set_filter (GTK_FILE_CHOOSER (dialog), filter);
+ appinfo_xml = gs_editor_get_default_save_location ();
+ gtk_file_chooser_set_current_folder (GTK_FILE_CHOOSER (dialog), appinfo_xml);
+ res = gtk_dialog_run (GTK_DIALOG (dialog));
+ if (res != GTK_RESPONSE_ACCEPT) {
+ gtk_widget_destroy (dialog);
+ return;
+ }
+ file = gtk_file_chooser_get_file (GTK_FILE_CHOOSER (dialog));
+ gtk_widget_destroy (dialog);
+ if (!as_store_to_file (self->store,
+ file,
+ AS_NODE_TO_XML_FLAG_ADD_HEADER |
+ AS_NODE_TO_XML_FLAG_FORMAT_MULTILINE |
+ AS_NODE_TO_XML_FLAG_FORMAT_INDENT,
+ self->cancellable,
+ &error)) {
+ /* TRANSLATORS: error dialog title */
+ gs_editor_error_message (self, _("Failed to save file"), error->message);
+ return;
+ }
+ self->pending_changes = FALSE;
+ gs_editor_refresh_file (self, file);
+ gs_editor_refresh_details (self);
+}
+
+static void
+gs_editor_show_notification (GsEditor *self, const gchar *text)
+{
+ GtkWidget *widget;
+
+ /* set text */
+ widget = GTK_WIDGET (gtk_builder_get_object (self->builder, "label_notification"));
+ gtk_label_set_markup (GTK_LABEL (widget), text);
+
+ /* show button: FIXME, use flags? */
+ widget = GTK_WIDGET (gtk_builder_get_object (self->builder, "button_notification_undo_remove"));
+ gtk_widget_set_visible (widget, TRUE);
+
+ /* show revealer */
+ widget = GTK_WIDGET (gtk_builder_get_object (self->builder, "revealer_notification"));
+ gtk_revealer_set_reveal_child (GTK_REVEALER (widget), TRUE);
+}
+
+static void
+gs_editor_button_notification_dismiss_clicked_cb (GtkWidget *widget, GsEditor *self)
+{
+ widget = GTK_WIDGET (gtk_builder_get_object (self->builder, "revealer_notification"));
+ gtk_revealer_set_reveal_child (GTK_REVEALER (widget), FALSE);
+}
+
+static void
+gs_editor_button_undo_remove_clicked_cb (GtkWidget *widget, GsEditor *self)
+{
+ if (self->deleted_item == NULL)
+ return;
+
+ /* add this back to the store and set it as current */
+ as_store_add_app (self->store, self->deleted_item);
+ g_set_object (&self->selected_item, self->deleted_item);
+ g_clear_object (&self->deleted_item);
+
+ /* hide notification */
+ widget = GTK_WIDGET (gtk_builder_get_object (self->builder, "revealer_notification"));
+ gtk_revealer_set_reveal_child (GTK_REVEALER (widget), FALSE);
+
+ self->pending_changes = TRUE;
+ gs_editor_refresh_choice (self);
+ gs_editor_refresh_details (self);
+ gs_editor_set_page (self, "details");
+}
+
+static void
+gs_editor_button_remove_clicked_cb (GtkWidget *widget, GsEditor *self)
+{
+ const gchar *name;
+ g_autofree gchar *msg = NULL;
+
+ if (self->selected_item == NULL)
+ return;
+
+ /* send notification */
+ name = as_app_get_name (self->selected_item, NULL);
+ if (name == NULL) {
+ AsApp *item_global = as_store_get_app_by_id (self->store_global,
+ as_app_get_id (self->selected_item));
+ if (item_global != NULL)
+ name = as_app_get_name (item_global, NULL);
+ }
+ if (name != NULL) {
+ msg = g_strdup_printf ("<b>%s</b> %s", name,
+ /* TRANSLATORS, this is prefixed with the
+ * app name, e.g. 'Inkscape ' */
+ _("banner design deleted."));
+ } else {
+ /* TRANSLATORS, this is a notification */
+ msg = g_strdup (_("Banner design deleted."));
+ }
+ gs_editor_show_notification (self, msg);
+
+ /* save this so we can undo */
+ g_set_object (&self->deleted_item, self->selected_item);
+
+ as_store_remove_app_by_id (self->store, as_app_get_id (self->selected_item));
+ self->pending_changes = TRUE;
+ gs_editor_refresh_choice (self);
+
+ /* set the appropriate page */
+ gs_editor_set_page (self, as_store_get_size (self->store) == 0 ? "none" : "choice");
+}
+
+static void
+gs_editor_checkbutton_editors_pick_cb (GtkToggleButton *widget, GsEditor *self)
+{
+ /* ignore, self change */
+ if (self->is_in_refresh)
+ return;
+ if (self->selected_item == NULL)
+ return;
+
+ if (gtk_toggle_button_get_active (widget)) {
+ as_app_add_kudo (self->selected_item, "GnomeSoftware::popular");
+ } else {
+ as_app_remove_kudo (self->selected_item, "GnomeSoftware::popular");
+ }
+ self->pending_changes = TRUE;
+ gs_editor_refresh_details (self);
+}
+
+static void
+gs_editor_checkbutton_category_featured_cb (GtkToggleButton *widget, GsEditor *self)
+{
+ /* ignore, self change */
+ if (self->is_in_refresh)
+ return;
+ if (self->selected_item == NULL)
+ return;
+
+ if (gtk_toggle_button_get_active (widget)) {
+ as_app_add_category (self->selected_item, "Featured");
+ } else {
+ as_app_remove_category (self->selected_item, "Featured");
+ }
+ self->pending_changes = TRUE;
+ gs_editor_refresh_details (self);
+}
+
+static void
+gs_editor_entry_desktop_id_notify_cb (GtkEntry *entry, GParamSpec *pspec, GsEditor *self)
+{
+ /* ignore, self change */
+ if (self->is_in_refresh)
+ return;
+ if (self->selected_item == NULL)
+ return;
+
+ /* check the name does not already exist */
+ //FIXME
+
+ as_store_remove_app (self->store, self->selected_item);
+ as_app_set_id (self->selected_item, gtk_entry_get_text (entry));
+ as_store_add_app (self->store, self->selected_item);
+
+ self->pending_changes = TRUE;
+ gs_editor_refresh_choice (self);
+ gs_editor_refresh_details (self);
+}
+
+static void
+gs_editor_entry_name_notify_cb (GtkEntry *entry, GParamSpec *pspec, GsEditor *self)
+{
+ /* ignore, self change */
+ if (self->is_in_refresh)
+ return;
+ if (self->selected_item == NULL)
+ return;
+
+ as_app_set_name (self->selected_item, NULL, gtk_entry_get_text (entry));
+
+ self->pending_changes = TRUE;
+ gs_editor_refresh_choice (self);
+ gs_editor_refresh_details (self);
+}
+
+static void
+gs_editor_entry_summary_notify_cb (GtkEntry *entry, GParamSpec *pspec, GsEditor *self)
+{
+ /* ignore, self change */
+ if (self->is_in_refresh)
+ return;
+ if (self->selected_item == NULL)
+ return;
+
+ as_app_set_comment (self->selected_item, NULL, gtk_entry_get_text (entry));
+
+ self->pending_changes = TRUE;
+ gs_editor_refresh_choice (self);
+ gs_editor_refresh_details (self);
+}
+
+static gboolean
+gs_editor_delete_event_cb (GtkWindow *window, GdkEvent *event, GsEditor *self)
+{
+ GtkWidget *dialog;
+ gint res;
+
+ if (!self->pending_changes)
+ return FALSE;
+
+ /* ask for confirmation */
+ dialog = gtk_message_dialog_new (window,
+ GTK_DIALOG_MODAL |
+ GTK_DIALOG_DESTROY_WITH_PARENT,
+ GTK_MESSAGE_WARNING,
+ GTK_BUTTONS_CANCEL,
+ /* TRANSLATORS: window title */
+ _("Unsaved changes"));
+ gtk_message_dialog_format_secondary_text (GTK_MESSAGE_DIALOG (dialog),
+ _("The application list has unsaved changes."));
+ gtk_dialog_add_button (GTK_DIALOG (dialog),
+ /* TRANSLATORS: button text */
+ _("Throw away changes"),
+ GTK_RESPONSE_CLOSE);
+ res = gtk_dialog_run (GTK_DIALOG (dialog));
+ gtk_widget_destroy (dialog);
+ if (res == GTK_RESPONSE_CLOSE)
+ return FALSE;
+ return TRUE;
+}
+
+static gint
+gs_editor_flow_box_sort_cb (GtkFlowBoxChild *row1, GtkFlowBoxChild *row2, gpointer user_data)
+{
+ GsAppTile *tile1 = GS_APP_TILE (gtk_bin_get_child (GTK_BIN (row1)));
+ GsAppTile *tile2 = GS_APP_TILE (gtk_bin_get_child (GTK_BIN (row2)));
+ return g_strcmp0 (gs_app_get_name (gs_app_tile_get_app (tile1)),
+ gs_app_get_name (gs_app_tile_get_app (tile2)));
+}
+
+static void
+gs_editor_load_completion_model (GsEditor *self)
+{
+ GPtrArray *apps;
+ GtkListStore *store;
+ GtkTreeIter iter;
+
+ store = GTK_LIST_STORE (gtk_builder_get_object (self->builder, "liststore_ids"));
+ apps = as_store_get_apps (self->store_global);
+ for (guint i = 0; i < apps->len; i++) {
+ AsApp *item = g_ptr_array_index (apps, i);
+ gtk_list_store_append (store, &iter);
+ gtk_list_store_set (store, &iter, 0, as_app_get_id (item), -1);
+ }
+}
+
+static void
+gs_editor_button_new_feature_clicked_cb (GtkApplication *application, GsEditor *self)
+{
+ g_autofree gchar *id = NULL;
+ g_autoptr(AsApp) item = as_app_new ();
+ const gchar *css = "border: 1px solid #808080;\nbackground: #eee;\ncolor: #000;";
+
+ /* add new app */
+ as_app_set_kind (item, AS_APP_KIND_DESKTOP);
+ id = g_strdup_printf ("example-%04x.desktop",
+ (guint) g_random_int_range (0x0000, 0xffff));
+ as_app_set_id (item, id);
+ as_app_add_metadata (item, "GnomeSoftware::FeatureTile-css", css);
+ as_app_add_kudo (item, "GnomeSoftware::popular");
+ as_app_add_category (item, "Featured");
+ as_store_add_app (self->store, item);
+ g_set_object (&self->selected_item, item);
+
+ self->pending_changes = TRUE;
+ gs_editor_refresh_choice (self);
+ gs_editor_refresh_details (self);
+ gs_editor_set_page (self, "details");
+}
+
+static void
+gs_editor_button_new_os_upgrade_clicked_cb (GtkApplication *application, GsEditor *self)
+{
+ g_autofree gchar *id = NULL;
+ g_autoptr(AsApp) item = as_app_new ();
+ const gchar *css = "border: 1px solid #808080;\nbackground: #fffeee;\ncolor: #000;";
+
+ /* add new app */
+ as_app_set_kind (item, AS_APP_KIND_OS_UPGRADE);
+ as_app_set_state (item, AS_APP_STATE_AVAILABLE);
+ as_app_set_id (item, "org.gnome.release");
+ as_app_set_name (item, NULL, "GNOME");
+ as_app_set_comment (item, NULL, "A major upgrade, with new features and added polish.");
+ as_app_add_metadata (item, "GnomeSoftware::UpgradeBanner-css", css);
+ as_store_add_app (self->store, item);
+ g_set_object (&self->selected_item, item);
+
+ self->pending_changes = TRUE;
+ gs_editor_refresh_choice (self);
+ gs_editor_refresh_details (self);
+ gs_editor_set_page (self, "details");
+}
+
+static void
+gs_editor_button_new_clicked_cb (GtkWidget *widget, GsEditor *self)
+{
+ widget = GTK_WIDGET (gtk_builder_get_object (self->builder, "popover_new"));
+ gtk_popover_popup (GTK_POPOVER (widget));
+}
+
+static void
+gs_editor_startup_cb (GtkApplication *application, GsEditor *self)
+{
+ GtkTextBuffer *buffer;
+ GtkWidget *main_window;
+ GtkWidget *widget;
+ gboolean ret;
+ guint retval;
+ g_autoptr(GError) error = NULL;
+
+ /* get UI */
+ retval = gtk_builder_add_from_resource (self->builder,
+ "/org/gnome/Software/Editor/gs-editor.ui",
+ &error);
+ if (retval == 0) {
+ g_warning ("failed to load ui: %s", error->message);
+ return;
+ }
+
+ /* load all system appstream */
+ as_store_set_add_flags (self->store_global, AS_STORE_ADD_FLAG_USE_MERGE_HEURISTIC);
+ ret = as_store_load (self->store_global,
+ AS_STORE_LOAD_FLAG_IGNORE_INVALID |
+ AS_STORE_LOAD_FLAG_APP_INFO_SYSTEM |
+ AS_STORE_LOAD_FLAG_APPDATA |
+ AS_STORE_LOAD_FLAG_DESKTOP,
+ self->cancellable,
+ &error);
+ if (!ret) {
+ g_warning ("failed to load global store: %s", error->message);
+ return;
+ }
+
+ /* load all the IDs into the completion model */
+ gs_editor_load_completion_model (self);
+
+ self->featured_tile1 = gs_feature_tile_new (NULL);
+ self->upgrade_banner = gs_upgrade_banner_new ();
+ widget = GTK_WIDGET (gtk_builder_get_object (self->builder, "box_featured"));
+ gtk_box_pack_start (GTK_BOX (widget), self->featured_tile1, FALSE, FALSE, 0);
+ gtk_box_pack_start (GTK_BOX (widget), self->upgrade_banner, FALSE, FALSE, 0);
+
+ widget = GTK_WIDGET (gtk_builder_get_object (self->builder, "textview_css"));
+ buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (widget));
+ g_signal_connect (buffer, "changed",
+ G_CALLBACK (gs_design_dialog_buffer_changed_cb), self);
+
+ widget = GTK_WIDGET (gtk_builder_get_object (self->builder, "flowbox_main"));
+ gtk_flow_box_set_sort_func (GTK_FLOW_BOX (widget),
+ gs_editor_flow_box_sort_cb,
+ self, NULL);
+
+ widget = GTK_WIDGET (gtk_builder_get_object (self->builder, "button_save"));
+ g_signal_connect (widget, "clicked",
+ G_CALLBACK (gs_editor_button_save_clicked_cb), self);
+
+ widget = GTK_WIDGET (gtk_builder_get_object (self->builder, "button_new_feature"));
+ g_signal_connect (widget, "clicked",
+ G_CALLBACK (gs_editor_button_new_feature_clicked_cb), self);
+
+ widget = GTK_WIDGET (gtk_builder_get_object (self->builder, "button_new_os_upgrade"));
+ g_signal_connect (widget, "clicked",
+ G_CALLBACK (gs_editor_button_new_os_upgrade_clicked_cb), self);
+
+ widget = GTK_WIDGET (gtk_builder_get_object (self->builder, "button_new"));
+ g_signal_connect (widget, "clicked",
+ G_CALLBACK (gs_editor_button_new_clicked_cb), self);
+
+ widget = GTK_WIDGET (gtk_builder_get_object (self->builder, "button_remove"));
+ g_signal_connect (widget, "clicked",
+ G_CALLBACK (gs_editor_button_remove_clicked_cb), self);
+
+ widget = GTK_WIDGET (gtk_builder_get_object (self->builder, "button_import"));
+ g_signal_connect (widget, "clicked",
+ G_CALLBACK (gs_editor_button_import_clicked_cb), self);
+
+ widget = GTK_WIDGET (gtk_builder_get_object (self->builder, "button_back"));
+ g_signal_connect (widget, "clicked",
+ G_CALLBACK (gs_editor_button_back_clicked_cb), self);
+
+ widget = GTK_WIDGET (gtk_builder_get_object (self->builder, "button_menu"));
+ g_signal_connect (widget, "clicked",
+ G_CALLBACK (gs_editor_button_menu_clicked_cb), self);
+
+ widget = GTK_WIDGET (gtk_builder_get_object (self->builder, "button_notification_dismiss"));
+ g_signal_connect (widget, "clicked",
+ G_CALLBACK (gs_editor_button_notification_dismiss_clicked_cb), self);
+
+ widget = GTK_WIDGET (gtk_builder_get_object (self->builder, "button_notification_undo_remove"));
+ g_signal_connect (widget, "clicked",
+ G_CALLBACK (gs_editor_button_undo_remove_clicked_cb), self);
+
+ widget = GTK_WIDGET (gtk_builder_get_object (self->builder,
+ "checkbutton_editors_pick"));
+ g_signal_connect (widget, "toggled",
+ G_CALLBACK (gs_editor_checkbutton_editors_pick_cb), self);
+ widget = GTK_WIDGET (gtk_builder_get_object (self->builder,
+ "checkbutton_category_featured"));
+ g_signal_connect (widget, "toggled",
+ G_CALLBACK (gs_editor_checkbutton_category_featured_cb), self);
+
+ widget = GTK_WIDGET (gtk_builder_get_object (self->builder, "entry_desktop_id"));
+ g_signal_connect (widget, "notify::text",
+ G_CALLBACK (gs_editor_entry_desktop_id_notify_cb), self);
+
+ widget = GTK_WIDGET (gtk_builder_get_object (self->builder, "entry_name"));
+ g_signal_connect (widget, "notify::text",
+ G_CALLBACK (gs_editor_entry_name_notify_cb), self);
+
+ widget = GTK_WIDGET (gtk_builder_get_object (self->builder, "entry_summary"));
+ g_signal_connect (widget, "notify::text",
+ G_CALLBACK (gs_editor_entry_summary_notify_cb), self);
+
+ widget = GTK_WIDGET (gtk_builder_get_object (self->builder, "window_main"));
+ g_signal_connect (widget, "delete_event",
+ G_CALLBACK (gs_editor_delete_event_cb), self);
+
+ /* clear entries */
+ gs_editor_refresh_choice (self);
+ gs_editor_refresh_details (self);
+ gs_editor_refresh_file (self, NULL);
+
+ /* set the appropriate page */
+ gs_editor_set_page (self, "none");
+
+ main_window = GTK_WIDGET (gtk_builder_get_object (self->builder, "window_main"));
+ gtk_application_add_window (application, GTK_WINDOW (main_window));
+ gtk_widget_show (main_window);
+}
+
+
+static int
+gs_editor_commandline_cb (GApplication *application,
+ GApplicationCommandLine *cmdline,
+ GsEditor *self)
+{
+ GtkWindow *window;
+ gint argc;
+ gboolean verbose = FALSE;
+ g_auto(GStrv) argv = NULL;
+ g_autoptr(GOptionContext) context = NULL;
+ const GOptionEntry options[] = {
+ { "verbose", '\0', 0, G_OPTION_ARG_NONE, &verbose,
+ /* TRANSLATORS: show the program version */
+ _("Use verbose logging"), NULL },
+ { NULL}
+ };
+
+ /* get arguments */
+ argv = g_application_command_line_get_arguments (cmdline, &argc);
+ context = g_option_context_new (NULL);
+ /* TRANSLATORS: program name, an application to add and remove software repositories */
+ g_option_context_set_summary(context, _("GNOME Software Banner Designer"));
+ g_option_context_add_main_entries (context, options, NULL);
+ if (!g_option_context_parse (context, &argc, &argv, NULL))
+ return FALSE;
+
+ /* simple logging... */
+ if (verbose)
+ g_setenv ("G_MESSAGES_DEBUG", "Gs", TRUE);
+
+ /* make sure the window is raised */
+ window = GTK_WINDOW (gtk_builder_get_object (self->builder, "window_main"));
+ gtk_window_present (window);
+
+ return TRUE;
+}
+
+int
+main (int argc, char *argv[])
+{
+ gint status = 0;
+ GsEditor *self = NULL;
+
+ setlocale (LC_ALL, "");
+
+ bindtextdomain (GETTEXT_PACKAGE, LOCALEDIR);
+ bind_textdomain_codeset (GETTEXT_PACKAGE, "UTF-8");
+ textdomain (GETTEXT_PACKAGE);
+
+ gtk_init (&argc, &argv);
+
+ self = g_new0 (GsEditor, 1);
+ self->cancellable = g_cancellable_new ();
+ self->builder = gtk_builder_new ();
+ self->store = as_store_new ();
+ as_store_set_add_flags (self->store, AS_STORE_ADD_FLAG_USE_UNIQUE_ID);
+ self->store_global = as_store_new ();
+ as_store_set_add_flags (self->store_global, AS_STORE_ADD_FLAG_USE_UNIQUE_ID);
+
+ /* are we already activated? */
+ self->application = gtk_application_new ("org.gnome.Software.Editor",
+ G_APPLICATION_HANDLES_COMMAND_LINE);
+ g_signal_connect (self->application, "startup",
+ G_CALLBACK (gs_editor_startup_cb), self);
+ g_signal_connect (self->application, "command-line",
+ G_CALLBACK (gs_editor_commandline_cb), self);
+
+ /* run */
+ status = g_application_run (G_APPLICATION (self->application), argc, argv);
+
+ if (self != NULL) {
+ if (self->selected_item != NULL)
+ g_object_unref (self->selected_item);
+ if (self->deleted_item != NULL)
+ g_object_unref (self->deleted_item);
+ if (self->refresh_details_delayed_id != 0)
+ g_source_remove (self->refresh_details_delayed_id);
+ g_object_unref (self->cancellable);
+ g_object_unref (self->store);
+ g_object_unref (self->store_global);
+ g_object_unref (self->builder);
+ g_free (self);
+ }
+ return status;
+}
diff --git a/src/gs-editor.ui b/src/gs-editor.ui
new file mode 100644
index 0000000..0e10884
--- /dev/null
+++ b/src/gs-editor.ui
@@ -0,0 +1,697 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Generated with glade 3.20.0 -->
+<interface>
+ <requires lib="gtk+" version="3.18"/>
+ <object class="GtkFileFilter" id="filefilter_appstream">
+ <patterns>
+ <pattern>*.xml</pattern>
+ </patterns>
+ </object>
+ <object class="GtkListStore" id="liststore_ids">
+ <columns>
+ <!-- column-name id -->
+ <column type="gchararray"/>
+ </columns>
+ </object>
+ <object class="GtkEntryCompletion" id="entrycompletion_ids">
+ <property name="model">liststore_ids</property>
+ <property name="minimum_key_length">2</property>
+ <property name="text_column">0</property>
+ <property name="inline_completion">True</property>
+ </object>
+ <object class="GtkApplicationWindow" id="window_main">
+ <property name="can_focus">False</property>
+ <property name="default_width">1400</property>
+ <property name="default_height">700</property>
+ <property name="icon_name">org.gnome.Software</property>
+ <child>
+ <object class="GtkOverlay">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <child>
+ <object class="GtkStack" id="stack_main">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <child>
+ <object class="GtkBox">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="halign">center</property>
+ <property name="valign">center</property>
+ <property name="orientation">vertical</property>
+ <property name="spacing">12</property>
+ <child>
+ <object class="GtkImage">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="pixel_size">128</property>
+ <property name="icon_name">input-tablet-symbolic.symbolic</property>
+ <style>
+ <class name="dim-label"/>
+ </style>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes">No Designs</property>
+ <style>
+ <class name="dim-label"/>
+ </style>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="name">none</property>
+ <property name="title" translatable="yes">No Designs</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkScrolledWindow">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="border_width">24</property>
+ <property name="hscrollbar_policy">never</property>
+ <child>
+ <object class="GtkViewport">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="shadow_type">none</property>
+ <child>
+ <object class="GtkFlowBox" id="flowbox_main">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="halign">start</property>
+ <property name="valign">start</property>
+ <property name="homogeneous">True</property>
+ <property name="column_spacing">12</property>
+ <property name="row_spacing">12</property>
+ <property name="min_children_per_line">4</property>
+ <property name="max_children_per_line">4</property>
+ <property name="selection_mode">none</property>
+ </object>
+ </child>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="name">choice</property>
+ <property name="title" translatable="yes">page1</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkBox" id="box_component">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="hexpand">True</property>
+ <property name="vexpand">True</property>
+ <child>
+ <object class="GtkBox">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="orientation">vertical</property>
+ <child>
+ <object class="GtkBox" id="box_featured">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="vexpand">True</property>
+ <property name="border_width">12</property>
+ <property name="orientation">vertical</property>
+ <property name="spacing">12</property>
+ <child>
+ <object class="GtkInfoBar" id="infobar_css">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="message_type">other</property>
+ <child internal-child="action_area">
+ <object class="GtkButtonBox">
+ <property name="can_focus">False</property>
+ <property name="spacing">6</property>
+ <property name="layout_style">end</property>
+ <child>
+ <placeholder/>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="pack_type">end</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child internal-child="content_area">
+ <object class="GtkBox">
+ <property name="can_focus">False</property>
+ <property name="spacing">16</property>
+ <child>
+ <object class="GtkLabel" id="label_infobar_css">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="halign">start</property>
+ <property name="hexpand">True</property>
+ <property name="label" translatable="yes">Error message here</property>
+ </object>
+ <packing>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <placeholder/>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="pack_type">end</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkScrolledWindow">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="shadow_type">in</property>
+ <child>
+ <object class="GtkTextView" id="textview_css">
+ <property name="width_request">400</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="left_margin">12</property>
+ <property name="right_margin">12</property>
+ <property name="top_margin">12</property>
+ <property name="bottom_margin">12</property>
+ <property name="monospace">True</property>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ <property name="pack_type">end</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <style>
+ <class name="view"/>
+ </style>
+ </object>
+ <packing>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkBox">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="border_width">12</property>
+ <property name="orientation">vertical</property>
+ <property name="spacing">12</property>
+ <child>
+ <object class="GtkBox" id="box_desktop_id">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="orientation">vertical</property>
+ <property name="spacing">3</property>
+ <child>
+ <object class="GtkLabel">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="halign">start</property>
+ <property name="valign">center</property>
+ <property name="label" translatable="yes">App ID</property>
+ <attributes>
+ <attribute name="weight" value="bold"/>
+ </attributes>
+ <style>
+ <class name="dim-label"/>
+ </style>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkEntry" id="entry_desktop_id">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="width_chars">25</property>
+ <property name="completion">entrycompletion_ids</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkBox" id="box_name">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="orientation">vertical</property>
+ <property name="spacing">3</property>
+ <child>
+ <object class="GtkLabel">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="halign">start</property>
+ <property name="valign">center</property>
+ <property name="label" translatable="yes">Name</property>
+ <attributes>
+ <attribute name="weight" value="bold"/>
+ </attributes>
+ <style>
+ <class name="dim-label"/>
+ </style>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkEntry" id="entry_name">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="width_chars">25</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkBox" id="box_summary">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="orientation">vertical</property>
+ <property name="spacing">3</property>
+ <child>
+ <object class="GtkLabel">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="halign">start</property>
+ <property name="valign">center</property>
+ <property name="label" translatable="yes">Summary</property>
+ <attributes>
+ <attribute name="weight" value="bold"/>
+ </attributes>
+ <style>
+ <class name="dim-label"/>
+ </style>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkEntry" id="entry_summary">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="width_chars">25</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">2</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkBox" id="box_kudos">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="orientation">vertical</property>
+ <property name="spacing">3</property>
+ <child>
+ <object class="GtkCheckButton" id="checkbutton_editors_pick">
+ <property name="label" translatable="yes">Editor's Pick</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">False</property>
+ <property name="halign">start</property>
+ <property name="draw_indicator">True</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkCheckButton" id="checkbutton_category_featured">
+ <property name="label" translatable="yes">Category Feature</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">False</property>
+ <property name="halign">start</property>
+ <property name="draw_indicator">True</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">3</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="name">details</property>
+ <property name="title" translatable="yes">page2</property>
+ <property name="position">2</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="index">-1</property>
+ </packing>
+ </child>
+ <child type="overlay">
+ <object class="GtkRevealer" id="revealer_notification">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="halign">center</property>
+ <property name="valign">start</property>
+ <child>
+ <object class="GtkBox">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="spacing">6</property>
+ <child>
+ <object class="GtkLabel" id="label_notification">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="halign">start</property>
+ <property name="margin_start">9</property>
+ <property name="margin_end">9</property>
+ <property name="label">Some Title</property>
+ <property name="wrap">True</property>
+ <property name="wrap_mode">word-char</property>
+ <property name="max_width_chars">60</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkButtonBox">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="layout_style">end</property>
+ <child>
+ <object class="GtkButton" id="button_notification_undo_remove">
+ <property name="label" translatable="yes" comments="button in the info
bar">Undo</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">True</property>
+ </object>
+ <packing>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ </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_notification_dismiss">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="receives_default">False</property>
+ <property name="valign">center</property>
+ <child>
+ <object class="GtkImage">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="icon_name">window-close-symbolic</property>
+ </object>
+ </child>
+ <style>
+ <class name="flat"/>
+ </style>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">2</property>
+ </packing>
+ </child>
+ <style>
+ <class name="app-notification"/>
+ </style>
+ </object>
+ </child>
+ </object>
+ </child>
+ </object>
+ </child>
+ <child type="titlebar">
+ <object class="GtkHeaderBar" id="headerbar_main">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="title">Banner Designer</property>
+ <property name="subtitle">fedora.xml</property>
+ <property name="show_close_button">True</property>
+ <child>
+ <object class="GtkButton" id="button_back">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">False</property>
+ <child>
+ <object class="GtkImage" id="back_image">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="icon_name">go-previous-symbolic</property>
+ <property name="icon_size">1</property>
+ </object>
+ </child>
+ <style>
+ <class name="image-button"/>
+ </style>
+ </object>
+ </child>
+ <child>
+ <object class="GtkButton" id="button_new">
+ <property name="label" translatable="yes">New Banner</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">True</property>
+ <property name="halign">start</property>
+ </object>
+ <packing>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkButton" id="button_search">
+ <property name="visible">True</property>
+ <property name="sensitive">False</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">True</property>
+ <child>
+ <object class="GtkImage">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="halign">center</property>
+ <property name="icon_name">system-search-symbolic</property>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="pack_type">end</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkButton" id="button_menu">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">True</property>
+ <child>
+ <object class="GtkImage">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="halign">center</property>
+ <property name="icon_name">open-menu-symbolic</property>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="pack_type">end</property>
+ <property name="position">5</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ </object>
+ <object class="GtkPopover" id="popover_menu">
+ <property name="can_focus">False</property>
+ <property name="border_width">6</property>
+ <property name="relative_to">button_menu</property>
+ <child>
+ <object class="GtkBox">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="orientation">vertical</property>
+ <property name="spacing">3</property>
+ <child>
+ <object class="GtkModelButton" id="button_import">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">True</property>
+ <property name="border_width">6</property>
+ <property name="text" translatable="yes">Import from file</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkModelButton" id="button_save">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">True</property>
+ <property name="border_width">6</property>
+ <property name="text" translatable="yes">Export to file</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkModelButton" id="button_remove">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">True</property>
+ <property name="border_width">6</property>
+ <property name="text" translatable="yes">Delete Design</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">2</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ </object>
+ <object class="GtkPopover" id="popover_new">
+ <property name="can_focus">False</property>
+ <property name="border_width">6</property>
+ <property name="relative_to">button_new</property>
+ <child>
+ <object class="GtkBox">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="orientation">vertical</property>
+ <property name="spacing">3</property>
+ <child>
+ <object class="GtkModelButton" id="button_new_feature">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">True</property>
+ <property name="border_width">6</property>
+ <property name="text" translatable="yes">Featured App</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkModelButton" id="button_new_os_upgrade">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">True</property>
+ <property name="border_width">6</property>
+ <property name="text" translatable="yes">OS Upgrade</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ </object>
+</interface>
diff --git a/src/gs-feature-tile.c b/src/gs-feature-tile.c
index 59d9773..dfda13e 100644
--- a/src/gs-feature-tile.c
+++ b/src/gs-feature-tile.c
@@ -165,11 +165,11 @@ GtkWidget *
gs_feature_tile_new (GsApp *app)
{
GsFeatureTile *tile;
-
- tile = g_object_new (GS_TYPE_FEATURE_TILE, NULL);
+ tile = g_object_new (GS_TYPE_FEATURE_TILE,
+ "vexpand", FALSE,
+ NULL);
if (app != NULL)
gs_app_tile_set_app (GS_APP_TILE (tile), app);
-
return GTK_WIDGET (tile);
}
diff --git a/src/gs-upgrade-banner.c b/src/gs-upgrade-banner.c
index cfebc63..c343013 100644
--- a/src/gs-upgrade-banner.c
+++ b/src/gs-upgrade-banner.c
@@ -332,9 +332,9 @@ GtkWidget *
gs_upgrade_banner_new (void)
{
GsUpgradeBanner *self;
-
- self = g_object_new (GS_TYPE_UPGRADE_BANNER, NULL);
-
+ self = g_object_new (GS_TYPE_UPGRADE_BANNER,
+ "vexpand", FALSE,
+ NULL);
return GTK_WIDGET (self);
}
diff --git a/src/gs-upgrade-banner.ui b/src/gs-upgrade-banner.ui
index 59a8b86..83727f3 100644
--- a/src/gs-upgrade-banner.ui
+++ b/src/gs-upgrade-banner.ui
@@ -8,8 +8,6 @@
<property name="orientation">vertical</property>
<property name="hexpand">True</property>
<property name="vexpand">True</property>
- <property name="margin-bottom">48</property>
- <property name="height-request">300</property>
<property name="valign">center</property>
<style>
<class name="upgrade-banner"/>
@@ -44,7 +42,7 @@
<property name="halign">fill</property>
<property name="valign">end</property>
<property name="spacing">12</property>
- <property name="margin_top">16</property>
+ <property name="margin_top">48</property>
<style>
<class name="osd"/>
<class name="upgrade-buttons"/>
diff --git a/src/meson.build b/src/meson.build
index d333edd..a7c29d0 100644
--- a/src/meson.build
+++ b/src/meson.build
@@ -127,6 +127,38 @@ executable(
install_dir : get_option('libexecdir')
)
+resources_editor_src = gnome.compile_resources(
+ 'gs-editor-resources',
+ 'gnome-software-editor.gresource.xml',
+ source_dir : '.',
+ c_name : 'gs'
+)
+
+executable(
+ 'gnome-software-editor',
+ resources_editor_src,
+ sources : [
+ 'gs-app-tile.c',
+ 'gs-common.c',
+ 'gs-editor.c',
+ 'gs-summary-tile.c',
+ 'gs-star-widget.c',
+ 'gs-feature-tile.c',
+ 'gs-upgrade-banner.c',
+ ],
+ include_directories : [
+ include_directories('..'),
+ include_directories('../lib'),
+ ],
+ dependencies : gnome_software_dependencies,
+ link_with : [
+ libgnomesoftware
+ ],
+ c_args : cargs,
+ install : true,
+ install_dir : get_option('bindir')
+)
+
# no quoting
cdata = configuration_data()
cdata.set('bindir', join_paths(get_option('prefix'),
@@ -161,6 +193,15 @@ i18n.merge_file(
)
i18n.merge_file(
+ input: 'org.gnome.Software.Editor.desktop.in',
+ output: 'org.gnome.Software.Editor.desktop',
+ type: 'desktop',
+ po_dir: join_paths(meson.source_root(), 'po'),
+ install: true,
+ install_dir: join_paths(get_option('datadir'), 'applications')
+)
+
+i18n.merge_file(
input: 'gnome-software-local-file.desktop.in',
output: 'gnome-software-local-file.desktop',
type: 'desktop',
@@ -174,7 +215,7 @@ install_data('org.gnome.Software-search-provider.ini',
if get_option('enable-man')
xsltproc = find_program('xsltproc')
- custom_target('manfile',
+ custom_target('manfile-gnome-software',
input: 'gnome-software.xml',
output: 'gnome-software.1',
install: true,
@@ -192,6 +233,24 @@ if get_option('enable-man')
'@INPUT@'
]
)
+ custom_target('manfile-gnome-software-editor',
+ input: 'gnome-software-editor.xml',
+ output: 'gnome-software-editor.1',
+ install: true,
+ install_dir: join_paths(get_option('mandir'), 'man1'),
+ command: [
+ xsltproc,
+ '--nonet',
+ '--stringparam', 'man.output.quietly', '1',
+ '--stringparam', 'funcsynopsis.style', 'ansi',
+ '--stringparam', 'man.th.extra1.suppress', '1',
+ '--stringparam', 'man.authors.section.enabled', '0',
+ '--stringparam', 'man.copyright.section.enabled', '0',
+ '-o', '@OUTPUT@',
+ 'http://docbook.sourceforge.net/release/xsl/current/manpages/docbook.xsl',
+ '@INPUT@'
+ ]
+ )
endif
if get_option('enable-packagekit')
diff --git a/src/org.gnome.Software.Editor.desktop.in b/src/org.gnome.Software.Editor.desktop.in
new file mode 100644
index 0000000..7cac266
--- /dev/null
+++ b/src/org.gnome.Software.Editor.desktop.in
@@ -0,0 +1,17 @@
+[Desktop Entry]
+Name=Banner Designer
+Comment=Design the featured banners for GNOME Software
+# Translators: Do NOT translate or transliterate this text (this is an icon file name)!
+Icon=org.gnome.Software
+Exec=gnome-software-editor
+NoDisplay=true
+Terminal=false
+Type=Application
+Categories=GNOME;GTK;System;PackageManager;
+# Translators: Search terms to find this application. Do NOT translate or localize the semicolons! The list
MUST also end with a semicolon!
+Keywords=AppStream;Software;App;
+StartupNotify=true
+X-GNOME-Bugzilla-Bugzilla=GNOME
+X-GNOME-Bugzilla-Product=gnome-software
+X-GNOME-Bugzilla-Component=editor
+X-GNOME-UsesNotifications=true
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]