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 (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