[gnome-boxes/wip/preferences-dialog: 7/7] preferences: Introduce the new preferences dialog




commit 2c2718df2fde8fb833bd0f4f0a17ee648f86a19b
Author: Felipe Borges <felipeborges gnome org>
Date:   Wed Nov 10 16:57:25 2021 +0100

    preferences: Introduce the new preferences dialog
    
    This dialog replaces the existing VM "Properties" widgets entirely.
    
    Most of this dialog uses the libhandy-1 APIs because of its decendent
    libadwaita, which depends on GTK 4. By the time that the Boxes display
    widgets are ported to GTK4, we can switch from libhandy to libadwaita
    with (hopefully) no trouble.
    
    Fixes #534

 data/gnome-boxes.gresource.xml                 |  24 +-
 data/ui/assistant/installation-summary.ui      |  12 -
 data/ui/assistant/pages/review-page.ui         |  84 ++-
 data/ui/preferences/cdrom-row.ui               |  31 +
 data/ui/preferences/device-list-row.ui         |  14 +
 data/ui/preferences/devices-page.ui            |  43 ++
 data/ui/preferences/memory-row.ui              |  17 +
 data/ui/preferences/preferences-toast.ui       |  46 ++
 data/ui/preferences/preferences-window.ui      |  24 +
 data/ui/preferences/resources-page.ui          | 196 ++++++
 data/ui/preferences/shared-folder-popover.ui   | 140 +++++
 data/ui/preferences/shared-folder-row.ui       |  25 +
 data/ui/preferences/shared-folders-widget.ui   |  26 +
 data/ui/{ => preferences}/snapshot-list-row.ui |   0
 data/ui/preferences/snapshots-page.ui          |  61 ++
 data/ui/properties-shared-folder-row.ui        |  61 --
 data/ui/properties-toolbar.ui                  | 180 ------
 data/ui/properties-window.ui                   |  10 -
 data/ui/resource-graph.ui                      |  12 -
 data/ui/shared-folders.ui                      |  47 --
 src/actions-popover.vala                       |  16 +-
 src/app-window.vala                            |  34 +-
 src/app.vala                                   |  34 +-
 src/assistant/installation-summary.vala        |  37 --
 src/assistant/meson.build                      |   1 -
 src/assistant/review-page.vala                 | 107 +---
 src/config-editor.vala                         | 105 ----
 src/display.vala                               |   4 +-
 src/i-properties-provider.vala                 | 286 ---------
 src/libvirt-machine-properties.vala            | 811 -------------------------
 src/libvirt-machine.vala                       | 178 +-----
 src/list-view-row.vala                         |  11 +-
 src/machine.vala                               |  10 +-
 src/meson.build                                |  15 +-
 src/os-database.vala                           |  18 -
 src/preferences/cdrom-row.vala                 |  76 +++
 src/preferences/device-list-row.vala           |  18 +
 src/preferences/devices-page.vala              |  59 ++
 src/preferences/memory-row.vala                |  32 +
 src/preferences/meson.build                    |  14 +
 src/preferences/preferences-toast.vala         |  53 ++
 src/preferences/preferences-window.vala        |  20 +
 src/preferences/ram-row.vala                   |  47 ++
 src/preferences/resources-page.vala            | 327 ++++++++++
 src/preferences/shared-folders-widget.vala     | 115 ++++
 src/{ => preferences}/snapshot-list-row.vala   |  18 +-
 src/preferences/snapshots-page.vala            | 147 +++++
 src/preferences/storage-row.vala               |  84 +++
 src/properties-page-widget.vala                |  71 ---
 src/properties-toolbar.vala                    | 100 ---
 src/properties-window.vala                     | 141 -----
 src/properties.vala                            | 114 ----
 src/remote-machine.vala                        | 135 ----
 src/resource-graph.vala                        |  79 ---
 src/shared-folders.vala                        | 102 ----
 src/snapshots-property.vala                    | 162 -----
 src/spice-display.vala                         | 148 ++---
 src/topbar.vala                                |   9 +-
 src/troubleshoot-log.vala                      |   8 -
 src/unattended-setup-box.vala                  |   2 +-
 src/vnc-display.vala                           |  16 -
 61 files changed, 1797 insertions(+), 3020 deletions(-)
---
diff --git a/data/gnome-boxes.gresource.xml b/data/gnome-boxes.gresource.xml
index 5fe5d7ed..d24a6ed9 100644
--- a/data/gnome-boxes.gresource.xml
+++ b/data/gnome-boxes.gresource.xml
@@ -13,7 +13,6 @@
     <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>
     <file preprocess="xml-stripblanks">ui/icon-view-child.ui</file>
@@ -21,26 +20,17 @@
     <file preprocess="xml-stripblanks">ui/list-view.ui</file>
     <file preprocess="xml-stripblanks">ui/list-view-row.ui</file>
     <file preprocess="xml-stripblanks">ui/notification.ui</file>
-    <file preprocess="xml-stripblanks">ui/properties-shared-folder-row.ui</file>
     <file preprocess="xml-stripblanks">ui/properties-page-widget.ui</file>
-    <file preprocess="xml-stripblanks">ui/properties-toolbar.ui</file>
-    <file preprocess="xml-stripblanks">ui/properties-window.ui</file>
-    <file preprocess="xml-stripblanks">ui/resource-graph.ui</file>
     <file preprocess="xml-stripblanks">ui/searchbar.ui</file>
     <file preprocess="xml-stripblanks">ui/selectionbar.ui</file>
     <file preprocess="xml-stripblanks">ui/selection-toolbar.ui</file>
-    <file preprocess="xml-stripblanks">ui/shared-folders.ui</file>
-    <file preprocess="xml-stripblanks">ui/shared-folder-popover.ui</file>
-    <file preprocess="xml-stripblanks">ui/snapshot-list-row.ui</file>
     <file preprocess="xml-stripblanks">ui/machine-config-editor.ui</file>
     <file preprocess="xml-stripblanks">ui/topbar.ui</file>
     <file preprocess="xml-stripblanks">ui/transfer-info-row.ui</file>
     <file preprocess="xml-stripblanks">ui/transfer-popover.ui</file>
-    <file preprocess="xml-stripblanks">ui/troubleshoot-log.ui</file>
     <file preprocess="xml-stripblanks">ui/troubleshoot-view.ui</file>
     <file preprocess="xml-stripblanks">ui/unattended-setup-box.ui</file>
     <!-- VM Creation Assistant -->
-    <file preprocess="xml-stripblanks">ui/assistant/installation-summary.ui</file>
     <file preprocess="xml-stripblanks">ui/assistant/downloadable-entry.ui</file>
     <file preprocess="xml-stripblanks">ui/assistant/media-entry.ui</file>
     <file preprocess="xml-stripblanks">ui/assistant/vm-assistant.ui</file>
@@ -52,6 +42,20 @@
     <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>
+    <!-- VM Preferences window -->
+    <file preprocess="xml-stripblanks">ui/preferences/cdrom-row.ui</file>
+    <file preprocess="xml-stripblanks">ui/preferences/devices-page.ui</file>
+    <file preprocess="xml-stripblanks">ui/preferences/device-list-row.ui</file>
+    <file preprocess="xml-stripblanks">ui/preferences/memory-row.ui</file>
+    <file preprocess="xml-stripblanks">ui/preferences/preferences-window.ui</file>
+    <file preprocess="xml-stripblanks">ui/preferences/preferences-toast.ui</file>
+    <file preprocess="xml-stripblanks">ui/preferences/resources-page.ui</file>
+    <file preprocess="xml-stripblanks">ui/preferences/shared-folders-widget.ui</file>
+    <file preprocess="xml-stripblanks">ui/preferences/shared-folder-popover.ui</file>
+    <file preprocess="xml-stripblanks">ui/preferences/shared-folder-row.ui</file>
+    <file preprocess="xml-stripblanks">ui/preferences/snapshot-list-row.ui</file>
+    <file preprocess="xml-stripblanks">ui/preferences/snapshots-page.ui</file>
+
     <!-- Welcome Tutorial -->
     <file preprocess="xml-stripblanks">ui/welcome-tutorial.ui</file>
     <file preprocess="xml-stripblanks">ui/welcome-tutorial-page.ui</file>
diff --git a/data/ui/assistant/pages/review-page.ui b/data/ui/assistant/pages/review-page.ui
index 9e71a475..5bcdd6a2 100644
--- a/data/ui/assistant/pages/review-page.ui
+++ b/data/ui/assistant/pages/review-page.ui
@@ -52,57 +52,77 @@ Check your BIOS settings to enable them.</property>
           </object>
         </child>
       </object>
-
-      <packing>
-        <property name="expand">False</property>
-        <property name="fill">False</property>
-      </packing>
     </child>
 
     <child>
-      <object class="GtkBox">
+      <object class="GtkListBox">
         <property name="visible">True</property>
+        <style>
+          <class name="content"/>
+        </style>
+
         <child>
-          <object class="GtkLabel">
+          <object class="HdyActionRow" id="os_row">
+            <property name="activatable">False</property>
+            <property name="title" translatable="yes">Operating System</property>
+
+            <child>
+              <object class="GtkLabel" id="os_label">
+                <property name="visible">True</property>
+                <style>
+                  <class name="dim-label"/>
+                </style>
+              </object>
+            </child>
+          </object>
+        </child>
+
+        <child>
+          <object class="BoxesRamRow" id="ram_row">
             <property name="visible">True</property>
-            <property name="label" translatable="yes">Resource Allocation</property>
-            <style>
-              <class name="bold-label"/>
-            </style>
+            <property name="title" translatable="yes">Memory</property>
           </object>
         </child>
+
         <child>
-          <object class="GtkToggleButton" id="customize_button">
+          <object class="BoxesStorageRow" id="storage_row">
             <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"/>
+            <property name="title" translatable="yes">Storage limit</property>
           </object>
         </child>
-      </object>
-    </child>
 
-    <child>
-      <object class="GtkStack" id="customization_stack">
-        <property name="visible">True</property>
         <child>
-          <object class="BoxesInstallationSummary" id="summary"/>
+          <object class="HdyActionRow" id="unattended_username_row">
+            <property name="title" translatable="yes">Username</property>
+
+            <child>
+              <object class="GtkLabel" id="username_label">
+                <property name="visible">True</property>
+                <style>
+                  <class name="dim-label"/>
+                </style>
+
+              </object>
+            </child>
+          </object>
         </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 class="HdyActionRow" id="unattended_password_row">
+            <property name="title" translatable="yes">Password</property>
+
+            <child>
+              <object class="GtkLabel" id="password_label">
+                <property name="visible">True</property>
+                <style>
+                  <class name="dim-label"/>
+                </style>
+
+              </object>
+            </child>
           </object>
         </child>
       </object>
-
-      <packing>
-        <property name="expand">True</property>
-        <property name="fill">False</property>
-      </packing>
     </child>
-
   </template>
 </interface>
diff --git a/data/ui/preferences/cdrom-row.ui b/data/ui/preferences/cdrom-row.ui
new file mode 100644
index 00000000..470b57f1
--- /dev/null
+++ b/data/ui/preferences/cdrom-row.ui
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interface>
+  <template class="BoxesCdromRow" parent="HdyActionRow">
+    <property name="can_focus">True</property>
+    <property name="title" translatable="yes">No CDROM/DVD image</property>
+
+    <child>
+      <object class="GtkStack" id="stack">
+        <property name="visible">True</property>
+
+        <child>
+          <object class="GtkButton" id="select_button">
+            <property name="visible">True</property>
+            <property name="valign">center</property>
+            <property name="label" translatable="yes">Select</property>
+            <signal name="clicked" handler="on_select_button_clicked"/>
+          </object>
+        </child>
+
+        <child>
+          <object class="GtkButton" id="remove_button">
+            <property name="visible">True</property>
+            <property name="valign">center</property>
+            <property name="label" translatable="yes">Remove</property>
+            <signal name="clicked" handler="on_remove_button_clicked"/>
+          </object>
+        </child>
+      </object>
+    </child>
+  </template>
+</interface>
diff --git a/data/ui/preferences/device-list-row.ui b/data/ui/preferences/device-list-row.ui
new file mode 100644
index 00000000..5c4df00a
--- /dev/null
+++ b/data/ui/preferences/device-list-row.ui
@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interface>
+  <template class="BoxesDeviceListRow" parent="HdyActionRow">
+    <property name="visible">True</property>
+    <property name="can_focus">True</property>
+
+    <child>
+      <object class="GtkSwitch" id="toggle">
+        <property name="visible">True</property>
+        <property name="valign">center</property>
+      </object>
+    </child>
+  </template>
+</interface>
diff --git a/data/ui/preferences/devices-page.ui b/data/ui/preferences/devices-page.ui
new file mode 100644
index 00000000..02e9b634
--- /dev/null
+++ b/data/ui/preferences/devices-page.ui
@@ -0,0 +1,43 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interface>
+  <requires lib="gtk+" version="3.0"/>
+  <template class="BoxesDevicesPage" parent="HdyPreferencesPage">
+    <property name="visible">True</property>
+    <property name="icon_name">input-mouse-symbolic</property>
+    <property name="title" translatable="yes">Devices &amp; Shares</property>
+
+    <child>
+      <object class="HdyPreferencesGroup">
+        <property name="visible">True</property>
+        <property name="title" translatable="yes">USB Devices</property>
+
+        <child>
+          <object class="GtkListBox" id="listbox">
+            <property name="visible">True</property>
+            <property name="selection-mode">none</property>
+            <style>
+              <class name="content"/>
+            </style>
+          </object>
+        </child>
+      </object>
+    </child>
+
+    <child>
+      <object class="BoxesSharedFoldersWidget" id="shared_folders_widget"/>
+    </child>
+
+    <child>
+      <object class="HdyPreferencesGroup">
+        <property name="visible">True</property>
+        <property name="title" translatable="yes">CDROM/DVD Drive</property>
+
+        <child>
+          <object class="BoxesCdromRow" id="cdrom_row">
+            <property name="visible">True</property>
+          </object>
+        </child>
+      </object>
+    </child>
+  </template>
+</interface>
diff --git a/data/ui/preferences/memory-row.ui b/data/ui/preferences/memory-row.ui
new file mode 100644
index 00000000..c6f3265c
--- /dev/null
+++ b/data/ui/preferences/memory-row.ui
@@ -0,0 +1,17 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interface>
+  <template class="BoxesMemoryRow" parent="HdyActionRow">
+    <property name="can_focus">True</property>
+    <property name="activatable">False</property>
+
+    <child>
+      <object class="GtkSpinButton" id="spin_button">
+        <property name="visible">True</property>
+        <property name="can_focus">True</property>
+        <property name="valign">center</property>
+        <signal name="input" handler="on_spin_button_input"/>
+        <signal name="output" handler="on_spin_button_output"/>
+      </object>
+    </child>
+  </template>
+</interface>
diff --git a/data/ui/preferences/preferences-toast.ui b/data/ui/preferences/preferences-toast.ui
new file mode 100644
index 00000000..75306c09
--- /dev/null
+++ b/data/ui/preferences/preferences-toast.ui
@@ -0,0 +1,46 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interface>
+  <template class="BoxesPreferencesToast" parent="GtkBox">
+    <property name="visible">True</property>
+    <property name="can_focus">True</property>
+    <property name="valign">end</property>
+    <style>
+      <class name="app-notification"/>
+    </style>
+
+    <child>
+      <object class="GtkLabel" id="label">
+        <property name="visible">True</property>
+        <property name="halign">start</property>
+        <property name="hexpand">True</property>
+        <property name="wrap">True</property>
+        <property name="lines">2</property>
+      </object>
+    </child>
+
+    <child>
+      <object class="GtkButton" id="button">
+        <property name="visible">True</property>
+        <property name="label" translatable="yes">Undo</property>
+        <signal name="clicked" handler="on_undo_button_clicked"/>
+      </object>
+    </child>
+
+    <child>
+      <object class="GtkButton">
+        <property name="visible">True</property>
+        <signal name="clicked" handler="on_dismiss_button_clicked"/>
+        <style>
+          <class name="flat"/>
+        </style>
+
+        <child>
+          <object class="GtkImage">
+            <property name="visible">True</property>
+            <property name="icon-name">window-close-symbolic</property>
+          </object>
+        </child>
+      </object>
+    </child>
+  </template>
+</interface>
diff --git a/data/ui/preferences/preferences-window.ui b/data/ui/preferences/preferences-window.ui
new file mode 100644
index 00000000..ace4ce98
--- /dev/null
+++ b/data/ui/preferences/preferences-window.ui
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interface>
+  <template class="BoxesPreferencesWindow" parent="HdyPreferencesWindow">
+    <property name="role">boxes-vm-preferences</property>
+    <property name="modal">True</property>
+    <property name="window-position">center</property>
+    <property name="destroy-with-parent">True</property>
+    <property name="icon-name">gtk-preferences</property>
+    <property name="can-swipe-back">True</property>
+    <signal name="delete-event" handler="gtk_widget_hide_on_delete"/>
+
+    <child>
+      <object class="BoxesResourcesPage" id="resources_page"/>
+    </child>
+
+    <child>
+      <object class="BoxesDevicesPage" id="devices_page"/>
+    </child>
+
+    <child>
+      <object class="BoxesSnapshotsPage" id="snapshots_page"/>
+    </child>
+  </template>
+</interface>
diff --git a/data/ui/preferences/resources-page.ui b/data/ui/preferences/resources-page.ui
new file mode 100644
index 00000000..cec4d6a5
--- /dev/null
+++ b/data/ui/preferences/resources-page.ui
@@ -0,0 +1,196 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interface>
+  <requires lib="gtk+" version="3.0"/>
+  <template class="BoxesResourcesPage" parent="HdyPreferencesPage">
+    <property name="visible">True</property>
+    <property name="icon_name">applications-system-symbolic</property>
+    <property name="title" translatable="yes">Resources</property>
+
+    <child>
+      <object class="HdyPreferencesGroup">
+        <property name="visible">True</property>
+
+        <child>
+          <object class="GtkBox">
+            <property name="visible">True</property>
+            <property name="spacing">40</property>
+
+            <child>
+              <object class="GtkLabel">
+                <property name="visible">True</property>
+                <property name="valign">center</property>
+                <property name="label" translatable="yes">_Name</property>
+                <property name="use-underline">True</property>
+                <attributes>
+                  <attribute name="weight" value="bold"/>
+                </attributes>
+              </object>
+            </child>
+
+            <child>
+              <object class="GtkEntry" id="box_name_entry">
+                <property name="visible">True</property>
+                <property name="valign">center</property>
+                <property name="hexpand">True</property>
+              </object>
+            </child>
+          </object>
+        </child>
+      </object>
+    </child>
+
+    <child>
+      <object class="HdyPreferencesGroup" id="address_group">
+
+        <child>
+          <object class="HdyActionRow" id="ip_address_row">
+            <property name="activatable">False</property>
+            <property name="title" translatable="yes">IP Address</property>
+
+            <child>
+              <object class="GtkLabel" id="ip_address_label">
+                <property name="visible">True</property>
+                <property name="selectable">True</property>
+                <style>
+                  <class name="dim-label"/>
+                </style>
+              </object>
+            </child>
+          </object>
+        </child>
+      </object>
+    </child>
+
+    <child>
+      <object class="HdyPreferencesGroup" id="resources_group">
+        <property name="visible">True</property>
+        <property name="title" translatable="yes">Resources</property>
+
+        <child>
+          <object class="BoxesRamRow" id="ram_row">
+            <property name="visible">True</property>
+            <property name="title" translatable="yes">Memory</property>
+          </object>
+        </child>
+
+        <child>
+          <object class="BoxesStorageRow" id="storage_row">
+            <property name="visible">True</property>
+            <property name="title" translatable="yes">Storage limit</property>
+          </object>
+        </child>
+
+        <child>
+          <object class="HdyActionRow">
+            <property name="visible">True</property>
+            <property name="activatable">False</property>
+            <property name="title" translatable="yes">CPUs</property>
+            <property name="use-underline">True</property>
+
+            <child>
+              <object class="GtkSpinButton" id="cpus_spin_button">
+                <property name="visible">True</property>
+                <property name="can_focus">True</property>
+                <property name="input_purpose">number</property>
+                <property name="valign">center</property>
+              </object>
+            </child>
+          </object>
+        </child>
+
+        <child>
+          <object class="HdyActionRow" id="acceleration_3d_row">
+            <property name="visible">True</property>
+            <property name="activatable">False</property>
+            <property name="title" translatable="yes">3D acceleration</property>
+            <property name="use-underline">True</property>
+
+            <child>
+              <object class="GtkSwitch" id="acceleration_3d_toggle">
+                <property name="visible">True</property>
+                <property name="valign">center</property>
+              </object>
+            </child>
+          </object>
+        </child>
+      </object>
+    </child>
+
+    <child>
+      <object class="HdyPreferencesGroup">
+        <property name="visible">True</property>
+
+        <child>
+          <object class="HdyActionRow">
+            <property name="visible">True</property>
+            <property name="activatable">False</property>
+            <property name="title" translatable="yes">Allow running in background</property>
+            <property name="use-underline">True</property>
+
+            <child>
+              <object class="GtkSwitch" id="run_in_bg_toggle">
+                <property name="visible">True</property>
+                <property name="valign">center</property>
+                <signal name="activate" handler="on_run_in_bg_toggled"/>
+              </object>
+            </child>
+          </object>
+        </child>
+      </object>
+    </child>
+
+    <child>
+      <object class="HdyPreferencesGroup">
+        <property name="visible">True</property>
+        <property name="title" translatable="yes">Configuration</property>
+
+        <child>
+          <object class="HdyActionRow">
+            <property name="visible">True</property>
+            <property name="title" translatable="yes">Troubleshooting Logs</property>
+            <property name="subtitle" translatable="yes">Diagnose problems with your box using the log 
file.</property>
+
+            <child>
+              <object class="GtkButton">
+                <property name="visible">True</property>
+                <property name="valign">center</property>
+                <signal name="clicked" handler="on_troubleshooting_logs_button_clicked"/>
+
+                <child>
+                  <object class="GtkImage">
+                    <property name="visible">True</property>
+                    <property name="icon-name">document-open-symbolic</property>
+                  </object>
+                </child>
+              </object>
+            </child>
+          </object>
+        </child>
+
+        <child>
+          <object class="HdyActionRow">
+            <property name="visible">True</property>
+            <property name="title" translatable="yes">Edit Configuration</property>
+            <property name="subtitle" translatable="yes">Edit the Libvirt domain configuration for this 
box.</property>
+
+            <child>
+              <object class="GtkButton">
+                <property name="visible">True</property>
+                <property name="valign">center</property>
+                <signal name="clicked" handler="on_edit_configuration_button_clicked"/>
+
+                <child>
+                  <object class="GtkImage">
+                    <property name="visible">True</property>
+                    <property name="icon-name">document-edit-symbolic</property>
+                  </object>
+                </child>
+              </object>
+            </child>
+          </object>
+        </child>
+
+      </object>
+    </child>
+  </template>
+</interface>
diff --git a/data/ui/preferences/shared-folder-popover.ui b/data/ui/preferences/shared-folder-popover.ui
new file mode 100644
index 00000000..b6676512
--- /dev/null
+++ b/data/ui/preferences/shared-folder-popover.ui
@@ -0,0 +1,140 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interface>
+  <requires lib="gtk+" version="3.19"/>
+  <template class="BoxesSharedFolderPopover" parent="GtkPopover">
+    <property name="can_focus">False</property>
+    <property name="modal">True</property>
+    <property name="position">bottom</property>
+    <child>
+      <object class="GtkBox">
+        <property name="visible">True</property>
+        <property name="can_focus">False</property>
+        <property name="margin_left">36</property>
+        <property name="margin_right">16</property>
+        <property name="margin_top">12</property>
+        <property name="margin_bottom">12</property>
+        <property name="spacing">23</property>
+        <child>
+          <object class="GtkBox">
+            <property name="visible">True</property>
+            <property name="can_focus">False</property>
+            <property name="orientation">vertical</property>
+            <child>
+              <object class="GtkLabel">
+                <property name="visible">True</property>
+                <property name="can_focus">False</property>
+                <property name="halign">end</property>
+                <property name="valign">start</property>
+                <property name="margin_top">6</property>
+                <property name="label" translatable="yes">Local Folder</property>
+              </object>
+              <packing>
+                <property name="expand">False</property>
+                <property name="fill">True</property>
+                <property name="position">0</property>
+              </packing>
+            </child>
+            <child>
+              <object class="GtkLabel">
+                <property name="visible">True</property>
+                <property name="can_focus">False</property>
+                <property name="halign">end</property>
+                <property name="valign">center</property>
+                <property name="margin_top">20</property>
+                <property name="label" translatable="yes">Name</property>
+              </object>
+              <packing>
+                <property name="expand">False</property>
+                <property name="fill">True</property>
+                <property name="position">1</property>
+              </packing>
+            </child>
+          </object>
+          <packing>
+            <property name="expand">False</property>
+            <property name="fill">True</property>
+            <property name="position">0</property>
+          </packing>
+        </child>
+        <child>
+          <object class="GtkBox">
+            <property name="visible">True</property>
+            <property name="can_focus">False</property>
+            <property name="orientation">vertical</property>
+            <property name="spacing">6</property>
+            <child>
+              <object class="GtkFileChooserButton" id="file_chooser_button">
+                <property name="visible">True</property>
+                <property name="can_focus">False</property>
+                <property name="action">select-folder</property>
+                <property name="title" translatable="yes">Select Shared Folder</property>
+              </object>
+              <packing>
+                <property name="expand">False</property>
+                <property name="fill">True</property>
+                <property name="position">0</property>
+              </packing>
+            </child>
+            <child>
+              <object class="GtkEntry" id="name_entry">
+                <property name="visible">True</property>
+                <property name="can_focus">True</property>
+              </object>
+              <packing>
+                <property name="expand">False</property>
+                <property name="fill">True</property>
+                <property name="position">1</property>
+              </packing>
+            </child>
+            <child>
+              <object class="GtkBox">
+                <property name="visible">True</property>
+                <property name="can_focus">False</property>
+                <property name="spacing">6</property>
+                <property name="homogeneous">True</property>
+                <child>
+                  <object class="GtkButton">
+                    <property name="label" translatable="yes">Cancel</property>
+                    <property name="visible">True</property>
+                    <property name="can_focus">True</property>
+                    <property name="receives_default">True</property>
+                    <signal name="clicked" handler="on_cancel"/>
+                  </object>
+                  <packing>
+                    <property name="expand">True</property>
+                    <property name="fill">True</property>
+                    <property name="position">0</property>
+                  </packing>
+                </child>
+                <child>
+                  <object class="GtkButton">
+                    <property name="label" translatable="yes">Save</property>
+                    <property name="visible">True</property>
+                    <property name="can_focus">True</property>
+                    <property name="receives_default">True</property>
+                    <signal name="clicked" handler="on_save"/>
+                  </object>
+                  <packing>
+                    <property name="expand">True</property>
+                    <property name="fill">True</property>
+                    <property name="position">1</property>
+                  </packing>
+                </child>
+              </object>
+              <packing>
+                <property name="expand">False</property>
+                <property name="fill">True</property>
+                <property name="position">2</property>
+              </packing>
+            </child>
+          </object>
+          <packing>
+            <property name="expand">False</property>
+            <property name="fill">True</property>
+            <property name="position">1</property>
+          </packing>
+        </child>
+      </object>
+    </child>
+  </template>
+</interface>
\ No newline at end of file
diff --git a/data/ui/preferences/shared-folder-row.ui b/data/ui/preferences/shared-folder-row.ui
new file mode 100644
index 00000000..b8147f10
--- /dev/null
+++ b/data/ui/preferences/shared-folder-row.ui
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interface>
+  <template class="BoxesSharedFolderRow" parent="HdyActionRow">
+    <property name="visible">True</property>
+
+    <child>
+      <object class="GtkButton">
+        <property name="visible">True</property>
+        <property name="valign">center</property>
+        <signal name="clicked" handler="on_delete_button_clicked"/>
+        <style>
+          <class name="flat"/>
+        </style>
+
+        <child>
+          <object class="GtkImage">
+            <property name="visible">True</property>
+            <property name="halign">end</property>
+            <property name="icon-name">window-close-symbolic</property>
+          </object>
+        </child>
+      </object>
+    </child>
+  </template>
+</interface>
diff --git a/data/ui/preferences/shared-folders-widget.ui b/data/ui/preferences/shared-folders-widget.ui
new file mode 100644
index 00000000..cc41eb6f
--- /dev/null
+++ b/data/ui/preferences/shared-folders-widget.ui
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interface>
+  <requires lib="gtk+" version="3.19"/>
+  <template class="BoxesSharedFoldersWidget" parent="HdyPreferencesGroup">
+    <property name="visible">True</property>
+    <property name="use-markup">True</property>
+    <property name="title" translatable="yes">Shared Folders</property>
+
+    <child>
+      <object class="GtkStack">
+        <property name="visible">True</property>
+
+        <child>
+          <object class="GtkListBox" id="listbox">
+            <property name="visible">True</property>
+            <property name="selection-mode">none</property>
+            <style>
+              <class name="content"/>
+            </style>
+          </object>
+        </child>
+      </object>
+    </child>
+
+  </template>
+</interface>
diff --git a/data/ui/snapshot-list-row.ui b/data/ui/preferences/snapshot-list-row.ui
similarity index 100%
rename from data/ui/snapshot-list-row.ui
rename to data/ui/preferences/snapshot-list-row.ui
diff --git a/data/ui/preferences/snapshots-page.ui b/data/ui/preferences/snapshots-page.ui
new file mode 100644
index 00000000..bdb48f08
--- /dev/null
+++ b/data/ui/preferences/snapshots-page.ui
@@ -0,0 +1,61 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interface>
+  <template class="BoxesSnapshotsPage" parent="HdyPreferencesPage">
+    <property name="visible">True</property>
+    <property name="icon_name">edit-copy-symbolic</property>
+    <property name="title" translatable="yes">Snapshots</property>
+
+    <child>
+      <object class="HdyPreferencesGroup" id="preferences_group">
+        <property name="visible">True</property>
+        <property name="title" translatable="yes">Snapshots</property>
+
+        <child>
+          <object class="GtkOverlay" id="toast_overlay">
+            <property name="visible">True</property>
+            <property name="vexpand">True</property>
+
+            <child>
+              <object class="GtkStack" id="stack">
+                <property name="visible">True</property>
+
+                <child>
+                  <object class="GtkListBox" id="listbox">
+                    <property name="visible">True</property>
+                    <property name="selection-mode">none</property>
+                    <signal name="add" handler="update_snapshot_stack_page"/>
+                    <signal name="remove" handler="update_snapshot_stack_page"/>
+                    <style>
+                      <class name="content"/>
+                    </style>
+                  </object>
+                </child>
+
+                <child>
+                  <object class="GtkBox" id="activity_page">
+                    <property name="visible">True</property>
+                    <property name="spacing">20</property>
+                    <property name="orientation">vertical</property>
+
+                    <child>
+                      <object class="GtkSpinner">
+                        <property name="visible">True</property>
+                        <property name="active">True</property>
+                      </object>
+                    </child>
+                    <child>
+                      <object class="GtkLabel" id="activity_label">
+                        <property name="visible">True</property>
+                        <property name="label" translatable="yes">Creating new snapshot…</property>
+                      </object>
+                    </child>
+                  </object>
+                </child>
+              </object>
+            </child>
+          </object>
+        </child>
+      </object>
+    </child>
+  </template>
+</interface>
diff --git a/data/ui/properties-window.ui b/data/ui/properties-window.ui
index 8a8e63d3..f8e2dd1e 100644
--- a/data/ui/properties-window.ui
+++ b/data/ui/properties-window.ui
@@ -70,16 +70,6 @@
               </packing>
             </child>
 
-            <child>
-              <object class="BoxesMachineConfigEditor" id="config_editor">
-                <property name="visible">True</property>
-              </object>
-
-              <packing>
-                <property name="name">config_editor</property>
-              </packing>
-            </child>
-
             <child>
               <object class="GtkBox">
                 <property name="visible">True</property>
diff --git a/src/actions-popover.vala b/src/actions-popover.vala
index c953532f..4ed0e194 100644
--- a/src/actions-popover.vala
+++ b/src/actions-popover.vala
@@ -8,7 +8,7 @@
         {"force_shutdown",  force_shutdown_activated},
         {"delete",          delete_activated},
         {"clone",           clone_activated},
-        {"properties",      properties_activated},
+        {"preferences",     preferences_activated},
         {"restart",         restart_activated},
         {"send_file",       send_file_activated}
 
@@ -98,9 +98,9 @@ public void update_for_item (CollectionItem item) {
 
         // Properties (in separate section)
         section = new GLib.Menu ();
-        section.append (_("Properties"), "box.properties");
+        section.append (_("Preferences"), "box.preferences");
         menu.append_section (null, section);
-        var action = action_group.lookup_action ("properties") as GLib.SimpleAction;
+        var action = action_group.lookup_action ("preferences") as GLib.SimpleAction;
         action.set_enabled (!importing);
 
         bind_model (menu, null);
@@ -178,7 +178,13 @@ private void send_file_activated () {
     }
 
 
-    private void properties_activated () {
-        window.show_properties ();
+    private void preferences_activated () {
+        var machine = window.current_item as Machine;
+
+        var preferences = new Boxes.PreferencesWindow () {
+            machine = machine,
+            transient_for = window,
+        };
+        preferences.present ();
     }
 }
diff --git a/src/app-window.vala b/src/app-window.vala
index 770cd436..dfc9f5af 100644
--- a/src/app-window.vala
+++ b/src/app-window.vala
@@ -68,17 +68,10 @@
 
     public Notificationbar notificationbar {
         get {
-            switch (ui_state) {
-            case UIState.PROPERTIES:
-                return props_window.notificationbar;
-            default:
-                return _notificationbar;
-            }
+            return _notificationbar;
         }
     }
 
-    public PropertiesWindow  props_window;
-
     [GtkChild]
     public unowned Searchbar searchbar;
     [GtkChild]
@@ -187,8 +180,6 @@ public void setup_ui () {
 
         group = new Gtk.WindowGroup ();
         group.add_window (this);
-        props_window = new PropertiesWindow (this);
-        group.add_window (props_window);
 
         notify["view-type"].connect (ui_state_changed);
     }
@@ -212,7 +203,6 @@ private void ui_state_changed () {
         foreach (var ui in new Boxes.UI[] { topbar,
                                             icon_view,
                                             list_view,
-                                            props_window,
                                             //wizard_window,
                                             empty_boxes }) {
             ui.set_state (ui_state);
@@ -294,28 +284,6 @@ public void show_welcome_tutorial () {
         }
     }
 
-    public void show_properties () {
-        if (current_item != null) {
-            if (ui_state == UIState.COLLECTION && selection_mode)
-                selection_mode = false;
-            set_state (UIState.PROPERTIES);
-
-            return;
-        }
-
-        var selected_items = view.get_selected_items ();
-
-        if (ui_state == UIState.COLLECTION && selection_mode)
-            selection_mode = false;
-
-        // Show for the first selected item
-        foreach (var item in selected_items) {
-            current_item = item;
-            set_state (UIState.PROPERTIES);
-            break;
-        }
-    }
-
     public void show_send_file () {
         var dialog = new Gtk.FileChooserDialog (
                 _("Select files to transfer"), this, Gtk.FileChooserAction.OPEN,
diff --git a/src/app.vala b/src/app.vala
index f562f30e..31d14b07 100644
--- a/src/app.vala
+++ b/src/app.vala
@@ -412,32 +412,16 @@ public async void add_collection_source (CollectionSource source) throws GLib.Er
             return; // Already added
         }
 
-        switch (source.source_type) {
-        case "vnc":
-        case "spice":
-        case "ssh":
-        case "rdp":
-            try {
-                var machine = new RemoteMachine (source);
-                collection.add_item (machine);
-            } catch (Boxes.Error error) {
-                warning (error.message);
+        Broker? broker = brokers.lookup (source.source_type);
+        if (broker != null) {
+            yield broker.add_source (source);
+            sources.insert (source.name, source);
+            if (source.name == DEFAULT_SOURCE_NAME) {
+                notify_property ("default-connection");
+                notify_property ("default-source");
             }
-            break;
-
-        default:
-            Broker? broker = brokers.lookup (source.source_type);
-            if (broker != null) {
-                yield broker.add_source (source);
-                sources.insert (source.name, source);
-                if (source.name == DEFAULT_SOURCE_NAME) {
-                    notify_property ("default-connection");
-                    notify_property ("default-source");
-                }
-            } else {
-                warning ("Unsupported source type %s", source.source_type);
-            }
-            break;
+        } else {
+            warning ("Unsupported source type %s", source.source_type);
         }
     }
 
diff --git a/src/assistant/meson.build b/src/assistant/meson.build
index a3076271..f2e438f1 100644
--- a/src/assistant/meson.build
+++ b/src/assistant/meson.build
@@ -5,7 +5,6 @@ vala_sources += files(
   'identify-os-page.vala',
   'identify-os-popover.vala',
   'index-page.vala',
-  'installation-summary.vala',
   'media-entry.vala',
   'preparation-page.vala',
   'review-page.vala',
diff --git a/src/assistant/review-page.vala b/src/assistant/review-page.vala
index b65e633b..42e6a21a 100644
--- a/src/assistant/review-page.vala
+++ b/src/assistant/review-page.vala
@@ -1,26 +1,28 @@
 using Gtk;
+using Hdy;
 
 [GtkTemplate (ui = "/org/gnome/Boxes/ui/assistant/pages/review-page.ui")]
 private class Boxes.AssistantReviewPage : AssistantPage {
     [GtkChild]
-    private unowned InstallationSummary summary;
+    private unowned Gtk.InfoBar nokvm_infobar;
     [GtkChild]
-    private unowned InfoBar nokvm_infobar;
+    private unowned Hdy.ActionRow os_row;
     [GtkChild]
-    private unowned Grid customization_grid;
+    private unowned Gtk.Label os_label;
     [GtkChild]
-    private unowned ToggleButton customize_button;
+    private unowned Boxes.RamRow ram_row;
     [GtkChild]
-    private unowned Stack customization_stack;
-    private GLib.List<Boxes.Property> resource_properties;
-
-    private Cancellable? cancellable;
+    private unowned Boxes.StorageRow storage_row;
+    [GtkChild]
+    private unowned Hdy.ActionRow unattended_username_row;
+    [GtkChild]
+    private unowned Gtk.Label username_label;
+    [GtkChild]
+    private unowned Hdy.ActionRow unattended_password_row;
+    [GtkChild]
+    private unowned Gtk.Label password_label;
 
-    [GtkCallback]
-    private void on_customize_button_toggled () {
-        customization_stack.set_visible_child (customize_button.active ?
-                                               customization_grid : summary);
-    }
+    private GLib.Cancellable? cancellable;
 
     public async void setup (VMCreator vm_creator) {
         cancellable = new GLib.Cancellable ();
@@ -36,66 +38,27 @@ public async void setup (VMCreator vm_creator) {
     }
 
     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);
+        var os = yield machine.get_os ();
+        if (os != null) {
+            os_row.visible = true;
+            os_label.label = os.get_name ();
         }
 
-        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);
-            }
+        ram_row.setup (machine);
+        storage_row.setup (machine);
 
-        }
+        bool show_unattended_rows = false;
+        if (machine.vm_creator.install_media is Boxes.UnattendedInstaller) {
+            var installer = machine.vm_creator.install_media as Boxes.UnattendedInstaller;
+            show_unattended_rows = installer.setup_box.express_toggle.active;
 
-        nokvm_infobar.visible = (machine.domain_config.get_virt_type () != GVirConfig.DomainVirtType.KVM);
+            if (!show_unattended_rows)
+                return;
 
-        populate_customization_grid (machine);
-    }
-
-    private void populate_customization_grid (LibvirtMachine machine) {
-        resource_properties = new GLib.List<Boxes.Property> ();
-        machine.properties.get_resources_properties (ref resource_properties);
-
-        return_if_fail (resource_properties.length () > 0);
-
-        foreach (var child in customization_grid.get_children ())
-            customization_grid.remove (child);
-
-        var current_row = 0;
-        foreach (var property in resource_properties) {
-            if (property.widget == null || property.extra_widget == null) {
-                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;
+            username_label.label = installer.setup_box.username;
+            password_label.label = installer.setup_box.hidden_password;
         }
-        customization_grid.show_all ();
+        unattended_username_row.visible = unattended_password_row.visible = show_unattended_rows;
     }
 
     public override void cleanup () {
@@ -104,15 +67,11 @@ public override void cleanup () {
             cancellable = null;
         }
 
-        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 () {
@@ -124,12 +83,6 @@ public override async void next () {
             disconnect (wait);
         }
 
-        // Let's apply all deferred changes in properties.
-        foreach (var property in resource_properties) {
-            property.flush ();
-        }
-        resource_properties = null;
-
         done (artifact);
 
         cancellable.reset ();
diff --git a/src/display.vala b/src/display.vala
index ee9c944c..c31894b1 100644
--- a/src/display.vala
+++ b/src/display.vala
@@ -1,7 +1,7 @@
 // This file is part of GNOME Boxes. License: LGPLv2+
 using Gtk;
 
-private abstract class Boxes.Display: GLib.Object, Boxes.IPropertiesProvider {
+private abstract class Boxes.Display: GLib.Object {
     public abstract string protocol { get; }
     public abstract string? uri { owned get; }
     public virtual bool can_transfer_files { get { return false; } }
@@ -41,8 +41,6 @@ public virtual void transfer_files (GLib.List<string> uris) {
     }
     public abstract void send_keys (uint[] keyvals);
 
-    public abstract List<Boxes.Property> get_properties (Boxes.PropertiesPage page);
-
     protected HashTable<int, Gtk.Widget?> displays;
 
     private int64 started_time;
diff --git a/src/libvirt-machine.vala b/src/libvirt-machine.vala
index b11c2d45..d07d7cac 100644
--- a/src/libvirt-machine.vala
+++ b/src/libvirt-machine.vala
@@ -27,8 +27,6 @@
     // If this machine is currently being imported
     public bool importing { get { return vm_creator != null && vm_creator is VMImporter; } }
 
-    public LibvirtMachineProperties properties;
-
     public bool save_on_quit {
         get { return source.get_boolean ("source", "save-on-quit"); }
         set { source.set_boolean ("source", "save-on-quit", value); }
@@ -155,28 +153,8 @@ public override async void connect_display (Machine.ConnectFlags flags) throws G
         }
     }
 
-    struct MachineStat {
-        int64 timestamp;
-        double cpu_time;
-        double cpu_time_abs;
-        double cpu_guest_percent;
-        double memory_percent;
-        DomainDiskStats disk;
-        double disk_read;
-        double disk_write;
-        DomainInterfaceStats net;
-        double net_read;
-        double net_write;
-    }
-
     private uint shutdown_timeout;
 
-    private uint stats_update_timeout;
-    private Cancellable stats_cancellable;
-
-    const int STATS_SIZE = 20;
-    private MachineStat[] stats;
-
     private bool force_stopped;
     private bool saving; // Machine is being saved currently..
 
@@ -184,11 +162,6 @@ public override async void connect_display (Machine.ConnectFlags flags) throws G
 
     private BoxConfig.SavedProperty[] saved_properties;
 
-    construct {
-        stats = new MachineStat[STATS_SIZE];
-        stats_cancellable = new Cancellable ();
-    }
-
     public void update_domain_config () {
         try {
             domain_config = domain.get_config (GVir.DomainXMLFlags.NONE);
@@ -219,7 +192,6 @@ public async LibvirtMachine (CollectionSource source,
         debug ("new libvirt machine: " + domain.get_name ());
         this.connection = connection;
         this.domain = domain;
-        this.properties = new LibvirtMachineProperties (this);
 
         try {
             var s = domain.get_info ().state;
@@ -227,7 +199,6 @@ public async LibvirtMachine (CollectionSource source,
             case DomainState.RUNNING:
             case DomainState.BLOCKED:
                 state = MachineState.RUNNING;
-                set_stats_enable (true);
                 break;
             case DomainState.PAUSED:
                 state = MachineState.PAUSED;
@@ -272,9 +243,7 @@ else if (force_stopped) {
         notify["state"].connect (() => {
             if (state == MachineState.RUNNING) {
                 reconnect_display ();
-                set_stats_enable (true);
-            } else
-                set_stats_enable (false);
+            }
         });
 
         update_domain_config ();
@@ -302,150 +271,6 @@ else if (force_stopped) {
         this.config.save_properties (this, saved_properties);
     }
 
-    private void update_cpu_stat (DomainInfo info, ref MachineStat stat) {
-        var prev = stats[STATS_SIZE - 1];
-
-        if (info.state == DomainState.CRASHED ||
-            info.state == DomainState.SHUTOFF)
-            return;
-
-        stat.cpu_time = info.cpuTime - prev.cpu_time_abs;
-        stat.cpu_time_abs = info.cpuTime;
-        // hmm, where does this x10 come from?
-        var dt = (stat.timestamp - prev.timestamp) * 10;
-        var percent = stat.cpu_time / dt;
-        percent = percent / info.nrVirtCpu;
-        stat.cpu_guest_percent = percent.clamp (0, 100);
-    }
-
-    private void update_mem_stat (DomainInfo info, ref MachineStat stat) {
-        if (info.state != DomainState.RUNNING)
-            return;
-
-        stat.memory_percent = info.memory * 100.0 / info.maxMem;
-    }
-
-    private async void update_io_stat (DomainInfo info, MachineStat *stat) {
-        if (info.state != DomainState.RUNNING)
-            return;
-
-        try {
-            var disk = get_domain_disk ();
-            if (disk == null)
-                return;
-
-            yield App.app.async_launcher.launch ( () => {
-                    stat.disk = disk.get_stats ();
-                } );
-            var prev = stats[STATS_SIZE - 1];
-            if (prev.disk != null) {
-                stat.disk_read = (stat.disk.rd_bytes - prev.disk.rd_bytes);
-                stat.disk_write = (stat.disk.wr_bytes - prev.disk.wr_bytes);
-            }
-        } catch (GLib.Error err) {
-            warning ("Failed to fetch I/O statistics for %s: %s", name, err.message);
-        }
-    }
-
-    private async void update_net_stat (DomainInfo info, MachineStat *stat) {
-        if (info.state != DomainState.RUNNING)
-            return;
-
-        try {
-            var net = get_domain_network_interface ();
-            if (net == null)
-                return;
-
-            yield App.app.async_launcher.launch ( () => {
-                    stat.net = net.get_stats ();
-                } );
-            var prev = stats[STATS_SIZE - 1];
-            if (prev.net != null) {
-                stat.net_read = (stat.net.rx_bytes - prev.net.rx_bytes);
-                stat.net_write = (stat.net.tx_bytes - prev.net.tx_bytes);
-            }
-        } catch (GLib.Error err) {
-            warning ("Failed to fetch network statistics for %s: %s", name, err.message);
-        }
-    }
-
-    public signal void stats_updated ();
-
-    public double[] cpu_stats;
-    public double[] io_stats;
-    public double[] net_stats;
-    private async void update_stats () {
-        try {
-            var now = get_monotonic_time ();
-            var stat = MachineStat () { timestamp = now };
-            var info = yield domain.get_info_async (stats_cancellable);
-
-            update_cpu_stat (info, ref stat);
-            update_mem_stat (info, ref stat);
-            yield update_io_stat (info, &stat);
-            yield update_net_stat (info, &stat);
-
-            stats = stats[1:STATS_SIZE];
-            stats += stat;
-
-        } catch (IOError.CANCELLED err) {
-            return;
-        } catch (GLib.Error err) {
-            warning (err.message);
-        }
-
-        cpu_stats = {};
-        io_stats = {};
-        net_stats = {};
-
-        foreach (var stat in stats) {
-            cpu_stats += stat.cpu_guest_percent;
-            net_stats += (stat.net_read + stat.net_write);
-            io_stats += (stat.disk_read + stat.disk_write);
-        }
-
-        stats_updated ();
-    }
-
-    private void set_stats_enable (bool enable) {
-        if (enable) {
-            debug ("enable statistics for " + name);
-            if (stats_update_timeout != 0)
-                return;
-
-            stats_cancellable.reset ();
-            var stats_updating = false;
-            stats_update_timeout = Timeout.add_seconds (1, () => {
-                if (stats_updating) {
-                    warning ("Fetching of stats for '%s' is taking too long. Probably a libvirt bug.", name);
-
-                    return true;
-                }
-
-                stats_updating = true;
-                update_stats.begin (() => { stats_updating = false; });
-
-                return true;
-            });
-        } else {
-            debug ("disable statistics for " + name);
-            if (stats_update_timeout != 0) {
-                stats_cancellable.cancel ();
-                GLib.Source.remove (stats_update_timeout);
-            }
-            stats_update_timeout = 0;
-        }
-    }
-
-    public override List<Boxes.Property> get_properties (Boxes.PropertiesPage page) {
-        var list = properties.get_properties (page);
-
-        if (display != null)
-            list.concat (display.get_properties (page));
-
-        return list;
-    }
-
     public bool update_display () throws GLib.Error {
         update_domain_config ();
 
@@ -518,7 +343,6 @@ public override void delete (bool by_user = true) {
         debug ("delete libvirt machine: " + name);
         base.delete (by_user);
 
-        set_stats_enable (false);
         if (shutdown_timeout != 0) {
             Source.remove (shutdown_timeout);
             shutdown_timeout = 0;
diff --git a/src/list-view-row.vala b/src/list-view-row.vala
index 09c601c6..a26a1427 100644
--- a/src/list-view-row.vala
+++ b/src/list-view-row.vala
@@ -118,10 +118,7 @@ private void update_info () {
     }
 
     private void update_status () {
-        if (machine is RemoteMachine)
-            update_status_label_style (!machine.is_connected);
-        else
-            update_status_label_style (!machine.is_on);
+        update_status_label_style (!machine.is_on);
 
         if (machine.status != null) {
             status_label.label = machine.status;
@@ -129,12 +126,6 @@ private void update_status () {
             return;
         }
 
-        if (machine is RemoteMachine) {
-            status_label.label = machine.is_connected ? _("Connected"): _("Disconnected");
-
-            return;
-        }
-
         if (machine.is_running) {
             status_label.label = _("Running");
 
diff --git a/src/machine.vala b/src/machine.vala
index cdc019a9..b093d465 100644
--- a/src/machine.vala
+++ b/src/machine.vala
@@ -2,7 +2,7 @@
 using Gdk;
 using Gtk;
 
-private abstract class Boxes.Machine: Boxes.CollectionItem, Boxes.IPropertiesProvider {
+private abstract class Boxes.Machine: Boxes.CollectionItem {
     const uint AUTOSAVE_TIMEOUT = 60; // seconds
 
     public Boxes.CollectionSource source;
@@ -354,8 +354,6 @@ protected virtual async void save_real () throws GLib.Error {
         return display.get_pixbuf (0);
     }
 
-    public abstract List<Boxes.Property> get_properties (Boxes.PropertiesPage page);
-
     public abstract async void connect_display (ConnectFlags flags) throws GLib.Error;
     public abstract void restart ();
     public abstract async void clone ();
@@ -610,11 +608,11 @@ private async void try_connect_display (ConnectFlags flags = ConnectFlags.NONE)
             if (this is LibvirtMachine) {
                 Notification.OKFunc troubleshoot = () => {
                     window.current_item = this;
-                    window.show_properties ();
+                    //window.show_properties ();
 
                     var libvirt_machine = this as LibvirtMachine;
-                    var logs = libvirt_machine.properties.collect_logs ();
-                    window.props_window.show_troubleshoot_log (logs);
+                    //FIXME: var logs = libvirt_machine.properties.collect_logs ();
+                    //window.props_window.show_troubleshoot_log (logs);
                 };
 
                 window.notificationbar.display_for_action (msg, _("Troubleshooting Log"), (owned) 
troubleshoot, null);
diff --git a/src/meson.build b/src/meson.build
index def83629..5d7e1127 100644
--- a/src/meson.build
+++ b/src/meson.build
@@ -53,8 +53,6 @@ vala_sources = [
   'display-toolbar.vala',
   'display.vala',
   'downloads-hub.vala',
-  'editable-entry.vala',
-  'i-properties-provider.vala',
   'i-collection-view.vala',
   'icon-view.vala',
   'installer-media.vala',
@@ -66,29 +64,21 @@ vala_sources = [
   'icon-view-child.vala',
   'libvirt-broker.vala',
   'libvirt-machine.vala',
-  'libvirt-machine-properties.vala',
   'list-view.vala',
   'list-view-row.vala',
   'machine.vala',
   'machine-thumbnailer.vala',
   'main.vala',
   'media-manager.vala',
-  'resource-graph.vala',
   'notification.vala',
   'notificationbar.vala',
   'os-database.vala',
   'portals.vala',
-  'properties.vala',
-  'properties-window.vala',
-  'properties-page-widget.vala',
-  'properties-toolbar.vala',
-  'remote-machine.vala',
   'search.vala',
   'searchbar.vala',
   'selectionbar.vala',
   'selection-toolbar.vala',
   'shared-folders.vala',
-  'config-editor.vala',
   'transfer-info-row.vala',
   'troubleshoot-view.vala',
   'topbar.vala',
@@ -108,12 +98,10 @@ vala_sources = [
   'downloader.vala',
   'empty-boxes.vala',
   'tracker-iso-query.vala',
-  'troubleshoot-log.vala',
-  'snapshot-list-row.vala',
-  'snapshots-property.vala',
 ]
 
 subdir('assistant')
+subdir('preferences')
 
 dependencies = [
   config_h,
@@ -123,7 +111,6 @@ dependencies = [
   dependency ('gtk+-3.0', version: '>= 3.22.20'),
   dependency ('gtk-vnc-2.0', version: '>= 0.4.4'),
   dependency ('gvncpulse-1.0'),
-  dependency ('gtksourceview-4'),
   dependency ('libhandy-1', version: '>= 1.0.0'),
   dependency ('libosinfo-1.0', version: '>= 1.7.0'),
   dependency ('libsecret-1'),
diff --git a/src/os-database.vala b/src/os-database.vala
index 5373c0de..a007b219 100644
--- a/src/os-database.vala
+++ b/src/os-database.vala
@@ -42,16 +42,6 @@ public static Resources get_default_resources () {
         return resources;
     }
 
-    public static Resources get_minimum_resources () {
-        var resources = new Resources ("whatever", "x86_64");
-
-        resources.n_cpus = DEFAULT_VCPUS;
-        resources.ram = DEFAULT_RAM;
-        resources.storage = MINIMAL_STORAGE;
-
-        return resources;
-    }
-
     public async void load () {
         db_loading = true;
         var loader = new Loader ();
@@ -224,14 +214,6 @@ public Resources get_resources_for_os (Os? os, string? architecture) {
         return get_prefered_resources (list, prefs);
     }
 
-    public static Resources? get_minimum_resources_for_os (Os os, string architecture) {
-        string[] prefs = { architecture, ARCHITECTURE_ALL };
-
-        var list = os.get_minimum_resources ();
-
-        return get_prefered_resources (list, prefs);
-    }
-
     public Datamap? get_datamap (string id) {
         return db.get_datamap (id);
     }
diff --git a/src/preferences/cdrom-row.vala b/src/preferences/cdrom-row.vala
new file mode 100644
index 00000000..fe1557b7
--- /dev/null
+++ b/src/preferences/cdrom-row.vala
@@ -0,0 +1,76 @@
+// This file is part of GNOME Boxes. License: LGPLv2+
+
+[GtkTemplate (ui = "/org/gnome/Boxes/ui/preferences/cdrom-row.ui")]
+private class Boxes.CdromRow : Hdy.ActionRow {
+    private LibvirtMachine machine;
+    private GVirConfig.DomainDisk cdrom_config;
+
+    [GtkChild]
+    private unowned Gtk.Stack stack;
+    [GtkChild]
+    private unowned Gtk.Button select_button;
+    [GtkChild]
+    private unowned Gtk.Button remove_button;
+
+    public void setup (LibvirtMachine machine) {
+        this.machine = machine;
+
+        foreach (var device_config in machine.domain_config.get_devices ()) {
+            if (device_config is GVirConfig.DomainDisk) {
+                var disk_config = device_config as GVirConfig.DomainDisk;
+                var disk_type = disk_config.get_guest_device_type ();
+
+                if (disk_type == GVirConfig.DomainDiskGuestDeviceType.CDROM) {
+                    cdrom_config = disk_config;
+
+                    break;
+                }
+            }
+        }
+
+        var source = cdrom_config.get_source ();
+        if (source == null || source == "") {
+            title = _("No CD/DVD image");
+
+            stack.visible_child = select_button;
+
+            return;
+        }
+
+        stack.visible_child = remove_button;
+        title = get_utf8_basename (source);
+    }
+
+    private void set_cdrom_source_path (string? path = null) {
+        cdrom_config.set_source (path != null ? path : "");
+
+        try {
+            machine.domain.update_device (cdrom_config, GVir.DomainUpdateDeviceFlags.CURRENT);
+
+            // Let's also refresh the interface
+            setup (machine);
+        } catch (GLib.Error error) {
+            debug ("Error inserting '%s' as CD into '%s': %s",
+                   path,
+                   machine.name,
+                   error.message);
+        }
+    }
+
+    [GtkCallback]
+    private void on_select_button_clicked () {
+        var file_chooser = new Gtk.FileChooserNative (_("Select a device or ISO file"),
+                                                      get_toplevel () as Gtk.Window,
+                                                      Gtk.FileChooserAction.OPEN,
+                                                      _("Open"), _("Cancel"));
+        var response = file_chooser.run ();
+        if (response == Gtk.ResponseType.ACCEPT) {
+            set_cdrom_source_path (file_chooser.get_filename ());
+        }
+    }
+
+    [GtkCallback]
+    private void on_remove_button_clicked () {
+        set_cdrom_source_path (null);
+    }
+}
diff --git a/src/preferences/device-list-row.vala b/src/preferences/device-list-row.vala
new file mode 100644
index 00000000..ec15581a
--- /dev/null
+++ b/src/preferences/device-list-row.vala
@@ -0,0 +1,18 @@
+// This file is part of GNOME Boxes. License: LGPLv2+
+
+[GtkTemplate (ui = "/org/gnome/Boxes/ui/preferences/device-list-row.ui")]
+private class Boxes.DeviceListRow : Hdy.ActionRow {
+    [GtkChild]
+    private unowned Gtk.Switch toggle;
+
+    public DeviceListRow (Boxes.UsbDevice device) {
+        title = device.title;
+
+        device.bind_property ("active", toggle, "active", BindingFlags.BIDIRECTIONAL);
+    }
+}
+
+class Boxes.UsbDevice : GLib.Object {
+    public string title;
+    public bool active { get; set; }
+}
diff --git a/src/preferences/devices-page.vala b/src/preferences/devices-page.vala
new file mode 100644
index 00000000..d8b65ee8
--- /dev/null
+++ b/src/preferences/devices-page.vala
@@ -0,0 +1,59 @@
+// This file is part of GNOME Boxes. License: LGPLv2+
+using Gtk;
+
+[GtkTemplate (ui = "/org/gnome/Boxes/ui/preferences/devices-page.ui")]
+private class Boxes.DevicesPage : Hdy.PreferencesPage {
+    private LibvirtMachine machine;
+
+    [GtkChild]
+    private unowned Gtk.ListBox listbox;
+    [GtkChild]
+    private unowned Boxes.SharedFoldersWidget shared_folders_widget;
+    [GtkChild]
+    private unowned Boxes.CdromRow cdrom_row;
+
+    public void setup (LibvirtMachine machine) {
+        this.machine = machine;
+
+        setup_usb_devices_list ();
+        cdrom_row.setup (machine);
+        shared_folders_widget.setup (machine.config.uuid);
+    }
+
+    private void setup_usb_devices_list () {
+        if (App.is_running_in_flatpak ()) {
+            var msg = new Gtk.Label (_("The Flatpak version of GNOME Boxes does not support USB 
redirection.")) {
+                visible = true,
+                margin = 10
+            };
+            msg.get_style_context ().add_class ("dim-label");
+            listbox.add (msg);
+
+            return;
+        }
+
+        if (!machine.is_running) {
+            var msg = new Gtk.Label (_("Turn on your box to see the USB devices available for 
redirection.")) {
+                visible = true,
+                margin = 10
+            };
+            msg.get_style_context ().add_class ("dim-label");
+            listbox.add (msg);
+
+            return;
+        }
+
+        if (!(machine.display is SpiceDisplay))
+            return;
+
+        var spice_display = machine.display as SpiceDisplay;
+        var model = spice_display.get_usb_devices_model ();
+        listbox.bind_model (model, add_usb_device_row);
+    }
+
+    private Gtk.Widget add_usb_device_row (GLib.Object item) {
+        var device = item as Boxes.UsbDevice;
+
+        return new Boxes.DeviceListRow (device);
+    }
+}
diff --git a/src/preferences/memory-row.vala b/src/preferences/memory-row.vala
new file mode 100644
index 00000000..7ccbef6b
--- /dev/null
+++ b/src/preferences/memory-row.vala
@@ -0,0 +1,32 @@
+// This file is part of GNOME Boxes. License: LGPLv2+
+
+[GtkTemplate (ui = "/org/gnome/Boxes/ui/preferences/memory-row.ui")]
+private class Boxes.MemoryRow : Hdy.ActionRow {
+    [GtkChild]
+    public unowned Gtk.SpinButton spin_button;
+
+    [GtkCallback]
+    private int on_spin_button_input (Gtk.SpinButton spin_button, out double new_value) {
+        uint64 current_value = (uint64)spin_button.get_value ();
+
+        /* FIXME: we should be getting the value with spin_button.get_text () so we can
+         * accept user manual input. This will require to parse the text properly and
+         * convert strings such as 2.0 GiB into 2.0 * Osinfo.MEBIBYTE * 1024.
+         *
+         * As it is now, we don't support manual input, and the value can only be changed
+         * by using the + and - buttons of the GtkSpinButton.
+        */
+        new_value = current_value;
+
+        return 1;
+    }
+
+    [GtkCallback]
+    private bool on_spin_button_output (Gtk.SpinButton spin_button) {
+        uint64 current_value = (uint64)spin_button.get_value ();
+
+        spin_button.text = GLib.format_size (current_value);
+
+        return true;
+    }
+}
diff --git a/src/preferences/meson.build b/src/preferences/meson.build
new file mode 100644
index 00000000..b95dd70f
--- /dev/null
+++ b/src/preferences/meson.build
@@ -0,0 +1,14 @@
+vala_sources += files( 
+  'cdrom-row.vala',
+  'devices-page.vala',
+  'device-list-row.vala',
+  'memory-row.vala',
+  'preferences-window.vala',
+  'preferences-toast.vala',
+  'ram-row.vala',
+  'resources-page.vala',
+  'shared-folders-widget.vala',
+  'snapshot-list-row.vala',
+  'snapshots-page.vala',
+  'storage-row.vala',
+)
diff --git a/src/preferences/preferences-toast.vala b/src/preferences/preferences-toast.vala
new file mode 100644
index 00000000..43e99440
--- /dev/null
+++ b/src/preferences/preferences-toast.vala
@@ -0,0 +1,53 @@
+// This file is part of GNOME Boxes. License: LGPLv2+
+
+[GtkTemplate (ui = "/org/gnome/Boxes/ui/preferences/preferences-toast.ui")]
+private class Boxes.PreferencesToast : Gtk.Box {
+    public delegate void OKFunc ();
+    public delegate void DismissFunc ();
+
+    [GtkChild]
+    private unowned Gtk.Label label;
+    [GtkChild]
+    private unowned Gtk.Button button;
+
+    public string message {
+        set {
+            label.label = value;
+        }
+        get {
+            return label.label;
+        }
+    }
+
+    public string action {
+        set {
+            button.label = value; 
+        }
+        get {
+            return button.label;
+        }
+    }
+
+    public OKFunc? undo_func;
+    public DismissFunc? dismiss_func;
+
+    public void dismiss () {
+        if (dismiss_func != null)
+            dismiss_func ();
+
+        destroy ();
+    }
+
+    [GtkCallback]
+    private void on_dismiss_button_clicked () {
+        dismiss ();
+    }
+
+    [GtkCallback]
+    private void on_undo_button_clicked () {
+        if (undo_func != null)
+            undo_func ();
+
+        destroy ();
+    }
+}
diff --git a/src/preferences/preferences-window.vala b/src/preferences/preferences-window.vala
new file mode 100644
index 00000000..e3bbd58c
--- /dev/null
+++ b/src/preferences/preferences-window.vala
@@ -0,0 +1,20 @@
+// This file is part of GNOME Boxes. License: LGPLv2+
+using Gtk;
+
+[GtkTemplate (ui = "/org/gnome/Boxes/ui/preferences/preferences-window.ui")]
+private class Boxes.PreferencesWindow : Hdy.PreferencesWindow {
+    public Machine machine {
+        set {
+            resources_page.setup (value as LibvirtMachine);
+            devices_page.setup (value as LibvirtMachine);
+            snapshots_page.setup (value as LibvirtMachine);
+        }
+    }
+
+    [GtkChild]
+    private unowned Boxes.ResourcesPage resources_page;
+    [GtkChild]
+    private unowned Boxes.DevicesPage devices_page;
+    [GtkChild]
+    private unowned Boxes.SnapshotsPage snapshots_page;
+}
diff --git a/src/preferences/ram-row.vala b/src/preferences/ram-row.vala
new file mode 100644
index 00000000..061d71c3
--- /dev/null
+++ b/src/preferences/ram-row.vala
@@ -0,0 +1,47 @@
+// This file is part of GNOME Boxes. License: LGPLv2+
+
+private class Boxes.RamRow : Boxes.MemoryRow {
+    private LibvirtMachine machine;
+
+    public void setup (LibvirtMachine machine) {
+        this.machine = machine;
+
+        try {
+            var host_topology = machine.connection.get_node_info ();
+
+            var available_ram = host_topology.memory;
+            uint64 ram = machine.domain_config.memory * Osinfo.KIBIBYTES;
+            uint64 max_ram = available_ram * Osinfo.KIBIBYTES; 
+            uint64 min_ram = 64 * Osinfo.MEBIBYTES;
+
+            spin_button.set_range (min_ram, max_ram);
+            spin_button.set_increments (min_ram, max_ram);
+            spin_button.set_value (ram);
+        } catch (GLib.Error error) {
+            warning ("Failed to obtain virtual resources for '%s', %s",
+                     machine.name,
+                     error.message);
+        }
+
+        spin_button.value_changed.connect (on_spin_button_changed);
+    }
+
+    [GtkCallback]
+    private void on_spin_button_changed () {
+        uint64 ram = (uint64)spin_button.get_value () / Osinfo.KIBIBYTES;
+
+        try {
+            var config = machine.domain.get_config (GVir.DomainXMLFlags.INACTIVE);
+            config.memory = ram;
+
+            if (config.get_class ().find_property ("current-memory") != null)
+                config.set ("current-memory", ram);
+
+            machine.domain.set_config (config);
+            debug ("RAM changed to %llu KiB", ram);
+        } catch (GLib.Error error) {
+            warning ("Failed to change RAM of box '%s' to %llu KiB: %s",
+                     machine.name, ram, error.message);
+        }
+    }
+}
diff --git a/src/preferences/resources-page.vala b/src/preferences/resources-page.vala
new file mode 100644
index 00000000..326c56bf
--- /dev/null
+++ b/src/preferences/resources-page.vala
@@ -0,0 +1,327 @@
+// This file is part of GNOME Boxes. License: LGPLv2+
+using GLib;
+using Gtk;
+
+[GtkTemplate (ui = "/org/gnome/Boxes/ui/preferences/resources-page.ui")]
+private class Boxes.ResourcesPage : Hdy.PreferencesPage {
+    private LibvirtMachine machine;
+
+    private FileMonitor config_file_monitor;
+
+    private string logs;
+
+    [GtkChild]
+    private unowned Gtk.Entry box_name_entry;
+
+    [GtkChild]
+    private unowned Hdy.PreferencesGroup address_group;
+    [GtkChild]
+    private unowned Hdy.ActionRow ip_address_row;
+    [GtkChild]
+    private unowned Gtk.Label ip_address_label;
+
+    [GtkChild]
+    private unowned Hdy.PreferencesGroup resources_group;
+    [GtkChild]
+    private unowned Boxes.RamRow ram_row;
+    [GtkChild]
+    private unowned Boxes.StorageRow storage_row;
+    [GtkChild]
+    private unowned Gtk.SpinButton cpus_spin_button;
+    [GtkChild]
+    private unowned Hdy.ActionRow acceleration_3d_row;
+    [GtkChild]
+    private unowned Gtk.Switch acceleration_3d_toggle;
+
+    [GtkChild]
+    private unowned Gtk.Switch run_in_bg_toggle;
+
+    public void setup (LibvirtMachine machine) {
+        this.machine = machine;
+
+        setup_address_group ();
+        bind_widget_property (box_name_entry, "text", "name");
+        bind_widget_property (run_in_bg_toggle, "active", "run-in-bg");
+
+        on_run_in_bg_toggled.begin ();
+        mark_recommended_resources.begin ();
+
+        machine.supports_accel3d.begin ((source, result) => {
+            acceleration_3d_row.visible = machine.supports_accel3d.end (result);
+            if (acceleration_3d_row.visible)
+                bind_widget_property (acceleration_3d_toggle,
+                                      "active",
+                                      "acceleration-3d");
+        });
+
+        ram_row.setup (machine);
+        storage_row.setup (machine);
+        setup_cpu_row ();
+
+        machine.notify["is-running"].connect (on_machine_state_changed);
+        on_machine_state_changed ();
+    }
+
+    private void setup_address_group () {
+        string? address = machine.get_ip_address ();
+
+        address_group.visible = ip_address_row.visible = address != null;
+        ip_address_label.label = address;
+    }
+
+    private async void mark_recommended_resources () {
+        var os = yield machine.get_os ();
+        if (os == null)
+            return;
+
+        var architecture = machine.domain_config.get_os ().get_arch ();
+        var recommended_resources = OSDatabase.get_recommended_resources_for_os (os, architecture);
+        if (recommended_resources != null) {
+            var row_subtitle = _("Recommended %s.");
+
+            ram_row.set_subtitle (row_subtitle.printf (GLib.format_size (recommended_resources.ram)));
+            storage_row.set_subtitle (row_subtitle.printf (GLib.format_size 
(recommended_resources.storage)));
+        }
+    }
+
+    private void setup_cpu_row () {
+        try {
+            var host_topology = machine.connection.get_node_info ();
+            uint host_vcpus = host_topology.cores * host_topology.sockets * host_topology.threads;
+            uint64 cpus = machine.domain_config.get_vcpus ();
+
+            cpus_spin_button.set_range (1, host_vcpus);
+            cpus_spin_button.set_increments (1, host_vcpus);
+            cpus_spin_button.set_value (cpus);
+        } catch (GLib.Error error) {
+            warning ("Failed to obtain virtual resources for machine: %s", error.message);
+        }
+
+        cpus_spin_button.value_changed.connect (on_cpu_spin_button_changed);
+    }
+
+    private void on_cpu_spin_button_changed () {
+        uint cores = cpus_spin_button.get_value_as_int ();
+
+        try {
+            if (machine.domain_config.get_vcpus () == cores)
+                return;
+
+            var config = machine.domain.get_config (GVir.DomainXMLFlags.INACTIVE);
+            config.set_vcpus (cores);
+            if (config.get_class ().find_property ("current-vcpus") != null)
+                config.set ("current-vcpus", cores);
+
+            /* TODO: research for a decent rationalization for the values below. */
+            var guest_topology = new GVirConfig.CapabilitiesCpuTopology ();
+            guest_topology.set_cores (cores);
+            guest_topology.set_sockets (1);
+            guest_topology.set_threads (1);
+
+            var cpu = machine.domain_config.get_cpu ();
+            cpu.set_topology (guest_topology);
+            config.set_cpu (cpu);
+
+            machine.domain.set_config (config);
+            debug ("vCPUS changed to %u", cores);
+
+            // TODO: If machine ON -> Restart!
+        } catch (GLib.Error error) {
+            debug ("Failed to set the number of vCPUS for '%s': %s", machine.name, error.message);
+        }
+    }
+
+    [GtkCallback]
+    private async void on_run_in_bg_toggled () {
+        if (!machine.run_in_bg)
+            return;
+
+        if (!App.is_running_in_flatpak ())
+            return;
+
+        if (!run_in_bg_toggle.get_active ())
+            return;
+
+        yield Portals.get_default ().request_to_run_in_background (
+        (response, results) => {
+            if (response == 0) {
+                debug ("User authorized Boxes to run in background");
+
+                return;
+            }
+
+            machine.run_in_bg = false;
+
+            var msg = _("Boxes is not authorized to run in background");
+            machine.window.notificationbar.display_for_action (msg,
+                                                               _("Manage permissions"),
+                                                               open_permission_settings);
+        });
+    }
+
+    [GtkCallback]
+    private void on_troubleshooting_logs_button_clicked () {
+        if (logs == null)
+            logs = collect_logs (machine);
+        
+        try {
+            var filename = get_cache ("logs", machine.domain.get_name () + ".logs");
+            File file = GLib.File.new_for_path (filename);
+
+            FileOutputStream os = file.replace (null, false, FileCreateFlags.REPLACE_DESTINATION);
+
+            os.write (logs.data);
+            GLib.AppInfo.launch_default_for_uri (file.get_uri (), null);
+
+            debug ("Showing vm configuration at %s", file.get_uri ());
+            GLib.stdout.printf (logs + "\n");
+        } catch (GLib.Error error) {
+            warning ("Failed to collect machine logs: %s", error.message);
+        }
+    }
+
+    [GtkCallback]
+    private async void on_edit_configuration_button_clicked () {
+        var message_dialog = new Gtk.MessageDialog (get_toplevel () as Gtk.Window,
+                                                    Gtk.DialogFlags.MODAL,
+                                                    Gtk.MessageType.QUESTION,
+                                                    Gtk.ButtonsType.YES_NO,
+                                                    _("Editing your box configuration can cause issues to 
the operating system of your box. Would you like to create a snapshot to recover from your changes?"));
+        message_dialog.show_all ();
+        message_dialog.response.connect (create_snapshot); 
+    }
+
+    private async void create_snapshot (Gtk.Dialog message_dialog, int response) {
+        if (response == Gtk.ResponseType.YES) {
+            debug ("Creating snapshot...");
+
+            try {
+                // TODO: create a better snapshot description label.
+                yield machine.create_snapshot ();
+            } catch (GLib.Error error) {
+                warning ("Failed to create snapshot: %s", error.message);
+            }
+        }
+
+        message_dialog.destroy ();
+        open_config_file ();
+    }
+
+    private void open_config_file () {
+        try {
+            var domain_xml = machine.domain_config.to_xml ();
+            File file = GLib.File.new_for_path (Path.build_filename (Environment.get_home_dir (),
+                                                "." + machine.domain.get_name () + ".draft.txt"));
+            FileOutputStream os = file.replace (null, false, FileCreateFlags.REPLACE_DESTINATION);
+            os.write (domain_xml.data);
+
+            config_file_monitor = file.monitor_file (GLib.FileMonitorFlags.NONE, null);
+            config_file_monitor.changed.connect (on_domain_configuration_edited);
+
+            GLib.AppInfo.launch_default_for_uri (file.get_uri (), null);
+        } catch (GLib.Error error) {
+            warning ("Failed to edit VM configuration: %s", error.message);
+        }
+    }
+
+    private void on_domain_configuration_edited (File file,
+                                                 File? other_file,
+                                                 FileMonitorEvent event_type) {
+        if (event_type != FileMonitorEvent.CHANGED &&
+            event_type != FileMonitorEvent.CHANGES_DONE_HINT) {
+            return;
+        }
+
+        try {
+            uint8[] contents;
+            string etag_out;
+            file.load_contents (null, out contents, out etag_out);
+
+            GVirConfig.Domain new_config = new GVirConfig.Domain.from_xml ((string)contents);
+            var edited_tag = "<edited>%s</edited>".printf (new DateTime.now_local ().to_string ());
+            new_config.set_custom_xml (edited_tag,
+                                       "edited",
+                                       "https://wiki.gnome.org/Apps/Boxes/edited";);
+            machine.domain.set_config (new_config);
+
+            debug ("Overriding configuration for %s", machine.domain.get_name ());
+        } catch (GLib.Error error) {
+            warning ("Failed to load new domain configuration: %s", error.message);
+
+            var message_dialog = new Gtk.MessageDialog (App.app.main_window,
+                                                        Gtk.DialogFlags.MODAL,
+                                                        Gtk.MessageType.ERROR,
+                                                        Gtk.ButtonsType.CLOSE,
+                                                        _("Failed to save domain configuration: %s"),
+                                                        error.message);
+            message_dialog.run ();
+            message_dialog.destroy ();
+        }
+    }
+
+    private void on_machine_state_changed () {
+        if (machine.is_running) {
+            resources_group.description = _("Changes to the settings bellow take effect after you restart 
your box.");
+        } else {
+            resources_group.description = null;
+        }
+    }
+
+    private void bind_widget_property (Gtk.Widget widget, string widget_property, string machine_property) {
+        machine.bind_property (machine_property, widget, widget_property, BindingFlags.BIDIRECTIONAL);
+
+        var value = GLib.Value (machine.get_class ().find_property (machine_property).value_type);
+        machine.get_property (machine_property, ref value);
+        widget.set_property (widget_property, value);
+    }
+
+    private string collect_logs (LibvirtMachine machine) {
+        var builder = new StringBuilder ();
+
+        builder.append_printf ("Broker URL: %s\n", machine.source.uri);
+        builder.append_printf ("Domain: %s\n", machine.domain.get_name ());
+        builder.append_printf ("UUID: %s\n", machine.domain.get_uuid ());
+        builder.append_printf ("Persistent: %s\n", machine.domain.get_persistent () ? "yes" : "no");
+        try {
+            var info = machine.domain.get_info ();
+            builder.append_printf ("Cpu time: %"+uint64.FORMAT_MODIFIER+"d\n", info.cpuTime);
+            builder.append_printf ("Memory: %"+uint64.FORMAT_MODIFIER+"d KiB\n", info.memory);
+            builder.append_printf ("Max memory: %"+uint64.FORMAT_MODIFIER+"d KiB\n",
+                                   machine.connection.get_node_info ().memory);
+            builder.append_printf ("CPUs: %d\n", info.nrVirtCpu);
+            builder.append_printf ("State: %s\n", info.state.to_string ());
+        } catch (GLib.Error e) {
+        }
+
+        if (machine.display != null)
+            machine.display.collect_logs (builder);
+
+
+        try {
+            var conf = machine.domain.get_config (GVir.DomainXMLFlags.NONE);
+            builder.append_printf ("\nDomain config:\n");
+            builder.append_printf ("------------------------------------------------------------\n");
+
+            builder.append (conf.to_xml ());
+            builder.append_printf ("\n" +
+                                   "------------------------------------------------------------\n");
+        } catch (GLib.Error error) {
+        }
+
+        try {
+            var logfile = Path.build_filename (Environment.get_user_cache_dir (),
+                                               "libvirt/qemu/log",
+                                               machine.domain.get_name ()  + ".log");
+            string data;
+            FileUtils.get_contents (logfile, out data);
+            builder.append_printf ("\nQEMU log:\n");
+            builder.append_printf ("------------------------------------------------------------\n");
+
+            builder.append (data);
+            builder.append_printf ("------------------------------------------------------------\n");
+        } catch (GLib.Error e) {
+        }
+
+        return builder.str;
+    }
+}
diff --git a/src/preferences/shared-folders-widget.vala b/src/preferences/shared-folders-widget.vala
new file mode 100644
index 00000000..cca1fe2b
--- /dev/null
+++ b/src/preferences/shared-folders-widget.vala
@@ -0,0 +1,115 @@
+// This file is part of GNOME Boxes. License: LGPLv2+
+
+[GtkTemplate (ui = "/org/gnome/Boxes/ui/preferences/shared-folder-row.ui")]
+private class Boxes.SharedFolderRow : Hdy.ActionRow {
+    public signal void removed (SharedFolder folder);
+
+    public SharedFolder folder { get; private set; }
+
+    public SharedFolderRow (SharedFolder folder) {
+        this.folder = folder;
+
+        folder.bind_property ("path", this, "title", BindingFlags.SYNC_CREATE);
+        folder.bind_property ("name", this, "subtitle", BindingFlags.SYNC_CREATE);
+    }
+
+    [GtkCallback]
+    private void on_delete_button_clicked () {
+        removed (folder);
+    }
+}
+
+[GtkTemplate (ui = "/org/gnome/Boxes/ui/preferences/shared-folders-widget.ui")]
+private class Boxes.SharedFoldersWidget: Hdy.PreferencesGroup {
+    private string machine_uuid;
+
+    private SharedFoldersManager manager = SharedFoldersManager.get_default ();
+
+    private GLib.ListStore list_model;
+
+    private Boxes.SharedFolderPopover popover;
+
+    [GtkChild]
+    private unowned Gtk.ListBox listbox;
+
+    construct {
+        popover = new SharedFolderPopover ();
+        popover.saved.connect (on_popover_saved);
+    }
+
+    public void setup (string machine_uuid) {
+        this.machine_uuid = machine_uuid;
+
+        list_model = manager.get_folders (machine_uuid);
+        list_model.items_changed.connect (on_list_updated);
+        listbox.bind_model (list_model, create_shared_folder_row);
+
+        var add_button = new Gtk.MenuButton () {
+            visible = true,
+            image = new Gtk.Image () {
+                icon_name = "list-add-symbolic"
+            },
+            popover = popover
+        }; 
+        add_button.get_style_context ().add_class ("flat");
+        listbox.add (add_button);
+
+        on_list_updated ();
+    }
+
+    private bool on_popover_saved (string path, string? name) {
+        return manager.add_item (new SharedFolder (machine_uuid, path, name));
+    }
+
+    private Gtk.Widget create_shared_folder_row (Object item) {
+        var folder = item as SharedFolder;
+        var row = new SharedFolderRow (folder);
+
+        row.removed.connect (manager.remove_item);
+
+        return row;
+    }
+
+    private void on_list_updated () {
+        if (list_model.get_n_items () == 0)
+            description = _("Use the button bellow to add your first shared folder.");
+        else
+            description = null;
+    }
+}
+
+[GtkTemplate (ui = "/org/gnome/Boxes/ui/preferences/shared-folder-popover.ui")]
+private class Boxes.SharedFolderPopover: Gtk.Popover {
+    public signal bool saved (string path, string name);
+
+    [GtkChild]
+    public unowned Gtk.FileChooserButton file_chooser_button;
+    [GtkChild]
+    public unowned Gtk.Entry name_entry;
+
+    construct {
+        var default_path = Environment.get_user_special_dir (UserDirectory.PUBLIC_SHARE);
+        file_chooser_button.set_current_folder (default_path);
+    }
+
+    [GtkCallback]
+    public void on_cancel (Gtk.Button cancel_button) {
+        popdown ();
+    }
+
+    [GtkCallback]
+    public void on_save (Gtk.Button save_button) {
+        var uri = file_chooser_button.get_uri ();
+        File file = File.new_for_uri (uri);
+        var name = name_entry.get_text ();
+
+        if (uri != null) {
+            if (name == "")
+                name = file.get_basename ();
+
+            saved (file.get_path (), name);
+        }
+
+        popdown ();
+    }
+}
diff --git a/src/snapshot-list-row.vala b/src/preferences/snapshot-list-row.vala
similarity index 93%
rename from src/snapshot-list-row.vala
rename to src/preferences/snapshot-list-row.vala
index dee9b007..3994edd3 100644
--- a/src/snapshot-list-row.vala
+++ b/src/preferences/snapshot-list-row.vala
@@ -1,7 +1,9 @@
 // This file is part of GNOME Boxes. License: LGPLv2+
 
-[GtkTemplate (ui = "/org/gnome/Boxes/ui/snapshot-list-row.ui")]
+[GtkTemplate (ui = "/org/gnome/Boxes/ui/preferences/snapshot-list-row.ui")]
 private class Boxes.SnapshotListRow : Gtk.ListBoxRow {
+    public signal void deletion_requested (Boxes.PreferencesToast toast);
+
     public GVir.DomainSnapshot snapshot;
     public string activity_message { get; set; default = ""; }
 
@@ -82,7 +84,8 @@ public override bool draw (Cairo.Context ct) {
             ct.line_to (height / 2.0 + 0.5, height / 2.0);
             ct.stroke ();
         }
-        if (index < parent_size - 1) {
+        // this row + the SnapshotPage's add_button row
+        if (index < parent_size - 2) {
             ct.move_to (height / 2.0 + 0.5, height / 2.0);
             ct.line_to (height / 2.0 + 0.5, height + 1);
             ct.stroke ();
@@ -190,10 +193,13 @@ private void delete_activated (GLib.SimpleAction action, GLib.Variant? v) {
             });
             row = null;
         };
-        machine.window.notificationbar.display_for_action (message,
-                                                           _("_Undo"),
-                                                           (owned) undo,
-                                                           (owned) really_remove);
+
+        var toast = new Boxes.PreferencesToast () {
+            message = message,
+            undo_func = (owned) undo,
+            dismiss_func = (owned) really_remove,
+        };
+        deletion_requested (toast);
     }
 
     private void rename_activated (GLib.SimpleAction action, GLib.Variant? v) {
diff --git a/src/preferences/snapshots-page.vala b/src/preferences/snapshots-page.vala
new file mode 100644
index 00000000..55102305
--- /dev/null
+++ b/src/preferences/snapshots-page.vala
@@ -0,0 +1,147 @@
+// This file is part of GNOME Boxes. License: LGPLv2+
+using Gtk;
+
+[GtkTemplate (ui = "/org/gnome/Boxes/ui/preferences/snapshots-page.ui")]
+private class Boxes.SnapshotsPage : Hdy.PreferencesPage {
+    private LibvirtMachine machine;
+
+    [GtkChild]
+    private unowned Gtk.Overlay toast_overlay;
+    private Boxes.PreferencesToast toast;
+
+    [GtkChild]
+    private unowned Hdy.PreferencesGroup preferences_group;
+
+    [GtkChild]
+    private unowned Gtk.Stack stack;
+    [GtkChild]
+    private unowned Gtk.ListBox listbox;
+    [GtkChild]
+    private unowned Gtk.Box activity_page;
+    [GtkChild]
+    private unowned Gtk.Label activity_label;
+
+    private Gtk.Button add_button;
+
+    private string? activity {
+        set {
+            if (value == null) {
+                stack.visible_child = listbox;
+            } else {
+                activity_label.label = value;
+                stack.visible_child = activity_page;
+            }
+        }
+    }
+
+    public void setup (LibvirtMachine machine) {
+        this.machine = machine;
+
+        listbox.set_sort_func (config_sort_func);
+
+        destroy.connect (() => { fetch_snapshots_cancellable.cancel (); });
+        fetch_snapshots.begin ();
+
+        add_button = new Gtk.Button () {
+            visible = true,
+            image = new Gtk.Image () {
+                visible = true,
+                icon_name = "list-add-symbolic"
+            }
+        };
+        add_button.get_style_context ().add_class ("flat");
+        add_button.clicked.connect (create_snapshot);
+        listbox.add (add_button);
+
+        update_snapshot_stack_page ();
+    }
+
+    private GLib.Cancellable fetch_snapshots_cancellable = new GLib.Cancellable ();
+    private async void fetch_snapshots () {
+        try {
+            yield machine.domain.fetch_snapshots_async (GVir.DomainSnapshotListFlags.ALL,
+                                                        fetch_snapshots_cancellable);
+            var snapshots =  machine.domain.get_snapshots ();
+            foreach (var snapshot in snapshots) {
+                add_snapshot_row (snapshot);
+            }
+        } catch (GLib.Error e) {
+            warning ("Could not fetch snapshots: %s", e.message);
+        }
+    }
+
+    private void add_snapshot_row (GVir.DomainSnapshot snapshot) {
+        var row = new SnapshotListRow (snapshot, machine);
+        row.notify["activity-message"].connect (row_activity_changed);
+        row.deletion_requested.connect (on_row_deleted);
+
+        listbox.add (row);
+    }
+
+    private void on_row_deleted (Boxes.PreferencesToast new_toast) {
+        if (toast != null) {
+            toast.dismiss ();
+            toast = null;
+        }
+
+        toast = new_toast;
+        toast_overlay.add_overlay (toast);
+    }
+
+    private int config_sort_func (Gtk.ListBoxRow row1, Gtk.ListBoxRow row2) {
+        if (row1.get_child () == add_button)
+            return 1;
+        if (row2.get_child () == add_button)
+            return 1;
+
+        try {
+            var snapshot_row1 = row1 as SnapshotListRow;
+            var snapshot_row2 = row2 as SnapshotListRow;
+
+            var conf1  = snapshot_row1.snapshot.get_config (0);
+            var conf2  = snapshot_row2.snapshot.get_config (0);
+            if (conf1.get_creation_time () < conf2.get_creation_time ())
+                return -1;
+            else
+                return 1;
+        } catch (GLib.Error e) {
+            warning ("Failed to fetch snapshot config: %s", e.message);
+
+            return 0;
+        }
+    }
+
+    private async void create_snapshot () {
+        if (machine.state == Machine.MachineState.RUNNING)
+            this.activity = _("Creating new snapshot…");
+
+        try {
+            var new_snapshot = yield machine.create_snapshot ();
+            add_snapshot_row (new_snapshot);
+        } catch (GLib.Error e) {
+            var msg = _("Failed to create snapshot of %s").printf (machine.name);
+            machine.window.notificationbar.display_error (msg);
+            warning (e.message);
+        }
+        this.activity = null;
+
+        update_snapshot_stack_page ();
+    }
+
+    private void row_activity_changed (GLib.Object source, GLib.ParamSpec param_spec) {
+        var row = source as SnapshotListRow;
+        this.activity = row.activity_message;
+    }
+
+    [GtkCallback]
+    private void update_snapshot_stack_page () {
+        var num_rows = listbox.get_children ().length ();
+
+        // we need to account for the "+" button
+        if (num_rows > 1) {
+           preferences_group.description = null;
+        } else {
+           preferences_group.description = _("Use the button bellow to create your first snapshot.");
+        }
+    }
+}
diff --git a/src/preferences/storage-row.vala b/src/preferences/storage-row.vala
new file mode 100644
index 00000000..2cdd5fe9
--- /dev/null
+++ b/src/preferences/storage-row.vala
@@ -0,0 +1,84 @@
+// This file is part of GNOME Boxes. License: LGPLv2+
+
+private class Boxes.StorageRow : Boxes.MemoryRow {
+    private LibvirtMachine machine;
+
+    public void setup (LibvirtMachine machine) {
+        this.machine = machine;
+
+        if (machine.importing || machine.storage_volume == null) {
+            sensitive = false;
+
+            return;
+        }
+
+        try {
+            var volume_info = machine.storage_volume.get_info ();
+            var pool = get_storage_pool (machine.connection);
+            var pool_info = pool.get_info ();
+            var min_storage = volume_info.allocation;
+            var max_storage = min_storage + pool_info.available;
+
+            // Translators: "%s" is a disk size string (for example "4.2 GB")
+            subtitle = _("Used %s.").printf (GLib.format_size (volume_info.allocation));
+
+            if (min_storage >= max_storage)
+                subtitle = _("There is not enough space on your machine to increate the maximum disk size.");
+
+            spin_button.set_range (min_storage, max_storage);
+            spin_button.set_increments (256 * 1000 * 1000 , volume_info.allocation);
+            spin_button.set_value (volume_info.capacity);
+        } catch (GLib.Error error) {
+            warning ("Failed to obtain virtual resources for '%s', %s",
+                     machine.name,
+                     error.message);
+        }
+
+        spin_button.value_changed.connect (on_spin_button_changed);
+    }
+
+    [GtkCallback]
+    private async void on_spin_button_changed () {
+        uint64 storage = (uint64)spin_button.get_value ();
+
+        try {
+            if (!machine.is_running) {
+                resize_storage_volume (storage);
+
+                return;
+            }
+
+            var disk = machine.get_domain_disk ();
+            if (disk == null)
+                return;
+
+            var size = (storage + Osinfo.KIBIBYTES - 1) / Osinfo.KIBIBYTES;
+            disk.resize (size, GVir.StorageVolResizeFlags.SHRINK);
+
+            var pool = get_storage_pool (machine.connection);
+            yield pool.refresh_async (null);
+            machine.update_domain_config ();
+        } catch (GLib.Error error) {
+            warning ("Failed to change storage capacity of volume '%s' to %llu KiB: %s",
+                     machine.storage_volume.get_name (),
+                     storage,
+                     error.message);
+        }
+    }
+
+    private void resize_storage_volume (uint64 size) throws GLib.Error {
+        var volume_info = machine.storage_volume.get_info ();
+        if (machine.vm_creator != null && size < volume_info.capacity) {
+            var config = machine.storage_volume.get_config (GVir.DomainXMLFlags.NONE);
+            config.set_capacity (size);
+            machine.storage_volume.delete (0);
+
+            var pool = get_storage_pool (machine.connection);
+            machine.storage_volume = pool.create_volume (config);
+        } else {
+            machine.storage_volume.resize (size, GVir.StorageVolResizeFlags.SHRINK);
+        }
+
+        debug ("Storage changed to %llu", size);
+    }
+}
diff --git a/src/shared-folders.vala b/src/shared-folders.vala
index b3c7c15c..ce14b132 100644
--- a/src/shared-folders.vala
+++ b/src/shared-folders.vala
@@ -179,105 +179,3 @@ private static string get_shared_folder_real_path (SharedFolder folder) {
                                          folder.machine_uuid);
     }
 }
-
-[GtkTemplate (ui = "/org/gnome/Boxes/ui/properties-shared-folder-row.ui")]
-private class Boxes.SharedFolderRow : Gtk.ListBoxRow {
-    public signal void removed (SharedFolder folder);
-    [GtkChild]
-    private unowned Gtk.Label folder_path_label;
-    [GtkChild]
-    private unowned Gtk.Label folder_name_label;
-
-    public SharedFolder folder { get; private set; }
-
-    public SharedFolderRow (SharedFolder folder) {
-        this.folder = folder;
-
-        folder.bind_property ("path", folder_path_label, "label", BindingFlags.SYNC_CREATE);
-        folder.bind_property ("name", folder_name_label, "label", BindingFlags.SYNC_CREATE);
-    }
-
-    [GtkCallback]
-    private void on_delete_button_clicked () {
-        removed (folder);
-    }
-}
-
-[GtkTemplate (ui = "/org/gnome/Boxes/ui/shared-folders.ui")]
-private class Boxes.SharedFoldersWidget: Gtk.Frame {
-    private string machine_uuid;
-
-    private SharedFoldersManager manager = SharedFoldersManager.get_default ();
-
-    private GLib.ListStore list_model;
-
-    private Boxes.SharedFolderPopover popover;
-    [GtkChild]
-    private unowned Gtk.ListBox listbox;
-
-    public SharedFoldersWidget (string machine_uuid) {
-        this.machine_uuid = machine_uuid;
-
-        list_model = manager.get_folders (machine_uuid);
-        listbox.bind_model (list_model, create_shared_folder_row);
-
-        popover = new SharedFolderPopover ();
-        popover.saved.connect (on_popover_saved);
-    }
-
-    private bool on_popover_saved (string path, string? name) {
-        return manager.add_item (new SharedFolder (machine_uuid, path, name));
-    }
-
-    [GtkCallback]
-    private void on_add_button_clicked (Gtk.Button button) {
-        popover.relative_to = button;
-
-        popover.popup ();
-    }
-
-    private Gtk.Widget create_shared_folder_row (Object item) {
-        var folder = item as SharedFolder;
-        var row = new SharedFolderRow (folder);
-
-        row.removed.connect (manager.remove_item);
-
-        return row;
-    }
-}
-
-[GtkTemplate (ui = "/org/gnome/Boxes/ui/shared-folder-popover.ui")]
-private class Boxes.SharedFolderPopover: Gtk.Popover {
-    public signal bool saved (string path, string name);
-
-    [GtkChild]
-    public unowned Gtk.FileChooserButton file_chooser_button;
-    [GtkChild]
-    public unowned Gtk.Entry name_entry;
-
-    construct {
-        var default_path = Environment.get_user_special_dir (UserDirectory.PUBLIC_SHARE);
-        file_chooser_button.set_current_folder (default_path);
-    }
-
-    [GtkCallback]
-    public void on_cancel (Gtk.Button cancel_button) {
-        popdown ();
-    }
-
-    [GtkCallback]
-    public void on_save (Gtk.Button save_button) {
-        var uri = file_chooser_button.get_uri ();
-        File file = File.new_for_uri (uri);
-        var name = name_entry.get_text ();
-
-        if (uri != null) {
-            if (name == "")
-                name = file.get_basename ();
-
-            saved (file.get_path (), name);
-        }
-
-        popdown ();
-    }
-}
diff --git a/src/spice-display.vala b/src/spice-display.vala
index f01d084e..166f4282 100644
--- a/src/spice-display.vala
+++ b/src/spice-display.vala
@@ -360,67 +360,6 @@ private void on_new_file_transfer (Spice.MainChannel main_channel, Object transf
         page.add_transfer (transfer_task);
     }
 
-    public override List<Boxes.Property> get_properties (Boxes.PropertiesPage page) {
-        var list = new List<Boxes.Property> ();
-
-        switch (page) {
-        case PropertiesPage.GENERAL:
-            if (!connected || main_channel.agent_connected)
-                break;
-            var link_address = "<a 
href=\"http://www.spice-space.org/download.html\";>http://www.spice-space.org/download.html</a>";
-
-            // Translators: %s => a link to the website where users can download the guest tools.
-            var message = _("SPICE guest tools are not running. These tools improve user experience and 
enable host and box interactions, such as copy and paste. Please visit %s to download and install these tools 
from within the box.").printf (link_address);
-            var label = new Gtk.Label (message);
-            label.vexpand = true;
-            label.valign = Gtk.Align.END;
-            label.wrap = true;
-            label.max_width_chars = 80;
-            label.use_markup = true;
-            label.get_style_context ().add_class ("boxes-spice-tools-notice-label");
-
-            add_property (ref list, null, label);
-            break;
-
-        case PropertiesPage.DEVICES:
-            try {
-                var manager = UsbDeviceManager.get (session);
-                var devs = get_usb_devices (manager);
-
-                if (connected && devs.length > 0) {
-                    devs.sort ( (a, b) => {
-                        string str_a = a.get_description ("    %1$s %2$s");
-                        string str_b = b.get_description ("    %1$s %2$s");
-
-                        return strcmp (str_a, str_b);
-                    });
-
-                    var frame = create_usb_frame (manager, devs);
-
-                    var usb_property = add_property (ref list, _("USB devices"), new Gtk.Label (""), frame);
-
-                    manager.device_added.connect ((manager, dev) => {
-                        usb_property.refresh_properties ();
-                    });
-                    manager.device_removed.connect ((manager, dev) => {
-                        usb_property.refresh_properties ();
-                    });
-                }
-            } catch (GLib.Error error) {
-            }
-
-            if (webdav_channel == null || !webdav_channel.port_opened)
-                break;
-
-            var frame = create_shared_folders_frame ();
-            add_property (ref list, _("Folder Shares"), new Gtk.Label (""), frame);
-
-            break;
-        }
-
-        return list;
-    }
-
     public override void send_keys (uint[] keyvals) {
         // TODO: multi display
         var display = get_display (0) as Spice.Display;
@@ -428,8 +367,8 @@ public override void send_keys (uint[] keyvals) {
         display.send_keys (keyvals, DisplayKeyEvent.CLICK);
     }
 
-    private GLib.GenericArray<UsbDevice> get_usb_devices (UsbDeviceManager manager) {
-        GLib.GenericArray<UsbDevice> ret = new GLib.GenericArray<UsbDevice> ();
+    private GLib.GenericArray<Spice.UsbDevice> get_usb_devices (UsbDeviceManager manager) {
+        GLib.GenericArray<Spice.UsbDevice> ret = new GLib.GenericArray<Spice.UsbDevice> ();
         var devs = manager.get_devices ();
 
         if (Environment.get_variable ("BOXES_USB_REDIR_ALL") != null)
@@ -474,57 +413,56 @@ public override void send_keys (uint[] keyvals) {
         return ret;
     }
 
-    private Gtk.Frame create_usb_frame (UsbDeviceManager manager, GLib.GenericArray<UsbDevice> devs) {
-        var frame = new Gtk.Frame (null);
-        var listbox = new Gtk.ListBox ();
-        listbox.hexpand = true;
-        frame.add (listbox);
+    public GLib.ListStore get_usb_devices_model () {
+        GLib.ListStore model = new GLib.ListStore (typeof (Boxes.UsbDevice));
+
+        GLib.GenericArray<Spice.UsbDevice> devs = new GLib.GenericArray<Spice.UsbDevice> ();
+        UsbDeviceManager manager;
+        try {
+            manager = UsbDeviceManager.get (session);
+            devs = get_usb_devices (manager);
+        }  catch (GLib.Error error) {
+            warning ("Failed to obtain usb devices list: %s", error.message);
+
+            return model;
+        }
 
         for (int i = 0; i < devs.length; i++) {
             var dev = devs[i];
 
-            var hbox = new Gtk.Box (Gtk.Orientation.HORIZONTAL, 0);
-            hbox.margin_start = 12;
-            hbox.margin_end = 12;
-            hbox.margin_top = 6;
-            hbox.margin_bottom = 6;
-            var label = new Gtk.Label (dev.get_description ("%1$s %2$s"));
-            label.halign = Gtk.Align.START;
-            hbox.pack_start (label, true, true, 0);
-            var dev_toggle = new Gtk.Switch ();
-            dev_toggle.halign = Gtk.Align.END;
-            hbox.pack_start (dev_toggle, true, true, 0);
-            listbox.prepend (hbox);
-
-            dev_toggle.active = manager.is_device_connected (dev);
-
-            dev_toggle.notify["active"].connect ( () => {
-                if (dev_toggle.active) {
-                    manager.connect_device_async.begin (dev, null, (obj, res) => {
-                        try {
-                            manager.connect_device_async.end (res);
-                        } catch (GLib.Error err) {
-                            dev_toggle.active = false;
-                            var device_desc = dev.get_description ("%1$s %2$s");
-                            var box_name = get_box_name ();
-                              var msg = _("Redirection of USB device “%s” for “%s” failed");
-                            got_error (msg.printf (device_desc, box_name));
-                            debug ("Error connecting %s to %s: %s",
-                                   device_desc,
-                                   box_name, err.message);
-                        }
-                    });
-                } else {
+            var usb_device = new Boxes.UsbDevice () {
+                title = dev.get_description ("%1$s %2$s"),
+                active = manager.is_device_connected (dev),
+            };
+
+            usb_device.notify["active"].connect (() => {
+                if (!usb_device.active) {
                     manager.disconnect_device (dev);
+
+                    return;
                 }
+
+                manager.connect_device_async.begin (dev, null, (obj, res) => {
+                    try {
+                        manager.connect_device_async.end (res);
+                    } catch (GLib.Error err) {
+                        usb_device.active = false;
+                        var device_desc = dev.get_description ("%1$s %2$s");
+                        var box_name = get_box_name ();
+                        var msg = _("Redirection of USB device “%s” for “%s” failed");
+
+                        got_error (msg.printf (device_desc, box_name));
+                        debug ("Error connecting %s to %s: %s",
+                               device_desc,
+                               box_name, err.message);
+                    }
+                });
             });
-        }
 
-        return frame;
-    }
+            model.append (usb_device);
+        }
 
-    private Gtk.Frame create_shared_folders_frame () {
-        return new SharedFoldersWidget (machine.config.uuid);
+        return model;
     }
 
     private bool is_usb_kbd_or_mouse (uint8 class, uint8 subclass, uint8 protocol) {
diff --git a/src/topbar.vala b/src/topbar.vala
index f0ca2626..60221acc 100644
--- a/src/topbar.vala
+++ b/src/topbar.vala
@@ -23,15 +23,8 @@
 
     private AppWindow window;
 
-    // Clicks the appropriate back button depending on the ui state.
     public void click_back_button () {
-        switch (window.ui_state) {
-        case UIState.PROPERTIES:
-            break;
-        case UIState.CREDS:
-            collection_toolbar.click_back_button ();
-            break;
-        }
+        collection_toolbar.click_back_button ();
     }
 
     // Clicks the appropriate cancel button dependent on the ui state.
diff --git a/src/unattended-setup-box.vala b/src/unattended-setup-box.vala
index 163197a3..b5f2479c 100644
--- a/src/unattended-setup-box.vala
+++ b/src/unattended-setup-box.vala
@@ -96,7 +96,7 @@
     [GtkChild]
     private unowned Gtk.Label express_label;
     [GtkChild]
-    private unowned Gtk.Switch express_toggle;
+    public unowned Gtk.Switch express_toggle;
     [GtkChild]
     private unowned Gtk.Image user_avatar;
     [GtkChild]
diff --git a/src/vnc-display.vala b/src/vnc-display.vala
index 5536a7b6..6e28ef88 100644
--- a/src/vnc-display.vala
+++ b/src/vnc-display.vala
@@ -114,22 +114,6 @@ public override void disconnect_it () {
             display.close ();
     }
 
-    public override List<Boxes.Property> get_properties (Boxes.PropertiesPage page) {
-        var list = new List<Boxes.Property> ();
-
-        switch (page) {
-        case PropertiesPage.GENERAL:
-            var toggle = new Gtk.Switch ();
-            toggle.halign = Gtk.Align.START;
-            display.bind_property ("read-only", toggle, "active",
-                                   BindingFlags.BIDIRECTIONAL | BindingFlags.SYNC_CREATE);
-            add_property (ref list, _("Read-only"), toggle);
-            break;
-        }
-
-        return list;
-    }
-
     public override void send_keys (uint[] keyvals) {
         display.send_keys (keyvals);
     }


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