[gnome-boxes/wip/new-properties-dialog] WIP
- From: Felipe Borges <felipeborges src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [gnome-boxes/wip/new-properties-dialog] WIP
- Date: Thu, 11 Feb 2021 10:41:50 +0000 (UTC)
commit 2f94186a4b9a9e80655fb58ee546968aa77819f9
Author: Felipe Borges <felipeborges gnome org>
Date: Wed Jan 13 11:02:12 2021 +0100
WIP
data/gnome-boxes.gresource.xml | 4 +
data/ui/preferences/devices-page.ui | 67 ++++++
data/ui/preferences/preferences-dialog.ui | 26 ++
data/ui/preferences/resources-page.ui | 158 +++++++++++++
data/ui/preferences/snapshots-page.ui | 80 +++++++
data/ui/properties-shared-folder-row.ui | 2 +-
data/ui/properties-window.ui | 8 +-
data/ui/shared-folders.ui | 7 +-
data/ui/snapshots-widget.ui | 68 ++++++
src/app-window.vala | 9 +-
src/meson.build | 5 +
src/preferences/devices-page.vala | 12 +
src/preferences/preferences-dialog.vala | 45 ++++
src/preferences/preferences-page.vala | 58 +++++
src/preferences/resources-page.vala | 378 ++++++++++++++++++++++++++++++
src/preferences/snapshots-page.vala | 153 ++++++++++++
src/properties-window.vala | 14 +-
src/shared-folders.vala | 8 +-
src/snapshot-list-row.vala | 47 +---
src/spice-display.vala | 7 -
20 files changed, 1089 insertions(+), 67 deletions(-)
---
diff --git a/data/gnome-boxes.gresource.xml b/data/gnome-boxes.gresource.xml
index 66528948..69fc28bf 100644
--- a/data/gnome-boxes.gresource.xml
+++ b/data/gnome-boxes.gresource.xml
@@ -24,6 +24,10 @@
<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/preferences/preferences-dialog.ui</file>
+ <file preprocess="xml-stripblanks">ui/preferences/devices-page.ui</file>
+ <file preprocess="xml-stripblanks">ui/preferences/resources-page.ui</file>
+ <file preprocess="xml-stripblanks">ui/preferences/snapshots-page.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>
diff --git a/data/ui/preferences/devices-page.ui b/data/ui/preferences/devices-page.ui
new file mode 100644
index 00000000..5380f1d0
--- /dev/null
+++ b/data/ui/preferences/devices-page.ui
@@ -0,0 +1,67 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interface>
+ <template class="BoxesPreferencesDevicesPage" parent="HdyPreferencesPage">
+ <property name="visible">True</property>
+ <property name="icon-name">media-removable-symbolic</property>
+ <property name="title" translatable="yes">Devices & Shares</property>
+
+ <child>
+ <object class="HdyPreferencesGroup" id="usb_devices_group">
+ <property name="visible">True</property>
+ <property name="title" translatable="yes">Devices</property>
+
+ <child>
+ <object class="HdyActionRow">
+ <property name="visible">True</property>
+ <property name="title" translatable="yes">No CD/DVD Image</property>
+
+ <child>
+ <object class="GtkButton">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">Select</property>
+ <property name="valign">center</property>
+ </object>
+ </child>
+ </object>
+ </child>
+ </object>
+ </child>
+
+ <child>
+ <object class="HdyPreferencesGroup">
+ <property name="visible">True</property>
+ <property name="title" translatable="yes">Folder Shares</property>
+
+ <child>
+ <object class="BoxesSharedFoldersWidget" id="shared_folders_widget">
+ <property name="visible">True</property>
+ </object>
+ </child>
+
+ <!--<child>
+ <object class="HdyPreferencesRow">
+ <property name="visible">True</property>
+
+ <child>
+ <object class="GtkButton">
+ <property name="visible">True</property>
+ <property name="valign">center</property>
+ <property name="halign">center</property>
+ <style>
+ <class name="flat"/>
+ </style>
+
+ <child>
+ <object class="GtkImage">
+ <property name="visible">True</property>
+ <property name="icon-name">list-add-symbolic</property>
+ </object>
+ </child>
+ </object>
+ </child>
+ </object>
+ </child> -->
+ </object>
+ </child>
+ </template>
+</interface>
diff --git a/data/ui/preferences/preferences-dialog.ui b/data/ui/preferences/preferences-dialog.ui
new file mode 100644
index 00000000..6785352a
--- /dev/null
+++ b/data/ui/preferences/preferences-dialog.ui
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interface>
+ <template class="BoxesPreferencesDialog" parent="HdyPreferencesWindow">
+ <property name="role">boxes-preferences</property>
+ <property name="can_focus">False</property>
+ <property name="resizable">True</property>
+ <property name="modal">True</property>
+ <property name="type-hint">dialog</property>
+ <property name="width-request">724</property>
+ <property name="height-request">468</property>
+ <signal name="key-press-event" after="yes" handler="on_key_pressed"/>
+
+ <child>
+ <object class="BoxesPreferencesResourcesPage" id="resources_page"/>
+ </child>
+
+ <child>
+ <object class="BoxesPreferencesDevicesPage" id="devices_page"/>
+ </child>
+
+ <child>
+ <object class="BoxesPreferencesSnapshotsPage" 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..ce3402b8
--- /dev/null
+++ b/data/ui/preferences/resources-page.ui
@@ -0,0 +1,158 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interface>
+ <template class="BoxesPreferencesResourcesPage" parent="HdyPreferencesPage">
+ <property name="visible">True</property>
+ <property name="icon-name">network-cellular-signal-excellent-symbolic</property>
+ <property name="title" translatable="yes">Resources</property>
+
+ <child>
+ <object class="HdyPreferencesGroup" id="box_stats">
+ <property name="visible">True</property>
+
+ <child>
+ <object class="HdyActionRow">
+ <property name="visible">True</property>
+ <property name="title" translatable="yes">Box Name</property>
+
+ <child>
+ <object class="GtkEntry" id="box_name_entry">
+ <property name="visible">True</property>
+ <property name="hexpand">True</property>
+ <property name="valign">center</property>
+ </object>
+ </child>
+ </object>
+ </child>
+
+ <child>
+ <object class="HdyActionRow">
+ <property name="visible">True</property>
+ <property name="title" translatable="yes">3D Acceleration</property>
+ <property name="activatable_widget">accel_3d_toggle</property>
+
+ <child>
+ <object class="GtkSwitch" id="accel_3d_toggle">
+ <property name="visible">True</property>
+ <property name="valign">center</property>
+ </object>
+ </child>
+ </object>
+ </child>
+
+ <child>
+ <object class="GtkBox">
+ <property name="visible">True</property>
+ <property name="margin-top">18</property>
+
+ <child>
+ <object class="GtkButton">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">Troubleshooting Log</property>
+ </object>
+ </child>
+
+ <child>
+ <object class="GtkBox">
+ <property name="visible">True</property>
+ <property name="spacing">10</property>
+
+ <child>
+ <object class="GtkButton" id="restart_button">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">Restart</property>
+ <signal name="clicked" handler="on_restart_button_clicked"/>
+ <style>
+ <class name="suggested-action"/>
+ </style>
+ </object>
+ </child>
+ <child>
+ <object class="GtkButton" id="force_shutdown_button">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">Force Shutdown</property>
+ <signal name="clicked" handler="on_force_shutdown_button_clicked"/>
+ <style>
+ <class name="destructive-action"/>
+ </style>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="pack-type">end</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ </object>
+ </child>
+
+ <child>
+ <object class="HdyPreferencesGroup" id="box_resources">
+ <property name="visible">True</property>
+ <property name="title" translatable="yes">Resources</property>
+
+ <child>
+ <object class="HdyActionRow">
+ <property name="visible">True</property>
+ <property name="title" translatable="yes">Memory</property>
+
+ <child>
+ <object class="GtkBox" id="memory_widget">
+ <property name="visible">True</property>
+ </object>
+ </child>
+ </object>
+ </child>
+
+ <child>
+ <object class="HdyActionRow">
+ <property name="visible">True</property>
+ <property name="title" translatable="yes">Storage</property>
+
+ <child>
+ <object class="GtkBox" id="storage_widget">
+ <property name="visible">True</property>
+ </object>
+ </child>
+ </object>
+ </child>
+
+ <child>
+ <object class="HdyActionRow">
+ <property name="visible">True</property>
+ <property name="title" translatable="yes">CPUs</property>
+
+ <child>
+ <object class="GtkSpinButton" id="cpus_spinbutton">
+ <property name="visible">True</property>
+ <property name="valign">center</property>
+ </object>
+ </child>
+ </object>
+ </child>
+
+ <child>
+ <object class="HdyActionRow">
+ <property name="visible">True</property>
+ <property name="title" translatable="yes">Allow running in background</property>
+
+ <child>
+ <object class="GtkSwitch" id="run_in_bg_switch">
+ <property name="visible">True</property>
+ <property name="valign">center</property>
+ </object>
+ </child>
+ </object>
+ </child>
+ </object>
+ </child>
+ </template>
+
+ <object class="GtkSizeGroup">
+ <property name="mode">horizontal</property>
+ <widgets>
+ <widget name="restart_button"/>
+ <widget name="force_shutdown_button"/>
+ </widgets>
+ </object>
+</interface>
diff --git a/data/ui/preferences/snapshots-page.ui b/data/ui/preferences/snapshots-page.ui
new file mode 100644
index 00000000..1fec905d
--- /dev/null
+++ b/data/ui/preferences/snapshots-page.ui
@@ -0,0 +1,80 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interface>
+ <template class="BoxesPreferencesSnapshotsPage" parent="BoxesPreferencesPage">
+ <property name="visible">True</property>
+ <property name="icon-name">rotation-allowed-symbolic</property>
+ <property name="title" translatable="yes">Snapshots</property>
+
+ <child>
+ <object class="HdyPreferencesGroup">
+ <property name="visible">True</property>
+
+ <child>
+ <object class="GtkBox">
+ <property name="visible">True</property>
+ <property name="orientation">vertical</property>
+ <property name="margin-top">20</property>
+ <property name="margin-bottom">20</property>
+ <style>
+ <class name="frame"/>
+ </style>
+ <child>
+ <object class="GtkStack" id="stack">
+ <property name="visible">True</property>
+
+ <child>
+ <object class="GtkListBox" id="list_page">
+ <property name="visible">True</property>
+ <property name="selection-mode">none</property>
+ <signal name="add" handler="update_stack"/>
+ <signal name="remove" handler="update_stack"/>
+ </object>
+ </child>
+
+ <child>
+ <object class="HdyStatusPage" id="empty_page">
+ <property name="visible">True</property>
+ <property name="hexpand">True</property>
+ <property name="vexpand">True</property>
+ <property name="icon-name">empty-boxes</property>
+ <property name="title" translatable="yes">No snapshots created yet.</property>
+ <property name="description" translatable="yes">Create one using the button
below.</property>
+ </object>
+ </child>
+
+ <child>
+ <object class="GtkBox" id="activity_page">
+ <property name="visible">True</property>
+
+ <child>
+ <object class="GtkSpinner">
+ <property name="visible">True</property>
+ <property name="active">True</property>
+ </object>
+ </child>
+ </object>
+ </child>
+ </object>
+ </child>
+
+ <child>
+ <object class="GtkButton">
+ <property name="visible">True</property>
+ <signal name="clicked" handler="create_snapshot"/>
+ <style>
+ <class name="flat"/>
+ </style>
+ <child>
+ <object class="GtkImage">
+ <property name="visible">True</property>
+ <property name="icon-name">list-add-symbolic</property>
+ </object>
+ </child>
+ </object>
+ </child>
+ </object>
+ </child>
+ </object>
+ </child>
+ </template>
+</interface>
diff --git a/data/ui/properties-shared-folder-row.ui b/data/ui/properties-shared-folder-row.ui
index b703084d..c4e9da00 100644
--- a/data/ui/properties-shared-folder-row.ui
+++ b/data/ui/properties-shared-folder-row.ui
@@ -2,7 +2,7 @@
<!-- Generated with glade 3.20.0 -->
<interface>
<requires lib="gtk+" version="3.19"/>
- <template class="BoxesSharedFolderRow" parent="GtkListBoxRow">
+ <template class="BoxesSharedFolderRow" parent="HdyPreferencesRow">
<property name="visible">True</property>
<child>
diff --git a/data/ui/properties-window.ui b/data/ui/properties-window.ui
index 8a8e63d3..1bd6053f 100644
--- a/data/ui/properties-window.ui
+++ b/data/ui/properties-window.ui
@@ -21,7 +21,7 @@
|-> topbar = new Boxes.PropertiesToolbar (); // as titlebar
-->
- <template class="BoxesPropertiesWindow" parent="GtkWindow">
+ <template class="BoxesPropertiesWindow" parent="HdyPreferencesWindow">
<property name="can_focus">False</property>
<property name="resizable">True</property>
<property name="modal">True</property>
@@ -95,12 +95,6 @@
</object>
</child>
-
- <child type="titlebar">
- <object class="BoxesPropertiesToolbar" id="topbar">
- <property name="visible">True</property>
- </object>
- </child>
</template>
<object class="GtkFileFilter" id="supported_files_filter">
diff --git a/data/ui/shared-folders.ui b/data/ui/shared-folders.ui
index 25431a8a..e3734645 100644
--- a/data/ui/shared-folders.ui
+++ b/data/ui/shared-folders.ui
@@ -24,15 +24,14 @@
<property name="visible">True</property>
<property name="halign">center</property>
<signal name="clicked" handler="on_add_button_clicked"/>
+ <style>
+ <class name="flat"/>
+ </style>
<child>
<object class="GtkImage">
<property name="visible">True</property>
<property name="icon-name">list-add-symbolic</property>
- <property name="icon-size">button</property>
- <style>
- <class name="flat"/>
- </style>
</object>
</child>
</object>
diff --git a/data/ui/snapshots-widget.ui b/data/ui/snapshots-widget.ui
new file mode 100644
index 00000000..6a0b89f1
--- /dev/null
+++ b/data/ui/snapshots-widget.ui
@@ -0,0 +1,68 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interface>
+ <template class="BoxesSnapshotsWidget" parent="GtkStack">
+ <property name="visible">True</property>
+
+ <child>
+ <object class="GtkBox" id="list_page">
+ <property name="visible">True</property>
+ <property name="margin">20</property>
+ <property name="orientation">vertical</property>
+ <signal name="add" handler="update_snapshot_stack_page"/>
+ <signal name="remove" handler="update_snapshot_stack_page"/>
+
+ <child>
+ <object class="GtkListBox" id="listbox">
+ <property name="visible">True</property>
+ <property name="selection-mode">none</property>
+
+ <style>
+ <class name="frame"/>
+ </style>
+ </object>
+ </child>
+
+ <child>
+ <object class="GtkButton">
+ <property name="visible">True</property>
+ <signal name="clicked" handler="create_snapshot"/>
+
+ <child>
+ <object class="GtkImage">
+ <property name="visible">True</property>
+ <property name="icon-name">list-add-symbolic</property>
+ <style>
+ <class name="flat"/>
+ </style>
+ </object>
+ </child>
+ </object>
+ </child>
+ </object>
+ </child>
+
+ <child>
+ <object class="HdyStatusPage" id="empty_page">
+ <property name="visible">True</property>
+ <property name="hexpand">True</property>
+ <property name="vexpand">True</property>
+ <property name="icon-name">empty-boxes</property>
+ <property name="title" translatable="yes">No snapshots created yet.</property>
+ <property name="description" translatable="yes">Create one using the button below.</property>
+ </object>
+ </child>
+
+ <child>
+ <object class="GtkBox" id="activity_page">
+ <property name="visible">True</property>
+
+ <child>
+ <object class="GtkSpinner">
+ <property name="visible">True</property>
+ <property name="active">True</property>
+ </object>
+ </child>
+ </object>
+ </child>
+ </template>
+</interface>
diff --git a/src/app-window.vala b/src/app-window.vala
index 348ce22c..88ae4947 100644
--- a/src/app-window.vala
+++ b/src/app-window.vala
@@ -296,7 +296,12 @@ public void show_welcome_tutorial () {
}
public void show_properties () {
- if (current_item != null) {
+ if (current_item == null)
+ return;
+
+ new Boxes.PreferencesDialog (this, current_item as Machine).present ();
+
+ /*if (current_item != null) {
if (ui_state == UIState.COLLECTION && selection_mode)
selection_mode = false;
set_state (UIState.PROPERTIES);
@@ -314,7 +319,7 @@ public void show_properties () {
current_item = item;
set_state (UIState.PROPERTIES);
break;
- }
+ }*/
}
public void show_send_file () {
diff --git a/src/meson.build b/src/meson.build
index aa45b773..9e54ee24 100644
--- a/src/meson.build
+++ b/src/meson.build
@@ -79,6 +79,11 @@ vala_sources = [
'notificationbar.vala',
'os-database.vala',
'portals.vala',
+ 'preferences/preferences-dialog.vala',
+ 'preferences/preferences-page.vala',
+ 'preferences/devices-page.vala',
+ 'preferences/resources-page.vala',
+ 'preferences/snapshots-page.vala',
'properties.vala',
'properties-window.vala',
'properties-page-widget.vala',
diff --git a/src/preferences/devices-page.vala b/src/preferences/devices-page.vala
new file mode 100644
index 00000000..32fe8cfd
--- /dev/null
+++ b/src/preferences/devices-page.vala
@@ -0,0 +1,12 @@
+// This file is part of GNOME Boxes. License: LGPLv2+
+using Gtk;
+
+[GtkTemplate (ui = "/org/gnome/Boxes/ui/preferences/devices-page.ui")]
+private class Boxes.PreferencesDevicesPage: Hdy.PreferencesPage {
+ [GtkChild]
+ private Boxes.SharedFoldersWidget shared_folders_widget;
+
+ public void setup (LibvirtMachine machine) {
+ shared_folders_widget.setup (machine.domain.get_uuid ());
+ }
+}
diff --git a/src/preferences/preferences-dialog.vala b/src/preferences/preferences-dialog.vala
new file mode 100644
index 00000000..6f71a5a8
--- /dev/null
+++ b/src/preferences/preferences-dialog.vala
@@ -0,0 +1,45 @@
+// This file is part of GNOME Boxes. License: LGPLv2+
+using Gtk;
+
+[GtkTemplate (ui = "/org/gnome/Boxes/ui/preferences/preferences-dialog.ui")]
+private class Boxes.PreferencesDialog: Hdy.PreferencesWindow {
+ private unowned AppWindow app_window;
+
+ [GtkChild]
+ private PreferencesResourcesPage resources_page;
+ [GtkChild]
+ private PreferencesDevicesPage devices_page;
+ [GtkChild]
+ private PreferencesSnapshotsPage snapshots_page;
+
+ public PreferencesDialog (AppWindow app_window, Machine machine) {
+ set_transient_for (app_window);
+ this.app_window = app_window;
+
+ resources_page.setup (machine as LibvirtMachine);
+ devices_page.setup (machine as LibvirtMachine);
+ snapshots_page.setup (machine as LibvirtMachine);
+ }
+
+ [GtkCallback]
+ private bool on_key_pressed (Widget widget, Gdk.EventKey event) {
+ var default_modifiers = Gtk.accelerator_get_default_mod_mask ();
+ var direction = get_direction ();
+
+ if (((direction == Gtk.TextDirection.LTR && // LTR
+ event.keyval == Gdk.Key.Left) || // ALT + Left -> back
+ (direction == Gtk.TextDirection.RTL && // RTL
+ event.keyval == Gdk.Key.Right)) && // ALT + Right -> back
+ (event.state & default_modifiers) == Gdk.ModifierType.MOD1_MASK) {
+ //topbar.click_back_button ();
+
+ return true;
+ } else if (event.keyval == Gdk.Key.Escape) { // ESC -> back
+ //revert_state ();
+
+ return true;
+ }
+
+ return false;
+ }
+}
diff --git a/src/preferences/preferences-page.vala b/src/preferences/preferences-page.vala
new file mode 100644
index 00000000..c5409370
--- /dev/null
+++ b/src/preferences/preferences-page.vala
@@ -0,0 +1,58 @@
+// This file is part of GNOME Boxes. License: LGPLv2+
+using Gtk;
+
+private class Boxes.PreferencesNotification: Gtk.InfoBar {
+ private Notification.DismissFunc? dismiss_func;
+
+ public PreferencesNotification (string message,
+ string action_label,
+ owned Notification.OKFunc ok_func,
+ owned Notification.DismissFunc? ignore_func = null) {
+ dismiss_func = ignore_func;
+
+ var label = new Gtk.Label (message) {
+ hexpand = true,
+ halign = Gtk.Align.START
+ };
+ var button = new Gtk.Button.with_label (action_label);
+ button.clicked.connect (() => {
+ if (ok_func != null)
+ ok_func ();
+
+ destroy ();
+ });
+
+ get_content_area ().add (label);
+ get_content_area ().add (button);
+ }
+
+ public void dismiss () {
+ if (dismiss_func != null)
+ dismiss_func ();
+ }
+}
+
+private class Boxes.PreferencesPage: Hdy.PreferencesPage {
+ protected PreferencesNotification active_notification = null;
+
+ protected void display_notification (string message,
+ string action_label,
+ owned Notification.OKFunc ok_func,
+ owned Notification.DismissFunc? ignore_func = null) {
+ if (active_notification != null) {
+ active_notification.dismiss ();
+ }
+
+ active_notification = new PreferencesNotification (message, action_label, ok_func, ignore_func);
+
+ var box = get_parent ().get_parent () as Gtk.Container;
+ box.add_with_properties (active_notification, "position", 0, null);
+
+ active_notification.show_all ();
+ }
+
+ ~PreferencesPage () {
+ if (active_notification != null)
+ active_notification.dismiss ();
+ }
+}
diff --git a/src/preferences/resources-page.vala b/src/preferences/resources-page.vala
new file mode 100644
index 00000000..bbe26f46
--- /dev/null
+++ b/src/preferences/resources-page.vala
@@ -0,0 +1,378 @@
+// This file is part of GNOME Boxes. License: LGPLv2+
+using Gtk;
+
+[GtkTemplate (ui = "/org/gnome/Boxes/ui/preferences/resources-page.ui")]
+private class Boxes.PreferencesResourcesPage: Hdy.PreferencesPage {
+ [GtkChild]
+ private Gtk.Entry box_name_entry;
+ [GtkChild]
+ private Gtk.Switch accel_3d_toggle;
+
+ [GtkChild]
+ private Gtk.Button restart_button;
+ [GtkChild]
+ private Gtk.Button force_shutdown_button;
+
+ [GtkChild]
+ private Gtk.Box memory_widget;
+ [GtkChild]
+ private Gtk.Box storage_widget;
+ [GtkChild]
+ private Gtk.SpinButton cpus_spinbutton;
+ [GtkChild]
+ private Gtk.Switch run_in_bg_switch;
+
+ private LibvirtMachine machine;
+
+ public void setup (LibvirtMachine machine) {
+ this.machine = machine;
+
+ box_name_entry.set_text (machine.name);
+ box_name_entry.bind_property ("text", machine, "name", BindingFlags.BIDIRECTIONAL);
+
+ accel_3d_toggle.set_active (machine.acceleration_3d);
+ accel_3d_toggle.bind_property ("active", machine, "acceleration-3d", BindingFlags.BIDIRECTIONAL);
+
+#if FLATPAK
+ if (accel_3d_toggle.get_active ())
+ on_run_in_bg ();
+#endif
+
+ // Action buttons
+ restart_button.sensitive = force_shutdown_button.sensitive = machine.is_running;
+ machine.notify["state"].connect (on_machine_state_changed);
+
+ var host_topology = machine.connection.get_node_info ();
+ add_ram_property ();
+ add_storage_property ();
+
+ /* CPU property */
+ var vcpus = machine.domain_config.get_vcpus ();
+ var max_vcpus = host_topology.cores * host_topology.sockets * host_topology.threads;
+ cpus_spinbutton.set_range (1, max_vcpus);
+ cpus_spinbutton.set_value (vcpus);
+
+ run_in_bg_switch.set_active (machine.run_in_bg);
+ //run_in_bg_switch.bind_property ("active", machine, "run-in-bg", BindingFlags.BIDIRECTIONAL);
+
+ run_in_bg_switch.notify["active"].connect (() => {
+ var box = get_parent ().get_parent () as Gtk.Container;
+ var infobar = new Gtk.InfoBar () {
+ valign = Gtk.Align.START
+ };
+ infobar.add (new Gtk.Label ("TEST LABEL"));
+ //box.pack_end (infobar, false, false, 0);
+ box.add_with_properties (infobar, "position", 0, null);
+ infobar.show_all ();
+ });
+
+ }
+
+ private async void on_run_in_bg () {
+ if (!machine.run_in_bg)
+ return;
+
+ yield Portals.get_default ().request_to_run_in_background (
+ (response, results) => {
+ if (response == 0) {
+ debug ("User authorized Boxes to run in background");
+
+ return;
+ }
+
+ try {
+ machine.run_in_bg = false;
+
+ var msg = _("Boxes is not authorized to run in the background");
+ machine.window.notificationbar.display_for_action (msg,
+ _("Manage permissions"),
+ open_permission_settings);
+ } catch (GLib.Error error) {
+ warning ("Failed to reset VM's run-in-bg setting: %s", error.message);
+ }
+ });
+ }
+
+ private void on_machine_state_changed () {
+ restart_button.sensitive = machine.is_running;
+ force_shutdown_button.sensitive = machine.is_running;
+ }
+
+ [GtkCallback]
+ private void on_restart_button_clicked () {
+ machine.restart ();
+ }
+
+ [GtkCallback]
+ private void on_force_shutdown_button_clicked () {
+ machine.force_shutdown ();
+ }
+
+ private void add_ram_property () {
+ try {
+ var host_topology = machine.connection.get_node_info ();
+
+ var min_ram = 64 * Osinfo.MEBIBYTES;
+ var max_ram = host_topology.memory * Osinfo.KIBIBYTES;
+ var size_ram = machine.domain_config.memory * Osinfo.KIBIBYTES;
+
+ var ram_property = new SizeProperty ("ram", size_ram, min_ram, max_ram, 0, min_ram,
GLib.FormatSizeFlags.IEC_UNITS);
+ memory_widget.add (ram_property.extra_widget);
+ ram_property.extra_widget.visible = true;
+
+ if ((VMConfigurator.is_install_config (machine.domain_config) ||
+ VMConfigurator.is_live_config (machine.domain_config)) &&
+ machine.state != Machine.MachineState.FORCE_STOPPED)
+ ram_property.sensitive = false;
+ else
+ ram_property.changed.connect ((property, value) => {
+ property.deferred_change = () => {
+ var ram = (value + Osinfo.KIBIBYTES - 1) / 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.domain.get_name (), ram, error.message);
+ }
+
+ update_ram_property (property);
+
+ return false;
+ };
+ });
+
+ machine.notify["state"].connect (() => {
+ if (!machine.is_on)
+ ram_property.reboot_required = false;
+ });
+
+ update_ram_property (ram_property);
+ } catch (GLib.Error error) {
+ warning ("Failed to add RAM property: %s", error.message);
+ }
+ }
+
+ private void update_ram_property (Boxes.Property property) {
+ try {
+ var config = machine.domain.get_config (GVir.DomainXMLFlags.INACTIVE);
+
+ // we use KiB unit, convert to MiB
+ var actual = machine.domain_config.memory / 1024;
+ var pending = config.memory / 1024;
+
+ debug ("RAM actual: %llu, pending: %llu", actual, pending);
+ // somehow, there are rounded errors, so let's forget about 1Mb diff
+ property.reboot_required = (actual - pending) > 1; // no need for abs()
+ } catch (GLib.Error e) {}
+ }
+
+ private const uint64 MEGABYTES = 1000 * 1000;
+ private void add_storage_property () {
+ if (machine.importing || machine.storage_volume == null)
+ 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 = get_minimum_disk_size ();
+ var max_storage = volume_info.allocation + pool_info.available;
+
+ if (min_storage >= max_storage) {
+ var label = new Gtk.Label ("");
+ var capacity = format_size (volume_info.capacity, FormatSizeFlags.DEFAULT);
+ var allocation = format_size (volume_info.allocation, FormatSizeFlags.DEFAULT);
+ var label_text = _("Maximum Disk Space");
+ var allocation_text = _("%s used").printf (allocation);
+ var markup = ("<span color=\"grey\">%s</span>\t\t %s <span
color=\"grey\">(%s)</span>").printf (label_text, capacity, allocation_text);
+ label.set_markup (markup);
+ label.halign = Gtk.Align.START;
+
+ storage_widget.add (label);
+
+ var infobar = new Gtk.InfoBar ();
+ infobar.message_type = Gtk.MessageType.WARNING;
+
+ var content = infobar.get_content_area ();
+
+ var image = new Gtk.Image ();
+ image.icon_name = "dialog-warning";
+ image.icon_size = 3;
+ content.add (image);
+
+ var msg = _("There is not enough space on your machine to increase the maximum disk size.");
+ label = new Gtk.Label (msg);
+ content.add (label);
+
+ //add_property (ref list, null, infobar);
+ storage_widget.add (infobar);
+ }
+
+ var property = new SizeProperty ("storage",
+ volume_info.capacity,
+ min_storage,
+ max_storage,
+ volume_info.allocation,
+ 256 * MEGABYTES,
+ GLib.FormatSizeFlags.DEFAULT);
+
+ // Disable 'save on timeout' all together since that could lead us to very bad user experience:
+ // You accidently increase the capacity to too high value and if you are not quick enough to
change
+ // it again, you'll not be able to correct this ever as we don't support shrinking of volumes.
+ property.defer_interval = 0;
+ if ((VMConfigurator.is_install_config (machine.domain_config) ||
+ VMConfigurator.is_live_config (machine.domain_config)) &&
+ machine.window.ui_state != Boxes.UIState.WIZARD &&
+ machine.state != Machine.MachineState.FORCE_STOPPED)
+ property.sensitive = false;
+ else
+ property.changed.connect (on_storage_changed);
+
+ storage_widget.add (property.extra_widget);
+ storage_widget.show_all ();
+ } catch (GLib.Error error) {
+ warning ("Failed to get information on volume '%s' or it's parent pool: %s",
+ machine.storage_volume.get_name (),
+ error.message);
+ }
+ }
+
+ private async void change_storage_size (Boxes.Property property, uint64 value) {
+ if (machine.storage_volume == null)
+ return;
+
+ List<GVir.DomainSnapshot> snapshots;
+ try {
+ snapshots = yield get_snapshots (null);
+ } catch (GLib.Error e) {
+ warning ("Error fetching snapshots for %s: %s", machine.name, e.message);
+ snapshots = new List<GVir.DomainSnapshot> ();
+ }
+
+ var num_snapshots = snapshots.length ();
+ if (num_snapshots != 0) {
+ // qemu-img doesn't support resizing disk image with snapshots:
+ // https://bugs.launchpad.net/qemu/+bug/1563931
+ var msg = ngettext ("Storage resize requires deleting associated snapshot.",
+ "Storage resize requires deleting %llu associated snapshots.",
+ num_snapshots).printf (num_snapshots);
+
+ Notification.OKFunc undo = () => {
+ debug ("Storage resize of '%s' cancelled by user.", machine.name);
+ };
+
+ Notification.DismissFunc really_resize = () => {
+ debug ("User did not cancel storage resize of '%s'. Deleting all snapshots..", machine.name);
+ force_change_storage_size.begin (property, value);
+ };
+
+ machine.window.notificationbar.display_for_action (msg, _("_Undo"), (owned) undo, (owned)
really_resize);
+
+ return;
+ }
+
+ try {
+ if (machine.is_running) {
+ var disk = machine.get_domain_disk ();
+ if (disk == null)
+ return;
+
+ var size = (value + Osinfo.KIBIBYTES - 1) / Osinfo.KIBIBYTES;
+ disk.resize (size, 0);
+
+ var pool = get_storage_pool (machine.connection);
+ try {
+ yield pool.refresh_async (null);
+ machine.update_domain_config ();
+ debug ("Storage changed to %llu KiB", size);
+ } catch (GLib.Error error) {
+ warning ("Failed to change storage capacity of volume '%s' to %llu KiB: %s",
+ machine.storage_volume.get_name (),
+ size,
+ error.message);
+ }
+ } else {
+ resize_storage_volume (value);
+ debug ("Storage changed to %llu", value);
+ }
+ } catch (GLib.Error error) {
+ warning ("Failed to change storage capacity of volume '%s' to %llu: %s",
+ machine.storage_volume.get_name (),
+ value,
+ error.message);
+ }
+ }
+
+ private async void force_change_storage_size (Boxes.Property property, uint64 value) {
+ try {
+ var snapshots = yield get_snapshots (null);
+
+ foreach (var snapshot in snapshots) {
+ yield snapshot.delete_async (0, null);
+ }
+
+ change_storage_size.begin (property, value);
+ } catch (GLib.Error e) {
+ warning ("Error while deleting snapshots: %s", e.message);
+ }
+ }
+
+ private uint64 get_minimum_disk_size () throws GLib.Error {
+ var volume_info = machine.storage_volume.get_info ();
+ if (machine.vm_creator == null) {
+ // Since we disable the properties during install going on we don't need to check for
+ // previous_ui_state here to be WIZARD.
+ return volume_info.capacity;
+ }
+
+ Osinfo.Resources minimum_resources = null;
+
+ if (machine.vm_creator.install_media.os != null) {
+ var os = machine.vm_creator.install_media.os;
+ var architecture = machine.domain_config.get_os ().get_arch ();
+ minimum_resources = OSDatabase.get_minimum_resources_for_os (os, architecture);
+ }
+
+ if (minimum_resources != null && minimum_resources.storage != -1) {
+ return minimum_resources.storage;
+ } else {
+ minimum_resources = OSDatabase.get_minimum_resources ();
+ return uint64.min (volume_info.capacity, minimum_resources.storage);
+ }
+ }
+
+ 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) {
+ // New VM Customization
+ 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.NONE);
+ }
+ }
+
+ private void on_storage_changed (Boxes.Property property, uint64 value) {
+ // Ensure that we don't end-up changing storage like a 1000 times a second while user moves the
slider..
+ property.deferred_change = () => {
+ change_storage_size.begin (property, value);
+
+ return false;
+ };
+ }
+
+ public async List<GVir.DomainSnapshot>? get_snapshots (GLib.Cancellable? cancellable) throws GLib.Error {
+ yield machine.domain.fetch_snapshots_async (GVir.DomainSnapshotListFlags.ALL, cancellable);
+
+ return machine.domain.get_snapshots ();
+ }
+}
diff --git a/src/preferences/snapshots-page.vala b/src/preferences/snapshots-page.vala
new file mode 100644
index 00000000..9dd24225
--- /dev/null
+++ b/src/preferences/snapshots-page.vala
@@ -0,0 +1,153 @@
+// This file is part of GNOME Boxes. License: LGPLv2+
+using Gtk;
+
+[GtkTemplate (ui = "/org/gnome/Boxes/ui/preferences/snapshots-page.ui")]
+private class Boxes.PreferencesSnapshotsPage: Boxes.PreferencesPage {
+ private LibvirtMachine machine;
+
+ [GtkChild]
+ private Gtk.Stack stack;
+ [GtkChild]
+ private Gtk.ListBox list_page;
+ [GtkChild]
+ private Hdy.StatusPage empty_page;
+ [GtkChild]
+ private Gtk.Box activity_page;
+
+ private string? activity {
+ set {
+ if (value == null) {
+ stack.visible_child = list_page;
+ } else {
+ stack.visible_child = activity_page;
+ }
+ }
+ }
+
+ public void setup (LibvirtMachine machine) {
+ this.machine = machine;
+
+ list_page.set_sort_func (config_sort_func);
+
+ fetch_snapshots.begin ();
+ }
+
+ private async void fetch_snapshots () {
+ try {
+ var snapshots = yield machine.properties.get_snapshots (null);
+
+ foreach (var snapshot in snapshots) {
+ list_page.add (create_snapshot_row (snapshot));
+ }
+ } catch (GLib.Error e) {
+ warning ("Could not fetch snapshots: %s", e.message);
+ }
+
+ update_stack ();
+ }
+
+ private SnapshotListRow create_snapshot_row (GVir.DomainSnapshot snapshot) {
+ var row = new SnapshotListRow (snapshot, machine);
+ row.notify["activity-message"].connect (row_activity_changed);
+ row.removed.connect (() => {
+ row.visible = false;
+
+ string snapshot_identifier = row.snapshot.get_name ();
+ try {
+ var config = row.snapshot.get_config (0);
+ snapshot_identifier = config.get_description ();
+ } catch (GLib.Error e) {
+ warning ("Could not get configuration of snapshot %s: %s",
+ row.snapshot.get_name (),
+ e.message);
+ }
+ var message = _("Snapshot “%s” deleted.").printf (snapshot_identifier);
+
+ Notification.OKFunc undo = () => {
+ row.visible = true;
+
+ active_notification = null;
+ };
+
+ Notification.DismissFunc really_remove = () => {
+ row.really_remove ();
+ };
+
+ display_notification (message, _("Undo"), (owned) undo, (owned) really_remove);
+ });
+
+ return row;
+ }
+
+ [GtkCallback]
+ private async void create_snapshot () {
+ if (machine.state == Machine.MachineState.RUNNING)
+ this.activity = _("Creating new snapshot…");
+
+ try {
+ var new_snapshot = yield machine.create_snapshot ();
+ list_page.add (create_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;
+ }
+
+ private void remove_snapshot (SnapshotListRow row) {
+ row.visible = false;
+
+ string snapshot_identifier = row.snapshot.get_name ();
+ try {
+ var config = row.snapshot.get_config (0);
+ snapshot_identifier = config.get_description ();
+ } catch (GLib.Error e) {
+ warning ("Could not get configuration of snapshot %s: %s",
+ row.snapshot.get_name (),
+ e.message);
+ }
+ var message = _("Snapshot “%s” deleted.").printf (snapshot_identifier);
+
+ Notification.OKFunc undo = () => {
+ row.visible = true;
+ };
+
+ Notification.DismissFunc really_remove = () => {
+ row.really_remove ();
+ };
+
+ display_notification (message, _("Undo"), (owned) undo, (owned) really_remove);
+ }
+
+ 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_stack () {
+ var num_snapshots = list_page.get_children ().length ();
+ if (num_snapshots > 0)
+ stack.visible_child = list_page;
+ else
+ stack.visible_child = empty_page;
+ }
+
+ private int config_sort_func (Gtk.ListBoxRow row1, Gtk.ListBoxRow row2) {
+ 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;
+ }
+ }
+}
diff --git a/src/properties-window.vala b/src/properties-window.vala
index 1a16b148..ff9971ac 100644
--- a/src/properties-window.vala
+++ b/src/properties-window.vala
@@ -11,7 +11,7 @@
public delegate void Boxes.FileChosenFunc (string path);
[GtkTemplate (ui = "/org/gnome/Boxes/ui/properties-window.ui")]
-private class Boxes.PropertiesWindow: Gtk.Window, Boxes.UI {
+private class Boxes.PropertiesWindow: Hdy.PreferencesWindow, Boxes.UI {
public const string[] page_names = { "main", "troubleshoot_log", "file_chooser", "config_editor" };
public UIState previous_ui_state { get; protected set; }
@@ -24,7 +24,7 @@
_page = value;
view.visible_child_name = page_names[value];
- topbar.page = value;
+ //topbar.page = value;
}
}
@@ -39,8 +39,6 @@
public Gtk.FileChooserNative file_chooser;
[GtkChild]
- public PropertiesToolbar topbar;
- [GtkChild]
public Notificationbar notificationbar;
private unowned AppWindow app_window;
@@ -49,7 +47,7 @@ public PropertiesWindow (AppWindow app_window) {
this.app_window = app_window;
properties.setup_ui (app_window, this);
- topbar.setup_ui (app_window, this);
+ //topbar.setup_ui (app_window, this);
set_transient_for (app_window);
@@ -70,9 +68,9 @@ public void show_troubleshoot_log (string log) {
public void show_editor_view (LibvirtMachine machine) {
page = PropsWindowPage.TEXT_EDITOR;
- config_editor.setup (machine, topbar.apply_config_button);
+ //config_editor.setup (machine, topbar.apply_config_button);
- topbar.config_editor.set_title (machine.name);
+ //topbar.config_editor.set_title (machine.name);
}
public void show_file_chooser (owned FileChosenFunc file_chosen_func) {
@@ -119,7 +117,7 @@ private bool on_key_pressed (Widget widget, Gdk.EventKey event) {
(direction == Gtk.TextDirection.RTL && // RTL
event.keyval == Gdk.Key.Right)) && // ALT + Right -> back
(event.state & default_modifiers) == Gdk.ModifierType.MOD1_MASK) {
- topbar.click_back_button ();
+ //topbar.click_back_button ();
return true;
} else if (event.keyval == Gdk.Key.Escape) { // ESC -> back
diff --git a/src/shared-folders.vala b/src/shared-folders.vala
index 0157025e..8c105bff 100644
--- a/src/shared-folders.vala
+++ b/src/shared-folders.vala
@@ -181,7 +181,7 @@ private static string get_shared_folder_real_path (SharedFolder folder) {
}
[GtkTemplate (ui = "/org/gnome/Boxes/ui/properties-shared-folder-row.ui")]
-private class Boxes.SharedFolderRow : Gtk.ListBoxRow {
+private class Boxes.SharedFolderRow : Hdy.PreferencesRow {
public signal void removed (SharedFolder folder);
[GtkChild]
private Gtk.Label folder_path_label;
@@ -211,17 +211,17 @@ private void on_delete_button_clicked () {
private GLib.ListStore list_model;
- private Boxes.SharedFolderPopover popover;
+ private Boxes.SharedFolderPopover popover = new SharedFolderPopover ();
[GtkChild]
private Gtk.ListBox listbox;
- public SharedFoldersWidget (string machine_uuid) {
+ public void setup (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 = new SharedFolderPopover ();
popover.saved.connect (on_popover_saved);
}
diff --git a/src/snapshot-list-row.vala b/src/snapshot-list-row.vala
index 5a758661..adca6638 100644
--- a/src/snapshot-list-row.vala
+++ b/src/snapshot-list-row.vala
@@ -23,6 +23,8 @@
private Boxes.LibvirtMachine machine;
private unowned Gtk.Container? parent_container = null;
+ public signal void removed ();
+
private const GLib.ActionEntry[] action_entries = {
{"revert-to", revert_to_activated},
{"rename", rename_activated},
@@ -160,40 +162,17 @@ private void revert_to_activated (GLib.SimpleAction action, GLib.Variant? v) {
private void delete_activated (GLib.SimpleAction action, GLib.Variant? v) {
- string snapshot_identifier = snapshot.get_name ();
- try {
- var config = snapshot.get_config (0);
- snapshot_identifier = config.get_description ();
- } catch (GLib.Error e) {
- warning ("Could not get configuration of snapshot %s: %s",
- snapshot.get_name (),
- e.message);
- }
- var message = _("Snapshot “%s” deleted.").printf (snapshot_identifier);
- parent_container = (Gtk.Container) this.get_parent ();
- var row = this;
- parent_container.remove (this);
-
- Notification.OKFunc undo = () => {
- parent_container.add (this);
- row = null;
- };
-
- Notification.DismissFunc really_remove = () => {
- this.snapshot.delete_async.begin (0, null, (obj, res) =>{
- try {
- this.snapshot.delete_async.end (res);
- parent_container.queue_draw ();
- } catch (GLib.Error e) {
- warning ("Error while deleting snapshot %s: %s", snapshot.get_name (), e.message);
- }
- });
- row = null;
- };
- machine.window.notificationbar.display_for_action (message,
- _("_Undo"),
- (owned) undo,
- (owned) really_remove);
+ removed ();
+ }
+
+ public void really_remove () {
+ snapshot.delete_async.begin (0, null, (obj, res) => {
+ try {
+ snapshot.delete_async.end (res);
+ } catch (GLib.Error e) {
+ warning ("Error while deleting snapshot %s: %s", snapshot.get_name (), e.message) ;
+ }
+ });
}
private void rename_activated (GLib.SimpleAction action, GLib.Variant? v) {
diff --git a/src/spice-display.vala b/src/spice-display.vala
index 0d2feee7..39b5b1ed 100644
--- a/src/spice-display.vala
+++ b/src/spice-display.vala
@@ -414,9 +414,6 @@ private void on_new_file_transfer (Spice.MainChannel main_channel, Object transf
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;
}
@@ -525,10 +522,6 @@ public override void send_keys (uint[] keyvals) {
return frame;
}
- private Gtk.Frame create_shared_folders_frame () {
- return new SharedFoldersWidget (machine.config.uuid);
- }
-
private bool is_usb_kbd_or_mouse (uint8 class, uint8 subclass, uint8 protocol) {
var ret = false;
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]