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