From f2304664d6f8c46432a1994812fad2273905caa3 Mon Sep 17 00:00:00 2001
From: Cole Robinson <crobinso@redhat.com>
Date: Thu, 23 May 2019 20:34:55 -0400
Subject: [PATCH] snapshots: Split out vmmSnapshotNew class

Simplifies code org
---
 ui/snapshots.ui          | 294 +-----------------------
 ui/snapshotsnew.ui       | 296 ++++++++++++++++++++++++
 virtManager/baseclass.py |  12 +-
 virtManager/snapshots.py | 475 +++++++++++++++++++++------------------
 4 files changed, 565 insertions(+), 512 deletions(-)
 create mode 100644 ui/snapshotsnew.ui

diff --git a/ui/snapshots.ui b/ui/snapshots.ui
index b91ff653d..09695a240 100644
--- a/ui/snapshots.ui
+++ b/ui/snapshots.ui
@@ -1,297 +1,14 @@
 <?xml version="1.0" encoding="UTF-8"?>
-<!-- Generated with glade 3.20.2 -->
+<!-- Generated with glade 3.22.1 -->
 <interface>
   <requires lib="gtk+" version="3.22"/>
-  <object class="GtkImage" id="image3">
-    <property name="visible">True</property>
-    <property name="can_focus">False</property>
-    <property name="stock">gtk-new</property>
-  </object>
-  <object class="GtkWindow" id="snapshot-new">
-    <property name="can_focus">False</property>
-    <property name="title" translatable="yes">Create snapshot</property>
-    <property name="resizable">False</property>
-    <property name="type_hint">dialog</property>
-    <signal name="delete-event" handler="on_snapshot_new_delete_event" swapped="no"/>
-    <child>
-      <object class="GtkBox" id="box1">
-        <property name="visible">True</property>
-        <property name="can_focus">False</property>
-        <property name="orientation">vertical</property>
-        <property name="spacing">6</property>
-        <child>
-          <object class="GtkViewport" id="header">
-            <property name="visible">True</property>
-            <property name="can_focus">False</property>
-            <property name="resize_mode">queue</property>
-            <child>
-              <object class="GtkBox" id="hbox77">
-                <property name="visible">True</property>
-                <property name="can_focus">False</property>
-                <property name="border_width">6</property>
-                <property name="spacing">10</property>
-                <child>
-                  <object class="GtkImage" id="image4">
-                    <property name="visible">True</property>
-                    <property name="can_focus">False</property>
-                    <property name="stock">gtk-new</property>
-                    <property name="icon_size">6</property>
-                  </object>
-                  <packing>
-                    <property name="expand">False</property>
-                    <property name="fill">False</property>
-                    <property name="position">0</property>
-                  </packing>
-                </child>
-                <child>
-                  <object class="GtkBox" id="hbox2">
-                    <property name="visible">True</property>
-                    <property name="can_focus">False</property>
-                    <child>
-                      <object class="GtkLabel" id="label2">
-                        <property name="visible">True</property>
-                        <property name="can_focus">False</property>
-                        <property name="halign">start</property>
-                        <property name="label" translatable="yes">&lt;span size='large' color='white'&gt;Create snapshot&lt;/span&gt;</property>
-                        <property name="use_markup">True</property>
-                      </object>
-                      <packing>
-                        <property name="expand">True</property>
-                        <property name="fill">True</property>
-                        <property name="position">0</property>
-                      </packing>
-                    </child>
-                  </object>
-                  <packing>
-                    <property name="expand">True</property>
-                    <property name="fill">True</property>
-                    <property name="position">1</property>
-                  </packing>
-                </child>
-              </object>
-            </child>
-          </object>
-          <packing>
-            <property name="expand">False</property>
-            <property name="fill">True</property>
-            <property name="position">0</property>
-          </packing>
-        </child>
-        <child>
-          <object class="GtkBox" id="box3">
-            <property name="visible">True</property>
-            <property name="can_focus">False</property>
-            <property name="border_width">12</property>
-            <property name="orientation">vertical</property>
-            <property name="spacing">18</property>
-            <child>
-              <object class="GtkGrid" id="box4">
-                <property name="visible">True</property>
-                <property name="can_focus">False</property>
-                <property name="row_spacing">6</property>
-                <property name="column_spacing">6</property>
-                <child>
-                  <object class="GtkLabel" id="label1">
-                    <property name="visible">True</property>
-                    <property name="can_focus">False</property>
-                    <property name="halign">start</property>
-                    <property name="label" translatable="yes">_Name:</property>
-                    <property name="use_underline">True</property>
-                    <property name="mnemonic_widget">snapshot-new-name</property>
-                  </object>
-                  <packing>
-                    <property name="left_attach">0</property>
-                    <property name="top_attach">0</property>
-                  </packing>
-                </child>
-                <child>
-                  <object class="GtkEntry" id="snapshot-new-name">
-                    <property name="visible">True</property>
-                    <property name="can_focus">True</property>
-                    <property name="invisible_char">●</property>
-                    <signal name="activate" handler="on_snapshot_new_name_activate" swapped="no"/>
-                    <signal name="changed" handler="on_snapshot_new_name_changed" swapped="no"/>
-                  </object>
-                  <packing>
-                    <property name="left_attach">1</property>
-                    <property name="top_attach">0</property>
-                  </packing>
-                </child>
-                <child>
-                  <object class="GtkLabel" id="label3">
-                    <property name="visible">True</property>
-                    <property name="can_focus">False</property>
-                    <property name="halign">start</property>
-                    <property name="valign">start</property>
-                    <property name="label" translatable="yes">_Description:</property>
-                    <property name="use_underline">True</property>
-                    <property name="mnemonic_widget">snapshot-new-description</property>
-                  </object>
-                  <packing>
-                    <property name="left_attach">0</property>
-                    <property name="top_attach">2</property>
-                  </packing>
-                </child>
-                <child>
-                  <object class="GtkScrolledWindow" id="scrolledwindow1">
-                    <property name="width_request">300</property>
-                    <property name="height_request">125</property>
-                    <property name="visible">True</property>
-                    <property name="can_focus">True</property>
-                    <property name="shadow_type">in</property>
-                    <child>
-                      <object class="GtkTextView" id="snapshot-new-description">
-                        <property name="visible">True</property>
-                        <property name="can_focus">True</property>
-                      </object>
-                    </child>
-                  </object>
-                  <packing>
-                    <property name="left_attach">1</property>
-                    <property name="top_attach">2</property>
-                  </packing>
-                </child>
-                <child>
-                  <object class="GtkLabel" id="label4">
-                    <property name="visible">True</property>
-                    <property name="can_focus">False</property>
-                    <property name="halign">start</property>
-                    <property name="label" translatable="yes">Status:</property>
-                  </object>
-                  <packing>
-                    <property name="left_attach">0</property>
-                    <property name="top_attach">1</property>
-                  </packing>
-                </child>
-                <child>
-                  <object class="GtkBox" id="hbox1">
-                    <property name="visible">True</property>
-                    <property name="can_focus">False</property>
-                    <property name="spacing">3</property>
-                    <child>
-                      <object class="GtkImage" id="snapshot-new-status-icon">
-                        <property name="visible">True</property>
-                        <property name="can_focus">False</property>
-                        <property name="stock">gtk-cancel</property>
-                      </object>
-                      <packing>
-                        <property name="expand">False</property>
-                        <property name="fill">True</property>
-                        <property name="position">0</property>
-                      </packing>
-                    </child>
-                    <child>
-                      <object class="GtkLabel" id="snapshot-new-status-text">
-                        <property name="visible">True</property>
-                        <property name="can_focus">False</property>
-                        <property name="halign">start</property>
-                        <property name="label">Shut down</property>
-                      </object>
-                      <packing>
-                        <property name="expand">False</property>
-                        <property name="fill">True</property>
-                        <property name="position">1</property>
-                      </packing>
-                    </child>
-                  </object>
-                  <packing>
-                    <property name="left_attach">1</property>
-                    <property name="top_attach">1</property>
-                  </packing>
-                </child>
-                <child>
-                  <object class="GtkLabel" id="label6">
-                    <property name="visible">True</property>
-                    <property name="can_focus">False</property>
-                    <property name="halign">start</property>
-                    <property name="valign">start</property>
-                    <property name="label" translatable="yes">Screenshot:</property>
-                  </object>
-                  <packing>
-                    <property name="left_attach">0</property>
-                    <property name="top_attach">3</property>
-                  </packing>
-                </child>
-                <child>
-                  <object class="GtkImage" id="snapshot-new-screenshot">
-                    <property name="visible">True</property>
-                    <property name="can_focus">False</property>
-                    <property name="halign">start</property>
-                    <property name="stock">gtk-missing-image</property>
-                  </object>
-                  <packing>
-                    <property name="left_attach">1</property>
-                    <property name="top_attach">3</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="GtkButtonBox" id="buttonbox2">
-                <property name="visible">True</property>
-                <property name="can_focus">False</property>
-                <property name="spacing">6</property>
-                <property name="layout_style">end</property>
-                <child>
-                  <object class="GtkButton" id="snapshot-new-cancel">
-                    <property name="label">gtk-cancel</property>
-                    <property name="visible">True</property>
-                    <property name="can_focus">True</property>
-                    <property name="receives_default">True</property>
-                    <property name="use_stock">True</property>
-                    <signal name="clicked" handler="on_snapshot_new_cancel_clicked" swapped="no"/>
-                  </object>
-                  <packing>
-                    <property name="expand">False</property>
-                    <property name="fill">True</property>
-                    <property name="position">0</property>
-                  </packing>
-                </child>
-                <child>
-                  <object class="GtkButton" id="snapshot-new-ok">
-                    <property name="label" translatable="yes">_Finish</property>
-                    <property name="visible">True</property>
-                    <property name="can_focus">True</property>
-                    <property name="receives_default">True</property>
-                    <property name="image">image3</property>
-                    <property name="use_underline">True</property>
-                    <signal name="clicked" handler="on_snapshot_new_ok_clicked" swapped="no"/>
-                  </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">1</property>
-              </packing>
-            </child>
-          </object>
-          <packing>
-            <property name="expand">False</property>
-            <property name="fill">True</property>
-            <property name="position">1</property>
-          </packing>
-        </child>
-      </object>
-    </child>
-    <child type="titlebar">
-      <placeholder/>
-    </child>
-  </object>
   <object class="GtkWindow" id="snapshot-top-window">
     <property name="width_request">600</property>
     <property name="height_request">400</property>
     <property name="can_focus">False</property>
+    <child type="titlebar">
+      <placeholder/>
+    </child>
     <child>
       <object class="GtkBox" id="snapshot-top-box">
         <property name="visible">True</property>
@@ -819,8 +536,5 @@
         </child>
       </object>
     </child>
-    <child type="titlebar">
-      <placeholder/>
-    </child>
   </object>
 </interface>
diff --git a/ui/snapshotsnew.ui b/ui/snapshotsnew.ui
new file mode 100644
index 000000000..0a6aa6935
--- /dev/null
+++ b/ui/snapshotsnew.ui
@@ -0,0 +1,296 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Generated with glade 3.22.1 -->
+<interface>
+  <requires lib="gtk+" version="3.20"/>
+  <object class="GtkImage" id="image3">
+    <property name="visible">True</property>
+    <property name="can_focus">False</property>
+    <property name="stock">gtk-new</property>
+  </object>
+  <object class="GtkWindow" id="snapshot-new">
+    <property name="can_focus">False</property>
+    <property name="title" translatable="yes">Create snapshot</property>
+    <property name="type_hint">dialog</property>
+    <signal name="delete-event" handler="on_snapshot_new_delete_event" swapped="no"/>
+    <child type="titlebar">
+      <placeholder/>
+    </child>
+    <child>
+      <object class="GtkBox" id="box1">
+        <property name="visible">True</property>
+        <property name="can_focus">False</property>
+        <property name="orientation">vertical</property>
+        <property name="spacing">6</property>
+        <child>
+          <object class="GtkViewport" id="header">
+            <property name="visible">True</property>
+            <property name="can_focus">False</property>
+            <property name="resize_mode">queue</property>
+            <child>
+              <object class="GtkBox" id="hbox77">
+                <property name="visible">True</property>
+                <property name="can_focus">False</property>
+                <property name="border_width">6</property>
+                <property name="spacing">10</property>
+                <child>
+                  <object class="GtkImage" id="image4">
+                    <property name="visible">True</property>
+                    <property name="can_focus">False</property>
+                    <property name="stock">gtk-new</property>
+                    <property name="icon_size">6</property>
+                  </object>
+                  <packing>
+                    <property name="expand">False</property>
+                    <property name="fill">False</property>
+                    <property name="position">0</property>
+                  </packing>
+                </child>
+                <child>
+                  <object class="GtkBox" id="hbox2">
+                    <property name="visible">True</property>
+                    <property name="can_focus">False</property>
+                    <child>
+                      <object class="GtkLabel" id="label2">
+                        <property name="visible">True</property>
+                        <property name="can_focus">False</property>
+                        <property name="halign">start</property>
+                        <property name="label" translatable="yes">&lt;span size='large' color='white'&gt;Create snapshot&lt;/span&gt;</property>
+                        <property name="use_markup">True</property>
+                      </object>
+                      <packing>
+                        <property name="expand">True</property>
+                        <property name="fill">True</property>
+                        <property name="position">0</property>
+                      </packing>
+                    </child>
+                  </object>
+                  <packing>
+                    <property name="expand">True</property>
+                    <property name="fill">True</property>
+                    <property name="position">1</property>
+                  </packing>
+                </child>
+              </object>
+            </child>
+          </object>
+          <packing>
+            <property name="expand">False</property>
+            <property name="fill">True</property>
+            <property name="position">0</property>
+          </packing>
+        </child>
+        <child>
+          <object class="GtkBox" id="box3">
+            <property name="visible">True</property>
+            <property name="can_focus">False</property>
+            <property name="border_width">12</property>
+            <property name="orientation">vertical</property>
+            <property name="spacing">18</property>
+            <child>
+              <object class="GtkAlignment" id="snapshot-new-box-align">
+                <property name="visible">True</property>
+                <property name="can_focus">False</property>
+                <child>
+                  <object class="GtkGrid" id="snapshot-new-box">
+                    <property name="visible">True</property>
+                    <property name="can_focus">False</property>
+                    <property name="row_spacing">6</property>
+                    <property name="column_spacing">6</property>
+                    <child>
+                      <object class="GtkLabel" id="label1">
+                        <property name="visible">True</property>
+                        <property name="can_focus">False</property>
+                        <property name="halign">start</property>
+                        <property name="label" translatable="yes">_Name:</property>
+                        <property name="use_underline">True</property>
+                        <property name="mnemonic_widget">snapshot-new-name</property>
+                      </object>
+                      <packing>
+                        <property name="left_attach">0</property>
+                        <property name="top_attach">0</property>
+                      </packing>
+                    </child>
+                    <child>
+                      <object class="GtkEntry" id="snapshot-new-name">
+                        <property name="visible">True</property>
+                        <property name="can_focus">True</property>
+                        <property name="invisible_char">●</property>
+                        <signal name="activate" handler="on_snapshot_new_name_activate" swapped="no"/>
+                        <signal name="changed" handler="on_snapshot_new_name_changed" swapped="no"/>
+                      </object>
+                      <packing>
+                        <property name="left_attach">1</property>
+                        <property name="top_attach">0</property>
+                      </packing>
+                    </child>
+                    <child>
+                      <object class="GtkLabel" id="label3">
+                        <property name="visible">True</property>
+                        <property name="can_focus">False</property>
+                        <property name="halign">start</property>
+                        <property name="valign">start</property>
+                        <property name="label" translatable="yes">_Description:</property>
+                        <property name="use_underline">True</property>
+                        <property name="mnemonic_widget">snapshot-new-description</property>
+                      </object>
+                      <packing>
+                        <property name="left_attach">0</property>
+                        <property name="top_attach">2</property>
+                      </packing>
+                    </child>
+                    <child>
+                      <object class="GtkScrolledWindow" id="scrolledwindow1">
+                        <property name="width_request">300</property>
+                        <property name="height_request">125</property>
+                        <property name="visible">True</property>
+                        <property name="can_focus">True</property>
+                        <property name="shadow_type">in</property>
+                        <child>
+                          <object class="GtkTextView" id="snapshot-new-description">
+                            <property name="visible">True</property>
+                            <property name="can_focus">True</property>
+                          </object>
+                        </child>
+                      </object>
+                      <packing>
+                        <property name="left_attach">1</property>
+                        <property name="top_attach">2</property>
+                      </packing>
+                    </child>
+                    <child>
+                      <object class="GtkLabel" id="label4">
+                        <property name="visible">True</property>
+                        <property name="can_focus">False</property>
+                        <property name="halign">start</property>
+                        <property name="label" translatable="yes">Status:</property>
+                      </object>
+                      <packing>
+                        <property name="left_attach">0</property>
+                        <property name="top_attach">1</property>
+                      </packing>
+                    </child>
+                    <child>
+                      <object class="GtkBox" id="hbox1">
+                        <property name="visible">True</property>
+                        <property name="can_focus">False</property>
+                        <property name="spacing">3</property>
+                        <child>
+                          <object class="GtkImage" id="snapshot-new-status-icon">
+                            <property name="visible">True</property>
+                            <property name="can_focus">False</property>
+                            <property name="stock">gtk-cancel</property>
+                          </object>
+                          <packing>
+                            <property name="expand">False</property>
+                            <property name="fill">True</property>
+                            <property name="position">0</property>
+                          </packing>
+                        </child>
+                        <child>
+                          <object class="GtkLabel" id="snapshot-new-status-text">
+                            <property name="visible">True</property>
+                            <property name="can_focus">False</property>
+                            <property name="halign">start</property>
+                            <property name="label">Shut down</property>
+                          </object>
+                          <packing>
+                            <property name="expand">False</property>
+                            <property name="fill">True</property>
+                            <property name="position">1</property>
+                          </packing>
+                        </child>
+                      </object>
+                      <packing>
+                        <property name="left_attach">1</property>
+                        <property name="top_attach">1</property>
+                      </packing>
+                    </child>
+                    <child>
+                      <object class="GtkLabel" id="label6">
+                        <property name="visible">True</property>
+                        <property name="can_focus">False</property>
+                        <property name="halign">start</property>
+                        <property name="valign">start</property>
+                        <property name="label" translatable="yes">Screenshot:</property>
+                      </object>
+                      <packing>
+                        <property name="left_attach">0</property>
+                        <property name="top_attach">3</property>
+                      </packing>
+                    </child>
+                    <child>
+                      <object class="GtkImage" id="snapshot-new-screenshot">
+                        <property name="visible">True</property>
+                        <property name="can_focus">False</property>
+                        <property name="halign">start</property>
+                        <property name="stock">gtk-missing-image</property>
+                      </object>
+                      <packing>
+                        <property name="left_attach">1</property>
+                        <property name="top_attach">3</property>
+                      </packing>
+                    </child>
+                  </object>
+                </child>
+              </object>
+              <packing>
+                <property name="expand">False</property>
+                <property name="fill">True</property>
+                <property name="position">0</property>
+              </packing>
+            </child>
+            <child>
+              <object class="GtkButtonBox" id="buttonbox2">
+                <property name="visible">True</property>
+                <property name="can_focus">False</property>
+                <property name="spacing">6</property>
+                <property name="layout_style">end</property>
+                <child>
+                  <object class="GtkButton" id="snapshot-new-cancel">
+                    <property name="label">gtk-cancel</property>
+                    <property name="visible">True</property>
+                    <property name="can_focus">True</property>
+                    <property name="receives_default">True</property>
+                    <property name="use_stock">True</property>
+                    <signal name="clicked" handler="on_snapshot_new_cancel_clicked" swapped="no"/>
+                  </object>
+                  <packing>
+                    <property name="expand">False</property>
+                    <property name="fill">True</property>
+                    <property name="position">0</property>
+                  </packing>
+                </child>
+                <child>
+                  <object class="GtkButton" id="snapshot-new-ok">
+                    <property name="label" translatable="yes">_Finish</property>
+                    <property name="visible">True</property>
+                    <property name="can_focus">True</property>
+                    <property name="receives_default">True</property>
+                    <property name="image">image3</property>
+                    <property name="use_underline">True</property>
+                    <signal name="clicked" handler="on_snapshot_new_ok_clicked" swapped="no"/>
+                  </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">1</property>
+              </packing>
+            </child>
+          </object>
+          <packing>
+            <property name="expand">False</property>
+            <property name="fill">True</property>
+            <property name="position">1</property>
+          </packing>
+        </child>
+      </object>
+    </child>
+  </object>
+</interface>
diff --git a/virtManager/baseclass.py b/virtManager/baseclass.py
index ccd307081..78902e839 100644
--- a/virtManager/baseclass.py
+++ b/virtManager/baseclass.py
@@ -258,13 +258,6 @@ class vmmGObject(GObject.GObject):
 
 
 class vmmGObjectUI(vmmGObject):
-    @staticmethod
-    def bind_escape_key_close_helper(topwin, close_cb):
-        def close_on_escape(src_ignore, event):
-            if Gdk.keyval_name(event.keyval) == "Escape":
-                close_cb()
-        topwin.connect("key-press-event", close_on_escape)
-
     def __init__(self, filename, windowname, builder=None, topwin=None):
         vmmGObject.__init__(self)
         self._external_topwin = bool(topwin)
@@ -325,7 +318,10 @@ class vmmGObjectUI(vmmGObject):
         return bool(self.topwin and self.topwin.get_visible())
 
     def bind_escape_key_close(self):
-        self.bind_escape_key_close_helper(self.topwin, self.close)
+        def close_on_escape(src_ignore, event):
+            if Gdk.keyval_name(event.keyval) == "Escape":
+                self.close()
+        self.topwin.connect("key-press-event", close_on_escape)
 
     def _set_cursor(self, cursor_type):
         gdk_window = self.topwin.get_window()
diff --git a/virtManager/snapshots.py b/virtManager/snapshots.py
index 49ec8b02b..2a267ff5c 100644
--- a/virtManager/snapshots.py
+++ b/virtManager/snapshots.py
@@ -29,6 +29,30 @@ mimemap = {
 }
 
 
+def _make_screenshot_pixbuf(mime, sdata):
+    loader = GdkPixbuf.PixbufLoader.new_with_mime_type(mime)
+    loader.write(sdata)
+    pixbuf = loader.get_pixbuf()
+    loader.close()
+
+    maxsize = 450
+    def _scale(big, small, maxsize):
+        if big <= maxsize:
+            return big, small
+        factor = float(maxsize) / float(big)
+        return maxsize, int(factor * float(small))
+
+    width = pixbuf.get_width()
+    height = pixbuf.get_height()
+    if width > height:
+        width, height = _scale(width, height, maxsize)
+    else:
+        height, width = _scale(height, width, maxsize)
+
+    return pixbuf.scale_simple(width, height,
+                               GdkPixbuf.InterpType.BILINEAR)
+
+
 def _mime_to_ext(val, reverse=False):
     for m, e in mimemap.items():
         if val == m and not reverse:
@@ -40,6 +64,226 @@ def _mime_to_ext(val, reverse=False):
                   reverse and "mime" or "extension")
 
 
+class vmmSnapshotNew(vmmGObjectUI):
+    __gsignals__ = {
+        "snapshot-created": (vmmGObjectUI.RUN_FIRST, None, [str]),
+    }
+
+    def __init__(self, vm):
+        vmmGObjectUI.__init__(self, "snapshotsnew.ui", "snapshot-new")
+        self.vm = vm
+
+        self._init_ui()
+
+        self.builder.connect_signals({
+            "on_snapshot_new_delete_event": self.close,
+            "on_snapshot_new_cancel_clicked": self.close,
+            "on_snapshot_new_name_changed": self._name_changed_cb,
+            "on_snapshot_new_name_activate": self._ok_clicked_cb,
+            "on_snapshot_new_ok_clicked": self._ok_clicked_cb,
+        })
+        self.bind_escape_key_close()
+
+
+    #######################
+    # Standard UI methods #
+    #######################
+
+    def show(self, parent):
+        logging.debug("Showing new snapshot wizard")
+        self._reset_state()
+        self.topwin.set_transient_for(parent)
+        self.topwin.present()
+
+    def close(self, ignore1=None, ignore2=None):
+        logging.debug("Closing new snapshot wizard")
+        self.topwin.hide()
+        return 1
+
+    def _cleanup(self):
+        self.vm = None
+
+
+    ###########
+    # UI init #
+    ###########
+
+    def _init_ui(self):
+        blue = Gdk.color_parse("#0072A8")
+        self.widget("header").modify_bg(Gtk.StateType.NORMAL, blue)
+
+        buf = Gtk.TextBuffer()
+        self.widget("snapshot-new-description").set_buffer(buf)
+
+    def _reset_state(self):
+        collidelist = [s.get_xmlobj().name for s in self.vm.list_snapshots()]
+        default_name = DomainSnapshot.find_free_name(
+            self.vm.get_backend(), collidelist)
+
+        self.widget("snapshot-new-name").set_text(default_name)
+        self.widget("snapshot-new-name").emit("changed")
+        self.widget("snapshot-new-description").get_buffer().set_text("")
+        self.widget("snapshot-new-ok").grab_focus()
+        self.widget("snapshot-new-status-text").set_text(self.vm.run_status())
+        self.widget("snapshot-new-status-icon").set_from_icon_name(
+            self.vm.run_status_icon_name(), Gtk.IconSize.BUTTON)
+
+        sn = self._get_screenshot()
+        uiutil.set_grid_row_visible(
+            self.widget("snapshot-new-screenshot"), bool(sn))
+        if sn:
+            self.widget("snapshot-new-screenshot").set_from_pixbuf(sn)
+
+        self.widget("snapshot-new-name").grab_focus()
+
+
+    ###################
+    # Create handling #
+    ###################
+
+    def _take_screenshot(self):
+        stream = None
+        try:
+            stream = self.vm.conn.get_backend().newStream(0)
+            screen = 0
+            flags = 0
+            mime = self.vm.get_backend().screenshot(stream, screen, flags)
+
+            ret = io.BytesIO()
+            def _write_cb(_stream, data, userdata):
+                ignore = stream
+                ignore = userdata
+                ret.write(data)
+
+            stream.recvAll(_write_cb, None)
+            return mime, ret.getvalue()
+        finally:
+            try:
+                if stream:
+                    stream.finish()
+            except Exception:
+                pass
+
+    def _get_screenshot(self):
+        if not self.vm.is_active():
+            logging.debug("Skipping screenshot since VM is not active")
+            return
+        if not self.vm.xmlobj.devices.graphics:
+            logging.debug("Skipping screenshot since VM has no graphics")
+            return
+
+        try:
+            # Perform two screenshots, because qemu + qxl has a bug where
+            # screenshot generally only shows the data from the previous
+            # screenshot request:
+            # https://bugs.launchpad.net/qemu/+bug/1314293
+            self._take_screenshot()
+            mime, sdata = self._take_screenshot()
+        except Exception:
+            logging.exception("Error taking screenshot")
+            return
+
+        ext = _mime_to_ext(mime)
+        if not ext:
+            return
+
+        newpix = _make_screenshot_pixbuf(mime, sdata)
+        setattr(newpix, "vmm_mimetype", mime)
+        setattr(newpix, "vmm_sndata", sdata)
+        return newpix
+
+    def _new_finish_cb(self, error, details, newname):
+        self.reset_finish_cursor()
+
+        if error is not None:
+            error = _("Error creating snapshot: %s") % error
+            self.err.show_err(error, details=details)
+            return
+        self.emit("snapshot-created", newname)
+
+    def _validate_new_snapshot(self):
+        name = self.widget("snapshot-new-name").get_text()
+        desc = self.widget("snapshot-new-description"
+                           ).get_buffer().get_property("text")
+
+        try:
+            newsnap = DomainSnapshot(self.vm.conn.get_backend())
+            newsnap.name = name
+            newsnap.description = desc or None
+            newsnap.validate()
+            newsnap.get_xml()
+            return newsnap
+        except Exception as e:
+            return self.err.val_err(_("Error validating snapshot: %s") % e)
+
+    def _get_screenshot_data_for_save(self):
+        snwidget = self.widget("snapshot-new-screenshot")
+        if not snwidget.is_visible():
+            return None, None
+
+        sn = snwidget.get_pixbuf()
+        if not sn:
+            return None, None
+
+        mime = getattr(sn, "vmm_mimetype", None)
+        sndata = getattr(sn, "vmm_sndata", None)
+        return mime, sndata
+
+    def _do_create_snapshot(self, asyncjob, xml, name, mime, sndata):
+        ignore = asyncjob
+
+        self.vm.create_snapshot(xml)
+
+        try:
+            cachedir = self.vm.get_cache_dir()
+            basesn = os.path.join(cachedir, "snap-screenshot-%s" % name)
+
+            # Remove any pre-existing screenshots so we don't show stale data
+            for ext in list(mimemap.values()):
+                p = basesn + "." + ext
+                if os.path.exists(basesn + "." + ext):
+                    os.unlink(p)
+
+            if not mime or not sndata:
+                return
+
+            filename = basesn + "." + _mime_to_ext(mime)
+            logging.debug("Writing screenshot to %s", filename)
+            open(filename, "wb").write(sndata)
+        except Exception:
+            logging.exception("Error saving screenshot")
+
+    def _create_new_snapshot(self):
+        snap = self._validate_new_snapshot()
+        if not snap:
+            return
+
+        xml = snap.get_xml()
+        name = snap.name
+        mime, sndata = self._get_screenshot_data_for_save()
+        self.close()
+
+        self.set_finish_cursor()
+        progWin = vmmAsyncJob(
+                    self._do_create_snapshot, [xml, name, mime, sndata],
+                    self._new_finish_cb, [name],
+                    _("Creating snapshot"),
+                    _("Creating virtual machine snapshot"),
+                    self.topwin)
+        progWin.run()
+
+
+    ################
+    # UI listeners #
+    ################
+
+    def _name_changed_cb(self, src):
+        self.widget("snapshot-new-ok").set_sensitive(bool(src.get_text()))
+
+    def _ok_clicked_cb(self, src):
+        return self._create_new_snapshot()
+
+
 class vmmSnapshotPage(vmmGObjectUI):
     def __init__(self, vm, builder, topwin):
         vmmGObjectUI.__init__(self, "snapshots.ui",
@@ -49,15 +293,11 @@ class vmmSnapshotPage(vmmGObjectUI):
 
         self._initial_populate = False
         self._unapplied_changes = False
+        self._snapshot_new = None
 
         self._snapmenu = None
         self._init_ui()
 
-        self._snapshot_new = self.widget("snapshot-new")
-        self._snapshot_new.set_transient_for(self.topwin)
-        self.bind_escape_key_close_helper(self._snapshot_new,
-                                          self._snapshot_new_close)
-
         self.builder.connect_signals({
             "on_snapshot_add_clicked": self._on_add_clicked,
             "on_snapshot_delete_clicked": self._on_delete_clicked,
@@ -67,13 +307,6 @@ class vmmSnapshotPage(vmmGObjectUI):
             "on_snapshot_list_button_press_event": self._popup_snapshot_menu,
             "on_snapshot_refresh_clicked": self._on_refresh_clicked,
             "on_snapshot_list_row_activated": self._on_start_clicked,
-
-            # 'Create' dialog
-            "on_snapshot_new_delete_event": self._snapshot_new_close,
-            "on_snapshot_new_ok_clicked": self._on_new_ok_clicked,
-            "on_snapshot_new_cancel_clicked": self._snapshot_new_close,
-            "on_snapshot_new_name_changed": self._snapshot_new_name_changed,
-            "on_snapshot_new_name_activate": self._on_new_ok_clicked,
         })
 
         self.top_box = self.widget("snapshot-top-box")
@@ -83,31 +316,27 @@ class vmmSnapshotPage(vmmGObjectUI):
         selection.set_mode(Gtk.SelectionMode.MULTIPLE)
         selection.set_select_function(self._confirm_changes, None)
 
+
     ##############
     # Init stuff #
     ##############
 
     def _cleanup(self):
         self.vm = None
-
-        self._snapshot_new.destroy()
-        self._snapshot_new = None
         self._snapmenu = None
 
+        if self._snapshot_new:
+            self._snapshot_new.cleanup()
+            self._snapshot_new = None
+
     def _init_ui(self):
         # pylint: disable=redefined-variable-type
-        blue = Gdk.color_parse("#0072A8")
-        self.widget("header").modify_bg(Gtk.StateType.NORMAL, blue)
-
         self.widget("snapshot-notebook").set_show_tabs(False)
 
         buf = Gtk.TextBuffer()
         buf.connect("changed", self._description_changed)
         self.widget("snapshot-description").set_buffer(buf)
 
-        buf = Gtk.TextBuffer()
-        self.widget("snapshot-new-description").set_buffer(buf)
-
         # [name, row label, tooltip, icon name, sortname, current]
         model = Gtk.ListStore(str, str, str, str, str, bool)
         model.set_sort_column_id(4, Gtk.SortType.ASCENDING)
@@ -254,29 +483,6 @@ class vmmSnapshotPage(vmmGObjectUI):
 
         self._initial_populate = True
 
-    def _make_screenshot_pixbuf(self, mime, sdata):
-        loader = GdkPixbuf.PixbufLoader.new_with_mime_type(mime)
-        loader.write(sdata)
-        pixbuf = loader.get_pixbuf()
-        loader.close()
-
-        maxsize = 450
-        def _scale(big, small, maxsize):
-            if big <= maxsize:
-                return big, small
-            factor = float(maxsize) / float(big)
-            return maxsize, int(factor * float(small))
-
-        width = pixbuf.get_width()
-        height = pixbuf.get_height()
-        if width > height:
-            width, height = _scale(width, height, maxsize)
-        else:
-            height, width = _scale(height, width, maxsize)
-
-        return pixbuf.scale_simple(width, height,
-                                   GdkPixbuf.InterpType.BILINEAR)
-
     def _read_screenshot_file(self, name):
         if not name:
             return
@@ -291,7 +497,7 @@ class vmmSnapshotPage(vmmGObjectUI):
         mime = _mime_to_ext(os.path.splitext(filename)[1][1:], reverse=True)
         if not mime:
             return
-        return self._make_screenshot_pixbuf(mime, open(filename, "rb").read())
+        return _make_screenshot_pixbuf(mime, open(filename, "rb").read())
 
     def _set_snapshot_state(self, snap=None):
         self.widget("snapshot-notebook").set_current_page(0)
@@ -363,164 +569,6 @@ class vmmSnapshotPage(vmmGObjectUI):
 
         return True
 
-    ##################
-    # 'New' handling #
-    ##################
-
-    def _take_screenshot(self):
-        stream = None
-        try:
-            stream = self.vm.conn.get_backend().newStream(0)
-            screen = 0
-            flags = 0
-            mime = self.vm.get_backend().screenshot(stream, screen, flags)
-
-            ret = io.BytesIO()
-            def _write_cb(_stream, data, userdata):
-                ignore = stream
-                ignore = userdata
-                ret.write(data)
-
-            stream.recvAll(_write_cb, None)
-            return mime, ret.getvalue()
-        finally:
-            try:
-                if stream:
-                    stream.finish()
-            except Exception:
-                pass
-
-    def _get_screenshot(self):
-        if not self.vm.is_active():
-            logging.debug("Skipping screenshot since VM is not active")
-            return
-        if not self.vm.xmlobj.devices.graphics:
-            logging.debug("Skipping screenshot since VM has no graphics")
-            return
-
-        try:
-            # Perform two screenshots, because qemu + qxl has a bug where
-            # screenshot generally only shows the data from the previous
-            # screenshot request:
-            # https://bugs.launchpad.net/qemu/+bug/1314293
-            self._take_screenshot()
-            mime, sdata = self._take_screenshot()
-        except Exception:
-            logging.exception("Error taking screenshot")
-            return
-
-        ext = _mime_to_ext(mime)
-        if not ext:
-            return
-
-        newpix = self._make_screenshot_pixbuf(mime, sdata)
-        setattr(newpix, "vmm_mimetype", mime)
-        setattr(newpix, "vmm_sndata", sdata)
-        return newpix
-
-    def _reset_new_state(self):
-        collidelist = [s.get_xmlobj().name for s in self.vm.list_snapshots()]
-        default_name = DomainSnapshot.find_free_name(
-            self.vm.get_backend(), collidelist)
-
-        self.widget("snapshot-new-name").set_text(default_name)
-        self.widget("snapshot-new-name").emit("changed")
-        self.widget("snapshot-new-description").get_buffer().set_text("")
-        self.widget("snapshot-new-ok").grab_focus()
-        self.widget("snapshot-new-status-text").set_text(self.vm.run_status())
-        self.widget("snapshot-new-status-icon").set_from_icon_name(
-            self.vm.run_status_icon_name(), Gtk.IconSize.BUTTON)
-
-        sn = self._get_screenshot()
-        uiutil.set_grid_row_visible(
-            self.widget("snapshot-new-screenshot"), bool(sn))
-        if sn:
-            self.widget("snapshot-new-screenshot").set_from_pixbuf(sn)
-
-
-    def _snapshot_new_name_changed(self, src):
-        self.widget("snapshot-new-ok").set_sensitive(bool(src.get_text()))
-
-    def _new_finish_cb(self, error, details, newname):
-        self.reset_finish_cursor()
-
-        if error is not None:
-            error = _("Error creating snapshot: %s") % error
-            self.err.show_err(error, details=details)
-            return
-        self._refresh_snapshots(newname)
-
-    def _validate_new_snapshot(self):
-        name = self.widget("snapshot-new-name").get_text()
-        desc = self.widget("snapshot-new-description"
-                           ).get_buffer().get_property("text")
-
-        try:
-            newsnap = DomainSnapshot(self.vm.conn.get_backend())
-            newsnap.name = name
-            newsnap.description = desc or None
-            newsnap.validate()
-            newsnap.get_xml()
-            return newsnap
-        except Exception as e:
-            return self.err.val_err(_("Error validating snapshot: %s") % e)
-
-    def _get_screenshot_data_for_save(self):
-        snwidget = self.widget("snapshot-new-screenshot")
-        if not snwidget.is_visible():
-            return None, None
-
-        sn = snwidget.get_pixbuf()
-        if not sn:
-            return None, None
-
-        mime = getattr(sn, "vmm_mimetype", None)
-        sndata = getattr(sn, "vmm_sndata", None)
-        return mime, sndata
-
-    def _do_create_snapshot(self, asyncjob, xml, name, mime, sndata):
-        ignore = asyncjob
-
-        self.vm.create_snapshot(xml)
-
-        try:
-            cachedir = self.vm.get_cache_dir()
-            basesn = os.path.join(cachedir, "snap-screenshot-%s" % name)
-
-            # Remove any pre-existing screenshots so we don't show stale data
-            for ext in list(mimemap.values()):
-                p = basesn + "." + ext
-                if os.path.exists(basesn + "." + ext):
-                    os.unlink(p)
-
-            if not mime or not sndata:
-                return
-
-            filename = basesn + "." + _mime_to_ext(mime)
-            logging.debug("Writing screenshot to %s", filename)
-            open(filename, "wb").write(sndata)
-        except Exception:
-            logging.exception("Error saving screenshot")
-
-    def _create_new_snapshot(self):
-        snap = self._validate_new_snapshot()
-        if not snap:
-            return
-
-        xml = snap.get_xml()
-        name = snap.name
-        mime, sndata = self._get_screenshot_data_for_save()
-        self._snapshot_new_close()
-
-        self.set_finish_cursor()
-        progWin = vmmAsyncJob(
-                    self._do_create_snapshot, [xml, name, mime, sndata],
-                    self._new_finish_cb, [name],
-                    _("Creating snapshot"),
-                    _("Creating virtual machine snapshot"),
-                    self.topwin)
-        progWin.run()
-
     def _apply(self):
         snaps = self._get_selected_snapshots()
         if not snaps or len(snaps) > 1:
@@ -554,10 +602,9 @@ class vmmSnapshotPage(vmmGObjectUI):
             return
         self._snapmenu.popup_at_pointer(event)
 
-    def _snapshot_new_close(self, *args, **kwargs):
-        ignore = args
-        ignore = kwargs
-        self._snapshot_new.hide()
+    def close(self, ignore1=None, ignore2=None):
+        if self._snapshot_new:
+            self._snapshot_new.close()
         return 1
 
     def _description_changed(self, ignore):
@@ -574,15 +621,15 @@ class vmmSnapshotPage(vmmGObjectUI):
         self._apply()
         self._refresh_snapshots()
 
-    def _on_new_ok_clicked(self, ignore):
-        return self._create_new_snapshot()
+    def _snapshot_created_cb(self, src, newname):
+        self._refresh_snapshots(newname)
 
     def _on_add_clicked(self, ignore):
-        if self._snapshot_new.is_visible():
-            return
-        self._reset_new_state()
-        self._snapshot_new.show()
-        self.widget("snapshot-new-name").grab_focus()
+        if not self._snapshot_new:
+            self._snapshot_new = vmmSnapshotNew(self.vm)
+            self._snapshot_new.connect("snapshot-created",
+                    self._snapshot_created_cb)
+        self._snapshot_new.show(self.topwin)
 
     def _on_refresh_clicked(self, ignore):
         self._refresh_snapshots()