[gnome-boxes/boxes-newbox-assistant-306] Downloads Hub WIP



commit f6df3b016edf6411a8332389000c3e457eb03307
Author: Felipe Borges <felipeborges gnome org>
Date:   Mon Nov 18 16:50:51 2019 +0100

    Downloads Hub WIP

 data/gnome-boxes.gresource.xml            |   2 +
 data/ui/assistant/pages/downloads-page.ui |  17 ++++
 data/ui/assistant/pages/index-page.ui     |   3 +-
 data/ui/collection-toolbar.ui             |  27 +++++++
 data/ui/downloads-hub-row.ui              |  70 ++++++++++++++++
 data/ui/downloads-hub.ui                  |  16 ++++
 src/app-window.vala                       |   6 +-
 src/app.vala                              |  10 ++-
 src/assistant/downloads-page.vala         |  31 +++++--
 src/assistant/index-page.vala             |  29 ++++---
 src/assistant/rhel-download-dialog.vala   |  22 ++---
 src/assistant/vm-assistant.vala           |  28 ++++++-
 src/collection-toolbar.vala               |   6 +-
 src/downloads-hub.vala                    | 129 ++++++++++++++++++++++++++++++
 src/meson.build                           |   1 +
 15 files changed, 355 insertions(+), 42 deletions(-)
---
diff --git a/data/gnome-boxes.gresource.xml b/data/gnome-boxes.gresource.xml
index 67dfbd8f..4db4ad80 100644
--- a/data/gnome-boxes.gresource.xml
+++ b/data/gnome-boxes.gresource.xml
@@ -13,6 +13,8 @@
     <file preprocess="xml-stripblanks">ui/collection-toolbar.ui</file>
     <file preprocess="xml-stripblanks">ui/display-page.ui</file>
     <file preprocess="xml-stripblanks">ui/display-toolbar.ui</file>
+    <file preprocess="xml-stripblanks">ui/downloads-hub.ui</file>
+    <file preprocess="xml-stripblanks">ui/downloads-hub-row.ui</file>
     <file preprocess="xml-stripblanks">ui/editable-entry.ui</file>
     <file preprocess="xml-stripblanks">ui/empty-boxes.ui</file>
     <file preprocess="xml-stripblanks">ui/icon-view.ui</file>
diff --git a/data/ui/assistant/pages/downloads-page.ui b/data/ui/assistant/pages/downloads-page.ui
index 03cf7b3b..ea80b0c4 100644
--- a/data/ui/assistant/pages/downloads-page.ui
+++ b/data/ui/assistant/pages/downloads-page.ui
@@ -2,6 +2,16 @@
 <interface>
   <!-- interface-requires gtk+ 3.9 -->
   <template class="BoxesAssistantDownloadsPage" parent="GtkStack">
+    <signal name="key-press-event" handler="on_key_pressed"/>
+    <child type="title">
+      <object class="GtkSearchEntry" id="search_entry">
+        <property name="visible">True</property>
+        <property name="width-chars">50</property>
+        <property name="can-focus">True</property>
+        <property name="placeholder-text" translatable="yes">Search for an OS or enter a download 
link…</property>
+        <signal name="search-changed" handler="on_search_changed"/>
+      </object>
+    </child>
 
     <child>
       <object class="GtkBox">
@@ -108,4 +118,11 @@
     </child>
 
   </template>
+
+       <object class="GtkSearchEntry" id="searchbar">
+            <property name="visible">True</property>
+            <property name="width-chars">50</property>
+            <property name="can-focus">True</property>
+            <property name="placeholder-text" translatable="yes">Search for an OS or enter a download 
link…</property>
+          </object>
 </interface>
diff --git a/data/ui/assistant/pages/index-page.ui b/data/ui/assistant/pages/index-page.ui
index b400f1ab..cd1380f5 100644
--- a/data/ui/assistant/pages/index-page.ui
+++ b/data/ui/assistant/pages/index-page.ui
@@ -7,6 +7,7 @@
     <child>
       <object class="GtkStack" id="stack">
         <property name="visible">True</property>
+        <signal name="notify::visible-child" handler="update_topbar"/>
 
         <child>
           <object class="GtkScrolledWindow" id="home_page">
@@ -271,7 +272,7 @@
         <child>
           <object class="BoxesAssistantDownloadsPage" id="recommended_downloads_page">
             <property name="visible">True</property>
-            <signal name="media-selected" handler="on_download_selected"/>
+            <signal name="media-selected" handler="on_featured_media_selected"/>
           </object>
         </child>
 
diff --git a/data/ui/collection-toolbar.ui b/data/ui/collection-toolbar.ui
index ad117123..64516535 100644
--- a/data/ui/collection-toolbar.ui
+++ b/data/ui/collection-toolbar.ui
@@ -61,6 +61,33 @@
       </object>
     </child>
 
+    <child>
+      <object class="GtkMenuButton" id="downloads_hub_btn">
+        <property name="visible">False</property>
+        <property name="valign">center</property>
+        <property name="use-underline">True</property>
+        <style>
+          <class name="image-button"/>
+        </style>
+        <child internal-child="accessible">
+          <object class="AtkObject">
+            <property name="accessible-name" translatable="yes">Downloads</property>
+          </object>
+        </child>
+        <child>
+          <object class="GtkImage">
+            <property name="visible">True</property>
+            <property name="icon-name">media-record-symbolic</property>
+            <property name="icon-size">1</property>
+          </object>
+        </child>
+      </object>
+
+      <packing>
+        <property name="pack-type">end</property>
+      </packing>
+    </child>
+
     <child>
       <object class="GtkMenuButton" id="hamburger_btn">
         <property name="visible">True</property>
diff --git a/data/ui/downloads-hub-row.ui b/data/ui/downloads-hub-row.ui
new file mode 100644
index 00000000..daf6a026
--- /dev/null
+++ b/data/ui/downloads-hub-row.ui
@@ -0,0 +1,70 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interface>
+  <requires lib="gtk+" version="3.19"/>
+  <template class="BoxesDownloadsHubRow" parent="GtkListBoxRow">
+    <property name="visible">True</property>
+    <property name="selectable">False</property>
+
+    <child>
+      <object class="GtkGrid">
+        <property name="visible">True</property>
+        <property name="border-width">10</property>
+        <property name="column-spacing">20</property>
+
+        <child>
+          <object class="GtkImage" id="image">
+            <property name="visible">True</property>
+            <property name="icon-name">media-optical</property>
+            <property name="icon-size">0</property>
+            <property name="pixel-size">64</property>
+          </object>
+          <packing>
+            <property name="height">2</property>
+            <property name="left-attach">0</property>
+          </packing>
+        </child>
+
+        <child>
+          <object class="GtkLabel" id="label">
+            <property name="visible">True</property>
+            <property name="wrap">True</property>
+            <property name="max-width-chars">40</property>
+            <property name="halign">start</property>
+          </object>
+          <packing>
+            <property name="left-attach">1</property>
+          </packing>
+        </child>
+
+        <child>
+          <object class="GtkProgressBar" id="progress_bar">
+            <property name="visible">True</property>
+            <property name="hexpand">True</property>
+          </object>
+          <packing>
+            <property name="left-attach">1</property>
+          </packing>
+        </child>
+
+        <child>
+          <object class="GtkButton">
+            <property name="visible">True</property>
+            <property name="valign">center</property>
+            <signal name="clicked" handler="cancel_download"/>
+
+            <child>
+              <object class="GtkImage">
+                <property name="visible">True</property>
+                <property name="icon-name">window-close-symbolic</property>
+              </object>
+            </child>
+          </object>
+          <packing>
+            <property name="left-attach">2</property>
+            <property name="height">2</property>
+          </packing>
+        </child>
+      </object>
+    </child>
+  </template>
+</interface>
diff --git a/data/ui/downloads-hub.ui b/data/ui/downloads-hub.ui
new file mode 100644
index 00000000..5d0665c2
--- /dev/null
+++ b/data/ui/downloads-hub.ui
@@ -0,0 +1,16 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interface>
+  <requires lib="gtk+" version="3.19"/>
+  <template class="BoxesDownloadsHub" parent="GtkPopover">
+    <property name="can_focus">False</property>
+    <property name="modal">True</property>
+    <property name="position">bottom</property>
+
+    <child>
+      <object class="GtkListBox" id="listbox">
+        <property name="visible">True</property>
+        <signal name="row-activated" handler="on_row_activated"/>
+      </object>
+    </child>
+  </template>
+</interface>
diff --git a/src/app-window.vala b/src/app-window.vala
index 51556afc..46d20ed4 100644
--- a/src/app-window.vala
+++ b/src/app-window.vala
@@ -232,7 +232,7 @@ private void ui_state_changed () {
                                             icon_view,
                                             list_view,
                                             props_window,
-                                            wizard_window,
+                                            //wizard_window,
                                             empty_boxes }) {
             ui.set_state (ui_state);
         }
@@ -303,8 +303,8 @@ public void show_remote_connection_assistant () {
         new Boxes.RemoteConnectionAssistant (this).run ();
     }
 
-    public void show_vm_assistant () {
-        new Boxes.VMAssistant (this).run ();
+    public void show_vm_assistant (string? path = null) {
+        new Boxes.VMAssistant (this, path).run ();
     }
 
     public void show_properties () {
diff --git a/src/app.vala b/src/app.vala
index c77b5e04..dd6509d8 100644
--- a/src/app.vala
+++ b/src/app.vala
@@ -91,6 +91,10 @@ public App () {
         action.activate.connect ((param) => { open_name (param.get_string ()); });
         add_action (action);
 
+        action = new GLib.SimpleAction ("install", GLib.VariantType.STRING);
+        action.activate.connect ((param) => { install (param.get_string ()); });
+        add_action (action);
+
         action = new GLib.SimpleAction ("about", null);
         action.activate.connect (() => {
             string[] authors = {
@@ -313,6 +317,10 @@ public void open_name (string name) {
         }
     }
 
+    public void install (string path) {
+        main_window.show_vm_assistant (path);
+    }
+
     public bool open_uuid (string uuid) {
         main_window.set_state (UIState.COLLECTION);
 
@@ -450,7 +458,7 @@ private async void setup_default_source () ensures (default_connection != null)
         }
     }
 
-    private new void send_notification (string notification_id, GLib.Notification notification) {
+    public new void send_notification (string notification_id, GLib.Notification notification) {
         base.send_notification (notification_id, notification);
 
         system_notifications.append (notification_id);
diff --git a/src/assistant/downloads-page.vala b/src/assistant/downloads-page.vala
index 8872117b..67d9a2e2 100644
--- a/src/assistant/downloads-page.vala
+++ b/src/assistant/downloads-page.vala
@@ -17,10 +17,12 @@
     private Gtk.ListBox listbox;
     [GtkChild]
     private Gtk.ListBox recommended_listbox;
+    [GtkChild]
+    public Gtk.SearchEntry search_entry;
 
     private GLib.ListStore recommended_model;
 
-    public signal void media_selected (string url);
+    public signal void media_selected (Gtk.ListBoxRow row);
 
     private AssistantDownloadsPageView _page;
     public AssistantDownloadsPageView page {
@@ -74,17 +76,12 @@ private async void populate_recommended_list () {
     }
 
     private Gtk.Widget create_downloads_entry (Object item) {
-        var media = item as Osinfo.Media;
-
-        return new WizardDownloadableEntry (media);
+        return new WizardDownloadableEntry (item as Osinfo.Media);
     }
 
     [GtkCallback]
     private void on_listbox_row_activated (Gtk.ListBoxRow row) {
-        // Start to download Entry
-        var entry = row as WizardDownloadableEntry;
-
-        media_selected (entry.url);
+        media_selected (row);
     }
 
     [GtkCallback]
@@ -93,4 +90,22 @@ private void on_show_more_button_clicked () {
 
         page = AssistantDownloadsPageView.SEARCH_RESULTS;
     }
+
+    [GtkCallback]
+    private void on_search_changed () {
+        var text = search_entry.get_text ();
+
+        if (text == null)
+            return;
+
+        search.text = text;
+    }
+
+    [GtkCallback]
+    private bool on_key_pressed (Gtk.Widget widget, Gdk.EventKey event) {
+        if (!search_entry.has_focus)
+            search_entry.grab_focus ();
+
+        return search_entry.key_press_event (event);
+    }
 }
diff --git a/src/assistant/index-page.vala b/src/assistant/index-page.vala
index 7a39d3df..f0fac237 100644
--- a/src/assistant/index-page.vala
+++ b/src/assistant/index-page.vala
@@ -42,11 +42,13 @@ public void setup (AppWindow app_window, VMAssistant dialog) {
 
     public void go_back () {
         if (stack.visible_child == home_page) {
-            dialog.close ();
+            dialog.shutdown ();
+
+            return;
         }
 
         stack.visible_child = home_page;
-        dialog.previous_button.label = _("Cancel");
+        update_topbar ();
     }
 
     private async void populate_media_lists () {
@@ -82,6 +84,18 @@ private void populate_detected_sources_list (int? number_of_items = null) {
         return new WizardDownloadableEntry (object as Osinfo.Media);
     }
 
+    [GtkCallback]
+    private void update_topbar () {
+        dialog.previous_button.label = _("Cancel");
+
+        var titlebar = dialog.get_titlebar () as Gtk.HeaderBar;
+        if (stack.visible_child == recommended_downloads_page) {
+            titlebar.set_custom_title (recommended_downloads_page.search_entry);
+        } else {
+            titlebar.set_custom_title (null);
+        }
+    }
+
     [GtkCallback]
     private void on_expand_detected_sources_list () {
         populate_detected_sources_list ();
@@ -99,7 +113,7 @@ private void on_featured_media_selected (Gtk.ListBoxRow row) {
         var entry = row as WizardDownloadableEntry;
 
         if (entry.os.id.has_prefix ("http://redhat.com/rhel/";)) {
-            var rhel_dialog = new RHELDownloadDialog (app_window, entry.os);
+            var rhel_dialog = new RHELDownloadDialog (app_window, entry);
             rhel_dialog.bind_property ("visible", dialog, "visible", BindingFlags.INVERT_BOOLEAN);
 
             int width, height;
@@ -108,9 +122,9 @@ private void on_featured_media_selected (Gtk.ListBoxRow row) {
 
             rhel_dialog.run ();
         } else
-            on_download_selected (entry.url);
+            DownloadsHub.get_instance ().add_item (entry);
 
-        dialog.close ();
+        dialog.shutdown ();
     }
 
     public override void cleanup () {
@@ -137,9 +151,4 @@ private void on_download_an_os_button_clicked () {
 
         dialog.previous_button.label = _("Previous");
     }
-
-    [GtkCallback]
-    private void on_download_selected (string url) {
-        print (url);
-    }
 }
diff --git a/src/assistant/rhel-download-dialog.vala b/src/assistant/rhel-download-dialog.vala
index 48bd1fb9..6716c51e 100644
--- a/src/assistant/rhel-download-dialog.vala
+++ b/src/assistant/rhel-download-dialog.vala
@@ -17,6 +17,8 @@
 
     private GLib.Cancellable cancellable = new GLib.Cancellable ();
 
+    private WizardDownloadableEntry entry;
+
     construct {
         var context = web_view.get_context ();
         var language_names = GLib.Intl.get_language_names ();
@@ -31,13 +33,15 @@
         });
     }
 
-    public RHELDownloadDialog (AppWindow app_window, Osinfo.Os os) {
+    public RHELDownloadDialog (AppWindow app_window, WizardDownloadableEntry entry) {
         set_transient_for (app_window);
+        this.entry = entry;
 
         var user_agent = GLib.Uri.escape_string (get_user_agent (), null, false);
         var authentication_uri = "https://developers.redhat.com/download-manager/rest/featured/file/rhel"; +
                                  "?tag=" + user_agent;
 
+        var os = entry.os;
         is_rhel8 = os.id.has_prefix ("http://redhat.com/rhel/8";);
 
         web_view.load_uri (authentication_uri);
@@ -71,19 +75,8 @@ private bool on_decide_policy (WebKit.WebView web_view,
 
         debug ("RHEL ISO download URI: %s", download_uri);
 
-        var soup_download_uri = new Soup.URI (download_uri);
-        var download_path = soup_download_uri.get_path ();
-
-        // Libsoup is supposed to ensure that the path is at least "/".
-        return_val_if_fail (download_path != null, false);
-        return_val_if_fail (download_path.length > 0, false);
-
-        if (!download_path.has_suffix (".iso")) {
-            download_path = is_rhel8 ? "/rhel-8.0-x86_64-dvd.iso" : "/rhel.iso";
-        }
-
-        var filename = GLib.Path.get_basename (download_path);
-        print ("===> %s download_path\n\n", download_path);
+        entry.url = download_uri;
+        DownloadsHub.get_instance ().add_item (entry);
 
         decision.ignore ();
         this.close ();
@@ -180,5 +173,6 @@ public override void close () {
         cancellable.cancel ();
 
         base.close ();
+        destroy ();
     }
 }
diff --git a/src/assistant/vm-assistant.vala b/src/assistant/vm-assistant.vala
index 164f1796..05707a0f 100644
--- a/src/assistant/vm-assistant.vala
+++ b/src/assistant/vm-assistant.vala
@@ -36,13 +36,31 @@
         use_header_bar = 1;
     }
 
-    public VMAssistant (AppWindow app_window) {
+    public VMAssistant (AppWindow app_window, string? path = null) {
         set_transient_for (app_window);
 
         // TODO: Make the Assistant independent from window states
         app_window.set_state (UIState.WIZARD);
 
         index_page.setup (app_window, this);
+
+        // TODO: Rename this to PATH
+        if (path != null)
+            prepare_for_path.begin (path);
+    }
+
+    private async void prepare_for_path (string path) {
+        var media_manager = MediaManager.get_instance ();
+
+        try {
+            var installer_media = yield media_manager.create_installer_media_for_path (path, null);
+            do_preparation (installer_media);
+        } catch (GLib.Error error) {
+            debug("Failed to analyze installer image: %s", error.message);
+
+            var msg = _("Failed to analyze installer media. Corrupted or incomplete media?");
+            App.app.main_window.notificationbar.display_error (msg);
+        }
     }
 
     [GtkCallback]
@@ -114,17 +132,19 @@ private async void do_create (Object object) {
             vm_creator.launch_vm (machine);
         } catch (GLib.Error error) {
             warning ("Failed to create machine: %s", error.message);
+
+            // TODO: launch Notification
         }
 
         vm_creator.install_media.clean_up_preparation_cache ();
 
-        close ();
+        shutdown ();
     }
 
-    public override void close () {
+    public void shutdown () {
         // TODO: Make the Assistant independent from window states
         App.app.main_window.set_state (UIState.COLLECTION);
 
-        base.close ();
+        destroy ();
     }
 }
diff --git a/src/collection-toolbar.vala b/src/collection-toolbar.vala
index 2e337148..a2b2139f 100644
--- a/src/collection-toolbar.vala
+++ b/src/collection-toolbar.vala
@@ -16,6 +16,8 @@
     [GtkChild]
     private Button new_btn;
     [GtkChild]
+    private MenuButton downloads_hub_btn;
+    [GtkChild]
     private MenuButton hamburger_btn;
     [GtkChild]
     private CollectionFilterSwitcher filter_switcher;
@@ -46,6 +48,8 @@ public void setup_ui (AppWindow window) {
         var builder = new Builder.from_resource ("/org/gnome/Boxes/ui/menus.ui");
         MenuModel menu = (MenuModel) builder.get_object ("app-menu");
         hamburger_btn.popover = new Popover.from_model (hamburger_btn, menu);
+
+        downloads_hub_btn.popover = DownloadsHub.get_instance ();
     }
 
     public void click_back_button () {
@@ -62,7 +66,7 @@ public void click_search_button () {
 
     [GtkCallback]
     private void on_new_vm_btn_clicked () {
-        window.set_state (UIState.WIZARD);
+        //window.set_state (UIState.WIZARD);
     }
 
     [GtkCallback]
diff --git a/src/downloads-hub.vala b/src/downloads-hub.vala
new file mode 100644
index 00000000..e4740fd1
--- /dev/null
+++ b/src/downloads-hub.vala
@@ -0,0 +1,129 @@
+// This file is part of GNOME Boxes. License: LGPLv2+
+using Gtk;
+
+[GtkTemplate (ui = "/org/gnome/Boxes/ui/downloads-hub.ui")]
+private class Boxes.DownloadsHub : Gtk.Popover {
+    private static DownloadsHub instance;
+    public static DownloadsHub get_instance () {
+        if (instance == null)
+            instance = new DownloadsHub ();
+
+        return instance;
+    }
+
+    [GtkChild]
+    private ListBox listbox;
+
+    private bool ongoing_downloads {
+        get { return (listbox.get_children ().length () > 0); }
+    }
+
+    // TODO: inhibit suspend
+
+    public void add_item (WizardDownloadableEntry entry) {
+        var row = new DownloadsHubRow.from_entry (entry);
+
+        if (!relative_to.visible)
+            relative_to.visible = true;
+
+        row.destroy.connect (on_row_deleted);
+        row.download_complete.connect (on_download_complete);
+
+        if (!ongoing_downloads) {
+            var reason = _("Downloading media");
+
+            App.app.inhibit (App.app.main_window, null, reason);
+        }
+
+        listbox.prepend (row);
+    }
+
+    private void on_row_deleted () {
+        if (!ongoing_downloads) {
+            // Hide the Downloads Hub when there aren't ongoing downloads
+            relative_to.visible = false;
+        }
+    }
+
+    private void on_download_complete (string label, string path) {
+        var msg = _("“%s“ download complete").printf (label);
+        var notification = new GLib.Notification (msg);
+        notification.add_button (_("Install"), "app.install::" + path);
+
+        App.app.send_notification ("downloaded-" + label, notification);
+
+        if (!ongoing_downloads) {
+            App.app.uninhibit ();
+        }
+    }
+
+    [GtkCallback]
+    private void on_row_activated (Gtk.ListBoxRow _row) {
+        var row = _row as DownloadsHubRow;
+
+        if (row.local_file != null) {
+            App.app.main_window.show_vm_assistant (row.local_file);
+
+            popup ();
+        }
+    }
+}
+
+[GtkTemplate (ui= "/org/gnome/Boxes/ui/downloads-hub-row.ui")]
+private class Boxes.DownloadsHubRow : Gtk.ListBoxRow {
+    [GtkChild]
+    private Label label;
+    [GtkChild]
+    private Image image;
+    [GtkChild]
+    private ProgressBar progress_bar;
+
+    private ActivityProgress progress = new ActivityProgress ();
+    private ulong progress_notify_id;
+
+    private Cancellable cancellable = new Cancellable ();
+
+    public string? local_file;
+
+    public signal void download_complete (string label, string path);
+
+    public DownloadsHubRow.from_entry (WizardDownloadableEntry entry) {
+        label.label = entry.title;
+
+        Downloader.fetch_os_logo.begin (image, entry.os, 64);
+
+        progress_notify_id = progress.notify["progress"].connect (() => {
+            progress_bar.fraction = progress.progress;
+        });
+        progress_bar.fraction = progress.progress = 0;
+
+        var soup_download_uri = new Soup.URI (entry.url);
+        var download_path = soup_download_uri.get_path ();
+
+        var filename = GLib.Path.get_basename (download_path);
+
+        download.begin (entry.url, filename);
+    }
+
+    private async void download (string url, string filename) {
+        try {
+            local_file = yield Downloader.fetch_media (url, filename, progress, cancellable);
+        } catch (IOError.CANCELLED cancel_error) { // We did this, so ignore!
+        } catch (GLib.Error error) {
+            App.app.main_window.notificationbar.display_error (_("Failed to download"));
+
+            warning (error.message);
+            return;
+        }
+
+        download_complete (label.label, local_file);
+    }
+
+    [GtkCallback]
+    private void cancel_download () {
+        progress.disconnect (progress_notify_id);
+        cancellable.cancel ();
+
+        destroy ();
+    }
+}
diff --git a/src/meson.build b/src/meson.build
index 4b741984..c78b8fbd 100644
--- a/src/meson.build
+++ b/src/meson.build
@@ -54,6 +54,7 @@ vala_sources = [
   'display-page.vala',
   'display-toolbar.vala',
   'display.vala',
+  'downloads-hub.vala',
   'editable-entry.vala',
   'i-properties-provider.vala',
   'i-collection-view.vala',


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