1
0

Major refactoring

- separation between Wallet and key management (Keystore)
 - simplification of wallet classes
 - remove support for multiple accounts in the same wallet
 - add support for OP_RETURN to Trezor plugin
 - split multi-accounts wallets for backward compatibility
This commit is contained in:
ThomasV
2016-07-02 08:58:56 +02:00
parent 6373a76a4a
commit 1159f85e05
35 changed files with 1753 additions and 2068 deletions

View File

@@ -163,8 +163,8 @@ class ElectrumGui:
wallet = wizard.run_and_get_wallet()
if not wallet:
return
if wallet.get_action():
return
#if wallet.get_action():
# return
self.daemon.add_wallet(wallet)
w = self.create_window_for_wallet(wallet)
if uri:

View File

@@ -41,26 +41,14 @@ class AddressList(MyTreeWidget):
def on_update(self):
self.wallet = self.parent.wallet
self.accounts_expanded = self.wallet.storage.get('accounts_expanded', {})
item = self.currentItem()
current_address = item.data(0, Qt.UserRole).toString() if item else None
self.clear()
accounts = self.wallet.get_accounts()
if self.parent.current_account is None:
account_items = sorted(accounts.items())
else:
account_items = [(self.parent.current_account, accounts.get(self.parent.current_account))]
for k, account in account_items:
if len(accounts) > 1:
name = self.wallet.get_account_name(k)
c, u, x = self.wallet.get_account_balance(k)
account_item = QTreeWidgetItem([ name, '', self.parent.format_amount(c + u + x), ''])
account_item.setData(0, Qt.UserRole, k)
self.addTopLevelItem(account_item)
account_item.setExpanded(self.accounts_expanded.get(k, True))
else:
account_item = self
sequences = [0,1] if account.has_change() else [0]
receiving_addresses = self.wallet.get_receiving_addresses()
change_addresses = self.wallet.get_change_addresses()
if True:
account_item = self
sequences = [0,1] if change_addresses else [0]
for is_change in sequences:
if len(sequences) > 1:
name = _("Receiving") if not is_change else _("Change")
@@ -72,7 +60,7 @@ class AddressList(MyTreeWidget):
seq_item = account_item
used_item = QTreeWidgetItem( [ _("Used"), '', '', '', ''] )
used_flag = False
addr_list = account.get_addresses(is_change)
addr_list = change_addresses if is_change else receiving_addresses
for address in addr_list:
num = len(self.wallet.history.get(address,[]))
is_used = self.wallet.is_used(address)
@@ -85,7 +73,7 @@ class AddressList(MyTreeWidget):
address_item.setData(0, Qt.UserRole+1, True) # label can be edited
if self.wallet.is_frozen(address):
address_item.setBackgroundColor(0, QColor('lightblue'))
if self.wallet.is_beyond_limit(address, account, is_change):
if self.wallet.is_beyond_limit(address, is_change):
address_item.setBackgroundColor(0, QColor('red'))
if is_used:
if not used_flag:
@@ -107,8 +95,9 @@ class AddressList(MyTreeWidget):
address_item.addChild(utxo_item)
def create_menu(self, position):
from electrum.wallet import Multisig_Wallet
from electrum.wallet import Multisig_Wallet, Imported_Wallet
is_multisig = isinstance(self.wallet, Multisig_Wallet)
is_imported = isinstance(self.wallet, Imported_Wallet)
selected = self.selectedItems()
multi_select = len(selected) > 1
addrs = [unicode(item.text(0)) for item in selected]
@@ -142,7 +131,7 @@ class AddressList(MyTreeWidget):
if not is_multisig and not self.wallet.is_watching_only():
menu.addAction(_("Sign/verify message"), lambda: self.parent.sign_verify_message(addr))
menu.addAction(_("Encrypt/decrypt message"), lambda: self.parent.encrypt_message(addr))
if self.wallet.is_imported(addr):
if is_imported:
menu.addAction(_("Remove from wallet"), lambda: self.parent.delete_imported_key(addr))
addr_URL = block_explorer_URL(self.config, 'addr', addr)
if addr_URL:
@@ -161,18 +150,3 @@ class AddressList(MyTreeWidget):
run_hook('receive_menu', menu, addrs, self.wallet)
menu.exec_(self.viewport().mapToGlobal(position))
def create_account_menu(self, position, k, item):
menu = QMenu()
exp = item.isExpanded()
menu.addAction(_("Minimize") if exp else _("Maximize"), lambda: self.set_account_expanded(item, k, not exp))
menu.addAction(_("Rename"), lambda: self.parent.edit_account_label(k))
if self.wallet.seed_version > 4:
menu.addAction(_("View details"), lambda: self.parent.show_account_details(k))
menu.exec_(self.viewport().mapToGlobal(position))
def set_account_expanded(self, item, k, b):
item.setExpanded(b)
self.accounts_expanded[k] = b
def on_close(self):
self.wallet.storage.put('accounts_expanded', self.accounts_expanded)

View File

@@ -61,7 +61,7 @@ class HistoryList(MyTreeWidget):
def get_domain(self):
'''Replaced in address_dialog.py'''
return self.wallet.get_account_addresses(self.parent.current_account)
return self.wallet.get_addresses()
def on_update(self):
self.wallet = self.parent.wallet

View File

@@ -1,4 +1,5 @@
import sys
import os
from PyQt4.QtGui import *
from PyQt4.QtCore import *
@@ -156,22 +157,47 @@ class InstallWizard(QDialog, MessageBoxMixin, BaseWizard):
if self.config.get('auto_connect') is None:
self.choose_server(self.network)
action = self.get_action()
if action != 'new':
path = self.storage.path
if self.storage.requires_split():
self.hide()
msg = _("The wallet '%s' contains multiple accounts, which are no longer supported in Electrum 2.7.\n\n"
"Do you want to split your wallet into multiple files?"%path)
if not self.question(msg):
return
file_list = '\n'.join(self.storage.split_accounts())
msg = _('Your accounts have been moved to:\n %s.\n\nDo you want to delete the old file:\n%s' % (file_list, path))
if self.question(msg):
os.remove(path)
self.show_warning(_('The file was removed'))
return
if self.storage.requires_upgrade():
self.hide()
msg = _("The format of your wallet '%s' must be upgraded for Electrum. This change will not be backward compatible"%path)
if not self.question(msg):
return
self.storage.upgrade()
self.show_warning(_('Your wallet was upgraded successfully'))
self.wallet = Wallet(self.storage)
self.terminate()
return self.wallet
action = self.storage.get_action()
if action and action != 'new':
self.hide()
path = self.storage.path
msg = _("The file '%s' contains an incompletely created wallet.\n"
"Do you want to complete its creation now?") % path
if not self.question(msg):
if self.question(_("Do you want to delete '%s'?") % path):
import os
os.remove(path)
self.show_warning(_('The file was removed'))
return
return
self.show()
self.run(action)
return self.wallet
if action:
# self.wallet is set in run
self.run(action)
return self.wallet
def finished(self):
'''Ensure the dialog is closed.'''

View File

@@ -51,7 +51,7 @@ from electrum.util import (block_explorer, block_explorer_info, format_time,
from electrum import Transaction, mnemonic
from electrum import util, bitcoin, commands, coinchooser
from electrum import SimpleConfig, paymentrequest
from electrum.wallet import Wallet, BIP32_RD_Wallet, Multisig_Wallet
from electrum.wallet import Wallet, Multisig_Wallet
from amountedit import BTCAmountEdit, MyLineEdit, BTCkBEdit
from network_dialog import NetworkDialog
@@ -248,21 +248,9 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, PrintError):
t.setDaemon(True)
t.start()
def update_account_selector(self):
# account selector
accounts = self.wallet.get_account_names()
self.account_selector.clear()
if len(accounts) > 1:
self.account_selector.addItems([_("All accounts")] + accounts.values())
self.account_selector.setCurrentIndex(0)
self.account_selector.show()
else:
self.account_selector.hide()
def close_wallet(self):
if self.wallet:
self.print_error('close_wallet', self.wallet.storage.path)
self.address_list.on_close()
run_hook('close_wallet', self.wallet)
def load_wallet(self, wallet):
@@ -270,13 +258,11 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, PrintError):
self.wallet = wallet
self.update_recently_visited(wallet.storage.path)
# address used to create a dummy transaction and estimate transaction fee
self.current_account = self.wallet.storage.get("current_account", None)
self.history_list.update()
self.need_update.set()
# Once GUI has been initialized check if we want to announce something since the callback has been called before the GUI was initialized
self.notify_transactions()
# update menus
self.update_new_account_menu()
self.seed_menu.setEnabled(self.wallet.has_seed())
self.mpk_menu.setEnabled(self.wallet.is_deterministic())
self.update_lock_icon()
@@ -391,8 +377,6 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, PrintError):
wallet_menu = menubar.addMenu(_("&Wallet"))
wallet_menu.addAction(_("&New contact"), self.new_contact_dialog)
self.new_account_menu = wallet_menu.addAction(_("&New account"), self.new_account_dialog)
wallet_menu.addSeparator()
self.password_menu = wallet_menu.addAction(_("&Password"), self.change_password_dialog)
@@ -569,7 +553,7 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, PrintError):
text = _("Server is lagging (%d blocks)"%server_lag)
icon = QIcon(":icons/status_lagging.png")
else:
c, u, x = self.wallet.get_account_balance(self.current_account)
c, u, x = self.wallet.get_balance()
text = _("Balance" ) + ": %s "%(self.format_amount_and_units(c))
if u:
text += " [%s unconfirmed]"%(self.format_amount(u, True).strip())
@@ -593,8 +577,6 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, PrintError):
self.update_status()
if self.wallet.up_to_date or not self.network or not self.network.is_connected():
self.update_tabs()
if self.wallet.up_to_date:
self.check_next_account()
def update_tabs(self):
self.history_list.update()
@@ -788,7 +770,7 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, PrintError):
self.saved = True
def new_payment_request(self):
addr = self.wallet.get_unused_address(self.current_account)
addr = self.wallet.get_unused_address(None)
if addr is None:
from electrum.wallet import Imported_Wallet
if isinstance(self.wallet, Imported_Wallet):
@@ -796,7 +778,7 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, PrintError):
return
if not self.question(_("Warning: The next address will not be recovered automatically if you restore your wallet from seed; you may need to add it manually.\n\nThis occurs because you have too many unused addresses in your wallet. To avoid this situation, use the existing addresses first.\n\nCreate anyway?")):
return
addr = self.wallet.create_new_address(self.current_account, False)
addr = self.wallet.create_new_address(None, False)
self.set_receive_address(addr)
self.expires_label.hide()
self.expires_combo.show()
@@ -809,7 +791,7 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, PrintError):
self.receive_amount_e.setAmount(None)
def clear_receive_tab(self):
addr = self.wallet.get_unused_address(self.current_account)
addr = self.wallet.get_unused_address()
self.receive_address_e.setText(addr if addr else '')
self.receive_message_e.setText('')
self.receive_amount_e.setAmount(None)
@@ -1102,7 +1084,7 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, PrintError):
def request_password(self, *args, **kwargs):
parent = self.top_level_window()
password = None
while self.wallet.use_encryption:
while self.wallet.has_password():
password = self.password_dialog(parent=parent)
try:
if password:
@@ -1208,7 +1190,7 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, PrintError):
if tx.get_fee() >= self.config.get('confirm_fee', 100000):
msg.append(_('Warning')+ ': ' + _("The fee for this transaction seems unusually high."))
if self.wallet.use_encryption:
if self.wallet.has_password():
msg.append("")
msg.append(_("Enter your password to proceed"))
password = self.password_dialog('\n'.join(msg))
@@ -1237,7 +1219,7 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, PrintError):
'''Sign the transaction in a separate thread. When done, calls
the callback with a success code of True or False.
'''
if self.wallet.use_encryption and not password:
if self.wallet.has_password() and not password:
callback(False) # User cancelled password input
return
@@ -1438,7 +1420,7 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, PrintError):
if self.pay_from:
return self.pay_from
else:
domain = self.wallet.get_account_addresses(self.current_account)
domain = self.wallet.get_addresses()
return self.wallet.get_spendable_coins(domain)
@@ -1561,18 +1543,6 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, PrintError):
console.updateNamespace(methods)
def change_account(self,s):
if s == _("All accounts"):
self.current_account = None
else:
accounts = self.wallet.get_account_names()
for k, v in accounts.items():
if v == s:
self.current_account = k
self.history_list.update()
self.update_status()
self.address_list.update()
self.request_list.update()
def create_status_bar(self):
@@ -1583,11 +1553,6 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, PrintError):
self.balance_label = QLabel("")
sb.addWidget(self.balance_label)
self.account_selector = QComboBox()
self.account_selector.setSizeAdjustPolicy(QComboBox.AdjustToContents)
self.connect(self.account_selector, SIGNAL("activated(QString)"), self.change_account)
sb.addPermanentWidget(self.account_selector)
self.search_box = QLineEdit()
self.search_box.textChanged.connect(self.do_search)
self.search_box.hide()
@@ -1606,7 +1571,7 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, PrintError):
self.setStatusBar(sb)
def update_lock_icon(self):
icon = QIcon(":icons/lock.png") if self.wallet.use_encryption else QIcon(":icons/unlock.png")
icon = QIcon(":icons/lock.png") if self.wallet.has_password() else QIcon(":icons/unlock.png")
self.password_button.setIcon(icon)
def update_buttons_on_seed(self):
@@ -1619,7 +1584,7 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, PrintError):
msg = (_('Your wallet is encrypted. Use this dialog to change your '
'password. To disable wallet encryption, enter an empty new '
'password.') if self.wallet.use_encryption
'password.') if self.wallet.has_password()
else _('Your wallet keys are not encrypted'))
d = PasswordDialog(self, self.wallet, msg, PW_CHANGE)
ok, password, new_password = d.run()
@@ -1684,48 +1649,6 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, PrintError):
if self.set_contact(unicode(line2.text()), str(line1.text())):
self.tabs.setCurrentIndex(4)
def update_new_account_menu(self):
self.new_account_menu.setVisible(self.wallet.can_create_accounts())
self.new_account_menu.setEnabled(self.wallet.permit_account_naming())
self.update_account_selector()
def new_account_dialog(self):
dialog = WindowModalDialog(self, _("New Account Name"))
vbox = QVBoxLayout()
msg = _("Enter a name to give the account. You will not be "
"permitted to create further accounts until the new account "
"receives at least one transaction.") + "\n"
label = QLabel(msg)
label.setWordWrap(True)
vbox.addWidget(label)
e = QLineEdit()
vbox.addWidget(e)
vbox.addLayout(Buttons(CancelButton(dialog), OkButton(dialog)))
dialog.setLayout(vbox)
if dialog.exec_():
self.wallet.set_label(self.wallet.last_account_id(), str(e.text()))
self.address_list.update()
self.tabs.setCurrentIndex(3)
self.update_new_account_menu()
def check_next_account(self):
if self.wallet.needs_next_account() and not self.checking_accounts:
self.checking_accounts = True
msg = _("All the accounts in your wallet have received "
"transactions. Electrum must check whether more "
"accounts exist; one will only be shown if "
"it has been used or you give it a name.")
self.show_message(msg, title=_("Check Accounts"))
self.create_next_account()
@protected
def create_next_account(self, password):
def on_done():
self.checking_accounts = False
self.update_new_account_menu()
task = partial(self.wallet.create_next_account, password)
self.wallet.thread.add(task, on_done=on_done)
def show_master_public_keys(self):
dialog = WindowModalDialog(self, "Master Public Keys")
mpk_dict = self.wallet.get_master_public_keys()
@@ -1741,7 +1664,7 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, PrintError):
if len(mpk_dict) > 1:
def label(key):
if isinstance(self.wallet, Multisig_Wallet):
is_mine = self.wallet.master_private_keys.has_key(key)
is_mine = False#self.wallet.master_private_keys.has_key(key)
mine_text = [_("cosigner"), _("self")]
return "%s (%s)" % (key, mine_text[is_mine])
return key
@@ -1759,19 +1682,19 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, PrintError):
@protected
def show_seed_dialog(self, password):
if self.wallet.use_encryption and password is None:
return # User cancelled password input
if self.wallet.has_password() and password is None:
# User cancelled password input
return
if not self.wallet.has_seed():
self.show_message(_('This wallet has no seed'))
return
try:
mnemonic = self.wallet.get_mnemonic(password)
except BaseException as e:
self.show_error(str(e))
return
from seed_dialog import SeedDialog
d = SeedDialog(self, mnemonic, self.wallet.has_imported_keys())
d = SeedDialog(self, mnemonic)
d.exec_()
@@ -1795,9 +1718,9 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, PrintError):
d.setMinimumSize(600, 200)
vbox = QVBoxLayout()
vbox.addWidget( QLabel(_("Address") + ': ' + address))
if isinstance(self.wallet, BIP32_RD_Wallet):
derivation = self.wallet.address_id(address)
vbox.addWidget(QLabel(_("Derivation") + ': ' + derivation))
#if isinstance(self.wallet, BIP32_RD_Wallet):
# derivation = self.wallet.address_id(address)
# vbox.addWidget(QLabel(_("Derivation") + ': ' + derivation))
vbox.addWidget(QLabel(_("Public key") + ':'))
keys_e = ShowQRTextEdit(text='\n'.join(pubkey_list))
keys_e.addCopyButton(self.app)
@@ -2045,7 +1968,6 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, PrintError):
if self.wallet.is_watching_only():
self.show_message(_("This is a watching-only wallet"))
return
try:
self.wallet.check_password(password)
except Exception as e:
@@ -2235,7 +2157,7 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, PrintError):
keys_e.setTabChangesFocus(True)
vbox.addWidget(keys_e)
addresses = self.wallet.get_unused_addresses(self.current_account)
addresses = self.wallet.get_unused_addresses(None)
h, address_e = address_field(addresses)
vbox.addLayout(h)
@@ -2271,19 +2193,16 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, PrintError):
@protected
def do_import_privkey(self, password):
if not self.wallet.has_imported_keys():
if not self.question('<b>'+_('Warning') +':\n</b><br/>'+ _('Imported keys are not recoverable from seed.') + ' ' \
+ _('If you ever need to restore your wallet from its seed, these keys will be lost.') + '<p>' \
+ _('Are you sure you understand what you are doing?'), title=_('Warning')):
return
if not self.wallet.keystore.can_import():
return
text = text_dialog(self, _('Import private keys'), _("Enter private keys")+':', _("Import"))
if not text: return
if not text:
return
text = str(text).split()
badkeys = []
addrlist = []
for key in text:
addr = self.wallet.import_key(key, password)
try:
addr = self.wallet.import_key(key, password)
except Exception as e:
@@ -2673,25 +2592,6 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, PrintError):
vbox.addLayout(Buttons(CloseButton(d)))
d.exec_()
def show_account_details(self, k):
account = self.wallet.accounts[k]
d = WindowModalDialog(self, _('Account Details'))
vbox = QVBoxLayout(d)
name = self.wallet.get_account_name(k)
label = QLabel('Name: ' + name)
vbox.addWidget(label)
vbox.addWidget(QLabel(_('Address type') + ': ' + account.get_type()))
vbox.addWidget(QLabel(_('Derivation') + ': ' + k))
vbox.addWidget(QLabel(_('Master Public Key:')))
text = QTextEdit()
text.setReadOnly(True)
text.setMaximumHeight(170)
vbox.addWidget(text)
mpk_text = '\n'.join(account.get_master_pubkeys())
text.setText(mpk_text)
vbox.addLayout(Buttons(CloseButton(d)))
d.exec_()
def bump_fee_dialog(self, tx):
is_relevant, is_mine, v, fee = self.wallet.get_wallet_delta(tx)
d = WindowModalDialog(self, _('Bump Fee'))

View File

@@ -94,7 +94,7 @@ class PasswordLayout(object):
m1 = _('New Password:') if kind == PW_NEW else _('Password:')
msgs = [m1, _('Confirm Password:')]
if wallet and wallet.use_encryption:
if wallet and wallet.has_password():
grid.addWidget(QLabel(_('Current Password:')), 0, 0)
grid.addWidget(self.pw, 0, 1)
lockfile = ":icons/lock.png"

View File

@@ -36,20 +36,19 @@ from util import MyTreeWidget, pr_tooltips, pr_icons
class RequestList(MyTreeWidget):
def __init__(self, parent):
MyTreeWidget.__init__(self, parent, self.create_menu, [_('Date'), _('Account'), _('Address'), '', _('Description'), _('Amount'), _('Status')], 4)
MyTreeWidget.__init__(self, parent, self.create_menu, [_('Date'), _('Address'), '', _('Description'), _('Amount'), _('Status')], 3)
self.currentItemChanged.connect(self.item_changed)
self.itemClicked.connect(self.item_changed)
self.setSortingEnabled(True)
self.setColumnWidth(0, 180)
self.hideColumn(1)
self.hideColumn(2)
def item_changed(self, item):
if item is None:
return
if not self.isItemSelected(item):
return
addr = str(item.text(2))
addr = str(item.text(1))
req = self.wallet.receive_requests[addr]
expires = age(req['time'] + req['exp']) if req.get('exp') else _('Never')
amount = req['amount']
@@ -72,13 +71,10 @@ class RequestList(MyTreeWidget):
self.parent.expires_label.hide()
self.parent.expires_combo.show()
# check if it is necessary to show the account
self.setColumnHidden(1, len(self.wallet.get_accounts()) == 1)
# update the receive address if necessary
current_address = self.parent.receive_address_e.text()
domain = self.wallet.get_account_addresses(self.parent.current_account, include_change=False)
addr = self.wallet.get_unused_address(self.parent.current_account)
domain = self.wallet.get_receiving_addresses()
addr = self.wallet.get_unused_address()
if not current_address in domain and addr:
self.parent.set_receive_address(addr)
self.parent.new_request_button.setEnabled(addr != current_address)
@@ -98,11 +94,10 @@ class RequestList(MyTreeWidget):
signature = req.get('sig')
requestor = req.get('name', '')
amount_str = self.parent.format_amount(amount) if amount else ""
account = ''
item = QTreeWidgetItem([date, account, address, '', message, amount_str, pr_tooltips.get(status,'')])
item = QTreeWidgetItem([date, address, '', message, amount_str, pr_tooltips.get(status,'')])
if signature is not None:
item.setIcon(3, QIcon(":icons/seal.png"))
item.setToolTip(3, 'signed by '+ requestor)
item.setIcon(2, QIcon(":icons/seal.png"))
item.setToolTip(2, 'signed by '+ requestor)
if status is not PR_UNKNOWN:
item.setIcon(6, QIcon(pr_icons.get(status)))
self.addTopLevelItem(item)

View File

@@ -39,19 +39,13 @@ def icon_filename(sid):
return ":icons/seed.png"
class SeedDialog(WindowModalDialog):
def __init__(self, parent, seed, imported_keys):
def __init__(self, parent, seed):
WindowModalDialog.__init__(self, parent, ('Electrum - ' + _('Seed')))
self.setMinimumWidth(400)
vbox = QVBoxLayout(self)
vbox.addLayout(SeedWarningLayout(seed).layout())
if imported_keys:
warning = ("<b>" + _("WARNING") + ":</b> " +
_("Your wallet contains imported keys. These keys "
"cannot be recovered from your seed.") + "</b><p>")
vbox.addWidget(WWLabel(warning))
vbox.addLayout(Buttons(CloseButton(self)))
class SeedLayoutBase(object):
def _seed_layout(self, seed=None, title=None, sid=None):
logo = QLabel()