Merge remote-tracking branch 'oliv/master' into new-backups

This commit is contained in:
Marek Marczykowski-Górecki 2013-11-25 05:05:43 +01:00
commit fa9d29075b
5 changed files with 265 additions and 110 deletions

View File

@ -148,21 +148,21 @@
</layout> </layout>
</widget> </widget>
<widget class="QWizardPage" name="select_dir_page"> <widget class="QWizardPage" name="select_dir_page">
<layout class="QHBoxLayout" name="horizontalLayout"> <layout class="QGridLayout" name="gridLayout_5">
<item> <item row="0" column="0">
<widget class="QGroupBox" name="groupBox"> <widget class="QGroupBox" name="groupBox">
<property name="title"> <property name="title">
<string>Backup destination directory</string> <string>Backup destination directory</string>
</property> </property>
<layout class="QGridLayout" name="gridLayout"> <layout class="QGridLayout" name="gridLayout">
<item row="0" column="0"> <item row="1" column="0">
<widget class="QLabel" name="label"> <widget class="QLabel" name="label">
<property name="text"> <property name="text">
<string>Device:</string> <string>Device:</string>
</property> </property>
</widget> </widget>
</item> </item>
<item row="0" column="1"> <item row="1" column="2">
<widget class="QComboBox" name="dev_combobox"> <widget class="QComboBox" name="dev_combobox">
<property name="sizePolicy"> <property name="sizePolicy">
<sizepolicy hsizetype="Maximum" vsizetype="Fixed"> <sizepolicy hsizetype="Maximum" vsizetype="Fixed">
@ -192,20 +192,73 @@
</item> </item>
</widget> </widget>
</item> </item>
<item row="1" column="0"> <item row="4" column="2">
<widget class="QLineEdit" name="dir_line_edit"/>
</item>
<item row="4" column="3">
<widget class="QToolButton" name="select_path_button">
<property name="text">
<string>...</string>
</property>
</widget>
</item>
<item row="2" column="2">
<widget class="QComboBox" name="appvm_combobox"/>
</item>
<item row="2" column="0">
<widget class="QLabel" name="label_5">
<property name="text">
<string>Target AppVM:</string>
</property>
</widget>
</item>
<item row="4" column="0">
<widget class="QLabel" name="label_2"> <widget class="QLabel" name="label_2">
<property name="text"> <property name="text">
<string>Backup directory:</string> <string>Backup directory:</string>
</property> </property>
</widget> </widget>
</item> </item>
<item row="1" column="1"> </layout>
<widget class="QLineEdit" name="dir_line_edit"/> </widget>
</item> </item>
<item row="1" column="2"> <item row="1" column="0">
<widget class="QToolButton" name="select_path_button"> <widget class="QGroupBox" name="groupBox_2">
<property name="title">
<string>Backup security</string>
</property>
<layout class="QGridLayout" name="gridLayout_4">
<item row="0" column="0">
<widget class="QLabel" name="label_10">
<property name="text"> <property name="text">
<string>...</string> <string>Encrypt backup:</string>
</property>
</widget>
</item>
<item row="2" column="0">
<widget class="QLabel" name="label_11">
<property name="text">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Encryption / Verification&lt;br/&gt;passphrase:&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QCheckBox" name="encryption_checkbox">
<property name="layoutDirection">
<enum>Qt::LeftToRight</enum>
</property>
<property name="text">
<string/>
</property>
<property name="checked">
<bool>true</bool>
</property>
</widget>
</item>
<item row="2" column="1" colspan="2">
<widget class="QLineEdit" name="passphrase_line_edit">
<property name="echoMode">
<enum>QLineEdit::Password</enum>
</property> </property>
</widget> </widget>
</item> </item>
@ -238,8 +291,8 @@
<string>&lt;!DOCTYPE HTML PUBLIC &quot;-//W3C//DTD HTML 4.0//EN&quot; &quot;http://www.w3.org/TR/REC-html40/strict.dtd&quot;&gt; <string>&lt;!DOCTYPE HTML PUBLIC &quot;-//W3C//DTD HTML 4.0//EN&quot; &quot;http://www.w3.org/TR/REC-html40/strict.dtd&quot;&gt;
&lt;html&gt;&lt;head&gt;&lt;meta name=&quot;qrichtext&quot; content=&quot;1&quot; /&gt;&lt;style type=&quot;text/css&quot;&gt; &lt;html&gt;&lt;head&gt;&lt;meta name=&quot;qrichtext&quot; content=&quot;1&quot; /&gt;&lt;style type=&quot;text/css&quot;&gt;
p, li { white-space: pre-wrap; } p, li { white-space: pre-wrap; }
&lt;/style&gt;&lt;/head&gt;&lt;body style=&quot; font-family:'Sans Serif'; font-size:9pt; font-weight:400; font-style:normal;&quot;&gt; &lt;/style&gt;&lt;/head&gt;&lt;body style=&quot; font-family:'Sans Serif'; font-size:10pt; font-weight:400; font-style:normal;&quot;&gt;
&lt;p style=&quot;-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string> &lt;p style=&quot;-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-size:9pt;&quot;&gt;&lt;br /&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property> </property>
</widget> </widget>
</item> </item>

View File

@ -79,7 +79,7 @@ class BackupVMsWindow(Ui_Backup, QWizard):
self.setupUi(self) self.setupUi(self)
self.show_running_vms_warning(False) self.show_running_vms_warning(False)
self.dir_line_edit.setReadOnly(True) self.dir_line_edit.setReadOnly(False)
self.select_vms_widget = MultiSelectWidget(self) self.select_vms_widget = MultiSelectWidget(self)
self.verticalLayout.insertWidget(1, self.select_vms_widget) self.verticalLayout.insertWidget(1, self.select_vms_widget)
@ -103,6 +103,8 @@ class BackupVMsWindow(Ui_Backup, QWizard):
self.__fill_vms_list__() self.__fill_vms_list__()
fill_devs_list(self) fill_devs_list(self)
fill_appvms_list(self)
def show_running_vms_warning(self, show): def show_running_vms_warning(self, show):
self.running_vms_warning.setVisible(show) self.running_vms_warning.setVisible(show)
self.shutdown_running_vms_button.setVisible(show) self.shutdown_running_vms_button.setVisible(show)
@ -261,10 +263,12 @@ class BackupVMsWindow(Ui_Backup, QWizard):
def __do_backup__(self, thread_monitor): def __do_backup__(self, thread_monitor):
msg = [] msg = []
try: try:
qubesutils.backup_do(str(self.backup_dir), self.files_to_backup, self.update_progress_bar) qubesutils.backup_do_copy(str(self.backup_dir), self.files_to_backup, str(self.passphrase_line_edit.text()), self.update_progress_bar, encrypt=self.encryption_checkbox.isChecked(), appvm=self.target_appvm)
#simulate_long_lasting_proces(10, self.update_progress_bar) #simulate_long_lasting_proces(10, self.update_progress_bar)
except Exception as ex: except Exception as ex:
print "Exception:",ex
msg.append(str(ex)) msg.append(str(ex))
if len(msg) > 0 : if len(msg) > 0 :
@ -275,11 +279,19 @@ class BackupVMsWindow(Ui_Backup, QWizard):
def current_page_changed(self, id): def current_page_changed(self, id):
if self.currentPage() is self.confirm_page: if self.currentPage() is self.confirm_page:
self.target_appvm = None
if self.appvm_combobox.currentText() != "None": #An existing appvm chosen
self.target_appvm = str(self.appvm_combobox.currentText())
# FIXME: ensure that at least a non empty passphrase has been provided
del self.func_output[:] del self.func_output[:]
try: try:
self.files_to_backup = qubesutils.backup_prepare(str(self.backup_dir), exclude_list = self.excluded, print_callback = self.gather_output) self.files_to_backup = qubesutils.backup_prepare(str(self.backup_dir), exclude_list = self.excluded, print_callback = self.gather_output)
except Exception as ex: except Exception as ex:
QMessageBox.critical(None, "Error while prepering backup.", "ERROR: {0}".format(ex)) print "Exception:",ex
QMessageBox.critical(None, "Error while preparing backup.", "ERROR: {0}".format(ex))
self.textEdit.setReadOnly(True) self.textEdit.setReadOnly(True)
self.textEdit.setFontFamily("Monospace") self.textEdit.setFontFamily("Monospace")

View File

@ -75,6 +75,20 @@ def umount_device(dev_mount_path):
if button == QMessageBox.Ok: if button == QMessageBox.Ok:
return dev_mount_path return dev_mount_path
def fill_appvms_list(dialog):
dialog.appvm_combobox.clear()
dialog.appvm_combobox.addItem("None")
dialog.appvm_combobox.setCurrentIndex(0) #current selected is null ""
for vm in dialog.qvm_collection.values():
if vm.is_appvm() and vm.internal:
continue
if vm.is_template() and vm.installed_by_rpm:
continue
if vm.is_running() and vm.qid != 0:
dialog.appvm_combobox.addItem(vm.name)
def fill_devs_list(dialog): def fill_devs_list(dialog):
dialog.dev_combobox.clear() dialog.dev_combobox.clear()
@ -157,18 +171,22 @@ def select_path_button_clicked(dialog):
file_dialog = QFileDialog() file_dialog = QFileDialog()
file_dialog.setReadOnly(True) file_dialog.setReadOnly(True)
if dialog.dev_mount_path != None: new_appvm = None
new_path = None
if dialog.appvm_combobox.currentText() != "None": #An existing appvm chosen
new_appvm = str(dialog.appvm_combobox.currentText())
elif dialog.dev_mount_path != None:
new_path = file_dialog.getExistingDirectory(dialog, "Select backup directory.", dialog.dev_mount_path) new_path = file_dialog.getExistingDirectory(dialog, "Select backup directory.", dialog.dev_mount_path)
else: else:
new_path = file_dialog.getExistingDirectory(dialog, "Select backup directory.", "~") new_path = file_dialog.getExistingDirectory(dialog, "Select backup directory.", "~")
if new_path: if new_path != None:
dialog.dir_line_edit.setText(new_path) dialog.dir_line_edit.setText(new_path)
dialog.backup_dir = new_path dialog.backup_dir = new_path
if (new_path or new_appvm) and len(dialog.backup_dir) > 0:
dialog.select_dir_page.emit(SIGNAL("completeChanged()")) dialog.select_dir_page.emit(SIGNAL("completeChanged()"))
def simulate_long_lasting_proces(period, progress_callback): def simulate_long_lasting_proces(period, progress_callback):
for i in range(period): for i in range(period):
progress_callback((i*100)/period) progress_callback((i*100)/period)

View File

@ -30,6 +30,7 @@ from qubes.qubes import QubesVmCollection
from qubes.qubes import QubesException from qubes.qubes import QubesException
from qubes.qubes import QubesDaemonPidfile from qubes.qubes import QubesDaemonPidfile
from qubes.qubes import QubesHost from qubes.qubes import QubesHost
from qubes.qubes import qubes_base_dir
import qubesmanager.resources_rc import qubesmanager.resources_rc
from pyinotify import WatchManager, Notifier, ThreadedNotifier, EventsCodes, ProcessEvent from pyinotify import WatchManager, Notifier, ThreadedNotifier, EventsCodes, ProcessEvent
@ -50,7 +51,7 @@ from backup_utils import *
class RestoreVMsWindow(Ui_Restore, QWizard): class RestoreVMsWindow(Ui_Restore, QWizard):
__pyqtSignals__ = ("restore_progress(int)",) __pyqtSignals__ = ("restore_progress(int)","backup_progress(int)")
def __init__(self, app, qvm_collection, blk_manager, parent=None): def __init__(self, app, qvm_collection, blk_manager, parent=None):
super(RestoreVMsWindow, self).__init__(parent) super(RestoreVMsWindow, self).__init__(parent)
@ -82,6 +83,7 @@ class RestoreVMsWindow(Ui_Restore, QWizard):
self.connect(self, SIGNAL("currentIdChanged(int)"), self.current_page_changed) self.connect(self, SIGNAL("currentIdChanged(int)"), self.current_page_changed)
self.connect(self.dev_combobox, SIGNAL("activated(int)"), self.dev_combobox_activated) self.connect(self.dev_combobox, SIGNAL("activated(int)"), self.dev_combobox_activated)
self.connect(self, SIGNAL("restore_progress(QString)"), self.commit_text_edit.append) self.connect(self, SIGNAL("restore_progress(QString)"), self.commit_text_edit.append)
self.connect(self, SIGNAL("backup_progress(int)"), self.progress_bar.setValue)
self.select_dir_page.isComplete = self.has_selected_dir self.select_dir_page.isComplete = self.has_selected_dir
self.select_vms_page.isComplete = self.has_selected_vms self.select_vms_page.isComplete = self.has_selected_vms
@ -90,6 +92,7 @@ class RestoreVMsWindow(Ui_Restore, QWizard):
self.select_vms_page.connect(self.select_vms_widget, SIGNAL("selected_changed()"), SIGNAL("completeChanged()")) self.select_vms_page.connect(self.select_vms_widget, SIGNAL("selected_changed()"), SIGNAL("completeChanged()"))
fill_devs_list(self) fill_devs_list(self)
fill_appvms_list(self)
self.__init_restore_options__() self.__init_restore_options__()
@ -118,7 +121,13 @@ class RestoreVMsWindow(Ui_Restore, QWizard):
self.select_vms_widget.selected_list.clear() self.select_vms_widget.selected_list.clear()
self.select_vms_widget.available_list.clear() self.select_vms_widget.available_list.clear()
self.vms_to_restore = qubesutils.backup_restore_prepare(str(self.backup_dir), self.restore_options, self.qvm_collection) self.target_appvm = None
if self.appvm_combobox.currentText() != "None": #An existing appvm chosen
self.target_appvm = str(self.appvm_combobox.currentText())
self.restore_tmpdir, qubes_xml = qubesutils.backup_restore_header(str(self.backup_dir), str(self.passphrase_line_edit.text()), self.encryption_checkbox.isChecked(), appvm=self.target_appvm)
self.vms_to_restore = qubesutils.backup_restore_prepare(str(self.backup_dir),os.path.join(self.restore_tmpdir, qubes_xml), str(self.passphrase_line_edit.text()), options=self.restore_options, host_collection=self.qvm_collection, encrypt=self.encryption_checkbox.isChecked(), appvm=self.target_appvm)
for vmname in self.vms_to_restore: for vmname in self.vms_to_restore:
self.select_vms_widget.available_list.addItem(vmname) self.select_vms_widget.available_list.addItem(vmname)
@ -147,17 +156,20 @@ class RestoreVMsWindow(Ui_Restore, QWizard):
def restore_error_output(self, s): def restore_error_output(self, s):
self.emit(SIGNAL("restore_progress(QString)"), '<font color="red">{0}</font>'.format(s)) self.emit(SIGNAL("restore_progress(QString)"), '<font color="red">{0}</font>'.format(s))
def restore_output(self, s): def restore_output(self, s):
self.emit(SIGNAL("restore_progress(QString)"),'<font color="black">{0}</font>'.format(s)) self.emit(SIGNAL("restore_progress(QString)"),'<font color="black">{0}</font>'.format(s))
def update_progress_bar(self, value):
self.emit(SIGNAL("backup_progress(int)"), value)
def __do_restore__(self, thread_monitor): def __do_restore__(self, thread_monitor):
err_msg = [] err_msg = []
self.qvm_collection.lock_db_for_writing() self.qvm_collection.lock_db_for_writing()
try: try:
qubesutils.backup_restore_do(str(self.backup_dir), self.vms_to_restore, self.qvm_collection, self.restore_output, self.restore_error_output) qubesutils.backup_restore_do(str(self.backup_dir), self.restore_tmpdir, str(self.passphrase_line_edit.text()), self.vms_to_restore, self.qvm_collection, encrypted=self.encryption_checkbox.isChecked(), appvm=self.target_appvm, print_callback=self.restore_output, error_callback=self.restore_error_output, progress_callback
=self.update_progress_bar)
except Exception as ex: except Exception as ex:
print "Exception:",ex
err_msg.append(str(ex)) err_msg.append(str(ex))
self.qvm_collection.unlock_db() self.qvm_collection.unlock_db()

View File

@ -20,83 +20,21 @@
<set>QWizard::NoBackButtonOnLastPage|QWizard::NoBackButtonOnStartPage</set> <set>QWizard::NoBackButtonOnLastPage|QWizard::NoBackButtonOnStartPage</set>
</property> </property>
<widget class="QWizardPage" name="select_dir_page"> <widget class="QWizardPage" name="select_dir_page">
<layout class="QVBoxLayout" name="verticalLayout"> <layout class="QGridLayout" name="gridLayout_3">
<item> <item row="3" column="0">
<widget class="QGroupBox" name="groupBox_2"> <spacer name="verticalSpacer">
<property name="font"> <property name="orientation">
<font> <enum>Qt::Vertical</enum>
<weight>50</weight>
<bold>false</bold>
</font>
</property> </property>
<property name="title"> <property name="sizeHint" stdset="0">
<string>Backup source location</string> <size>
<width>20</width>
<height>215</height>
</size>
</property> </property>
<layout class="QGridLayout" name="gridLayout"> </spacer>
<item row="0" column="0">
<widget class="QLabel" name="label">
<property name="font">
<font>
<weight>50</weight>
<bold>false</bold>
</font>
</property>
<property name="text">
<string>Device</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QComboBox" name="dev_combobox">
<property name="sizePolicy">
<sizepolicy hsizetype="Maximum" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<item>
<property name="text">
<string>dev1</string>
</property>
</item>
<item>
<property name="text">
<string>longdeviceblablabla</string>
</property>
</item>
<item>
<property name="text">
<string>dev2</string>
</property>
</item>
<item>
<property name="text">
<string>dev3</string>
</property>
</item>
</widget>
</item> </item>
<item row="1" column="0"> <item row="1" column="0">
<widget class="QLabel" name="label_2">
<property name="text">
<string>Backup directory:</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QLineEdit" name="dir_line_edit"/>
</item>
<item row="1" column="2">
<widget class="QToolButton" name="select_path_button">
<property name="text">
<string>...</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QGroupBox" name="options_groupbox"> <widget class="QGroupBox" name="options_groupbox">
<property name="font"> <property name="font">
<font> <font>
@ -138,18 +76,127 @@
</layout> </layout>
</widget> </widget>
</item> </item>
<item row="0" column="0">
<widget class="QGroupBox" name="groupBox_2">
<property name="font">
<font>
<weight>50</weight>
<bold>false</bold>
</font>
</property>
<property name="title">
<string>Backup source location</string>
</property>
<layout class="QGridLayout" name="gridLayout">
<item row="0" column="1">
<widget class="QComboBox" name="dev_combobox">
<property name="sizePolicy">
<sizepolicy hsizetype="Maximum" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<item> <item>
<spacer name="verticalSpacer"> <property name="text">
<property name="orientation"> <string>dev1</string>
<enum>Qt::Vertical</enum>
</property> </property>
<property name="sizeHint" stdset="0"> </item>
<size> <item>
<width>20</width> <property name="text">
<height>215</height> <string>longdeviceblablabla</string>
</size>
</property> </property>
</spacer> </item>
<item>
<property name="text">
<string>dev2</string>
</property>
</item>
<item>
<property name="text">
<string>dev3</string>
</property>
</item>
</widget>
</item>
<item row="6" column="1">
<widget class="QLineEdit" name="dir_line_edit"/>
</item>
<item row="6" column="2">
<widget class="QToolButton" name="select_path_button">
<property name="text">
<string>...</string>
</property>
</widget>
</item>
<item row="6" column="0">
<widget class="QLabel" name="label_2">
<property name="text">
<string>Backup directory:</string>
</property>
</widget>
</item>
<item row="0" column="0">
<widget class="QLabel" name="label">
<property name="font">
<font>
<weight>50</weight>
<bold>false</bold>
</font>
</property>
<property name="text">
<string>Device:</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QComboBox" name="appvm_combobox"/>
</item>
<item row="1" column="0">
<widget class="QLabel" name="label_3">
<property name="text">
<string>AppVM:</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item row="2" column="0">
<widget class="QGroupBox" name="groupBox">
<property name="title">
<string>Security options</string>
</property>
<layout class="QGridLayout" name="gridLayout_4">
<item row="0" column="1">
<widget class="QCheckBox" name="encryption_checkbox">
<property name="text">
<string/>
</property>
</widget>
</item>
<item row="0" column="0">
<widget class="QLabel" name="label_4">
<property name="text">
<string>Encrypted backup:</string>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QLabel" name="label_5">
<property name="text">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Decryption / Verification&lt;br/&gt;passphrase:&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QLineEdit" name="passphrase_line_edit">
<property name="echoMode">
<enum>QLineEdit::Password</enum>
</property>
</widget>
</item>
</layout>
</widget>
</item> </item>
</layout> </layout>
</widget> </widget>
@ -189,8 +236,8 @@
<string>&lt;!DOCTYPE HTML PUBLIC &quot;-//W3C//DTD HTML 4.0//EN&quot; &quot;http://www.w3.org/TR/REC-html40/strict.dtd&quot;&gt; <string>&lt;!DOCTYPE HTML PUBLIC &quot;-//W3C//DTD HTML 4.0//EN&quot; &quot;http://www.w3.org/TR/REC-html40/strict.dtd&quot;&gt;
&lt;html&gt;&lt;head&gt;&lt;meta name=&quot;qrichtext&quot; content=&quot;1&quot; /&gt;&lt;style type=&quot;text/css&quot;&gt; &lt;html&gt;&lt;head&gt;&lt;meta name=&quot;qrichtext&quot; content=&quot;1&quot; /&gt;&lt;style type=&quot;text/css&quot;&gt;
p, li { white-space: pre-wrap; } p, li { white-space: pre-wrap; }
&lt;/style&gt;&lt;/head&gt;&lt;body style=&quot; font-family:'Sans Serif'; font-size:9pt; font-weight:400; font-style:normal;&quot;&gt; &lt;/style&gt;&lt;/head&gt;&lt;body style=&quot; font-family:'Sans Serif'; font-size:10pt; font-weight:400; font-style:normal;&quot;&gt;
&lt;p style=&quot;-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string> &lt;p style=&quot;-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-size:9pt;&quot;&gt;&lt;br /&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property> </property>
</widget> </widget>
</item> </item>
@ -217,6 +264,19 @@ p, li { white-space: pre-wrap; }
<item> <item>
<widget class="QTextEdit" name="commit_text_edit"/> <widget class="QTextEdit" name="commit_text_edit"/>
</item> </item>
<item>
<widget class="QProgressBar" name="progress_bar">
<property name="value">
<number>0</number>
</property>
<property name="textVisible">
<bool>false</bool>
</property>
<property name="format">
<string/>
</property>
</widget>
</item>
</layout> </layout>
</widget> </widget>
</widget> </widget>