[gnome-software/wip/hughsie/appstream-editor: 12/12] Add a banner designer utility
- From: Richard Hughes <rhughes src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [gnome-software/wip/hughsie/appstream-editor: 12/12] Add a banner designer utility
- Date: Mon, 8 May 2017 10:26:01 +0000 (UTC)
commit 2e6ab5abf2bb8a6cdf25a12c906be0e60b96dc57
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 | 7 +-
meson.build | 2 +-
po/POTFILES.in | 2 +
src/gnome-software-editor.gresource.xml | 11 +
src/gs-editor.c | 952 ++++++++++++++++++++++++++++++
src/gs-editor.ui | 434 ++++++++++++++
src/meson.build | 40 ++
src/org.gnome.Software.Editor.desktop.in | 17 +
8 files changed, 1463 insertions(+), 2 deletions(-)
---
diff --git a/contrib/gnome-software.spec.in b/contrib/gnome-software.spec.in
index 0b7600e..d5a59f2 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,10 @@ 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
+%{_libexecdir}/gnome-software-editor
+
%files devel
%{_libdir}/pkgconfig/gnome-software.pc
%dir %{_includedir}/gnome-software
diff --git a/meson.build b/meson.build
index 1075150..5d61280 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..d6dbd5f
--- /dev/null
+++ b/src/gnome-software-editor.gresource.xml
@@ -0,0 +1,11 @@
+<?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-star-widget.ui</file>
+ </gresource>
+</gresources>
diff --git a/src/gs-editor.c b/src/gs-editor.c
new file mode 100644
index 0000000..7c288a1
--- /dev/null
+++ b/src/gs-editor.c
@@ -0,0 +1,952 @@
+/* -*- 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-feature-tile.h"
+#include "gs-summary-tile.h"
+#include "gs-common.h"
+
+typedef struct {
+ GCancellable *cancellable;
+ GtkApplication *application;
+ GtkBuilder *builder;
+ GtkWidget *featured_tile1;
+ AsStore *store;
+ AsStore *store_global;
+ AsApp *selected_item;
+ gboolean is_in_refresh;
+ gboolean pending_changes;
+} 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;
+ GsApp *app;
+ const gchar *css = NULL;
+ g_autofree gchar *css_new = NULL;
+ g_autoptr(GError) error = NULL;
+
+ 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) {
+ g_autoptr(AsIcon) ic = NULL;
+ g_debug ("no app found for %s, using fallback", as_app_get_id (item));
+ gs_app_set_name (app, GS_APP_QUALITY_NORMAL,
+ "Application");
+ gs_app_set_summary (app, GS_APP_QUALITY_NORMAL,
+ "Description");
+ gs_app_set_description (app, GS_APP_QUALITY_NORMAL,
+ "A multiline description");
+ 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);
+ } 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);
+ }
+ }
+ gs_editor_refine_app_pixbuf (app);
+ css = as_app_get_metadata_item (item, "GnomeSoftware::FeatureTile-css");
+ if (css != 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, "GnomeSoftware::FeatureTile-css", css);
+ } else {
+ gs_app_set_metadata (app, "GnomeSoftware::FeatureTile-css", css_new);
+ }
+ } else {
+ gs_app_set_metadata (app, "GnomeSoftware::FeatureTile-css", NULL);
+ }
+ return app;
+}
+
+static void
+gs_editor_refresh_details (GsEditor *self)
+{
+ 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);
+
+ /* update */
+ if (app != NULL) {
+ g_debug ("refreshing details for %s", gs_app_get_id (app));
+ 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);
+ }
+
+ /* 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) {
+ 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);
+ }
+ 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)) {
+ widget = GTK_WIDGET (gtk_builder_get_object (self->builder, "label_infobar_css"));
+ gtk_label_set_label (GTK_LABEL (widget), error->message);
+ 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 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_editor_refresh_details (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_add"));
+ 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);
+
+ } 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_add"));
+ 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);
+
+ } 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_add"));
+ 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);
+ }
+}
+
+static void
+gs_editor_app_tile_clicked_cb (GsAppTile *tile, GsEditor *self)
+{
+ GsApp *app;
+ AsApp *item = NULL;
+ GPtrArray *apps;
+
+ app = gs_app_tile_get_app (tile);
+ apps = as_store_get_apps (self->store);
+ for (guint i = 0; i < apps->len; i++) {
+ AsApp *item_tmp = g_ptr_array_index (apps, i);
+ if (g_strcmp0 (as_app_get_id (item_tmp),
+ gs_app_get_id (app)) == 0) {
+ item = item_tmp;
+ break;
+ }
+ }
+// 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_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_button_add_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_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_remove_clicked_cb (GtkApplication *application, GsEditor *self)
+{
+ if (self->selected_item == NULL)
+ return;
+ 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_text_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_app_set_id (self->selected_item, 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_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);
+ gtk_widget_show (self->featured_tile1);
+ widget = GTK_WIDGET (gtk_builder_get_object (self->builder, "box_featured"));
+ gtk_box_pack_start (GTK_BOX (widget), self->featured_tile1, 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_add"));
+ g_signal_connect (widget, "clicked",
+ G_CALLBACK (gs_editor_button_add_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,
+ "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_text_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);
+ 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..f72ee10
--- /dev/null
+++ b/src/gs-editor.ui
@@ -0,0 +1,434 @@
+<?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="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">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="GtkGrid">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="margin_left">18</property>
+ <property name="margin_right">18</property>
+ <property name="margin_top">18</property>
+ <property name="margin_bottom">18</property>
+ <property name="row_spacing">12</property>
+ <property name="column_spacing">12</property>
+ <child>
+ <object class="GtkLabel">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="halign">end</property>
+ <property name="valign">center</property>
+ <property name="label" translatable="yes">App ID</property>
+ <style>
+ <class name="dim-label"/>
+ </style>
+ </object>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="top_attach">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="text" translatable="yes">gimp.desktop</property>
+ <property name="completion">entrycompletion_ids</property>
+ </object>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="top_attach">0</property>
+ </packing>
+ </child>
+ <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="left_attach">1</property>
+ <property name="top_attach">1</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="left_attach">1</property>
+ <property name="top_attach">2</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ </object>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="top_attach">1</property>
+ <property name="height">3</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkButton" id="button_remove">
+ <property name="label" translatable="yes">Delete</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">True</property>
+ <property name="halign">start</property>
+ <property name="valign">end</property>
+ <style>
+ <class name="destructive-action"/>
+ </style>
+ </object>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="top_attach">3</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="name">details</property>
+ <property name="title">page2</property>
+ <property name="position">2</property>
+ </packing>
+ </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_add">
+ <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>
+ </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_save">
+ <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">document-save-symbolic</property>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="pack_type">end</property>
+ <property name="position">3</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkButton" id="button_import">
+ <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">document-open-symbolic</property>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="pack_type">end</property>
+ <property name="position">3</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ </object>
+</interface>
diff --git a/src/meson.build b/src/meson.build
index d333edd..264161a 100644
--- a/src/meson.build
+++ b/src/meson.build
@@ -127,6 +127,37 @@ 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',
+ ],
+ include_directories : [
+ include_directories('..'),
+ include_directories('../lib'),
+ ],
+ dependencies : gnome_software_dependencies,
+ link_with : [
+ libgnomesoftware
+ ],
+ c_args : cargs,
+ install : true,
+ install_dir : 'libexec'
+)
+
# no quoting
cdata = configuration_data()
cdata.set('bindir', join_paths(get_option('prefix'),
@@ -161,6 +192,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',
diff --git a/src/org.gnome.Software.Editor.desktop.in b/src/org.gnome.Software.Editor.desktop.in
new file mode 100644
index 0000000..4be01cc
--- /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=/usr/libexec/gnome-software-editor %U
+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]