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




commit adca5155cb102abf0e88604efaf06f362fb00ba9
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 "Properties" widgets entirely.
    
    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            | 325 ++++++++++
 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, 1795 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..65801d75
--- /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">input-mouse-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 7ee13cbc..cc5ff2c2 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]
@@ -188,8 +181,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);
     }
@@ -213,7 +204,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);
@@ -295,28 +285,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 a84bde5d..9d06b775 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..ba5891ce
--- /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.begin (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..337755dc
--- /dev/null
+++ b/src/preferences/resources-page.vala
@@ -0,0 +1,325 @@
+// 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 async 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");
+
+        yield on_run_in_bg_toggled ();
+        yield mark_recommended_resources ();
+
+        var accel3d_is_supported = yield machine.supports_accel3d ();
+        acceleration_3d_row.visible = accel3d_is_supported;
+        if (accel3d_is_supported) {
+            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]