mirror of
https://github.com/virt-manager/virt-manager.git
synced 2025-01-05 09:17:57 +03:00
f107e39989
Init a shared log instance in virtinst/logger.py, and use that throughout the code base, so we aren't calling directly into 'logging'. This helps protect our logging output from being cluttered with other library output, as happens with some 'requests' usage
394 lines
14 KiB
Python
394 lines
14 KiB
Python
# Copyright (C) 2007, 2013-2014 Red Hat, Inc.
|
|
#
|
|
# This work is licensed under the GNU GPLv2 or later.
|
|
# See the COPYING file in the top-level directory.
|
|
|
|
import sys
|
|
import traceback
|
|
|
|
from gi.repository import Gtk
|
|
|
|
from virtinst import log
|
|
|
|
from .baseclass import vmmGObject
|
|
|
|
|
|
def _launch_dialog(dialog, primary_text, secondary_text, title,
|
|
widget=None, modal=True):
|
|
dialog.set_property("text", primary_text)
|
|
dialog.format_secondary_text(secondary_text or None)
|
|
dialog.set_title(title)
|
|
|
|
if widget:
|
|
dialog.get_content_area().add(widget)
|
|
|
|
res = False
|
|
if modal:
|
|
res = dialog.run()
|
|
res = bool(res in [Gtk.ResponseType.YES, Gtk.ResponseType.OK])
|
|
dialog.destroy()
|
|
else:
|
|
def response_destroy(src, ignore):
|
|
src.destroy()
|
|
dialog.connect("response", response_destroy)
|
|
dialog.show()
|
|
|
|
return res
|
|
|
|
|
|
class vmmErrorDialog(vmmGObject):
|
|
# singleton instance for non-UI classes
|
|
@classmethod
|
|
def get_instance(cls):
|
|
if not cls._instance:
|
|
cls._instance = vmmErrorDialog(None)
|
|
return cls._instance
|
|
|
|
def __init__(self, parent):
|
|
vmmGObject.__init__(self)
|
|
self._parent = parent
|
|
self._simple = None
|
|
|
|
self._modal_default = False
|
|
|
|
def _cleanup(self):
|
|
pass
|
|
|
|
def set_modal_default(self, val):
|
|
self._modal_default = val
|
|
def get_parent(self):
|
|
return self._parent
|
|
|
|
def show_err(self, summary, details=None, title="",
|
|
modal=None, debug=True,
|
|
dialog_type=Gtk.MessageType.ERROR,
|
|
buttons=Gtk.ButtonsType.CLOSE,
|
|
text2=None):
|
|
if modal is None:
|
|
modal = self._modal_default
|
|
|
|
if details is None:
|
|
details = summary
|
|
if sys.exc_info()[0] is not None:
|
|
details += "\n\n" + "".join(traceback.format_exc()).strip()
|
|
else:
|
|
details = str(details)
|
|
|
|
if debug:
|
|
debugmsg = "error dialog message:\nsummary=%s" % summary
|
|
if details and details != summary:
|
|
det = details
|
|
if details.startswith(summary):
|
|
det = details[len(summary):].strip()
|
|
debugmsg += "\ndetails=%s" % det
|
|
log.debug(debugmsg)
|
|
|
|
# Make sure we have consistent details for error dialogs
|
|
if (dialog_type == Gtk.MessageType.ERROR and summary not in details):
|
|
details = summary + "\n\n" + details
|
|
|
|
dialog = _errorDialog(parent=self.get_parent(),
|
|
flags=0,
|
|
message_type=dialog_type,
|
|
buttons=buttons)
|
|
|
|
return dialog.show_dialog(primary_text=summary,
|
|
secondary_text=text2,
|
|
details=details, title=title,
|
|
modal=modal)
|
|
|
|
|
|
###################################
|
|
# Simple one shot message dialogs #
|
|
###################################
|
|
|
|
def _simple_dialog(self, dialog_type, buttons, text1,
|
|
text2, title, widget=None, modal=True):
|
|
|
|
dialog = Gtk.MessageDialog(self.get_parent(),
|
|
flags=Gtk.DialogFlags.DESTROY_WITH_PARENT,
|
|
message_type=dialog_type,
|
|
buttons=buttons)
|
|
if self._simple:
|
|
self._simple.destroy()
|
|
self._simple = dialog
|
|
self._simple.get_accessible().set_name("vmm dialog")
|
|
|
|
return _launch_dialog(self._simple,
|
|
text1, text2 or "", title or "",
|
|
widget=widget,
|
|
modal=modal)
|
|
|
|
def val_err(self, text1, text2=None, title=_("Input Error"), modal=True):
|
|
logtext = _("Validation Error: %s") % text1
|
|
if text2:
|
|
logtext += " %s" % text2
|
|
|
|
if isinstance(text1, Exception) or isinstance(text2, Exception):
|
|
log.exception(logtext)
|
|
else:
|
|
self._logtrace(logtext)
|
|
|
|
dtype = Gtk.MessageType.ERROR
|
|
buttons = Gtk.ButtonsType.OK
|
|
self._simple_dialog(dtype, buttons,
|
|
str(text1),
|
|
text2 and str(text2) or "",
|
|
str(title), None, modal)
|
|
return False
|
|
|
|
def show_info(self, text1, text2=None, title="", widget=None, modal=True,
|
|
buttons=Gtk.ButtonsType.OK):
|
|
dtype = Gtk.MessageType.INFO
|
|
self._simple_dialog(dtype, buttons, text1, text2, title, widget, modal)
|
|
return False
|
|
|
|
def yes_no(self, text1, text2=None, title=None):
|
|
dtype = Gtk.MessageType.WARNING
|
|
buttons = Gtk.ButtonsType.YES_NO
|
|
return self._simple_dialog(dtype, buttons, text1, text2, title)
|
|
|
|
def ok_cancel(self, text1, text2=None, title=None):
|
|
dtype = Gtk.MessageType.WARNING
|
|
buttons = Gtk.ButtonsType.OK_CANCEL
|
|
return self._simple_dialog(dtype, buttons, text1, text2, title)
|
|
|
|
def ok(self, text1, text2=None, title=None):
|
|
dtype = Gtk.MessageType.WARNING
|
|
buttons = Gtk.ButtonsType.OK
|
|
return self._simple_dialog(dtype, buttons, text1, text2, title)
|
|
|
|
def confirm_unapplied_changes(self):
|
|
"""
|
|
Helper function for confirming whether to apply unapplied changes
|
|
"""
|
|
return self.chkbox_helper(
|
|
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."),
|
|
default=False)
|
|
|
|
|
|
##########################################
|
|
# One shot dialog with a checkbox prompt #
|
|
##########################################
|
|
|
|
def warn_chkbox(self, text1, text2=None, chktext=None, buttons=None):
|
|
dtype = Gtk.MessageType.WARNING
|
|
buttons = buttons or Gtk.ButtonsType.OK_CANCEL
|
|
chkbox = _errorDialog(parent=self.get_parent(),
|
|
flags=0,
|
|
message_type=dtype,
|
|
buttons=buttons)
|
|
return chkbox.show_dialog(primary_text=text1,
|
|
secondary_text=text2,
|
|
chktext=chktext)
|
|
|
|
def err_chkbox(self, text1, text2=None, chktext=None, buttons=None):
|
|
dtype = Gtk.MessageType.ERROR
|
|
buttons = buttons or Gtk.ButtonsType.OK
|
|
chkbox = _errorDialog(parent=self.get_parent(),
|
|
flags=0,
|
|
message_type=dtype,
|
|
buttons=buttons)
|
|
return chkbox.show_dialog(primary_text=text1,
|
|
secondary_text=text2,
|
|
chktext=chktext)
|
|
|
|
def chkbox_helper(self, getcb, setcb, text1, text2=None,
|
|
default=True,
|
|
chktext=_("Don't ask me again")):
|
|
"""
|
|
Helper to prompt user about proceeding with an operation
|
|
Returns True if the 'yes' or 'ok' button was selected, False otherwise
|
|
|
|
@default: What value to return if getcb tells us not to prompt
|
|
"""
|
|
do_prompt = getcb()
|
|
if not do_prompt:
|
|
return default
|
|
|
|
# pylint: disable=unpacking-non-sequence
|
|
res = self.warn_chkbox(text1=text1, text2=text2,
|
|
chktext=chktext,
|
|
buttons=Gtk.ButtonsType.YES_NO)
|
|
response, skip_prompt = res
|
|
setcb(not skip_prompt)
|
|
|
|
return response
|
|
|
|
def browse_local(self, conn, dialog_name, start_folder=None,
|
|
_type=None, dialog_type=None,
|
|
confirm_func=None, browse_reason=None,
|
|
choose_button=None, default_name=None):
|
|
"""
|
|
Helper function for launching a filechooser
|
|
|
|
@dialog_name: String to use in the title bar of the filechooser.
|
|
@conn: vmmConnection used by calling class
|
|
@start_folder: Folder the filechooser is viewing at startup
|
|
@_type: File extension to filter by (e.g. "iso", "png")
|
|
@dialog_type: Maps to FileChooserDialog 'action'
|
|
@confirm_func: Optional callback function if file is chosen.
|
|
@browse_reason: The vmmConfig.CONFIG_DIR* reason we are browsing.
|
|
If set, this will override the 'folder' parameter with the gsettings
|
|
value, and store the user chosen path.
|
|
"""
|
|
import os
|
|
|
|
# Initial setup
|
|
overwrite_confirm = False
|
|
|
|
if dialog_type is None:
|
|
dialog_type = Gtk.FileChooserAction.OPEN
|
|
if dialog_type == Gtk.FileChooserAction.SAVE:
|
|
if choose_button is None:
|
|
choose_button = Gtk.STOCK_SAVE
|
|
overwrite_confirm = True
|
|
|
|
if choose_button is None:
|
|
choose_button = Gtk.STOCK_OPEN
|
|
|
|
fcdialog = Gtk.FileChooserDialog(title=dialog_name,
|
|
parent=self.get_parent(),
|
|
action=dialog_type,
|
|
buttons=(Gtk.STOCK_CANCEL,
|
|
Gtk.ResponseType.CANCEL,
|
|
choose_button,
|
|
Gtk.ResponseType.ACCEPT))
|
|
fcdialog.set_default_response(Gtk.ResponseType.ACCEPT)
|
|
|
|
if default_name:
|
|
fcdialog.set_current_name(default_name)
|
|
|
|
# If confirm is set, warn about a file overwrite
|
|
if confirm_func:
|
|
overwrite_confirm = True
|
|
fcdialog.connect("confirm-overwrite", confirm_func)
|
|
fcdialog.set_do_overwrite_confirmation(overwrite_confirm)
|
|
|
|
# Set file match pattern (ex. *.png)
|
|
if _type is not None:
|
|
pattern = _type
|
|
name = None
|
|
if isinstance(_type, tuple):
|
|
pattern = _type[0]
|
|
name = _type[1]
|
|
|
|
f = Gtk.FileFilter()
|
|
f.add_pattern("*." + pattern)
|
|
if name:
|
|
f.set_name(name)
|
|
fcdialog.set_filter(f)
|
|
|
|
# Set initial dialog folder
|
|
if browse_reason:
|
|
start_folder = self.config.get_default_directory(
|
|
conn, browse_reason)
|
|
|
|
if start_folder is not None:
|
|
if os.access(start_folder, os.R_OK):
|
|
fcdialog.set_current_folder(start_folder)
|
|
|
|
# Run the dialog and parse the response
|
|
ret = None
|
|
if fcdialog.run() == Gtk.ResponseType.ACCEPT:
|
|
ret = fcdialog.get_filename()
|
|
fcdialog.destroy()
|
|
|
|
# Store the chosen directory in gsettings if necessary
|
|
if ret and browse_reason and not ret.startswith("/dev"):
|
|
self.config.set_default_directory(
|
|
os.path.dirname(ret), browse_reason)
|
|
return ret
|
|
|
|
|
|
class _errorDialog (Gtk.MessageDialog):
|
|
"""
|
|
Custom error dialog with optional check boxes or details drop down
|
|
"""
|
|
def __init__(self, *args, **kwargs):
|
|
Gtk.MessageDialog.__init__(self, *args, **kwargs)
|
|
|
|
self.set_title("")
|
|
for child in self.get_message_area().get_children():
|
|
if hasattr(child, "set_max_width_chars"):
|
|
child.set_max_width_chars(40)
|
|
|
|
self.get_accessible().set_name("vmm dialog")
|
|
|
|
self.chk_vbox = None
|
|
self.chk_align = None
|
|
self.init_chkbox()
|
|
|
|
self.buffer = None
|
|
self.buf_expander = None
|
|
self.init_details()
|
|
|
|
def init_chkbox(self):
|
|
# Init check items
|
|
self.chk_vbox = Gtk.VBox(False, False)
|
|
self.chk_vbox.set_spacing(0)
|
|
|
|
self.chk_align = Gtk.Alignment()
|
|
self.chk_align.set_padding(0, 0, 0, 0)
|
|
self.chk_align.add(self.chk_vbox)
|
|
|
|
self.chk_align.show_all()
|
|
self.vbox.pack_start( # pylint: disable=no-member
|
|
self.chk_align, False, False, 0)
|
|
|
|
def init_details(self):
|
|
# Init details buffer
|
|
self.buffer = Gtk.TextBuffer()
|
|
self.buf_expander = Gtk.Expander.new(_("Details"))
|
|
sw = Gtk.ScrolledWindow()
|
|
sw.set_shadow_type(Gtk.ShadowType.IN)
|
|
sw.set_size_request(400, 240)
|
|
sw.set_policy(Gtk.PolicyType.AUTOMATIC, Gtk.PolicyType.AUTOMATIC)
|
|
details = Gtk.TextView.new_with_buffer(self.buffer)
|
|
details.set_editable(False)
|
|
details.set_overwrite(False)
|
|
details.set_cursor_visible(False)
|
|
details.set_wrap_mode(Gtk.WrapMode.WORD)
|
|
details.set_border_width(6)
|
|
sw.add(details)
|
|
self.buf_expander.add(sw)
|
|
self.vbox.pack_start( # pylint: disable=no-member
|
|
self.buf_expander, False, False, 0)
|
|
self.buf_expander.show_all()
|
|
|
|
def show_dialog(self, primary_text, secondary_text="",
|
|
title="", details="", chktext="",
|
|
modal=True):
|
|
chkbox = None
|
|
res = None
|
|
|
|
# Hide starting widgets
|
|
self.hide()
|
|
self.buf_expander.hide()
|
|
for c in self.chk_vbox.get_children():
|
|
self.chk_vbox.remove(c)
|
|
|
|
if details:
|
|
self.buffer.set_text(details)
|
|
title = title or ""
|
|
self.buf_expander.show()
|
|
|
|
if chktext:
|
|
chkbox = Gtk.CheckButton(chktext)
|
|
self.chk_vbox.add(chkbox)
|
|
chkbox.show()
|
|
|
|
res = _launch_dialog(self,
|
|
primary_text, secondary_text or "",
|
|
title,
|
|
modal=modal)
|
|
|
|
if chktext:
|
|
res = [res, chkbox.get_active()]
|
|
|
|
return res
|