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