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.
756 lines
32 KiB
756 lines
32 KiB
''' |
|
|
|
Revealer |
|
Do you have something to hide? |
|
Secret backup plug-in for the electrum wallet. |
|
|
|
Tiago Romagnani Silveira, 2017 |
|
|
|
|
|
''' |
|
|
|
import os |
|
import random |
|
import qrcode |
|
import traceback |
|
from hashlib import sha256 |
|
from decimal import Decimal |
|
import binascii |
|
|
|
from PyQt5.QtPrintSupport import QPrinter |
|
|
|
from electrum.plugin import BasePlugin, hook |
|
from electrum.i18n import _ |
|
from electrum.util import to_bytes, make_dir |
|
from electrum.gui.qt.util import * |
|
from electrum.gui.qt.qrtextedit import ScanQRTextEdit |
|
from electrum.gui.qt.main_window import StatusBarButton |
|
|
|
from .hmac_drbg import DRBG |
|
|
|
class Plugin(BasePlugin): |
|
|
|
def __init__(self, parent, config, name): |
|
BasePlugin.__init__(self, parent, config, name) |
|
self.base_dir = 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.version = '1' |
|
self.size = (159, 97) |
|
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) |
|
|
|
@hook |
|
def revealer_hook(self, parent): |
|
parent.addPermanentWidget(StatusBarButton(QIcon(':icons/revealer.png'), "Revealer"+_(" secret backup utility"), partial(self.setup_dialog, parent))) |
|
|
|
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: |
|
return |
|
|
|
keystore = self.wallet.get_keystore() |
|
try: |
|
self.cseed = keystore.get_seed(password) |
|
if keystore.get_passphrase(password): |
|
self.extension = True |
|
except Exception: |
|
traceback.print_exc(file=sys.stdout) |
|
return |
|
|
|
def setup_dialog(self, window): |
|
self.wallet = window.parent().wallet |
|
self.update_wallet_name(self.wallet) |
|
self.user_input = False |
|
self.noise_seed = False |
|
|
|
self.d = WindowModalDialog(window, "Setup Dialog") |
|
self.d.setMinimumWidth(500) |
|
self.d.setMinimumHeight(210) |
|
self.d.setMaximumHeight(320) |
|
self.d.setContentsMargins(11,11,1,1) |
|
|
|
self.hbox = QHBoxLayout(self.d) |
|
vbox = QVBoxLayout() |
|
logo = QLabel() |
|
self.hbox.addWidget(logo) |
|
logo.setPixmap(QPixmap(':icons/revealer.png')) |
|
logo.setAlignment(Qt.AlignLeft) |
|
self.hbox.addSpacing(16) |
|
vbox.addWidget(WWLabel("<b>"+_("Revealer Secret Backup Plugin")+"</b><br>" |
|
+_("To encrypt your backup, first we need to load some noise.")+"</br>")) |
|
vbox.addSpacing(7) |
|
bcreate = QPushButton(_("Create a new Revealer")) |
|
bcreate.setMaximumWidth(181) |
|
bcreate.setDefault(True) |
|
vbox.addWidget(bcreate, Qt.AlignCenter) |
|
self.load_noise = ScanQRTextEdit() |
|
self.load_noise.setTabChangesFocus(True) |
|
self.load_noise.textChanged.connect(self.on_edit) |
|
self.load_noise.setMaximumHeight(33) |
|
self.hbox.addLayout(vbox) |
|
vbox.addWidget(WWLabel(_("or type a existing revealer code below and click 'next':"))) |
|
vbox.addWidget(self.load_noise) |
|
vbox.addSpacing(3) |
|
self.next_button = QPushButton(_("Next"), self.d) |
|
self.next_button.setEnabled(False) |
|
vbox.addLayout(Buttons(self.next_button)) |
|
self.next_button.clicked.connect(self.d.close) |
|
self.next_button.clicked.connect(partial(self.cypherseed_dialog, window)) |
|
vbox.addWidget( |
|
QLabel("<b>" + _("Warning") + "</b>: " + _("Each revealer should be used only once.") |
|
+"<br>"+_("more info at https://revealer.cc/faq"))) |
|
|
|
def mk_digital(): |
|
try: |
|
self.make_digital(self.d) |
|
except Exception: |
|
traceback.print_exc(file=sys.stdout) |
|
else: |
|
self.cypherseed_dialog(window) |
|
|
|
bcreate.clicked.connect(mk_digital) |
|
return bool(self.d.exec_()) |
|
|
|
def get_noise(self): |
|
text = self.load_noise.text() |
|
return ''.join(text.split()).lower() |
|
|
|
def on_edit(self): |
|
s = self.get_noise() |
|
b = self.is_noise(s) |
|
if b: |
|
self.noise_seed = s[1:-3] |
|
self.user_input = True |
|
self.next_button.setEnabled(b) |
|
|
|
def code_hashid(self, txt): |
|
x = to_bytes(txt, 'utf8') |
|
hash = sha256(x).hexdigest() |
|
return hash[-3:].upper() |
|
|
|
def is_noise(self, txt): |
|
if (len(txt) >= 34): |
|
try: |
|
int(txt, 16) |
|
except: |
|
self.user_input = False |
|
return False |
|
else: |
|
id = self.code_hashid(txt[:-3]) |
|
if (txt[-3:].upper() == id.upper()): |
|
self.code_id = id |
|
self.user_input = True |
|
return True |
|
else: |
|
return False |
|
else: |
|
|
|
if (len(txt)>0 and txt[0]=='0'): |
|
self.d.show_message(''.join(["<b>",_("Warning: "), "</b>", _("Revealers starting with 0 had a vulnerability and are not supported.")])) |
|
self.user_input = False |
|
return False |
|
|
|
def make_digital(self, dialog): |
|
self.make_rawnoise(True) |
|
self.bdone(dialog) |
|
self.d.close() |
|
|
|
def bcrypt(self, dialog): |
|
self.rawnoise = False |
|
dialog.show_message(''.join([_("{} encrypted for Revealer {}_{} saved as PNG and PDF at:").format(self.was, self.version, self.code_id), |
|
"<br/>","<b>", self.base_dir+ self.filename+self.version+"_"+self.code_id,"</b>"])) |
|
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.")])) |
|
dialog.close() |
|
|
|
def bdone(self, dialog): |
|
dialog.show_message(''.join([_("Digital Revealer ({}_{}) saved as PNG and PDF at:").format(self.version, self.code_id), |
|
"<br/>","<b>", self.base_dir + 'revealer_' +self.version + '_'+ self.code_id, '</b>'])) |
|
|
|
|
|
def customtxt_limits(self): |
|
txt = self.text.text() |
|
self.max_chars.setVisible(False) |
|
self.char_count.setText("("+str(len(txt))+"/216)") |
|
if len(txt)>0: |
|
self.ctext.setEnabled(True) |
|
if len(txt) > 216: |
|
self.text.setPlainText(self.text.toPlainText()[:216]) |
|
self.max_chars.setVisible(True) |
|
|
|
def t(self): |
|
self.txt = self.text.text() |
|
self.seed_img(is_seed=False) |
|
|
|
def cypherseed_dialog(self, window): |
|
|
|
d = WindowModalDialog(window, "Encryption Dialog") |
|
d.setMinimumWidth(500) |
|
d.setMinimumHeight(210) |
|
d.setMaximumHeight(450) |
|
d.setContentsMargins(11, 11, 1, 1) |
|
self.c_dialog = d |
|
|
|
hbox = QHBoxLayout(d) |
|
self.vbox = QVBoxLayout() |
|
logo = QLabel() |
|
hbox.addWidget(logo) |
|
logo.setPixmap(QPixmap(':icons/revealer.png')) |
|
logo.setAlignment(Qt.AlignLeft) |
|
hbox.addSpacing(16) |
|
self.vbox.addWidget(WWLabel("<b>" + _("Revealer Secret Backup Plugin") + "</b><br>" |
|
+ _("Ready to encrypt for revealer ")+self.version+'_'+self.code_id )) |
|
self.vbox.addSpacing(11) |
|
hbox.addLayout(self.vbox) |
|
grid = QGridLayout() |
|
self.vbox.addLayout(grid) |
|
|
|
cprint = QPushButton(_("Encrypt ")+self.wallet_name+_("'s seed")) |
|
cprint.setMaximumWidth(250) |
|
cprint.clicked.connect(partial(self.seed_img, True)) |
|
self.vbox.addWidget(cprint) |
|
self.vbox.addSpacing(1) |
|
self.vbox.addWidget(WWLabel("<b>"+_("OR ")+"</b>"+_("type a custom alphanumerical secret below:"))) |
|
self.text = ScanQRTextEdit() |
|
self.text.setTabChangesFocus(True) |
|
self.text.setMaximumHeight(70) |
|
self.text.textChanged.connect(self.customtxt_limits) |
|
self.vbox.addWidget(self.text) |
|
self.char_count = WWLabel("") |
|
self.char_count.setAlignment(Qt.AlignRight) |
|
self.vbox.addWidget(self.char_count) |
|
self.max_chars = WWLabel("<font color='red'>" + _("This version supports a maximum of 216 characters.")+"</font>") |
|
self.vbox.addWidget(self.max_chars) |
|
self.max_chars.setVisible(False) |
|
self.ctext = QPushButton(_("Encrypt custom secret")) |
|
self.ctext.clicked.connect(self.t) |
|
self.vbox.addWidget(self.ctext) |
|
self.ctext.setEnabled(False) |
|
self.vbox.addSpacing(11) |
|
self.vbox.addLayout(Buttons(CloseButton(d))) |
|
return bool(d.exec_()) |
|
|
|
|
|
def update_wallet_name (self, name): |
|
self.wallet_name = str(name) |
|
self.base_name = self.base_dir + self.wallet_name |
|
|
|
def seed_img(self, is_seed = True): |
|
|
|
if is_seed: |
|
self.get_seed() |
|
txt = self.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 = 23 |
|
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 (self.rawnoise == False): |
|
self.make_rawnoise() |
|
|
|
self.make_cypherseed(img, self.rawnoise, False, is_seed) |
|
return img |
|
|
|
def make_rawnoise(self, create_revealer=False): |
|
w = self.size[0] |
|
h = self.size[1] |
|
rawnoise = QImage(w, h, QImage.Format_Mono) |
|
|
|
if(self.noise_seed == False): |
|
self.noise_seed = random.SystemRandom().getrandbits(128) |
|
self.hex_noise = format(self.noise_seed, '032x') |
|
self.hex_noise = self.version + str(self.hex_noise) |
|
|
|
if (self.user_input == True): |
|
self.noise_seed = int(self.noise_seed, 16) |
|
self.hex_noise = self.version + str(format(self.noise_seed, '032x')) |
|
|
|
self.code_id = self.code_hashid(self.hex_noise) |
|
self.hex_noise = ' '.join(self.hex_noise[i:i+4] for i in range(0,len(self.hex_noise),4)) |
|
|
|
entropy = binascii.unhexlify(str(format(self.noise_seed, '032x'))) |
|
code_id = binascii.unhexlify(self.version + self.code_id) |
|
print (self.hex_noise) |
|
drbg = DRBG(entropy + code_id) |
|
noise_array=bin(int.from_bytes(drbg.generate(1929), 'big'))[2:] |
|
|
|
i=0 |
|
for x in range(w): |
|
for y in range(h): |
|
rawnoise.setPixel(x,y,int(noise_array[i])) |
|
i+=1 |
|
|
|
self.rawnoise = rawnoise |
|
if create_revealer==True: |
|
self.make_revealer() |
|
self.noise_seed = False |
|
|
|
def make_calnoise(self): |
|
random.seed(self.calibration_noise) |
|
w = self.size[0] |
|
h = self.size[1] |
|
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 = 'Revealer - ' |
|
revealer.save(self.base_dir + self.filename + self.version+'_'+self.code_id + '.png') |
|
self.toPdf(QImage(revealer)) |
|
QDesktopServices.openUrl(QUrl.fromLocalFile(os.path.abspath(self.base_dir + self.filename + self.version+'_'+ self.code_id + '.pdf'))) |
|
|
|
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 = _('custom_secret')+'_' |
|
self.was = _('Custom secret') |
|
else: |
|
self.filename = 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)) |
|
QDesktopServices.openUrl (QUrl.fromLocalFile(os.path.abspath(self.base_dir+self.filename+self.version+'_'+self.code_id+'.pdf'))) |
|
cypherseed.save(self.base_dir + self.filename +self.version + '_'+ self.code_id + '.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(os.path.abspath(self.base_dir+_('calibration')+'.pdf'))) |
|
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.base_dir+self.filename+self.version + '_'+self.code_id+'.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 = 2028+((int(self.calibration_h)*2028/(2028-(delta_h*2)+int(self.calibration_h)))/2) |
|
size_v = 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.base_dir+_('calibration')+'.pdf') |
|
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 iluminated 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(':icons/electrumb.png').scaledToWidth(2.1*(total_distance_h), Qt.SmoothTransformation)) |
|
|
|
painter.setPen(QPen(Qt.white, border_thick*8)) |
|
painter.drawLine(base_img.width()-((total_distance_h))-(border_thick*8)/2-(border_thick/2)-2, |
|
(base_img.height()-((total_distance_h)))-((border_thick*8)/2)-(border_thick/2)-2, |
|
base_img.width()-((total_distance_h))-(border_thick*8)/2-(border_thick/2)-2 - 77, |
|
(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.version + '_'+self.code_id) |
|
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(':icons/revealer_c.png').scaledToWidth(1.3*(total_distance_h)) |
|
painter.drawImage((total_distance_h)+ (border_thick), ((total_distance_h))+ (border_thick), logo, Qt.SmoothTransformation) |
|
|
|
#frame around logo |
|
painter.setPen(QPen(Qt.black, border_thick)) |
|
painter.drawLine(total_distance_h+border_thick, total_distance_h+logo.height()+3*(border_thick/2), |
|
total_distance_h+logo.width()+border_thick, total_distance_h+logo.height()+3*(border_thick/2)) |
|
painter.drawLine(logo.width()+total_distance_h+3*(border_thick/2), total_distance_h+(border_thick), |
|
total_distance_h+logo.width()+3*(border_thick/2), total_distance_h+logo.height()+(border_thick)) |
|
|
|
#frame around code/qr |
|
qr_size = 179 |
|
|
|
painter.drawLine((base_img.width()-((total_distance_h))-(border_thick/2)-2)-qr_size, |
|
(base_img.height()-((total_distance_h)))-((border_thick*8))-(border_thick/2)-2, |
|
(base_img.width()/2+(total_distance_h/2)-border_thick-(border_thick*8)/2)-qr_size, |
|
(base_img.height()-((total_distance_h)))-((border_thick*8))-(border_thick/2)-2) |
|
|
|
painter.drawLine((base_img.width()/2+(total_distance_h/2)-border_thick-(border_thick*8)/2)-qr_size, |
|
(base_img.height()-((total_distance_h)))-((border_thick*8))-(border_thick/2)-2, |
|
base_img.width()/2 + (total_distance_h/2)-border_thick-(border_thick*8)/2-qr_size, |
|
((base_img.height()-((total_distance_h)))-(border_thick/2)-2)) |
|
|
|
painter.setPen(QPen(Qt.white, border_thick * 8)) |
|
painter.drawLine( |
|
base_img.width() - ((total_distance_h)) - (border_thick * 8) / 2 - (border_thick / 2) - 2, |
|
(base_img.height() - ((total_distance_h))) - ((border_thick * 8) / 2) - (border_thick / 2) - 2, |
|
base_img.width() / 2 + (total_distance_h / 2) - border_thick - qr_size, |
|
(base_img.height() - ((total_distance_h))) - ((border_thick * 8) / 2) - (border_thick / 2) - 2) |
|
|
|
painter.setPen(QColor(0,0,0,255)) |
|
painter.drawText(QRect(((base_img.width()/2) +21)-qr_size, base_img.height()-107, |
|
base_img.width()-total_distance_h - border_thick -93, |
|
base_img.height()-total_distance_h - border_thick), Qt.AlignLeft, self.hex_noise.upper()) |
|
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.code_id) |
|
|
|
# draw qr code |
|
qr_qt = self.paintQR(self.hex_noise.upper() +self.code_id) |
|
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(base_img.width()-65-qr_size, |
|
base_img.height()-65-qr_size, |
|
base_img.width() - 65 - qr_size, |
|
(base_img.height() - ((total_distance_h))) - ((border_thick * 8)) - (border_thick / 2) - 4 |
|
) |
|
painter.drawLine(base_img.width()-65-qr_size, |
|
base_img.height()-65-qr_size, |
|
base_img.width() - 65, |
|
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((((base_img.width())/(n*2)) *(x))+ (base_img.width()/2)-13, |
|
x+2+base_img.height()-(dist_v), |
|
(((base_img.width())/(n*2)) *(x))+ (base_img.width()/2)+13, |
|
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((((base_img.width())/(n*2)) *(x))+ (base_img.width()/2)-num_pos, |
|
50+base_img.height()-(dist_v), |
|
str(x)) |
|
|
|
#lines on the right (horizontal calibrations) |
|
|
|
cal_painter.drawLine(x+2+(base_img.width()-(dist_h)), |
|
((base_img.height()/(2*n)) *(x))+ (base_img.height()/n)+(base_img.height()/2)-13, |
|
x+2+(base_img.width()-(dist_h)), |
|
((base_img.height()/(2*n)) *(x))+ (base_img.height()/n)+(base_img.height()/2)+13) |
|
|
|
|
|
cal_painter.drawText(30+(base_img.width()-(dist_h)), |
|
((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) |
|
|
|
|
|
|