URI: 
       tqt.py - electrum - Electrum Bitcoin wallet
  HTML git clone https://git.parazyd.org/electrum
   DIR Log
   DIR Files
   DIR Refs
   DIR Submodules
       ---
       tqt.py (23765B)
       ---
            1 from functools import partial
            2 import threading
            3 
            4 from PyQt5.QtCore import Qt, QEventLoop, pyqtSignal, QRegExp
            5 from PyQt5.QtGui import QRegExpValidator
            6 from PyQt5.QtWidgets import (QVBoxLayout, QLabel, QGridLayout, QPushButton,
            7                              QHBoxLayout, QButtonGroup, QGroupBox, QDialog,
            8                              QTextEdit, QLineEdit, QRadioButton, QCheckBox, QWidget,
            9                              QMessageBox, QFileDialog, QSlider, QTabWidget)
           10 
           11 from electrum.gui.qt.util import (WindowModalDialog, WWLabel, Buttons, CancelButton,
           12                                   OkButton, CloseButton)
           13 from electrum.i18n import _
           14 from electrum.plugin import hook
           15 from electrum.util import bh2u
           16 
           17 from ..hw_wallet.qt import QtHandlerBase, QtPluginBase
           18 from ..hw_wallet.plugin import only_hook_if_libraries_available
           19 from .keepkey import KeepKeyPlugin, TIM_NEW, TIM_RECOVER, TIM_MNEMONIC
           20 
           21 
           22 PASSPHRASE_HELP_SHORT =_(
           23     "Passphrases allow you to access new wallets, each "
           24     "hidden behind a particular case-sensitive passphrase.")
           25 PASSPHRASE_HELP = PASSPHRASE_HELP_SHORT + "  " + _(
           26     "You need to create a separate Electrum wallet for each passphrase "
           27     "you use as they each generate different addresses.  Changing "
           28     "your passphrase does not lose other wallets, each is still "
           29     "accessible behind its own passphrase.")
           30 RECOMMEND_PIN = _(
           31     "You should enable PIN protection.  Your PIN is the only protection "
           32     "for your bitcoins if your device is lost or stolen.")
           33 PASSPHRASE_NOT_PIN = _(
           34     "If you forget a passphrase you will be unable to access any "
           35     "bitcoins in the wallet behind it.  A passphrase is not a PIN. "
           36     "Only change this if you are sure you understand it.")
           37 CHARACTER_RECOVERY = (
           38     "Use the recovery cipher shown on your device to input your seed words.  "
           39     "The cipher changes with every keypress.\n"
           40     "After at most 4 letters the device will auto-complete a word.\n"
           41     "Press SPACE or the Accept Word button to accept the device's auto-"
           42     "completed word and advance to the next one.\n"
           43     "Press BACKSPACE to go back a character or word.\n"
           44     "Press ENTER or the Seed Entered button once the last word in your "
           45     "seed is auto-completed.")
           46 
           47 class CharacterButton(QPushButton):
           48     def __init__(self, text=None):
           49         QPushButton.__init__(self, text)
           50 
           51     def keyPressEvent(self, event):
           52         event.setAccepted(False)   # Pass through Enter and Space keys
           53 
           54 
           55 class CharacterDialog(WindowModalDialog):
           56 
           57     def __init__(self, parent):
           58         super(CharacterDialog, self).__init__(parent)
           59         self.setWindowTitle(_("KeepKey Seed Recovery"))
           60         self.character_pos = 0
           61         self.word_pos = 0
           62         self.loop = QEventLoop()
           63         self.word_help = QLabel()
           64         self.char_buttons = []
           65 
           66         vbox = QVBoxLayout(self)
           67         vbox.addWidget(WWLabel(CHARACTER_RECOVERY))
           68         hbox = QHBoxLayout()
           69         hbox.addWidget(self.word_help)
           70         for i in range(4):
           71             char_button = CharacterButton('*')
           72             char_button.setMaximumWidth(36)
           73             self.char_buttons.append(char_button)
           74             hbox.addWidget(char_button)
           75         self.accept_button = CharacterButton(_("Accept Word"))
           76         self.accept_button.clicked.connect(partial(self.process_key, 32))
           77         self.rejected.connect(partial(self.loop.exit, 1))
           78         hbox.addWidget(self.accept_button)
           79         hbox.addStretch(1)
           80         vbox.addLayout(hbox)
           81 
           82         self.finished_button = QPushButton(_("Seed Entered"))
           83         self.cancel_button = QPushButton(_("Cancel"))
           84         self.finished_button.clicked.connect(partial(self.process_key,
           85                                                      Qt.Key_Return))
           86         self.cancel_button.clicked.connect(self.rejected)
           87         buttons = Buttons(self.finished_button, self.cancel_button)
           88         vbox.addSpacing(40)
           89         vbox.addLayout(buttons)
           90         self.refresh()
           91         self.show()
           92 
           93     def refresh(self):
           94         self.word_help.setText("Enter seed word %2d:" % (self.word_pos + 1))
           95         self.accept_button.setEnabled(self.character_pos >= 3)
           96         self.finished_button.setEnabled((self.word_pos in (11, 17, 23)
           97                                          and self.character_pos >= 3))
           98         for n, button in enumerate(self.char_buttons):
           99             button.setEnabled(n == self.character_pos)
          100             if n == self.character_pos:
          101                 button.setFocus()
          102 
          103     def is_valid_alpha_space(self, key):
          104         # Auto-completion requires at least 3 characters
          105         if key == ord(' ') and self.character_pos >= 3:
          106             return True
          107         # Firmware aborts protocol if the 5th character is non-space
          108         if self.character_pos >= 4:
          109             return False
          110         return (key >= ord('a') and key <= ord('z')
          111                 or (key >= ord('A') and key <= ord('Z')))
          112 
          113     def process_key(self, key):
          114         self.data = None
          115         if key == Qt.Key_Return and self.finished_button.isEnabled():
          116             self.data = {'done': True}
          117         elif key == Qt.Key_Backspace and (self.word_pos or self.character_pos):
          118             self.data = {'delete': True}
          119         elif self.is_valid_alpha_space(key):
          120             self.data = {'character': chr(key).lower()}
          121         if self.data:
          122             self.loop.exit(0)
          123 
          124     def keyPressEvent(self, event):
          125         self.process_key(event.key())
          126         if not self.data:
          127             QDialog.keyPressEvent(self, event)
          128 
          129     def get_char(self, word_pos, character_pos):
          130         self.word_pos = word_pos
          131         self.character_pos = character_pos
          132         self.refresh()
          133         if self.loop.exec_():
          134             self.data = None  # User cancelled
          135 
          136 
          137 class QtHandler(QtHandlerBase):
          138 
          139     char_signal = pyqtSignal(object)
          140     pin_signal = pyqtSignal(object, object)
          141     close_char_dialog_signal = pyqtSignal()
          142 
          143     def __init__(self, win, pin_matrix_widget_class, device):
          144         super(QtHandler, self).__init__(win, device)
          145         self.char_signal.connect(self.update_character_dialog)
          146         self.pin_signal.connect(self.pin_dialog)
          147         self.close_char_dialog_signal.connect(self._close_char_dialog)
          148         self.pin_matrix_widget_class = pin_matrix_widget_class
          149         self.character_dialog = None
          150 
          151     def get_char(self, msg):
          152         self.done.clear()
          153         self.char_signal.emit(msg)
          154         self.done.wait()
          155         data = self.character_dialog.data
          156         if not data or 'done' in data:
          157             self.close_char_dialog_signal.emit()
          158         return data
          159 
          160     def _close_char_dialog(self):
          161         if self.character_dialog:
          162             self.character_dialog.accept()
          163             self.character_dialog = None
          164 
          165     def get_pin(self, msg, *, show_strength=True):
          166         self.done.clear()
          167         self.pin_signal.emit(msg, show_strength)
          168         self.done.wait()
          169         return self.response
          170 
          171     def pin_dialog(self, msg, show_strength):
          172         # Needed e.g. when resetting a device
          173         self.clear_dialog()
          174         dialog = WindowModalDialog(self.top_level_window(), _("Enter PIN"))
          175         matrix = self.pin_matrix_widget_class(show_strength)
          176         vbox = QVBoxLayout()
          177         vbox.addWidget(QLabel(msg))
          178         vbox.addWidget(matrix)
          179         vbox.addLayout(Buttons(CancelButton(dialog), OkButton(dialog)))
          180         dialog.setLayout(vbox)
          181         dialog.exec_()
          182         self.response = str(matrix.get_value())
          183         self.done.set()
          184 
          185     def update_character_dialog(self, msg):
          186         if not self.character_dialog:
          187             self.character_dialog = CharacterDialog(self.top_level_window())
          188         self.character_dialog.get_char(msg.word_pos, msg.character_pos)
          189         self.done.set()
          190 
          191 
          192 
          193 class QtPlugin(QtPluginBase):
          194     # Derived classes must provide the following class-static variables:
          195     #   icon_file
          196     #   pin_matrix_widget_class
          197 
          198     @only_hook_if_libraries_available
          199     @hook
          200     def receive_menu(self, menu, addrs, wallet):
          201         if len(addrs) != 1:
          202             return
          203         for keystore in wallet.get_keystores():
          204             if type(keystore) == self.keystore_class:
          205                 def show_address():
          206                     keystore.thread.add(partial(self.show_address, wallet, addrs[0], keystore))
          207                 device_name = "{} ({})".format(self.device, keystore.label)
          208                 menu.addAction(_("Show on {}").format(device_name), show_address)
          209 
          210     def show_settings_dialog(self, window, keystore):
          211         def connect():
          212             device_id = self.choose_device(window, keystore)
          213             return device_id
          214         def show_dialog(device_id):
          215             if device_id:
          216                 SettingsDialog(window, self, keystore, device_id).exec_()
          217         keystore.thread.add(connect, on_success=show_dialog)
          218 
          219     def request_trezor_init_settings(self, wizard, method, device):
          220         vbox = QVBoxLayout()
          221         next_enabled = True
          222         label = QLabel(_("Enter a label to name your device:"))
          223         name = QLineEdit()
          224         hl = QHBoxLayout()
          225         hl.addWidget(label)
          226         hl.addWidget(name)
          227         hl.addStretch(1)
          228         vbox.addLayout(hl)
          229 
          230         def clean_text(widget):
          231             text = widget.toPlainText().strip()
          232             return ' '.join(text.split())
          233 
          234         if method in [TIM_NEW, TIM_RECOVER]:
          235             gb = QGroupBox()
          236             hbox1 = QHBoxLayout()
          237             gb.setLayout(hbox1)
          238             # KeepKey recovery doesn't need a word count
          239             if method == TIM_NEW:
          240                 vbox.addWidget(gb)
          241             gb.setTitle(_("Select your seed length:"))
          242             bg = QButtonGroup()
          243             for i, count in enumerate([12, 18, 24]):
          244                 rb = QRadioButton(gb)
          245                 rb.setText(_("{} words").format(count))
          246                 bg.addButton(rb)
          247                 bg.setId(rb, i)
          248                 hbox1.addWidget(rb)
          249                 rb.setChecked(True)
          250             cb_pin = QCheckBox(_('Enable PIN protection'))
          251             cb_pin.setChecked(True)
          252         else:
          253             text = QTextEdit()
          254             text.setMaximumHeight(60)
          255             if method == TIM_MNEMONIC:
          256                 msg = _("Enter your BIP39 mnemonic:")
          257             else:
          258                 msg = _("Enter the master private key beginning with xprv:")
          259                 def set_enabled():
          260                     from electrum.bip32 import is_xprv
          261                     wizard.next_button.setEnabled(is_xprv(clean_text(text)))
          262                 text.textChanged.connect(set_enabled)
          263                 next_enabled = False
          264 
          265             vbox.addWidget(QLabel(msg))
          266             vbox.addWidget(text)
          267             pin = QLineEdit()
          268             pin.setValidator(QRegExpValidator(QRegExp('[1-9]{0,9}')))
          269             pin.setMaximumWidth(100)
          270             hbox_pin = QHBoxLayout()
          271             hbox_pin.addWidget(QLabel(_("Enter your PIN (digits 1-9):")))
          272             hbox_pin.addWidget(pin)
          273             hbox_pin.addStretch(1)
          274 
          275         if method in [TIM_NEW, TIM_RECOVER]:
          276             vbox.addWidget(WWLabel(RECOMMEND_PIN))
          277             vbox.addWidget(cb_pin)
          278         else:
          279             vbox.addLayout(hbox_pin)
          280 
          281         passphrase_msg = WWLabel(PASSPHRASE_HELP_SHORT)
          282         passphrase_warning = WWLabel(PASSPHRASE_NOT_PIN)
          283         passphrase_warning.setStyleSheet("color: red")
          284         cb_phrase = QCheckBox(_('Enable passphrases'))
          285         cb_phrase.setChecked(False)
          286         vbox.addWidget(passphrase_msg)
          287         vbox.addWidget(passphrase_warning)
          288         vbox.addWidget(cb_phrase)
          289 
          290         wizard.exec_layout(vbox, next_enabled=next_enabled)
          291 
          292         if method in [TIM_NEW, TIM_RECOVER]:
          293             item = bg.checkedId()
          294             pin = cb_pin.isChecked()
          295         else:
          296             item = ' '.join(str(clean_text(text)).split())
          297             pin = str(pin.text())
          298 
          299         return (item, name.text(), pin, cb_phrase.isChecked())
          300 
          301 
          302 class Plugin(KeepKeyPlugin, QtPlugin):
          303     icon_paired = "keepkey.png"
          304     icon_unpaired = "keepkey_unpaired.png"
          305 
          306     def create_handler(self, window):
          307         return QtHandler(window, self.pin_matrix_widget_class(), self.device)
          308 
          309     @classmethod
          310     def pin_matrix_widget_class(self):
          311         from keepkeylib.qt.pinmatrix import PinMatrixWidget
          312         return PinMatrixWidget
          313 
          314 
          315 class SettingsDialog(WindowModalDialog):
          316     '''This dialog doesn't require a device be paired with a wallet.
          317     We want users to be able to wipe a device even if they've forgotten
          318     their PIN.'''
          319 
          320     def __init__(self, window, plugin, keystore, device_id):
          321         title = _("{} Settings").format(plugin.device)
          322         super(SettingsDialog, self).__init__(window, title)
          323         self.setMaximumWidth(540)
          324 
          325         devmgr = plugin.device_manager()
          326         config = devmgr.config
          327         handler = keystore.handler
          328         thread = keystore.thread
          329 
          330         def invoke_client(method, *args, **kw_args):
          331             unpair_after = kw_args.pop('unpair_after', False)
          332 
          333             def task():
          334                 client = devmgr.client_by_id(device_id)
          335                 if not client:
          336                     raise RuntimeError("Device not connected")
          337                 if method:
          338                     getattr(client, method)(*args, **kw_args)
          339                 if unpair_after:
          340                     devmgr.unpair_id(device_id)
          341                 return client.features
          342 
          343             thread.add(task, on_success=update)
          344 
          345         def update(features):
          346             self.features = features
          347             set_label_enabled()
          348             bl_hash = bh2u(features.bootloader_hash)
          349             bl_hash = "\n".join([bl_hash[:32], bl_hash[32:]])
          350             noyes = [_("No"), _("Yes")]
          351             endis = [_("Enable Passphrases"), _("Disable Passphrases")]
          352             disen = [_("Disabled"), _("Enabled")]
          353             setchange = [_("Set a PIN"), _("Change PIN")]
          354 
          355             version = "%d.%d.%d" % (features.major_version,
          356                                     features.minor_version,
          357                                     features.patch_version)
          358             coins = ", ".join(coin.coin_name for coin in features.coins)
          359 
          360             device_label.setText(features.label)
          361             pin_set_label.setText(noyes[features.pin_protection])
          362             passphrases_label.setText(disen[features.passphrase_protection])
          363             bl_hash_label.setText(bl_hash)
          364             label_edit.setText(features.label)
          365             device_id_label.setText(features.device_id)
          366             initialized_label.setText(noyes[features.initialized])
          367             version_label.setText(version)
          368             coins_label.setText(coins)
          369             clear_pin_button.setVisible(features.pin_protection)
          370             clear_pin_warning.setVisible(features.pin_protection)
          371             pin_button.setText(setchange[features.pin_protection])
          372             pin_msg.setVisible(not features.pin_protection)
          373             passphrase_button.setText(endis[features.passphrase_protection])
          374             language_label.setText(features.language)
          375 
          376         def set_label_enabled():
          377             label_apply.setEnabled(label_edit.text() != self.features.label)
          378 
          379         def rename():
          380             invoke_client('change_label', label_edit.text())
          381 
          382         def toggle_passphrase():
          383             title = _("Confirm Toggle Passphrase Protection")
          384             currently_enabled = self.features.passphrase_protection
          385             if currently_enabled:
          386                 msg = _("After disabling passphrases, you can only pair this "
          387                         "Electrum wallet if it had an empty passphrase.  "
          388                         "If its passphrase was not empty, you will need to "
          389                         "create a new wallet with the install wizard.  You "
          390                         "can use this wallet again at any time by re-enabling "
          391                         "passphrases and entering its passphrase.")
          392             else:
          393                 msg = _("Your current Electrum wallet can only be used with "
          394                         "an empty passphrase.  You must create a separate "
          395                         "wallet with the install wizard for other passphrases "
          396                         "as each one generates a new set of addresses.")
          397             msg += "\n\n" + _("Are you sure you want to proceed?")
          398             if not self.question(msg, title=title):
          399                 return
          400             invoke_client('toggle_passphrase', unpair_after=currently_enabled)
          401 
          402         def set_pin():
          403             invoke_client('set_pin', remove=False)
          404 
          405         def clear_pin():
          406             invoke_client('set_pin', remove=True)
          407 
          408         def wipe_device():
          409             wallet = window.wallet
          410             if wallet and sum(wallet.get_balance()):
          411                 title = _("Confirm Device Wipe")
          412                 msg = _("Are you SURE you want to wipe the device?\n"
          413                         "Your wallet still has bitcoins in it!")
          414                 if not self.question(msg, title=title,
          415                                      icon=QMessageBox.Critical):
          416                     return
          417             invoke_client('wipe_device', unpair_after=True)
          418 
          419         def slider_moved():
          420             mins = timeout_slider.sliderPosition()
          421             timeout_minutes.setText(_("{:2d} minutes").format(mins))
          422 
          423         def slider_released():
          424             config.set_session_timeout(timeout_slider.sliderPosition() * 60)
          425 
          426         # Information tab
          427         info_tab = QWidget()
          428         info_layout = QVBoxLayout(info_tab)
          429         info_glayout = QGridLayout()
          430         info_glayout.setColumnStretch(2, 1)
          431         device_label = QLabel()
          432         pin_set_label = QLabel()
          433         passphrases_label = QLabel()
          434         version_label = QLabel()
          435         device_id_label = QLabel()
          436         bl_hash_label = QLabel()
          437         bl_hash_label.setWordWrap(True)
          438         coins_label = QLabel()
          439         coins_label.setWordWrap(True)
          440         language_label = QLabel()
          441         initialized_label = QLabel()
          442         rows = [
          443             (_("Device Label"), device_label),
          444             (_("PIN set"), pin_set_label),
          445             (_("Passphrases"), passphrases_label),
          446             (_("Firmware Version"), version_label),
          447             (_("Device ID"), device_id_label),
          448             (_("Bootloader Hash"), bl_hash_label),
          449             (_("Supported Coins"), coins_label),
          450             (_("Language"), language_label),
          451             (_("Initialized"), initialized_label),
          452         ]
          453         for row_num, (label, widget) in enumerate(rows):
          454             info_glayout.addWidget(QLabel(label), row_num, 0)
          455             info_glayout.addWidget(widget, row_num, 1)
          456         info_layout.addLayout(info_glayout)
          457 
          458         # Settings tab
          459         settings_tab = QWidget()
          460         settings_layout = QVBoxLayout(settings_tab)
          461         settings_glayout = QGridLayout()
          462 
          463         # Settings tab - Label
          464         label_msg = QLabel(_("Name this {}.  If you have multiple devices "
          465                              "their labels help distinguish them.")
          466                            .format(plugin.device))
          467         label_msg.setWordWrap(True)
          468         label_label = QLabel(_("Device Label"))
          469         label_edit = QLineEdit()
          470         label_edit.setMinimumWidth(150)
          471         label_edit.setMaxLength(plugin.MAX_LABEL_LEN)
          472         label_apply = QPushButton(_("Apply"))
          473         label_apply.clicked.connect(rename)
          474         label_edit.textChanged.connect(set_label_enabled)
          475         settings_glayout.addWidget(label_label, 0, 0)
          476         settings_glayout.addWidget(label_edit, 0, 1, 1, 2)
          477         settings_glayout.addWidget(label_apply, 0, 3)
          478         settings_glayout.addWidget(label_msg, 1, 1, 1, -1)
          479 
          480         # Settings tab - PIN
          481         pin_label = QLabel(_("PIN Protection"))
          482         pin_button = QPushButton()
          483         pin_button.clicked.connect(set_pin)
          484         settings_glayout.addWidget(pin_label, 2, 0)
          485         settings_glayout.addWidget(pin_button, 2, 1)
          486         pin_msg = QLabel(_("PIN protection is strongly recommended.  "
          487                            "A PIN is your only protection against someone "
          488                            "stealing your bitcoins if they obtain physical "
          489                            "access to your {}.").format(plugin.device))
          490         pin_msg.setWordWrap(True)
          491         pin_msg.setStyleSheet("color: red")
          492         settings_glayout.addWidget(pin_msg, 3, 1, 1, -1)
          493 
          494         # Settings tab - Session Timeout
          495         timeout_label = QLabel(_("Session Timeout"))
          496         timeout_minutes = QLabel()
          497         timeout_slider = QSlider(Qt.Horizontal)
          498         timeout_slider.setRange(1, 60)
          499         timeout_slider.setSingleStep(1)
          500         timeout_slider.setTickInterval(5)
          501         timeout_slider.setTickPosition(QSlider.TicksBelow)
          502         timeout_slider.setTracking(True)
          503         timeout_msg = QLabel(
          504             _("Clear the session after the specified period "
          505               "of inactivity.  Once a session has timed out, "
          506               "your PIN and passphrase (if enabled) must be "
          507               "re-entered to use the device."))
          508         timeout_msg.setWordWrap(True)
          509         timeout_slider.setSliderPosition(config.get_session_timeout() // 60)
          510         slider_moved()
          511         timeout_slider.valueChanged.connect(slider_moved)
          512         timeout_slider.sliderReleased.connect(slider_released)
          513         settings_glayout.addWidget(timeout_label, 6, 0)
          514         settings_glayout.addWidget(timeout_slider, 6, 1, 1, 3)
          515         settings_glayout.addWidget(timeout_minutes, 6, 4)
          516         settings_glayout.addWidget(timeout_msg, 7, 1, 1, -1)
          517         settings_layout.addLayout(settings_glayout)
          518         settings_layout.addStretch(1)
          519 
          520         # Advanced tab
          521         advanced_tab = QWidget()
          522         advanced_layout = QVBoxLayout(advanced_tab)
          523         advanced_glayout = QGridLayout()
          524 
          525         # Advanced tab - clear PIN
          526         clear_pin_button = QPushButton(_("Disable PIN"))
          527         clear_pin_button.clicked.connect(clear_pin)
          528         clear_pin_warning = QLabel(
          529             _("If you disable your PIN, anyone with physical access to your "
          530               "{} device can spend your bitcoins.").format(plugin.device))
          531         clear_pin_warning.setWordWrap(True)
          532         clear_pin_warning.setStyleSheet("color: red")
          533         advanced_glayout.addWidget(clear_pin_button, 0, 2)
          534         advanced_glayout.addWidget(clear_pin_warning, 1, 0, 1, 5)
          535 
          536         # Advanced tab - toggle passphrase protection
          537         passphrase_button = QPushButton()
          538         passphrase_button.clicked.connect(toggle_passphrase)
          539         passphrase_msg = WWLabel(PASSPHRASE_HELP)
          540         passphrase_warning = WWLabel(PASSPHRASE_NOT_PIN)
          541         passphrase_warning.setStyleSheet("color: red")
          542         advanced_glayout.addWidget(passphrase_button, 3, 2)
          543         advanced_glayout.addWidget(passphrase_msg, 4, 0, 1, 5)
          544         advanced_glayout.addWidget(passphrase_warning, 5, 0, 1, 5)
          545 
          546         # Advanced tab - wipe device
          547         wipe_device_button = QPushButton(_("Wipe Device"))
          548         wipe_device_button.clicked.connect(wipe_device)
          549         wipe_device_msg = QLabel(
          550             _("Wipe the device, removing all data from it.  The firmware "
          551               "is left unchanged."))
          552         wipe_device_msg.setWordWrap(True)
          553         wipe_device_warning = QLabel(
          554             _("Only wipe a device if you have the recovery seed written down "
          555               "and the device wallet(s) are empty, otherwise the bitcoins "
          556               "will be lost forever."))
          557         wipe_device_warning.setWordWrap(True)
          558         wipe_device_warning.setStyleSheet("color: red")
          559         advanced_glayout.addWidget(wipe_device_button, 6, 2)
          560         advanced_glayout.addWidget(wipe_device_msg, 7, 0, 1, 5)
          561         advanced_glayout.addWidget(wipe_device_warning, 8, 0, 1, 5)
          562         advanced_layout.addLayout(advanced_glayout)
          563         advanced_layout.addStretch(1)
          564 
          565         tabs = QTabWidget(self)
          566         tabs.addTab(info_tab, _("Information"))
          567         tabs.addTab(settings_tab, _("Settings"))
          568         tabs.addTab(advanced_tab, _("Advanced"))
          569         dialog_vbox = QVBoxLayout(self)
          570         dialog_vbox.addWidget(tabs)
          571         dialog_vbox.addLayout(Buttons(CloseButton(self)))
          572 
          573         # Update information
          574         invoke_client(None)