[gnome-boxes/tell-us-what-os-this-is: 3/3] assistant: Let users pick the OS from a list if it is unknown




commit 8118bfb740d393eaf3e5174945d62ba2aa694458
Author: Felipe Borges <felipeborges gnome org>
Date:   Fri Jun 26 12:57:50 2020 +0200

    assistant: Let users pick the OS from a list if it is unknown
    
    When we fail to detect an operating system with libosinfo, we often
    go with our default settings. Those work for the majority of OSes
    but not for all. By allowing users to enter which operating system
    they intend to install, we can cover many more installations by
    offering better support for unrecognized medias.

 data/ui/assistant/pages/preparation-page.ui | 218 ++++++++++++++++++++--------
 src/assistant/preparation-page.vala         |  89 +++++++++++-
 src/media-manager.vala                      |   5 +-
 src/os-database.vala                        |  27 +++-
 4 files changed, 274 insertions(+), 65 deletions(-)
---
diff --git a/data/ui/assistant/pages/preparation-page.ui b/data/ui/assistant/pages/preparation-page.ui
index 9ddec630..70a497be 100644
--- a/data/ui/assistant/pages/preparation-page.ui
+++ b/data/ui/assistant/pages/preparation-page.ui
@@ -5,78 +5,178 @@
     <property name="title" translatable="yes">Preparing…</property>
 
     <child>
-      <object class="GtkGrid">
+      <object class="GtkStack" id="stack">
         <property name="visible">True</property>
-        <property name="expand">True</property>
-        <property name="halign">center</property>
-        <property name="valign">center</property>
-        <property name="column-spacing">10</property>
 
         <child>
-          <object class="GtkLabel">
+          <object class="GtkGrid">
             <property name="visible">True</property>
-            <property name="wrap">True</property>
-            <property name="halign">start</property>
-            <property name="label" translatable="yes">Preparing to create a new box</property>
-            <property name="margin-bottom">20</property>
-          </object>
-          <packing>
-            <property name="left-attach">0</property>
-            <property name="top-attach">0</property>
-            <property name="width">2</property>
-          </packing>
-        </child>
+            <property name="expand">True</property>
+            <property name="halign">center</property>
+            <property name="valign">center</property>
+            <property name="column-spacing">10</property>
 
-        <child>
-          <object class="GtkImage" id="installer_image">
-            <property name="visible">True</property>
-            <property name="icon-size">0</property>
-            <property name="pixel-size">128</property>
-            <property name="icon-name">media-optical</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="GtkLabel">
+                <property name="visible">True</property>
+                <property name="wrap">True</property>
+                <property name="halign">start</property>
+                <property name="label" translatable="yes">Preparing to create a new box</property>
+                <property name="margin-bottom">20</property>
+              </object>
+              <packing>
+                <property name="left-attach">0</property>
+                <property name="top-attach">0</property>
+                <property name="width">2</property>
+              </packing>
+            </child>
 
-        <child>
-          <object class="GtkLabel" id="media_label">
-            <property name="visible">True</property>
-            <property name="halign">start</property>
-            <property name="valign">end</property>
-            <style>
-              <class name="boxes-wizard-media-os-label"/>
-            </style>
-          </object>
-          <packing>
-            <property name="left-attach">1</property>
-            <property name="top-attach">1</property>
-          </packing>
-        </child>
+            <child>
+              <object class="GtkImage" id="installer_image">
+                <property name="visible">True</property>
+                <property name="icon-size">0</property>
+                <property name="pixel-size">128</property>
+                <property name="icon-name">media-optical</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="GtkLabel" id="status_label">
-            <property name="visible">True</property>
-            <property name="halign">start</property>
-            <property name="valign">center</property>
+            <child>
+              <object class="GtkLabel" id="media_label">
+                <property name="visible">True</property>
+                <property name="halign">start</property>
+                <property name="valign">end</property>
+                <style>
+                  <class name="boxes-wizard-media-os-label"/>
+                </style>
+              </object>
+              <packing>
+                <property name="left-attach">1</property>
+                <property name="top-attach">1</property>
+              </packing>
+            </child>
+
+            <child>
+              <object class="GtkLabel" id="status_label">
+                <property name="visible">True</property>
+                <property name="halign">start</property>
+                <property name="valign">center</property>
+              </object>
+              <packing>
+                <property name="left-attach">1</property>
+                <property name="top-attach">2</property>
+              </packing>
+            </child>
+
+            <child>
+              <object class="GtkProgressBar" id="progress_bar">
+                <property name="visible">True</property>
+                <property name="valign">start</property>
+              </object>
+              <packing>
+                <property name="left-attach">1</property>
+                <property name="top-attach">3</property>
+              </packing>
+            </child>
           </object>
-          <packing>
-            <property name="left-attach">1</property>
-            <property name="top-attach">2</property>
-          </packing>
         </child>
 
         <child>
-          <object class="GtkProgressBar" id="progress_bar">
+          <object class="GtkBox" id="unknown_os_view">
             <property name="visible">True</property>
-            <property name="valign">start</property>
+            <property name="orientation">vertical</property>
+            <property name="margin">30</property>
+            <property name="spacing">20</property>
+
+            <child>
+              <object class="GtkLabel">
+                <property name="visible">True</property>
+                <property name="wrap">True</property>
+                <property name="xalign">0</property>
+                <property name="halign">start</property>
+                <property name="max-width-chars">80</property>
+                <property name="label" translatable="yes">Boxes couldn't detect the operating system of this 
file. Please, search below for the operating system you intend to install.</property>
+              </object>
+            </child>
+
+            <child>
+              <object class="GtkInfoBar">
+                <property name="visible">True</property>
+                <property name="halign">fill</property>
+                <property name="spacing">0</property>
+                <property name="message-type">warning</property>
+
+                <child internal-child="content_area">
+                  <object class="GtkContainer">
+                    <property name="visible">True</property>
+
+                    <child>
+                      <object class="GtkImage">
+                        <property name="visible">True</property>
+                        <property name="icon-name">dialog-information-symbolic</property>
+                        <property name="icon-size">3</property>
+                        <property name="pixel-size">48</property>
+                      </object>
+                    </child>
+
+                    <child>
+                      <object class="GtkLabel">
+                        <property name="visible">True</property>
+                        <property name="label" translatable="yes">Can't find the operating system you are 
looking for? Click the &lt;b&gt;Next&lt;/b&gt; button and let Boxes pick the default settings for 
you.</property>
+                        <property name="wrap">True</property>
+                        <property name="xalign">0</property>
+                        <property name="max-width-chars">70</property>
+                        <property name="halign">start</property>
+                        <property name="hexpand">True</property>
+                        <property name="use-markup">True</property>
+                      </object>
+                    </child>
+                  </object>
+                </child>
+              </object>
+
+              <packing>
+                <property name="expand">False</property>
+                <property name="fill">False</property>
+              </packing>
+            </child>
+
+            <child>
+              <object class="GtkBox">
+                <property name="visible">True</property>
+                <property name="orientation">vertical</property>
+
+                <child>
+                  <object class="GtkSearchEntry" id="os_id_entry">
+                    <property name="visible">True</property>
+                    <property name="placeholder-text" translatable="yes">Search for an OS name</property>
+                    <signal name="changed" handler="on_os_id_entry_changed"/>
+                  </object>
+                </child>
+
+                <child>
+                  <object class="GtkScrolledWindow">
+                    <property name="visible">True</property>
+                    <property name="vexpand">True</property>
+                    <style>
+                      <class name="frame"/>
+                    </style>
+
+                    <child>
+                      <object class="GtkListBox" id="listbox">
+                        <property name="visible">True</property>
+                      </object>
+                    </child>
+                  </object>
+                </child>
+              </object>
+            </child>
+
           </object>
-          <packing>
-            <property name="left-attach">1</property>
-            <property name="top-attach">3</property>
-          </packing>
         </child>
       </object>
     </child>
diff --git a/src/assistant/preparation-page.vala b/src/assistant/preparation-page.vala
index 2579284a..14a23c10 100644
--- a/src/assistant/preparation-page.vala
+++ b/src/assistant/preparation-page.vala
@@ -2,6 +2,15 @@
 
 [GtkTemplate (ui = "/org/gnome/Boxes/ui/assistant/pages/preparation-page.ui")]
 private class Boxes.AssistantPreparationPage : AssistantPage {
+    [GtkChild]
+    private Gtk.Stack stack;
+    [GtkChild]
+    private Gtk.Box unknown_os_view;
+    [GtkChild]
+    private Gtk.Entry os_id_entry;
+    [GtkChild]
+    private Gtk.ListBox listbox;
+
     [GtkChild]
     private Gtk.Label media_label;
     [GtkChild]
@@ -26,20 +35,87 @@
         }
     }
 
-    public void setup (InstallerMedia media) {
+    public void setup (InstallerMedia media, Osinfo.Os? os = null) {
         try {
             var media_manager = MediaManager.get_instance ();
-            media = media_manager.create_installer_media_from_media (media);
+            this.media = media_manager.create_installer_media_from_media (media, os);
+
         } catch (GLib.Error error) {
             warning ("Failed to setup installation media '%s': %s", media.device_file, error.message);
         }
 
-        prepare.begin (media);
+        if (media.os == null) {
+            stack.visible_child = unknown_os_view;
+            setup_pick_os_view.begin ();
+        } else {
+            prepare.begin (media);
+        }
+    }
 
-        skip = true;
+    public async override void next () {
+        var media_manager = MediaManager.get_instance ();
+        var row = listbox.get_selected_row ();
+        if (row == null) {
+            prepare.begin (media);
+
+            return;
+        }
+
+        var item = row.get_child () as SearchResult;
+        try {
+            var os = yield media_manager.os_db.get_os_by_id (item.os.id);
+
+            setup (media, os);
+        } catch (GLib.Error error) {
+            warning ("OS not found! %s", error.message);
+        }
+    }
+
+    private async void setup_pick_os_view () {
+        var os_db = MediaManager.get_instance ().os_db;
+        var model = yield os_db.get_all_oses_sorted_by_release_date ();
+
+        listbox.set_filter_func (model_filter);
+        listbox.bind_model (model, create_listbox_entries);
+
+        os_id_entry.grab_focus ();
+    }
+
+    private Gtk.Widget create_listbox_entries (Object item) {
+        var os_item = item as Osinfo.Os;
+
+        var os_name = os_item.get_name ();
+        if (os_name != null)
+            os_name = os_name.replace ("Unknown", "");
+
+        return new SearchResult () {
+            os = os_item,
+            label = os_name,
+            visible = (os_name != null),
+            margin = 5,
+            halign = Gtk.Align.START,
+            xalign = 0
+        };
+    }
+
+    private bool model_filter (Gtk.ListBoxRow row) {
+        if (row == null)
+            return false;
+
+        var os = row.get_child () as SearchResult;
+        if (os == null)
+            return false;
+
+        return (os.get_text ().down ().contains (os_id_entry.get_text ().down ()));
+    }
+
+    [GtkCallback]
+    private void on_os_id_entry_changed () {
+        listbox.invalidate_filter ();
     }
 
     public async void prepare (InstallerMedia media) {
+        skip = true;
         var progress = create_preparation_progress ();
         if (!yield media.prepare (progress, cancellable)) // add cancellable
             return;
@@ -65,5 +141,10 @@ private ActivityProgress create_preparation_progress () {
 
     public override void cleanup () {
         cancellable.reset ();
+        os_id_entry.set_text ("");
     }
 }
+
+class SearchResult : Gtk.Label {
+    public Osinfo.Os os;
+}
diff --git a/src/media-manager.vala b/src/media-manager.vala
index fee4b4c3..79b198c3 100644
--- a/src/media-manager.vala
+++ b/src/media-manager.vala
@@ -206,7 +206,10 @@ private static InstallerMedia create_unattended_installer (InstallerMedia media)
         return install_media;
     }
 
-    public InstallerMedia create_installer_media_from_media (InstallerMedia media) throws GLib.Error {
+    public InstallerMedia create_installer_media_from_media (InstallerMedia media, Os? os = null) throws 
GLib.Error {
+        if (os != null)
+            media.os = os;
+
         if (media.os == null)
             return media;
 
diff --git a/src/os-database.vala b/src/os-database.vala
index 41fd8e70..3ec1f072 100644
--- a/src/os-database.vala
+++ b/src/os-database.vala
@@ -26,7 +26,7 @@
     // dependent on the OS/guest.
     private const int64 DEFAULT_STORAGE = 20 * (int64) GIBIBYTES;
 
-    private Db? db;
+    public Db? db;
 
     private bool db_loading;
 
@@ -101,6 +101,31 @@ public async Os get_os_by_id (string id) throws OSDatabaseError {
         return os;
     }
 
+    public async GLib.ListModel get_all_oses_sorted_by_release_date () {
+        if (!yield ensure_db_loaded ())
+            throw new OSDatabaseError.DB_LOADING_FAILED ("Failed to load OS database");
+
+        var os_list = db.get_os_list ().get_elements ();
+        os_list.sort ((os_a, os_b) => {
+            var release_a = (os_a as Os).get_release_date ();
+            var release_b = (os_b as Os).get_release_date ();
+
+            if (release_a == null)
+                return -1;
+            else if (release_b == null)
+                return 1;
+
+            return release_b.compare (release_a);
+        });
+
+        var model = new GLib.ListStore (typeof (Osinfo.Os));
+        foreach (var os in os_list) {
+            model.append (os as Os);
+        }
+
+        return model;
+    }
+
     public async GLib.List<Osinfo.Media> list_downloadable_oses () throws OSDatabaseError {
         if (!yield ensure_db_loaded ())
             throw new OSDatabaseError.DB_LOADING_FAILED ("Failed to load OS database");


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