details: Confirm with user before throwing away changes on page change

We've had many users be confused by changes in the details pages not
taking immediate effect, or just forgetting to hit apply.

Now by default we prompt users if switching pages if there are unapplied
changes. This can be disabled like other prompts in Edit->Prefs or with
a 'Don't warn me again' chkbox in the dialog.
This commit is contained in:
Cole Robinson 2011-07-19 20:29:07 -04:00
parent b14a44bf62
commit 7042c43bbe
6 changed files with 145 additions and 23 deletions

View File

@ -351,6 +351,19 @@
</locale>
</schema>
<schema>
<key>/schemas/apps/::PACKAGE::/confirm/unapplied_dev</key>
<applyto>/apps/::PACKAGE::/confirm/unapplied_dev</applyto>
<owner>::PACKAGE::</owner>
<type>bool</type>
<default>1</default>
<locale name="C">
<short>Confirm about unapplied device changes</short>
<long>Whether we ask the user to apply or discard unapplied device changes</long>
</locale>
</schema>
<schema>
<key>/schemas/apps/::PACKAGE::/manager_window_height</key>
<applyto>/apps/::PACKAGE::/manager_window_height</applyto>

View File

@ -386,6 +386,8 @@ class vmmConfig(object):
return self.conf.get_bool(self.conf_dir + "/confirm/removedev")
def get_confirm_interface(self):
return self.conf.get_bool(self.conf_dir + "/confirm/interface_power")
def get_confirm_unapplied(self):
return self.conf.get_bool(self.conf_dir + "/confirm/unapplied_dev")
def set_confirm_forcepoweroff(self, val):
@ -398,6 +400,8 @@ class vmmConfig(object):
self.conf.set_bool(self.conf_dir + "/confirm/removedev", val)
def set_confirm_interface(self, val):
self.conf.set_bool(self.conf_dir + "/confirm/interface_power", val)
def set_confirm_unapplied(self, val):
self.conf.set_bool(self.conf_dir + "/confirm/unapplied_dev", val)
def on_confirm_forcepoweroff_changed(self, cb):
return self.conf.notify_add(self.conf_dir + "/confirm/forcepoweroff", cb)
@ -409,6 +413,8 @@ class vmmConfig(object):
return self.conf.notify_add(self.conf_dir + "/confirm/removedev", cb)
def on_confirm_interface_changed(self, cb):
return self.conf.notify_add(self.conf_dir + "/confirm/interface_power", cb)
def on_confirm_unapplied_changed(self, cb):
return self.conf.notify_add(self.conf_dir + "/confirm/unapplied_dev", cb)
# System tray visibility

View File

@ -307,6 +307,7 @@ class vmmDetails(vmmGObjectUI):
w, h = self.vm.get_details_window_size()
self.topwin.set_default_size(w or 800, h or 600)
self.oldhwrow = None
self.addhwmenu = None
self.keycombo_menu = None
self.init_menus()
@ -458,7 +459,7 @@ class vmmDetails(vmmGObjectUI):
self.vm.connect("config-changed", self.refresh_vm_state)
self.vm.connect("resources-sampled", self.refresh_resources)
self.widget("hw-list").get_selection().connect("changed",
self.hw_selected)
self.hw_changed)
self.widget("config-boot-list").get_selection().connect(
"changed",
self.config_bootdev_selected)
@ -477,6 +478,8 @@ class vmmDetails(vmmGObjectUI):
self.close()
try:
self.oldhwrow = None
if self.addhw:
self.addhw.cleanup()
self.addhw = None
@ -1084,26 +1087,29 @@ class vmmDetails(vmmGObjectUI):
else:
self.widget("toolbar-box").hide()
def get_boot_selection(self):
widget = self.widget("config-boot-list")
def get_selected_row(self, widget):
selection = widget.get_selection()
model, treepath = selection.get_selected()
if treepath == None:
return None
return model[treepath]
def get_boot_selection(self):
return self.get_selected_row(self.widget("config-boot-list"))
def set_hw_selection(self, page):
hwlist = self.widget("hw-list")
selection = hwlist.get_selection()
selection.select_path(str(page))
def get_hw_row(self):
return self.get_selected_row(self.widget("hw-list"))
def get_hw_selection(self, field):
vmlist = self.widget("hw-list")
selection = vmlist.get_selection()
active = selection.get_selected()
if active[1] == None:
row = self.get_hw_row()
if not row:
return None
return active[0].get_value(active[1], field)
return row[field]
def force_get_hw_pagetype(self, page=None):
if page:
@ -1116,7 +1122,56 @@ class vmmDetails(vmmGObjectUI):
return page
def hw_selected(self, ignore1=None, page=None, selected=True):
def compare_hw_rows(self, row1, row2):
if row1 == row2:
return True
if not row1 or not row2:
return False
for idx in range(len(row1)):
if row1[idx] != row2[idx]:
return False
return True
def has_unapplied_changes(self, row):
if not row:
return False
if not self.widget("config-apply").get_property("sensitive"):
return False
if util.chkbox_helper(self,
self.config.get_confirm_unapplied,
self.config.set_confirm_unapplied,
text1=(_("There are unapplied changes. Would you like to apply "
"them now?")),
chktext=_("Don't warn me again."),
alwaysrecord=True):
return False
return not self.config_apply(row=row)
def hw_changed(self, ignore):
newrow = self.get_hw_row()
oldrow = self.oldhwrow
model = self.widget("hw-list").get_model()
if self.compare_hw_rows(newrow, oldrow):
return
if self.has_unapplied_changes(oldrow):
# Unapplied changes, and syncing them failed
pageidx = 0
for idx in range(len(model)):
if self.compare_hw_rows(model[idx], oldrow):
pageidx = idx
break
self.set_hw_selection(pageidx)
else:
self.oldhwrow = newrow
self.hw_selected()
def hw_selected(self, page=None):
pagetype = self.force_get_hw_pagetype(page)
self.widget("config-remove").set_sensitive(True)
@ -1165,8 +1220,7 @@ class vmmDetails(vmmGObjectUI):
return
rem = pagetype in remove_pages
if selected:
self.disable_apply()
self.disable_apply()
self.widget("config-remove").set_property("visible", rem)
self.widget("hw-panel").set_current_page(pagetype)
@ -1184,6 +1238,11 @@ class vmmDetails(vmmGObjectUI):
is_details = True
pages = self.widget("details-pages")
if pages.get_current_page() == PAGE_DETAILS:
if self.has_unapplied_changes(self.get_hw_row()):
self.sync_details_console_view(True)
return
if is_details:
pages.set_current_page(PAGE_DETAILS)
else:
@ -1732,13 +1791,20 @@ class vmmDetails(vmmGObjectUI):
# Details/Hardware config changes (apply button) #
##################################################
def config_cancel(self, ignore):
def config_cancel(self, ignore=None):
# Remove current changes and deactive 'apply' button
self.hw_selected()
def config_apply(self, ignore):
pagetype = self.get_hw_selection(HW_LIST_COL_TYPE)
devobj = self.get_hw_selection(HW_LIST_COL_DEVICE)
def config_apply(self, ignore=None, row=None):
pagetype = None
devobj = None
if not row:
row = self.get_hw_row()
if row:
pagetype = row[HW_LIST_COL_TYPE]
devobj = row[HW_LIST_COL_DEVICE]
key = devobj
ret = False
@ -1768,10 +1834,11 @@ class vmmDetails(vmmGObjectUI):
else:
ret = False
except Exception, e:
self.err.show_err(_("Error apply changes: %s") % e)
return self.err.show_err(_("Error apply changes: %s") % e)
if ret is not False:
self.disable_apply()
return True
def get_text(self, widgetname, strip=True):
ret = self.widget(widgetname).get_text()

View File

@ -46,6 +46,7 @@ class vmmPreferences(vmmGObjectUI):
self.add_gconf_handle(self.config.on_confirm_pause_changed(self.refresh_confirm_pause))
self.add_gconf_handle(self.config.on_confirm_removedev_changed(self.refresh_confirm_removedev))
self.add_gconf_handle(self.config.on_confirm_interface_changed(self.refresh_confirm_interface))
self.add_gconf_handle(self.config.on_confirm_unapplied_changed(self.refresh_confirm_unapplied))
self.refresh_view_system_tray()
self.refresh_update_interval()
@ -63,6 +64,7 @@ class vmmPreferences(vmmGObjectUI):
self.refresh_confirm_pause()
self.refresh_confirm_removedev()
self.refresh_confirm_interface()
self.refresh_confirm_unapplied()
self.window.signal_autoconnect({
"on_prefs_system_tray_toggled" : self.change_view_system_tray,
@ -82,6 +84,7 @@ class vmmPreferences(vmmGObjectUI):
"on_prefs_confirm_pause_toggled": self.change_confirm_pause,
"on_prefs_confirm_removedev_toggled": self.change_confirm_removedev,
"on_prefs_confirm_interface_toggled": self.change_confirm_interface,
"on_prefs_confirm_unapplied_toggled": self.change_confirm_unapplied,
"on_prefs_btn_keys_define_clicked": self.change_grab_keys,
"on_prefs_graphics_type_changed": self.change_graphics_type,
})
@ -192,6 +195,10 @@ class vmmPreferences(vmmGObjectUI):
ignore3=None, ignore4=None):
self.widget("prefs-confirm-interface").set_active(
self.config.get_confirm_interface())
def refresh_confirm_unapplied(self, ignore1=None, ignore2=None,
ignore3=None, ignore4=None):
self.widget("prefs-confirm-unapplied").set_active(
self.config.get_confirm_unapplied())
def grabkeys_get_string(self, keysyms):
keystr = None
@ -286,6 +293,8 @@ class vmmPreferences(vmmGObjectUI):
self.config.set_confirm_removedev(src.get_active())
def change_confirm_interface(self, src):
self.config.set_confirm_interface(src.get_active())
def change_confirm_unapplied(self, src):
self.config.set_confirm_unapplied(src.get_active())
def change_graphics_type(self, src):
gtype = 'vnc'

View File

@ -361,7 +361,8 @@ def pretty_bytes(val):
xpath = virtinst.util.get_xml_path
def chkbox_helper(src, getcb, setcb, text1, text2=None):
def chkbox_helper(src, getcb, setcb, text1, text2=None, alwaysrecord=False,
chktext=_("Don't ask me again")):
"""
Helper to prompt user about proceeding with an operation
Returns True if operation should be cancelled
@ -373,14 +374,13 @@ def chkbox_helper(src, getcb, setcb, text1, text2=None):
return False
res = src.err.warn_chkbox(text1=text1, text2=text2,
chktext=_("Don't ask me again."),
chktext=chktext,
buttons=gtk.BUTTONS_YES_NO)
response, skip_prompt = res
if not response:
return True
if alwaysrecord or response:
setcb(not skip_prompt)
setcb(not skip_prompt)
return False
return not response
def get_list_selection(widget):
selection = widget.get_selection()

View File

@ -589,7 +589,7 @@ Spice</property>
<child>
<widget class="GtkTable" id="table6">
<property name="visible">True</property>
<property name="n_rows">5</property>
<property name="n_rows">6</property>
<property name="n_columns">2</property>
<property name="column_spacing">12</property>
<property name="row_spacing">6</property>
@ -741,6 +741,33 @@ Spice</property>
<property name="x_options">GTK_FILL</property>
</packing>
</child>
<child>
<widget class="GtkLabel" id="label5">
<property name="visible">True</property>
<property name="xalign">0</property>
<property name="label" translatable="yes">Unapplied changes:</property>
</widget>
<packing>
<property name="top_attach">5</property>
<property name="bottom_attach">6</property>
<property name="x_options">GTK_FILL</property>
</packing>
</child>
<child>
<widget class="GtkCheckButton" id="prefs-confirm-unapplied">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">False</property>
<property name="draw_indicator">True</property>
<signal name="toggled" handler="on_prefs_confirm_unapplied_toggled"/>
</widget>
<packing>
<property name="left_attach">1</property>
<property name="right_attach">2</property>
<property name="top_attach">5</property>
<property name="bottom_attach">6</property>
</packing>
</child>
</widget>
</child>
</widget>