[gnome-boxes/wip/text-editor: 5/5] WIP




commit 03365b9fb65c27657f97060ca01bd1a536c5f561
Author: Felipe Borges <felipeborges gnome org>
Date:   Mon Aug 3 10:51:31 2020 +0200

    WIP

 data/gnome-boxes.gresource.xml      |  1 +
 data/ui/properties-toolbar.ui       | 86 ++++++++++++++++++++++++++++++++++-
 data/ui/properties-window.ui        | 10 +++++
 data/ui/text-editor.ui              | 31 +++++++++++++
 src/libvirt-machine-properties.vala |  7 +++
 src/meson.build                     |  2 +
 src/properties-toolbar.vala         | 15 ++++++-
 src/properties-window.vala          | 12 ++++-
 src/text-editor.vala                | 90 +++++++++++++++++++++++++++++++++++++
 src/vm-configurator.vala            | 10 ++++-
 10 files changed, 259 insertions(+), 5 deletions(-)
---
diff --git a/data/gnome-boxes.gresource.xml b/data/gnome-boxes.gresource.xml
index 33f344cb..15a3c289 100644
--- a/data/gnome-boxes.gresource.xml
+++ b/data/gnome-boxes.gresource.xml
@@ -35,6 +35,7 @@
     <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/text-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>
diff --git a/data/ui/properties-toolbar.ui b/data/ui/properties-toolbar.ui
index 3d096ba3..592d7e50 100644
--- a/data/ui/properties-toolbar.ui
+++ b/data/ui/properties-toolbar.ui
@@ -55,13 +55,13 @@
             </style>
 
             <child internal-child="accessible">
-              <object class="AtkObject" id="a11y-button6">
+              <object class="AtkObject">
                 <property name="accessible-name" translatable="yes">Back</property>
               </object>
             </child>
 
             <child>
-              <object class="GtkImage" id="back_image2">
+              <object class="GtkImage">
                 <property name="visible">True</property>
                 <property name="icon-name">go-previous-symbolic</property>
               </object>
@@ -98,6 +98,88 @@
       </packing>
     </child>
 
+    <!-- Text Editor page -->
+    <child>
+      <object class="GtkHeaderBar" id="text_editor">
+        <property name="visible">True</property>
+        <property name="show-close-button">True</property>
+        <style>
+          <class name="titlebar"/>
+        </style>
+
+        <child>
+          <object class="GtkButton">
+            <property name="visible">True</property>
+            <property name="valign">center</property>
+            <signal name="clicked" handler="on_troubleshooting_back_clicked"/>
+            <style>
+              <class name="image-button"/>
+            </style>
+
+            <child internal-child="accessible">
+              <object class="AtkObject" id="a11y-button6">
+                <property name="accessible-name" translatable="yes">Back</property>
+              </object>
+            </child>
+
+            <child>
+              <object class="GtkImage" id="back_image2">
+                <property name="visible">True</property>
+                <property name="icon-name">go-previous-symbolic</property>
+              </object>
+            </child>
+          </object>
+
+          <packing>
+            <property name="pack-type">start</property>
+          </packing>
+        </child>
+
+        <child>
+          <object class="GtkButton" id="revert_button">
+            <property name="visible">True</property>
+            <property name="valign">center</property>
+            <property name="use-underline">True</property>
+            <signal name="clicked" handler="on_revert_changes_clicked"/>
+            <style>
+              <class name="image-button"/>
+            </style>
+            <child>
+              <object class="GtkImage">
+                <property name="visible">True</property>
+                <property name="icon-name">document-revert-symbolic</property>
+              </object>
+            </child>
+          </object>
+
+          <packing>
+            <property name="pack-type">end</property>
+          </packing>
+        </child>
+
+        <child>
+          <object class="GtkButton" id="save_button">
+            <property name="visible">True</property>
+            <property name="valign">center</property>
+            <property name="use-underline">True</property>
+            <property name="label" translatable="yes">_Save</property>
+            <signal name="clicked" handler="on_text_editor_save_clicked"/>
+            <style>
+              <class name="text-button"/>
+            </style>
+          </object>
+
+          <packing>
+            <property name="pack-type">end</property>
+          </packing>
+        </child>
+      </object>
+
+      <packing>
+        <property name="name">text_editor</property>
+      </packing>
+    </child>
+
     <!-- File chooser page -->
     <child>
       <object class="GtkHeaderBar" id="file_chooser">
diff --git a/data/ui/properties-window.ui b/data/ui/properties-window.ui
index f8e2dd1e..4b459eef 100644
--- a/data/ui/properties-window.ui
+++ b/data/ui/properties-window.ui
@@ -70,6 +70,16 @@
               </packing>
             </child>
 
+            <child>
+              <object class="BoxesTextEditor" id="text_editor">
+                <property name="visible">True</property>
+              </object>
+
+              <packing>
+                <property name="name">text_editor</property>
+              </packing>
+            </child>
+
             <child>
               <object class="GtkBox">
                 <property name="visible">True</property>
diff --git a/data/ui/text-editor.ui b/data/ui/text-editor.ui
new file mode 100644
index 00000000..be34f3d5
--- /dev/null
+++ b/data/ui/text-editor.ui
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interface>
+  <!-- interface-requires gtk+ 3.9 -->
+  <template class="BoxesTextEditor" parent="GtkScrolledWindow">
+    <property name="visible">True</property>
+    <property name="vexpand">True</property>
+    <property name="no-show-all">True</property>
+    <property name="min-content-width">640</property>
+    <property name="min-content-height">480</property>
+    <property name="margin-top">10</property>
+    <property name="margin-start">10</property>
+    <property name="margin-end">10</property>
+    <property name="margin-bottom">10</property>
+
+    <child>
+      <object class="GtkSourceView" id="view">
+        <property name="visible">True</property>
+        <property name="editable">True</property>
+        <property name="auto-indent">True</property>
+        <property name="indent-on-tab">True</property>
+        <property name="insert-spaces-instead-of-tabs">True</property>
+        <property name="monospace">True</property>
+        <property name="show-line-marks">True</property>
+        <property name="show-line-numbers">True</property>
+        <property name="background-pattern">grid</property>
+        <property name="wrap-mode">word</property>
+      </object>
+    </child>
+
+  </template>
+</interface>
diff --git a/src/libvirt-machine-properties.vala b/src/libvirt-machine-properties.vala
index eabdaa4d..4661bd2b 100644
--- a/src/libvirt-machine-properties.vala
+++ b/src/libvirt-machine-properties.vala
@@ -383,6 +383,13 @@ private void add_system_props_buttons (ref List<Boxes.Property> list) {
             machine.window.props_window.show_troubleshoot_log (log);
         });
 
+        var edit_button = new Gtk.Button.with_mnemonic (_("Edit XML"));
+        edit_button.halign = Gtk.Align.END;
+        grid.attach (edit_button, 2, 0, 1, 1);
+        edit_button.clicked.connect (() => {
+            machine.window.props_window.show_editor_view (machine);
+        });
+
         var prop = add_property (ref list, null, grid);
         ulong flushed_id = 0;
         flushed_id = prop.flushed.connect (() => {
diff --git a/src/meson.build b/src/meson.build
index 63006644..ff890164 100644
--- a/src/meson.build
+++ b/src/meson.build
@@ -90,6 +90,7 @@ vala_sources = [
   'selection-toolbar.vala',
   'shared-folders.vala',
   'spice-display.vala',
+  'text-editor.vala',
   'transfer-info-row.vala',
   'transfer-popover.vala',
   'troubleshoot-view.vala',
@@ -142,6 +143,7 @@ dependencies = [
   dependency ('libvirt-gobject-1.0', version: '>= 3.0.0'),
   dependency ('libxml-2.0', version: '>= 2.7.8'),
   dependency ('spice-client-gtk-3.0', version: '>= 0.32'),
+  dependency ('gtksourceview-4'),
   dependency ('tracker-sparql-2.0'),
   dependency ('vte-2.91', version: '>= 0.40.2'),
   dependency ('webkit2gtk-4.0', version: '>= 2.26.0'),
diff --git a/src/properties-toolbar.vala b/src/properties-toolbar.vala
index 6d15cfa3..a2f15d9a 100644
--- a/src/properties-toolbar.vala
+++ b/src/properties-toolbar.vala
@@ -16,6 +16,9 @@
     [GtkChild]
     public Gtk.HeaderBar main;
 
+    [GtkChild]
+    public Gtk.HeaderBar text_editor;
+
     [GtkChild]
     public Gtk.Button troubleshooting_back_button;
 
@@ -48,7 +51,7 @@ public void click_back_button () {
     }
 
     [GtkCallback]
-    private void on_troubleshooting_back_clicked () requires (page == PropsWindowPage.TROUBLESHOOTING_LOG) {
+    private void on_troubleshooting_back_clicked () requires (page == PropsWindowPage.TROUBLESHOOTING_LOG || 
page == PropsWindowPage.TEXT_EDITOR) {
         props_window.page = PropsWindowPage.MAIN;
     }
 
@@ -57,6 +60,16 @@ private void on_copy_clipboard_clicked () requires (page == PropsWindowPage.TROU
         props_window.copy_troubleshoot_log_to_clipboard ();
     }
 
+    [GtkCallback]
+    private void on_revert_changes_clicked () requires (page == PropsWindowPage.TEXT_EDITOR) {
+        props_window.text_editor.revert_to_original ();
+    }
+
+    [GtkCallback]
+    private void on_text_editor_save_clicked () {
+        props_window.text_editor.save ();
+    }
+
     [GtkCallback]
     private void on_title_entry_changed () {
         window.current_item.name = title_entry.text;
diff --git a/src/properties-window.vala b/src/properties-window.vala
index f920a72e..46f02ec1 100644
--- a/src/properties-window.vala
+++ b/src/properties-window.vala
@@ -5,13 +5,14 @@
     MAIN,
     TROUBLESHOOTING_LOG,
     FILE_CHOOSER,
+    TEXT_EDITOR,
 }
 
 public delegate void Boxes.FileChosenFunc (string path);
 
 [GtkTemplate (ui = "/org/gnome/Boxes/ui/properties-window.ui")]
 private class Boxes.PropertiesWindow: Gtk.Window, Boxes.UI {
-    public const string[] page_names = { "main", "troubleshoot_log", "file_chooser" };
+    public const string[] page_names = { "main", "troubleshoot_log", "file_chooser", "text_editor" };
 
     public UIState previous_ui_state { get; protected set; }
     public UIState ui_state { get; protected set; }
@@ -33,6 +34,8 @@
     public Properties properties;
     [GtkChild]
     public TroubleshootLog troubleshoot_log;
+    [GtkChild]
+    public TextEditor text_editor;
 
     public Gtk.FileChooserNative file_chooser;
     [GtkChild]
@@ -65,6 +68,13 @@ public void show_troubleshoot_log (string log) {
         page = PropsWindowPage.TROUBLESHOOTING_LOG;
     }
 
+    public void show_editor_view (LibvirtMachine machine) {
+        page = PropsWindowPage.TEXT_EDITOR;
+        text_editor.setup (machine);
+
+        topbar.text_editor.set_title (machine.name);
+    }
+
     public void show_file_chooser (owned FileChosenFunc file_chosen_func) {
         page = PropsWindowPage.FILE_CHOOSER;
         var res = file_chooser.run ();
diff --git a/src/text-editor.vala b/src/text-editor.vala
new file mode 100644
index 00000000..d2a7ae87
--- /dev/null
+++ b/src/text-editor.vala
@@ -0,0 +1,90 @@
+// This file is part of GNOME Boxes. License: LGPLv2+
+using Gtk;
+
+[GtkTemplate (ui = "/org/gnome/Boxes/ui/text-editor.ui")]
+private class Boxes.TextEditor: Gtk.ScrolledWindow {
+    private const string BOXES_NS = "boxes";
+    private const string BOXES_NS_URI = "https://wiki.gnome.org/Apps/Boxes/edited";;
+    private const string MANUALLY_EDITED_XML = "<edited>%u</edited>";
+    private const string FILE_SUFFIX = ".original.xml";
+
+    [GtkChild]
+    private Gtk.SourceView view;
+
+    private LibvirtMachine machine;
+
+    public void setup (LibvirtMachine machine) {
+        this.machine = machine;
+
+        var buffer = new Gtk.SourceBuffer (null);
+        buffer.language = Gtk.SourceLanguageManager.get_default ().get_language ("xml");
+        view.buffer = buffer;
+
+        try {
+            var config = machine.domain.get_config (GVir.DomainXMLFlags.NONE);
+            buffer.set_text (config.to_xml ());
+        } catch (GLib.Error error) {
+            warning ("Failed to load machine configuration: %s", error.message);
+        }
+    }
+
+    public async void save () {
+        GVirConfig.Domain? config = null;
+        try {
+            config = machine.domain.get_config (GVir.DomainXMLFlags.NONE);
+        } catch (GLib.Error error) {
+            warning ("Failed to load machine configuration: %s", error.message);
+            return;
+        }
+
+        var saved = yield save_original_config (config);
+
+        var xml = view.buffer.text;
+        GVirConfig.Domain? custom_config = null;
+        try {
+            custom_config = new GVirConfig.Domain.from_xml (xml);
+        } catch (GLib.Error error) {
+            warning ("Failed to save changes!\n");
+        }
+
+        add_metadata (custom_config);
+
+        machine.domain.set_config (custom_config);
+    }
+
+    private void add_metadata (GVirConfig.Domain config) {
+        string edited_xml = MANUALLY_EDITED_XML.printf (1);
+
+        try {
+            config.set_custom_xml (edited_xml, "edited", BOXES_NS_URI); 
+        } catch (GLib.Error error) {
+            warning ("Failed to save custom XML: %s", error.message);
+        }
+    }
+
+    private async bool save_original_config (GVirConfig.Domain config) {
+        var old_config_path = get_user_pkgconfig (config.get_name () + FILE_SUFFIX);
+
+        return FileUtils.set_contents (old_config_path, config.to_xml (), -1);
+    }
+
+    public async void revert_to_original () {
+        var original_config_path = get_user_pkgconfig (machine.domain_config.get_name () + FILE_SUFFIX);
+
+        string data;
+        FileUtils.get_contents (original_config_path, out data);
+        if (data == null) {
+            warning ("Failed to load original config");
+            return;
+        }
+
+        try {
+            var config = new GVirConfig.Domain.from_xml (data);
+            machine.domain.set_config (config);
+
+            view.buffer.text = data;
+        } catch (GLib.Error error) {
+            warning ("Failed to load old configurations %s", error.message);
+        }
+    }
+}
diff --git a/src/vm-configurator.vala b/src/vm-configurator.vala
index 7679cdc5..7d026586 100644
--- a/src/vm-configurator.vala
+++ b/src/vm-configurator.vala
@@ -254,6 +254,10 @@ public static async void update_existing_domain (Domain          domain,
         if (!boxes_created_domain (domain))
             return;
 
+        if (boxes_edited_domain (domain)) {
+            return;
+        }
+
         try {
             var cpu = domain.get_cpu ();
             if (cpu != null &&
@@ -513,7 +517,7 @@ private static StoragePermissions get_default_permissions () {
         return null;
     }
 
-    private static string? get_custom_xml_node (Domain domain, string node_name) {
+    public static string? get_custom_xml_node (Domain domain, string node_name) {
         var ns_uri = BOXES_NS_URI;
         var xml = domain.get_custom_xml (ns_uri);
         if (xml == null) {
@@ -557,6 +561,10 @@ private static bool boxes_created_domain (Domain domain) {
         return (xml != null);
     }
 
+    private static bool boxes_edited_domain (Domain domain) {
+        return (get_custom_xml_node (domain, "edited") != null);
+    }
+
     private static void update_custom_xml (Domain domain,
                                            InstallerMedia? install_media,
                                            uint num_reboots = 0,


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