1''' 2 3Revealer 4Do you have something to hide? 5Secret backup plug-in for the electrum wallet. 6 7Tiago Romagnani Silveira, 2017 8 9 10''' 11 12import os 13import random 14import traceback 15from decimal import Decimal 16from functools import partial 17import sys 18 19import qrcode 20from PyQt5.QtPrintSupport import QPrinter 21from PyQt5.QtCore import Qt, QRectF, QRect, QSizeF, QUrl, QPoint, QSize 22from PyQt5.QtGui import (QPixmap, QImage, QBitmap, QPainter, QFontDatabase, QPen, QFont, 23 QColor, QDesktopServices, qRgba, QPainterPath) 24from PyQt5.QtWidgets import (QGridLayout, QVBoxLayout, QHBoxLayout, QLabel, 25 QPushButton, QLineEdit) 26 27from electrum.plugin import hook 28from electrum.i18n import _ 29from electrum.util import make_dir, InvalidPassword, UserCancelled 30from electrum.gui.qt.util import (read_QIcon, EnterButton, WWLabel, icon_path, 31 WindowModalDialog, Buttons, CloseButton, OkButton) 32from electrum.gui.qt.qrtextedit import ScanQRTextEdit 33from electrum.gui.qt.main_window import StatusBarButton 34 35from .revealer import RevealerPlugin 36 37 38class 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 "+_("Visual Cryptography Plugin"), 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 Visual Cryptography Plugin")+"</b><br><br>" 113 +_("To encrypt a secret, first create or load 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 133 134 def mk_digital(): 135 try: 136 self.make_digital(self.d) 137 except Exception: 138 self.logger.exception('') 139 else: 140 self.cypherseed_dialog(window) 141 142 bcreate.clicked.connect(mk_digital) 143 return bool(self.d.exec_()) 144 145 def get_noise(self): 146 text = self.load_noise.text() 147 return ''.join(text.split()).lower() 148 149 def on_edit(self): 150 txt = self.get_noise() 151 versioned_seed = self.get_versioned_seed_from_user_input(txt) 152 if versioned_seed: 153 self.versioned_seed = versioned_seed 154 self.user_input = bool(versioned_seed) 155 self.next_button.setEnabled(bool(versioned_seed)) 156 157 def make_digital(self, dialog): 158 self.make_rawnoise(True) 159 self.bdone(dialog) 160 self.d.close() 161 162 def get_path_to_revealer_file(self, ext: str= '') -> str: 163 version = self.versioned_seed.version 164 code_id = self.versioned_seed.checksum 165 filename = self.filename_prefix + version + "_" + code_id + ext 166 path = os.path.join(self.base_dir, filename) 167 return os.path.normcase(os.path.abspath(path)) 168 169 def get_path_to_calibration_file(self): 170 path = os.path.join(self.base_dir, 'calibration.pdf') 171 return os.path.normcase(os.path.abspath(path)) 172 173 def bcrypt(self, dialog): 174 self.rawnoise = False 175 version = self.versioned_seed.version 176 code_id = self.versioned_seed.checksum 177 dialog.show_message(''.join([_("{} encrypted for Revealer {}_{} saved as PNG and PDF at: ").format(self.was, version, code_id), 178 "<b>", self.get_path_to_revealer_file(), "</b>", "<br/>", 179 "<br/>", "<b>", _("Always check your backups.")]), 180 rich_text=True) 181 dialog.close() 182 183 def ext_warning(self, dialog): 184 dialog.show_message(''.join(["<b>",_("Warning"), ": </b>", 185 _("your seed extension will <b>not</b> be included in the encrypted backup.")]), 186 rich_text=True) 187 dialog.close() 188 189 def bdone(self, dialog): 190 version = self.versioned_seed.version 191 code_id = self.versioned_seed.checksum 192 dialog.show_message(''.join([_("Digital Revealer ({}_{}) saved as PNG and PDF at:").format(version, code_id), 193 "<br/>","<b>", self.get_path_to_revealer_file(), '</b>']), 194 rich_text=True) 195 196 197 def customtxt_limits(self): 198 txt = self.text.text() 199 self.max_chars.setVisible(False) 200 self.char_count.setText(f"({len(txt)}/{self.MAX_PLAINTEXT_LEN})") 201 if len(txt)>0: 202 self.ctext.setEnabled(True) 203 if len(txt) > self.MAX_PLAINTEXT_LEN: 204 self.text.setPlainText(txt[:self.MAX_PLAINTEXT_LEN]) 205 self.max_chars.setVisible(True) 206 207 def t(self): 208 self.txt = self.text.text() 209 self.seed_img(is_seed=False) 210 211 def warn_old_revealer(self): 212 if self.versioned_seed.version == '0': 213 link = "https://revealer.cc/revealer-warning-and-upgrade/" 214 self.d.show_warning(("<b>{warning}: </b>{ver0}<br>" 215 "{url}<br>" 216 "{risk}") 217 .format(warning=_("Warning"), 218 ver0=_("Revealers starting with 0 are not secure due to a vulnerability."), 219 url=_("More info at: {}").format(f'<a href="{link}">{link}</a>'), 220 risk=_("Proceed at your own risk.")), 221 rich_text=True) 222 223 def cypherseed_dialog(self, window): 224 self.warn_old_revealer() 225 226 d = WindowModalDialog(window, "Encryption Dialog") 227 d.setMinimumWidth(500) 228 d.setMinimumHeight(210) 229 d.setMaximumHeight(450) 230 d.setContentsMargins(11, 11, 1, 1) 231 self.c_dialog = d 232 233 hbox = QHBoxLayout(d) 234 self.vbox = QVBoxLayout() 235 logo = QLabel() 236 hbox.addWidget(logo) 237 logo.setPixmap(QPixmap(icon_path('revealer.png'))) 238 logo.setAlignment(Qt.AlignLeft) 239 hbox.addSpacing(16) 240 self.vbox.addWidget(WWLabel("<b>" + _("Revealer Visual Cryptography Plugin") + "</b><br><br>" 241 + _("Ready to encrypt for revealer {}") 242 .format(self.versioned_seed.version+'_'+self.versioned_seed.checksum))) 243 self.vbox.addSpacing(11) 244 hbox.addLayout(self.vbox) 245 grid = QGridLayout() 246 self.vbox.addLayout(grid) 247 248 cprint = QPushButton(_("Encrypt {}'s seed").format(self.wallet_name)) 249 cprint.setMaximumWidth(250) 250 cprint.clicked.connect(partial(self.seed_img, True)) 251 self.vbox.addWidget(cprint) 252 self.vbox.addSpacing(1) 253 self.vbox.addWidget(WWLabel("<b>"+_("OR")+"</b> "+_("type a custom alphanumerical secret below:"))) 254 self.text = ScanQRTextEdit(config=self.config) 255 self.text.setTabChangesFocus(True) 256 self.text.setMaximumHeight(70) 257 self.text.textChanged.connect(self.customtxt_limits) 258 self.vbox.addWidget(self.text) 259 self.char_count = WWLabel("") 260 self.char_count.setAlignment(Qt.AlignRight) 261 self.vbox.addWidget(self.char_count) 262 self.max_chars = WWLabel("<font color='red'>" 263 + _("This version supports a maximum of {} characters.").format(self.MAX_PLAINTEXT_LEN) 264 +"</font>") 265 self.vbox.addWidget(self.max_chars) 266 self.max_chars.setVisible(False) 267 self.ctext = QPushButton(_("Encrypt custom secret")) 268 self.ctext.clicked.connect(self.t) 269 self.vbox.addWidget(self.ctext) 270 self.ctext.setEnabled(False) 271 self.vbox.addSpacing(11) 272 self.vbox.addLayout(Buttons(CloseButton(d))) 273 self.vbox.addWidget( 274 QLabel("<br>"+"<b>" + _("Warning ") + "</b>: " + _("each Revealer is a one-time-pad, use it for a single secret."))) 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 374 def make_cypherseed(self, img, rawnoise, calibration=False, is_seed = True): 375 img = img.convertToFormat(QImage.Format_Mono) 376 p = QPainter() 377 p.begin(img) 378 p.setCompositionMode(26) #xor 379 p.drawImage(0, 0, rawnoise) 380 p.end() 381 cypherseed = self.pixelcode_2x2(img) 382 cypherseed = QBitmap.fromImage(cypherseed) 383 cypherseed = cypherseed.scaled(self.f_size, Qt.KeepAspectRatio) 384 cypherseed = self.overlay_marks(cypherseed, True, calibration) 385 386 if not is_seed: 387 self.filename_prefix = 'custom_secret_' 388 self.was = _('Custom secret') 389 else: 390 self.filename_prefix = self.wallet_name + '_seed_' 391 self.was = self.wallet_name + ' ' + _('seed') 392 if self.extension: 393 self.ext_warning(self.c_dialog) 394 395 396 if not calibration: 397 self.toPdf(QImage(cypherseed)) 398 cypherseed.save(self.get_path_to_revealer_file('.png')) 399 self.bcrypt(self.c_dialog) 400 return cypherseed 401 402 def calibration(self): 403 img = QImage(self.SIZE[0], self.SIZE[1], QImage.Format_Mono) 404 bitmap = QBitmap.fromImage(img, Qt.MonoOnly) 405 bitmap.fill(Qt.black) 406 self.make_calnoise() 407 img = self.overlay_marks(self.calnoise.scaledToHeight(self.f_size.height()), False, True) 408 self.calibration_pdf(img) 409 QDesktopServices.openUrl(QUrl.fromLocalFile(self.get_path_to_calibration_file())) 410 return img 411 412 def toPdf(self, image): 413 printer = QPrinter() 414 printer.setPaperSize(QSizeF(210, 297), QPrinter.Millimeter) 415 printer.setResolution(600) 416 printer.setOutputFormat(QPrinter.PdfFormat) 417 printer.setOutputFileName(self.get_path_to_revealer_file('.pdf')) 418 printer.setPageMargins(0,0,0,0,6) 419 painter = QPainter() 420 painter.begin(printer) 421 422 delta_h = round(image.width()/self.abstand_v) 423 delta_v = round(image.height()/self.abstand_h) 424 425 size_h = 2028+((int(self.calibration_h)*2028/(2028-(delta_h*2)+int(self.calibration_h)))/2) 426 size_v = 1284+((int(self.calibration_v)*1284/(1284-(delta_v*2)+int(self.calibration_v)))/2) 427 428 image = image.scaled(size_h, size_v) 429 430 painter.drawImage(553,533, image) 431 wpath = QPainterPath() 432 wpath.addRoundedRect(QRectF(553,533, size_h, size_v), 19, 19) 433 painter.setPen(QPen(Qt.black, 1)) 434 painter.drawPath(wpath) 435 painter.end() 436 437 def calibration_pdf(self, image): 438 printer = QPrinter() 439 printer.setPaperSize(QSizeF(210, 297), QPrinter.Millimeter) 440 printer.setResolution(600) 441 printer.setOutputFormat(QPrinter.PdfFormat) 442 printer.setOutputFileName(self.get_path_to_calibration_file()) 443 printer.setPageMargins(0,0,0,0,6) 444 445 painter = QPainter() 446 painter.begin(printer) 447 painter.drawImage(553,533, image) 448 font = QFont('Source Sans Pro', 10, QFont.Bold) 449 painter.setFont(font) 450 painter.drawText(254,277, _("Calibration sheet")) 451 font = QFont('Source Sans Pro', 7, QFont.Bold) 452 painter.setFont(font) 453 painter.drawText(600,2077, _("Instructions:")) 454 font = QFont('Source Sans Pro', 7, QFont.Normal) 455 painter.setFont(font) 456 painter.drawText(700, 2177, _("1. Place this paper on a flat and well iluminated surface.")) 457 painter.drawText(700, 2277, _("2. Align your Revealer borderlines to the dashed lines on the top and left.")) 458 painter.drawText(700, 2377, _("3. Press slightly the Revealer against the paper and read the numbers that best " 459 "match on the opposite sides. ")) 460 painter.drawText(700, 2477, _("4. Type the numbers in the software")) 461 painter.end() 462 463 def pixelcode_2x2(self, img): 464 result = QImage(img.width()*2, img.height()*2, QImage.Format_ARGB32) 465 white = qRgba(255,255,255,0) 466 black = qRgba(0,0,0,255) 467 468 for x in range(img.width()): 469 for y in range(img.height()): 470 c = img.pixel(QPoint(x,y)) 471 colors = QColor(c).getRgbF() 472 if colors[0]: 473 result.setPixel(x*2+1,y*2+1, black) 474 result.setPixel(x*2,y*2+1, white) 475 result.setPixel(x*2+1,y*2, white) 476 result.setPixel(x*2, y*2, black) 477 478 else: 479 result.setPixel(x*2+1,y*2+1, white) 480 result.setPixel(x*2,y*2+1, black) 481 result.setPixel(x*2+1,y*2, black) 482 result.setPixel(x*2, y*2, white) 483 return result 484 485 def overlay_marks(self, img, is_cseed=False, calibration_sheet=False): 486 border_color = Qt.white 487 base_img = QImage(self.f_size.width(),self.f_size.height(), QImage.Format_ARGB32) 488 base_img.fill(border_color) 489 img = QImage(img) 490 491 painter = QPainter() 492 painter.begin(base_img) 493 494 total_distance_h = round(base_img.width() / self.abstand_v) 495 dist_v = round(total_distance_h) / 2 496 dist_h = round(total_distance_h) / 2 497 498 img = img.scaledToWidth(base_img.width() - (2 * (total_distance_h))) 499 painter.drawImage(total_distance_h, 500 total_distance_h, 501 img) 502 503 #frame around image 504 pen = QPen(Qt.black, 2) 505 painter.setPen(pen) 506 507 #horz 508 painter.drawLine(0, total_distance_h, base_img.width(), total_distance_h) 509 painter.drawLine(0, base_img.height()-(total_distance_h), base_img.width(), base_img.height()-(total_distance_h)) 510 #vert 511 painter.drawLine(total_distance_h, 0, total_distance_h, base_img.height()) 512 painter.drawLine(base_img.width()-(total_distance_h), 0, base_img.width()-(total_distance_h), base_img.height()) 513 514 #border around img 515 border_thick = 6 516 Rpath = QPainterPath() 517 Rpath.addRect(QRectF((total_distance_h)+(border_thick/2), 518 (total_distance_h)+(border_thick/2), 519 base_img.width()-((total_distance_h)*2)-((border_thick)-1), 520 (base_img.height()-((total_distance_h))*2)-((border_thick)-1))) 521 pen = QPen(Qt.black, border_thick) 522 pen.setJoinStyle (Qt.MiterJoin) 523 524 painter.setPen(pen) 525 painter.drawPath(Rpath) 526 527 Bpath = QPainterPath() 528 Bpath.addRect(QRectF((total_distance_h), (total_distance_h), 529 base_img.width()-((total_distance_h)*2), (base_img.height()-((total_distance_h))*2))) 530 pen = QPen(Qt.black, 1) 531 painter.setPen(pen) 532 painter.drawPath(Bpath) 533 534 pen = QPen(Qt.black, 1) 535 painter.setPen(pen) 536 painter.drawLine(0, base_img.height()/2, total_distance_h, base_img.height()/2) 537 painter.drawLine(base_img.width()/2, 0, base_img.width()/2, total_distance_h) 538 539 painter.drawLine(base_img.width()-total_distance_h, base_img.height()/2, base_img.width(), base_img.height()/2) 540 painter.drawLine(base_img.width()/2, base_img.height(), base_img.width()/2, base_img.height() - total_distance_h) 541 542 #print code 543 f_size = 37 544 QFontDatabase.addApplicationFont(os.path.join(os.path.dirname(__file__), 'DejaVuSansMono-Bold.ttf')) 545 font = QFont("DejaVu Sans Mono", f_size-11, QFont.Bold) 546 font.setPixelSize(35) 547 painter.setFont(font) 548 549 if not calibration_sheet: 550 if is_cseed: #its a secret 551 painter.setPen(QPen(Qt.black, 1, Qt.DashDotDotLine)) 552 painter.drawLine(0, dist_v, base_img.width(), dist_v) 553 painter.drawLine(dist_h, 0, dist_h, base_img.height()) 554 painter.drawLine(0, base_img.height()-dist_v, base_img.width(), base_img.height()-(dist_v)) 555 painter.drawLine(base_img.width()-(dist_h), 0, base_img.width()-(dist_h), base_img.height()) 556 557 painter.drawImage(((total_distance_h))+11, ((total_distance_h))+11, 558 QImage(icon_path('electrumb.png')).scaledToWidth(2.1*(total_distance_h), Qt.SmoothTransformation)) 559 560 painter.setPen(QPen(Qt.white, border_thick*8)) 561 painter.drawLine(base_img.width()-((total_distance_h))-(border_thick*8)/2-(border_thick/2)-2, 562 (base_img.height()-((total_distance_h)))-((border_thick*8)/2)-(border_thick/2)-2, 563 base_img.width()-((total_distance_h))-(border_thick*8)/2-(border_thick/2)-2 - 77, 564 (base_img.height()-((total_distance_h)))-((border_thick*8)/2)-(border_thick/2)-2) 565 painter.setPen(QColor(0,0,0,255)) 566 painter.drawText(QRect(0, base_img.height()-107, base_img.width()-total_distance_h - border_thick - 11, 567 base_img.height()-total_distance_h - border_thick), Qt.AlignRight, 568 self.versioned_seed.version + '_'+self.versioned_seed.checksum) 569 painter.end() 570 571 else: # revealer 572 573 painter.setPen(QPen(border_color, 17)) 574 painter.drawLine(0, dist_v, base_img.width(), dist_v) 575 painter.drawLine(dist_h, 0, dist_h, base_img.height()) 576 painter.drawLine(0, base_img.height()-dist_v, base_img.width(), base_img.height()-(dist_v)) 577 painter.drawLine(base_img.width()-(dist_h), 0, base_img.width()-(dist_h), base_img.height()) 578 579 painter.setPen(QPen(Qt.black, 2)) 580 painter.drawLine(0, dist_v, base_img.width(), dist_v) 581 painter.drawLine(dist_h, 0, dist_h, base_img.height()) 582 painter.drawLine(0, base_img.height()-dist_v, base_img.width(), base_img.height()-(dist_v)) 583 painter.drawLine(base_img.width()-(dist_h), 0, base_img.width()-(dist_h), base_img.height()) 584 logo = QImage(icon_path('revealer_c.png')).scaledToWidth(1.3*(total_distance_h)) 585 painter.drawImage((total_distance_h)+ (border_thick), ((total_distance_h))+ (border_thick), logo, Qt.SmoothTransformation) 586 587 #frame around logo 588 painter.setPen(QPen(Qt.black, border_thick)) 589 painter.drawLine(total_distance_h+border_thick, total_distance_h+logo.height()+3*(border_thick/2), 590 total_distance_h+logo.width()+border_thick, total_distance_h+logo.height()+3*(border_thick/2)) 591 painter.drawLine(logo.width()+total_distance_h+3*(border_thick/2), total_distance_h+(border_thick), 592 total_distance_h+logo.width()+3*(border_thick/2), total_distance_h+logo.height()+(border_thick)) 593 594 #frame around code/qr 595 qr_size = 179 596 597 painter.drawLine((base_img.width()-((total_distance_h))-(border_thick/2)-2)-qr_size, 598 (base_img.height()-((total_distance_h)))-((border_thick*8))-(border_thick/2)-2, 599 (base_img.width()/2+(total_distance_h/2)-border_thick-(border_thick*8)/2)-qr_size, 600 (base_img.height()-((total_distance_h)))-((border_thick*8))-(border_thick/2)-2) 601 602 painter.drawLine((base_img.width()/2+(total_distance_h/2)-border_thick-(border_thick*8)/2)-qr_size, 603 (base_img.height()-((total_distance_h)))-((border_thick*8))-(border_thick/2)-2, 604 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/2)-2)) 606 607 painter.setPen(QPen(Qt.white, border_thick * 8)) 608 painter.drawLine( 609 base_img.width() - ((total_distance_h)) - (border_thick * 8) / 2 - (border_thick / 2) - 2, 610 (base_img.height() - ((total_distance_h))) - ((border_thick * 8) / 2) - (border_thick / 2) - 2, 611 base_img.width() / 2 + (total_distance_h / 2) - border_thick - qr_size, 612 (base_img.height() - ((total_distance_h))) - ((border_thick * 8) / 2) - (border_thick / 2) - 2) 613 614 painter.setPen(QColor(0,0,0,255)) 615 painter.drawText(QRect(((base_img.width()/2) +21)-qr_size, base_img.height()-107, 616 base_img.width()-total_distance_h - border_thick -93, 617 base_img.height()-total_distance_h - border_thick), Qt.AlignLeft, self.versioned_seed.get_ui_string_version_plus_seed()) 618 painter.drawText(QRect(0, base_img.height()-107, base_img.width()-total_distance_h - border_thick -3 -qr_size, 619 base_img.height()-total_distance_h - border_thick), Qt.AlignRight, self.versioned_seed.checksum) 620 621 # draw qr code 622 qr_qt = self.paintQR(self.versioned_seed.get_ui_string_version_plus_seed() 623 + self.versioned_seed.checksum) 624 target = QRectF(base_img.width()-65-qr_size, 625 base_img.height()-65-qr_size, 626 qr_size, qr_size) 627 painter.drawImage(target, qr_qt) 628 painter.setPen(QPen(Qt.black, 4)) 629 painter.drawLine(base_img.width()-65-qr_size, 630 base_img.height()-65-qr_size, 631 base_img.width() - 65 - qr_size, 632 (base_img.height() - ((total_distance_h))) - ((border_thick * 8)) - (border_thick / 2) - 4 633 ) 634 painter.drawLine(base_img.width()-65-qr_size, 635 base_img.height()-65-qr_size, 636 base_img.width() - 65, 637 base_img.height()-65-qr_size 638 ) 639 painter.end() 640 641 else: # calibration only 642 painter.end() 643 cal_img = QImage(self.f_size.width() + 100, self.f_size.height() + 100, 644 QImage.Format_ARGB32) 645 cal_img.fill(Qt.white) 646 647 cal_painter = QPainter() 648 cal_painter.begin(cal_img) 649 cal_painter.drawImage(0,0, base_img) 650 651 #black lines in the middle of border top left only 652 cal_painter.setPen(QPen(Qt.black, 1, Qt.DashDotDotLine)) 653 cal_painter.drawLine(0, dist_v, base_img.width(), dist_v) 654 cal_painter.drawLine(dist_h, 0, dist_h, base_img.height()) 655 656 pen = QPen(Qt.black, 2, Qt.DashDotDotLine) 657 cal_painter.setPen(pen) 658 n=15 659 660 cal_painter.setFont(QFont("DejaVu Sans Mono", 21, QFont.Bold)) 661 for x in range(-n,n): 662 #lines on bottom (vertical calibration) 663 cal_painter.drawLine((((base_img.width())/(n*2)) *(x))+ (base_img.width()/2)-13, 664 x+2+base_img.height()-(dist_v), 665 (((base_img.width())/(n*2)) *(x))+ (base_img.width()/2)+13, 666 x+2+base_img.height()-(dist_v)) 667 668 num_pos = 9 669 if x > 9 : num_pos = 17 670 if x < 0 : num_pos = 20 671 if x < -9: num_pos = 27 672 673 cal_painter.drawText((((base_img.width())/(n*2)) *(x))+ (base_img.width()/2)-num_pos, 674 50+base_img.height()-(dist_v), 675 str(x)) 676 677 #lines on the right (horizontal calibrations) 678 679 cal_painter.drawLine(x+2+(base_img.width()-(dist_h)), 680 ((base_img.height()/(2*n)) *(x))+ (base_img.height()/n)+(base_img.height()/2)-13, 681 x+2+(base_img.width()-(dist_h)), 682 ((base_img.height()/(2*n)) *(x))+ (base_img.height()/n)+(base_img.height()/2)+13) 683 684 685 cal_painter.drawText(30+(base_img.width()-(dist_h)), 686 ((base_img.height()/(2*n)) *(x))+ (base_img.height()/2)+13, str(x)) 687 688 cal_painter.end() 689 base_img = cal_img 690 691 return base_img 692 693 def paintQR(self, data): 694 if not data: 695 return 696 qr = qrcode.QRCode() 697 qr.add_data(data) 698 matrix = qr.get_matrix() 699 k = len(matrix) 700 border_color = Qt.white 701 base_img = QImage(k * 5, k * 5, QImage.Format_ARGB32) 702 base_img.fill(border_color) 703 qrpainter = QPainter() 704 qrpainter.begin(base_img) 705 boxsize = 5 706 size = k * boxsize 707 left = (base_img.width() - size)/2 708 top = (base_img.height() - size)/2 709 qrpainter.setBrush(Qt.black) 710 qrpainter.setPen(Qt.black) 711 712 for r in range(k): 713 for c in range(k): 714 if matrix[r][c]: 715 qrpainter.drawRect(left+c*boxsize, top+r*boxsize, boxsize - 1, boxsize - 1) 716 qrpainter.end() 717 return base_img 718 719 def calibration_dialog(self, window): 720 d = WindowModalDialog(window, _("Revealer - Printer calibration settings")) 721 722 d.setMinimumSize(100, 200) 723 724 vbox = QVBoxLayout(d) 725 vbox.addWidget(QLabel(''.join(["<br/>", _("If you have an old printer, or want optimal precision"),"<br/>", 726 _("print the calibration pdf and follow the instructions "), "<br/>","<br/>", 727 ]))) 728 self.calibration_h = self.config.get('calibration_h') 729 self.calibration_v = self.config.get('calibration_v') 730 cprint = QPushButton(_("Open calibration pdf")) 731 cprint.clicked.connect(self.calibration) 732 vbox.addWidget(cprint) 733 734 vbox.addWidget(QLabel(_('Calibration values:'))) 735 grid = QGridLayout() 736 vbox.addLayout(grid) 737 grid.addWidget(QLabel(_('Right side')), 0, 0) 738 horizontal = QLineEdit() 739 horizontal.setText(str(self.calibration_h)) 740 grid.addWidget(horizontal, 0, 1) 741 742 grid.addWidget(QLabel(_('Bottom')), 1, 0) 743 vertical = QLineEdit() 744 vertical.setText(str(self.calibration_v)) 745 grid.addWidget(vertical, 1, 1) 746 747 vbox.addStretch() 748 vbox.addSpacing(13) 749 vbox.addLayout(Buttons(CloseButton(d), OkButton(d))) 750 751 if not d.exec_(): 752 return 753 754 self.calibration_h = int(Decimal(horizontal.text())) 755 self.config.set_key('calibration_h', self.calibration_h) 756 self.calibration_v = int(Decimal(vertical.text())) 757 self.config.set_key('calibration_v', self.calibration_v) 758 759 760