You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 

867 lines
38 KiB

'''
Revealer
Do you have something to hide?
Secret backup plug-in for the electrum wallet.
Copyright:
2017 Tiago Romagnani Silveira
2023 Soren Stoutner <soren@debian.org>
Distributed under the MIT software license, see the accompanying
file LICENCE or http://www.opensource.org/licenses/mit-license.php
'''
import os
import random
import traceback
from decimal import Decimal
from functools import partial
import sys
import qrcode
from PyQt5.QtPrintSupport import QPrinter
from PyQt5.QtCore import Qt, QRectF, QRect, QSizeF, QUrl, QPoint, QSize
from PyQt5.QtGui import (QPixmap, QImage, QBitmap, QPainter, QFontDatabase, QPen, QFont,
QColor, QDesktopServices, qRgba, QPainterPath)
from PyQt5.QtWidgets import (QGridLayout, QVBoxLayout, QHBoxLayout, QLabel,
QPushButton, QLineEdit)
from electrum.plugin import hook
from electrum.i18n import _
from electrum.util import make_dir, InvalidPassword, UserCancelled
from electrum.gui.qt.util import (read_QIcon, EnterButton, WWLabel, icon_path,
WindowModalDialog, Buttons, CloseButton, OkButton)
from electrum.gui.qt.qrtextedit import ScanQRTextEdit
from electrum.gui.qt.main_window import StatusBarButton
from .revealer import RevealerPlugin
class Plugin(RevealerPlugin):
MAX_PLAINTEXT_LEN = 189 # chars
def __init__(self, parent, config, name):
RevealerPlugin.__init__(self, parent, config, name)
self.base_dir = os.path.join(config.electrum_path(), 'revealer')
if self.config.get('calibration_h') is None:
self.config.set_key('calibration_h', 0)
if self.config.get('calibration_v') is None:
self.config.set_key('calibration_v', 0)
self.calibration_h = self.config.get('calibration_h')
self.calibration_v = self.config.get('calibration_v')
self.f_size = QSize(1014*2, 642*2)
self.abstand_h = 21
self.abstand_v = 34
self.calibration_noise = int('10' * 128)
self.rawnoise = False
make_dir(self.base_dir)
self.extension = False
@hook
def create_status_bar(self, sb):
b = StatusBarButton(read_QIcon('revealer.png'), "Revealer "+_("Visual Cryptography Plugin"),
partial(self.setup_dialog, sb), sb.height())
sb.addPermanentWidget(b)
def requires_settings(self):
return True
def settings_widget(self, window):
return EnterButton(_('Printer Calibration'), partial(self.calibration_dialog, window))
def password_dialog(self, msg=None, parent=None):
from electrum.gui.qt.password_dialog import PasswordDialog
parent = parent or self
d = PasswordDialog(parent, msg)
return d.run()
def get_seed(self):
password = None
if self.wallet.has_keystore_encryption():
password = self.password_dialog(parent=self.d.parent())
if not password:
raise UserCancelled()
keystore = self.wallet.get_keystore()
if not keystore or not keystore.has_seed():
return
self.extension = bool(keystore.get_passphrase(password))
return keystore.get_seed(password)
def setup_dialog(self, window):
self.wallet = window.parent().wallet
self.update_wallet_name(self.wallet)
self.user_input = False
self.d = WindowModalDialog(window, "Revealer Visual Cryptography Plugin - Select Noise File")
self.d.setContentsMargins(11,11,1,1)
# Create an HBox layout. The logo will be on the left and the rest of the dialog on the right.
hbox_layout = QHBoxLayout(self.d)
# Create the logo label.
logo_label = QLabel()
# Set the logo label pixmap.
logo_label.setPixmap(QPixmap(icon_path('revealer.png')))
# Align the logo label to the top left.
logo_label.setAlignment(Qt.AlignLeft)
# Create a VBox layout for the main contents of the dialog.
vbox_layout = QVBoxLayout()
# Populate the HBox layout with spacing between the two columns.
hbox_layout.addWidget(logo_label)
hbox_layout.addSpacing(16)
hbox_layout.addLayout(vbox_layout)
# Create the labels.
create_or_load_noise_file_label = QLabel(_("To encrypt a secret, you must first create or load a noise file."))
instructions_label = QLabel(_("Click the button above or type an existing revealer code in the box below."))
# Allow users to select text in the labels.
create_or_load_noise_file_label.setTextInteractionFlags(Qt.TextSelectableByMouse)
instructions_label.setTextInteractionFlags(Qt.TextSelectableByMouse)
# Create the buttons.
create_button = QPushButton(_("Create a new Revealer noise file"))
self.next_button = QPushButton(_("Next"), self.d)
# Calculate the desired width of the create button
create_button_width = create_button.fontMetrics().boundingRect(create_button.text()).width() + 40
# Set the create button width.
create_button.setMaximumWidth(create_button_width)
# Set the create button to be the default.
create_button.setDefault(True)
# Initially disable the next button.
self.next_button.setEnabled(False)
# Define the create noise file function.
def create_noise_file():
try:
self.make_digital(self.d)
except Exception:
self.logger.exception('')
else:
self.cypherseed_dialog(window)
# Handle clicks on the buttons.
create_button.clicked.connect(create_noise_file)
self.next_button.clicked.connect(self.d.close)
self.next_button.clicked.connect(partial(self.cypherseed_dialog, window))
# Create the noise scan QR text edit.
self.noise_scan_qr_textedit = ScanQRTextEdit(config=self.config)
# Make tabs change focus from the text edit instead of inserting a tab into the field.
self.noise_scan_qr_textedit.setTabChangesFocus(True)
# Update the UI when the text changes.
self.noise_scan_qr_textedit.textChanged.connect(self.on_edit)
# Populate the VBox layout.
vbox_layout.addWidget(create_or_load_noise_file_label)
vbox_layout.addWidget(create_button, alignment=Qt.AlignCenter)
vbox_layout.addWidget(instructions_label)
vbox_layout.addWidget(self.noise_scan_qr_textedit)
vbox_layout.addLayout(Buttons(self.next_button))
# Add stretches to the end of the layouts to prevent the contents from spreading when the dialog is enlarged.
hbox_layout.addStretch(1)
vbox_layout.addStretch(1)
return bool(self.d.exec_())
def get_noise(self):
# Get the text from the scan QR text edit.
text = self.noise_scan_qr_textedit.text()
return ''.join(text.split()).lower()
def on_edit(self):
txt = self.get_noise()
versioned_seed = self.get_versioned_seed_from_user_input(txt)
if versioned_seed:
self.versioned_seed = versioned_seed
self.user_input = bool(versioned_seed)
self.next_button.setEnabled(bool(versioned_seed))
def make_digital(self, dialog):
self.make_rawnoise(True)
self.bdone(dialog)
self.d.close()
def get_path_to_revealer_file(self, ext: str= '') -> str:
version = self.versioned_seed.version
code_id = self.versioned_seed.checksum
filename = self.filename_prefix + version + "_" + code_id + ext
path = os.path.join(self.base_dir, filename)
return os.path.normcase(os.path.abspath(path))
def get_path_to_calibration_file(self):
path = os.path.join(self.base_dir, 'calibration.pdf')
return os.path.normcase(os.path.abspath(path))
def bcrypt(self, dialog):
self.rawnoise = False
version = self.versioned_seed.version
code_id = self.versioned_seed.checksum
dialog.show_message(''.join([_("{} encrypted for Revealer {}_{} saved as PNG and PDF at: ").format(self.was, version, code_id),
"<b>", self.get_path_to_revealer_file(), "</b>", "<br/>",
"<br/>", "<b>", _("Always check your backups.")]),
rich_text=True)
dialog.close()
def ext_warning(self, dialog):
dialog.show_message(''.join(["<b>",_("Warning"), ": </b>",
_("your seed extension will <b>not</b> be included in the encrypted backup.")]),
rich_text=True)
dialog.close()
def bdone(self, dialog):
version = self.versioned_seed.version
code_id = self.versioned_seed.checksum
dialog.show_message(''.join([_("Digital Revealer ({}_{}) saved as PNG and PDF at:").format(version, code_id),
"<br/>","<b>", self.get_path_to_revealer_file(), '</b>']),
rich_text=True)
def customtxt_limits(self):
txt = self.custom_secret_scan_qr_textedit.text()
self.custom_secret_character_count_label.setText(f"({len(txt)}/{self.MAX_PLAINTEXT_LEN})")
# Hide the custom secret maximum characters warning label.
self.custom_secret_maximum_characters_warning_label.setVisible(False)
# Update the status of the encrypt custom secret button.
self.encrypt_custom_secret_button.setEnabled(len(txt)>0)
# Check to make sure the length of the text has not exceeded the limit.
if len(txt) > self.MAX_PLAINTEXT_LEN:
# Truncate the text to the maximum limit.
self.custom_secret_scan_qr_textedit.setPlainText(txt[:self.MAX_PLAINTEXT_LEN])
# Get the text cursor.
textCursor = self.custom_secret_scan_qr_textedit.textCursor()
# Move the cursor position to the end (setting the text above automatically moves the cursor to the beginning, which is undesirable)
textCursor.movePosition(textCursor.End)
# Set the text cursor with the corrected position.
self.custom_secret_scan_qr_textedit.setTextCursor(textCursor)
# Display the custom secret maximum characters warning label.
self.custom_secret_maximum_characters_warning_label.setVisible(True)
def t(self):
self.txt = self.custom_secret_scan_qr_textedit.text()
self.seed_img(is_seed=False)
def warn_old_revealer(self):
if self.versioned_seed.version == '0':
link = "https://revealer.cc/revealer-warning-and-upgrade/"
self.d.show_warning(("<b>{warning}: </b>{ver0}<br>"
"{url}<br>"
"{risk}")
.format(warning=_("Warning"),
ver0=_("Revealers starting with 0 are not secure due to a vulnerability."),
url=_("More info at: {}").format(f'<a href="{link}">{link}</a>'),
risk=_("Proceed at your own risk.")),
rich_text=True)
def cypherseed_dialog(self, window):
self.warn_old_revealer()
d = WindowModalDialog(window, "Revealer Visual Cryptography Plugin - Encryption Data")
d.setContentsMargins(11, 11, 1, 1)
self.c_dialog = d
# Create an HBox layout. The logo will be on the left and the rest of the dialog on the right.
hbox_layout = QHBoxLayout(d)
# Create the logo label.
logo_label = QLabel()
# Set the logo label pixmap.
logo_label.setPixmap(QPixmap(icon_path('revealer.png')))
# Align the logo label to the top left.
logo_label.setAlignment(Qt.AlignLeft)
# Create a VBox layout for the main contents of the dialog.
vbox_layout = QVBoxLayout()
# Populate the HBox layout.
hbox_layout.addWidget(logo_label)
hbox_layout.addSpacing(16)
hbox_layout.addLayout(vbox_layout)
# Create the labels.
ready_to_encrypt_label = QLabel(_("Ready to encrypt for revealer {}.").format(self.versioned_seed.version+'_'+self.versioned_seed.checksum))
instructions_label = QLabel(_("Click the button above to encrypt the seed or type a custom alphanumerical secret below."))
self.custom_secret_character_count_label = QLabel(f"(0/{self.MAX_PLAINTEXT_LEN})")
self.custom_secret_maximum_characters_warning_label = QLabel("<font color='red'>"
+ _("This version supports a maximum of {} characters.").format(self.MAX_PLAINTEXT_LEN)
+"</font>")
one_time_pad_warning_label = QLabel("<b>" + _("Warning ") + "</b>: " + _("each Revealer is a one-time-pad, use it for a single secret."))
# Allow users to select text in the labels.
ready_to_encrypt_label.setTextInteractionFlags(Qt.TextSelectableByMouse)
instructions_label.setTextInteractionFlags(Qt.TextSelectableByMouse)
self.custom_secret_character_count_label.setTextInteractionFlags(Qt.TextSelectableByMouse)
self.custom_secret_maximum_characters_warning_label
one_time_pad_warning_label.setTextInteractionFlags(Qt.TextSelectableByMouse)
# Align the custom secret character count label to the right.
self.custom_secret_character_count_label.setAlignment(Qt.AlignRight)
# Initially hide the custom secret character count label.
self.custom_secret_maximum_characters_warning_label.setVisible(False)
# Create the buttons.
encrypt_seed_button = QPushButton(_("Encrypt {}'s seed").format(self.wallet_name))
self.encrypt_custom_secret_button = QPushButton(_("Encrypt custom secret"))
# Calculate the desired width of the buttons.
encrypt_seed_button_width = encrypt_seed_button.fontMetrics().boundingRect(encrypt_seed_button.text()).width() + 40
encrypt_custom_secret_button_width = self.encrypt_custom_secret_button.fontMetrics().boundingRect(self.encrypt_custom_secret_button.text()).width() + 40
# Set the button widths.
encrypt_seed_button.setMaximumWidth(encrypt_seed_button_width)
self.encrypt_custom_secret_button.setMaximumWidth(encrypt_custom_secret_button_width)
# Set the encrypt seed button to be the default.
encrypt_seed_button.setDefault(True)
# Initially disable the encrypt custom secret button.
self.encrypt_custom_secret_button.setEnabled(False)
# Handle clicks on the buttons.
encrypt_seed_button.clicked.connect(partial(self.seed_img, True))
self.encrypt_custom_secret_button.clicked.connect(self.t)
# Create the custom secret scan QR text edit.
self.custom_secret_scan_qr_textedit = ScanQRTextEdit(config=self.config)
# Make tabs change focus from the text edit instead of inserting a tab into the field.
self.custom_secret_scan_qr_textedit.setTabChangesFocus(True)
# Update the UI when the custom secret text changes.
self.custom_secret_scan_qr_textedit.textChanged.connect(self.customtxt_limits)
# Populate the VBox layout.
vbox_layout.addWidget(ready_to_encrypt_label)
vbox_layout.addWidget(encrypt_seed_button, alignment=Qt.AlignCenter)
vbox_layout.addWidget(instructions_label)
vbox_layout.addWidget(self.custom_secret_scan_qr_textedit)
vbox_layout.addWidget(self.custom_secret_character_count_label)
vbox_layout.addWidget(self.custom_secret_maximum_characters_warning_label)
vbox_layout.addWidget(self.encrypt_custom_secret_button, alignment=Qt.AlignCenter)
vbox_layout.addSpacing(40)
vbox_layout.addWidget(one_time_pad_warning_label)
vbox_layout.addLayout(Buttons(CloseButton(d)))
# Add stretches to the end of the layouts to prevent the contents from spreading when the dialog is enlarged.
hbox_layout.addStretch(1)
vbox_layout.addStretch(1)
return bool(d.exec_())
def update_wallet_name(self, name):
self.wallet_name = str(name)
def seed_img(self, is_seed = True):
if is_seed:
try:
cseed = self.get_seed()
except UserCancelled:
return
except InvalidPassword as e:
self.d.show_error(str(e))
return
if not cseed:
self.d.show_message(_("This wallet has no seed"))
return
txt = cseed.upper()
else:
txt = self.txt.upper()
img = QImage(self.SIZE[0], self.SIZE[1], QImage.Format_Mono)
bitmap = QBitmap.fromImage(img, Qt.MonoOnly)
bitmap.fill(Qt.white)
painter = QPainter()
painter.begin(bitmap)
QFontDatabase.addApplicationFont(os.path.join(os.path.dirname(__file__), 'SourceSansPro-Bold.otf'))
if len(txt) < 102 :
fontsize = 15
linespace = 15
max_letters = 17
max_lines = 6
max_words = 3
else:
fontsize = 12
linespace = 10
max_letters = 21
max_lines = 9
max_words = int(max_letters/4)
font = QFont('Source Sans Pro', fontsize, QFont.Bold)
font.setLetterSpacing(QFont.PercentageSpacing, 100)
font.setPixelSize(fontsize)
painter.setFont(font)
seed_array = txt.split(' ')
for n in range(max_lines):
nwords = max_words
temp_seed = seed_array[:nwords]
while len(' '.join(map(str, temp_seed))) > max_letters:
nwords = nwords - 1
temp_seed = seed_array[:nwords]
painter.drawText(QRect(0, linespace*n, self.SIZE[0], self.SIZE[1]), Qt.AlignHCenter, ' '.join(map(str, temp_seed)))
del seed_array[:nwords]
painter.end()
img = bitmap.toImage()
if not self.rawnoise:
self.make_rawnoise()
self.make_cypherseed(img, self.rawnoise, False, is_seed)
return img
def make_rawnoise(self, create_revealer=False):
if not self.user_input:
self.versioned_seed = self.gen_random_versioned_seed()
assert self.versioned_seed
w, h = self.SIZE
rawnoise = QImage(w, h, QImage.Format_Mono)
noise_map = self.get_noise_map(self.versioned_seed)
for (x,y), pixel in noise_map.items():
rawnoise.setPixel(x, y, pixel)
self.rawnoise = rawnoise
if create_revealer:
self.make_revealer()
def make_calnoise(self):
random.seed(self.calibration_noise)
w, h = self.SIZE
rawnoise = QImage(w, h, QImage.Format_Mono)
for x in range(w):
for y in range(h):
rawnoise.setPixel(x,y,random.randint(0, 1))
self.calnoise = self.pixelcode_2x2(rawnoise)
def make_revealer(self):
revealer = self.pixelcode_2x2(self.rawnoise)
revealer.invertPixels()
revealer = QBitmap.fromImage(revealer)
revealer = revealer.scaled(self.f_size, Qt.KeepAspectRatio)
revealer = self.overlay_marks(revealer)
self.filename_prefix = 'revealer_'
revealer.save(self.get_path_to_revealer_file('.png'))
self.toPdf(QImage(revealer))
def make_cypherseed(self, img, rawnoise, calibration=False, is_seed = True):
img = img.convertToFormat(QImage.Format_Mono)
p = QPainter()
p.begin(img)
p.setCompositionMode(26) #xor
p.drawImage(0, 0, rawnoise)
p.end()
cypherseed = self.pixelcode_2x2(img)
cypherseed = QBitmap.fromImage(cypherseed)
cypherseed = cypherseed.scaled(self.f_size, Qt.KeepAspectRatio)
cypherseed = self.overlay_marks(cypherseed, True, calibration)
if not is_seed:
self.filename_prefix = 'custom_secret_'
self.was = _('Custom secret')
else:
self.filename_prefix = self.wallet_name + '_seed_'
self.was = self.wallet_name + ' ' + _('seed')
if self.extension:
self.ext_warning(self.c_dialog)
if not calibration:
self.toPdf(QImage(cypherseed))
cypherseed.save(self.get_path_to_revealer_file('.png'))
self.bcrypt(self.c_dialog)
return cypherseed
def calibration(self):
img = QImage(self.SIZE[0], self.SIZE[1], QImage.Format_Mono)
bitmap = QBitmap.fromImage(img, Qt.MonoOnly)
bitmap.fill(Qt.black)
self.make_calnoise()
img = self.overlay_marks(self.calnoise.scaledToHeight(self.f_size.height()), False, True)
self.calibration_pdf(img)
QDesktopServices.openUrl(QUrl.fromLocalFile(self.get_path_to_calibration_file()))
return img
def toPdf(self, image):
printer = QPrinter()
printer.setPaperSize(QSizeF(210, 297), QPrinter.Millimeter)
printer.setResolution(600)
printer.setOutputFormat(QPrinter.PdfFormat)
printer.setOutputFileName(self.get_path_to_revealer_file('.pdf'))
printer.setPageMargins(0,0,0,0,6)
painter = QPainter()
painter.begin(printer)
delta_h = round(image.width()/self.abstand_v)
delta_v = round(image.height()/self.abstand_h)
size_h = round(2028+((int(self.calibration_h)*2028/(2028-(delta_h*2)+int(self.calibration_h)))//2))
size_v = round(1284+((int(self.calibration_v)*1284/(1284-(delta_v*2)+int(self.calibration_v)))//2))
image = image.scaled(size_h, size_v)
painter.drawImage(553,533, image)
wpath = QPainterPath()
wpath.addRoundedRect(QRectF(553,533, size_h, size_v), 19, 19)
painter.setPen(QPen(Qt.black, 1))
painter.drawPath(wpath)
painter.end()
def calibration_pdf(self, image):
printer = QPrinter()
printer.setPaperSize(QSizeF(210, 297), QPrinter.Millimeter)
printer.setResolution(600)
printer.setOutputFormat(QPrinter.PdfFormat)
printer.setOutputFileName(self.get_path_to_calibration_file())
printer.setPageMargins(0,0,0,0,6)
painter = QPainter()
painter.begin(printer)
painter.drawImage(553,533, image)
font = QFont('Source Sans Pro', 10, QFont.Bold)
painter.setFont(font)
painter.drawText(254,277, _("Calibration sheet"))
font = QFont('Source Sans Pro', 7, QFont.Bold)
painter.setFont(font)
painter.drawText(600,2077, _("Instructions:"))
font = QFont('Source Sans Pro', 7, QFont.Normal)
painter.setFont(font)
painter.drawText(700, 2177, _("1. Place this paper on a flat and well illuminated surface."))
painter.drawText(700, 2277, _("2. Align your Revealer borderlines to the dashed lines on the top and left."))
painter.drawText(700, 2377, _("3. Press slightly the Revealer against the paper and read the numbers that best "
"match on the opposite sides. "))
painter.drawText(700, 2477, _("4. Type the numbers in the software"))
painter.end()
def pixelcode_2x2(self, img):
result = QImage(img.width()*2, img.height()*2, QImage.Format_ARGB32)
white = qRgba(255,255,255,0)
black = qRgba(0,0,0,255)
for x in range(img.width()):
for y in range(img.height()):
c = img.pixel(QPoint(x,y))
colors = QColor(c).getRgbF()
if colors[0]:
result.setPixel(x*2+1,y*2+1, black)
result.setPixel(x*2,y*2+1, white)
result.setPixel(x*2+1,y*2, white)
result.setPixel(x*2, y*2, black)
else:
result.setPixel(x*2+1,y*2+1, white)
result.setPixel(x*2,y*2+1, black)
result.setPixel(x*2+1,y*2, black)
result.setPixel(x*2, y*2, white)
return result
def overlay_marks(self, img, is_cseed=False, calibration_sheet=False):
border_color = Qt.white
base_img = QImage(self.f_size.width(),self.f_size.height(), QImage.Format_ARGB32)
base_img.fill(border_color)
img = QImage(img)
painter = QPainter()
painter.begin(base_img)
total_distance_h = round(base_img.width() / self.abstand_v)
dist_v = round(total_distance_h) // 2
dist_h = round(total_distance_h) // 2
img = img.scaledToWidth(base_img.width() - (2 * (total_distance_h)))
painter.drawImage(total_distance_h,
total_distance_h,
img)
#frame around image
pen = QPen(Qt.black, 2)
painter.setPen(pen)
#horz
painter.drawLine(0, total_distance_h, base_img.width(), total_distance_h)
painter.drawLine(0, base_img.height()-(total_distance_h), base_img.width(), base_img.height()-(total_distance_h))
#vert
painter.drawLine(total_distance_h, 0, total_distance_h, base_img.height())
painter.drawLine(base_img.width()-(total_distance_h), 0, base_img.width()-(total_distance_h), base_img.height())
#border around img
border_thick = 6
Rpath = QPainterPath()
Rpath.addRect(QRectF((total_distance_h)+(border_thick/2),
(total_distance_h)+(border_thick/2),
base_img.width()-((total_distance_h)*2)-((border_thick)-1),
(base_img.height()-((total_distance_h))*2)-((border_thick)-1)))
pen = QPen(Qt.black, border_thick)
pen.setJoinStyle (Qt.MiterJoin)
painter.setPen(pen)
painter.drawPath(Rpath)
Bpath = QPainterPath()
Bpath.addRect(QRectF((total_distance_h), (total_distance_h),
base_img.width()-((total_distance_h)*2), (base_img.height()-((total_distance_h))*2)))
pen = QPen(Qt.black, 1)
painter.setPen(pen)
painter.drawPath(Bpath)
pen = QPen(Qt.black, 1)
painter.setPen(pen)
painter.drawLine(0, base_img.height()//2, total_distance_h, base_img.height()//2)
painter.drawLine(base_img.width()//2, 0, base_img.width()//2, total_distance_h)
painter.drawLine(base_img.width()-total_distance_h, base_img.height()//2, base_img.width(), base_img.height()//2)
painter.drawLine(base_img.width()//2, base_img.height(), base_img.width()//2, base_img.height() - total_distance_h)
#print code
f_size = 37
QFontDatabase.addApplicationFont(os.path.join(os.path.dirname(__file__), 'DejaVuSansMono-Bold.ttf'))
font = QFont("DejaVu Sans Mono", f_size-11, QFont.Bold)
font.setPixelSize(35)
painter.setFont(font)
if not calibration_sheet:
if is_cseed: #its a secret
painter.setPen(QPen(Qt.black, 1, Qt.DashDotDotLine))
painter.drawLine(0, dist_v, base_img.width(), dist_v)
painter.drawLine(dist_h, 0, dist_h, base_img.height())
painter.drawLine(0, base_img.height()-dist_v, base_img.width(), base_img.height()-(dist_v))
painter.drawLine(base_img.width()-(dist_h), 0, base_img.width()-(dist_h), base_img.height())
painter.drawImage(((total_distance_h))+11, ((total_distance_h))+11,
QImage(icon_path('electrumb.png')).scaledToWidth(round(2.1*total_distance_h), Qt.SmoothTransformation))
painter.setPen(QPen(Qt.white, border_thick*8))
painter.drawLine(int(base_img.width()-total_distance_h-(border_thick*8)/2-(border_thick/2)-2),
int(base_img.height()-total_distance_h-((border_thick*8)/2)-(border_thick/2)-2),
int(base_img.width()-total_distance_h-(border_thick*8)/2-(border_thick/2)-2 - 77),
int(base_img.height()-total_distance_h-((border_thick*8)/2)-(border_thick/2)-2))
painter.setPen(QColor(0,0,0,255))
painter.drawText(QRect(0, base_img.height()-107, base_img.width()-total_distance_h - border_thick - 11,
base_img.height()-total_distance_h - border_thick), Qt.AlignRight,
self.versioned_seed.version + '_'+self.versioned_seed.checksum)
painter.end()
else: # revealer
painter.setPen(QPen(border_color, 17))
painter.drawLine(0, dist_v, base_img.width(), dist_v)
painter.drawLine(dist_h, 0, dist_h, base_img.height())
painter.drawLine(0, base_img.height()-dist_v, base_img.width(), base_img.height()-(dist_v))
painter.drawLine(base_img.width()-(dist_h), 0, base_img.width()-(dist_h), base_img.height())
painter.setPen(QPen(Qt.black, 2))
painter.drawLine(0, dist_v, base_img.width(), dist_v)
painter.drawLine(dist_h, 0, dist_h, base_img.height())
painter.drawLine(0, base_img.height()-dist_v, base_img.width(), base_img.height()-(dist_v))
painter.drawLine(base_img.width()-(dist_h), 0, base_img.width()-(dist_h), base_img.height())
logo = QImage(icon_path('revealer_c.png')).scaledToWidth(round(1.3*(total_distance_h)))
painter.drawImage(int(total_distance_h+border_thick), int(total_distance_h+border_thick), logo, Qt.SmoothTransformation)
#frame around logo
painter.setPen(QPen(Qt.black, border_thick))
painter.drawLine(int(total_distance_h+border_thick), int(total_distance_h+logo.height()+3*(border_thick/2)),
int(total_distance_h+logo.width()+border_thick), int(total_distance_h+logo.height()+3*(border_thick/2)))
painter.drawLine(int(logo.width()+total_distance_h+3*(border_thick/2)), int(total_distance_h+(border_thick)),
int(total_distance_h+logo.width()+3*(border_thick/2)), int(total_distance_h+logo.height()+(border_thick)))
#frame around code/qr
qr_size = 179
painter.drawLine(int((base_img.width()-((total_distance_h))-(border_thick/2)-2)-qr_size),
int((base_img.height()-((total_distance_h)))-((border_thick*8))-(border_thick/2)-2),
int((base_img.width()//2+(total_distance_h/2)-border_thick-(border_thick*8)//2)-qr_size),
int((base_img.height()-((total_distance_h)))-((border_thick*8))-(border_thick/2)-2))
painter.drawLine(int((base_img.width()//2+(total_distance_h/2)-border_thick-(border_thick*8)//2)-qr_size),
int((base_img.height()-((total_distance_h)))-((border_thick*8))-(border_thick/2)-2),
int(base_img.width()//2 + (total_distance_h/2)-border_thick-(border_thick*8)//2-qr_size),
int((base_img.height()-((total_distance_h)))-(border_thick/2)-2))
painter.setPen(QPen(Qt.white, border_thick * 8))
painter.drawLine(
int(base_img.width() - ((total_distance_h)) - (border_thick * 8) / 2 - (border_thick / 2) - 2),
int((base_img.height() - ((total_distance_h))) - ((border_thick * 8) / 2) - (border_thick / 2) - 2),
int(base_img.width() / 2 + (total_distance_h / 2) - border_thick - qr_size),
int((base_img.height() - ((total_distance_h))) - ((border_thick * 8) / 2) - (border_thick / 2) - 2))
painter.setPen(QColor(0,0,0,255))
painter.drawText(QRect(int(((base_img.width()/2) +21)-qr_size),
int(base_img.height()-107),
int(base_img.width()-total_distance_h - border_thick -93),
int(base_img.height()-total_distance_h - border_thick)),
Qt.AlignLeft, self.versioned_seed.get_ui_string_version_plus_seed())
painter.drawText(QRect(0, base_img.height()-107, base_img.width()-total_distance_h - border_thick -3 -qr_size,
base_img.height()-total_distance_h - border_thick), Qt.AlignRight, self.versioned_seed.checksum)
# draw qr code
qr_qt = self.paintQR(self.versioned_seed.get_ui_string_version_plus_seed()
+ self.versioned_seed.checksum)
target = QRectF(base_img.width()-65-qr_size,
base_img.height()-65-qr_size,
qr_size, qr_size)
painter.drawImage(target, qr_qt)
painter.setPen(QPen(Qt.black, 4))
painter.drawLine(
int(base_img.width()-65-qr_size),
int(base_img.height()-65-qr_size),
int(base_img.width() - 65 - qr_size),
int((base_img.height() - total_distance_h) - (border_thick * 8) - (border_thick / 2) - 4),
)
painter.drawLine(
int(base_img.width()-65-qr_size),
int(base_img.height()-65-qr_size),
int(base_img.width() - 65),
int(base_img.height()-65-qr_size),
)
painter.end()
else: # calibration only
painter.end()
cal_img = QImage(self.f_size.width() + 100, self.f_size.height() + 100,
QImage.Format_ARGB32)
cal_img.fill(Qt.white)
cal_painter = QPainter()
cal_painter.begin(cal_img)
cal_painter.drawImage(0,0, base_img)
#black lines in the middle of border top left only
cal_painter.setPen(QPen(Qt.black, 1, Qt.DashDotDotLine))
cal_painter.drawLine(0, dist_v, base_img.width(), dist_v)
cal_painter.drawLine(dist_h, 0, dist_h, base_img.height())
pen = QPen(Qt.black, 2, Qt.DashDotDotLine)
cal_painter.setPen(pen)
n=15
cal_painter.setFont(QFont("DejaVu Sans Mono", 21, QFont.Bold))
for x in range(-n,n):
#lines on bottom (vertical calibration)
cal_painter.drawLine(int((((base_img.width())/(n*2)) *(x))+ (base_img.width()//2)-13),
int(x+2+base_img.height()-(dist_v)),
int((((base_img.width())/(n*2)) *(x))+ (base_img.width()//2)+13),
int(x+2+base_img.height()-(dist_v)))
num_pos = 9
if x > 9 : num_pos = 17
if x < 0 : num_pos = 20
if x < -9: num_pos = 27
cal_painter.drawText(int((((base_img.width())/(n*2)) *(x)) + (base_img.width()//2)-num_pos),
int(50+base_img.height()-(dist_v)),
str(x))
#lines on the right (horizontal calibrations)
cal_painter.drawLine(int(x+2+(base_img.width()-(dist_h))),
int(((base_img.height()/(2*n)) *(x))+ (base_img.height()/n)+(base_img.height()//2)-13),
int(x+2+(base_img.width()-(dist_h))),
int(((base_img.height()/(2*n)) *(x))+ (base_img.height()/n)+(base_img.height()//2)+13))
cal_painter.drawText(int(30+(base_img.width()-(dist_h))),
int(((base_img.height()/(2*n)) *(x))+ (base_img.height()//2)+13),
str(x))
cal_painter.end()
base_img = cal_img
return base_img
def paintQR(self, data):
if not data:
return
qr = qrcode.QRCode()
qr.add_data(data)
matrix = qr.get_matrix()
k = len(matrix)
border_color = Qt.white
base_img = QImage(k * 5, k * 5, QImage.Format_ARGB32)
base_img.fill(border_color)
qrpainter = QPainter()
qrpainter.begin(base_img)
boxsize = 5
size = k * boxsize
left = (base_img.width() - size)//2
top = (base_img.height() - size)//2
qrpainter.setBrush(Qt.black)
qrpainter.setPen(Qt.black)
for r in range(k):
for c in range(k):
if matrix[r][c]:
qrpainter.drawRect(left+c*boxsize, top+r*boxsize, boxsize - 1, boxsize - 1)
qrpainter.end()
return base_img
def calibration_dialog(self, window):
d = WindowModalDialog(window, _("Revealer - Printer calibration settings"))
d.setMinimumSize(100, 200)
vbox = QVBoxLayout(d)
vbox.addWidget(QLabel(''.join(["<br/>", _("If you have an old printer, or want optimal precision"),"<br/>",
_("print the calibration pdf and follow the instructions "), "<br/>","<br/>",
])))
self.calibration_h = self.config.get('calibration_h')
self.calibration_v = self.config.get('calibration_v')
cprint = QPushButton(_("Open calibration pdf"))
cprint.clicked.connect(self.calibration)
vbox.addWidget(cprint)
vbox.addWidget(QLabel(_('Calibration values:')))
grid = QGridLayout()
vbox.addLayout(grid)
grid.addWidget(QLabel(_('Right side')), 0, 0)
horizontal = QLineEdit()
horizontal.setText(str(self.calibration_h))
grid.addWidget(horizontal, 0, 1)
grid.addWidget(QLabel(_('Bottom')), 1, 0)
vertical = QLineEdit()
vertical.setText(str(self.calibration_v))
grid.addWidget(vertical, 1, 1)
vbox.addStretch()
vbox.addSpacing(13)
vbox.addLayout(Buttons(CloseButton(d), OkButton(d)))
if not d.exec_():
return
self.calibration_h = int(Decimal(horizontal.text()))
self.config.set_key('calibration_h', self.calibration_h)
self.calibration_v = int(Decimal(vertical.text()))
self.config.set_key('calibration_v', self.calibration_v)