electrum

Electrum Bitcoin wallet
git clone https://git.parazyd.org/electrum
Log | Files | Refs | Submodules

qt.py (33307B)


      1 '''
      2 
      3 Revealer
      4 Do you have something to hide?
      5 Secret backup plug-in for the electrum wallet.
      6 
      7 Tiago Romagnani Silveira, 2017
      8 
      9 
     10 '''
     11 
     12 import os
     13 import random
     14 import traceback
     15 from decimal import Decimal
     16 from functools import partial
     17 import sys
     18 
     19 import qrcode
     20 from PyQt5.QtPrintSupport import QPrinter
     21 from PyQt5.QtCore import Qt, QRectF, QRect, QSizeF, QUrl, QPoint, QSize
     22 from PyQt5.QtGui import (QPixmap, QImage, QBitmap, QPainter, QFontDatabase, QPen, QFont,
     23                          QColor, QDesktopServices, qRgba, QPainterPath)
     24 from PyQt5.QtWidgets import (QGridLayout, QVBoxLayout, QHBoxLayout, QLabel,
     25                              QPushButton, QLineEdit)
     26 
     27 from electrum.plugin import hook
     28 from electrum.i18n import _
     29 from electrum.util import make_dir, InvalidPassword, UserCancelled
     30 from electrum.gui.qt.util import (read_QIcon, EnterButton, WWLabel, icon_path,
     31                                   WindowModalDialog, Buttons, CloseButton, OkButton)
     32 from electrum.gui.qt.qrtextedit import ScanQRTextEdit
     33 from electrum.gui.qt.main_window import StatusBarButton
     34 
     35 from .revealer import RevealerPlugin
     36 
     37 
     38 class Plugin(RevealerPlugin):
     39 
     40     MAX_PLAINTEXT_LEN = 189  # chars
     41 
     42     def __init__(self, parent, config, name):
     43         RevealerPlugin.__init__(self, parent, config, name)
     44         self.base_dir = os.path.join(config.electrum_path(), 'revealer')
     45 
     46         if self.config.get('calibration_h') is None:
     47             self.config.set_key('calibration_h', 0)
     48         if self.config.get('calibration_v') is None:
     49             self.config.set_key('calibration_v', 0)
     50 
     51         self.calibration_h = self.config.get('calibration_h')
     52         self.calibration_v = self.config.get('calibration_v')
     53 
     54         self.f_size = QSize(1014*2, 642*2)
     55         self.abstand_h = 21
     56         self.abstand_v = 34
     57         self.calibration_noise = int('10' * 128)
     58         self.rawnoise = False
     59         make_dir(self.base_dir)
     60 
     61         self.extension = False
     62 
     63     @hook
     64     def create_status_bar(self, parent):
     65         b = StatusBarButton(read_QIcon('revealer.png'), "Revealer "+_("secret backup utility"),
     66                             partial(self.setup_dialog, parent))
     67         parent.addPermanentWidget(b)
     68 
     69     def requires_settings(self):
     70         return True
     71 
     72     def settings_widget(self, window):
     73         return EnterButton(_('Printer Calibration'), partial(self.calibration_dialog, window))
     74 
     75     def password_dialog(self, msg=None, parent=None):
     76         from electrum.gui.qt.password_dialog import PasswordDialog
     77         parent = parent or self
     78         d = PasswordDialog(parent, msg)
     79         return d.run()
     80 
     81     def get_seed(self):
     82         password = None
     83         if self.wallet.has_keystore_encryption():
     84             password = self.password_dialog(parent=self.d.parent())
     85             if not password:
     86                 raise UserCancelled()
     87 
     88         keystore = self.wallet.get_keystore()
     89         if not keystore or not keystore.has_seed():
     90             return
     91         self.extension = bool(keystore.get_passphrase(password))
     92         return keystore.get_seed(password)
     93 
     94     def setup_dialog(self, window):
     95         self.wallet = window.parent().wallet
     96         self.update_wallet_name(self.wallet)
     97         self.user_input = False
     98 
     99         self.d = WindowModalDialog(window, "Setup Dialog")
    100         self.d.setMinimumWidth(500)
    101         self.d.setMinimumHeight(210)
    102         self.d.setMaximumHeight(320)
    103         self.d.setContentsMargins(11,11,1,1)
    104 
    105         self.hbox = QHBoxLayout(self.d)
    106         vbox = QVBoxLayout()
    107         logo = QLabel()
    108         self.hbox.addWidget(logo)
    109         logo.setPixmap(QPixmap(icon_path('revealer.png')))
    110         logo.setAlignment(Qt.AlignLeft)
    111         self.hbox.addSpacing(16)
    112         vbox.addWidget(WWLabel("<b>"+_("Revealer Secret Backup Plugin")+"</b><br>"
    113                                     +_("To encrypt your backup, first we need to load some noise.")+"<br/>"))
    114         vbox.addSpacing(7)
    115         bcreate = QPushButton(_("Create a new Revealer"))
    116         bcreate.setMaximumWidth(181)
    117         bcreate.setDefault(True)
    118         vbox.addWidget(bcreate, Qt.AlignCenter)
    119         self.load_noise = ScanQRTextEdit(config=self.config)
    120         self.load_noise.setTabChangesFocus(True)
    121         self.load_noise.textChanged.connect(self.on_edit)
    122         self.load_noise.setMaximumHeight(33)
    123         self.hbox.addLayout(vbox)
    124         vbox.addWidget(WWLabel(_("or type an existing revealer code below and click 'next':")))
    125         vbox.addWidget(self.load_noise)
    126         vbox.addSpacing(3)
    127         self.next_button = QPushButton(_("Next"), self.d)
    128         self.next_button.setEnabled(False)
    129         vbox.addLayout(Buttons(self.next_button))
    130         self.next_button.clicked.connect(self.d.close)
    131         self.next_button.clicked.connect(partial(self.cypherseed_dialog, window))
    132         vbox.addWidget(
    133             QLabel("<b>" + _("Warning") + "</b>: " + _("Each revealer should be used only once.")
    134                    +"<br>"+_("more information at <a href=\"https://revealer.cc/faq\">https://revealer.cc/faq</a>")))
    135 
    136         def mk_digital():
    137             try:
    138                 self.make_digital(self.d)
    139             except Exception:
    140                 self.logger.exception('')
    141             else:
    142                 self.cypherseed_dialog(window)
    143 
    144         bcreate.clicked.connect(mk_digital)
    145         return bool(self.d.exec_())
    146 
    147     def get_noise(self):
    148         text = self.load_noise.text()
    149         return ''.join(text.split()).lower()
    150 
    151     def on_edit(self):
    152         txt = self.get_noise()
    153         versioned_seed = self.get_versioned_seed_from_user_input(txt)
    154         if versioned_seed:
    155             self.versioned_seed = versioned_seed
    156         self.user_input = bool(versioned_seed)
    157         self.next_button.setEnabled(bool(versioned_seed))
    158 
    159     def make_digital(self, dialog):
    160         self.make_rawnoise(True)
    161         self.bdone(dialog)
    162         self.d.close()
    163 
    164     def get_path_to_revealer_file(self, ext: str= '') -> str:
    165         version = self.versioned_seed.version
    166         code_id = self.versioned_seed.checksum
    167         filename = self.filename_prefix + version + "_" + code_id + ext
    168         path = os.path.join(self.base_dir, filename)
    169         return os.path.normcase(os.path.abspath(path))
    170 
    171     def get_path_to_calibration_file(self):
    172         path = os.path.join(self.base_dir, 'calibration.pdf')
    173         return os.path.normcase(os.path.abspath(path))
    174 
    175     def bcrypt(self, dialog):
    176         self.rawnoise = False
    177         version = self.versioned_seed.version
    178         code_id = self.versioned_seed.checksum
    179         dialog.show_message(''.join([_("{} encrypted for Revealer {}_{} saved as PNG and PDF at: ").format(self.was, version, code_id),
    180                                      "<b>", self.get_path_to_revealer_file(), "</b>", "<br/>",
    181                                      "<br/>", "<b>", _("Always check your backups.")]),
    182                             rich_text=True)
    183         dialog.close()
    184 
    185     def ext_warning(self, dialog):
    186         dialog.show_message(''.join(["<b>",_("Warning"), ": </b>",
    187                                      _("your seed extension will <b>not</b> be included in the encrypted backup.")]),
    188                             rich_text=True)
    189         dialog.close()
    190 
    191     def bdone(self, dialog):
    192         version = self.versioned_seed.version
    193         code_id = self.versioned_seed.checksum
    194         dialog.show_message(''.join([_("Digital Revealer ({}_{}) saved as PNG and PDF at:").format(version, code_id),
    195                                      "<br/>","<b>", self.get_path_to_revealer_file(), '</b>']),
    196                             rich_text=True)
    197 
    198 
    199     def customtxt_limits(self):
    200         txt = self.text.text()
    201         self.max_chars.setVisible(False)
    202         self.char_count.setText(f"({len(txt)}/{self.MAX_PLAINTEXT_LEN})")
    203         if len(txt)>0:
    204             self.ctext.setEnabled(True)
    205         if len(txt) > self.MAX_PLAINTEXT_LEN:
    206             self.text.setPlainText(txt[:self.MAX_PLAINTEXT_LEN])
    207             self.max_chars.setVisible(True)
    208 
    209     def t(self):
    210         self.txt = self.text.text()
    211         self.seed_img(is_seed=False)
    212 
    213     def warn_old_revealer(self):
    214         if self.versioned_seed.version == '0':
    215             link = "https://revealer.cc/revealer-warning-and-upgrade/"
    216             self.d.show_warning(("<b>{warning}: </b>{ver0}<br>"
    217                                  "{url}<br>"
    218                                  "{risk}")
    219                                 .format(warning=_("Warning"),
    220                                         ver0=_("Revealers starting with 0 are not secure due to a vulnerability."),
    221                                         url=_("More info at: {}").format(f'<a href="{link}">{link}</a>'),
    222                                         risk=_("Proceed at your own risk.")),
    223                                 rich_text=True)
    224 
    225     def cypherseed_dialog(self, window):
    226         self.warn_old_revealer()
    227 
    228         d = WindowModalDialog(window, "Encryption Dialog")
    229         d.setMinimumWidth(500)
    230         d.setMinimumHeight(210)
    231         d.setMaximumHeight(450)
    232         d.setContentsMargins(11, 11, 1, 1)
    233         self.c_dialog = d
    234 
    235         hbox = QHBoxLayout(d)
    236         self.vbox = QVBoxLayout()
    237         logo = QLabel()
    238         hbox.addWidget(logo)
    239         logo.setPixmap(QPixmap(icon_path('revealer.png')))
    240         logo.setAlignment(Qt.AlignLeft)
    241         hbox.addSpacing(16)
    242         self.vbox.addWidget(WWLabel("<b>" + _("Revealer Secret Backup Plugin") + "</b><br>"
    243                                + _("Ready to encrypt for revealer {}")
    244                                     .format(self.versioned_seed.version+'_'+self.versioned_seed.checksum)))
    245         self.vbox.addSpacing(11)
    246         hbox.addLayout(self.vbox)
    247         grid = QGridLayout()
    248         self.vbox.addLayout(grid)
    249 
    250         cprint = QPushButton(_("Encrypt {}'s seed").format(self.wallet_name))
    251         cprint.setMaximumWidth(250)
    252         cprint.clicked.connect(partial(self.seed_img, True))
    253         self.vbox.addWidget(cprint)
    254         self.vbox.addSpacing(1)
    255         self.vbox.addWidget(WWLabel("<b>"+_("OR")+"</b> "+_("type a custom alphanumerical secret below:")))
    256         self.text = ScanQRTextEdit(config=self.config)
    257         self.text.setTabChangesFocus(True)
    258         self.text.setMaximumHeight(70)
    259         self.text.textChanged.connect(self.customtxt_limits)
    260         self.vbox.addWidget(self.text)
    261         self.char_count = WWLabel("")
    262         self.char_count.setAlignment(Qt.AlignRight)
    263         self.vbox.addWidget(self.char_count)
    264         self.max_chars = WWLabel("<font color='red'>"
    265                                  + _("This version supports a maximum of {} characters.").format(self.MAX_PLAINTEXT_LEN)
    266                                  +"</font>")
    267         self.vbox.addWidget(self.max_chars)
    268         self.max_chars.setVisible(False)
    269         self.ctext = QPushButton(_("Encrypt custom secret"))
    270         self.ctext.clicked.connect(self.t)
    271         self.vbox.addWidget(self.ctext)
    272         self.ctext.setEnabled(False)
    273         self.vbox.addSpacing(11)
    274         self.vbox.addLayout(Buttons(CloseButton(d)))
    275         return bool(d.exec_())
    276 
    277     def update_wallet_name(self, name):
    278         self.wallet_name = str(name)
    279 
    280     def seed_img(self, is_seed = True):
    281 
    282         if is_seed:
    283             try:
    284                 cseed = self.get_seed()
    285             except UserCancelled:
    286                 return
    287             except InvalidPassword as e:
    288                 self.d.show_error(str(e))
    289                 return
    290             if not cseed:
    291                 self.d.show_message(_("This wallet has no seed"))
    292                 return
    293             txt = cseed.upper()
    294         else:
    295             txt = self.txt.upper()
    296 
    297         img = QImage(self.SIZE[0], self.SIZE[1], QImage.Format_Mono)
    298         bitmap = QBitmap.fromImage(img, Qt.MonoOnly)
    299         bitmap.fill(Qt.white)
    300         painter = QPainter()
    301         painter.begin(bitmap)
    302         QFontDatabase.addApplicationFont(os.path.join(os.path.dirname(__file__), 'SourceSansPro-Bold.otf') )
    303         if len(txt) < 102 :
    304             fontsize = 15
    305             linespace = 15
    306             max_letters = 17
    307             max_lines = 6
    308             max_words = 3
    309         else:
    310             fontsize = 12
    311             linespace = 10
    312             max_letters = 21
    313             max_lines = 9
    314             max_words = int(max_letters/4)
    315 
    316         font = QFont('Source Sans Pro', fontsize, QFont.Bold)
    317         font.setLetterSpacing(QFont.PercentageSpacing, 100)
    318         font.setPixelSize(fontsize)
    319         painter.setFont(font)
    320         seed_array = txt.split(' ')
    321 
    322         for n in range(max_lines):
    323             nwords = max_words
    324             temp_seed = seed_array[:nwords]
    325             while len(' '.join(map(str, temp_seed))) > max_letters:
    326                nwords = nwords - 1
    327                temp_seed = seed_array[:nwords]
    328             painter.drawText(QRect(0, linespace*n , self.SIZE[0], self.SIZE[1]), Qt.AlignHCenter, ' '.join(map(str, temp_seed)))
    329             del seed_array[:nwords]
    330 
    331         painter.end()
    332         img = bitmap.toImage()
    333         if (self.rawnoise == False):
    334             self.make_rawnoise()
    335 
    336         self.make_cypherseed(img, self.rawnoise, False, is_seed)
    337         return img
    338 
    339     def make_rawnoise(self, create_revealer=False):
    340         if not self.user_input:
    341             self.versioned_seed = self.gen_random_versioned_seed()
    342         assert self.versioned_seed
    343         w, h = self.SIZE
    344         rawnoise = QImage(w, h, QImage.Format_Mono)
    345 
    346         noise_map = self.get_noise_map(self.versioned_seed)
    347         for (x,y), pixel in noise_map.items():
    348             rawnoise.setPixel(x, y, pixel)
    349 
    350         self.rawnoise = rawnoise
    351         if create_revealer:
    352             self.make_revealer()
    353 
    354     def make_calnoise(self):
    355         random.seed(self.calibration_noise)
    356         w, h = self.SIZE
    357         rawnoise = QImage(w, h, QImage.Format_Mono)
    358         for x in range(w):
    359             for y in range(h):
    360                 rawnoise.setPixel(x,y,random.randint(0, 1))
    361         self.calnoise = self.pixelcode_2x2(rawnoise)
    362 
    363     def make_revealer(self):
    364         revealer = self.pixelcode_2x2(self.rawnoise)
    365         revealer.invertPixels()
    366         revealer = QBitmap.fromImage(revealer)
    367         revealer = revealer.scaled(self.f_size, Qt.KeepAspectRatio)
    368         revealer = self.overlay_marks(revealer)
    369 
    370         self.filename_prefix = 'revealer_'
    371         revealer.save(self.get_path_to_revealer_file('.png'))
    372         self.toPdf(QImage(revealer))
    373         QDesktopServices.openUrl(QUrl.fromLocalFile(self.get_path_to_revealer_file('.pdf')))
    374 
    375     def make_cypherseed(self, img, rawnoise, calibration=False, is_seed = True):
    376         img = img.convertToFormat(QImage.Format_Mono)
    377         p = QPainter()
    378         p.begin(img)
    379         p.setCompositionMode(26) #xor
    380         p.drawImage(0, 0, rawnoise)
    381         p.end()
    382         cypherseed = self.pixelcode_2x2(img)
    383         cypherseed = QBitmap.fromImage(cypherseed)
    384         cypherseed = cypherseed.scaled(self.f_size, Qt.KeepAspectRatio)
    385         cypherseed = self.overlay_marks(cypherseed, True, calibration)
    386 
    387         if not is_seed:
    388             self.filename_prefix = 'custom_secret_'
    389             self.was = _('Custom secret')
    390         else:
    391             self.filename_prefix = self.wallet_name + '_seed_'
    392             self.was = self.wallet_name + ' ' + _('seed')
    393             if self.extension:
    394                 self.ext_warning(self.c_dialog)
    395 
    396 
    397         if not calibration:
    398             self.toPdf(QImage(cypherseed))
    399             QDesktopServices.openUrl(QUrl.fromLocalFile(self.get_path_to_revealer_file('.pdf')))
    400             cypherseed.save(self.get_path_to_revealer_file('.png'))
    401             self.bcrypt(self.c_dialog)
    402         return cypherseed
    403 
    404     def calibration(self):
    405         img = QImage(self.SIZE[0], self.SIZE[1], QImage.Format_Mono)
    406         bitmap = QBitmap.fromImage(img, Qt.MonoOnly)
    407         bitmap.fill(Qt.black)
    408         self.make_calnoise()
    409         img = self.overlay_marks(self.calnoise.scaledToHeight(self.f_size.height()), False, True)
    410         self.calibration_pdf(img)
    411         QDesktopServices.openUrl(QUrl.fromLocalFile(self.get_path_to_calibration_file()))
    412         return img
    413 
    414     def toPdf(self, image):
    415         printer = QPrinter()
    416         printer.setPaperSize(QSizeF(210, 297), QPrinter.Millimeter)
    417         printer.setResolution(600)
    418         printer.setOutputFormat(QPrinter.PdfFormat)
    419         printer.setOutputFileName(self.get_path_to_revealer_file('.pdf'))
    420         printer.setPageMargins(0,0,0,0,6)
    421         painter = QPainter()
    422         painter.begin(printer)
    423 
    424         delta_h = round(image.width()/self.abstand_v)
    425         delta_v = round(image.height()/self.abstand_h)
    426 
    427         size_h = 2028+((int(self.calibration_h)*2028/(2028-(delta_h*2)+int(self.calibration_h)))/2)
    428         size_v = 1284+((int(self.calibration_v)*1284/(1284-(delta_v*2)+int(self.calibration_v)))/2)
    429 
    430         image =  image.scaled(size_h, size_v)
    431 
    432         painter.drawImage(553,533, image)
    433         wpath = QPainterPath()
    434         wpath.addRoundedRect(QRectF(553,533, size_h, size_v), 19, 19)
    435         painter.setPen(QPen(Qt.black, 1))
    436         painter.drawPath(wpath)
    437         painter.end()
    438 
    439     def calibration_pdf(self, image):
    440         printer = QPrinter()
    441         printer.setPaperSize(QSizeF(210, 297), QPrinter.Millimeter)
    442         printer.setResolution(600)
    443         printer.setOutputFormat(QPrinter.PdfFormat)
    444         printer.setOutputFileName(self.get_path_to_calibration_file())
    445         printer.setPageMargins(0,0,0,0,6)
    446 
    447         painter = QPainter()
    448         painter.begin(printer)
    449         painter.drawImage(553,533, image)
    450         font = QFont('Source Sans Pro', 10, QFont.Bold)
    451         painter.setFont(font)
    452         painter.drawText(254,277, _("Calibration sheet"))
    453         font = QFont('Source Sans Pro', 7, QFont.Bold)
    454         painter.setFont(font)
    455         painter.drawText(600,2077, _("Instructions:"))
    456         font = QFont('Source Sans Pro', 7, QFont.Normal)
    457         painter.setFont(font)
    458         painter.drawText(700, 2177, _("1. Place this paper on a flat and well iluminated surface."))
    459         painter.drawText(700, 2277, _("2. Align your Revealer borderlines to the dashed lines on the top and left."))
    460         painter.drawText(700, 2377, _("3. Press slightly the Revealer against the paper and read the numbers that best "
    461                                       "match on the opposite sides. "))
    462         painter.drawText(700, 2477, _("4. Type the numbers in the software"))
    463         painter.end()
    464 
    465     def pixelcode_2x2(self, img):
    466         result = QImage(img.width()*2, img.height()*2, QImage.Format_ARGB32 )
    467         white = qRgba(255,255,255,0)
    468         black = qRgba(0,0,0,255)
    469 
    470         for x in range(img.width()):
    471             for y in range(img.height()):
    472                 c = img.pixel(QPoint(x,y))
    473                 colors = QColor(c).getRgbF()
    474                 if colors[0]:
    475                     result.setPixel(x*2+1,y*2+1, black)
    476                     result.setPixel(x*2,y*2+1, white)
    477                     result.setPixel(x*2+1,y*2, white)
    478                     result.setPixel(x*2, y*2, black)
    479 
    480                 else:
    481                     result.setPixel(x*2+1,y*2+1, white)
    482                     result.setPixel(x*2,y*2+1, black)
    483                     result.setPixel(x*2+1,y*2, black)
    484                     result.setPixel(x*2, y*2, white)
    485         return result
    486 
    487     def overlay_marks(self, img, is_cseed=False, calibration_sheet=False):
    488         border_color = Qt.white
    489         base_img = QImage(self.f_size.width(),self.f_size.height(), QImage.Format_ARGB32)
    490         base_img.fill(border_color)
    491         img = QImage(img)
    492 
    493         painter = QPainter()
    494         painter.begin(base_img)
    495 
    496         total_distance_h = round(base_img.width() / self.abstand_v)
    497         dist_v = round(total_distance_h) / 2
    498         dist_h = round(total_distance_h) / 2
    499 
    500         img = img.scaledToWidth(base_img.width() - (2 * (total_distance_h)))
    501         painter.drawImage(total_distance_h,
    502                           total_distance_h,
    503                           img)
    504 
    505         #frame around image
    506         pen = QPen(Qt.black, 2)
    507         painter.setPen(pen)
    508 
    509         #horz
    510         painter.drawLine(0, total_distance_h, base_img.width(), total_distance_h)
    511         painter.drawLine(0, base_img.height()-(total_distance_h), base_img.width(), base_img.height()-(total_distance_h))
    512         #vert
    513         painter.drawLine(total_distance_h, 0,  total_distance_h, base_img.height())
    514         painter.drawLine(base_img.width()-(total_distance_h), 0,  base_img.width()-(total_distance_h), base_img.height())
    515 
    516         #border around img
    517         border_thick = 6
    518         Rpath = QPainterPath()
    519         Rpath.addRect(QRectF((total_distance_h)+(border_thick/2),
    520                              (total_distance_h)+(border_thick/2),
    521                              base_img.width()-((total_distance_h)*2)-((border_thick)-1),
    522                              (base_img.height()-((total_distance_h))*2)-((border_thick)-1)))
    523         pen = QPen(Qt.black, border_thick)
    524         pen.setJoinStyle (Qt.MiterJoin)
    525 
    526         painter.setPen(pen)
    527         painter.drawPath(Rpath)
    528 
    529         Bpath = QPainterPath()
    530         Bpath.addRect(QRectF((total_distance_h), (total_distance_h),
    531                              base_img.width()-((total_distance_h)*2), (base_img.height()-((total_distance_h))*2)))
    532         pen = QPen(Qt.black, 1)
    533         painter.setPen(pen)
    534         painter.drawPath(Bpath)
    535 
    536         pen = QPen(Qt.black, 1)
    537         painter.setPen(pen)
    538         painter.drawLine(0, base_img.height()/2, total_distance_h, base_img.height()/2)
    539         painter.drawLine(base_img.width()/2, 0, base_img.width()/2, total_distance_h)
    540 
    541         painter.drawLine(base_img.width()-total_distance_h, base_img.height()/2, base_img.width(), base_img.height()/2)
    542         painter.drawLine(base_img.width()/2, base_img.height(), base_img.width()/2, base_img.height() - total_distance_h)
    543 
    544         #print code
    545         f_size = 37
    546         QFontDatabase.addApplicationFont(os.path.join(os.path.dirname(__file__), 'DejaVuSansMono-Bold.ttf'))
    547         font = QFont("DejaVu Sans Mono", f_size-11, QFont.Bold)
    548         font.setPixelSize(35)
    549         painter.setFont(font)
    550 
    551         if not calibration_sheet:
    552             if is_cseed: #its a secret
    553                 painter.setPen(QPen(Qt.black, 1, Qt.DashDotDotLine))
    554                 painter.drawLine(0, dist_v, base_img.width(), dist_v)
    555                 painter.drawLine(dist_h, 0,  dist_h, base_img.height())
    556                 painter.drawLine(0, base_img.height()-dist_v, base_img.width(), base_img.height()-(dist_v))
    557                 painter.drawLine(base_img.width()-(dist_h), 0,  base_img.width()-(dist_h), base_img.height())
    558 
    559                 painter.drawImage(((total_distance_h))+11, ((total_distance_h))+11,
    560                                   QImage(icon_path('electrumb.png')).scaledToWidth(2.1*(total_distance_h), Qt.SmoothTransformation))
    561 
    562                 painter.setPen(QPen(Qt.white, border_thick*8))
    563                 painter.drawLine(base_img.width()-((total_distance_h))-(border_thick*8)/2-(border_thick/2)-2,
    564                                 (base_img.height()-((total_distance_h)))-((border_thick*8)/2)-(border_thick/2)-2,
    565                                 base_img.width()-((total_distance_h))-(border_thick*8)/2-(border_thick/2)-2 - 77,
    566                                 (base_img.height()-((total_distance_h)))-((border_thick*8)/2)-(border_thick/2)-2)
    567                 painter.setPen(QColor(0,0,0,255))
    568                 painter.drawText(QRect(0, base_img.height()-107, base_img.width()-total_distance_h - border_thick - 11,
    569                                        base_img.height()-total_distance_h - border_thick), Qt.AlignRight,
    570                                  self.versioned_seed.version + '_'+self.versioned_seed.checksum)
    571                 painter.end()
    572 
    573             else: # revealer
    574 
    575                 painter.setPen(QPen(border_color, 17))
    576                 painter.drawLine(0, dist_v, base_img.width(), dist_v)
    577                 painter.drawLine(dist_h, 0,  dist_h, base_img.height())
    578                 painter.drawLine(0, base_img.height()-dist_v, base_img.width(), base_img.height()-(dist_v))
    579                 painter.drawLine(base_img.width()-(dist_h), 0,  base_img.width()-(dist_h), base_img.height())
    580 
    581                 painter.setPen(QPen(Qt.black, 2))
    582                 painter.drawLine(0, dist_v, base_img.width(), dist_v)
    583                 painter.drawLine(dist_h, 0,  dist_h, base_img.height())
    584                 painter.drawLine(0, base_img.height()-dist_v, base_img.width(), base_img.height()-(dist_v))
    585                 painter.drawLine(base_img.width()-(dist_h), 0,  base_img.width()-(dist_h), base_img.height())
    586                 logo = QImage(icon_path('revealer_c.png')).scaledToWidth(1.3*(total_distance_h))
    587                 painter.drawImage((total_distance_h)+ (border_thick), ((total_distance_h))+ (border_thick), logo, Qt.SmoothTransformation)
    588 
    589                 #frame around logo
    590                 painter.setPen(QPen(Qt.black, border_thick))
    591                 painter.drawLine(total_distance_h+border_thick, total_distance_h+logo.height()+3*(border_thick/2),
    592                                  total_distance_h+logo.width()+border_thick, total_distance_h+logo.height()+3*(border_thick/2))
    593                 painter.drawLine(logo.width()+total_distance_h+3*(border_thick/2), total_distance_h+(border_thick),
    594                                  total_distance_h+logo.width()+3*(border_thick/2), total_distance_h+logo.height()+(border_thick))
    595 
    596                 #frame around code/qr
    597                 qr_size = 179
    598 
    599                 painter.drawLine((base_img.width()-((total_distance_h))-(border_thick/2)-2)-qr_size,
    600                                 (base_img.height()-((total_distance_h)))-((border_thick*8))-(border_thick/2)-2,
    601                                  (base_img.width()/2+(total_distance_h/2)-border_thick-(border_thick*8)/2)-qr_size,
    602                                 (base_img.height()-((total_distance_h)))-((border_thick*8))-(border_thick/2)-2)
    603 
    604                 painter.drawLine((base_img.width()/2+(total_distance_h/2)-border_thick-(border_thick*8)/2)-qr_size,
    605                                 (base_img.height()-((total_distance_h)))-((border_thick*8))-(border_thick/2)-2,
    606                                  base_img.width()/2 + (total_distance_h/2)-border_thick-(border_thick*8)/2-qr_size,
    607                                  ((base_img.height()-((total_distance_h)))-(border_thick/2)-2))
    608 
    609                 painter.setPen(QPen(Qt.white, border_thick * 8))
    610                 painter.drawLine(
    611                     base_img.width() - ((total_distance_h)) - (border_thick * 8) / 2 - (border_thick / 2) - 2,
    612                     (base_img.height() - ((total_distance_h))) - ((border_thick * 8) / 2) - (border_thick / 2) - 2,
    613                     base_img.width() / 2 + (total_distance_h / 2) - border_thick - qr_size,
    614                     (base_img.height() - ((total_distance_h))) - ((border_thick * 8) / 2) - (border_thick / 2) - 2)
    615 
    616                 painter.setPen(QColor(0,0,0,255))
    617                 painter.drawText(QRect(((base_img.width()/2) +21)-qr_size, base_img.height()-107,
    618                                        base_img.width()-total_distance_h - border_thick -93,
    619                                        base_img.height()-total_distance_h - border_thick), Qt.AlignLeft, self.versioned_seed.get_ui_string_version_plus_seed())
    620                 painter.drawText(QRect(0, base_img.height()-107, base_img.width()-total_distance_h - border_thick -3 -qr_size,
    621                                        base_img.height()-total_distance_h - border_thick), Qt.AlignRight, self.versioned_seed.checksum)
    622 
    623                 # draw qr code
    624                 qr_qt = self.paintQR(self.versioned_seed.get_ui_string_version_plus_seed()
    625                                      + self.versioned_seed.checksum)
    626                 target = QRectF(base_img.width()-65-qr_size,
    627                                 base_img.height()-65-qr_size,
    628                                 qr_size, qr_size )
    629                 painter.drawImage(target, qr_qt)
    630                 painter.setPen(QPen(Qt.black, 4))
    631                 painter.drawLine(base_img.width()-65-qr_size,
    632                                 base_img.height()-65-qr_size,
    633                                  base_img.width() - 65 - qr_size,
    634                                 (base_img.height() - ((total_distance_h))) - ((border_thick * 8)) - (border_thick / 2) - 4
    635                                  )
    636                 painter.drawLine(base_img.width()-65-qr_size,
    637                                 base_img.height()-65-qr_size,
    638                                  base_img.width() - 65,
    639                                 base_img.height()-65-qr_size
    640                                  )
    641                 painter.end()
    642 
    643         else: # calibration only
    644             painter.end()
    645             cal_img = QImage(self.f_size.width() + 100, self.f_size.height() + 100,
    646                               QImage.Format_ARGB32)
    647             cal_img.fill(Qt.white)
    648 
    649             cal_painter = QPainter()
    650             cal_painter.begin(cal_img)
    651             cal_painter.drawImage(0,0, base_img)
    652 
    653             #black lines in the middle of border top left only
    654             cal_painter.setPen(QPen(Qt.black, 1, Qt.DashDotDotLine))
    655             cal_painter.drawLine(0, dist_v, base_img.width(), dist_v)
    656             cal_painter.drawLine(dist_h, 0,  dist_h, base_img.height())
    657 
    658             pen = QPen(Qt.black, 2, Qt.DashDotDotLine)
    659             cal_painter.setPen(pen)
    660             n=15
    661 
    662             cal_painter.setFont(QFont("DejaVu Sans Mono", 21, QFont.Bold))
    663             for x in range(-n,n):
    664                 #lines on bottom (vertical calibration)
    665                 cal_painter.drawLine((((base_img.width())/(n*2)) *(x))+ (base_img.width()/2)-13,
    666                                  x+2+base_img.height()-(dist_v),
    667                                  (((base_img.width())/(n*2)) *(x))+ (base_img.width()/2)+13,
    668                                  x+2+base_img.height()-(dist_v))
    669 
    670                 num_pos = 9
    671                 if x > 9 : num_pos = 17
    672                 if x < 0 : num_pos = 20
    673                 if x < -9: num_pos = 27
    674 
    675                 cal_painter.drawText((((base_img.width())/(n*2)) *(x))+ (base_img.width()/2)-num_pos,
    676                                  50+base_img.height()-(dist_v),
    677                                   str(x))
    678 
    679                 #lines on the right (horizontal calibrations)
    680 
    681                 cal_painter.drawLine(x+2+(base_img.width()-(dist_h)),
    682                                  ((base_img.height()/(2*n)) *(x))+ (base_img.height()/n)+(base_img.height()/2)-13,
    683                                  x+2+(base_img.width()-(dist_h)),
    684                                  ((base_img.height()/(2*n)) *(x))+ (base_img.height()/n)+(base_img.height()/2)+13)
    685 
    686 
    687                 cal_painter.drawText(30+(base_img.width()-(dist_h)),
    688                                 ((base_img.height()/(2*n)) *(x))+ (base_img.height()/2)+13, str(x))
    689 
    690             cal_painter.end()
    691             base_img = cal_img
    692 
    693         return base_img
    694 
    695     def paintQR(self, data):
    696         if not data:
    697             return
    698         qr = qrcode.QRCode()
    699         qr.add_data(data)
    700         matrix = qr.get_matrix()
    701         k = len(matrix)
    702         border_color = Qt.white
    703         base_img = QImage(k * 5, k * 5, QImage.Format_ARGB32)
    704         base_img.fill(border_color)
    705         qrpainter = QPainter()
    706         qrpainter.begin(base_img)
    707         boxsize = 5
    708         size = k * boxsize
    709         left = (base_img.width() - size)/2
    710         top = (base_img.height() - size)/2
    711         qrpainter.setBrush(Qt.black)
    712         qrpainter.setPen(Qt.black)
    713 
    714         for r in range(k):
    715             for c in range(k):
    716                 if matrix[r][c]:
    717                     qrpainter.drawRect(left+c*boxsize, top+r*boxsize, boxsize - 1, boxsize - 1)
    718         qrpainter.end()
    719         return base_img
    720 
    721     def calibration_dialog(self, window):
    722         d = WindowModalDialog(window, _("Revealer - Printer calibration settings"))
    723 
    724         d.setMinimumSize(100, 200)
    725 
    726         vbox = QVBoxLayout(d)
    727         vbox.addWidget(QLabel(''.join(["<br/>", _("If you have an old printer, or want optimal precision"),"<br/>",
    728                                        _("print the calibration pdf and follow the instructions "), "<br/>","<br/>",
    729                                     ])))
    730         self.calibration_h = self.config.get('calibration_h')
    731         self.calibration_v = self.config.get('calibration_v')
    732         cprint = QPushButton(_("Open calibration pdf"))
    733         cprint.clicked.connect(self.calibration)
    734         vbox.addWidget(cprint)
    735 
    736         vbox.addWidget(QLabel(_('Calibration values:')))
    737         grid = QGridLayout()
    738         vbox.addLayout(grid)
    739         grid.addWidget(QLabel(_('Right side')), 0, 0)
    740         horizontal = QLineEdit()
    741         horizontal.setText(str(self.calibration_h))
    742         grid.addWidget(horizontal, 0, 1)
    743 
    744         grid.addWidget(QLabel(_('Bottom')), 1, 0)
    745         vertical = QLineEdit()
    746         vertical.setText(str(self.calibration_v))
    747         grid.addWidget(vertical, 1, 1)
    748 
    749         vbox.addStretch()
    750         vbox.addSpacing(13)
    751         vbox.addLayout(Buttons(CloseButton(d), OkButton(d)))
    752 
    753         if not d.exec_():
    754             return
    755 
    756         self.calibration_h = int(Decimal(horizontal.text()))
    757         self.config.set_key('calibration_h', self.calibration_h)
    758         self.calibration_v = int(Decimal(vertical.text()))
    759         self.config.set_key('calibration_v', self.calibration_v)
    760 
    761