[gnome-boxes/boxes-newbox-assistant-306: 1/2] WIP
- From: Felipe Borges <felipeborges src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [gnome-boxes/boxes-newbox-assistant-306: 1/2] WIP
- Date: Mon, 18 Nov 2019 15:51:15 +0000 (UTC)
commit a68b020ac3fd4be8753cf0b2ebc88971a6a6b637
Author: Felipe Borges <felipeborges gnome org>
Date: Mon Nov 4 10:00:33 2019 +0100
WIP
data/gnome-boxes.gresource.xml | 7 +
data/gtk-style.css | 4 +
data/ui/assistant/pages/.index-page.ui.swp | Bin 0 -> 16384 bytes
data/ui/assistant/pages/downloads-page.ui | 111 +++++++++++
data/ui/assistant/pages/index-page.ui | 282 ++++++++++++++++++++++++++++
data/ui/assistant/pages/preparation-page.ui | 84 +++++++++
data/ui/assistant/pages/review-page.ui | 108 +++++++++++
data/ui/assistant/pages/setup-page.ui | 21 +++
data/ui/assistant/rhel-download-dialog.ui | 45 +++++
data/ui/assistant/vm-assistant.ui | 65 +++++++
data/ui/collection-toolbar.ui | 7 +
src/app-window.vala | 4 +
src/assistant/assistant-page.vala | 17 ++
src/assistant/downloads-page.vala | 96 ++++++++++
src/assistant/index-page.vala | 145 ++++++++++++++
src/assistant/preparation-page.vala | 67 +++++++
src/assistant/review-page.vala | 118 ++++++++++++
src/assistant/rhel-download-dialog.vala | 184 ++++++++++++++++++
src/assistant/setup-page.vala | 34 ++++
src/assistant/vm-assistant.vala | 130 +++++++++++++
src/collection-toolbar.vala | 5 +
src/meson.build | 8 +
src/vm-creator.vala | 2 +-
23 files changed, 1543 insertions(+), 1 deletion(-)
---
diff --git a/data/gnome-boxes.gresource.xml b/data/gnome-boxes.gresource.xml
index c7ba7de1..67dfbd8f 100644
--- a/data/gnome-boxes.gresource.xml
+++ b/data/gnome-boxes.gresource.xml
@@ -48,5 +48,12 @@
<file preprocess="xml-stripblanks">ui/wizard-web-view.ui</file>
<file preprocess="xml-stripblanks">ui/wizard-window.ui</file>
<file preprocess="xml-stripblanks">ui/assistant/remote-connection.ui</file>
+ <file preprocess="xml-stripblanks">ui/assistant/vm-assistant.ui</file>
+ <file preprocess="xml-stripblanks">ui/assistant/rhel-download-dialog.ui</file>
+ <file preprocess="xml-stripblanks">ui/assistant/pages/index-page.ui</file>
+ <file preprocess="xml-stripblanks">ui/assistant/pages/downloads-page.ui</file>
+ <file preprocess="xml-stripblanks">ui/assistant/pages/preparation-page.ui</file>
+ <file preprocess="xml-stripblanks">ui/assistant/pages/setup-page.ui</file>
+ <file preprocess="xml-stripblanks">ui/assistant/pages/review-page.ui</file>
</gresource>
</gresources>
diff --git a/data/gtk-style.css b/data/gtk-style.css
index 40f0391d..72e2f215 100644
--- a/data/gtk-style.css
+++ b/data/gtk-style.css
@@ -43,6 +43,10 @@
border: 1px solid @theme_bg_color;
}
+.bold-label {
+ font-weight: bold;
+}
+
/******************
* New Box Wizard *
******************/
diff --git a/data/ui/assistant/pages/.index-page.ui.swp b/data/ui/assistant/pages/.index-page.ui.swp
new file mode 100644
index 00000000..b6aad11d
Binary files /dev/null and b/data/ui/assistant/pages/.index-page.ui.swp differ
diff --git a/data/ui/assistant/pages/downloads-page.ui b/data/ui/assistant/pages/downloads-page.ui
new file mode 100644
index 00000000..03cf7b3b
--- /dev/null
+++ b/data/ui/assistant/pages/downloads-page.ui
@@ -0,0 +1,111 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interface>
+ <!-- interface-requires gtk+ 3.9 -->
+ <template class="BoxesAssistantDownloadsPage" parent="GtkStack">
+
+ <child>
+ <object class="GtkBox">
+ <property name="visible">True</property>
+ <property name="orientation">vertical</property>
+ <property name="border-width">20</property>
+ <property name="margin-start">20</property>
+ <property name="margin-end">20</property>
+
+ <child>
+ <object class="GtkListBox" id="recommended_listbox">
+ <property name="visible">True</property>
+ <property name="vexpand">True</property>
+ <property name="selection-mode">none</property>
+ <signal name="row-activated" handler="on_listbox_row_activated"/>
+ </object>
+ </child>
+
+ <child>
+ <object class="GtkButton" id="show_more_button">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">Show more…</property>
+ <signal name="clicked" handler="on_show_more_button_clicked"/>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="name">recommended</property>
+ </packing>
+ </child>
+
+ <child>
+ <object class="GtkScrolledWindow">
+ <property name="visible">True</property>
+ <property name="expand">True</property>
+
+ <child>
+ <object class="GtkBox">
+ <property name="visible">True</property>
+ <property name="orientation">vertical</property>
+ <property name="border-width">20</property>
+ <property name="margin-start">20</property>
+ <property name="margin-end">20</property>
+
+ <child>
+ <object class="GtkListBox" id="listbox">
+ <property name="visible">True</property>
+ <property name="selection-mode">none</property>
+ <signal name="row-activated" handler="on_listbox_row_activated"/>
+ </object>
+ </child>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="name">search-results</property>
+ </packing>
+ </child>
+
+ <child>
+ <object class="GtkBox">
+ <property name="visible">True</property>
+ <property name="orientation">vertical</property>
+ <property name="opacity">0.5</property>
+ <property name="spacing">10</property>
+ <property name="valign">center</property>
+ <style>
+ <class name="dim-label"/>
+ </style>
+
+ <child>
+ <object class="GtkImage">
+ <property name="visible">True</property>
+ <property name="resource">/org/gnome/Boxes/icons/empty-boxes.png</property>
+ <property name="pixel-size">128</property>
+ <property name="halign">center</property>
+ <property name="valign">center</property>
+ <property name="margin">18</property>
+ </object>
+ </child>
+
+ <child>
+ <object class="GtkLabel">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">No operating systems found</property>
+ <attributes>
+ <attribute name="scale" value="2"/>
+ <attribute name="weight" value="bold"/>
+ </attributes>
+ </object>
+ </child>
+
+ <child>
+ <object class="GtkLabel">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">Try a different search</property>
+ </object>
+ </child>
+
+ </object>
+ <packing>
+ <property name="name">no-results</property>
+ </packing>
+ </child>
+
+ </template>
+</interface>
diff --git a/data/ui/assistant/pages/index-page.ui b/data/ui/assistant/pages/index-page.ui
new file mode 100644
index 00000000..b400f1ab
--- /dev/null
+++ b/data/ui/assistant/pages/index-page.ui
@@ -0,0 +1,282 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interface>
+ <template class="BoxesAssistantIndexPage" parent="BoxesAssistantPage">
+ <property name="visible">True</property>
+ <property name="title" translatable="yes">Create a Virtual Machine</property>
+
+ <child>
+ <object class="GtkStack" id="stack">
+ <property name="visible">True</property>
+
+ <child>
+ <object class="GtkScrolledWindow" id="home_page">
+ <property name="visible">True</property>
+ <property name="expand">True</property>
+ <property name="hscrollbar-policy">never</property>
+
+ <child>
+ <object class="GtkBox">
+ <property name="visible">True</property>
+ <property name="orientation">vertical</property>
+ <property name="spacing">30</property>
+ <property name="halign">center</property>
+ <property name="border-width">20</property>
+ <property name="margin-left">30</property>
+ <property name="margin-right">30</property>
+
+ <child>
+ <object class="GtkLabel">
+ <property name="visible">True</property>
+ <property name="wrap">True</property>
+ <property name="max-width-chars">60</property>
+ <property name="xalign">0</property>
+ <property name="halign">start</property>
+ <property name="label" translatable="yes">A new virtual machine will be created and an
operating system installed into it. Select an operating system source to begin.</property>
+ </object>
+ </child>
+
+ <child>
+ <object class="GtkBox" id="detected_sources_section">
+ <property name="visible">True</property>
+ <property name="spacing">10</property>
+ <property name="orientation">vertical</property>
+
+ <child>
+ <object class="GtkLabel">
+ <property name="visible">True</property>
+ <property name="xalign">0</property>
+ <property name="label" translatable="yes">Detected Sources</property>
+ <style>
+ <class name="bold-label"/>
+ </style>
+ </object>
+ </child>
+
+ <child>
+ <object class="GtkBox">
+ <property name="visible">True</property>
+ <property name="orientation">vertical</property>
+ <child>
+ <object class="GtkListBox" id="source_medias">
+ <property name="visible">True</property>
+ <signal name="row-activated" handler="on_source_media_selected"/>
+ </object>
+ </child>
+
+ <child>
+ <object class="GtkButton" id="expand_detected_sources_list_button">
+ <property name="visible">True</property>
+ <signal name="clicked" handler="on_expand_detected_sources_list"/>
+ <child>
+ <object class="GtkImage">
+ <property name="visible">True</property>
+ <property name="icon-name">view-more-symbolic</property>
+ </object>
+ </child>
+ </object>
+ </child>
+ </object>
+ </child>
+ </object>
+ </child>
+
+ <child>
+ <object class="GtkBox">
+ <property name="visible">True</property>
+ <property name="spacing">10</property>
+ <property name="orientation">vertical</property>
+
+ <child>
+ <object class="GtkLabel">
+ <property name="visible">True</property>
+ <property name="xalign">0</property>
+ <property name="label" translatable="yes">Featured Downloads</property>
+ <style>
+ <class name="bold-label"/>
+ </style>
+ </object>
+ </child>
+
+ <child>
+ <object class="GtkLabel">
+ <property name="visible">True</property>
+ <property name="xalign">0</property>
+ <property name="label" translatable="yes">You will be notified when the download has
completed.</property>
+ </object>
+ </child>
+
+ <child>
+ <object class="GtkListBox" id="featured_medias">
+ <property name="visible">True</property>
+ <signal name="row-activated" handler="on_featured_media_selected"/>
+ </object>
+ </child>
+ </object>
+ </child>
+
+ <child>
+ <object class="GtkBox">
+ <property name="visible">True</property>
+ <property name="spacing">10</property>
+ <property name="orientation">vertical</property>
+
+ <child>
+ <object class="GtkLabel">
+ <property name="visible">True</property>
+ <property name="xalign">0</property>
+ <property name="label" translatable="yes">Select an OS Source</property>
+ <style>
+ <class name="bold-label"/>
+ </style>
+ </object>
+ </child>
+
+ <child>
+ <object class="GtkBox">
+ <property name="visible">True</property>
+ <property name="orientation">vertical</property>
+
+ <child>
+ <object class="GtkButton">
+ <property name="visible">True</property>
+ <signal name="clicked" handler="on_download_an_os_button_clicked"/>
+ <style>
+ <class name="boxes-menu-row"/>
+ </style>
+
+ <child>
+ <object class="GtkGrid">
+ <property name="visible">True</property>
+ <property name="border-width">10</property>
+
+ <child>
+ <object class="GtkLabel">
+ <property name="visible">True</property>
+ <property name="hexpand">True</property>
+ <property name="halign">start</property>
+ <property name="label" translatable="yes">Operating System
Download</property>
+ <style>
+ <class name="bold-label"/>
+ </style>
+ </object>
+ <packing>
+ <property name="left-attach">0</property>
+ <property name="top-attach">0</property>
+ </packing>
+ </child>
+
+ <child>
+ <object class="GtkLabel">
+ <property name="visible">True</property>
+ <property name="hexpand">True</property>
+ <property name="halign">start</property>
+ <property name="label" translatable="yes">Browse and search for
operating systems to install.</property>
+ <style>
+ <class name="dim-label"/>
+ </style>
+ </object>
+ <packing>
+ <property name="left-attach">0</property>
+ <property name="top-attach">1</property>
+ </packing>
+ </child>
+
+ <child>
+ <object class="GtkImage">
+ <property name="visible">True</property>
+ <property name="icon-name">go-next-symbolic</property>
+ </object>
+ <packing>
+ <property name="left-attach">1</property>
+ <property name="top-attach">0</property>
+ <property name="height">2</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+
+ </object>
+ </child>
+
+ <child>
+ <object class="GtkButton">
+ <property name="visible">True</property>
+ <signal name="clicked" handler="on_select_file_button_clicked"/>
+ <style>
+ <class name="boxes-menu-row"/>
+ </style>
+
+ <child>
+ <object class="GtkGrid">
+ <property name="visible">True</property>
+ <property name="border-width">10</property>
+
+ <child>
+ <object class="GtkLabel">
+ <property name="visible">True</property>
+ <property name="hexpand">True</property>
+ <property name="halign">start</property>
+ <property name="label" translatable="yes">Operating System Image
File</property>
+ <style>
+ <class name="bold-label"/>
+ </style>
+ </object>
+ <packing>
+ <property name="left-attach">0</property>
+ <property name="top-attach">0</property>
+ </packing>
+ </child>
+
+ <child>
+ <object class="GtkLabel">
+ <property name="visible">True</property>
+ <property name="hexpand">True</property>
+ <property name="halign">start</property>
+ <property name="label" translatable="yes">Select an .iso file to install
a virtual machine.</property>
+ <style>
+ <class name="dim-label"/>
+ </style>
+ </object>
+ <packing>
+ <property name="left-attach">0</property>
+ <property name="top-attach">1</property>
+ </packing>
+ </child>
+
+ <child>
+ <object class="GtkImage">
+ <property name="visible">True</property>
+ <property name="icon-name">go-next-symbolic</property>
+ </object>
+ <packing>
+ <property name="left-attach">1</property>
+ <property name="top-attach">0</property>
+ <property name="height">2</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+
+ </object>
+ </child>
+ </object>
+ </child>
+ </object>
+ </child>
+ </object>
+ </child>
+ </object>
+ </child>
+
+ <child>
+ <object class="BoxesAssistantDownloadsPage" id="recommended_downloads_page">
+ <property name="visible">True</property>
+ <signal name="media-selected" handler="on_download_selected"/>
+ </object>
+ </child>
+
+ </object>
+ </child>
+
+ </template>
+</interface>
diff --git a/data/ui/assistant/pages/preparation-page.ui b/data/ui/assistant/pages/preparation-page.ui
new file mode 100644
index 00000000..9ddec630
--- /dev/null
+++ b/data/ui/assistant/pages/preparation-page.ui
@@ -0,0 +1,84 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interface>
+ <template class="BoxesAssistantPreparationPage" parent="BoxesAssistantPage">
+ <property name="visible">True</property>
+ <property name="title" translatable="yes">Preparing…</property>
+
+ <child>
+ <object class="GtkGrid">
+ <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">
+ <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="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="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>
+ </child>
+ </template>
+</interface>
diff --git a/data/ui/assistant/pages/review-page.ui b/data/ui/assistant/pages/review-page.ui
new file mode 100644
index 00000000..a135151d
--- /dev/null
+++ b/data/ui/assistant/pages/review-page.ui
@@ -0,0 +1,108 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interface>
+ <template class="BoxesAssistantReviewPage" parent="BoxesAssistantPage">
+ <property name="visible">True</property>
+ <property name="title" translatable="yes">Review and Create</property>
+ <property name="vexpand">False</property>
+ <property name="valign">start</property>
+ <property name="orientation">vertical</property>
+ <property name="border-width">30</property>
+ <property name="spacing">20</property>
+
+ <child>
+ <object class="GtkLabel" id="review_label">
+ <property name="visible">True</property>
+ <property name="halign">start</property>
+ <property name="wrap">True</property>
+ <property name="width-chars">30</property>
+ <property name="label" translatable="yes">Boxes is ready to set up a new box with the following
properties:</property>
+ </object>
+ </child>
+
+ <child>
+ <object class="GtkInfoBar" id="nokvm_infobar">
+ <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" id="nokvm_container">
+ <property name="visible">True</property>
+
+ <child>
+ <object class="GtkImage" id="nokvm_image">
+ <property name="visible">True</property>
+ <property name="icon-name">dialog-warning</property>
+ <property name="icon-size">3</property>
+ <property name="pixel-size">48</property>
+ </object>
+ </child>
+
+ <child>
+ <object class="GtkLabel" id="nokvm_label">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">Virtualization extensions are unavailable on your
system.
+Check your BIOS settings to enable them.</property>
+ <property name="wrap">True</property>
+ <property name="halign">start</property>
+ <property name="hexpand">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>
+ <child>
+ <object class="GtkLabel">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">Resource Allocation</property>
+ <style>
+ <class name="bold-label"/>
+ </style>
+ </object>
+ </child>
+ <child>
+ <object class="GtkToggleButton" id="customize_button">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">Customize</property>
+ <property name="halign">end</property>
+ <property name="hexpand">True</property>
+ <signal name="toggled" handler="on_customize_button_toggled"/>
+ </object>
+ </child>
+ </object>
+ </child>
+
+ <child>
+ <object class="GtkStack" id="customization_stack">
+ <property name="visible">True</property>
+ <child>
+ <object class="BoxesWizardSummary" id="summary"/>
+ </child>
+ <child>
+ <object class="GtkGrid" id="customization_grid">
+ <property name="visible">True</property>
+ <property name="row_spacing">10</property>
+ <property name="column_spacing">20</property>
+ </object>
+ </child>
+ </object>
+
+ <packing>
+ <property name="expand">True</property>
+ <property name="fill">False</property>
+ </packing>
+ </child>
+
+ </template>
+</interface>
diff --git a/data/ui/assistant/pages/setup-page.ui b/data/ui/assistant/pages/setup-page.ui
new file mode 100644
index 00000000..f5425b7a
--- /dev/null
+++ b/data/ui/assistant/pages/setup-page.ui
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interface>
+ <template class="BoxesAssistantSetupPage" parent="BoxesAssistantPage">
+ <property name="visible">True</property>
+ <property name="title" translatable="yes">Express Installation</property>
+ <property name="expand">True</property>
+
+ <child>
+ <object class="GtkBox" id="setup_box">
+ <property name="visible">True</property>
+ <property name="expand">True</property>
+ <property name="orientation">vertical</property>
+ <property name="spacing">0</property>
+ <property name="margin-start">10</property>
+ <property name="margin-end">10</property>
+ <property name="valign">center</property>
+ </object>
+ </child>
+
+ </template>
+</interface>
diff --git a/data/ui/assistant/rhel-download-dialog.ui b/data/ui/assistant/rhel-download-dialog.ui
new file mode 100644
index 00000000..ad8b9e1c
--- /dev/null
+++ b/data/ui/assistant/rhel-download-dialog.ui
@@ -0,0 +1,45 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interface>
+ <template class="BoxesRHELDownloadDialog" parent="GtkDialog">
+ <property name="modal">True</property>
+ <property name="type-hint">dialog</property>
+ <property name="height-request">250</property>
+
+ <child internal-child="vbox">
+ <object class="GtkBox">
+ <property name="visible">True</property>
+ <property name="orientation">vertical</property>
+
+ <child>
+ <object class="GtkOverlay" id="overlay">
+ <property name="visible">True</property>
+
+ <child type="overlay">
+ <object class="GtkProgressBar" id="progress_bar">
+ <property name="no-show-all">True</property>
+ <property name="valign">start</property>
+ <style>
+ <class name="osd"/>
+ </style>
+ </object>
+ </child>
+
+ <child>
+ <!-- https://bugzilla.gnome.org/show_bug.cgi?id=786932 -->
+ <!-- https://bugzilla.gnome.org/show_bug.cgi?id=787033 -->
+ <!-- https://bugs.webkit.org/show_bug.cgi?id=175937 -->
+ <object class="WebKitWebView" type-func="webkit_web_view_get_type" id="web_view">
+ <property name="hexpand">True</property>
+ <property name="vexpand">True</property>
+ <property name="visible">True</property>
+ <signal name="context-menu" handler="on_context_menu" />
+ <signal name="notify::estimated-load-progress" handler="on_notify_estimated_load_progress" />
+ <signal name="decide-policy" handler="on_decide_policy"/>
+ </object>
+ </child>
+ </object>
+ </child>
+ </object>
+ </child>
+ </template>
+</interface>
diff --git a/data/ui/assistant/vm-assistant.ui b/data/ui/assistant/vm-assistant.ui
new file mode 100644
index 00000000..fba260f2
--- /dev/null
+++ b/data/ui/assistant/vm-assistant.ui
@@ -0,0 +1,65 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interface>
+ <template class="BoxesVMAssistant" parent="GtkDialog">
+ <property name="modal">True</property>
+ <property name="type-hint">dialog</property>
+ <property name="title" translatable="yes">Create a Virtual Machine</property>
+ <property name="width-request">724</property>
+ <property name="height-request">568</property>
+
+ <child internal-child="vbox">
+ <object class="GtkBox">
+ <property name="visible">True</property>
+ <property name="orientation">vertical</property>
+
+ <child>
+ <object class="GtkStack" id="pages">
+ <property name="visible">True</property>
+ <signal name="notify::visible-child" handler="update_titlebar_buttons"/>
+ <child>
+ <object class="BoxesAssistantIndexPage" id="index_page">
+ <signal name="done" handler="do_preparation"/>
+ </object>
+ </child>
+ <child>
+ <object class="BoxesAssistantPreparationPage" id="preparation_page">
+ <signal name="done" handler="do_setup"/>
+ </object>
+ </child>
+ <child>
+ <object class="BoxesAssistantSetupPage" id="setup_page">
+ <signal name="done" handler="do_review"/>
+ </object>
+ </child>
+ <child>
+ <object class="BoxesAssistantReviewPage" id="review_page">
+ <signal name="done" handler="do_create"/>
+ </object>
+ </child>
+ </object>
+ </child>
+ </object>
+ </child>
+
+ <child type="action">
+ <object class="GtkButton" id="previous_button">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">Cancel</property>
+ <signal name="clicked" handler="on_previous_button_clicked"/>
+ </object>
+ </child>
+ <child type="action">
+ <object class="GtkButton" id="next_button">
+ <property name="visible">False</property>
+ <property name="label" translatable="yes">Next</property>
+ <signal name="clicked" handler="on_next_button_clicked"/>
+ </object>
+ </child>
+
+ <action-widgets>
+ <action-widget response="cancel">previous_button</action-widget>
+ <action-widget response="ok">next_button</action-widget>
+ </action-widgets>
+
+ </template>
+</interface>
diff --git a/data/ui/collection-toolbar.ui b/data/ui/collection-toolbar.ui
index a9810b6e..ad117123 100644
--- a/data/ui/collection-toolbar.ui
+++ b/data/ui/collection-toolbar.ui
@@ -213,6 +213,13 @@
<signal name="clicked" handler="on_new_vm_btn_clicked"/>
</object>
</child>
+ <child>
+ <object class="GtkModelButton">
+ <property name="visible">True</property>
+ <property name="text" translatable="yes">Create a Virtual Machine…</property>
+ <signal name="clicked" handler="on_create_vm_btn_clicked"/>
+ </object>
+ </child>
<child>
<object class="GtkModelButton">
<property name="visible">True</property>
diff --git a/src/app-window.vala b/src/app-window.vala
index 82a9f834..51556afc 100644
--- a/src/app-window.vala
+++ b/src/app-window.vala
@@ -303,6 +303,10 @@ public void show_remote_connection_assistant () {
new Boxes.RemoteConnectionAssistant (this).run ();
}
+ public void show_vm_assistant () {
+ new Boxes.VMAssistant (this).run ();
+ }
+
public void show_properties () {
if (current_item != null) {
if (ui_state == UIState.COLLECTION && selection_mode)
diff --git a/src/assistant/assistant-page.vala b/src/assistant/assistant-page.vala
new file mode 100644
index 00000000..589500db
--- /dev/null
+++ b/src/assistant/assistant-page.vala
@@ -0,0 +1,17 @@
+using Gtk;
+
+private abstract class Boxes.AssistantPage : Gtk.Box {
+ protected Object? artifact;
+ public bool skip = false;
+ protected signal void done (Object artifact);
+
+ public virtual string title {
+ protected set; get;
+ }
+
+ public async virtual void next () {
+ done (artifact);
+ }
+
+ public abstract void cleanup ();
+}
diff --git a/src/assistant/downloads-page.vala b/src/assistant/downloads-page.vala
new file mode 100644
index 00000000..8872117b
--- /dev/null
+++ b/src/assistant/downloads-page.vala
@@ -0,0 +1,96 @@
+// This file is part of GNOME Boxes. License: LGPLv2+
+
+public enum AssistantDownloadsPageView {
+ RECOMMENDED,
+ SEARCH_RESULTS,
+ NO_RESULTS,
+}
+
+[GtkTemplate (ui = "/org/gnome/Boxes/ui/assistant/pages/downloads-page.ui")]
+public class Boxes.AssistantDownloadsPage : Gtk.Stack {
+ private OSDatabase os_db = new OSDatabase ();
+ public DownloadsSearch search { private set; get; }
+
+ public DownloadChosenFunc download_chosen_func;
+
+ [GtkChild]
+ private Gtk.ListBox listbox;
+ [GtkChild]
+ private Gtk.ListBox recommended_listbox;
+
+ private GLib.ListStore recommended_model;
+
+ public signal void media_selected (string url);
+
+ private AssistantDownloadsPageView _page;
+ public AssistantDownloadsPageView page {
+ get { return _page; }
+ set {
+ _page = value;
+
+ switch (_page) {
+ case AssistantDownloadsPageView.SEARCH_RESULTS:
+ visible_child_name = "search-results";
+ break;
+ case AssistantDownloadsPageView.NO_RESULTS:
+ visible_child_name = "no-results";
+ break;
+ case AssistantDownloadsPageView.RECOMMENDED:
+ default:
+ visible_child_name = "recommended";
+ break;
+ }
+ }
+ }
+
+ construct {
+ os_db.load.begin ();
+
+ search = new DownloadsSearch ();
+
+ recommended_model = new GLib.ListStore (typeof (Osinfo.Media));
+ recommended_listbox.bind_model (recommended_model, create_downloads_entry);
+ populate_recommended_list.begin ();
+
+ listbox.bind_model (search.model, create_downloads_entry);
+
+ search.search_changed.connect (set_visible_view);
+ }
+
+ private void set_visible_view () {
+ if (search.text.length == 0) {
+ page = AssistantDownloadsPageView.RECOMMENDED;
+ } else if (search.model.get_n_items () == 0) {
+ page = AssistantDownloadsPageView.NO_RESULTS;
+ } else {
+ page = AssistantDownloadsPageView.SEARCH_RESULTS;
+ }
+ }
+
+ private async void populate_recommended_list () {
+ foreach (var media in yield get_recommended_downloads ()) {
+ recommended_model.append (media);
+ }
+ }
+
+ private Gtk.Widget create_downloads_entry (Object item) {
+ var media = item as Osinfo.Media;
+
+ return new WizardDownloadableEntry (media);
+ }
+
+ [GtkCallback]
+ private void on_listbox_row_activated (Gtk.ListBoxRow row) {
+ // Start to download Entry
+ var entry = row as WizardDownloadableEntry;
+
+ media_selected (entry.url);
+ }
+
+ [GtkCallback]
+ private void on_show_more_button_clicked () {
+ search.show_all ();
+
+ page = AssistantDownloadsPageView.SEARCH_RESULTS;
+ }
+}
diff --git a/src/assistant/index-page.vala b/src/assistant/index-page.vala
new file mode 100644
index 00000000..7a39d3df
--- /dev/null
+++ b/src/assistant/index-page.vala
@@ -0,0 +1,145 @@
+using Gtk;
+
+[GtkTemplate (ui = "/org/gnome/Boxes/ui/assistant/pages/index-page.ui")]
+private class Boxes.AssistantIndexPage : AssistantPage {
+ GLib.ListStore source_model = new GLib.ListStore (typeof (InstallerMedia));
+ GLib.ListStore featured_model = new GLib.ListStore (typeof (Osinfo.Media));
+
+ private AppWindow app_window;
+ private VMAssistant dialog;
+
+ private GLib.List<InstallerMedia> installer_medias;
+
+ private const int MAX_MEDIA_ENTRIES = 3;
+
+ [GtkChild]
+ private Stack stack;
+ [GtkChild]
+ private AssistantDownloadsPage recommended_downloads_page;
+ [GtkChild]
+ private ScrolledWindow home_page;
+
+ [GtkChild]
+ private Box detected_sources_section;
+ [GtkChild]
+ private ListBox source_medias;
+ [GtkChild]
+ private ListBox featured_medias;
+ [GtkChild]
+ private Button expand_detected_sources_list_button;
+
+ construct {
+ populate_media_lists.begin ();
+
+ source_medias.bind_model (source_model, add_media_entry);
+ featured_medias.bind_model (featured_model, add_featured_media_entry);
+ }
+
+ public void setup (AppWindow app_window, VMAssistant dialog) {
+ this.app_window = app_window;
+ this.dialog = dialog;
+ }
+
+ public void go_back () {
+ if (stack.visible_child == home_page) {
+ dialog.close ();
+ }
+
+ stack.visible_child = home_page;
+ dialog.previous_button.label = _("Cancel");
+ }
+
+ private async void populate_media_lists () {
+ var media_manager = MediaManager.get_instance ();
+
+ installer_medias = yield media_manager.list_installer_medias ();
+ populate_detected_sources_list (MAX_MEDIA_ENTRIES);
+
+ var recommended_downloads = yield get_recommended_downloads ();
+ for (var i = 0; i < MAX_MEDIA_ENTRIES; i++)
+ featured_model.append (recommended_downloads.nth (i).data);
+ }
+
+ private void populate_detected_sources_list (int? number_of_items = null) {
+ source_model.remove_all ();
+
+ detected_sources_section.visible = (installer_medias.length () > 0);
+
+ if (number_of_items != null) {
+ for (var i = 0; i < number_of_items; i++)
+ source_model.append (installer_medias.nth (i).data);
+ } else {
+ foreach (var media in installer_medias)
+ source_model.append (media);
+ }
+ }
+
+ private Gtk.Widget add_media_entry (GLib.Object object) {
+ return new WizardMediaEntry (object as InstallerMedia);
+ }
+
+ private Gtk.Widget add_featured_media_entry (GLib.Object object) {
+ return new WizardDownloadableEntry (object as Osinfo.Media);
+ }
+
+ [GtkCallback]
+ private void on_expand_detected_sources_list () {
+ populate_detected_sources_list ();
+
+ expand_detected_sources_list_button.hide ();
+ }
+
+ [GtkCallback]
+ private void on_source_media_selected (Gtk.ListBoxRow row) {
+ done ((row as WizardMediaEntry).media);
+ }
+
+ [GtkCallback]
+ 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);
+ rhel_dialog.bind_property ("visible", dialog, "visible", BindingFlags.INVERT_BOOLEAN);
+
+ int width, height;
+ dialog.get_size_request (out width, out height);
+ rhel_dialog.set_size_request (width, height);
+
+ rhel_dialog.run ();
+ } else
+ on_download_selected (entry.url);
+
+ dialog.close ();
+ }
+
+ public override void cleanup () {
+ }
+
+ [GtkCallback]
+ private async void on_select_file_button_clicked () {
+ var file_chooser = new Gtk.FileChooserNative (_("Select a device or ISO file"),
+ app_window,
+ Gtk.FileChooserAction.OPEN,
+ _("Open"), _("Cancel"));
+ file_chooser.bind_property ("visible", dialog, "visible", BindingFlags.INVERT_BOOLEAN);
+ if (file_chooser.run () == Gtk.ResponseType.ACCEPT) {
+ var media_manager = MediaManager.get_instance ();
+ var media = yield media_manager.create_installer_media_for_path (file_chooser.get_filename (),
+ null); // TODO: make it
cancellable
+ done (media);
+ }
+ }
+
+ [GtkCallback]
+ private void on_download_an_os_button_clicked () {
+ stack.set_visible_child (recommended_downloads_page);
+
+ dialog.previous_button.label = _("Previous");
+ }
+
+ [GtkCallback]
+ private void on_download_selected (string url) {
+ print (url);
+ }
+}
diff --git a/src/assistant/preparation-page.vala b/src/assistant/preparation-page.vala
new file mode 100644
index 00000000..b8a9f5cd
--- /dev/null
+++ b/src/assistant/preparation-page.vala
@@ -0,0 +1,67 @@
+using Gtk;
+
+[GtkTemplate (ui = "/org/gnome/Boxes/ui/assistant/pages/preparation-page.ui")]
+private class Boxes.AssistantPreparationPage : AssistantPage {
+ [GtkChild]
+ private Gtk.Label media_label;
+ [GtkChild]
+ private Gtk.Label status_label;
+ [GtkChild]
+ private Gtk.Image installer_image;
+ [GtkChild]
+ private Gtk.ProgressBar progress_bar;
+
+ private InstallerMedia _media;
+ public InstallerMedia media {
+ get { return _media; }
+ set {
+ _media = value;
+
+ if (_media.os != null) {
+ media_label.label = _media.os.name;
+ Downloader.fetch_os_logo.begin (installer_image, _media.os, 128);
+ }
+ }
+ }
+
+ public void setup (InstallerMedia media) {
+ try {
+ var media_manager = MediaManager.get_instance ();
+ media = media_manager.create_installer_media_from_media (media);
+ } catch (GLib.Error error) {
+ warning ("Failed to setup installation media '%s': %s", media.device_file, error.message);
+ }
+
+ prepare (media);
+
+ skip = true;
+ }
+
+ public async void prepare (InstallerMedia media) {
+ var progress = create_preparation_progress ();
+ if (!yield media.prepare (progress, null)) // add cancellable
+ return;
+
+ progress_bar.fraction = 1.0;
+
+ done (media.get_vm_creator ());
+ }
+
+ private ActivityProgress create_preparation_progress () {
+ var progress = new ActivityProgress ();
+
+ progress.notify["progress"].connect (() => {
+ if (progress.progress - progress_bar.fraction >= 0.01)
+ progress_bar.fraction = progress.progress;
+ });
+ progress_bar.fraction = progress.progress = 0;
+
+ progress.bind_property ("info", status_label, "label");
+
+ return progress;
+ }
+
+ public override void cleanup () {
+ // reset cancellation singleton
+ }
+}
diff --git a/src/assistant/review-page.vala b/src/assistant/review-page.vala
new file mode 100644
index 00000000..d03fe923
--- /dev/null
+++ b/src/assistant/review-page.vala
@@ -0,0 +1,118 @@
+using Gtk;
+
+[GtkTemplate (ui = "/org/gnome/Boxes/ui/assistant/pages/review-page.ui")]
+private class Boxes.AssistantReviewPage : AssistantPage {
+
+ [GtkChild]
+ private WizardSummary summary;
+ [GtkChild]
+ private InfoBar nokvm_infobar;
+ [GtkChild]
+ private Grid customization_grid;
+ [GtkChild]
+ private ToggleButton customize_button;
+ [GtkChild]
+ private Stack customization_stack;
+
+ [GtkCallback]
+ private void on_customize_button_toggled () {
+ customization_stack.set_visible_child (customize_button.active ?
+ customization_grid : summary);
+ }
+
+ public async void setup (VMCreator vm_creator) {
+ try {
+ artifact = yield vm_creator.create_vm (null); // TODO: make it cancellable
+ } catch (IOError.CANCELLED cancel_error) { // We did this, so ignore!
+ } catch (GLib.Error error) {
+ warning ("Box setup failed: %s", error.message);
+ }
+
+ yield populate (artifact as LibvirtMachine);
+ }
+
+ public async void populate (LibvirtMachine machine) {
+ var vm_creator = machine.vm_creator;
+ foreach (var property in vm_creator.install_media.get_vm_properties ())
+ summary.add_property (property.first, property.second);
+
+ try {
+ var config = null as GVirConfig.Domain;
+ yield App.app.async_launcher.launch (() => {
+ config = machine.domain.get_config (GVir.DomainXMLFlags.INACTIVE);
+ });
+
+ var memory = format_size (config.memory * Osinfo.KIBIBYTES, FormatSizeFlags.IEC_UNITS);
+ summary.add_property (_("Memory"), memory);
+ } catch (GLib.Error error) {
+ warning ("Failed to get configuration for machine '%s': %s", machine.name, error.message);
+ }
+
+ if (!machine.importing && machine.storage_volume != null) {
+ try {
+ var volume_info = machine.storage_volume.get_info ();
+ var capacity = format_size (volume_info.capacity);
+ summary.add_property (_("Disk"),
+ // Translators: This is disk size. E.g "1 GB maximum".
+ _("%s maximum").printf (capacity));
+ } catch (GLib.Error error) {
+ warning ("Failed to get information on volume '%s': %s",
+ machine.storage_volume.get_name (),
+ error.message);
+ }
+
+ nokvm_infobar.visible = (machine.domain_config.get_virt_type () !=
GVirConfig.DomainVirtType.KVM);
+ }
+
+ populate_customization_grid (machine);
+ }
+
+ private void populate_customization_grid (LibvirtMachine machine) {
+ var resource_properties = new GLib.List<Boxes.Property> ();
+ machine.properties.get_resources_properties (ref resource_properties);
+
+ return_if_fail (resource_properties.length () > 0);
+
+ var current_row = 0;
+ foreach (var property in resource_properties) {
+ if (property.widget == null || property.extra_widget == null) {
+ warn_if_reached ();
+
+ continue;
+ }
+
+ property.widget.hexpand = true;
+ customization_grid.attach (property.widget, 0, current_row, 1, 1);
+
+ property.extra_widget.hexpand = true;
+ customization_grid.attach (property.extra_widget, 0, current_row + 1, 1, 1);
+
+ current_row += 2;
+ }
+ customization_grid.show_all ();
+ }
+
+ public override void cleanup () {
+ summary.clear ();
+ nokvm_infobar.hide ();
+
+ if (artifact != null) {
+ App.app.delete_machine (artifact as Machine);
+ }
+
+ foreach (var child in customization_grid.get_children ())
+ customization_grid.remove (child);
+ }
+
+ public override async void next () {
+ if (artifact == null) {
+ var wait = notify["artifact"].connect (() => {
+ next.callback ();
+ });
+ yield;
+ disconnect (wait);
+ }
+
+ done (artifact);
+ }
+}
diff --git a/src/assistant/rhel-download-dialog.vala b/src/assistant/rhel-download-dialog.vala
new file mode 100644
index 00000000..48bd1fb9
--- /dev/null
+++ b/src/assistant/rhel-download-dialog.vala
@@ -0,0 +1,184 @@
+// This file is part of GNOME Boxes. License: LGPLv2+
+using Gtk;
+
+[GtkTemplate (ui = "/org/gnome/Boxes/ui/assistant/rhel-download-dialog.ui")]
+private class Boxes.RHELDownloadDialog : Gtk.Dialog {
+ private AppWindow app_window;
+
+ [GtkChild]
+ private Gtk.ProgressBar progress_bar;
+ [GtkChild]
+ private WebKit.WebView web_view;
+
+ private uint hide_progress_bar_id;
+ private const uint progress_bar_id_timeout = 500; // 500ms
+
+ private bool is_rhel8 = false;
+
+ private GLib.Cancellable cancellable = new GLib.Cancellable ();
+
+ construct {
+ var context = web_view.get_context ();
+ var language_names = GLib.Intl.get_language_names ();
+ context.set_preferred_languages (language_names);
+
+ cancellable.connect (() => {
+ web_view.stop_loading ();
+ web_view.load_uri ("about:blank");
+
+ var data_manager = web_view.get_website_data_manager ();
+ data_manager.clear.begin (WebKit.WebsiteDataTypes.COOKIES, 0, null);
+ });
+ }
+
+ public RHELDownloadDialog (AppWindow app_window, Osinfo.Os os) {
+ set_transient_for (app_window);
+
+ 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;
+
+ is_rhel8 = os.id.has_prefix ("http://redhat.com/rhel/8");
+
+ web_view.load_uri (authentication_uri);
+ }
+
+ [GtkCallback]
+ private bool on_decide_policy (WebKit.WebView web_view,
+ WebKit.PolicyDecision decision,
+ WebKit.PolicyDecisionType decision_type) {
+ if (decision_type != WebKit.PolicyDecisionType.NAVIGATION_ACTION)
+ return false;
+
+ var action = (decision as WebKit.NavigationPolicyDecision).get_navigation_action ();
+ var request = action.get_request ();
+ var request_uri = request.get_uri ();
+
+ if (!request_uri.has_prefix ("https://developers.redhat.com/products/rhel") &&
+ !request_uri.has_prefix ("https://access.cdn.redhat.com"))
+ return false;
+
+ var soup_request_uri = new Soup.URI (request_uri);
+ var query = soup_request_uri.get_query ();
+ if (query == null)
+ return false;
+
+ var key_value_pairs = Soup.Form.decode (query);
+
+ var download_uri = is_rhel8 ? request_uri : key_value_pairs.lookup ("tcDownloadURL");
+ if (download_uri == null)
+ return false;
+
+ 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);
+
+ decision.ignore ();
+ this.close ();
+
+ return true;
+ }
+
+ public override void dispose () {
+ if (hide_progress_bar_id != 0) {
+ GLib.Source.remove (hide_progress_bar_id);
+ hide_progress_bar_id = 0;
+ }
+
+ base.dispose ();
+ }
+
+ [GtkCallback]
+ private bool on_context_menu (WebKit.WebView web_view,
+ WebKit.ContextMenu context_menu,
+ Gdk.Event event,
+ WebKit.HitTestResult hit_test_result) {
+ var items_to_remove = new GLib.List<WebKit.ContextMenuItem> ();
+
+ foreach (var item in context_menu.get_items ()) {
+ var action = item.get_stock_action ();
+ if (action == WebKit.ContextMenuAction.GO_BACK ||
+ action == WebKit.ContextMenuAction.GO_FORWARD ||
+ action == WebKit.ContextMenuAction.DOWNLOAD_AUDIO_TO_DISK ||
+ action == WebKit.ContextMenuAction.DOWNLOAD_IMAGE_TO_DISK ||
+ action == WebKit.ContextMenuAction.DOWNLOAD_LINK_TO_DISK ||
+ action == WebKit.ContextMenuAction.DOWNLOAD_VIDEO_TO_DISK ||
+ action == WebKit.ContextMenuAction.OPEN_AUDIO_IN_NEW_WINDOW ||
+ action == WebKit.ContextMenuAction.OPEN_FRAME_IN_NEW_WINDOW ||
+ action == WebKit.ContextMenuAction.OPEN_IMAGE_IN_NEW_WINDOW ||
+ action == WebKit.ContextMenuAction.OPEN_LINK_IN_NEW_WINDOW ||
+ action == WebKit.ContextMenuAction.OPEN_VIDEO_IN_NEW_WINDOW ||
+ action == WebKit.ContextMenuAction.RELOAD ||
+ action == WebKit.ContextMenuAction.STOP) {
+ items_to_remove.prepend (item);
+ }
+ }
+
+ foreach (var item in items_to_remove) {
+ context_menu.remove (item);
+ }
+
+ var separators_to_remove = new GLib.List<WebKit.ContextMenuItem> ();
+ WebKit.ContextMenuAction previous_action = WebKit.ContextMenuAction.NO_ACTION; // same as a separator
+
+ foreach (var item in context_menu.get_items ()) {
+ var action = item.get_stock_action ();
+ if (action == WebKit.ContextMenuAction.NO_ACTION && action == previous_action)
+ separators_to_remove.prepend (item);
+
+ previous_action = action;
+ }
+
+ foreach (var item in separators_to_remove) {
+ context_menu.remove (item);
+ }
+
+ var n_items = context_menu.get_n_items ();
+ return n_items == 0;
+ }
+
+ [GtkCallback]
+ private void on_notify_estimated_load_progress () {
+ if (hide_progress_bar_id != 0) {
+ GLib.Source.remove (hide_progress_bar_id);
+ hide_progress_bar_id = 0;
+ }
+
+ string? uri = web_view.get_uri ();
+ if (uri == null || uri == "about:blank")
+ return;
+
+ var progress = web_view.get_estimated_load_progress ();
+ bool loading = web_view.is_loading;
+
+ if (progress == 1.0 || !loading) {
+ hide_progress_bar_id = GLib.Timeout.add (progress_bar_id_timeout, () => {
+ progress_bar.hide ();
+ hide_progress_bar_id = 0;
+ return GLib.Source.REMOVE;
+ });
+ } else {
+ progress_bar.show ();
+ }
+
+ progress_bar.set_fraction (loading || progress == 1.0 ? progress : 0.0);
+ }
+
+ public override void close () {
+ cancellable.cancel ();
+
+ base.close ();
+ }
+}
diff --git a/src/assistant/setup-page.vala b/src/assistant/setup-page.vala
new file mode 100644
index 00000000..64279ebd
--- /dev/null
+++ b/src/assistant/setup-page.vala
@@ -0,0 +1,34 @@
+using Gtk;
+
+[GtkTemplate (ui = "/org/gnome/Boxes/ui/assistant/pages/setup-page.ui")]
+private class Boxes.AssistantSetupPage : AssistantPage {
+ [GtkChild]
+ private Box setup_box;
+
+ public async void setup (VMCreator vm_creator) {
+ this.artifact = vm_creator;
+
+ vm_creator.install_media.populate_setup_box (setup_box);
+ if (!vm_creator.install_media.need_user_input_for_vm_creation &&
+ vm_creator.install_media.ready_to_create) {
+ done (vm_creator);
+ }
+
+ skip = !vm_creator.install_media.need_user_input_for_vm_creation;
+ }
+
+ public override async void next () {
+ var vm_creator = artifact as VMCreator;
+ if (vm_creator.install_media.ready_to_create) {
+ done (vm_creator);
+ }
+ }
+
+ public override void cleanup () {
+ if (!skip)
+ return;
+
+ foreach (var child in setup_box.get_children ())
+ child.destroy ();
+ }
+}
diff --git a/src/assistant/vm-assistant.vala b/src/assistant/vm-assistant.vala
new file mode 100644
index 00000000..164f1796
--- /dev/null
+++ b/src/assistant/vm-assistant.vala
@@ -0,0 +1,130 @@
+// This file is part of GNOME Boxes. License: LGPLv2+
+using Gtk;
+
+[GtkTemplate (ui = "/org/gnome/Boxes/ui/assistant/vm-assistant.ui")]
+private class Boxes.VMAssistant : Gtk.Dialog {
+ [GtkChild]
+ private Stack pages;
+ [GtkChild]
+ private AssistantIndexPage index_page;
+ [GtkChild]
+ private AssistantPreparationPage preparation_page;
+ [GtkChild]
+ private AssistantSetupPage setup_page;
+ [GtkChild]
+ private AssistantReviewPage review_page;
+
+ [GtkChild]
+ public Button previous_button;
+ [GtkChild]
+ private Button next_button;
+
+ private AssistantPage visible_page {
+ get {
+ return pages.get_visible_child () as AssistantPage;
+ }
+ }
+
+ private AssistantPage? previous_page {
+ get {
+ var current_page_index = pages.get_children ().index (visible_page);
+ return pages.get_children ().nth_data (current_page_index - 1) as AssistantPage;
+ }
+ }
+
+ construct {
+ use_header_bar = 1;
+ }
+
+ public VMAssistant (AppWindow app_window) {
+ 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);
+ }
+
+ [GtkCallback]
+ private void update_titlebar_buttons () {
+ var is_index = (visible_page == index_page);
+ var is_last = (visible_page == review_page);
+
+ next_button.visible = !is_index;
+
+ next_button.label = is_last ? _("Create") : _("Next");
+ previous_button.label = is_index ? _("Cancel") : _("Previous");
+
+ title = visible_page.title;
+ }
+
+ [GtkCallback]
+ private void on_previous_button_clicked () {
+ if (visible_page == index_page)
+ index_page.go_back ();
+ else
+ go_back ();
+ }
+
+ private void go_back () {
+ visible_page.cleanup ();
+
+ pages.set_visible_child (previous_page);
+ if (visible_page.skip)
+ go_back ();
+ }
+
+ [GtkCallback]
+ private void on_next_button_clicked () {
+ visible_page.next ();
+ }
+
+ [GtkCallback]
+ private void do_preparation (Object object) {
+ pages.set_visible_child (preparation_page);
+
+ preparation_page.setup (object as InstallerMedia);
+ }
+
+ [GtkCallback]
+ private void do_setup (Object object) {
+ pages.set_visible_child (setup_page);
+
+ var vm_creator = object as VMCreator;
+ vm_creator.install_media.bind_property ("ready-to-create",
+ next_button, "sensitive",
+ BindingFlags.SYNC_CREATE);
+
+ setup_page.setup (vm_creator);
+ }
+
+ [GtkCallback]
+ private async void do_review (Object object) {
+ pages.set_visible_child (review_page);
+
+ review_page.setup (object as VMCreator);
+ }
+
+ [GtkCallback]
+ private async void do_create (Object object) {
+ var machine = object as LibvirtMachine;
+
+ var vm_creator = machine.vm_creator;
+ try {
+ vm_creator.launch_vm (machine);
+ } catch (GLib.Error error) {
+ warning ("Failed to create machine: %s", error.message);
+ }
+
+ vm_creator.install_media.clean_up_preparation_cache ();
+
+ close ();
+ }
+
+ public override void close () {
+ // TODO: Make the Assistant independent from window states
+ App.app.main_window.set_state (UIState.COLLECTION);
+
+ base.close ();
+ }
+}
diff --git a/src/collection-toolbar.vala b/src/collection-toolbar.vala
index 10fd5b94..2e337148 100644
--- a/src/collection-toolbar.vala
+++ b/src/collection-toolbar.vala
@@ -70,6 +70,11 @@ private void on_connect_to_remote_btn_clicked () {
window.show_remote_connection_assistant ();
}
+ [GtkCallback]
+ private void on_create_vm_btn_clicked () {
+ window.show_vm_assistant ();
+ }
+
[GtkCallback]
private void on_back_btn_clicked () {
window.set_state (UIState.COLLECTION);
diff --git a/src/meson.build b/src/meson.build
index 5b59c3b8..4b741984 100644
--- a/src/meson.build
+++ b/src/meson.build
@@ -117,7 +117,15 @@ vala_sources = [
'troubleshoot-log.vala',
'snapshot-list-row.vala',
'snapshots-property.vala',
+ 'assistant/rhel-download-dialog.vala',
'assistant/remote-connection.vala',
+ 'assistant/vm-assistant.vala',
+ 'assistant/assistant-page.vala',
+ 'assistant/index-page.vala',
+ 'assistant/downloads-page.vala',
+ 'assistant/preparation-page.vala',
+ 'assistant/setup-page.vala',
+ 'assistant/review-page.vala',
]
dependencies = [
diff --git a/src/vm-creator.vala b/src/vm-creator.vala
index 8851bd4b..7fe0d7f6 100644
--- a/src/vm-creator.vala
+++ b/src/vm-creator.vala
@@ -3,7 +3,7 @@
using Osinfo;
using GVir;
-private class Boxes.VMCreator {
+private class Boxes.VMCreator : Object {
// Seems installers aren't very consistent about exact number of bytes written so we ought to leave some
margin
// of error. It's better to report '100%' done while it's not exactly 100% than reporting '99%' done
forever..
private const int INSTALL_COMPLETE_PERCENT = 99;
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]