text: #!/usr/bin/env python
  #
  # Electrum - lightweight Bitcoin client
  # Copyright (C) 2013 ecdsa@github
  #
  # Permission is hereby granted, free of charge, to any person
  # obtaining a copy of this software and associated documentation files
  # (the "Software"), to deal in the Software without restriction,
  # including without limitation the rights to use, copy, modify, merge,
  # publish, distribute, sublicense, and/or sell copies of the Software,
  # and to permit persons to whom the Software is furnished to do so,
  # subject to the following conditions:
  #
  # The above copyright notice and this permission notice shall be
  # included in all copies or substantial portions of the Software.
  #
  # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
  # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
  # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
  # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
  # BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
  # ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
  # CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
  # SOFTWARE.
  
  from typing import TYPE_CHECKING
  
  from PyQt5.QtCore import Qt
  from PyQt5.QtGui import QPixmap
  from PyQt5.QtWidgets import (QVBoxLayout, QCheckBox, QHBoxLayout, QLineEdit,
                               QLabel, QCompleter, QDialog, QStyledItemDelegate,
                               QScrollArea, QWidget, QPushButton)
  
  from electrum_grs.i18n import _
  from electrum_grs.mnemonic import Mnemonic, seed_type
  from electrum_grs import old_mnemonic
  from electrum_grs import slip39
  
  from .util import (Buttons, OkButton, WWLabel, ButtonsTextEdit, icon_path,
                     EnterButton, CloseButton, WindowModalDialog, ColorScheme,
                     ChoicesLayout)
  from .qrtextedit import ShowQRTextEdit, ScanQRTextEdit
  from .completion_text_edit import CompletionTextEdit
  
  if TYPE_CHECKING:
      from electrum_grs.simple_config import SimpleConfig
  
  
  def seed_warning_msg(seed):
      return ''.join([
          "<p>",
          _("Please save these {0} words on paper (order is important). "),
          _("This seed will allow you to recover your wallet in case "
            "of computer failure."),
          "</p>",
          "<b>" + _("WARNING") + ":</b>",
          "<ul>",
          "<li>" + _("Never disclose your seed.") + "</li>",
          "<li>" + _("Never type it on a website.") + "</li>",
          "<li>" + _("Do not store it electronically.") + "</li>",
          "</ul>"
      ]).format(len(seed.split()))
  
  
  class SeedLayout(QVBoxLayout):
  
      def seed_options(self):
          dialog = QDialog()
          vbox = QVBoxLayout(dialog)
  
          seed_types = [
              (value, title) for value, title in (
                  ('electrum', _('Electrum')),
                  ('bip39', _('BIP39 seed')),
                  ('slip39', _('SLIP39 seed')),
              )
              if value in self.options or value == 'electrum'
          ]
          seed_type_values = [t[0] for t in seed_types]
  
          if 'ext' in self.options:
              cb_ext = QCheckBox(_('Extend this seed with custom words'))
              cb_ext.setChecked(self.is_ext)
              vbox.addWidget(cb_ext)
          if len(seed_types) >= 2:
              def f(choices_layout):
                  self.seed_type = seed_type_values[choices_layout.selected_index()]
                  self.is_seed = (lambda x: bool(x)) if self.seed_type != 'electrum' else self.saved_is_seed
                  self.slip39_current_mnemonic_invalid = None
                  self.seed_status.setText('')
                  self.on_edit()
                  if self.seed_type == 'bip39':
                      msg = ' '.join([
                          '<b>' + _('Warning') + ':</b>  ',
                          _('BIP39 seeds can be imported in Electrum-GRS, so that users can access funds locked in other wallets.'),
                          _('However, we do not generate BIP39 seeds, because they do not meet our safety standard.'),
                          _('BIP39 seeds do not include a version number, which compromises compatibility with future software.'),
                          _('We do not guarantee that BIP39 imports will always be supported in Electrum-GRS.'),
                      ])
                  elif self.seed_type == 'slip39':
                      msg = ' '.join([
                          '<b>' + _('Warning') + ':</b>  ',
                          _('SLIP39 seeds can be imported in Electrum, so that users can access funds locked in other wallets.'),
                          _('However, we do not generate SLIP39 seeds.'),
                      ])
                  else:
                      msg = ''
                  self.update_share_buttons()
                  self.initialize_completer()
                  self.seed_warning.setText(msg)
  
              checked_index = seed_type_values.index(self.seed_type)
              titles = [t[1] for t in seed_types]
              clayout = ChoicesLayout(_('Seed type'), titles, on_clicked=f, checked_index=checked_index)
              vbox.addLayout(clayout.layout())
  
          vbox.addLayout(Buttons(OkButton(dialog)))
          if not dialog.exec_():
              return None
          self.is_ext = cb_ext.isChecked() if 'ext' in self.options else False
          self.seed_type = seed_type_values[clayout.selected_index()] if len(seed_types) >= 2 else 'electrum'
  
      def __init__(
              self,
              seed=None,
              title=None,
              icon=True,
              msg=None,
              options=None,
              is_seed=None,
              passphrase=None,
              parent=None,
              for_seed_words=True,
              *,
              config: 'SimpleConfig',
      ):
          QVBoxLayout.__init__(self)
          self.parent = parent
          self.options = options
          self.config = config
          self.seed_type = 'electrum'
          if title:
              self.addWidget(WWLabel(title))
          if seed:  # "read only", we already have the text
              if for_seed_words:
                  self.seed_e = ButtonsTextEdit()
              else:  # e.g. xpub
                  self.seed_e = ShowQRTextEdit(config=self.config)
              self.seed_e.setReadOnly(True)
              self.seed_e.setText(seed)
          else:  # we expect user to enter text
              assert for_seed_words
              self.seed_e = CompletionTextEdit()
              self.seed_e.setTabChangesFocus(False)  # so that tab auto-completes
              self.is_seed = is_seed
              self.saved_is_seed = self.is_seed
              self.seed_e.textChanged.connect(self.on_edit)
              self.initialize_completer()
  
          self.seed_e.setMaximumHeight(75)
          hbox = QHBoxLayout()
          if icon:
              logo = QLabel()
              logo.setPixmap(QPixmap(icon_path("seed.png"))
                             .scaledToWidth(64, mode=Qt.SmoothTransformation))
              logo.setMaximumWidth(60)
              hbox.addWidget(logo)
          hbox.addWidget(self.seed_e)
          self.addLayout(hbox)
          hbox = QHBoxLayout()
          hbox.addStretch(1)
          self.seed_type_label = QLabel('')
          hbox.addWidget(self.seed_type_label)
  
          # options
          self.is_ext = False
          if options:
              opt_button = EnterButton(_('Options'), self.seed_options)
              hbox.addWidget(opt_button)
              self.addLayout(hbox)
          if passphrase:
              hbox = QHBoxLayout()
              passphrase_e = QLineEdit()
              passphrase_e.setText(passphrase)
              passphrase_e.setReadOnly(True)
              hbox.addWidget(QLabel(_("Your seed extension is") + ':'))
              hbox.addWidget(passphrase_e)
              self.addLayout(hbox)
  
          # slip39 shares
          self.slip39_mnemonic_index = 0
          self.slip39_mnemonics = [""]
          self.slip39_seed = None
          self.slip39_current_mnemonic_invalid = None
          hbox = QHBoxLayout()
          hbox.addStretch(1)
          self.prev_share_btn = QPushButton(_("Previous share"))
          self.prev_share_btn.clicked.connect(self.on_prev_share)
          hbox.addWidget(self.prev_share_btn)
          self.next_share_btn = QPushButton(_("Next share"))
          self.next_share_btn.clicked.connect(self.on_next_share)
          hbox.addWidget(self.next_share_btn)
          self.update_share_buttons()
          self.addLayout(hbox)
  
          self.addStretch(1)
          self.seed_status = WWLabel('')
          self.addWidget(self.seed_status)
          self.seed_warning = WWLabel('')
          if msg:
              self.seed_warning.setText(seed_warning_msg(seed))
          self.addWidget(self.seed_warning)
  
      def initialize_completer(self):
          if self.seed_type != 'slip39':
              bip39_english_list = Mnemonic('en').wordlist
              old_list = old_mnemonic.wordlist
              only_old_list = set(old_list) - set(bip39_english_list)
              self.wordlist = list(bip39_english_list) + list(only_old_list)  # concat both lists
              self.wordlist.sort()
  
              class CompleterDelegate(QStyledItemDelegate):
                  def initStyleOption(self, option, index):
                      super().initStyleOption(option, index)
                      # Some people complained that due to merging the two word lists,
                      # it is difficult to restore from a metal backup, as they planned
                      # to rely on the "4 letter prefixes are unique in bip39 word list" property.
                      # So we color words that are only in old list.
                      if option.text in only_old_list:
                          # yellow bg looks ~ok on both light/dark theme, regardless if (un)selected
                          option.backgroundBrush = ColorScheme.YELLOW.as_color(background=True)
  
              delegate = CompleterDelegate(self.seed_e)
          else:
              self.wordlist = list(slip39.get_wordlist())
              delegate = None
  
          self.completer = QCompleter(self.wordlist)
          if delegate:
              self.completer.popup().setItemDelegate(delegate)
          self.seed_e.set_completer(self.completer)
  
      def get_seed_words(self):
          return self.seed_e.text().split()
  
      def get_seed(self):
          if self.seed_type != 'slip39':
              return ' '.join(self.get_seed_words())
          else:
              return self.slip39_seed
  
      def on_edit(self):
          s = ' '.join(self.get_seed_words())
          b = self.is_seed(s)
          if self.seed_type == 'bip39':
              from electrum_grs.keystore import bip39_is_checksum_valid
              is_checksum, is_wordlist = bip39_is_checksum_valid(s)
              status = ('checksum: ' + ('ok' if is_checksum else 'failed')) if is_wordlist else 'unknown wordlist'
              label = 'BIP39' + ' (%s)'%status
          elif self.seed_type == 'slip39':
              self.slip39_mnemonics[self.slip39_mnemonic_index] = s
              try:
                  slip39.decode_mnemonic(s)
              except slip39.Slip39Error as e:
                  share_status = str(e)
                  current_mnemonic_invalid = True
              else:
                  share_status = _('Valid.')
                  current_mnemonic_invalid = False
  
              label = _('SLIP39 share') + ' #%d: %s' % (self.slip39_mnemonic_index + 1, share_status)
  
              # No need to process mnemonics if the current mnemonic remains invalid after editing.
              if not (self.slip39_current_mnemonic_invalid and current_mnemonic_invalid):
                  self.slip39_seed, seed_status = slip39.process_mnemonics(self.slip39_mnemonics)
                  self.seed_status.setText(seed_status)
              self.slip39_current_mnemonic_invalid = current_mnemonic_invalid
  
              b = self.slip39_seed is not None
              self.update_share_buttons()
          else:
              t = seed_type(s)
              label = _('Seed Type') + ': ' + t if t else ''
  
          self.seed_type_label.setText(label)
          self.parent.next_button.setEnabled(b)
  
          # disable suggestions if user already typed an unknown word
          for word in self.get_seed_words()[:-1]:
              if word not in self.wordlist:
                  self.seed_e.disable_suggestions()
                  return
          self.seed_e.enable_suggestions()
  
      def update_share_buttons(self):
          if self.seed_type != 'slip39':
              self.prev_share_btn.hide()
              self.next_share_btn.hide()
              return
  
          finished = self.slip39_seed is not None
          self.prev_share_btn.show()
          self.next_share_btn.show()
          self.prev_share_btn.setEnabled(self.slip39_mnemonic_index != 0)
          self.next_share_btn.setEnabled(
              # already pressed "prev" and undoing that:
              self.slip39_mnemonic_index < len(self.slip39_mnemonics) - 1
              # finished entering latest share and starting new one:
              or (bool(self.seed_e.text().strip()) and not self.slip39_current_mnemonic_invalid and not finished)
          )
  
      def on_prev_share(self):
          if not self.slip39_mnemonics[self.slip39_mnemonic_index]:
              del self.slip39_mnemonics[self.slip39_mnemonic_index]
  
          self.slip39_mnemonic_index -= 1
          self.seed_e.setText(self.slip39_mnemonics[self.slip39_mnemonic_index])
          self.slip39_current_mnemonic_invalid = None
  
      def on_next_share(self):
          if not self.slip39_mnemonics[self.slip39_mnemonic_index]:
              del self.slip39_mnemonics[self.slip39_mnemonic_index]
          else:
              self.slip39_mnemonic_index += 1
  
          if len(self.slip39_mnemonics) <= self.slip39_mnemonic_index:
              self.slip39_mnemonics.append("")
              self.seed_e.setFocus()
          self.seed_e.setText(self.slip39_mnemonics[self.slip39_mnemonic_index])
          self.slip39_current_mnemonic_invalid = None
  
  
  class KeysLayout(QVBoxLayout):
      def __init__(
              self,
              parent=None,
              header_layout=None,
              is_valid=None,
              allow_multi=False,
              *,
              config: 'SimpleConfig',
      ):
          QVBoxLayout.__init__(self)
          self.parent = parent
          self.is_valid = is_valid
          self.text_e = ScanQRTextEdit(allow_multi=allow_multi, config=config)
          self.text_e.textChanged.connect(self.on_edit)
          if isinstance(header_layout, str):
              self.addWidget(WWLabel(header_layout))
          else:
              self.addLayout(header_layout)
          self.addWidget(self.text_e)
  
      def get_text(self):
          return self.text_e.text()
  
      def on_edit(self):
          valid = False
          try:
              valid = self.is_valid(self.get_text())
          except Exception as e:
              self.parent.next_button.setToolTip(f'{_("Error")}: {str(e)}')
          else:
              self.parent.next_button.setToolTip('')
          self.parent.next_button.setEnabled(valid)
  
  
  class SeedDialog(WindowModalDialog):
  
      def __init__(self, parent, seed, passphrase, *, config: 'SimpleConfig'):
          WindowModalDialog.__init__(self, parent, ('Electrum-GRS - ' + _('Seed')))
          self.setMinimumWidth(400)
          vbox = QVBoxLayout(self)
          title =  _("Your wallet generation seed is:")
          slayout = SeedLayout(
              title=title,
              seed=seed,
              msg=True,
              passphrase=passphrase,
              config=config,
          )
          vbox.addLayout(slayout)
          vbox.addLayout(Buttons(CloseButton(self)))
  