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