From deeac2c99b5b49d2ec198c03c238c6e12a505277 Mon Sep 17 00:00:00 2001 From: sendiulo Date: Tue, 27 Aug 2013 11:47:14 +0200 Subject: [PATCH 01/48] Identicons working in most lists, but not working for new items yet. There are no settings yet. --- src/bitmessageqt/__init__.py | 79 ++++++- src/bitmessageqt/settings.py | 2 +- src/identicon.py | 262 ++++++++++++++++++++++ src/identicon_py_license | 11 + src/pydenticon.py | 409 +++++++++++++++++++++++++++++++++++ 5 files changed, 759 insertions(+), 4 deletions(-) create mode 100644 src/identicon.py create mode 100644 src/identicon_py_license create mode 100644 src/pydenticon.py diff --git a/src/bitmessageqt/__init__.py b/src/bitmessageqt/__init__.py index 3e513295..84136424 100644 --- a/src/bitmessageqt/__init__.py +++ b/src/bitmessageqt/__init__.py @@ -47,6 +47,20 @@ except Exception as err: print 'Error message:', err sys.exit() +# for the md5 hash (used for the identicons) +import hashlib + +# load identicon code +# :Author:Shin Adachi +# Licesensed under FreeBSD License. +import identicon +# usage: identicon.render_identicon(code, size) +# requires PIL + +# load another identicon code +# http://boottunes.googlecode.com/svn-history/r302/trunk/src/pydenticon.py +from pydenticon import Pydenticon + try: _encoding = QtGui.QApplication.UnicodeUTF8 except AttributeError: @@ -55,6 +69,34 @@ except AttributeError: def _translate(context, text): return QtGui.QApplication.translate(context, text) +def identiconize(address): + suffix = "" # here you could put "@bitmessge.ch" or "@bm.addr" to make it compatible with other identicon generators + # instead, you could also use a pseudo-password to salt the generation of the identicons + # Attacks where someone creates an address to mimic someone else's identicon should be impossible then + # i think it should generate a random string by default + + # hash = hashlib.md5(addBMIfNotPresent(address)+suffix).hexdigest()[:8] + # print hash + ##japanese code + # idcon_render = identicon.render_identicon(int(hash, 16), 8) + # idcon_render.save('images/'+hash+'.png') + # im = idcon_render + # http://qt-project.org/forums/viewthread/5866 + # from PIL import Image + # from PyQt4.QtGui import QImage, QImageReader, QLabel, QPixmap, QApplication + + # PHP-like code + idcon_render = Pydenticon(addBMIfNotPresent(address)+suffix) + image = idcon_render._render() + + # im = Image.open('images/'+hash+'.png') + # http://stackoverflow.com/questions/6756820/python-pil-image-tostring + data = image.convert("RGBA").tostring("raw", "RGBA") + image = QImage(data, image.size[0], image.size[1], QImage.Format_ARGB32) + pix = QPixmap.fromImage(image) + idcon = QtGui.QIcon() + idcon.addPixmap(pix, QtGui.QIcon.Normal, QtGui.QIcon.Off) + return idcon class MyForm(QtGui.QMainWindow): @@ -327,6 +369,7 @@ class MyForm(QtGui.QMainWindow): newItem.setTextColor(QtGui.QColor(128, 128, 128)) if shared.safeConfigGetBoolean(addressInKeysFile, 'mailinglist'): newItem.setTextColor(QtGui.QColor(137, 04, 177)) # magenta + newItem.setIcon(identiconize(addressInKeysFile)) self.ui.tableWidgetYourIdentities.setItem(0, 1, newItem) newItem = QtGui.QTableWidgetItem(str( addressStream(addressInKeysFile))) @@ -354,9 +397,11 @@ class MyForm(QtGui.QMainWindow): for row in queryreturn: label, address = row self.ui.tableWidgetAddressBook.insertRow(0) + # address book item newItem = QtGui.QTableWidgetItem(unicode(label, 'utf-8')) self.ui.tableWidgetAddressBook.setItem(0, 0, newItem) newItem = QtGui.QTableWidgetItem(address) + newItem.setIcon(identiconize(address)) newItem.setFlags( QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled) self.ui.tableWidgetAddressBook.setItem(0, 1, newItem) @@ -401,6 +446,16 @@ class MyForm(QtGui.QMainWindow): self.numberOfBroadcastsProcessed = 0 self.numberOfPubkeysProcessed = 0 + # Set the icon sizes for the identicons + identicon_size = 3*7 + self.ui.tableWidgetInbox.setIconSize(QtCore.QSize(identicon_size, identicon_size)) + self.ui.tableWidgetSent.setIconSize(QtCore.QSize(identicon_size, identicon_size)) + self.ui.tableWidgetYourIdentities.setIconSize(QtCore.QSize(identicon_size, identicon_size)) + self.ui.tableWidgetSubscriptions.setIconSize(QtCore.QSize(identicon_size, identicon_size)) + self.ui.tableWidgetAddressBook.setIconSize(QtCore.QSize(identicon_size, identicon_size)) + self.ui.tableWidgetWhitelist.setIconSize(QtCore.QSize(identicon_size, identicon_size)) + self.ui.tableWidgetBlacklist.setIconSize(QtCore.QSize(identicon_size, identicon_size)) + self.UISignalThread = UISignaler() QtCore.QObject.connect(self.UISignalThread, QtCore.SIGNAL( "writeNewAddressToTable(PyQt_PyObject,PyQt_PyObject,PyQt_PyObject)"), self.writeNewAddressToTable) @@ -607,6 +662,7 @@ class MyForm(QtGui.QMainWindow): else: newItem = QtGui.QTableWidgetItem(unicode(toLabel, 'utf-8')) newItem.setToolTip(unicode(toLabel, 'utf-8')) + newItem.setIcon(identiconize(toAddress)) newItem.setData(Qt.UserRole, str(toAddress)) newItem.setFlags( QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled) @@ -618,6 +674,7 @@ class MyForm(QtGui.QMainWindow): else: newItem = QtGui.QTableWidgetItem(unicode(fromLabel, 'utf-8')) newItem.setToolTip(unicode(fromLabel, 'utf-8')) + newItem.setIcon(identiconize(fromAddress)) newItem.setData(Qt.UserRole, str(fromAddress)) newItem.setFlags( QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled) @@ -747,10 +804,12 @@ class MyForm(QtGui.QMainWindow): if queryreturn != []: for row in queryreturn: fromLabel, = row - + + # message row self.ui.tableWidgetInbox.insertRow(0) newItem = QtGui.QTableWidgetItem(unicode(toLabel, 'utf-8')) newItem.setToolTip(unicode(toLabel, 'utf-8')) + # to newItem.setFlags( QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled) if not read: @@ -760,7 +819,9 @@ class MyForm(QtGui.QMainWindow): newItem.setTextColor(QtGui.QColor(137, 04, 177)) if shared.safeConfigGetBoolean(str(toAddress), 'chan'): newItem.setTextColor(QtGui.QColor(216, 119, 0)) # orange + newItem.setIcon(identiconize(toAddress)) self.ui.tableWidgetInbox.setItem(0, 0, newItem) + # from if fromLabel == '': newItem = QtGui.QTableWidgetItem( unicode(fromAddress, 'utf-8')) @@ -773,8 +834,9 @@ class MyForm(QtGui.QMainWindow): if not read: newItem.setFont(font) newItem.setData(Qt.UserRole, str(fromAddress)) - + newItem.setIcon(identiconize(fromAddress)) self.ui.tableWidgetInbox.setItem(0, 1, newItem) + # subject newItem = QtGui.QTableWidgetItem(unicode(subject, 'utf-8')) newItem.setToolTip(unicode(subject, 'utf-8')) newItem.setData(Qt.UserRole, unicode(message, 'utf-8)')) @@ -783,6 +845,7 @@ class MyForm(QtGui.QMainWindow): if not read: newItem.setFont(font) self.ui.tableWidgetInbox.setItem(0, 2, newItem) + # time received newItem = myTableWidgetItem(unicode(strftime(shared.config.get( 'bitmessagesettings', 'timeformat'), localtime(int(received))), 'utf-8')) newItem.setToolTip(unicode(strftime(shared.config.get( @@ -1473,6 +1536,8 @@ class MyForm(QtGui.QMainWindow): toLabel = toAddress self.ui.tableWidgetInbox.item( i, 0).setText(unicode(toLabel, 'utf-8')) + self.ui.tableWidgetInbox.item( + i, 0).setIcon(identiconize(toAddress)) # Set the color according to whether it is the address of a mailing # list or not. if shared.safeConfigGetBoolean(toAddress, 'mailinglist'): @@ -1493,6 +1558,8 @@ class MyForm(QtGui.QMainWindow): fromLabel = fromAddress self.ui.tableWidgetSent.item( i, 1).setText(unicode(fromLabel, 'utf-8')) + self.ui.tableWidgetSent.item( + i, 0).setIcon(identiconize(fromAddress)) def rerenderSentToLabels(self): for i in range(self.ui.tableWidgetSent.rowCount()): @@ -1512,7 +1579,7 @@ class MyForm(QtGui.QMainWindow): toLabel, = row self.ui.tableWidgetSent.item( i, 0).setText(unicode(toLabel, 'utf-8')) - + def rerenderSubscriptions(self): self.ui.tableWidgetSubscriptions.setRowCount(0) shared.sqlLock.acquire() @@ -1529,6 +1596,7 @@ class MyForm(QtGui.QMainWindow): newItem.setTextColor(QtGui.QColor(128, 128, 128)) self.ui.tableWidgetSubscriptions.setItem(0, 0, newItem) newItem = QtGui.QTableWidgetItem(address) + newItem.setIcon(identiconize(address)) newItem.setFlags( QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled) if not enabled: @@ -1791,6 +1859,7 @@ class MyForm(QtGui.QMainWindow): newItem = QtGui.QTableWidgetItem(unicode(fromLabel, 'utf-8')) newItem.setToolTip(unicode(fromLabel, 'utf-8')) newItem.setData(Qt.UserRole, str(fromAddress)) + newItem.setIcon(identiconize(address)) self.ui.tableWidgetSent.setItem(0, 1, newItem) newItem = QtGui.QTableWidgetItem(unicode(subject, 'utf-8)')) newItem.setToolTip(unicode(subject, 'utf-8)')) @@ -1920,6 +1989,7 @@ class MyForm(QtGui.QMainWindow): newItem = QtGui.QTableWidgetItem(unicode(label, 'utf-8')) self.ui.tableWidgetAddressBook.setItem(0, 0, newItem) newItem = QtGui.QTableWidgetItem(address) + newItem.setIcon(identiconize(address)) newItem.setFlags( QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled) self.ui.tableWidgetAddressBook.setItem(0, 1, newItem) @@ -1949,6 +2019,7 @@ class MyForm(QtGui.QMainWindow): newItem = QtGui.QTableWidgetItem(unicode(label, 'utf-8')) self.ui.tableWidgetSubscriptions.setItem(0,0,newItem) newItem = QtGui.QTableWidgetItem(address) + newItem.setIcon(identiconize(address)) newItem.setFlags( QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled ) self.ui.tableWidgetSubscriptions.setItem(0,1,newItem) self.ui.tableWidgetSubscriptions.setSortingEnabled(True) @@ -1998,6 +2069,7 @@ class MyForm(QtGui.QMainWindow): newItem.setTextColor(QtGui.QColor(128, 128, 128)) self.ui.tableWidgetBlacklist.setItem(0, 0, newItem) newItem = QtGui.QTableWidgetItem(address) + newItem.setIcon(identiconize(address)) newItem.setFlags( QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled) if not enabled: @@ -2971,6 +3043,7 @@ class MyForm(QtGui.QMainWindow): self.ui.tableWidgetYourIdentities.setItem( 0, 0, newItem) newItem = QtGui.QTableWidgetItem(address) + newItem.setIcon(identiconize(address)) newItem.setFlags( QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled) if shared.safeConfigGetBoolean(address, 'chan'): diff --git a/src/bitmessageqt/settings.py b/src/bitmessageqt/settings.py index ad597773..821df6cf 100644 --- a/src/bitmessageqt/settings.py +++ b/src/bitmessageqt/settings.py @@ -2,7 +2,7 @@ # Form implementation generated from reading ui file 'settings.ui' # -# Created: Sat Aug 24 09:19:58 2013 +# Created: Sun Aug 25 18:09:38 2013 # by: PyQt4 UI code generator 4.10.2 # # WARNING! All changes made in this file will be lost! diff --git a/src/identicon.py b/src/identicon.py new file mode 100644 index 00000000..fae5a250 --- /dev/null +++ b/src/identicon.py @@ -0,0 +1,262 @@ +#!/usr/bin/env python +# -*- coding:utf-8 -*- +""" +identicon.py +identicon python implementation. +by Shin Adachi + += usage = + +== commandline == +>>> python identicon.py [code] + +== python == +>>> import identicon +>>> identicon.render_identicon(code, size) + +Return a PIL Image class instance which have generated identicon image. +```size``` specifies `patch size`. Generated image size is 3 * ```size```. +""" +# g +# PIL Modules +import Image +import ImageDraw +import ImagePath +import ImageColor + + +__all__ = ['render_identicon', 'IdenticonRendererBase'] + + +class Matrix2D(list): + """Matrix for Patch rotation""" + def __init__(self, initial=[0.] * 9): + assert isinstance(initial, list) and len(initial) == 9 + list.__init__(self, initial) + + def clear(self): + for i in xrange(9): + self[i] = 0. + + def set_identity(self): + self.clear() + for i in xrange(3): + self[i] = 1. + + def __str__(self): + return '[%s]' % ', '.join('%3.2f' % v for v in self) + + def __mul__(self, other): + r = [] + if isinstance(other, Matrix2D): + for y in xrange(3): + for x in xrange(3): + v = 0.0 + for i in xrange(3): + v += (self[i * 3 + x] * other[y * 3 + i]) + r.append(v) + else: + raise NotImplementedError + return Matrix2D(r) + + def for_PIL(self): + return self[0:6] + + @classmethod + def translate(kls, x, y): + return kls([1.0, 0.0, float(x), + 0.0, 1.0, float(y), + 0.0, 0.0, 1.0]) + + @classmethod + def scale(kls, x, y): + return kls([float(x), 0.0, 0.0, + 0.0, float(y), 0.0, + 0.0, 0.0, 1.0]) + + """ + # need `import math` + @classmethod + def rotate(kls, theta, pivot=None): + c = math.cos(theta) + s = math.sin(theta) + + matR = kls([c, -s, 0., s, c, 0., 0., 0., 1.]) + if not pivot: + return matR + return kls.translate(-pivot[0], -pivot[1]) * matR * + kls.translate(*pivot) + """ + + @classmethod + def rotateSquare(kls, theta, pivot=None): + theta = theta % 4 + c = [1., 0., -1., 0.][theta] + s = [0., 1., 0., -1.][theta] + + matR = kls([c, -s, 0., s, c, 0., 0., 0., 1.]) + if not pivot: + return matR + return kls.translate(-pivot[0], -pivot[1]) * matR * \ + kls.translate(*pivot) + + +class IdenticonRendererBase(object): + PATH_SET = [] + + def __init__(self, code): + """ + @param code code for icon + """ + if not isinstance(code, int): + code = int(code) + self.code = code + + def render(self, size): + """ + render identicon to PIL.Image + + @param size identicon patchsize. (image size is 3 * [size]) + @return PIL.Image + """ + + # decode the code + middle, corner, side, foreColor, backColor = self.decode(self.code) + + # make image + image = Image.new("RGB", (size * 3, size * 3)) + draw = ImageDraw.Draw(image) + + # fill background + draw.rectangle((0, 0, image.size[0], image.size[1]), fill=0) + + kwds = { + 'draw': draw, + 'size': size, + 'foreColor': foreColor, + 'backColor': backColor} + # middle patch + self.drawPatch((1, 1), middle[2], middle[1], middle[0], **kwds) + + # side patch + kwds['type'] = side[0] + for i in xrange(4): + pos = [(1, 0), (2, 1), (1, 2), (0, 1)][i] + self.drawPatch(pos, side[2] + 1 + i, side[1], **kwds) + + # corner patch + kwds['type'] = corner[0] + for i in xrange(4): + pos = [(0, 0), (2, 0), (2, 2), (0, 2)][i] + self.drawPatch(pos, corner[2] + 1 + i, corner[1], **kwds) + + return image + + def drawPatch(self, pos, turn, invert, type, draw, size, foreColor, + backColor): + """ + @param size patch size + """ + path = self.PATH_SET[type] + if not path: + # blank patch + invert = not invert + path = [(0., 0.), (1., 0.), (1., 1.), (0., 1.), (0., 0.)] + patch = ImagePath.Path(path) + if invert: + foreColor, backColor = backColor, foreColor + + mat = Matrix2D.rotateSquare(turn, pivot=(0.5, 0.5)) *\ + Matrix2D.translate(*pos) *\ + Matrix2D.scale(size, size) + + patch.transform(mat.for_PIL()) + draw.rectangle((pos[0] * size, pos[1] * size, (pos[0] + 1) * size, + (pos[1] + 1) * size), fill=backColor) + draw.polygon(patch, fill=foreColor, outline=foreColor) + + ### virtual functions + def decode(self, code): + raise NotImplementedError + + +class DonRenderer(IdenticonRendererBase): + """ + Don Park's implementation of identicon + see : http://www.docuverse.com/blog/donpark/2007/01/19/identicon-updated-and-source-released + """ + + PATH_SET = [ + [(0, 0), (4, 0), (4, 4), (0, 4)], # 0 + [(0, 0), (4, 0), (0, 4)], + [(2, 0), (4, 4), (0, 4)], + [(0, 0), (2, 0), (2, 4), (0, 4)], + [(2, 0), (4, 2), (2, 4), (0, 2)], # 4 + [(0, 0), (4, 2), (4, 4), (2, 4)], + [(2, 0), (4, 4), (2, 4), (3, 2), (1, 2), (2, 4), (0, 4)], + [(0, 0), (4, 2), (2, 4)], + [(1, 1), (3, 1), (3, 3), (1, 3)], # 8 + [(2, 0), (4, 0), (0, 4), (0, 2), (2, 2)], + [(0, 0), (2, 0), (2, 2), (0, 2)], + [(0, 2), (4, 2), (2, 4)], + [(2, 2), (4, 4), (0, 4)], + [(2, 0), (2, 2), (0, 2)], + [(0, 0), (2, 0), (0, 2)], + []] # 15 + MIDDLE_PATCH_SET = [0, 4, 8, 15] + + # modify path set + for idx in xrange(len(PATH_SET)): + if PATH_SET[idx]: + p = map(lambda vec: (vec[0] / 4.0, vec[1] / 4.0), PATH_SET[idx]) + PATH_SET[idx] = p + p[:1] + + def decode(self, code): + # decode the code + middleType = self.MIDDLE_PATCH_SET[code & 0x03] + middleInvert= (code >> 2) & 0x01 + cornerType = (code >> 3) & 0x0F + cornerInvert= (code >> 7) & 0x01 + cornerTurn = (code >> 8) & 0x03 + sideType = (code >> 10) & 0x0F + sideInvert = (code >> 14) & 0x01 + sideTurn = (code >> 15) & 0x03 + # bug reported by Masato Hagiwara + # http://lilyx.net/iconlang-en/ + # blue = (code >> 16) & 0x1F + # green = (code >> 21) & 0x1F + blue = (code >> 17) & 0x1F + green = (code >> 22) & 0x1F + red = (code >> 27) & 0x1F + + foreColor = (red << 3, green << 3, blue << 3) + + return (middleType, middleInvert, 0),\ + (cornerType, cornerInvert, cornerTurn),\ + (sideType, sideInvert, sideTurn),\ + foreColor, ImageColor.getrgb('white') + + +def render_identicon(code, size, renderer=None): + if not renderer: + renderer = DonRenderer + return renderer(code).render(size) + + +if __name__ == '__main__': + import sys + + if len(sys.argv) < 2: + print 'usage: python identicon.py [CODE]....' + raise SystemExit + + for code in sys.argv[1:]: + if code.startswith('0x') or code.startswith('0X'): + code = int(code[2:], 16) + elif code.startswith('0'): + code = int(code[1:], 8) + else: + code = int(code) + + icon = render_identicon(code, 24) + icon.save('%08x.png' % code, 'PNG') diff --git a/src/identicon_py_license b/src/identicon_py_license new file mode 100644 index 00000000..e6e964fb --- /dev/null +++ b/src/identicon_py_license @@ -0,0 +1,11 @@ +identicon.py is Licesensed under FreeBSD License. +(http://www.freebsd.org/copyright/freebsd-license.html) + +Copyright 1994-2009 Shin Adachi. All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/src/pydenticon.py b/src/pydenticon.py new file mode 100644 index 00000000..57076093 --- /dev/null +++ b/src/pydenticon.py @@ -0,0 +1,409 @@ +""" +Python implementation of PHP Identicons, found here: +http://sourceforge.net/projects/identicons/ + +Licensed under the GPLv3. +""" +from PIL import Image, ImageDraw, ImagePath, ImageColor +from math import fabs as abs +from hashlib import md5 +import StringIO + +class Pydenticon: + """ + Generate an identicon. + + """ + + WHITE = (255, 255, 255) + + def __init__(self, input, size = 128): + self.hash = md5(input).hexdigest() + self.size = size + + def as_string(self): + """ + Return the image as a string representation of a PNG file. + + """ + if not hasattr(self, 'image'): + self.image = self._render() + output = StringIO.StringIO() + self.image.save(output, format="PNG") + contents = output.getvalue() + output.close() + return contents + + def save(self, filename): + """ + Save the result as a PNG file. + + """ + if not hasattr(self, 'image'): + self.image = self._render() + self.image.save(filename, format="PNG") + + def _get_sprite(self, shape, r, g, b, rotation): + """ + Generate sprite for corners and sides and returnro a PIL.Image object. + + """ + sprite = Image.new("RGB", (self.size, self.size), self.WHITE) + + if shape == 0: # triangle + points = [ + 0.5,1, + 1,0, + 1,1 + ] + elif shape == 1: #parallelogram + points = [ + 0.5,0, + 1,0, + 0.5,1, + 0,1 + ] + elif shape == 2: # mouse ears + points = [ + 0.5,0, + 1,0, + 1,1, + 0.5,1, + 1,0.5 + ] + elif shape == 3: # ribbon + points = [ + 0,0.5, + 0.5,0, + 1,0.5, + 0.5,1, + 0.5,0.5 + ] + elif shape == 4: # sails + points = [ + 0,0.5, + 1,0, + 1,1, + 0,1, + 1,0.5 + ] + elif shape == 5: # fins + points = [ + 1,0, + 1,1, + 0.5,1, + 1,0.5, + 0.5,0.5 + ] + elif shape == 6: # beak + points = [ + 0,0, + 1,0, + 1,0.5, + 0,0, + 0.5,1, + 0,1 + ] + elif shape == 7: # chevron + points = [ + 0,0, + 0.5,0, + 1,0.5, + 0.5,1, + 0,1, + 0.5,0.5 + ] + elif shape == 8: # fish + points = [ + 0.5,0, + 0.5,0.5, + 1,0.5, + 1,1, + 0.5,1, + 0.5,0.5, + 0,0.5 + ] + elif shape == 9: # kite + points = [ + 0,0, + 1,0, + 0.5,0.5, + 1,0.5, + 0.5,1, + 0.5,0.5, + 0,1 + ] + elif shape == 10: # trough + points = [ + 0,0.5, + 0.5,1, + 1,0.5, + 0.5,0, + 1,0, + 1,1, + 0,1 + ] + elif shape == 11: # rays + points = [ + 0.5,0, + 1,0, + 1,1, + 0.5,1, + 1,0.75, + 0.5,0.5, + 1,0.25 + ] + elif shape == 12: # double rhombus + points = [ + 0,0.5, + 0.5,0, + 0.5,0.5, + 1,0, + 1,0.5, + 0.5,1, + 0.5,0.5, + 0,1 + ] + elif shape == 13: # crown + points = [ + 0,0, + 1,0, + 1,1, + 0,1, + 1,0.5, + 0.5,0.25, + 0.5,0.75, + 0,0.5, + 0.5,0.25 + ] + elif shape == 14: # radioactive + points = [ + 0,0.5, + 0.5,0.5, + 0.5,0, + 1,0, + 0.5,0.5, + 1,0.5, + 0.5,1, + 0.5,0.5, + 0,1 + ] + else: # tiles + points = [ + 0,0, + 1,0, + 0.5,0.5, + 0.5,0, + 0,0.5, + 1,0.5, + 0.5,1, + 0.5,0.5, + 0,1 + ] + + # apply ratios + for i in range(0, len(points)): + points[i] = points[i] * self.size + + draw = ImageDraw.Draw(sprite) + draw.polygon(points, fill=(r, g, b)) + + for i in range(rotation): + sprite = sprite.rotate(90) + + return sprite + + def _get_center(self, shape, fR, fG, fB, bR, bG, bB, useBg): + """ + Generate sprite for center block and return a PIL.Image object. + + """ + sprite = Image.new("RGB", (self.size, self.size), self.WHITE) + + # make sure there's enough contrast before we use background color of side sprite + sufficient_contrast = ( + abs(fR - bR) > 127 or abs(fG - bG) > 127 or abs(fB - bB) > 127 + ) + if useBg > 0 and sufficient_contrast: + bg = (bR, bG, bB) + else: + bg = (255, 255, 255) + + if shape == 0: # empty + points = [] + + elif shape == 1: # fill + points = [ + 0,0, + 1,0, + 1,1, + 0,1 + ] + elif shape == 2: # diamond + points = [ + 0.5,0, + 1,0.5, + 0.5,1, + 0,0.5 + ] + elif shape == 3: # reverse diamond + points = [ + 0,0, + 1,0, + 1,1, + 0,1, + 0,0.5, + 0.5,1, + 1,0.5, + 0.5,0, + 0,0.5 + ] + elif shape == 4: # cross + points = [ + 0.25,0, + 0.75,0, + 0.5,0.5, + 1,0.25, + 1,0.75, + 0.5,0.5, + 0.75,1, + 0.25,1, + 0.5,0.5, + 0,0.75, + 0,0.25, + 0.5,0.5 + ] + elif shape == 5: # morning star + points = [ + 0,0, + 0.5,0.25, + 1,0, + 0.75,0.5, + 1,1, + 0.5,0.75, + 0,1, + 0.25,0.5 + ] + elif shape == 6: # small square + points = [ + 0.33,0.33, + 0.67,0.33, + 0.67,0.67, + 0.33,0.67 + ] + elif shape == 7: # checkerboard + points = [ + 0,0, + 0.33,0, + 0.33,0.33, + 0.66,0.33, + 0.67,0, + 1,0, + 1,0.33, + 0.67,0.33, + 0.67,0.67, + 1,0.67, + 1,1, + 0.67,1, + 0.67,0.67, + 0.33,0.67, + 0.33,1, + 0,1, + 0,0.67, + 0.33,0.67, + 0.33,0.33, + 0,0.33 + ] + + # apply ratios + for i in range(0, len(points)): + points[i] = points[i] * self.size + + if len(points) > 0: + draw = ImageDraw.Draw(sprite) + draw.polygon(points, fill=(fR, fG, fB)) + + return sprite + + def _render(self): + """ + Render the image and return the PIL.Image object. + + """ + # parse hash string + corner_sprite_shape = int(self.hash[0:1], 16) + side_sprite_shape = int(self.hash[1:2], 16) + center_sprite_shape = int(self.hash[2:3], 16) & 7 + + corner_sprite_rot = int(self.hash[3:4], 16) & 3 + side_sprite_rot = int(self.hash[4:5], 16) & 3 + center_sprite_bg = int(self.hash[5:6], 16) % 2 + + # corner sprite foreground color + corner_sprite_fg_r = int(self.hash[6:8], 16) + corner_sprite_fg_g = int(self.hash[8:10], 16) + corner_sprite_fg_b = int(self.hash[10:12], 16) + + # side sprite foreground color + side_sprite_fg_r = int(self.hash[12:14], 16) + side_sprite_fg_g = int(self.hash[14:16], 16) + side_sprite_fg_b = int(self.hash[16:18], 16) + + # final angle of rotation + angle = int(self.hash[18:20], 16) + + # start with blank 3X sized identicon + identicon = Image.new("RGB", (self.size*3, self.size*3), self.WHITE) + + # generate corner sprites + corner = self._get_sprite( + corner_sprite_shape, + corner_sprite_fg_r, + corner_sprite_fg_g, + corner_sprite_fg_b, + corner_sprite_rot + ) + identicon.paste(corner, (0, 0)) + corner = corner.rotate(90) + identicon.paste(corner, (0, self.size*2)) + corner = corner.rotate(90) + identicon.paste(corner, (self.size*2, self.size*2)) + corner = corner.rotate(90) + identicon.paste(corner, (self.size*2, 0)) + + # generate side sprites + side = self._get_sprite( + side_sprite_shape, + side_sprite_fg_r, + side_sprite_fg_g, + side_sprite_fg_b, + side_sprite_rot + ) + identicon.paste(side, (self.size, 0)) + side = side.rotate(90) + identicon.paste(side, (0, self.size)) + side = side.rotate(90) + identicon.paste(side, (self.size, self.size*2)) + side = side.rotate(90) + identicon.paste(side, (self.size*2, self.size)) + + # generate center sprite + center = self._get_center( + center_sprite_shape, + corner_sprite_fg_r, + corner_sprite_fg_g, + corner_sprite_fg_b, + side_sprite_fg_r, + side_sprite_fg_g, + side_sprite_fg_b, + center_sprite_bg + ) + identicon.paste(center, (self.size, self.size)) + + # resize image + resized = identicon.resize( + (self.size, self.size), + Image.ANTIALIAS + ) + + return resized \ No newline at end of file From 07b67051c97611a3a4e06732ab4fe850c72930ed Mon Sep 17 00:00:00 2001 From: sendiulo Date: Wed, 28 Aug 2013 16:50:46 +0200 Subject: [PATCH 02/48] Identicons now in new messages and in the From combobox. [Broadcast Subscribers] gets the Bitmessage Icon for identification. This is hard-coded now, I would favour to put [Broadcast Subscribers] as a fixed item into the Address Book, so you can set an avatar as soon as i implement avatars. Still no options yet: There will be an option to disable Identicons and Avatars separately. Another option will be for the suffix (to mimic adresses like "BM-...@bitmessage.ch" or to add a personal "salt" to prevent identicon attacks where someone creates an address with an identicon that looks alike the one of a known address). Also, identicon size (coupled with row height) should be available, but is of low priority. Next step (after the settings) is to load specific avatars for each address. --- src/bitmessageqt/__init__.py | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/src/bitmessageqt/__init__.py b/src/bitmessageqt/__init__.py index 84136424..784bd6f9 100644 --- a/src/bitmessageqt/__init__.py +++ b/src/bitmessageqt/__init__.py @@ -70,6 +70,11 @@ def _translate(context, text): return QtGui.QApplication.translate(context, text) def identiconize(address): + youdontwantidenticons = False + if youdontwantidenticons == True: + idcon = QtGui.QIcon() + return idcon + suffix = "" # here you could put "@bitmessge.ch" or "@bm.addr" to make it compatible with other identicon generators # instead, you could also use a pseudo-password to salt the generation of the identicons # Attacks where someone creates an address to mimic someone else's identicon should be impossible then @@ -85,6 +90,11 @@ def identiconize(address): # from PIL import Image # from PyQt4.QtGui import QImage, QImageReader, QLabel, QPixmap, QApplication + str_broadcast_subscribers = '[Broadcast subscribers]' + if address == str_broadcast_subscribers: + idcon = QtGui.QIcon(":/newPrefix/images/can-icon-24px.png") + return idcon + # PHP-like code idcon_render = Pydenticon(addBMIfNotPresent(address)+suffix) image = idcon_render._render() @@ -453,7 +463,7 @@ class MyForm(QtGui.QMainWindow): self.ui.tableWidgetYourIdentities.setIconSize(QtCore.QSize(identicon_size, identicon_size)) self.ui.tableWidgetSubscriptions.setIconSize(QtCore.QSize(identicon_size, identicon_size)) self.ui.tableWidgetAddressBook.setIconSize(QtCore.QSize(identicon_size, identicon_size)) - self.ui.tableWidgetWhitelist.setIconSize(QtCore.QSize(identicon_size, identicon_size)) + #self.ui.tableWidgetWhitelist.setIconSize(QtCore.QSize(identicon_size, identicon_size)) self.ui.tableWidgetBlacklist.setIconSize(QtCore.QSize(identicon_size, identicon_size)) self.UISignalThread = UISignaler() @@ -1819,7 +1829,7 @@ class MyForm(QtGui.QMainWindow): isEnabled = shared.config.getboolean( addressInKeysFile, 'enabled') # I realize that this is poor programming practice but I don't care. It's easier for others to read. if isEnabled: - self.ui.comboBoxSendFrom.insertItem(0, unicode(shared.config.get( + self.ui.comboBoxSendFrom.insertItem(0, identiconize(addressInKeysFile), unicode(shared.config.get( addressInKeysFile, 'label'), 'utf-8'), addressInKeysFile) self.ui.comboBoxSendFrom.insertItem(0, '', '') if(self.ui.comboBoxSendFrom.count() == 2): @@ -1851,6 +1861,7 @@ class MyForm(QtGui.QMainWindow): newItem = QtGui.QTableWidgetItem(unicode(toLabel, 'utf-8')) newItem.setToolTip(unicode(toLabel, 'utf-8')) newItem.setData(Qt.UserRole, str(toAddress)) + newItem.setIcon(identiconize(toAddress)) self.ui.tableWidgetSent.setItem(0, 0, newItem) if fromLabel == '': newItem = QtGui.QTableWidgetItem(unicode(fromAddress, 'utf-8')) @@ -1859,7 +1870,7 @@ class MyForm(QtGui.QMainWindow): newItem = QtGui.QTableWidgetItem(unicode(fromLabel, 'utf-8')) newItem.setToolTip(unicode(fromLabel, 'utf-8')) newItem.setData(Qt.UserRole, str(fromAddress)) - newItem.setIcon(identiconize(address)) + newItem.setIcon(identiconize(fromAddress)) self.ui.tableWidgetSent.setItem(0, 1, newItem) newItem = QtGui.QTableWidgetItem(unicode(subject, 'utf-8)')) newItem.setToolTip(unicode(subject, 'utf-8)')) @@ -1929,6 +1940,7 @@ class MyForm(QtGui.QMainWindow): if shared.safeConfigGetBoolean(str(toAddress), 'chan'): newItem.setTextColor(QtGui.QColor(216, 119, 0)) # orange self.ui.tableWidgetInbox.insertRow(0) + newItem.setIcon(identiconize(toAddress)) self.ui.tableWidgetInbox.setItem(0, 0, newItem) if fromLabel == '': @@ -1943,6 +1955,7 @@ class MyForm(QtGui.QMainWindow): self.notifierShow(unicode(_translate("MainWindow",'New Message').toUtf8(),'utf-8'), unicode(_translate("MainWindow",'From ').toUtf8(),'utf-8') + unicode(fromLabel, 'utf-8'), self.SOUND_KNOWN, unicode(fromLabel, 'utf-8')) newItem.setData(Qt.UserRole, str(fromAddress)) newItem.setFont(font) + newItem.setIcon(identiconize(fromAddress)) self.ui.tableWidgetInbox.setItem(0, 1, newItem) newItem = QtGui.QTableWidgetItem(unicode(subject, 'utf-8)')) newItem.setToolTip(unicode(subject, 'utf-8)')) From 9b28e1932dcb9705b82fbf89e1ddf56d316457c9 Mon Sep 17 00:00:00 2001 From: sendiulo Date: Fri, 30 Aug 2013 09:02:49 +0200 Subject: [PATCH 03/48] quoted out the BSD licensed code (as i didn't get it to work properly yet) --- src/bitmessageqt/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/bitmessageqt/__init__.py b/src/bitmessageqt/__init__.py index 784bd6f9..13101cce 100644 --- a/src/bitmessageqt/__init__.py +++ b/src/bitmessageqt/__init__.py @@ -53,7 +53,7 @@ import hashlib # load identicon code # :Author:Shin Adachi # Licesensed under FreeBSD License. -import identicon +# import identicon # usage: identicon.render_identicon(code, size) # requires PIL From a9fb87173fc707f1da284c5ed745acd0932e4b9b Mon Sep 17 00:00:00 2001 From: sendiulo Date: Wed, 4 Sep 2013 13:45:23 +0200 Subject: [PATCH 04/48] now it should also work with the other identicon file --- src/bitmessageqt/__init__.py | 36 +++++++++++++++++------------------- 1 file changed, 17 insertions(+), 19 deletions(-) diff --git a/src/bitmessageqt/__init__.py b/src/bitmessageqt/__init__.py index 13101cce..23bbf8cd 100644 --- a/src/bitmessageqt/__init__.py +++ b/src/bitmessageqt/__init__.py @@ -53,7 +53,7 @@ import hashlib # load identicon code # :Author:Shin Adachi # Licesensed under FreeBSD License. -# import identicon +import identicon # usage: identicon.render_identicon(code, size) # requires PIL @@ -75,29 +75,27 @@ def identiconize(address): idcon = QtGui.QIcon() return idcon - suffix = "" # here you could put "@bitmessge.ch" or "@bm.addr" to make it compatible with other identicon generators - # instead, you could also use a pseudo-password to salt the generation of the identicons - # Attacks where someone creates an address to mimic someone else's identicon should be impossible then - # i think it should generate a random string by default - - # hash = hashlib.md5(addBMIfNotPresent(address)+suffix).hexdigest()[:8] - # print hash - ##japanese code - # idcon_render = identicon.render_identicon(int(hash, 16), 8) - # idcon_render.save('images/'+hash+'.png') - # im = idcon_render - # http://qt-project.org/forums/viewthread/5866 - # from PIL import Image - # from PyQt4.QtGui import QImage, QImageReader, QLabel, QPixmap, QApplication - str_broadcast_subscribers = '[Broadcast subscribers]' if address == str_broadcast_subscribers: idcon = QtGui.QIcon(":/newPrefix/images/can-icon-24px.png") return idcon + + suffix = "asdf" + # here you could put "@bitmessge.ch" or "@bm.addr" to make it compatible with other identicon generators + # instead, you could also use a pseudo-password to salt the generation of the identicons + # Attacks where someone creates an address to mimic someone else's identicon should be impossible then + # i think it should generate a random string by default - # PHP-like code - idcon_render = Pydenticon(addBMIfNotPresent(address)+suffix) - image = idcon_render._render() + if True: # identicon.py + hash = hashlib.md5(addBMIfNotPresent(address)+suffix).hexdigest() + idcon_render = identicon.render_identicon(int(hash, 16), 48) + image = idcon_render + # http://qt-project.org/forums/viewthread/5866 + # from PIL import Image + # from PyQt4.QtGui import QImage, QImageReader, QLabel, QPixmap, QApplication + else: # pydenticon.py + idcon_render = Pydenticon(addBMIfNotPresent(address)+suffix) + image = idcon_render._render() # im = Image.open('images/'+hash+'.png') # http://stackoverflow.com/questions/6756820/python-pil-image-tostring From 4765705764d543ccbadc9a569bb03eebfdaa4e0f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gerg=C3=B6=20Barany?= Date: Wed, 11 Sep 2013 12:18:13 +0200 Subject: [PATCH 05/48] More informative "message truncated" text that tells users what to do to view the full message. --- src/bitmessageqt/__init__.py | 29 ++++++++++++++++------------- 1 file changed, 16 insertions(+), 13 deletions(-) diff --git a/src/bitmessageqt/__init__.py b/src/bitmessageqt/__init__.py index 54e10adf..113c53c1 100644 --- a/src/bitmessageqt/__init__.py +++ b/src/bitmessageqt/__init__.py @@ -2718,23 +2718,26 @@ class MyForm(QtGui.QMainWindow): fromAddress = str(self.ui.tableWidgetInbox.item( currentRow, 1).data(Qt.UserRole).toPyObject()) + messageItem = self.ui.tableWidgetInbox.item(currentRow, 2) + messageText = messageItem.data(Qt.UserRole).toPyObject() + if len(messageText) > 30000: + messageText = ( + messageText[:30000] + '\n' + + '--- Display of the remainder of the message ' + + 'truncated because it is too long.\n' + + '--- To see the full message, right-click in the ' + + 'Inbox view and select "View HTML code as formatted ' + + 'text",\n' + + '--- or select "Save message as..." to save it to a ' + + 'file, or select "Reply" and ' + + 'view the full message in the quote.') # If we have received this message from either a broadcast address # or from someone in our address book, display as HTML if decodeAddress(fromAddress)[3] in shared.broadcastSendersForWhichImWatching or shared.isAddressInMyAddressBook(fromAddress): - if len(self.ui.tableWidgetInbox.item(currentRow, 2).data(Qt.UserRole).toPyObject()) < 30000: - self.ui.textEditInboxMessage.setText(self.ui.tableWidgetInbox.item( - currentRow, 2).data(Qt.UserRole).toPyObject()) # Only show the first 30K characters - else: - self.ui.textEditInboxMessage.setText(self.ui.tableWidgetInbox.item(currentRow, 2).data(Qt.UserRole).toPyObject()[ - :30000] + '\n\nDisplay of the remainder of the message truncated because it is too long.') # Only show the first 30K characters + self.ui.textEditInboxMessage.setText(messageText) else: - if len(self.ui.tableWidgetInbox.item(currentRow, 2).data(Qt.UserRole).toPyObject()) < 30000: - self.ui.textEditInboxMessage.setPlainText(self.ui.tableWidgetInbox.item( - currentRow, 2).data(Qt.UserRole).toPyObject()) # Only show the first 30K characters - else: - self.ui.textEditInboxMessage.setPlainText(self.ui.tableWidgetInbox.item(currentRow, 2).data(Qt.UserRole).toPyObject()[ - :30000] + '\n\nDisplay of the remainder of the message truncated because it is too long.') # Only show the first 30K characters - + self.ui.textEditInboxMessage.setPlainText(messageText) + self.ui.tableWidgetInbox.item(currentRow, 0).setFont(font) self.ui.tableWidgetInbox.item(currentRow, 1).setFont(font) self.ui.tableWidgetInbox.item(currentRow, 2).setFont(font) From 40033d9e87d0bb821b34cbf6291c0d769d969276 Mon Sep 17 00:00:00 2001 From: Amos Bairn Date: Wed, 11 Sep 2013 23:04:16 -0700 Subject: [PATCH 06/48] add api method addChan This solves issue #484 --- src/bitmessagemain.py | 64 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 64 insertions(+) diff --git a/src/bitmessagemain.py b/src/bitmessagemain.py index 88538297..413ae481 100755 --- a/src/bitmessagemain.py +++ b/src/bitmessagemain.py @@ -384,6 +384,70 @@ class MySimpleXMLRPCRequestHandler(SimpleXMLRPCRequestHandler): ('getDeterministicAddress', addressVersionNumber, streamNumber, 'unused API address', numberOfAddresses, passphrase, eighteenByteRipe)) return shared.apiAddressGeneratorReturnQueue.get() + elif method == 'addChan': + #get passphrase, addressVersionNumber, and streamNumber + if len(params) == 0: + raise APIError(0, 'I need parameters.') + elif len(params) == 1: + passphrase, = params + address = '' + addressVersionNumber = 0 + streamNumber = 0 + label = '' + elif len(params) == 2: + passphrase, address = params + status, addressVersionNumber, streamNumber, ripe = decodeAddress(address) + label = '' + elif len(params) == 3: + passphrase, addressVersionNumber, streamNumber = params + address = '' + label = '' + elif len(params) == 4: + passphrase, addressVersionNumber, streamNumber, label = params + address = '' + label = self._decode(label, "base64") + else: + raise APIError(0, 'Too many parameters!') + + if len(passphrase) == 0: + raise APIError(1, 'The specified passphrase is blank.') + passphrase = self._decode(passphrase, "base64") + if label == '': + label = '[chan] ' + passphrase + if addressVersionNumber == 0: # 0 means "just use the proper addressVersionNumber" + addressVersionNumber = 3 + if addressVersionNumber != 3: + raise APIError(2,'The address version number currently must be 3 (or 0 which means auto-select). ' + addressVersionNumber + ' isn\'t supported.') + if streamNumber == 0: # 0 means "just use the most available stream" + streamNumber = 1 + if streamNumber != 1: + raise APIError(3,'The stream number must be 1 (or 0 which means auto-select). Others aren\'t supported.') + + #create identity + shared.apiAddressGeneratorReturnQueue.queue.clear() + logger.debug('Requesting that the addressGenerator create chan %s.', passphrase) + shared.addressGeneratorQueue.put(('createChan', addressVersionNumber, streamNumber, label, passphrase)) + queueReturn = shared.apiAddressGeneratorReturnQueue.get() + if len(queueReturn) == 0: + raise APIError(24, 'Chan address already present.') + createdAddress = queueReturn[0] + if address == '': + address = createdAddress + elif createdAddress != address: + raise APIError(18, 'Chan name does not match address.') + + #add address to addressbook + address = addBMIfNotPresent(address) + self._verifyAddress(address) + queryreturn = sqlQuery("SELECT address FROM addressbook WHERE address=?", address) + if queryreturn != []: + raise APIError(16, 'You already have this address in your address book.') + + sqlExecute("INSERT INTO addressbook VALUES(?,?)", label, address) + shared.UISignalQueue.put(('rerenderInboxFromLabels','')) + shared.UISignalQueue.put(('rerenderSentToLabels','')) + shared.UISignalQueue.put(('rerenderAddressBook','')) + return "Added chan %s with address %s and label %s." %(passphrase, address, label) elif method == 'getAllInboxMessages': queryreturn = sqlQuery( '''SELECT msgid, toaddress, fromaddress, subject, received, message, encodingtype, read FROM inbox where folder='inbox' ORDER BY received''') From 240e9b5b58e156d32bec2caa373e9d3889971cc1 Mon Sep 17 00:00:00 2001 From: sendiulo Date: Mon, 16 Sep 2013 21:08:55 +0200 Subject: [PATCH 07/48] Updated to work with QPixmap instead of PIL! The original source is licensed under a BSD-License, so it should be fine to use. --- src/bitmessageqt/__init__.py | 61 ++--- src/identicon_py_license | 11 - src/pydenticon.py | 409 ---------------------------- src/{identicon.py => qidenticon.py} | 122 ++++++--- 4 files changed, 119 insertions(+), 484 deletions(-) delete mode 100644 src/identicon_py_license delete mode 100644 src/pydenticon.py rename src/{identicon.py => qidenticon.py} (56%) diff --git a/src/bitmessageqt/__init__.py b/src/bitmessageqt/__init__.py index 23bbf8cd..1ceca0eb 100644 --- a/src/bitmessageqt/__init__.py +++ b/src/bitmessageqt/__init__.py @@ -47,20 +47,6 @@ except Exception as err: print 'Error message:', err sys.exit() -# for the md5 hash (used for the identicons) -import hashlib - -# load identicon code -# :Author:Shin Adachi -# Licesensed under FreeBSD License. -import identicon -# usage: identicon.render_identicon(code, size) -# requires PIL - -# load another identicon code -# http://boottunes.googlecode.com/svn-history/r302/trunk/src/pydenticon.py -from pydenticon import Pydenticon - try: _encoding = QtGui.QApplication.UnicodeUTF8 except AttributeError: @@ -74,7 +60,8 @@ def identiconize(address): if youdontwantidenticons == True: idcon = QtGui.QIcon() return idcon - + size = 3 + str_broadcast_subscribers = '[Broadcast subscribers]' if address == str_broadcast_subscribers: idcon = QtGui.QIcon(":/newPrefix/images/can-icon-24px.png") @@ -86,25 +73,37 @@ def identiconize(address): # Attacks where someone creates an address to mimic someone else's identicon should be impossible then # i think it should generate a random string by default - if True: # identicon.py + identicon_lib = 'qidenticon' + if identicon_lib == 'qidenticon': + # originally by: + # :Author:Shin Adachi + # Licesensed under FreeBSD License. + # stripped from PIL and uses QT instead + import qidenticon + + import hashlib hash = hashlib.md5(addBMIfNotPresent(address)+suffix).hexdigest() - idcon_render = identicon.render_identicon(int(hash, 16), 48) - image = idcon_render - # http://qt-project.org/forums/viewthread/5866 - # from PIL import Image - # from PyQt4.QtGui import QImage, QImageReader, QLabel, QPixmap, QApplication - else: # pydenticon.py + image = qidenticon.render_identicon(int(hash, 16), 48) + idcon = QtGui.QIcon() + idcon.addPixmap(image, QtGui.QIcon.Normal, QtGui.QIcon.Off) + return idcon + + elif identicon_lib == 'pydenticon': # pydenticon.py + # print identicon_lib + # load another identicon code + # http://boottunes.googlecode.com/svn-history/r302/trunk/src/pydenticon.py + from pydenticon import Pydenticon + # GPLv3 !!! idcon_render = Pydenticon(addBMIfNotPresent(address)+suffix) image = idcon_render._render() - - # im = Image.open('images/'+hash+'.png') - # http://stackoverflow.com/questions/6756820/python-pil-image-tostring - data = image.convert("RGBA").tostring("raw", "RGBA") - image = QImage(data, image.size[0], image.size[1], QImage.Format_ARGB32) - pix = QPixmap.fromImage(image) - idcon = QtGui.QIcon() - idcon.addPixmap(pix, QtGui.QIcon.Normal, QtGui.QIcon.Off) - return idcon + # im = Image.open('images/'+hash+'.png') + # http://stackoverflow.com/questions/6756820/python-pil-image-tostring + data = image.convert("RGBA").tostring("raw", "RGBA") + image = QImage(data, image.size[0], image.size[1], QImage.Format_ARGB32) + pix = QPixmap.fromImage(image) + idcon = QtGui.QIcon() + idcon.addPixmap(pix, QtGui.QIcon.Normal, QtGui.QIcon.Off) + return idcon class MyForm(QtGui.QMainWindow): diff --git a/src/identicon_py_license b/src/identicon_py_license deleted file mode 100644 index e6e964fb..00000000 --- a/src/identicon_py_license +++ /dev/null @@ -1,11 +0,0 @@ -identicon.py is Licesensed under FreeBSD License. -(http://www.freebsd.org/copyright/freebsd-license.html) - -Copyright 1994-2009 Shin Adachi. All rights reserved. - -Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: - - 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. - 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/src/pydenticon.py b/src/pydenticon.py deleted file mode 100644 index 57076093..00000000 --- a/src/pydenticon.py +++ /dev/null @@ -1,409 +0,0 @@ -""" -Python implementation of PHP Identicons, found here: -http://sourceforge.net/projects/identicons/ - -Licensed under the GPLv3. -""" -from PIL import Image, ImageDraw, ImagePath, ImageColor -from math import fabs as abs -from hashlib import md5 -import StringIO - -class Pydenticon: - """ - Generate an identicon. - - """ - - WHITE = (255, 255, 255) - - def __init__(self, input, size = 128): - self.hash = md5(input).hexdigest() - self.size = size - - def as_string(self): - """ - Return the image as a string representation of a PNG file. - - """ - if not hasattr(self, 'image'): - self.image = self._render() - output = StringIO.StringIO() - self.image.save(output, format="PNG") - contents = output.getvalue() - output.close() - return contents - - def save(self, filename): - """ - Save the result as a PNG file. - - """ - if not hasattr(self, 'image'): - self.image = self._render() - self.image.save(filename, format="PNG") - - def _get_sprite(self, shape, r, g, b, rotation): - """ - Generate sprite for corners and sides and returnro a PIL.Image object. - - """ - sprite = Image.new("RGB", (self.size, self.size), self.WHITE) - - if shape == 0: # triangle - points = [ - 0.5,1, - 1,0, - 1,1 - ] - elif shape == 1: #parallelogram - points = [ - 0.5,0, - 1,0, - 0.5,1, - 0,1 - ] - elif shape == 2: # mouse ears - points = [ - 0.5,0, - 1,0, - 1,1, - 0.5,1, - 1,0.5 - ] - elif shape == 3: # ribbon - points = [ - 0,0.5, - 0.5,0, - 1,0.5, - 0.5,1, - 0.5,0.5 - ] - elif shape == 4: # sails - points = [ - 0,0.5, - 1,0, - 1,1, - 0,1, - 1,0.5 - ] - elif shape == 5: # fins - points = [ - 1,0, - 1,1, - 0.5,1, - 1,0.5, - 0.5,0.5 - ] - elif shape == 6: # beak - points = [ - 0,0, - 1,0, - 1,0.5, - 0,0, - 0.5,1, - 0,1 - ] - elif shape == 7: # chevron - points = [ - 0,0, - 0.5,0, - 1,0.5, - 0.5,1, - 0,1, - 0.5,0.5 - ] - elif shape == 8: # fish - points = [ - 0.5,0, - 0.5,0.5, - 1,0.5, - 1,1, - 0.5,1, - 0.5,0.5, - 0,0.5 - ] - elif shape == 9: # kite - points = [ - 0,0, - 1,0, - 0.5,0.5, - 1,0.5, - 0.5,1, - 0.5,0.5, - 0,1 - ] - elif shape == 10: # trough - points = [ - 0,0.5, - 0.5,1, - 1,0.5, - 0.5,0, - 1,0, - 1,1, - 0,1 - ] - elif shape == 11: # rays - points = [ - 0.5,0, - 1,0, - 1,1, - 0.5,1, - 1,0.75, - 0.5,0.5, - 1,0.25 - ] - elif shape == 12: # double rhombus - points = [ - 0,0.5, - 0.5,0, - 0.5,0.5, - 1,0, - 1,0.5, - 0.5,1, - 0.5,0.5, - 0,1 - ] - elif shape == 13: # crown - points = [ - 0,0, - 1,0, - 1,1, - 0,1, - 1,0.5, - 0.5,0.25, - 0.5,0.75, - 0,0.5, - 0.5,0.25 - ] - elif shape == 14: # radioactive - points = [ - 0,0.5, - 0.5,0.5, - 0.5,0, - 1,0, - 0.5,0.5, - 1,0.5, - 0.5,1, - 0.5,0.5, - 0,1 - ] - else: # tiles - points = [ - 0,0, - 1,0, - 0.5,0.5, - 0.5,0, - 0,0.5, - 1,0.5, - 0.5,1, - 0.5,0.5, - 0,1 - ] - - # apply ratios - for i in range(0, len(points)): - points[i] = points[i] * self.size - - draw = ImageDraw.Draw(sprite) - draw.polygon(points, fill=(r, g, b)) - - for i in range(rotation): - sprite = sprite.rotate(90) - - return sprite - - def _get_center(self, shape, fR, fG, fB, bR, bG, bB, useBg): - """ - Generate sprite for center block and return a PIL.Image object. - - """ - sprite = Image.new("RGB", (self.size, self.size), self.WHITE) - - # make sure there's enough contrast before we use background color of side sprite - sufficient_contrast = ( - abs(fR - bR) > 127 or abs(fG - bG) > 127 or abs(fB - bB) > 127 - ) - if useBg > 0 and sufficient_contrast: - bg = (bR, bG, bB) - else: - bg = (255, 255, 255) - - if shape == 0: # empty - points = [] - - elif shape == 1: # fill - points = [ - 0,0, - 1,0, - 1,1, - 0,1 - ] - elif shape == 2: # diamond - points = [ - 0.5,0, - 1,0.5, - 0.5,1, - 0,0.5 - ] - elif shape == 3: # reverse diamond - points = [ - 0,0, - 1,0, - 1,1, - 0,1, - 0,0.5, - 0.5,1, - 1,0.5, - 0.5,0, - 0,0.5 - ] - elif shape == 4: # cross - points = [ - 0.25,0, - 0.75,0, - 0.5,0.5, - 1,0.25, - 1,0.75, - 0.5,0.5, - 0.75,1, - 0.25,1, - 0.5,0.5, - 0,0.75, - 0,0.25, - 0.5,0.5 - ] - elif shape == 5: # morning star - points = [ - 0,0, - 0.5,0.25, - 1,0, - 0.75,0.5, - 1,1, - 0.5,0.75, - 0,1, - 0.25,0.5 - ] - elif shape == 6: # small square - points = [ - 0.33,0.33, - 0.67,0.33, - 0.67,0.67, - 0.33,0.67 - ] - elif shape == 7: # checkerboard - points = [ - 0,0, - 0.33,0, - 0.33,0.33, - 0.66,0.33, - 0.67,0, - 1,0, - 1,0.33, - 0.67,0.33, - 0.67,0.67, - 1,0.67, - 1,1, - 0.67,1, - 0.67,0.67, - 0.33,0.67, - 0.33,1, - 0,1, - 0,0.67, - 0.33,0.67, - 0.33,0.33, - 0,0.33 - ] - - # apply ratios - for i in range(0, len(points)): - points[i] = points[i] * self.size - - if len(points) > 0: - draw = ImageDraw.Draw(sprite) - draw.polygon(points, fill=(fR, fG, fB)) - - return sprite - - def _render(self): - """ - Render the image and return the PIL.Image object. - - """ - # parse hash string - corner_sprite_shape = int(self.hash[0:1], 16) - side_sprite_shape = int(self.hash[1:2], 16) - center_sprite_shape = int(self.hash[2:3], 16) & 7 - - corner_sprite_rot = int(self.hash[3:4], 16) & 3 - side_sprite_rot = int(self.hash[4:5], 16) & 3 - center_sprite_bg = int(self.hash[5:6], 16) % 2 - - # corner sprite foreground color - corner_sprite_fg_r = int(self.hash[6:8], 16) - corner_sprite_fg_g = int(self.hash[8:10], 16) - corner_sprite_fg_b = int(self.hash[10:12], 16) - - # side sprite foreground color - side_sprite_fg_r = int(self.hash[12:14], 16) - side_sprite_fg_g = int(self.hash[14:16], 16) - side_sprite_fg_b = int(self.hash[16:18], 16) - - # final angle of rotation - angle = int(self.hash[18:20], 16) - - # start with blank 3X sized identicon - identicon = Image.new("RGB", (self.size*3, self.size*3), self.WHITE) - - # generate corner sprites - corner = self._get_sprite( - corner_sprite_shape, - corner_sprite_fg_r, - corner_sprite_fg_g, - corner_sprite_fg_b, - corner_sprite_rot - ) - identicon.paste(corner, (0, 0)) - corner = corner.rotate(90) - identicon.paste(corner, (0, self.size*2)) - corner = corner.rotate(90) - identicon.paste(corner, (self.size*2, self.size*2)) - corner = corner.rotate(90) - identicon.paste(corner, (self.size*2, 0)) - - # generate side sprites - side = self._get_sprite( - side_sprite_shape, - side_sprite_fg_r, - side_sprite_fg_g, - side_sprite_fg_b, - side_sprite_rot - ) - identicon.paste(side, (self.size, 0)) - side = side.rotate(90) - identicon.paste(side, (0, self.size)) - side = side.rotate(90) - identicon.paste(side, (self.size, self.size*2)) - side = side.rotate(90) - identicon.paste(side, (self.size*2, self.size)) - - # generate center sprite - center = self._get_center( - center_sprite_shape, - corner_sprite_fg_r, - corner_sprite_fg_g, - corner_sprite_fg_b, - side_sprite_fg_r, - side_sprite_fg_g, - side_sprite_fg_b, - center_sprite_bg - ) - identicon.paste(center, (self.size, self.size)) - - # resize image - resized = identicon.resize( - (self.size, self.size), - Image.ANTIALIAS - ) - - return resized \ No newline at end of file diff --git a/src/identicon.py b/src/qidenticon.py similarity index 56% rename from src/identicon.py rename to src/qidenticon.py index fae5a250..5d77aeb6 100644 --- a/src/identicon.py +++ b/src/qidenticon.py @@ -1,6 +1,40 @@ #!/usr/bin/env python # -*- coding:utf-8 -*- + +### +# qidenticon.py is Licesensed under FreeBSD License. +# (http://www.freebsd.org/copyright/freebsd-license.html) +# +# Copyright 2013 "Sendiulo". All rights reserved. +# +# Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: +# +# 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. +# 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +### + +### +# identicon.py is Licesensed under FreeBSD License. +# (http://www.freebsd.org/copyright/freebsd-license.html) +# +# Copyright 1994-2009 Shin Adachi. All rights reserved. +# +# Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: +# +# 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. +# 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +### + """ +qidenticon.py +identicon python implementation with QPixmap output +by sendiulo + +based on identicon.py identicon python implementation. by Shin Adachi @@ -11,19 +45,17 @@ by Shin Adachi >>> python identicon.py [code] == python == ->>> import identicon ->>> identicon.render_identicon(code, size) +>>> import qtidenticon +>>> qtidenticon.render_identicon(code, size) Return a PIL Image class instance which have generated identicon image. ```size``` specifies `patch size`. Generated image size is 3 * ```size```. """ -# g -# PIL Modules -import Image -import ImageDraw -import ImagePath -import ImageColor +# we probably don't need all of them, but i don't want to check now +from PyQt4 import QtCore, QtGui +from PyQt4.QtCore import * +from PyQt4.QtGui import * __all__ = ['render_identicon', 'IdenticonRendererBase'] @@ -114,45 +146,46 @@ class IdenticonRendererBase(object): def render(self, size): """ - render identicon to PIL.Image + render identicon to QPixmap @param size identicon patchsize. (image size is 3 * [size]) - @return PIL.Image + @return QPixmap """ # decode the code middle, corner, side, foreColor, backColor = self.decode(self.code) - # make image - image = Image.new("RGB", (size * 3, size * 3)) - draw = ImageDraw.Draw(image) + # make image + image = QPixmap(QSize(size * 3, size * 3)) # fill background - draw.rectangle((0, 0, image.size[0], image.size[1]), fill=0) + image.fill(QtGui.QColor(0,0,0)) kwds = { - 'draw': draw, + 'image': image, 'size': size, 'foreColor': foreColor, 'backColor': backColor} + # middle patch - self.drawPatch((1, 1), middle[2], middle[1], middle[0], **kwds) - + image = self.drawPatchQt((1, 1), middle[2], middle[1], middle[0], **kwds) + # side patch kwds['type'] = side[0] for i in xrange(4): pos = [(1, 0), (2, 1), (1, 2), (0, 1)][i] - self.drawPatch(pos, side[2] + 1 + i, side[1], **kwds) - + image = self.drawPatchQt(pos, side[2] + 1 + i, side[1], **kwds) + # corner patch kwds['type'] = corner[0] for i in xrange(4): pos = [(0, 0), (2, 0), (2, 2), (0, 2)][i] - self.drawPatch(pos, corner[2] + 1 + i, corner[1], **kwds) + image = self.drawPatchQt(pos, corner[2] + 1 + i, corner[1], **kwds) return image - def drawPatch(self, pos, turn, invert, type, draw, size, foreColor, + + def drawPatchQt(self, pos, turn, invert, type, image, size, foreColor, backColor): """ @param size patch size @@ -162,24 +195,43 @@ class IdenticonRendererBase(object): # blank patch invert = not invert path = [(0., 0.), (1., 0.), (1., 1.), (0., 1.), (0., 0.)] - patch = ImagePath.Path(path) + + + polygon = QPolygonF([QPointF(x*size,y*size) for x,y in path]) + + rot = turn % 4 + rot_trans = [QPointF(0.,0.), QPointF(size, 0.), QPointF(size, size), QPointF(0., size)] + rotation = [0,90,180,270] + # print rotation[rot], rot_trans[rot], turn + pen_color = QtGui.QColor(255, 255, 255, 0) + pen = QtGui.QPen(pen_color, Qt.SolidPattern) + foreBrush = QtGui.QBrush(foreColor, Qt.SolidPattern) + backBrush = QtGui.QBrush(backColor, Qt.SolidPattern) if invert: - foreColor, backColor = backColor, foreColor + foreBrush, backBrush = backBrush, foreBrush - mat = Matrix2D.rotateSquare(turn, pivot=(0.5, 0.5)) *\ - Matrix2D.translate(*pos) *\ - Matrix2D.scale(size, size) + painter = QPainter() + painter.begin(image) + painter.setPen(pen) - patch.transform(mat.for_PIL()) - draw.rectangle((pos[0] * size, pos[1] * size, (pos[0] + 1) * size, - (pos[1] + 1) * size), fill=backColor) - draw.polygon(patch, fill=foreColor, outline=foreColor) + painter.translate(pos[0]*size, pos[1]*size) + painter.translate(rot_trans[rot]) + painter.rotate(rotation[rot]) + + painter.setBrush(backBrush) + painter.drawRect(0,0, size, size) + + painter.setBrush(foreBrush) + painter.drawPolygon(polygon, Qt.WindingFill) + + painter.end() + + return image ### virtual functions def decode(self, code): raise NotImplementedError - - + class DonRenderer(IdenticonRendererBase): """ Don Park's implementation of identicon @@ -230,11 +282,14 @@ class DonRenderer(IdenticonRendererBase): red = (code >> 27) & 0x1F foreColor = (red << 3, green << 3, blue << 3) + foreColor = QtGui.QColor(*foreColor) + + bgcolor = QtGui.QColor(255,255,255) return (middleType, middleInvert, 0),\ (cornerType, cornerInvert, cornerTurn),\ (sideType, sideInvert, sideTurn),\ - foreColor, ImageColor.getrgb('white') + foreColor, bgcolor def render_identicon(code, size, renderer=None): @@ -258,5 +313,6 @@ if __name__ == '__main__': else: code = int(code) + app = Qt.QApplication(sys.argv) icon = render_identicon(code, 24) icon.save('%08x.png' % code, 'PNG') From 0959c9c07c2534d12bdde3c62060e07c1dfad61b Mon Sep 17 00:00:00 2001 From: sendiulo Date: Tue, 17 Sep 2013 10:55:26 +0200 Subject: [PATCH 08/48] ... --- src/bitmessageqt/__init__.py | 64 ++++++----- src/qidenticon.py | 198 +++++++++++------------------------ 2 files changed, 98 insertions(+), 164 deletions(-) diff --git a/src/bitmessageqt/__init__.py b/src/bitmessageqt/__init__.py index 1ceca0eb..42ccb7ae 100644 --- a/src/bitmessageqt/__init__.py +++ b/src/bitmessageqt/__init__.py @@ -56,54 +56,68 @@ def _translate(context, text): return QtGui.QApplication.translate(context, text) def identiconize(address): - youdontwantidenticons = False - if youdontwantidenticons == True: - idcon = QtGui.QIcon() - return idcon - size = 3 + size = 1 str_broadcast_subscribers = '[Broadcast subscribers]' if address == str_broadcast_subscribers: idcon = QtGui.QIcon(":/newPrefix/images/can-icon-24px.png") return idcon - - suffix = "asdf" + try: + identiconsuffix = shared.config.get('bitmessagesettings', 'identiconsuffix') + except: + identiconsuffix = '' # here you could put "@bitmessge.ch" or "@bm.addr" to make it compatible with other identicon generators # instead, you could also use a pseudo-password to salt the generation of the identicons # Attacks where someone creates an address to mimic someone else's identicon should be impossible then # i think it should generate a random string by default - identicon_lib = 'qidenticon' - if identicon_lib == 'qidenticon': + try: + identicon_lib = shared.config.get('bitmessagesettings', 'identicon') + except: + identicon_lib = 'qidenticon' + + + if (identicon_lib[:len('qidenticon')] == 'qidenticon'): + # print identicon_lib # originally by: # :Author:Shin Adachi # Licesensed under FreeBSD License. # stripped from PIL and uses QT instead import qidenticon - import hashlib - hash = hashlib.md5(addBMIfNotPresent(address)+suffix).hexdigest() - image = qidenticon.render_identicon(int(hash, 16), 48) + hash = hashlib.md5(addBMIfNotPresent(address)+identiconsuffix).hexdigest() + use_two_colors = (identicon_lib[:len('qidenticon_two')] == 'qidenticon_two') + transparent = (identicon_lib == 'qidenticon_x') | (identicon_lib == 'qidenticon_two_x') + penwidth = 0 + pixmap = qidenticon.render_identicon(int(hash, 16), 48, use_two_colors, transparent, penwidth) idcon = QtGui.QIcon() - idcon.addPixmap(image, QtGui.QIcon.Normal, QtGui.QIcon.Off) + idcon.addPixmap(pixmap, QtGui.QIcon.Normal, QtGui.QIcon.Off) return idcon - - elif identicon_lib == 'pydenticon': # pydenticon.py + elif identicon_lib == 'pydenticon': # print identicon_lib - # load another identicon code - # http://boottunes.googlecode.com/svn-history/r302/trunk/src/pydenticon.py + # Here you could load pydenticon.py (just put it in the "src" folder of your Bitmessage source) from pydenticon import Pydenticon - # GPLv3 !!! - idcon_render = Pydenticon(addBMIfNotPresent(address)+suffix) - image = idcon_render._render() - # im = Image.open('images/'+hash+'.png') - # http://stackoverflow.com/questions/6756820/python-pil-image-tostring - data = image.convert("RGBA").tostring("raw", "RGBA") - image = QImage(data, image.size[0], image.size[1], QImage.Format_ARGB32) - pix = QPixmap.fromImage(image) + # It is not included in the source, because it is licensed under GPLv3 + # GPLv3 is a copyleft license that would influence our licensing + # Find the source here: http://boottunes.googlecode.com/svn-history/r302/trunk/src/pydenticon.py + # note that it requires PIL to be installed: http://www.pythonware.com/products/pil/ + idcon_render = Pydenticon(addBMIfNotPresent(address)+identiconsuffix, size) + rendering = idcon_render._render() + data = rendering.convert("RGBA").tostring("raw", "RGBA") + qim = QImage(data, size, size, QImage.Format_ARGB32) + pix = QPixmap.fromImage(qim) idcon = QtGui.QIcon() idcon.addPixmap(pix, QtGui.QIcon.Normal, QtGui.QIcon.Off) return idcon + elif identicon_lib == False: + idcon = QtGui.QIcon() + return idcon + else: + if identicon_lib & len(identicon_lib) > 0: + print 'Error: couldn\'t find this identicon library: ', identicon_lib + print 'Control for typos!' + idcon = QtGui.QIcon() + return idcon class MyForm(QtGui.QMainWindow): diff --git a/src/qidenticon.py b/src/qidenticon.py index 5d77aeb6..ffc25eea 100644 --- a/src/qidenticon.py +++ b/src/qidenticon.py @@ -41,9 +41,6 @@ by Shin Adachi = usage = -== commandline == ->>> python identicon.py [code] - == python == >>> import qtidenticon >>> qtidenticon.render_identicon(code, size) @@ -59,80 +56,6 @@ from PyQt4.QtGui import * __all__ = ['render_identicon', 'IdenticonRendererBase'] - -class Matrix2D(list): - """Matrix for Patch rotation""" - def __init__(self, initial=[0.] * 9): - assert isinstance(initial, list) and len(initial) == 9 - list.__init__(self, initial) - - def clear(self): - for i in xrange(9): - self[i] = 0. - - def set_identity(self): - self.clear() - for i in xrange(3): - self[i] = 1. - - def __str__(self): - return '[%s]' % ', '.join('%3.2f' % v for v in self) - - def __mul__(self, other): - r = [] - if isinstance(other, Matrix2D): - for y in xrange(3): - for x in xrange(3): - v = 0.0 - for i in xrange(3): - v += (self[i * 3 + x] * other[y * 3 + i]) - r.append(v) - else: - raise NotImplementedError - return Matrix2D(r) - - def for_PIL(self): - return self[0:6] - - @classmethod - def translate(kls, x, y): - return kls([1.0, 0.0, float(x), - 0.0, 1.0, float(y), - 0.0, 0.0, 1.0]) - - @classmethod - def scale(kls, x, y): - return kls([float(x), 0.0, 0.0, - 0.0, float(y), 0.0, - 0.0, 0.0, 1.0]) - - """ - # need `import math` - @classmethod - def rotate(kls, theta, pivot=None): - c = math.cos(theta) - s = math.sin(theta) - - matR = kls([c, -s, 0., s, c, 0., 0., 0., 1.]) - if not pivot: - return matR - return kls.translate(-pivot[0], -pivot[1]) * matR * - kls.translate(*pivot) - """ - - @classmethod - def rotateSquare(kls, theta, pivot=None): - theta = theta % 4 - c = [1., 0., -1., 0.][theta] - s = [0., 1., 0., -1.][theta] - - matR = kls([c, -s, 0., s, c, 0., 0., 0., 1.]) - if not pivot: - return matR - return kls.translate(-pivot[0], -pivot[1]) * matR * \ - kls.translate(*pivot) - - class IdenticonRendererBase(object): PATH_SET = [] @@ -144,7 +67,7 @@ class IdenticonRendererBase(object): code = int(code) self.code = code - def render(self, size): + def render(self, size, twoColor, transparent, penwidth): """ render identicon to QPixmap @@ -153,30 +76,34 @@ class IdenticonRendererBase(object): """ # decode the code - middle, corner, side, foreColor, backColor = self.decode(self.code) + middle, corner, side, foreColor, secondColor, swap_cross = self.decode(self.code, twoColor) # make image - image = QPixmap(QSize(size * 3, size * 3)) + image = QPixmap(QSize(size * 3 +penwidth, size * 3 +penwidth)) # fill background - image.fill(QtGui.QColor(0,0,0)) + backColor = QtGui.QColor(255,255,255,(not transparent) * 255) + image.fill(backColor) kwds = { 'image': image, 'size': size, - 'foreColor': foreColor, + 'foreColor': foreColor if swap_cross else secondColor, + 'penwidth': penwidth, 'backColor': backColor} # middle patch image = self.drawPatchQt((1, 1), middle[2], middle[1], middle[0], **kwds) - + # side patch + kwds['foreColor'] = foreColor kwds['type'] = side[0] for i in xrange(4): pos = [(1, 0), (2, 1), (1, 2), (0, 1)][i] image = self.drawPatchQt(pos, side[2] + 1 + i, side[1], **kwds) # corner patch + kwds['foreColor'] = secondColor kwds['type'] = corner[0] for i in xrange(4): pos = [(0, 0), (2, 0), (2, 2), (0, 2)][i] @@ -186,7 +113,7 @@ class IdenticonRendererBase(object): def drawPatchQt(self, pos, turn, invert, type, image, size, foreColor, - backColor): + backColor, penwidth): """ @param size patch size """ @@ -200,30 +127,37 @@ class IdenticonRendererBase(object): polygon = QPolygonF([QPointF(x*size,y*size) for x,y in path]) rot = turn % 4 - rot_trans = [QPointF(0.,0.), QPointF(size, 0.), QPointF(size, size), QPointF(0., size)] + rect = [QPointF(0.,0.), QPointF(size, 0.), QPointF(size, size), QPointF(0., size)] rotation = [0,90,180,270] - # print rotation[rot], rot_trans[rot], turn - pen_color = QtGui.QColor(255, 255, 255, 0) - pen = QtGui.QPen(pen_color, Qt.SolidPattern) + + nopen = QtGui.QPen(foreColor, Qt.NoPen) foreBrush = QtGui.QBrush(foreColor, Qt.SolidPattern) - backBrush = QtGui.QBrush(backColor, Qt.SolidPattern) - if invert: - foreBrush, backBrush = backBrush, foreBrush + if penwidth > 0: + pen_color = QtGui.QColor(223, 223, 223) + pen = QtGui.QPen(pen_color, Qt.SolidPattern) + pen.setWidth(penwidth) painter = QPainter() painter.begin(image) - painter.setPen(pen) + painter.setPen(nopen) - painter.translate(pos[0]*size, pos[1]*size) - painter.translate(rot_trans[rot]) + painter.translate(pos[0]*size +penwidth/2, pos[1]*size +penwidth/2) + painter.translate(rect[rot]) painter.rotate(rotation[rot]) - painter.setBrush(backBrush) - painter.drawRect(0,0, size, size) - + if invert: + # subtract the actual polygon from a rectangle to invert it + rect_polygon = QPolygonF(rect) + polygon = rect_polygon.subtracted(polygon) painter.setBrush(foreBrush) + if penwidth > 0: + # draw the borders + painter.setPen(pen) + painter.drawPolygon(polygon, Qt.WindingFill) + # draw the fill + painter.setPen(nopen) painter.drawPolygon(polygon, Qt.WindingFill) - + painter.end() return image @@ -263,56 +197,42 @@ class DonRenderer(IdenticonRendererBase): p = map(lambda vec: (vec[0] / 4.0, vec[1] / 4.0), PATH_SET[idx]) PATH_SET[idx] = p + p[:1] - def decode(self, code): - # decode the code - middleType = self.MIDDLE_PATCH_SET[code & 0x03] - middleInvert= (code >> 2) & 0x01 - cornerType = (code >> 3) & 0x0F - cornerInvert= (code >> 7) & 0x01 - cornerTurn = (code >> 8) & 0x03 - sideType = (code >> 10) & 0x0F - sideInvert = (code >> 14) & 0x01 - sideTurn = (code >> 15) & 0x03 - # bug reported by Masato Hagiwara - # http://lilyx.net/iconlang-en/ - # blue = (code >> 16) & 0x1F - # green = (code >> 21) & 0x1F - blue = (code >> 17) & 0x1F - green = (code >> 22) & 0x1F - red = (code >> 27) & 0x1F + def decode(self, code, twoColor): + # decode the code + shift = 0; middleType = (code >> shift) & 0x03 + shift += 2; middleInvert= (code >> shift) & 0x01 + shift += 1; cornerType = (code >> shift) & 0x0F + shift += 4; cornerInvert= (code >> shift) & 0x01 + shift += 1; cornerTurn = (code >> shift) & 0x03 + shift += 2; sideType = (code >> shift) & 0x0F + shift += 4; sideInvert = (code >> shift) & 0x01 + shift += 1; sideTurn = (code >> shift) & 0x03 + shift += 2; blue = (code >> shift) & 0x1F + shift += 5; green = (code >> shift) & 0x1F + shift += 5; red = (code >> shift) & 0x1F + shift += 5; second_blue = (code >> shift) & 0x1F + shift += 5; second_green= (code >> shift) & 0x1F + shift += 5; second_red = (code >> shift) & 0x1F + shift += 1; swap_cross = (code >> shift) & 0x01 + + middleType = self.MIDDLE_PATCH_SET[middleType] foreColor = (red << 3, green << 3, blue << 3) foreColor = QtGui.QColor(*foreColor) - bgcolor = QtGui.QColor(255,255,255) + if twoColor: + secondColor = (second_blue << 3, second_green << 3, second_red << 3) + secondColor = QtGui.QColor(*secondColor) + else: + secondColor = foreColor return (middleType, middleInvert, 0),\ (cornerType, cornerInvert, cornerTurn),\ (sideType, sideInvert, sideTurn),\ - foreColor, bgcolor + foreColor, secondColor, swap_cross -def render_identicon(code, size, renderer=None): +def render_identicon(code, size, twoColor=False, transparent=False, penwidth=0, renderer=None): if not renderer: renderer = DonRenderer - return renderer(code).render(size) - - -if __name__ == '__main__': - import sys - - if len(sys.argv) < 2: - print 'usage: python identicon.py [CODE]....' - raise SystemExit - - for code in sys.argv[1:]: - if code.startswith('0x') or code.startswith('0X'): - code = int(code[2:], 16) - elif code.startswith('0'): - code = int(code[1:], 8) - else: - code = int(code) - - app = Qt.QApplication(sys.argv) - icon = render_identicon(code, 24) - icon.save('%08x.png' % code, 'PNG') + return renderer(code).render(size, twoColor, transparent, penwidth) \ No newline at end of file From 86485a9b096bb5b6995f900a2e8970f074a85859 Mon Sep 17 00:00:00 2001 From: sendiulo Date: Wed, 18 Sep 2013 17:39:45 +0200 Subject: [PATCH 09/48] nonfunctional identicon settings-GUI --- src/bitmessageqt/__init__.py | 23 ++++-- src/bitmessageqt/bitmessage_icons.qrc | 5 ++ src/bitmessageqt/bitmessageui.py | 12 ++- src/bitmessageqt/settings.py | 89 ++++++++++++++------ src/bitmessageqt/settings.ui | 113 ++++++++++++++++++++++---- src/qidenticon.py | 45 ++++++---- 6 files changed, 222 insertions(+), 65 deletions(-) diff --git a/src/bitmessageqt/__init__.py b/src/bitmessageqt/__init__.py index 42ccb7ae..2a5eb10e 100644 --- a/src/bitmessageqt/__init__.py +++ b/src/bitmessageqt/__init__.py @@ -56,7 +56,7 @@ def _translate(context, text): return QtGui.QApplication.translate(context, text) def identiconize(address): - size = 1 + size = 48 str_broadcast_subscribers = '[Broadcast subscribers]' if address == str_broadcast_subscribers: @@ -71,11 +71,15 @@ def identiconize(address): # Attacks where someone creates an address to mimic someone else's identicon should be impossible then # i think it should generate a random string by default + # If you include another identicon library, please generate an + # example identicon with the following md5 hash: + # 3fd4bf901b9d4ea1394f0fb358725b28 + try: identicon_lib = shared.config.get('bitmessagesettings', 'identicon') except: - identicon_lib = 'qidenticon' - + # default to no identicons + identicon_lib = False if (identicon_lib[:len('qidenticon')] == 'qidenticon'): # print identicon_lib @@ -87,11 +91,13 @@ def identiconize(address): import hashlib hash = hashlib.md5(addBMIfNotPresent(address)+identiconsuffix).hexdigest() use_two_colors = (identicon_lib[:len('qidenticon_two')] == 'qidenticon_two') - transparent = (identicon_lib == 'qidenticon_x') | (identicon_lib == 'qidenticon_two_x') + opacity = int(not((identicon_lib == 'qidenticon_x') | (identicon_lib == 'qidenticon_two_x') | (identicon_lib == 'qidenticon_b') | (identicon_lib == 'qidenticon_two_b')))*255 penwidth = 0 - pixmap = qidenticon.render_identicon(int(hash, 16), 48, use_two_colors, transparent, penwidth) - idcon = QtGui.QIcon() - idcon.addPixmap(pixmap, QtGui.QIcon.Normal, QtGui.QIcon.Off) + image = qidenticon.render_identicon(int(hash, 16), size, use_two_colors, opacity, penwidth) + # filename = './images/identicons/'+hash+'.png' + # image.save(filename) + idcon = QIcon() + idcon.addPixmap(image, QtGui.QIcon.Normal, QtGui.QIcon.Off) return idcon elif identicon_lib == 'pydenticon': # print identicon_lib @@ -101,7 +107,7 @@ def identiconize(address): # GPLv3 is a copyleft license that would influence our licensing # Find the source here: http://boottunes.googlecode.com/svn-history/r302/trunk/src/pydenticon.py # note that it requires PIL to be installed: http://www.pythonware.com/products/pil/ - idcon_render = Pydenticon(addBMIfNotPresent(address)+identiconsuffix, size) + idcon_render = Pydenticon(addBMIfNotPresent(address)+identiconsuffix, size*3) rendering = idcon_render._render() data = rendering.convert("RGBA").tostring("raw", "RGBA") qim = QImage(data, size, size, QImage.Format_ARGB32) @@ -116,6 +122,7 @@ def identiconize(address): if identicon_lib & len(identicon_lib) > 0: print 'Error: couldn\'t find this identicon library: ', identicon_lib print 'Control for typos!' + # default to no identicons idcon = QtGui.QIcon() return idcon diff --git a/src/bitmessageqt/bitmessage_icons.qrc b/src/bitmessageqt/bitmessage_icons.qrc index bdd3fd07..68574630 100644 --- a/src/bitmessageqt/bitmessage_icons.qrc +++ b/src/bitmessageqt/bitmessage_icons.qrc @@ -1,5 +1,10 @@ + ../images/no_identicons.png + ../images/qidenticon_x.png + ../images/qidenticon.png + ../images/qidenticon_two.png + ../images/qidenticon_two_x.png ../images/can-icon-24px-yellow.png ../images/can-icon-24px-red.png ../images/can-icon-24px-green.png diff --git a/src/bitmessageqt/bitmessageui.py b/src/bitmessageqt/bitmessageui.py index c051c076..8f8b8951 100644 --- a/src/bitmessageqt/bitmessageui.py +++ b/src/bitmessageqt/bitmessageui.py @@ -2,7 +2,7 @@ # Form implementation generated from reading ui file 'bitmessageui.ui' # -# Created: Mon Aug 12 00:08:20 2013 +# Created: Wed Sep 18 17:38:54 2013 # by: PyQt4 UI code generator 4.10.2 # # WARNING! All changes made in this file will be lost! @@ -630,3 +630,13 @@ class Ui_MainWindow(object): self.actionJoinChan.setText(_translate("MainWindow", "Join / Create chan", None)) import bitmessage_icons_rc + +if __name__ == "__main__": + import sys + app = QtGui.QApplication(sys.argv) + MainWindow = QtGui.QMainWindow() + ui = Ui_MainWindow() + ui.setupUi(MainWindow) + MainWindow.show() + sys.exit(app.exec_()) + diff --git a/src/bitmessageqt/settings.py b/src/bitmessageqt/settings.py index 821df6cf..290c0cb6 100644 --- a/src/bitmessageqt/settings.py +++ b/src/bitmessageqt/settings.py @@ -2,7 +2,7 @@ # Form implementation generated from reading ui file 'settings.ui' # -# Created: Sun Aug 25 18:09:38 2013 +# Created: Wed Sep 18 17:38:55 2013 # by: PyQt4 UI code generator 4.10.2 # # WARNING! All changes made in this file will be lost! @@ -45,8 +45,6 @@ class Ui_settingsDialog(object): self.checkBoxMinimizeToTray.setChecked(True) self.checkBoxMinimizeToTray.setObjectName(_fromUtf8("checkBoxMinimizeToTray")) self.gridLayout_5.addWidget(self.checkBoxMinimizeToTray, 2, 0, 1, 1) - spacerItem = QtGui.QSpacerItem(20, 40, QtGui.QSizePolicy.Minimum, QtGui.QSizePolicy.Expanding) - self.gridLayout_5.addItem(spacerItem, 10, 0, 1, 1) self.checkBoxStartOnLogon = QtGui.QCheckBox(self.tabUserInterface) self.checkBoxStartOnLogon.setObjectName(_fromUtf8("checkBoxStartOnLogon")) self.gridLayout_5.addWidget(self.checkBoxStartOnLogon, 0, 0, 1, 1) @@ -82,7 +80,35 @@ class Ui_settingsDialog(object): self.languageComboBox.addItem(_fromUtf8("")) self.languageComboBox.addItem(_fromUtf8("")) self.horizontalLayout_2.addWidget(self.languageComboBox) - self.gridLayout_5.addWidget(self.groupBox, 10, 1, 1, 1) + self.gridLayout_5.addWidget(self.groupBox, 7, 1, 4, 1) + self.groupBox_3 = QtGui.QGroupBox(self.tabUserInterface) + self.groupBox_3.setObjectName(_fromUtf8("groupBox_3")) + self.comboBox = QtGui.QComboBox(self.groupBox_3) + self.comboBox.setGeometry(QtCore.QRect(20, 20, 251, 31)) + self.comboBox.setIconSize(QtCore.QSize(24, 24)) + self.comboBox.setObjectName(_fromUtf8("comboBox")) + icon = QtGui.QIcon() + icon.addPixmap(QtGui.QPixmap(_fromUtf8(":/newPrefix/images/no_identicons.png")), QtGui.QIcon.Normal, QtGui.QIcon.Off) + self.comboBox.addItem(icon, _fromUtf8("")) + icon1 = QtGui.QIcon() + icon1.addPixmap(QtGui.QPixmap(_fromUtf8(":/newPrefix/images/qidenticon.png")), QtGui.QIcon.Normal, QtGui.QIcon.Off) + self.comboBox.addItem(icon1, _fromUtf8("")) + icon2 = QtGui.QIcon() + icon2.addPixmap(QtGui.QPixmap(_fromUtf8(":/newPrefix/images/qidenticon_x.png")), QtGui.QIcon.Normal, QtGui.QIcon.Off) + self.comboBox.addItem(icon2, _fromUtf8("")) + icon3 = QtGui.QIcon() + icon3.addPixmap(QtGui.QPixmap(_fromUtf8(":/newPrefix/images/qidenticon_two.png")), QtGui.QIcon.Normal, QtGui.QIcon.Off) + self.comboBox.addItem(icon3, _fromUtf8("")) + icon4 = QtGui.QIcon() + icon4.addPixmap(QtGui.QPixmap(_fromUtf8(":/newPrefix/images/qidenticon_two_x.png")), QtGui.QIcon.Normal, QtGui.QIcon.Off) + self.comboBox.addItem(icon4, _fromUtf8("")) + self.checkBoxLoadAvatars = QtGui.QCheckBox(self.groupBox_3) + self.checkBoxLoadAvatars.setGeometry(QtCore.QRect(20, 50, 121, 18)) + self.checkBoxLoadAvatars.setObjectName(_fromUtf8("checkBoxLoadAvatars")) + self.lineEdit = QtGui.QLineEdit(self.groupBox_3) + self.lineEdit.setGeometry(QtCore.QRect(140, 50, 131, 16)) + self.lineEdit.setObjectName(_fromUtf8("lineEdit")) + self.gridLayout_5.addWidget(self.groupBox_3, 7, 0, 4, 1) self.tabWidgetSettings.addTab(self.tabUserInterface, _fromUtf8("")) self.tabNetworkSettings = QtGui.QWidget() self.tabNetworkSettings.setObjectName(_fromUtf8("tabNetworkSettings")) @@ -92,8 +118,8 @@ class Ui_settingsDialog(object): self.groupBox1.setObjectName(_fromUtf8("groupBox1")) self.gridLayout_3 = QtGui.QGridLayout(self.groupBox1) self.gridLayout_3.setObjectName(_fromUtf8("gridLayout_3")) - spacerItem1 = QtGui.QSpacerItem(125, 20, QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Minimum) - self.gridLayout_3.addItem(spacerItem1, 0, 0, 1, 1) + spacerItem = QtGui.QSpacerItem(125, 20, QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Minimum) + self.gridLayout_3.addItem(spacerItem, 0, 0, 1, 1) self.label = QtGui.QLabel(self.groupBox1) self.label.setObjectName(_fromUtf8("label")) self.gridLayout_3.addWidget(self.label, 0, 1, 1, 1) @@ -150,8 +176,8 @@ class Ui_settingsDialog(object): self.checkBoxSocksListen.setObjectName(_fromUtf8("checkBoxSocksListen")) self.gridLayout_2.addWidget(self.checkBoxSocksListen, 3, 1, 1, 4) self.gridLayout_4.addWidget(self.groupBox_2, 1, 0, 1, 1) - spacerItem2 = QtGui.QSpacerItem(20, 70, QtGui.QSizePolicy.Minimum, QtGui.QSizePolicy.Expanding) - self.gridLayout_4.addItem(spacerItem2, 2, 0, 1, 1) + spacerItem1 = QtGui.QSpacerItem(20, 70, QtGui.QSizePolicy.Minimum, QtGui.QSizePolicy.Expanding) + self.gridLayout_4.addItem(spacerItem1, 2, 0, 1, 1) self.tabWidgetSettings.addTab(self.tabNetworkSettings, _fromUtf8("")) self.tab = QtGui.QWidget() self.tab.setObjectName(_fromUtf8("tab")) @@ -161,8 +187,8 @@ class Ui_settingsDialog(object): self.label_8.setWordWrap(True) self.label_8.setObjectName(_fromUtf8("label_8")) self.gridLayout_6.addWidget(self.label_8, 0, 0, 1, 3) - spacerItem3 = QtGui.QSpacerItem(203, 20, QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Minimum) - self.gridLayout_6.addItem(spacerItem3, 1, 0, 1, 1) + spacerItem2 = QtGui.QSpacerItem(203, 20, QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Minimum) + self.gridLayout_6.addItem(spacerItem2, 1, 0, 1, 1) self.label_9 = QtGui.QLabel(self.tab) self.label_9.setAlignment(QtCore.Qt.AlignRight|QtCore.Qt.AlignTrailing|QtCore.Qt.AlignVCenter) self.label_9.setObjectName(_fromUtf8("label_9")) @@ -176,8 +202,8 @@ class Ui_settingsDialog(object): self.lineEditTotalDifficulty.setMaximumSize(QtCore.QSize(70, 16777215)) self.lineEditTotalDifficulty.setObjectName(_fromUtf8("lineEditTotalDifficulty")) self.gridLayout_6.addWidget(self.lineEditTotalDifficulty, 1, 2, 1, 1) - spacerItem4 = QtGui.QSpacerItem(203, 20, QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Minimum) - self.gridLayout_6.addItem(spacerItem4, 3, 0, 1, 1) + spacerItem3 = QtGui.QSpacerItem(203, 20, QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Minimum) + self.gridLayout_6.addItem(spacerItem3, 3, 0, 1, 1) self.label_11 = QtGui.QLabel(self.tab) self.label_11.setAlignment(QtCore.Qt.AlignRight|QtCore.Qt.AlignTrailing|QtCore.Qt.AlignVCenter) self.label_11.setObjectName(_fromUtf8("label_11")) @@ -208,8 +234,8 @@ class Ui_settingsDialog(object): self.label_15.setWordWrap(True) self.label_15.setObjectName(_fromUtf8("label_15")) self.gridLayout_7.addWidget(self.label_15, 0, 0, 1, 3) - spacerItem5 = QtGui.QSpacerItem(102, 20, QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Minimum) - self.gridLayout_7.addItem(spacerItem5, 1, 0, 1, 1) + spacerItem4 = QtGui.QSpacerItem(102, 20, QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Minimum) + self.gridLayout_7.addItem(spacerItem4, 1, 0, 1, 1) self.label_13 = QtGui.QLabel(self.tab_2) self.label_13.setLayoutDirection(QtCore.Qt.LeftToRight) self.label_13.setAlignment(QtCore.Qt.AlignRight|QtCore.Qt.AlignTrailing|QtCore.Qt.AlignVCenter) @@ -224,8 +250,8 @@ class Ui_settingsDialog(object): self.lineEditMaxAcceptableTotalDifficulty.setMaximumSize(QtCore.QSize(70, 16777215)) self.lineEditMaxAcceptableTotalDifficulty.setObjectName(_fromUtf8("lineEditMaxAcceptableTotalDifficulty")) self.gridLayout_7.addWidget(self.lineEditMaxAcceptableTotalDifficulty, 1, 2, 1, 1) - spacerItem6 = QtGui.QSpacerItem(102, 20, QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Minimum) - self.gridLayout_7.addItem(spacerItem6, 2, 0, 1, 1) + spacerItem5 = QtGui.QSpacerItem(102, 20, QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Minimum) + self.gridLayout_7.addItem(spacerItem5, 2, 0, 1, 1) self.label_14 = QtGui.QLabel(self.tab_2) self.label_14.setAlignment(QtCore.Qt.AlignRight|QtCore.Qt.AlignTrailing|QtCore.Qt.AlignVCenter) self.label_14.setObjectName(_fromUtf8("label_14")) @@ -239,15 +265,15 @@ class Ui_settingsDialog(object): self.lineEditMaxAcceptableSmallMessageDifficulty.setMaximumSize(QtCore.QSize(70, 16777215)) self.lineEditMaxAcceptableSmallMessageDifficulty.setObjectName(_fromUtf8("lineEditMaxAcceptableSmallMessageDifficulty")) self.gridLayout_7.addWidget(self.lineEditMaxAcceptableSmallMessageDifficulty, 2, 2, 1, 1) - spacerItem7 = QtGui.QSpacerItem(20, 147, QtGui.QSizePolicy.Minimum, QtGui.QSizePolicy.Expanding) - self.gridLayout_7.addItem(spacerItem7, 3, 1, 1, 1) + spacerItem6 = QtGui.QSpacerItem(20, 147, QtGui.QSizePolicy.Minimum, QtGui.QSizePolicy.Expanding) + self.gridLayout_7.addItem(spacerItem6, 3, 1, 1, 1) self.tabWidgetSettings.addTab(self.tab_2, _fromUtf8("")) self.tabNamecoin = QtGui.QWidget() self.tabNamecoin.setObjectName(_fromUtf8("tabNamecoin")) self.gridLayout_8 = QtGui.QGridLayout(self.tabNamecoin) self.gridLayout_8.setObjectName(_fromUtf8("gridLayout_8")) - spacerItem8 = QtGui.QSpacerItem(40, 20, QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Minimum) - self.gridLayout_8.addItem(spacerItem8, 2, 0, 1, 1) + spacerItem7 = QtGui.QSpacerItem(40, 20, QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Minimum) + self.gridLayout_8.addItem(spacerItem7, 2, 0, 1, 1) self.label_16 = QtGui.QLabel(self.tabNamecoin) self.label_16.setWordWrap(True) self.label_16.setObjectName(_fromUtf8("label_16")) @@ -259,10 +285,10 @@ class Ui_settingsDialog(object): self.lineEditNamecoinHost = QtGui.QLineEdit(self.tabNamecoin) self.lineEditNamecoinHost.setObjectName(_fromUtf8("lineEditNamecoinHost")) self.gridLayout_8.addWidget(self.lineEditNamecoinHost, 2, 2, 1, 1) + spacerItem8 = QtGui.QSpacerItem(40, 20, QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Minimum) + self.gridLayout_8.addItem(spacerItem8, 3, 0, 1, 1) spacerItem9 = QtGui.QSpacerItem(40, 20, QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Minimum) - self.gridLayout_8.addItem(spacerItem9, 3, 0, 1, 1) - spacerItem10 = QtGui.QSpacerItem(40, 20, QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Minimum) - self.gridLayout_8.addItem(spacerItem10, 4, 0, 1, 1) + self.gridLayout_8.addItem(spacerItem9, 4, 0, 1, 1) self.label_18 = QtGui.QLabel(self.tabNamecoin) self.label_18.setEnabled(True) self.label_18.setAlignment(QtCore.Qt.AlignRight|QtCore.Qt.AlignTrailing|QtCore.Qt.AlignVCenter) @@ -271,8 +297,8 @@ class Ui_settingsDialog(object): self.lineEditNamecoinPort = QtGui.QLineEdit(self.tabNamecoin) self.lineEditNamecoinPort.setObjectName(_fromUtf8("lineEditNamecoinPort")) self.gridLayout_8.addWidget(self.lineEditNamecoinPort, 3, 2, 1, 1) - spacerItem11 = QtGui.QSpacerItem(20, 40, QtGui.QSizePolicy.Minimum, QtGui.QSizePolicy.Expanding) - self.gridLayout_8.addItem(spacerItem11, 8, 1, 1, 1) + spacerItem10 = QtGui.QSpacerItem(20, 40, QtGui.QSizePolicy.Minimum, QtGui.QSizePolicy.Expanding) + self.gridLayout_8.addItem(spacerItem10, 8, 1, 1, 1) self.labelNamecoinUser = QtGui.QLabel(self.tabNamecoin) self.labelNamecoinUser.setAlignment(QtCore.Qt.AlignRight|QtCore.Qt.AlignTrailing|QtCore.Qt.AlignVCenter) self.labelNamecoinUser.setObjectName(_fromUtf8("labelNamecoinUser")) @@ -280,8 +306,8 @@ class Ui_settingsDialog(object): self.lineEditNamecoinUser = QtGui.QLineEdit(self.tabNamecoin) self.lineEditNamecoinUser.setObjectName(_fromUtf8("lineEditNamecoinUser")) self.gridLayout_8.addWidget(self.lineEditNamecoinUser, 4, 2, 1, 1) - spacerItem12 = QtGui.QSpacerItem(40, 20, QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Minimum) - self.gridLayout_8.addItem(spacerItem12, 5, 0, 1, 1) + spacerItem11 = QtGui.QSpacerItem(40, 20, QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Minimum) + self.gridLayout_8.addItem(spacerItem11, 5, 0, 1, 1) self.labelNamecoinPassword = QtGui.QLabel(self.tabNamecoin) self.labelNamecoinPassword.setAlignment(QtCore.Qt.AlignRight|QtCore.Qt.AlignTrailing|QtCore.Qt.AlignVCenter) self.labelNamecoinPassword.setObjectName(_fromUtf8("labelNamecoinPassword")) @@ -353,6 +379,14 @@ class Ui_settingsDialog(object): self.languageComboBox.setItemText(6, _translate("settingsDialog", "Russian", "ru")) self.languageComboBox.setItemText(7, _translate("settingsDialog", "Pirate English", "en_pirate")) self.languageComboBox.setItemText(8, _translate("settingsDialog", "Other (set in keys.dat)", "other")) + self.groupBox_3.setTitle(_translate("settingsDialog", "Identicons (with example image)", None)) + self.comboBox.setItemText(0, _translate("settingsDialog", "no identicons", None)) + self.comboBox.setItemText(1, _translate("settingsDialog", "qidenticon", None)) + self.comboBox.setItemText(2, _translate("settingsDialog", "qidenticon_x", None)) + self.comboBox.setItemText(3, _translate("settingsDialog", "qidenticon_two", None)) + self.comboBox.setItemText(4, _translate("settingsDialog", "qidenticon_two_x", None)) + self.checkBoxLoadAvatars.setText(_translate("settingsDialog", "Load avatar images", None)) + self.lineEdit.setToolTip(_translate("settingsDialog", "

The content of this text field will be appended to the BM-address before creating the hash for the identicons. By default it is filled with a random string to make the identicons in your client unique, otherwise the identicon could be an attack vector if an adversary creates an address resulting in a similar identicon. If you keep this string (or any other random or non-random string) you will be able to keep the same identicons.

", None)) self.tabWidgetSettings.setTabText(self.tabWidgetSettings.indexOf(self.tabUserInterface), _translate("settingsDialog", "User Interface", None)) self.groupBox1.setTitle(_translate("settingsDialog", "Listening port", None)) self.label.setText(_translate("settingsDialog", "Listen for connections on port:", None)) @@ -389,6 +423,7 @@ class Ui_settingsDialog(object): self.radioButtonNamecoinNmcontrol.setText(_translate("settingsDialog", "NMControl", None)) self.tabWidgetSettings.setTabText(self.tabWidgetSettings.indexOf(self.tabNamecoin), _translate("settingsDialog", "Namecoin integration", None)) +import bitmessage_icons_rc if __name__ == "__main__": import sys diff --git a/src/bitmessageqt/settings.ui b/src/bitmessageqt/settings.ui index eec38d8d..a5e6e4e6 100644 --- a/src/bitmessageqt/settings.ui +++ b/src/bitmessageqt/settings.ui @@ -47,19 +47,6 @@ - - - - Qt::Vertical - - - - 20 - 40 - - - - @@ -105,7 +92,7 @@ - + Interface Language @@ -163,6 +150,100 @@ + + + + Identicons (with example image) + + + + + 20 + 20 + 251 + 31 + + + + + 24 + 24 + + + + + no identicons + + + + :/newPrefix/images/no_identicons.png:/newPrefix/images/no_identicons.png + + + + + qidenticon + + + + :/newPrefix/images/qidenticon.png:/newPrefix/images/qidenticon.png + + + + + qidenticon_x + + + + :/newPrefix/images/qidenticon_x.png:/newPrefix/images/qidenticon_x.png + + + + + qidenticon_two + + + + :/newPrefix/images/qidenticon_two.png:/newPrefix/images/qidenticon_two.png + + + + + qidenticon_two_x + + + + :/newPrefix/images/qidenticon_two_x.png:/newPrefix/images/qidenticon_two_x.png + + + + + + + 20 + 50 + 121 + 18 + + + + Load avatar images + + + + + + 140 + 50 + 131 + 16 + + + + <html><head/><body><p>The content of this text field will be appended to the BM-address before creating the hash for the identicons. By default it is filled with a random string to make the identicons in your client unique, otherwise the identicon could be an attack vector if an adversary creates an address resulting in a similar identicon. If you keep this string (or any other random or non-random string) you will be able to keep the same identicons.</p></body></html> + + + + @@ -756,7 +837,9 @@ checkBoxSocksListen buttonBox - + + + buttonBox diff --git a/src/qidenticon.py b/src/qidenticon.py index ffc25eea..cc3af6b3 100644 --- a/src/qidenticon.py +++ b/src/qidenticon.py @@ -67,12 +67,12 @@ class IdenticonRendererBase(object): code = int(code) self.code = code - def render(self, size, twoColor, transparent, penwidth): + def render(self, size, twoColor, opacity, penwidth): """ - render identicon to QPixmap + render identicon to QPicture @param size identicon patchsize. (image size is 3 * [size]) - @return QPixmap + @return QPicture """ # decode the code @@ -82,7 +82,7 @@ class IdenticonRendererBase(object): image = QPixmap(QSize(size * 3 +penwidth, size * 3 +penwidth)) # fill background - backColor = QtGui.QColor(255,255,255,(not transparent) * 255) + backColor = QtGui.QColor(255,255,255,opacity) image.fill(backColor) kwds = { @@ -133,7 +133,7 @@ class IdenticonRendererBase(object): nopen = QtGui.QPen(foreColor, Qt.NoPen) foreBrush = QtGui.QBrush(foreColor, Qt.SolidPattern) if penwidth > 0: - pen_color = QtGui.QColor(223, 223, 223) + pen_color = QtGui.QColor(255, 255, 255) pen = QtGui.QPen(pen_color, Qt.SolidPattern) pen.setWidth(penwidth) @@ -147,8 +147,8 @@ class IdenticonRendererBase(object): if invert: # subtract the actual polygon from a rectangle to invert it - rect_polygon = QPolygonF(rect) - polygon = rect_polygon.subtracted(polygon) + poly_rect = QPolygonF(rect) + polygon = poly_rect.subtracted(polygon) painter.setBrush(foreBrush) if penwidth > 0: # draw the borders @@ -157,7 +157,7 @@ class IdenticonRendererBase(object): # draw the fill painter.setPen(nopen) painter.drawPolygon(polygon, Qt.WindingFill) - + painter.end() return image @@ -173,22 +173,39 @@ class DonRenderer(IdenticonRendererBase): """ PATH_SET = [ - [(0, 0), (4, 0), (4, 4), (0, 4)], # 0 + #[0] full square: + [(0, 0), (4, 0), (4, 4), (0, 4)], + #[1] right-angled triangle pointing top-left: [(0, 0), (4, 0), (0, 4)], + #[2] upwardy triangle: [(2, 0), (4, 4), (0, 4)], + #[3] left half of square, standing rectangle: [(0, 0), (2, 0), (2, 4), (0, 4)], - [(2, 0), (4, 2), (2, 4), (0, 2)], # 4 + #[4] square standing on diagonale: + [(2, 0), (4, 2), (2, 4), (0, 2)], + #[5] kite pointing topleft: [(0, 0), (4, 2), (4, 4), (2, 4)], + #[6] Sierpinski triangle, fractal triangles: [(2, 0), (4, 4), (2, 4), (3, 2), (1, 2), (2, 4), (0, 4)], + #[7] sharp angled lefttop pointing triangle: [(0, 0), (4, 2), (2, 4)], - [(1, 1), (3, 1), (3, 3), (1, 3)], # 8 + #[8] small centered square: + [(1, 1), (3, 1), (3, 3), (1, 3)], + #[9] two small triangles: [(2, 0), (4, 0), (0, 4), (0, 2), (2, 2)], + #[10] small topleft square: [(0, 0), (2, 0), (2, 2), (0, 2)], + #[11] downpointing right-angled triangle on bottom: [(0, 2), (4, 2), (2, 4)], + #[12] uppointing right-angled triangle on bottom: [(2, 2), (4, 4), (0, 4)], + #[13] small rightbottom pointing right-angled triangle on topleft: [(2, 0), (2, 2), (0, 2)], + #[14] small lefttop pointing right-angled triangle on topleft: [(0, 0), (2, 0), (0, 2)], - []] # 15 + #[15] empty: + []] + # get the [0] full square, [4] square standing on diagonale, [8] small centered square, or [15] empty tile: MIDDLE_PATCH_SET = [0, 4, 8, 15] # modify path set @@ -232,7 +249,7 @@ class DonRenderer(IdenticonRendererBase): foreColor, secondColor, swap_cross -def render_identicon(code, size, twoColor=False, transparent=False, penwidth=0, renderer=None): +def render_identicon(code, size, twoColor=False, opacity=255, penwidth=0, renderer=None): if not renderer: renderer = DonRenderer - return renderer(code).render(size, twoColor, transparent, penwidth) \ No newline at end of file + return renderer(code).render(size, twoColor, opacity, penwidth) \ No newline at end of file From 31affd438f132177b98e58498c01078898a93d95 Mon Sep 17 00:00:00 2001 From: sendiulo Date: Thu, 19 Sep 2013 21:28:22 +0200 Subject: [PATCH 10/48] avatarize --- src/bitmessageqt/__init__.py | 64 +++++++++++++++++++++++--------- src/images/can-icon-24px_2.png | Bin 0 -> 1852 bytes src/images/no_identicons.png | Bin 0 -> 197 bytes src/images/qidenticon.png | Bin 0 -> 1649 bytes src/images/qidenticon_two.png | Bin 0 -> 1737 bytes src/images/qidenticon_two_x.png | Bin 0 -> 1826 bytes src/images/qidenticon_x.png | Bin 0 -> 1763 bytes 7 files changed, 47 insertions(+), 17 deletions(-) create mode 100644 src/images/can-icon-24px_2.png create mode 100644 src/images/no_identicons.png create mode 100644 src/images/qidenticon.png create mode 100644 src/images/qidenticon_two.png create mode 100644 src/images/qidenticon_two_x.png create mode 100644 src/images/qidenticon_x.png diff --git a/src/bitmessageqt/__init__.py b/src/bitmessageqt/__init__.py index 2a5eb10e..cecb6401 100644 --- a/src/bitmessageqt/__init__.py +++ b/src/bitmessageqt/__init__.py @@ -58,14 +58,6 @@ def _translate(context, text): def identiconize(address): size = 48 - str_broadcast_subscribers = '[Broadcast subscribers]' - if address == str_broadcast_subscribers: - idcon = QtGui.QIcon(":/newPrefix/images/can-icon-24px.png") - return idcon - try: - identiconsuffix = shared.config.get('bitmessagesettings', 'identiconsuffix') - except: - identiconsuffix = '' # here you could put "@bitmessge.ch" or "@bm.addr" to make it compatible with other identicon generators # instead, you could also use a pseudo-password to salt the generation of the identicons # Attacks where someone creates an address to mimic someone else's identicon should be impossible then @@ -81,13 +73,18 @@ def identiconize(address): # default to no identicons identicon_lib = False + try: + identiconsuffix = shared.config.get('bitmessagesettings', 'identiconsuffix') + except: + identiconsuffix = '' + if (identicon_lib[:len('qidenticon')] == 'qidenticon'): # print identicon_lib # originally by: # :Author:Shin Adachi # Licesensed under FreeBSD License. - # stripped from PIL and uses QT instead - import qidenticon + # stripped from PIL and uses QT instead (by sendiulo, same license) + import qidenticon import hashlib hash = hashlib.md5(addBMIfNotPresent(address)+identiconsuffix).hexdigest() use_two_colors = (identicon_lib[:len('qidenticon_two')] == 'qidenticon_two') @@ -125,6 +122,36 @@ def identiconize(address): # default to no identicons idcon = QtGui.QIcon() return idcon + +def avatarize(address, fallBackToIdenticon = False): + str_broadcast_subscribers = '[Broadcast subscribers]' + # don't add the suffix for [Broadcast subscribers] + import hashlib + hash = hashlib.md5(addBMIfNotPresent(address)).hexdigest() + if address == str_broadcast_subscribers: + hash = address + print address, ' => ', hash + idcon = QtGui.QIcon() + # http://pyqt.sourceforge.net/Docs/PyQt4/qimagereader.html#supportedImageFormats + # print QImageReader.supportedImageFormats () + # QImageReader.supportedImageFormats () + extensions = ['PNG', 'GIF', 'JPG', 'JPEG', 'SVG', 'BMP', 'MNG', 'PBM', 'PGM', 'PPM', 'TIFF', 'XBM', 'XPM', 'TGA'] + for ext in extensions: + upper = shared.appdata + 'avatars/' + hash + '.' + ext.upper() + lower = shared.appdata + 'avatars/' + hash + '.' + ext.lower() + if os.path.isfile(lower): + print 'found avatar of ', address + idcon.addFile(upper) + return idcon + elif os.path.isfile(upper): + print 'found avatar of ', address + idcon.addFile(upper) + return idcon + if fallBackToIdenticon: + return identiconize(address) + else: + return idcon + #identiconize(address) class MyForm(QtGui.QMainWindow): @@ -387,6 +414,7 @@ class MyForm(QtGui.QMainWindow): if not isEnabled: newItem.setTextColor(QtGui.QColor(128, 128, 128)) self.ui.tableWidgetYourIdentities.insertRow(0) + newItem.setIcon(avatarize(addressInKeysFile)) self.ui.tableWidgetYourIdentities.setItem(0, 0, newItem) newItem = QtGui.QTableWidgetItem(addressInKeysFile) newItem.setFlags( @@ -427,6 +455,7 @@ class MyForm(QtGui.QMainWindow): self.ui.tableWidgetAddressBook.insertRow(0) # address book item newItem = QtGui.QTableWidgetItem(unicode(label, 'utf-8')) + newItem.setIcon(avatarize(addressInKeysFile)) self.ui.tableWidgetAddressBook.setItem(0, 0, newItem) newItem = QtGui.QTableWidgetItem(address) newItem.setIcon(identiconize(address)) @@ -690,7 +719,7 @@ class MyForm(QtGui.QMainWindow): else: newItem = QtGui.QTableWidgetItem(unicode(toLabel, 'utf-8')) newItem.setToolTip(unicode(toLabel, 'utf-8')) - newItem.setIcon(identiconize(toAddress)) + newItem.setIcon(avatarize(toAddress, True)) newItem.setData(Qt.UserRole, str(toAddress)) newItem.setFlags( QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled) @@ -702,7 +731,7 @@ class MyForm(QtGui.QMainWindow): else: newItem = QtGui.QTableWidgetItem(unicode(fromLabel, 'utf-8')) newItem.setToolTip(unicode(fromLabel, 'utf-8')) - newItem.setIcon(identiconize(fromAddress)) + newItem.setIcon(avatarize(fromAddress, True)) newItem.setData(Qt.UserRole, str(fromAddress)) newItem.setFlags( QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled) @@ -847,7 +876,7 @@ class MyForm(QtGui.QMainWindow): newItem.setTextColor(QtGui.QColor(137, 04, 177)) if shared.safeConfigGetBoolean(str(toAddress), 'chan'): newItem.setTextColor(QtGui.QColor(216, 119, 0)) # orange - newItem.setIcon(identiconize(toAddress)) + newItem.setIcon(avatarize(toAddress, True)) self.ui.tableWidgetInbox.setItem(0, 0, newItem) # from if fromLabel == '': @@ -862,7 +891,7 @@ class MyForm(QtGui.QMainWindow): if not read: newItem.setFont(font) newItem.setData(Qt.UserRole, str(fromAddress)) - newItem.setIcon(identiconize(fromAddress)) + newItem.setIcon(avatarize(fromAddress, True)) self.ui.tableWidgetInbox.setItem(0, 1, newItem) # subject newItem = QtGui.QTableWidgetItem(unicode(subject, 'utf-8')) @@ -1565,7 +1594,7 @@ class MyForm(QtGui.QMainWindow): self.ui.tableWidgetInbox.item( i, 0).setText(unicode(toLabel, 'utf-8')) self.ui.tableWidgetInbox.item( - i, 0).setIcon(identiconize(toAddress)) + i, 0).setIcon(avatarize(toAddress, True)) # Set the color according to whether it is the address of a mailing # list or not. if shared.safeConfigGetBoolean(toAddress, 'mailinglist'): @@ -1587,7 +1616,7 @@ class MyForm(QtGui.QMainWindow): self.ui.tableWidgetSent.item( i, 1).setText(unicode(fromLabel, 'utf-8')) self.ui.tableWidgetSent.item( - i, 0).setIcon(identiconize(fromAddress)) + i, 0).setIcon(avatarize(fromAddress, True)) def rerenderSentToLabels(self): for i in range(self.ui.tableWidgetSent.rowCount()): @@ -1622,13 +1651,14 @@ class MyForm(QtGui.QMainWindow): newItem = QtGui.QTableWidgetItem(unicode(label, 'utf-8')) if not enabled: newItem.setTextColor(QtGui.QColor(128, 128, 128)) + newItem.setIcon(avatarize(address)) self.ui.tableWidgetSubscriptions.setItem(0, 0, newItem) newItem = QtGui.QTableWidgetItem(address) - newItem.setIcon(identiconize(address)) newItem.setFlags( QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled) if not enabled: newItem.setTextColor(QtGui.QColor(128, 128, 128)) + newItem.setIcon(identiconize(address)) self.ui.tableWidgetSubscriptions.setItem(0, 1, newItem) def click_pushButtonSend(self): diff --git a/src/images/can-icon-24px_2.png b/src/images/can-icon-24px_2.png new file mode 100644 index 0000000000000000000000000000000000000000..30f7313eb378b69c4e2f6cb66264c2ae678059e1 GIT binary patch literal 1852 zcmV-C2gCS@P)M^!E?E!Z`=4*Xy`ht_>fhjLl+eV_hy>=-Ek(A(EP8wh@6>5>i{Jah=UrfpOJpyyEE@4x%__we9`O^8Gz zY+YB^0D>Su5CjlH;N;0u?*~Bu&N&bS0ppzGa8EB4rY4DVPDBKSF-HCU16P-HEJ7j{ zNB_V8#>OVLvurL0*LBHtT@Xo}Jb4<4WQ?)(U)V^qv$OC6A8)<&J1DB6+N!HAqgS40M1-^F1`!DUX8C&gnRGe@ zugOKk3q#X23}v$zA1@%4ZpGc13}Zxu2iAXy3X>BMLSomhm+{n7Pk?idful#^*Ezyr z?^5Ynsk5uAtC7-^+n#tfol2v%r3J6O`Z`vx?m{NB3ZCb|_3ZsV_dC<+bR%^l!h!ug z@VU>%#>bmZoH+5fD_6!g$w1(jJD&Yfx-GS5)rys1j5Ug#pWXb-IOmw2o?hS6(|2Sf zpMOr0oWEy8bh}FcG{zVdMbZCv^7OGpGT!ZY?yDjYR4SFe34j4u5I6t=RaNovCkHF@ zoiaipD2jq)GP(ESrQv7p6jU1RJ^w~D8qrNF1lzV@nHGW|z_EWH$MA4|HvlM%F)+q3 zSF1lXI(qr%k!S>tSt5A%%qNy7fY#@OJ=EhAw1( z_N}dtZ{57*k%%G? zyxTA|2q7B+k`g<9wZry251!{C8jT8 z-p;x=cW;j;6M^e_Sar{;s;=ulO|_=rc^-)9w)un*3;rOYd80A9^_8lsC>CeFcKY<0 z4{lZ}Ry-a%wttgbW>)Wx=%_7={5wQBW?I>Bfy4Z=_PG=NJe~Q9lP@w~J_0!8!Mi z9zFhP-S^kT<8fHF3CFQv7zRw!gk#&#H626*-}f;&HHG}>D5{mY;+HpXy5F+Q%Xe-M z0U*X0G)+UPbnTIkKRHlVHEm6MdpjbL2t3b)X_;^w2d-^HS2Yk3s?{on^CK7@$wLGZ zp64ZohO$2d01W`9rl;?qQ0VgL*oRY7S08R~Z--+$Fw77_rVcY?z%ng_LLsP%0zrgw zxr|)yA}WL9CMO3t=b?@zi(yy>Of!T~C+VOB*94LhM2&|}VG20s7@sU4HnuD?>4oOphsFb>PI zZUf%`%U?g-yLaC=7-Oi_YVf%apL0|y71Zl>Y~1)DcJ6!;YuDTZB0_HPA}(DTUI=X2 zCW^)4PlABI9SVie+1UxpvTpCezI`9Hzx&R+{}~^jXqkryAs~c+loA9&&G%88o5S~i zunk+bJl6RAN16Z-N~LQLgR%WmN-SO4fpAk3q?Bk*BpZN(hk5|U(0jP|^`65=ej%iQ zs;ZDuEGZ;^06r_>FlqtwW!jai!%a;Br5)J=X qQ5iHQ#>b literal 0 HcmV?d00001 diff --git a/src/images/no_identicons.png b/src/images/no_identicons.png new file mode 100644 index 0000000000000000000000000000000000000000..513f1d10ac78407b11c593756bd08ab3ff3cca03 GIT binary patch literal 197 zcmeAS@N?(olHy`uVBq!ia0vp^6F``Q4M;wBd$a>cu@pObhHwBu4M$1`kk47*5n0T@ zz;^_M8K-LVNdpDbJzX3_D(1XBXvhfU9a`|!|7|Ac0w9yB-~pSOLn6$5Jhp02g!1>on(F9 zPO=;i=>cgh3!!g)UX}G`-LXN`)#W1#xO~0;EX#7ce=ZpL{`l2yLL8{#TsB}^pjxKg zghvp{vKQpCu4_~sg!XL#!KPb~s0i1yPat61+5m!0KF1Be-9Inux+ed&e}cVTby377 zUxW;fgki^TB4B$F+cF!tRp8IC{71ttAVe6O%!XIizhKyLi3r%7*;cb5d)Xg`Rem%2 zzIp_(@oj8J!5a)at_cB~QQMjt9M<{Cu+{|~7pEI0Y_uCVSm92?jx9jI25wtl!=l2z z<^SB&8jwmMbe$-K#UU^(jJj=MU2gYJ^*%J%fMX>Sz{a;z1zWPsiaSZBl@c2kMhsi! zC8roRsud+mmj*_-GhxNnadOOxO`uOO8Wx5eTX$)gu#qqDtX8MMIF8sP*hpGMh)wta zDP&lfZfs1wxO=gDs4~UL0b@NaZoUM(mX7N&?ztTdI_)W@0&z+|pHlh{;x zaR!_EHTafz@b?(`Ukh2UW9p;WV={@y5<3<_8<38?K#oZ+HNfP9P$`wM?3xX)0w!O< z+x=4u8_v%T$ta&C=3{gWurHMzw04)MxSJBN4cw|Q(1wMvG6vX}`X1SE(t&U;KxmsG ztHPKuEDY=zU|*WLVB2Xuf@FI-Y%^+A7?XyD!5IVWOKYENzHo+;Y){5E2UdkKZ&;Yf zHhfMUy}xH@3RAK@;LRJ1E5jBGR_*EQSe=8vfW6q+vFa=y4713oyT|wcyn-Ovi&z!L zs$r%%jr8@QA23xUdqJzhSTf89r@>x6Iz>S24tpQ8#)EE{Gfw0En_vJWXpscb zFt40QdiK?8b-V`AFtm)DL3np0*+f!=fe)t8-i$gJ8eooTjfa*u%LpX*9BAJb$DWlGTxN#7^x&)soD~?M5IGpveAU6~};8Br|-=ELvpv zNj$p9F=lzmj6L=NHe%$29uDRhwy2gS$Bb;Ji2JYE{0IDw`EpbBvi~UU++I8g`V?7 zWm0}qdQH62B7AN8LbL}$eDO9yI9_c_!k%E}uQR>CkHpikM`T_YJ-CEX!+hukZdI69 zH%!2)D1%*m$S~7-8L}$OD;g$mRg{sA-fx&iy^LBF=2Z++w<=0^ckeXJkX}%n#^c;D zWLk0)N%8gCIr)31l{dqZ$0}HC9j$6UhGA~`QnB~jT8_MvtceH1n$7^xY-?>yn_*C! zx(w)I*EXw$kz7}S*w)mB2E!1xZDG*DmTi^{V;!x~wXJ^DwT9{HCD#4%Z9_LqWeiZo zw#qeD8K$C_dN&6-W=8=vOr4fWYPv`nm}Eyo8w1>S$WXlp0f&8K!dal))z4 z9ujOx$r8W@SED{I5Vc>iJsh@CjwPav;w%8YXBjKY5q4}?Uw1&LOENgWZ&BG6-<00E zM9dq3lR?0?Wu(YYhBYk+XZtJT!?q{GDigD!7`wJT49kB< z_!3w$2-vpNAh;GkinBdTv1QwzVaX3Ej#g*|0o#@{BG=^KaM~>#eA_PTx`zL&-d2RU vH(S(8u_vs_hogT`N00000NkvXXu0mjfXoLD% literal 0 HcmV?d00001 diff --git a/src/images/qidenticon_two.png b/src/images/qidenticon_two.png new file mode 100644 index 0000000000000000000000000000000000000000..351806593fabc7add7f1f23038216bf4dc788d2c GIT binary patch literal 1737 zcmV;)1~&PLP)J9^VV6h_B`lG=Xy>B#2$+|J|26J#qQVNFCm1({V`zh zc=u*~2y>oK57vk9!PEQK{q(DVp>3QUKy9{+fC3qDD3*42-wl!7ZzlD#&wr?8W0Rd%g zI)!DBhGMTVb@21pbfmX3RU51Vn1#Fxaer=4u~C`t-lGz$zdog#=N#TujkW) zd2K{b*E6{w>~Mc1K8%UBMK^{ol0vUH%!S3MX-oQ0*2bl3aq5)mG#{L7T!V zE?8I$ySD0i7t+SAYHhAog)v4p!J>_2t{Jr{zc*GYET&o;dlp1(a;i2aXG@qwWD`Z& zL>Hb4ZHAvX4GN1%YEye_m#Ixb)%KG(Axt!~iA8NDAD%X}f$ZZL7Zx*VGvA(=Y134d zTqEXti4{Ze0`f!-|FwPp^TYZ;E=UX02VwX2@$OAO^qe-bUJ`XyvKYfXK=T*mZGhIW zYm~Lws>)n?a)s$fHld&mys8-J!eWH9fvC!VXICK1FtQ0oZ6K;jX{N9kqBb;DMIW5X zgds#WQK1b@RRv8J7L%zBR#oY~M6oca$R;+l!K$jG`NCpm+VE9XZ2Q%0O(C2!x&gw@ zmK?4M3oTSNQTM#B6b87GHKdAyhr(J^RlTg#3qy#kMO8#vEv#u(ly_|;VJ49^t%^=d zh51lL{7^&{rW2VDRV<(v=1diR&w)XhP-MCY$uXB-XrIFiDnsxN0?(mSR%;bmu`tID=qOkezp#p9!6uQF33ELA zbaeb_T)N1yX8p(tggKsfI%?*WE)M2cwsK^-XH9XPIn*K2PjhPw#4~?eMC}zQYYpRC zf%-~DCoQboQh@($eb<&mC$faF?nwcut*I_Ai9%#s!nz>^WdC`kD@-CC*{ZPaMgfge z1iHv1qLD2N>sA!dJI$@@Od=QAg0SvG0qs+@yVN8?k+`Mv%B73C>?DkleF}ZXRp4^A`}z4d%S1C? zl=Su63)fI|7_S4#^7q=K*@EKv^zih~IU0SCej@2I5-;yh?_VJ<((EiR@Rhh1c1A%J zJu<2=A9;aS6?5x_>8m0}MiSO^UJX?hbBl$UtD;0!FRaD9%vI63mBP?eRYz7Stf9Qn zR1s0Sihvc!w(6erHNx=onmj$;y*ayjfwwhmF)6P9tA?G5m8@78Y+gwpu-c07P*{-z zG;O7kl?g-3t9stiwB^%kVJveEqOB;h0%0I|vCl7vwoFW(3V`u62cS?F}czzYuk7rHEkPM7FM)}rl2izaA|GZ zk!=Z+JErPWP=vHq3}{uGc473B%LA)6;vhoWHX>UU#(oeis$z)R7&FzZO`|Y&RWTLX zgcd?xZEKM&3*&lJQB^US+7#YuO4lY;n3}4X4Q+Po@C&wZ8r=<{QJ@oh2E@7fS_%JGRYT( z6%7Pi)fzr(yC^Jk2!+p8(jaIXDV_6kVLA6~uBtWKt?gXc_I;E;0&9YxZ6rs*K6z6& zRjtuhZ6Ad#-pFvb!U_a!BWp(Xi$8N;)f)J@dfjH{QCeYRmtsp59w?I_&T&v}`+F$JD<^AbKDWnwI16v9t`u>6Q;whFEZS(Yb z{WvcOgY8;4q}i(5ra+g|^=_e+0%Vc)B#DWHERsgjizQGFl1MF5Ad18-&R&gn1>#Df z6ityvBtQ@eVQi_EKsk~kQ5Ic8BuQ~f^(YD?kwPh)B5@X3LL`L5$$?3r97>Ti=5#3U zsU=QL-&YAFokA%iA|WhKjI;qUadKc2D5pgvq$i{vkr2eGeZy7@q?tk~dmRK4Zg{462{92+&KmPpGZpsi%0-<7U;q&?N?S`6~ zts&9;5r#4r%0ZYJE`i*(Ud)bxwm=jKsV#f-3{koSLQSC*1d*(WGs1KUl%pY%MR9r` zkh%r3kU}XEB3Tq?l<5;FheIUm;*3ns3<+c{g;FR)vMvq+_yo#X6KPg)D3+#3mNF36$VRcYJfejs&RLnM%k^9jdv3yVM~DdghtGi3s~ zIQMW(_Oc12kwPwxJX0i)i?a{sQqu!x=l}k0f8UdJet-WJLO8Qs-_)+oS>al^jkV>x zKKsT(U)3(d{U+wTKC3`3j~-G|ifnCmk;+(2AvS?r8a*Us6gynOg(_n)g;)e~S@e*W zQ1o~g7psi56e1VMCDB7eKJf#lT(B~hQixO_mqQON=~RxmbJ5CJNg*j1#$`WK#)uQpm7(l41y?I3<>1L*8@c=^#lsR zMXUiun4E5byy$u$$pn!=A-Iq=Ac+&{+Lgh*i@>piZ|iY}m||z@(LnkHXdVM`k5zmu zR0io&qe&o_XI?}f<6R_uP*SK`AeUxdRF*PaD18u8s7fH0WnNVOox{b_M>~az0=Xpf z0`V`cE|@-=DYPq)%P}ud+j?9yeY8?&Qy`aOULdzNyKwqwq|mBBF2lS)AMfJg=_8#& zcLKQt^J3zNI~PzN$rSn`kQaYmOda#9e4iR;|2$Zi$1<-U=jBJ>?<39{`^G{Aq(9PM-k)yfzI4~q z=bjnIf}TJEAc(`2LM#G#`h0>Q4mX}a44{g`kwWAG9XWka#o@#ghyY9CP^S>7Ku1d- zOX6_h3DkjgaVS%WOrRsAk9Bc4fH|cVo!R7}dc%_lG;8{#(mX%D-LwxfK*YyDE9|9B zlxT)Tq8JMe3p7*uv`8_NNF#Uxg)s@Ni!_=-LjqY(AIhRz7pa#+CJ<+lEs8XfLfryc zOdrXjS`?{=LnM%95v_>Sn?hXzSxFzHqFE8Cl_?Vlxk$(&^`ua*2)wT3FokWJw-mS_7g% z(w4DMwLl0jtmU1GC{j&ZkVLA%L;{UOJc39)_tqAvHifDL(s~h;V*?G5DDI^#QVGZe z;v7^;h{SPi4UuY6s3?$RgC(CvI7E_2H?%~8NFdF}SPGFe##d4zl~QO|AnwK?&42WW zgz$u?ok%JPgd|Q*jYtUMtEJHicr*at)46bX*`Zgb5xP?+TL}-$PF|;rnFb!k0ug}KnU|V z+8jxwwBBD{bnC?s2F54?MUEiSus{gnM9>6^%q~)N;NOd^`9maeBB%mIW)-PPAVhH@ z=mJG%6zOuhF1yp;$l^q-2o#w~q)mY=h!e3UP-GU7mh=af#EDoHC}Lftzo^XKM|QZg Q>Hq)$07*qoM6N<$f^(E5=l}o! literal 0 HcmV?d00001 diff --git a/src/images/qidenticon_x.png b/src/images/qidenticon_x.png new file mode 100644 index 0000000000000000000000000000000000000000..07e903e5837b35e6a8e3e8322c2b41171e9d4fcc GIT binary patch literal 1763 zcmV<91|0c`P)#(-4VoeXSbp=#UX ziEaa(|7o=_4Umn5rfH8>x#nMmkBE%c=w1+wgren4|4$pJ{32^08A)5iQ7z8(z7;+z zthNZVAQ(wYJBq$0GtkwSTSC)F(weQbwXF5Ba0)TQNc~7@#nG>92D&?-6(o(str1mQ zuIjukoLWGOMv^iSmo^f8_?PkkH7qUKUab!zOtyRBsP(Sjs-4Lx8lsVEm+V`gS%FFm zjs6l#pc14+nz?|6lt|pQ)cUB|dDiCvH7J&EG|yNO`bI5*N+c7B<1AA$k&w<1yE@Cu zwGoM{?3~L%i2VSQKqZukq%mSCkEu1^>d~HcWKl9>B+d(Hlr`LazXi=<6R2b)k+g+B zGm}UN_4f{%p^=5qu*_8hx$2{wkJau!N7wfXvj)mYTqQ=D&`U7V-alMlECq5$)Iij7 zh9Lu?Vy!biKfb-l;dsI_H~jWdgxv#?$eC!|QV)R-B7Q$JzoBRwUI zMDmz8CGHZa#QRzm6|;kFdJ?TYx<`>nq#Y8c#C-ylcv`EXVr5`0oyJ>Bp)(YTv}59w zY?nYKR@bViSRF*m7ZNR}(3ze@io|&b{$}nTc3Z2Wf9j-ymc2m99ZX019SxoHB7p%cKnEys62P?Ro16jk$CYx8^ z&;DQ`${86l5*^L;-K+)*^XMUEN|8sK!$@tcrjV=C_}~XgGm34l5Qb`FF@;<&W$cKw z2}QSe31hXfmO@+I&fy&m^NDXT6$Wc#DTTJarPUK!rc>GCE{xX3N(#keJr2+`o7yIu zVYoIHQYap6HjcK*RJZvJCs#V`owQr~Di4A=(2D0ghT#hrCfOr@ScDTNVx zKru|oHrxDq0?AAe36xS8vIivNWTxJ=W%Vh7fFAajwFSP3=fVtv^a}v@zOa}JwL$vT zfO_R+m`4xM)A%rwJ}4>FZJ;oXUsRSd!ch7kq)?ZE!YqDKy`3YBrH^(BH4PLd@e9N) ztzj^IG*hT*pfHDDpdRZ9qv@lSLbnDAQ}_k)(dICmJ{l=>X`nEJU!b>l3FGM_okDL0 z3KRIn#1?m9Kz$@r=$C<_^ZjCKo8K^^K2j<4zk#CD{bKS}2VqEkBvR;4O3~S#;Wp9n z`SJD5*}>ZPs|38n8w(YAz8)eEc5(&;%(L-3@q9fF$)9yjld?UT8tL<9IR>MLU+~ zKwiX5Y3yezx7*uK+bEGhY3;1adLpgXzLY?gg-eMxJ$6l`Efa|II*!FeTB&zBfvky? zl59Hcl1N)5kmi+RD~U8)<5U7!5F{n6(eE{pTqY3mS`~64&D1rVK&XvR$=2vb)^P+k z31rDU$c!3$$UtbL#`;=S{;9QW{-MHAI;b>G>oY5Gw7FIrqQ*74=RgPzv%Xf;Tt#tA zj;X1241#g)knS_l3tAk)d!3`LLamFcfjpd*STfHU?tYw}IL;oB8nn||RJbmPvKYwA z{(m)gHB#B`HxX72pdqUE^^o0+#9|=t*Fk#N$w=q6{%Z8-mm!FTVxSCyCNqraxNIOK zk Date: Fri, 20 Sep 2013 14:30:53 +0200 Subject: [PATCH 11/48] load avatar from file --- src/bitmessageqt/__init__.py | 66 ++++++++++++++++++++++++++++++------ 1 file changed, 55 insertions(+), 11 deletions(-) diff --git a/src/bitmessageqt/__init__.py b/src/bitmessageqt/__init__.py index cecb6401..61b93e2b 100644 --- a/src/bitmessageqt/__init__.py +++ b/src/bitmessageqt/__init__.py @@ -124,13 +124,13 @@ def identiconize(address): return idcon def avatarize(address, fallBackToIdenticon = False): - str_broadcast_subscribers = '[Broadcast subscribers]' - # don't add the suffix for [Broadcast subscribers] import hashlib hash = hashlib.md5(addBMIfNotPresent(address)).hexdigest() + str_broadcast_subscribers = '[Broadcast subscribers]' if address == str_broadcast_subscribers: + # don't hash [Broadcast subscribers] hash = address - print address, ' => ', hash + # print address, ' => ', hash idcon = QtGui.QIcon() # http://pyqt.sourceforge.net/Docs/PyQt4/qimagereader.html#supportedImageFormats # print QImageReader.supportedImageFormats () @@ -140,11 +140,11 @@ def avatarize(address, fallBackToIdenticon = False): upper = shared.appdata + 'avatars/' + hash + '.' + ext.upper() lower = shared.appdata + 'avatars/' + hash + '.' + ext.lower() if os.path.isfile(lower): - print 'found avatar of ', address + # print 'found avatar of ', address idcon.addFile(upper) return idcon elif os.path.isfile(upper): - print 'found avatar of ', address + # print 'found avatar of ', address idcon.addFile(upper) return idcon if fallBackToIdenticon: @@ -292,6 +292,8 @@ class MyForm(QtGui.QMainWindow): "MainWindow", "Enable"), self.on_action_YourIdentitiesEnable) self.actionDisable = self.ui.addressContextMenuToolbar.addAction(_translate( "MainWindow", "Disable"), self.on_action_YourIdentitiesDisable) + self.actionSetAvatar = self.ui.addressContextMenuToolbar.addAction(_translate( + "MainWindow", "Set avatar..."), self.on_action_YourIdentitiesSetAvatar) self.actionClipboard = self.ui.addressContextMenuToolbar.addAction(_translate( "MainWindow", "Copy address to clipboard"), self.on_action_YourIdentitiesClipboard) self.actionSpecialAddressBehavior = self.ui.addressContextMenuToolbar.addAction(_translate( @@ -307,6 +309,7 @@ class MyForm(QtGui.QMainWindow): self.popMenu.addSeparator() self.popMenu.addAction(self.actionEnable) self.popMenu.addAction(self.actionDisable) + self.popMenu.addAction(self.actionSetAvatar) self.popMenu.addAction(self.actionSpecialAddressBehavior) # Popup menu for the Address Book page @@ -455,7 +458,7 @@ class MyForm(QtGui.QMainWindow): self.ui.tableWidgetAddressBook.insertRow(0) # address book item newItem = QtGui.QTableWidgetItem(unicode(label, 'utf-8')) - newItem.setIcon(avatarize(addressInKeysFile)) + newItem.setIcon(avatarize(address)) self.ui.tableWidgetAddressBook.setItem(0, 0, newItem) newItem = QtGui.QTableWidgetItem(address) newItem.setIcon(identiconize(address)) @@ -1877,7 +1880,7 @@ class MyForm(QtGui.QMainWindow): isEnabled = shared.config.getboolean( addressInKeysFile, 'enabled') # I realize that this is poor programming practice but I don't care. It's easier for others to read. if isEnabled: - self.ui.comboBoxSendFrom.insertItem(0, identiconize(addressInKeysFile), unicode(shared.config.get( + self.ui.comboBoxSendFrom.insertItem(0, avatarize(addressInKeysFile, True), unicode(shared.config.get( addressInKeysFile, 'label'), 'utf-8'), addressInKeysFile) self.ui.comboBoxSendFrom.insertItem(0, '', '') if(self.ui.comboBoxSendFrom.count() == 2): @@ -1909,7 +1912,7 @@ class MyForm(QtGui.QMainWindow): newItem = QtGui.QTableWidgetItem(unicode(toLabel, 'utf-8')) newItem.setToolTip(unicode(toLabel, 'utf-8')) newItem.setData(Qt.UserRole, str(toAddress)) - newItem.setIcon(identiconize(toAddress)) + newItem.setIcon(avatarize(toAddress, True)) self.ui.tableWidgetSent.setItem(0, 0, newItem) if fromLabel == '': newItem = QtGui.QTableWidgetItem(unicode(fromAddress, 'utf-8')) @@ -1918,7 +1921,7 @@ class MyForm(QtGui.QMainWindow): newItem = QtGui.QTableWidgetItem(unicode(fromLabel, 'utf-8')) newItem.setToolTip(unicode(fromLabel, 'utf-8')) newItem.setData(Qt.UserRole, str(fromAddress)) - newItem.setIcon(identiconize(fromAddress)) + newItem.setIcon(avatarize(fromAddress, True)) self.ui.tableWidgetSent.setItem(0, 1, newItem) newItem = QtGui.QTableWidgetItem(unicode(subject, 'utf-8)')) newItem.setToolTip(unicode(subject, 'utf-8)')) @@ -1988,7 +1991,7 @@ class MyForm(QtGui.QMainWindow): if shared.safeConfigGetBoolean(str(toAddress), 'chan'): newItem.setTextColor(QtGui.QColor(216, 119, 0)) # orange self.ui.tableWidgetInbox.insertRow(0) - newItem.setIcon(identiconize(toAddress)) + newItem.setIcon(avatarize(toAddress, True)) self.ui.tableWidgetInbox.setItem(0, 0, newItem) if fromLabel == '': @@ -2003,7 +2006,7 @@ class MyForm(QtGui.QMainWindow): self.notifierShow(unicode(_translate("MainWindow",'New Message').toUtf8(),'utf-8'), unicode(_translate("MainWindow",'From ').toUtf8(),'utf-8') + unicode(fromLabel, 'utf-8'), self.SOUND_KNOWN, unicode(fromLabel, 'utf-8')) newItem.setData(Qt.UserRole, str(fromAddress)) newItem.setFont(font) - newItem.setIcon(identiconize(fromAddress)) + newItem.setIcon(avatarize(fromAddress, True)) self.ui.tableWidgetInbox.setItem(0, 1, newItem) newItem = QtGui.QTableWidgetItem(unicode(subject, 'utf-8)')) newItem.setToolTip(unicode(subject, 'utf-8)')) @@ -2048,6 +2051,7 @@ class MyForm(QtGui.QMainWindow): self.ui.tableWidgetAddressBook.setSortingEnabled(False) self.ui.tableWidgetAddressBook.insertRow(0) newItem = QtGui.QTableWidgetItem(unicode(label, 'utf-8')) + newItem.setIcon(avatarize(address)) self.ui.tableWidgetAddressBook.setItem(0, 0, newItem) newItem = QtGui.QTableWidgetItem(address) newItem.setIcon(identiconize(address)) @@ -2078,6 +2082,7 @@ class MyForm(QtGui.QMainWindow): self.ui.tableWidgetSubscriptions.setSortingEnabled(False) self.ui.tableWidgetSubscriptions.insertRow(0) newItem = QtGui.QTableWidgetItem(unicode(label, 'utf-8')) + newItem.setIcon(avatarize(address)) self.ui.tableWidgetSubscriptions.setItem(0,0,newItem) newItem = QtGui.QTableWidgetItem(address) newItem.setIcon(identiconize(address)) @@ -2128,6 +2133,7 @@ class MyForm(QtGui.QMainWindow): newItem = QtGui.QTableWidgetItem(unicode(label, 'utf-8')) if not enabled: newItem.setTextColor(QtGui.QColor(128, 128, 128)) + newItem.setIcon(avatarize(address)) self.ui.tableWidgetBlacklist.setItem(0, 0, newItem) newItem = QtGui.QTableWidgetItem(address) newItem.setIcon(identiconize(address)) @@ -2955,6 +2961,43 @@ class MyForm(QtGui.QMainWindow): clipboard = QtGui.QApplication.clipboard() clipboard.setText(str(addressAtCurrentRow)) + def on_action_YourIdentitiesSetAvatar(self): + currentRow = self.ui.tableWidgetYourIdentities.currentRow() + addressAtCurrentRow = self.ui.tableWidgetYourIdentities.item( + currentRow, 1).text() + import hashlib + hash = hashlib.md5(addBMIfNotPresent(addressAtCurrentRow)).hexdigest() + extensions = ['PNG', 'GIF', 'JPG', 'JPEG', 'SVG', 'BMP', 'MNG', 'PBM', 'PGM', 'PPM', 'TIFF', 'XBM', 'XPM', 'TGA'] + # http://pyqt.sourceforge.net/Docs/PyQt4/qimagereader.html#supportedImageFormats + names = {'BMP':'Windows Bitmap', 'GIF':'Graphic Interchange Format', 'JPG':'Joint Photographic Experts Group', 'JPEG':'Joint Photographic Experts Group', 'MNG':'Multiple-image Network Graphics', 'PNG':'Portable Network Graphics', 'PBM':'Portable Bitmap', 'PGM':'Portable Graymap', 'PPM':'Portable Pixmap', 'TIFF':'Tagged Image File Format', 'XBM':'X11 Bitmap', 'XPM':'X11 Pixmap', 'SVG':'Scalable Vector Graphics', 'TGA':'Targa Image Format'} + filters = [] + all_images_filter = [] + for ext in extensions: + filters += [ names[ext] + ' (*.' + ext.lower() + ')' ] + all_images_filter += [ '*.' + ext.lower() ] + filters[0:0] = ['Image files (' + ' '.join(all_images_filter) + ')'] + filters[1:1] = ['All files (*.*)'] + sourcefile = QFileDialog.getOpenFileName(self, _translate("MainWindow","Set avatar..."), filter = ';;'.join(filters)) + # determine the correct filename (note that avatars don't use the suffix) + destination = shared.appdata + 'avatars/' + hash + '.' + sourcefile.split('.')[-1] + # copy the image file to the appdata folder + exists = QtCore.QFile.exists(destination) + overwrite = QtGui.QMessageBox.No + if exists: + displayMsg = _translate("MainWindow", "You have already set an avatar for this address. Do you really want to overwrite it?") + overwrite = QtGui.QMessageBox.question( + self, 'Message', displayMsg, QtGui.QMessageBox.Yes, QtGui.QMessageBox.No) + if (not exists) | (overwrite == QtGui.QMessageBox.Yes): + if overwrite == QtGui.QMessageBox.Yes: + QtCore.QFile.remove(destination) + copied = QtCore.QFile.copy(sourcefile, destination) + if not copied: + print 'couldn\'t copy :(' + return False + # set the icon + self.ui.tableWidgetYourIdentities.item( + currentRow, 0).setIcon(avatarize(addressAtCurrentRow)) + def on_context_menuYourIdentities(self, point): self.popMenu.exec_( self.ui.tableWidgetYourIdentities.mapToGlobal(point)) @@ -3101,6 +3144,7 @@ class MyForm(QtGui.QMainWindow): self.ui.tableWidgetYourIdentities.setSortingEnabled(False) self.ui.tableWidgetYourIdentities.insertRow(0) newItem = QtGui.QTableWidgetItem(unicode(label, 'utf-8')) + newItem.setIcon(avatarize(address)) self.ui.tableWidgetYourIdentities.setItem( 0, 0, newItem) newItem = QtGui.QTableWidgetItem(address) From 9a1226ba37fc8840af54997f3a57fa8b44b37135 Mon Sep 17 00:00:00 2001 From: sendiulo Date: Sat, 21 Sep 2013 09:41:23 +0200 Subject: [PATCH 12/48] - "set avatar" via context menu - remove avatar by canceling "set avatar" - get FROM address labels also from YourIdentities --- src/bitmessageqt/__init__.py | 157 +++++++++++++++++++++++++++-------- 1 file changed, 123 insertions(+), 34 deletions(-) diff --git a/src/bitmessageqt/__init__.py b/src/bitmessageqt/__init__.py index 61b93e2b..f4422ff7 100644 --- a/src/bitmessageqt/__init__.py +++ b/src/bitmessageqt/__init__.py @@ -321,6 +321,8 @@ class MyForm(QtGui.QMainWindow): "MainWindow", "Copy address to clipboard"), self.on_action_AddressBookClipboard) self.actionAddressBookSubscribe = self.ui.addressBookContextMenuToolbar.addAction(_translate( "MainWindow", "Subscribe to this address"), self.on_action_AddressBookSubscribe) + self.actionAddressBookSetAvatar = self.ui.addressBookContextMenuToolbar.addAction(_translate( + "MainWindow", "Set avatar..."), self.on_action_AddressBookSetAvatar) self.actionAddressBookNew = self.ui.addressBookContextMenuToolbar.addAction(_translate( "MainWindow", "Add New Address"), self.on_action_AddressBookNew) self.actionAddressBookDelete = self.ui.addressBookContextMenuToolbar.addAction(_translate( @@ -332,7 +334,8 @@ class MyForm(QtGui.QMainWindow): self.popMenuAddressBook = QtGui.QMenu(self) self.popMenuAddressBook.addAction(self.actionAddressBookSend) self.popMenuAddressBook.addAction(self.actionAddressBookClipboard) - self.popMenuAddressBook.addAction( self.actionAddressBookSubscribe ) + self.popMenuAddressBook.addAction(self.actionAddressBookSubscribe) + self.popMenuAddressBook.addAction(self.actionAddressBookSetAvatar) self.popMenuAddressBook.addSeparator() self.popMenuAddressBook.addAction(self.actionAddressBookNew) self.popMenuAddressBook.addAction(self.actionAddressBookDelete) @@ -350,6 +353,8 @@ class MyForm(QtGui.QMainWindow): _translate("MainWindow", "Enable"), self.on_action_SubscriptionsEnable) self.actionsubscriptionsDisable = self.ui.subscriptionsContextMenuToolbar.addAction( _translate("MainWindow", "Disable"), self.on_action_SubscriptionsDisable) + self.actionsubscriptionsSetAvatar = self.ui.subscriptionsContextMenuToolbar.addAction( + _translate("MainWindow", "Set avatar..."), self.on_action_SubscriptionsSetAvatar) self.ui.tableWidgetSubscriptions.setContextMenuPolicy( QtCore.Qt.CustomContextMenu) self.connect(self.ui.tableWidgetSubscriptions, QtCore.SIGNAL( @@ -360,6 +365,7 @@ class MyForm(QtGui.QMainWindow): self.popMenuSubscriptions.addSeparator() self.popMenuSubscriptions.addAction(self.actionsubscriptionsEnable) self.popMenuSubscriptions.addAction(self.actionsubscriptionsDisable) + self.popMenuSubscriptions.addAction(self.actionsubscriptionsSetAvatar) self.popMenuSubscriptions.addSeparator() self.popMenuSubscriptions.addAction(self.actionsubscriptionsClipboard) @@ -393,6 +399,8 @@ class MyForm(QtGui.QMainWindow): "MainWindow", "Enable"), self.on_action_BlacklistEnable) self.actionBlacklistDisable = self.ui.blacklistContextMenuToolbar.addAction(_translate( "MainWindow", "Disable"), self.on_action_BlacklistDisable) + self.actionBlacklistSetAvatar = self.ui.blacklistContextMenuToolbar.addAction(_translate( + "MainWindow", "Set avatar..."), self.on_action_BlacklistSetAvatar) self.ui.tableWidgetBlacklist.setContextMenuPolicy( QtCore.Qt.CustomContextMenu) self.connect(self.ui.tableWidgetBlacklist, QtCore.SIGNAL( @@ -405,6 +413,7 @@ class MyForm(QtGui.QMainWindow): self.popMenuBlacklist.addSeparator() self.popMenuBlacklist.addAction(self.actionBlacklistEnable) self.popMenuBlacklist.addAction(self.actionBlacklistDisable) + self.popMenuBlacklist.addAction(self.actionBlacklistSetAvatar) # Initialize the user's list of addresses on the 'Your Identities' tab. configSections = shared.config.sections() @@ -427,7 +436,7 @@ class MyForm(QtGui.QMainWindow): if not isEnabled: newItem.setTextColor(QtGui.QColor(128, 128, 128)) if shared.safeConfigGetBoolean(addressInKeysFile, 'mailinglist'): - newItem.setTextColor(QtGui.QColor(137, 04, 177)) # magenta + newItem.setTextColor(QtGui.QColor(137, 04, 177)) # magenta newItem.setIcon(identiconize(addressInKeysFile)) self.ui.tableWidgetYourIdentities.setItem(0, 1, newItem) newItem = QtGui.QTableWidgetItem(str( @@ -839,18 +848,25 @@ class MyForm(QtGui.QMainWindow): if toLabel == '': toLabel = toAddress - fromLabel = '' - t = (fromAddress,) - shared.sqlLock.acquire() - shared.sqlSubmitQueue.put( - '''select label from addressbook where address=?''') - shared.sqlSubmitQueue.put(t) - queryreturn = shared.sqlReturnQueue.get() - shared.sqlLock.release() + try: # try to get the from label fom YourIdentites (for chan messages) + fromLabel = shared.config.get(fromAddress, 'label') + checkIfChan = True + except: + fromLabel = '' + checkIfChan = False + + if fromLabel == '': # If this address wasn't in our address book... + t = (fromAddress,) + shared.sqlLock.acquire() + shared.sqlSubmitQueue.put( + '''select label from addressbook where address=?''') + shared.sqlSubmitQueue.put(t) + queryreturn = shared.sqlReturnQueue.get() + shared.sqlLock.release() - if queryreturn != []: - for row in queryreturn: - fromLabel, = row + if queryreturn != []: + for row in queryreturn: + fromLabel, = row if fromLabel == '': # If this address wasn't in our address book... t = (fromAddress,) @@ -876,7 +892,7 @@ class MyForm(QtGui.QMainWindow): newItem.setFont(font) newItem.setData(Qt.UserRole, str(toAddress)) if shared.safeConfigGetBoolean(toAddress, 'mailinglist'): - newItem.setTextColor(QtGui.QColor(137, 04, 177)) + newItem.setTextColor(QtGui.QColor(137, 04, 177)) # magenta if shared.safeConfigGetBoolean(str(toAddress), 'chan'): newItem.setTextColor(QtGui.QColor(216, 119, 0)) # orange newItem.setIcon(avatarize(toAddress, True)) @@ -894,6 +910,8 @@ class MyForm(QtGui.QMainWindow): if not read: newItem.setFont(font) newItem.setData(Qt.UserRole, str(fromAddress)) + if checkIfChan & shared.safeConfigGetBoolean(str(toAddress), 'chan'): + newItem.setTextColor(QtGui.QColor(216, 119, 0)) # orange newItem.setIcon(avatarize(fromAddress, True)) self.ui.tableWidgetInbox.setItem(0, 1, newItem) # subject @@ -1568,6 +1586,8 @@ class MyForm(QtGui.QMainWindow): fromLabel, = row self.ui.tableWidgetInbox.item( i, 1).setText(unicode(fromLabel, 'utf-8')) + self.ui.tableWidgetInbox.item( + i, 1).setIcon(avatarize(addressToLookup, True)) else: # It might be a broadcast message. We should check for that # label. @@ -1583,6 +1603,29 @@ class MyForm(QtGui.QMainWindow): fromLabel, = row self.ui.tableWidgetInbox.item( i, 1).setText(unicode(fromLabel, 'utf-8')) + self.ui.tableWidgetInbox.item( + i, 1).setIcon(avatarize(addressToLookup, True)) + else: + # It might be a chan message. We should check for that + # label. + try: + fromLabel = shared.config.get(addressToLookup, 'label') + except: + fromLabel = '' + if fromLabel == '': + fromLabel = addressToLookup + self.ui.tableWidgetInbox.item( + i, 1).setText(unicode(fromLabel, 'utf-8')) + self.ui.tableWidgetInbox.item( + i, 1).setIcon(avatarize(addressToLookup, True)) + # Set the color according to whether it is the address of a mailing + # list or not. + if shared.safeConfigGetBoolean(addressToLookup, 'chan'): + self.ui.tableWidgetInbox.item(i, 1).setTextColor(QtGui.QColor(216, 119, 0)) # orange + else: + self.ui.tableWidgetInbox.item( + i, 1).setTextColor(QApplication.palette().text().color()) + def rerenderInboxToLabels(self): for i in range(self.ui.tableWidgetInbox.rowCount()): @@ -1599,9 +1642,11 @@ class MyForm(QtGui.QMainWindow): self.ui.tableWidgetInbox.item( i, 0).setIcon(avatarize(toAddress, True)) # Set the color according to whether it is the address of a mailing - # list or not. - if shared.safeConfigGetBoolean(toAddress, 'mailinglist'): - self.ui.tableWidgetInbox.item(i, 0).setTextColor(QtGui.QColor(137, 04, 177)) + # list, a chan or neither. + if shared.safeConfigGetBoolean(toAddress, 'chan'): + self.ui.tableWidgetInbox.item(i, 0).setTextColor(QtGui.QColor(216, 119, 0)) # orange + elif shared.safeConfigGetBoolean(toAddress, 'mailinglist'): + self.ui.tableWidgetInbox.item(i, 0).setTextColor(QtGui.QColor(137, 04, 177)) # magenta else: self.ui.tableWidgetInbox.item( i, 0).setTextColor(QApplication.palette().text().color()) @@ -1987,7 +2032,7 @@ class MyForm(QtGui.QMainWindow): newItem.setFont(font) newItem.setData(Qt.UserRole, str(toAddress)) if shared.safeConfigGetBoolean(str(toAddress), 'mailinglist'): - newItem.setTextColor(QtGui.QColor(137, 04, 177)) + newItem.setTextColor(QtGui.QColor(137, 04, 177)) # magenta if shared.safeConfigGetBoolean(str(toAddress), 'chan'): newItem.setTextColor(QtGui.QColor(216, 119, 0)) # orange self.ui.tableWidgetInbox.insertRow(0) @@ -2395,7 +2440,7 @@ class MyForm(QtGui.QMainWindow): addressAtCurrentRow), 'mailinglist', 'true') shared.config.set(str(addressAtCurrentRow), 'mailinglistname', str( self.dialog.ui.lineEditMailingListName.text().toUtf8())) - self.ui.tableWidgetYourIdentities.item(currentRow, 1).setTextColor(QtGui.QColor(137, 04, 177)) + self.ui.tableWidgetYourIdentities.item(currentRow, 1).setTextColor(QtGui.QColor(137, 04, 177)) # magenta with open(shared.appdata + 'keys.dat', 'wb') as configfile: shared.config.write(configfile) self.rerenderInboxToLabels() @@ -2932,7 +2977,7 @@ class MyForm(QtGui.QMainWindow): self.ui.tableWidgetYourIdentities.item( currentRow, 2).setTextColor(QApplication.palette().text().color()) if shared.safeConfigGetBoolean(addressAtCurrentRow, 'mailinglist'): - self.ui.tableWidgetYourIdentities.item(currentRow, 1).setTextColor(QtGui.QColor(137, 04, 177)) + self.ui.tableWidgetYourIdentities.item(currentRow, 1).setTextColor(QtGui.QColor(137, 04, 177)) # magenta if shared.safeConfigGetBoolean(addressAtCurrentRow, 'chan'): self.ui.tableWidgetYourIdentities.item(currentRow, 1).setTextColor(QtGui.QColor(216, 119, 0)) # orange shared.reloadMyAddressHashes() @@ -2949,7 +2994,7 @@ class MyForm(QtGui.QMainWindow): self.ui.tableWidgetYourIdentities.item( currentRow, 2).setTextColor(QtGui.QColor(128, 128, 128)) if shared.safeConfigGetBoolean(addressAtCurrentRow, 'mailinglist'): - self.ui.tableWidgetYourIdentities.item(currentRow, 1).setTextColor(QtGui.QColor(137, 04, 177)) + self.ui.tableWidgetYourIdentities.item(currentRow, 1).setTextColor(QtGui.QColor(137, 04, 177)) # magenta with open(shared.appdata + 'keys.dat', 'wb') as configfile: shared.config.write(configfile) shared.reloadMyAddressHashes() @@ -2962,8 +3007,21 @@ class MyForm(QtGui.QMainWindow): clipboard.setText(str(addressAtCurrentRow)) def on_action_YourIdentitiesSetAvatar(self): - currentRow = self.ui.tableWidgetYourIdentities.currentRow() - addressAtCurrentRow = self.ui.tableWidgetYourIdentities.item( + self.on_action_SetAvatar(self.ui.tableWidgetYourIdentities) + + def on_action_AddressBookSetAvatar(self): + self.on_action_SetAvatar(self.ui.tableWidgetAddressBook) + + def on_action_SubscriptionsSetAvatar(self): + self.on_action_SetAvatar(self.ui.tableWidgetSubscriptions) + + def on_action_BlacklistSetAvatar(self): + self.on_action_SetAvatar(self.ui.tableWidgetBlacklist) + + def on_action_SetAvatar(self, thisTableWidget): + # thisTableWidget = self.ui.tableWidgetYourIdentities + currentRow = thisTableWidget.currentRow() + addressAtCurrentRow = thisTableWidget.item( currentRow, 1).text() import hashlib hash = hashlib.md5(addBMIfNotPresent(addressAtCurrentRow)).hexdigest() @@ -2972,31 +3030,62 @@ class MyForm(QtGui.QMainWindow): names = {'BMP':'Windows Bitmap', 'GIF':'Graphic Interchange Format', 'JPG':'Joint Photographic Experts Group', 'JPEG':'Joint Photographic Experts Group', 'MNG':'Multiple-image Network Graphics', 'PNG':'Portable Network Graphics', 'PBM':'Portable Bitmap', 'PGM':'Portable Graymap', 'PPM':'Portable Pixmap', 'TIFF':'Tagged Image File Format', 'XBM':'X11 Bitmap', 'XPM':'X11 Pixmap', 'SVG':'Scalable Vector Graphics', 'TGA':'Targa Image Format'} filters = [] all_images_filter = [] + current_files = [] for ext in extensions: filters += [ names[ext] + ' (*.' + ext.lower() + ')' ] all_images_filter += [ '*.' + ext.lower() ] + upper = shared.appdata + 'avatars/' + hash + '.' + ext.upper() + lower = shared.appdata + 'avatars/' + hash + '.' + ext.lower() + if os.path.isfile(lower): + current_files += [lower] + elif os.path.isfile(upper): + current_files += [upper] filters[0:0] = ['Image files (' + ' '.join(all_images_filter) + ')'] filters[1:1] = ['All files (*.*)'] sourcefile = QFileDialog.getOpenFileName(self, _translate("MainWindow","Set avatar..."), filter = ';;'.join(filters)) # determine the correct filename (note that avatars don't use the suffix) destination = shared.appdata + 'avatars/' + hash + '.' + sourcefile.split('.')[-1] - # copy the image file to the appdata folder exists = QtCore.QFile.exists(destination) - overwrite = QtGui.QMessageBox.No - if exists: - displayMsg = _translate("MainWindow", "You have already set an avatar for this address. Do you really want to overwrite it?") - overwrite = QtGui.QMessageBox.question( - self, 'Message', displayMsg, QtGui.QMessageBox.Yes, QtGui.QMessageBox.No) + if sourcefile == '': + # ask for removal of avatar + if exists | (len(current_files)>0): + displayMsg = _translate("MainWindow", "Do you really want to remove this avatar?") + overwrite = QtGui.QMessageBox.question( + self, 'Message', displayMsg, QtGui.QMessageBox.Yes, QtGui.QMessageBox.No) + else: + overwrite = QtGui.QMessageBox.No + else: + # ask whether to overwrite old avatar + if exists | (len(current_files)>0): + displayMsg = _translate("MainWindow", "You have already set an avatar for this address. Do you really want to overwrite it?") + overwrite = QtGui.QMessageBox.question( + self, 'Message', displayMsg, QtGui.QMessageBox.Yes, QtGui.QMessageBox.No) + else: + overwrite = QtGui.QMessageBox.No + + # copy the image file to the appdata folder if (not exists) | (overwrite == QtGui.QMessageBox.Yes): if overwrite == QtGui.QMessageBox.Yes: + for file in current_files: + QtCore.QFile.remove(file) QtCore.QFile.remove(destination) - copied = QtCore.QFile.copy(sourcefile, destination) - if not copied: - print 'couldn\'t copy :(' - return False + # copy it + if sourcefile != '': + copied = QtCore.QFile.copy(sourcefile, destination) + if not copied: + print 'couldn\'t copy :(' + return False # set the icon - self.ui.tableWidgetYourIdentities.item( + thisTableWidget.item( currentRow, 0).setIcon(avatarize(addressAtCurrentRow)) + ### + shared.reloadBroadcastSendersForWhichImWatching() + self.rerenderSubscriptions() + self.rerenderComboBoxSendFrom() + self.rerenderInboxFromLabels() + self.rerenderInboxToLabels() + self.rerenderSentFromLabels() + self.rerenderSentToLabels() def on_context_menuYourIdentities(self, point): self.popMenu.exec_( From a58164d8318d60a4073e52136d0d001e9b14505d Mon Sep 17 00:00:00 2001 From: sendiulo Date: Sat, 21 Sep 2013 13:21:36 +0200 Subject: [PATCH 13/48] - additional identicon type "empty" if you want to have a placeholder - place image named 'default.*' to display a generic user icon as fallback - place image named '[Broadcast Subscribers].*' to set the icon for broadcasts --- src/bitmessageqt/__init__.py | 92 +- src/bitmessageqt/bitmessage_icons_rc.py | 1894 ++++++++++++++--------- src/bitmessageqt/bitmessageui.py | 2 +- src/bitmessageqt/settings.py | 2 +- src/helper_startup.py | 4 + 5 files changed, 1257 insertions(+), 737 deletions(-) diff --git a/src/bitmessageqt/__init__.py b/src/bitmessageqt/__init__.py index f4422ff7..dd1c1a7a 100644 --- a/src/bitmessageqt/__init__.py +++ b/src/bitmessageqt/__init__.py @@ -58,11 +58,6 @@ def _translate(context, text): def identiconize(address): size = 48 - # here you could put "@bitmessge.ch" or "@bm.addr" to make it compatible with other identicon generators - # instead, you could also use a pseudo-password to salt the generation of the identicons - # Attacks where someone creates an address to mimic someone else's identicon should be impossible then - # i think it should generate a random string by default - # If you include another identicon library, please generate an # example identicon with the following md5 hash: # 3fd4bf901b9d4ea1394f0fb358725b28 @@ -74,6 +69,11 @@ def identiconize(address): identicon_lib = False try: + # As an 'identiconsuffix' you could put "@bitmessge.ch" or "@bm.addr" to make it compatible with other identicon generators. (Note however, that E-Mail programs might convert the BM-address to lowercase first.) + # It can be used as a pseudo-password to salt the generation of the identicons to decrease the risk + # of attacks where someone creates an address to mimic someone else's identicon. + # If not set yet it will be filled by a random string. + # Another good idea would be to fill it with the private key of one of your addresses (because you don't need to memorize it then). identiconsuffix = shared.config.get('bitmessagesettings', 'identiconsuffix') except: identiconsuffix = '' @@ -93,7 +93,7 @@ def identiconize(address): image = qidenticon.render_identicon(int(hash, 16), size, use_two_colors, opacity, penwidth) # filename = './images/identicons/'+hash+'.png' # image.save(filename) - idcon = QIcon() + idcon = QtGui.QIcon() idcon.addPixmap(image, QtGui.QIcon.Normal, QtGui.QIcon.Off) return idcon elif identicon_lib == 'pydenticon': @@ -112,46 +112,72 @@ def identiconize(address): idcon = QtGui.QIcon() idcon.addPixmap(pix, QtGui.QIcon.Normal, QtGui.QIcon.Off) return idcon - elif identicon_lib == False: + elif identicon_lib == 'empty': + idcon = QtGui.QIcon(":/newPrefix/images/no_identicons.png") + return idcon + elif identicon_lib in ['False', 'false', 'None', 'none']: idcon = QtGui.QIcon() return idcon else: - if identicon_lib & len(identicon_lib) > 0: - print 'Error: couldn\'t find this identicon library: ', identicon_lib - print 'Control for typos!' # default to no identicons idcon = QtGui.QIcon() return idcon def avatarize(address, fallBackToIdenticon = False): - import hashlib - hash = hashlib.md5(addBMIfNotPresent(address)).hexdigest() - str_broadcast_subscribers = '[Broadcast subscribers]' - if address == str_broadcast_subscribers: - # don't hash [Broadcast subscribers] - hash = address - # print address, ' => ', hash + """ + loads a supported image for the given address' hash form 'avatars' folder + falls back to default avatar if 'default.*' file exists + falls back to identiconize(address) if second argument fallBackToIdenticon == True + """ idcon = QtGui.QIcon() - # http://pyqt.sourceforge.net/Docs/PyQt4/qimagereader.html#supportedImageFormats - # print QImageReader.supportedImageFormats () - # QImageReader.supportedImageFormats () - extensions = ['PNG', 'GIF', 'JPG', 'JPEG', 'SVG', 'BMP', 'MNG', 'PBM', 'PGM', 'PPM', 'TIFF', 'XBM', 'XPM', 'TGA'] - for ext in extensions: - upper = shared.appdata + 'avatars/' + hash + '.' + ext.upper() - lower = shared.appdata + 'avatars/' + hash + '.' + ext.lower() - if os.path.isfile(lower): - # print 'found avatar of ', address - idcon.addFile(upper) - return idcon - elif os.path.isfile(upper): - # print 'found avatar of ', address - idcon.addFile(upper) - return idcon + if shared.safeConfigGetBoolean('bitmessagesettings', 'avatars'): + import hashlib + hash = hashlib.md5(addBMIfNotPresent(address)).hexdigest() + str_broadcast_subscribers = '[Broadcast subscribers]' + if address == str_broadcast_subscribers: + # don't hash [Broadcast subscribers] + hash = address + # http://pyqt.sourceforge.net/Docs/PyQt4/qimagereader.html#supportedImageFormats + # print QImageReader.supportedImageFormats () + # QImageReader.supportedImageFormats () + extensions = ['PNG', 'GIF', 'JPG', 'JPEG', 'SVG', 'BMP', 'MNG', 'PBM', 'PGM', 'PPM', 'TIFF', 'XBM', 'XPM', 'TGA'] + # try to find a specific avatar + for ext in extensions: + lower_hash = shared.appdata + 'avatars/' + hash + '.' + ext.lower() + upper_hash = shared.appdata + 'avatars/' + hash + '.' + ext.upper() + if os.path.isfile(lower_hash): + # print 'found avatar of ', address + idcon.addFile(lower_hash) + return idcon + elif os.path.isfile(upper_hash): + # print 'found avatar of ', address + idcon.addFile(upper_hash) + return idcon + # if we haven't found any, try to find a default avatar + for ext in extensions: + lower_default = shared.appdata + 'avatars/' + 'default.' + ext.lower() + upper_default = shared.appdata + 'avatars/' + 'default.' + ext.upper() + if os.path.isfile(lower_default): + default = lower_default + idcon.addFile(lower_default) + return idcon + elif os.path.isfile(upper_default): + default = upper_default + idcon.addFile(upper_default) + return idcon + # if avatars are deactivated or none found if fallBackToIdenticon: return identiconize(address) else: + try: + identicon_lib = shared.config.get('bitmessagesettings', 'identicon') + except: + # default to no identicons + identicon_lib = False + if identicon_lib == 'empty': + idcon = QtGui.QIcon(":/newPrefix/images/no_identicons.png") + return idcon return idcon - #identiconize(address) class MyForm(QtGui.QMainWindow): diff --git a/src/bitmessageqt/bitmessage_icons_rc.py b/src/bitmessageqt/bitmessage_icons_rc.py index 377ce101..2db046a4 100644 --- a/src/bitmessageqt/bitmessage_icons_rc.py +++ b/src/bitmessageqt/bitmessage_icons_rc.py @@ -2,7 +2,7 @@ # Resource object code # -# Created: jeu. juin 13 10:47:41 2013 +# Created: Sa 21. Sep 09:44:10 2013 # by: The Resource Compiler for PyQt (Qt v4.8.4) # # WARNING! All changes made in this file will be lost! @@ -10,6 +10,495 @@ from PyQt4 import QtCore qt_resource_data = "\ +\x00\x00\x03\x66\ +\x89\ +\x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\ +\x00\x00\x10\x00\x00\x00\x10\x08\x06\x00\x00\x00\x1f\xf3\xff\x61\ +\x00\x00\x00\x19\x74\x45\x58\x74\x53\x6f\x66\x74\x77\x61\x72\x65\ +\x00\x41\x64\x6f\x62\x65\x20\x49\x6d\x61\x67\x65\x52\x65\x61\x64\ +\x79\x71\xc9\x65\x3c\x00\x00\x03\x08\x49\x44\x41\x54\x78\xda\x84\ +\x53\x6d\x48\x53\x51\x18\x7e\xce\xfd\xd8\x75\x9b\x8e\xdc\x2c\xdd\ +\x4c\x5d\x4e\xa7\xc9\xe6\xb7\xf6\x61\x61\x11\x14\x52\x16\xf5\xc7\ +\x0a\x0b\xa2\x3f\x41\x51\x41\x61\x7f\x0a\x84\xa2\x9f\xfd\xeb\x67\ +\x7f\xfa\x51\x44\x50\x91\x14\x15\x5a\x14\x41\x25\x6d\x44\x59\x68\ +\x69\xd9\x74\xa6\x6d\xd7\x7d\x38\xb6\xdd\x6d\x77\xa7\x73\x2d\x4d\ +\x84\xe8\x81\x87\xf7\x7d\xef\x7b\xde\xe7\xbe\xe7\x9c\xf7\x10\x30\ +\x48\x84\x20\x4f\xb3\xf8\x8b\xb3\x1b\xe9\xbc\xe5\x38\x14\xb3\x74\ +\x2f\x73\xab\x18\x47\x28\x45\x6f\x36\x0b\xff\xc2\x3a\xde\xc6\xb2\ +\x06\xcd\x61\x24\x4b\x04\xbe\x87\x09\x48\x82\x89\x8a\xb8\x62\xaf\ +\x76\x75\x5a\x4a\xcb\x9d\x31\x85\xae\x9d\x0d\xce\x15\x7c\xf1\xa3\ +\xef\x67\x18\xd0\xc8\xe1\x1f\xf0\xcf\x01\x43\x53\xc4\xf1\x33\x04\ +\x57\x20\x12\x29\xcc\x31\x5b\x84\x4d\x7b\xf6\x18\xb5\x78\xcc\x0f\ +\x07\x23\x34\x0a\xcb\xea\x0a\x19\x4f\x32\xda\x19\xc7\x53\x04\x91\ +\x99\x10\xc4\xde\xd3\xa7\x61\x30\x1a\xa1\xb2\xde\xb5\x98\xe7\xb0\ +\x85\xe5\xc7\xb4\x02\x81\x2e\xa9\x66\xfe\xb9\x86\xd6\xd6\xfd\xee\ +\xba\x3a\xcb\x3b\x8f\x47\x9e\x78\xe7\x8d\xc5\x13\x88\x4a\x3a\x1d\ +\x94\x78\x1c\x82\x28\x22\xae\x6d\x8b\x47\x23\x5b\x7e\x6d\x5e\xa0\ +\xdd\xf9\x77\xe7\xcf\x3e\xd3\x0d\xbd\xa7\x3a\xac\x2e\xa7\x15\x43\ +\x9f\x6d\xd6\xae\x43\xde\xb0\x51\x44\x74\x6c\x78\x18\xf6\x8a\x0a\ +\x68\x96\xc5\x1a\x4a\x16\x6a\x84\xad\xce\xc5\xfa\xae\xc1\x69\x53\ +\x65\xbd\xdb\x8e\x74\x32\x09\xcd\xea\xf2\x4c\xb9\x0e\x5b\x94\x0c\ +\xdc\xba\xe9\x6d\xda\xbe\xa3\xd1\xf3\xe4\xb1\x37\xf7\xb7\x40\xc1\ +\xa2\x40\x26\xbb\x28\xc0\x75\xd5\x29\x23\xc9\xb9\xb9\x8d\x99\x74\ +\x1a\x2a\xe3\xae\xfa\xf4\xc7\xf1\x92\xa2\x60\xce\xc4\x0f\x4b\x85\ +\xb3\x0a\xcf\xfb\x6e\xd2\x57\xdd\x35\x1f\x73\x43\xc9\x47\x33\x25\ +\x26\x4c\x15\xe7\x82\x27\xb5\x07\x41\x09\x87\x7c\x75\x66\xc8\x28\ +\x66\xaa\x4b\x2a\xdd\x4d\xec\x42\x85\xf0\x6c\x20\xf5\x32\x3c\xfa\ +\x4d\x3a\xd1\xe3\xd4\xd7\xb4\x54\xa5\x14\x17\xa6\xdb\xaa\x6d\x85\ +\x5b\xda\x0b\x9e\xe6\x04\x12\xe1\x3c\xc1\x8e\x2c\xfd\xc2\x7f\x6d\ +\xba\x8c\x41\x7d\x07\x1e\x99\x8e\x40\xa5\x24\xc0\x7d\xb8\xb1\x3e\ +\x96\x26\xb6\x57\xaf\x07\xfc\x74\x77\x77\x45\xc1\x6a\x87\x79\x2a\ +\x91\xc0\xd9\x8e\xa3\xb8\x3d\xe5\x41\xe9\xaa\x62\x93\xcb\x5c\x5e\ +\x6b\xa0\xba\x35\xdf\x02\x93\xe2\x92\x39\xa0\xcd\xfd\xa6\xc3\x3b\ +\x83\xf2\x2c\x69\x6c\x6e\x41\x24\x1a\x13\xef\x8f\xb4\xbe\x1f\xf7\ +\x49\x93\x49\x76\x26\xb2\x2c\x43\xb3\x1a\xd4\x54\x46\xaa\x36\x97\ +\xb9\x69\x54\x69\x23\x7c\x77\xdf\x0a\x70\xe2\x7e\x83\x24\xd4\x1c\ +\xeb\x74\xef\x5b\x19\x19\x2a\xb6\x4b\x32\xc6\x15\x0b\x82\xf9\x95\ +\xa1\xab\x0f\xfb\x3d\x49\xce\x17\x6b\x19\xf6\x0e\x0c\x6e\xf0\x6f\ +\xa3\x69\x55\x0f\x45\x35\xd0\x74\x36\x07\xa3\xd1\x27\x84\x3f\x70\ +\xe7\x4c\xe7\xfa\xf2\xee\xa6\x2a\xeb\x5a\x4b\x7e\x9e\xe4\xf3\x4d\ +\xe3\xd2\xde\x52\x9c\xbf\xeb\x43\x59\x99\x15\x72\x28\x9a\x7a\xfb\ +\xe9\xfb\x68\x5f\xff\xeb\x7b\xea\x83\x93\xd7\x97\x0d\x9e\xcc\x41\ +\x89\x36\xd7\xda\xcd\xf5\xd9\x4c\x76\xfe\x2d\x2d\x6f\x97\xaa\xd0\ +\xd5\x39\xac\x35\x90\x4c\xe5\xfc\xe6\x9e\x11\xed\x41\x2d\x61\x90\ +\xf0\xf5\x87\x2e\xc0\xda\xd0\x4e\x79\x29\x41\x05\x7d\x0c\x82\x3e\ +\xde\x36\x7d\xf5\xcd\xcb\xa2\xe3\xeb\x48\x26\x69\x20\x99\x84\x91\ +\xa8\x8a\x1e\x3f\xbc\x2f\xe8\xec\xe8\x45\x1a\x99\x04\x8d\x4c\x2c\ +\xb6\x40\xfe\x0c\x85\x05\xff\x87\xac\xfd\x71\xf9\xc7\x5f\x02\x0c\ +\x00\x00\x31\x44\x70\x94\xe4\x6d\xa8\x00\x00\x00\x00\x49\x45\x4e\ +\x44\xae\x42\x60\x82\ +\x00\x00\x02\xaf\ +\x89\ +\x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\ +\x00\x00\x10\x00\x00\x00\x10\x08\x06\x00\x00\x00\x1f\xf3\xff\x61\ +\x00\x00\x00\x19\x74\x45\x58\x74\x53\x6f\x66\x74\x77\x61\x72\x65\ +\x00\x41\x64\x6f\x62\x65\x20\x49\x6d\x61\x67\x65\x52\x65\x61\x64\ +\x79\x71\xc9\x65\x3c\x00\x00\x02\x51\x49\x44\x41\x54\x78\xda\x9c\ +\x53\xcf\x6b\x13\x51\x10\xfe\x36\xfb\x62\x8d\x69\x48\x62\x9b\x18\ +\x8d\xab\x5d\xd3\x84\xa2\x10\xbd\x48\x0f\x62\xa9\xd8\x93\xe0\x49\ +\x0f\xa5\x20\x52\xb0\xe0\x7f\xe0\x45\x3c\xf5\xa6\x77\x7b\xe8\x55\ +\x28\x41\x3d\x78\x28\x7a\xf0\x47\x51\xa4\xbd\x58\x0d\xa8\x60\x5a\ +\x13\x51\xd0\x43\x89\x69\xf3\x63\xb3\xc9\xee\x3e\x67\x9e\xd9\xa2\ +\x08\x4a\x1d\x18\xde\xdb\x99\xf9\xbe\xf7\xcd\x7b\xb3\xda\x8d\x85\ +\x05\xb0\x69\x9a\x76\x9e\x96\xfd\xf8\xbb\x3d\x71\x1c\x67\xad\xdb\ +\xe9\xe0\xdc\xf3\x19\x48\x0a\x08\xd7\x75\xfd\xe4\x81\xeb\x93\x93\ +\x73\x0e\x7d\x73\xc2\x95\x12\x5d\xda\x77\x3d\x4f\xed\x2b\x95\x0a\ +\x1e\x15\x8b\x57\xa5\x94\x1a\xa5\x4b\x3e\x28\x30\xf1\xf8\x32\xcc\ +\xb5\x7b\x20\x56\x4d\x72\xb1\xe3\xc0\xe9\x76\xe1\xf6\xbc\xd3\x6e\ +\xc3\x6a\x36\xd1\x68\x34\x30\x3b\x35\x35\x47\xb9\xb3\x44\x92\xf5\ +\x09\x04\xfb\xf0\xa7\x07\x57\x5a\x32\x78\x41\xd3\x2e\xe1\xc5\xea\ +\x2a\x3c\x22\x8a\xc5\x62\x68\xb5\x5a\x38\x3e\x32\xa2\x0a\xab\xd5\ +\x2a\xee\x2c\x2e\x22\x9f\x4c\xde\x5e\x29\xcc\x3e\x85\x8e\x02\x85\ +\xe7\x05\xa9\x1b\x44\x40\xcf\x65\x8f\x9e\x9c\x60\x6d\x99\x4c\x06\ +\x74\x82\x22\x89\xc7\xe3\x08\xea\xba\x22\x38\x35\x3a\x8a\x0e\xa9\ +\x0b\x85\xc3\x18\x68\x5d\x3c\x23\x1f\xbe\x7a\x2d\x3d\x77\x50\xb8\ +\x12\xc6\x5e\xe3\xd8\xf0\x26\x5d\x4c\x40\xd3\x54\xaf\xd1\x68\x54\ +\x9d\xc8\x24\x1f\x89\x8c\x09\x39\xc6\x8a\x4e\xe4\xf3\xb0\x6d\x1b\ +\x49\xc2\x54\x2b\x45\x43\xb8\x1e\x0e\xed\x8e\x26\xf7\x59\x56\x1b\ +\xbf\x2a\xe0\xd3\x7d\x25\xb2\x47\xe2\x2b\xe2\x5a\xc6\x30\x96\x14\ +\xc8\xa1\x60\x38\x16\x6a\x12\x3b\x3d\x25\xca\xe5\xf2\x36\xc0\x57\ +\xc2\x2b\x7f\xb3\x82\xc3\xa9\x14\xb8\x96\x31\x8c\x15\x8e\x87\x5c\ +\x24\x65\x26\xac\xf7\x75\x94\x0b\xd7\x30\x40\xb7\xde\x97\x1b\x47\ +\x5f\x76\xec\x37\x25\xf6\x87\x25\x04\x4b\x4b\xf8\xba\xbe\x07\x56\ +\xdb\x46\xc4\x34\x13\x8c\xe5\x16\x44\x24\x91\x4e\x4d\x27\x7e\x3e\ +\x0b\x4f\xd2\xca\xf2\x7d\x38\xc2\x50\x40\x7e\x0d\x6e\x63\x73\xf9\ +\x2e\x4e\x8f\x8d\xab\x9a\x69\x53\x2d\x29\xc6\xb2\x02\xb1\xb5\xb1\ +\x41\x7d\x59\x2a\xda\x4f\x00\x23\x9d\xc6\x97\x67\x37\x15\x41\x93\ +\x62\x3c\x58\xe6\x90\x89\x66\xbd\x8e\x46\xad\xa6\xea\x42\xa1\x10\ +\x1c\x45\xe0\x4a\xe1\xf0\xf0\x90\xb3\xd5\x88\xcc\xc8\x66\x71\xd0\ +\x3c\xf2\xc7\x1c\x7f\x2e\x6d\x0f\xa0\xaa\x67\xac\xe8\x7a\x08\x76\ +\x3a\x34\x71\xe4\xbe\xad\xbf\x7d\x87\x7f\x99\xae\x0b\x30\x56\x34\ +\x6c\xf4\x4b\xc9\x5a\x74\xec\xc4\x18\xc3\x58\xf1\xe6\x9b\xac\x6c\ +\xcd\xdf\x7a\x89\xff\xb0\xf2\x77\x54\x78\x76\x76\x91\xc7\x7a\xff\ +\xc5\x4e\x8c\x2f\xad\xf6\x43\x80\x01\x00\xc1\x52\x4e\xcc\x97\x5f\ +\x6c\x5a\x00\x00\x00\x00\x49\x45\x4e\x44\xae\x42\x60\x82\ +\x00\x00\x02\xcc\ +\x89\ +\x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\ +\x00\x00\x10\x00\x00\x00\x10\x08\x06\x00\x00\x01\x68\xf4\xcf\xf7\ +\x00\x00\x00\x06\x62\x4b\x47\x44\x00\xff\x00\xff\x00\xff\xa0\xbd\ +\xa7\x93\x00\x00\x00\x09\x70\x48\x59\x73\x00\x00\x0b\x13\x00\x00\ +\x0b\x13\x01\x00\x9a\x9c\x18\x00\x00\x02\x6c\x49\x44\x41\x54\x38\ +\xcb\x4d\xd2\xbd\x6b\xdd\x65\x14\x07\xf0\xcf\xaf\xb9\x49\x9a\x10\ +\x69\x08\x06\x6a\x2c\x82\xf4\x25\x2e\xed\x50\x87\x4e\x85\x2e\x42\ +\xb2\x34\xe0\xe4\x58\x10\xea\x58\x69\x57\x47\x57\x83\xd2\x49\xc1\ +\xad\xfe\x01\xba\x28\xb8\xa8\x93\x83\xa0\x4b\x41\x89\x10\x1b\x43\ +\x20\x37\x37\xf7\xe6\xbe\xfe\xee\xef\x2d\xc7\xe1\xb9\x6a\x1f\x38\ +\x1c\x9e\xf3\x7c\xbf\xe7\x7c\x9f\x73\x4e\x16\xb7\x6e\x71\xf5\x6a\ +\x2d\xae\x5f\x2f\x82\x10\x0f\x1e\x6c\xc6\x8d\x1b\xc4\xc5\x8b\xf1\ +\x17\x21\xee\xdf\xbf\x19\x77\xee\x44\xac\xad\x45\xcc\xcf\x47\x36\ +\xc4\x11\x91\xe3\x67\x64\xb1\xb3\xc3\xa5\x4b\xbf\xd8\xdf\xff\xd1\ +\xf3\xe7\x4f\xc4\xce\x4e\xc4\x95\x2b\x11\xab\xab\x31\xa0\x16\x1b\ +\x1b\x11\x44\x45\xfc\x40\x64\x07\xc4\x18\x2f\xb0\xc7\x6e\x16\xdb\ +\xdb\xac\xaf\x7f\xab\x69\xb6\x74\x3a\x9c\x9d\x31\x1e\x27\xdf\xed\ +\x2e\xb6\x8c\xc7\x7b\x8e\x8f\xaf\x39\x3c\xe4\xf4\x94\xf3\x73\xd0\ +\x8f\xd0\xa6\x10\xcb\xcb\x4f\x83\x28\x67\x56\x10\x6d\xe2\x27\xe2\ +\x19\x91\x75\x92\x6e\x6d\x86\x7d\x56\x06\xe8\xa2\xe6\x83\xd7\xf9\ +\x22\x8b\x7b\xf7\xd8\xd8\x60\x71\xf1\x13\x79\xfe\xc8\xd9\xd9\x6f\ +\xda\xed\xf7\xb4\xdb\x7f\xea\xf5\xb4\x5c\xbe\xbc\x60\x7e\xbe\xd0\ +\xef\xd3\xe9\x30\x1a\xbd\x6d\x30\xd8\x33\x99\x7c\xa7\x28\xb6\x5b\ +\xca\x72\xa2\xdb\xe5\xe0\x20\x89\xac\x6b\xea\x5a\x33\x1c\x6e\x9d\ +\xb1\xd9\x72\x7c\x3c\xa7\xdd\xe6\xf0\x90\xe9\x14\x54\x11\x4e\xd0\ +\xe1\xab\x96\xa3\x23\xfa\xfd\xf4\x18\x21\x90\xe3\x24\x89\x7f\x23\ +\x8b\x56\x2b\x9a\xba\x56\x63\x0e\x25\xfe\xc6\xef\x18\xf0\x59\xd6\ +\xe6\xd3\x21\x8f\x4a\x34\x29\xe8\x45\xfa\xb6\x55\xb2\xd6\x84\x0f\ +\x8f\xd9\xef\x26\xa0\x5e\x02\x8d\x96\x79\xe5\x35\x64\x71\xf7\x2e\ +\x6b\x6b\xac\xac\xb0\xb0\xf0\x58\x96\x7d\xac\xae\x97\x14\x45\xd2\ +\x35\x9d\x52\x14\xe4\x39\x93\x49\x6e\x32\xf9\xc8\x64\xb2\x2b\xcf\ +\x29\xcb\xd9\x42\x2c\x2d\x7d\xee\xc2\x85\x87\xaa\x2a\x01\x87\x43\ +\x46\xa3\x44\x2e\x4b\x9a\x26\x59\x59\xa6\x58\x9e\x8b\xe9\x74\xb7\ +\xe2\x49\x4b\x51\x3c\x55\x96\x0f\x4d\xa7\x89\xd8\xeb\xa5\x4d\xc8\ +\x73\xaa\x8a\x08\x20\xcb\xa8\x6b\x65\x84\x1c\x13\x1e\x17\xcc\x65\ +\x71\xfb\x76\xa1\xae\x17\xe4\x79\x5a\xa3\xe1\x30\x91\x9b\xe6\x7f\ +\x32\xff\xb5\x77\x34\x6b\xd4\x20\x25\x39\x69\x39\x3a\x3a\x50\x55\ +\xd7\x54\x55\xaa\x58\x96\xa2\x69\xbc\x7c\xce\x67\xed\x1f\xa6\xe1\ +\xe9\xe2\x0c\x05\x07\x59\xc3\xcd\x29\xbf\x56\xcc\xd5\xb3\x4a\x90\ +\xcd\x7c\x83\xe9\x4b\xe4\x53\xf4\x53\xc2\x66\x81\xb7\xb2\x6e\x92\ +\xb5\x30\xe6\xeb\x9c\xad\x7f\xe7\xd9\xa0\x4a\x55\xe4\x33\xc9\xa3\ +\xd9\x1d\xdf\x2c\xf3\xee\xab\x34\x59\x0f\xe3\x19\xa0\x9f\xfc\x9b\ +\x23\xde\x1f\xf1\x4e\xce\x66\x91\x12\xfd\xd1\xf0\xfd\x1c\x5f\x2e\ +\xb1\x7f\x09\xeb\x33\xfb\x07\x6a\x4f\x76\xe7\x35\x05\x41\x4b\x00\ +\x00\x00\x00\x49\x45\x4e\x44\xae\x42\x60\x82\ +\x00\x00\x02\x24\ +\x89\ +\x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\ +\x00\x00\x10\x00\x00\x00\x10\x08\x06\x00\x00\x00\x1f\xf3\xff\x61\ +\x00\x00\x00\x19\x74\x45\x58\x74\x53\x6f\x66\x74\x77\x61\x72\x65\ +\x00\x41\x64\x6f\x62\x65\x20\x49\x6d\x61\x67\x65\x52\x65\x61\x64\ +\x79\x71\xc9\x65\x3c\x00\x00\x01\xc6\x49\x44\x41\x54\x78\xda\x8c\ +\x53\x3d\x4b\x03\x41\x10\x7d\xd9\x3b\xf5\x44\x45\x8b\x44\x34\xa2\ +\xc4\x42\x49\x2b\xe8\xcf\xb0\xf3\x07\x28\xd6\x82\x9d\x20\x58\x88\ +\x0a\x36\x82\x8d\x95\xa0\x58\x0b\x16\xda\x8b\xd8\x08\x09\x44\x0b\ +\x8b\x54\x22\x7e\x44\x10\x41\x42\xce\xe4\x2e\xb7\x1f\xce\x9c\xe6\ +\xcb\x5c\xc0\xe5\x06\x6e\xdf\xbe\xf7\x66\x76\x76\x37\x96\xdd\x05\ +\x62\xc0\x12\x80\x14\xfe\x3f\x1e\x0d\x70\x3c\xbb\x66\x60\x1b\xfa\ +\xa3\x6f\x72\x6e\xf5\x62\x03\x5a\x03\x4a\x75\x96\x59\x16\x20\x04\ +\xb2\xfb\xf3\x5b\x35\x48\x84\x06\x06\xe2\x25\x93\x01\x1b\x18\x29\ +\x61\xaa\x7e\x7b\x10\xce\xeb\xcc\x63\x3e\xeb\x42\x03\xc5\x49\x35\ +\x44\x6f\x3c\x8e\xfb\xcb\x4b\xca\x22\x60\x44\x7b\x30\xce\xeb\xcc\ +\x63\x3e\xeb\x78\xd8\xfa\xc7\xc9\x1a\x1a\x4e\xa0\xe2\x96\x70\x73\ +\x7e\x51\xaf\xd8\xf3\x3c\x38\x8e\x53\x9f\x4f\x4c\x4f\x81\x79\xa4\ +\xb1\x6a\x98\xfd\xeb\x24\x0c\xed\x7d\x38\x39\x1a\x46\x08\x74\x75\ +\xe3\x29\x9f\xc7\x44\x3a\x0d\x1d\x54\xeb\x26\xcc\xe3\x0a\xfe\x1a\ +\x58\x5a\x05\x50\x32\x68\x34\x4c\xc4\x30\xd0\xd7\x87\x28\x9c\x34\ +\x56\xbb\x81\x54\xd0\xdc\xa8\xdf\x11\x13\x16\x1d\x08\x63\x11\x78\ +\x94\x81\x51\x92\xb2\x35\x88\x42\x59\x90\x94\x39\x0a\xef\x50\x41\ +\x00\xdd\x54\xaa\x1f\x28\x2c\xf6\x6c\xa2\xfa\xa6\xa8\x99\x92\x22\ +\x80\xef\x2b\x64\xa6\x8f\x5a\x0d\xa4\xaa\x19\x48\xda\x6b\x23\x53\ +\xd9\xf5\x70\x32\x53\x6e\xba\x45\x22\x0c\xf7\xae\x04\xd2\x44\x54\ +\x10\x96\xda\xa8\xc0\xfd\x2c\xc2\xae\x54\x90\xcb\xe5\x90\x48\x24\ +\xc2\x7e\xa4\x52\x29\xe8\x62\xa9\x53\x0f\xa8\x59\x4d\xd7\xd8\x25\ +\x62\x77\xb9\x8c\x34\x1d\x63\xbd\x2a\x9a\xeb\xd2\x57\xab\xc1\xdd\ +\x23\x90\x4e\xc2\x79\x79\x7a\xa5\x9b\xaa\x9a\x7a\xe0\xe3\xe3\x74\ +\xa5\xed\x39\x0c\xc6\x87\xe0\x55\xe1\xe4\x0b\xc0\x02\x1b\xec\x9c\ +\x61\xf0\x60\x19\xfd\xe3\xe3\xc9\xd6\xf3\x1e\x1b\x89\x7e\x4f\x76\ +\x17\x6e\xaf\xd1\xcf\xba\x6d\xa0\x68\xb3\xe9\xfd\x33\x0a\x87\x7b\ +\xeb\x57\xff\x7d\xcb\x0f\xef\x28\xb0\x8e\xa2\xf8\x2d\xc0\x00\x14\ +\x2c\x1a\x71\xde\xeb\xd2\x54\x00\x00\x00\x00\x49\x45\x4e\x44\xae\ +\x42\x60\x82\ +\x00\x00\x06\xe3\ +\x89\ +\x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\ +\x00\x00\x90\x00\x00\x00\x90\x08\x06\x00\x00\x00\xe7\x46\xe2\xb8\ +\x00\x00\x00\x04\x73\x42\x49\x54\x08\x08\x08\x08\x7c\x08\x64\x88\ +\x00\x00\x00\x09\x70\x48\x59\x73\x00\x00\x0e\xc4\x00\x00\x0e\xc4\ +\x01\x95\x2b\x0e\x1b\x00\x00\x06\x85\x49\x44\x41\x54\x78\x9c\xed\ +\x9d\x4b\x72\xe3\x38\x10\x44\xc1\x8e\x5e\xf8\x3c\x3a\x8e\x0f\xa8\ +\xe3\xf8\x3c\xda\x79\x16\x0e\x4c\xd3\x10\x89\x6f\x7d\xb2\x0a\xf5\ +\x96\x0e\x59\x2c\x24\x12\x29\x90\x20\xc1\xe3\xf3\xe3\xeb\x3b\xa5\ +\x94\x9e\xaf\xc7\x91\x0c\x92\xeb\xb7\x8c\x65\xed\x8f\xb2\x03\x2c\ +\x37\x46\xbb\x86\x59\xac\x69\x7e\xd6\xfa\x28\xff\x90\xb1\xd6\xa8\ +\x8c\x45\x23\x59\xd1\xfa\x4a\xdb\x5b\x03\x65\xac\x34\xae\xc4\x92\ +\x91\xd0\x35\xbe\xd3\xf2\xf9\x7a\x1c\x47\xeb\x43\xe7\x0f\x53\x17\ +\x26\x81\x05\x23\xa1\x6a\xdb\xe3\x89\x6e\x03\x9d\xff\x69\xb5\x30\ +\x0d\x90\x8d\x84\xa6\x69\x8f\x56\xb9\xe6\x5f\x85\x8f\x88\x8c\xd6\ +\xe8\x5e\x10\x8d\x84\xa2\xe5\x4c\xff\x4f\x1b\xa8\xfc\x22\x6b\x20\ +\x19\x49\x5b\xc3\x51\x2d\xce\xf5\xbe\x15\x3e\x2b\xac\xb6\x08\xb3\ +\x20\x18\x49\x4b\x3b\x8a\xbe\x26\x33\xd0\xd5\x97\x5b\x42\xd3\x48\ +\xd2\x9a\xad\xb4\xb5\xac\xf5\xb2\x70\x0a\x31\xc3\x48\xfd\x48\x69\ +\xc5\xd1\xaf\x6c\x06\xba\x3b\xa0\x15\x24\x8d\xc4\xad\x11\x55\x5b\ +\xae\xea\xbc\x2d\x9c\x5a\x40\x8b\x46\x92\x32\x11\x97\x36\x12\x7d\ +\xf8\x97\xf2\x00\x35\x2c\x2d\xda\x5e\xad\x0f\x22\x4c\xb6\x7b\xe1\ +\xa8\xf5\xae\xdf\xaa\x9d\xc9\x29\x1a\xa2\x91\x6a\x97\xec\x5b\x9f\ +\x59\x81\x4a\x0b\x8d\xfe\x12\x4b\xa0\x12\xa4\x44\x9a\xb9\x80\x86\ +\x94\x48\xdc\xb5\xd4\xfa\xa8\xd9\x79\xd6\xe7\x01\x35\x28\x96\x6f\ +\x34\xcf\x58\x11\xfa\x46\x2d\x81\x4a\x24\x13\x89\xe3\x2c\x53\x32\ +\x91\x90\xce\x10\xbb\x3a\xcb\xcb\xb5\x11\x89\xab\xec\x9c\xcb\x41\ +\x88\xfd\x00\x93\x40\x25\x94\x89\xa4\x31\x62\x29\x8f\xa9\x35\xdf\ +\xea\xd1\x9e\x75\x64\x51\x32\x63\x24\xce\x0b\x68\x94\x35\xdc\x7d\ +\xbf\x05\xcd\x61\x13\xa8\x64\x24\x91\xb4\x85\x3f\x33\x93\x48\x08\ +\xf5\xf7\x0e\x9a\xa1\x91\x85\xd0\xb0\xcc\x55\x03\xb9\xea\xa3\x9c\ +\x8f\xd5\xee\x3f\x47\xd7\xf7\x0a\xb3\x06\xca\x48\x5c\x25\x46\x9a\ +\xd0\x4b\x30\xd2\xde\x3f\x5c\x5f\x2c\x05\x72\x47\xd4\x40\xd4\x72\ +\x86\x21\x03\xa1\x62\xad\x33\x3e\x3f\xbe\xbe\x51\x8d\x3f\xaa\xe5\ +\xb0\x81\x50\x3b\xeb\xf9\x7a\x1c\xa8\xb5\x65\x90\x8d\x33\x8b\x99\ +\xb3\xb0\x5e\x10\x27\xa4\x48\xb5\xd4\x98\x19\x80\x53\x3f\x61\xe8\ +\x23\x3d\x25\x8c\x44\xf2\x98\x38\x25\xee\x12\xa8\xc4\xfb\x5a\x15\ +\x15\xb3\x83\x6d\x7a\x12\xad\x3d\xba\x47\x91\x48\xa4\x1d\x12\xa7\ +\xc4\x7d\x02\x95\x78\x5a\xab\xa2\x62\x65\x60\x2d\x9d\xc6\x5b\x4b\ +\xa1\x33\x14\x89\xb4\x63\xe2\x94\x6c\x97\x40\x25\x56\xd7\xaa\xa8\ +\x58\x1d\x44\xcb\x17\x12\x2d\xa7\xd0\x99\x9e\x44\x8a\xc4\x79\x07\ +\xfe\x66\xee\x1e\x76\x5b\xab\xa2\x82\x42\x37\x92\xa5\x0c\x2f\x29\ +\x74\xc6\x63\x9b\x38\xd8\x7e\x0e\x74\x45\xa4\x4f\x3f\x64\x8b\xa9\ +\x1e\x46\x6c\xcc\x71\xc6\x89\x04\x4a\x7b\x24\xce\x19\xca\xc1\x4e\ +\x7a\x3b\x87\xb5\x14\x8a\xc4\x59\x67\xcb\x04\xda\xd9\x34\xd4\x83\ +\x9c\xfc\x86\x32\xe4\x14\x8a\xc4\xa1\x67\x8b\x04\x0a\xd3\xfc\xc0\ +\x31\xb8\x59\x6e\x69\x45\x49\xa1\x48\x1c\x7e\x5c\x26\x50\x98\xe6\ +\x1d\xae\x41\xcd\x76\x53\xbd\xd6\x6e\x1b\x61\x1e\x59\x4c\xec\xcd\ +\x17\xac\xc1\x39\x98\xff\x46\x27\x07\x2b\xb8\x9c\x03\x59\xc3\xca\ +\x26\x9b\x57\xdf\xcf\xfe\x60\x21\xca\x19\x19\x32\x12\x1d\xcd\xf5\ +\xdd\xac\x06\x0a\xf3\xe8\x21\x65\x4a\x91\x47\x9b\xc3\x48\x6d\xac\ +\xa6\x90\xab\xd3\xf8\xe0\x07\x49\x33\x8a\x6d\xae\x10\x86\x6a\x63\ +\x31\x85\x5c\x2f\x65\xec\x88\xb4\x09\x45\xb7\x77\x09\x63\xb5\xb1\ +\x96\x42\x5b\xdd\xce\xe1\x1d\x0d\xf3\x89\x6f\x30\x15\x06\x6b\x63\ +\x29\x85\xb6\xbe\xa5\xd5\x13\x5a\xa6\x53\xd9\xe2\x2e\x8c\xd6\xc6\ +\x4a\x0a\xc5\x63\x3d\x0e\xd0\x34\x9b\xda\x26\x9b\x61\xb8\x36\x16\ +\x52\x28\x1e\x6d\x36\x8e\xb6\xc9\x54\xb7\xf9\x0d\xe3\xb5\xd1\x36\ +\x48\x8b\xd8\xde\xc5\x30\x08\xe6\x52\xdf\x68\x3c\x0c\xd8\x06\xc1\ +\x28\x77\x6c\xbb\xc5\x9d\x75\x50\x4c\xa5\x9e\x40\x29\x85\x11\x7b\ +\x40\x31\x4c\xc9\x36\xdb\xfc\x7a\x02\xc9\x4c\x10\x09\x94\x52\x18\ +\xb2\x07\x24\xe3\x64\xa6\xde\xb5\x65\xf5\x29\x82\x80\x1e\xf6\x97\ +\xb5\x05\xbe\x89\xe7\xc2\x0c\x82\xf4\x0b\x00\xf7\xbe\xb0\x98\x0b\ +\xb5\x41\xfa\xd5\x80\x7a\xe5\x65\x98\x47\x0f\xd1\xd3\xf8\x30\x92\ +\x3e\x28\x29\xd4\x6d\xa0\x30\x8d\x5f\x54\x96\x32\xc2\x50\xfa\x20\ +\xa4\x50\x97\x81\xc2\x2c\x7e\x51\xbd\x9d\x23\x8c\xa5\x8f\x76\x0a\ +\x35\x0d\x14\x26\xf1\x0b\xc4\x2d\xad\x61\x30\x7d\x34\x53\xa8\x6a\ +\xa0\x30\x87\x5f\xa0\x1e\xeb\x09\xa3\xe9\xa3\x95\x42\xb7\x06\x0a\ +\x53\xf8\x05\xf2\xd1\xe6\x30\x9c\x3e\x1a\x29\x74\x69\xa0\x30\x83\ +\x5f\xa0\xb7\x77\x09\xe3\xe9\x23\x9d\x42\x6f\x06\x0a\x13\xf8\xc5\ +\xc4\x16\x77\x61\x40\x7d\x24\x53\xe8\x97\x81\xa2\xf3\xfd\x62\x6a\ +\x9b\xdf\x30\xa2\x3e\x52\x29\xf4\xbf\x81\xa2\xd3\xfd\x62\xf2\x55\ +\x07\x61\x48\x7d\x24\x52\xe8\x4f\x4a\xd1\xd9\x9e\xe1\x36\xd1\xf1\ +\xf9\xf1\xf5\xcd\xd9\xc1\xda\xf7\xab\x04\xbc\xc4\x1b\x0b\x15\x79\ +\xbe\x1e\x22\x0f\x76\x72\x06\x04\xcc\xb3\xf1\x3b\xf1\x7c\x3d\x0e\ +\xc9\x9f\x75\x4e\x93\xb2\x3d\x99\x1a\xe9\xf3\x8e\xc7\xb9\x60\x24\ +\x90\x00\xd2\x89\x73\x05\xd7\x80\x66\x49\xa0\x48\x9f\x1f\xb4\x4d\ +\x23\x41\x24\x10\x03\x08\x89\x73\x05\xc7\xc0\x26\x4f\xa0\x9d\xd3\ +\x07\xd1\x34\xdc\x44\x02\x11\x80\x9a\x38\x57\x50\x0f\x70\xd2\x04\ +\xda\x2d\x7d\xac\x98\x86\x93\x48\xa0\x09\x2c\x25\xce\x15\x94\x03\ +\x9d\x2c\x81\x76\x48\x1f\xcb\xa6\xe1\x22\x12\xa8\x13\x6f\xe6\x81\ +\x7a\xb0\xd0\x6b\xfa\x9c\x4d\xf3\xf9\xf1\xf5\xed\xb5\x9d\x2b\x44\ +\x02\x5d\x50\x9b\xe3\x78\x32\x12\x45\x3b\x96\xe7\x40\x5e\xc4\x4c\ +\x69\xec\x67\x2a\xb7\xdb\xdb\x4f\xdb\x28\x91\x40\x69\xed\xac\xca\ +\x7a\x22\xad\xd6\xbe\x94\x40\x96\x85\x4b\x89\x36\x3d\x76\x4d\xa4\ +\x2d\x13\x88\xf3\x3a\x8e\xc5\x44\x5a\xa9\x77\x3a\x81\xac\x89\x94\ +\x92\x6c\x3a\xec\x92\x48\x5b\x24\x90\xe6\x95\x63\x2b\x89\x34\x5b\ +\xe3\x54\x02\x59\x10\x24\x25\xac\xd1\xef\x35\x91\x5c\x26\x10\xf2\ +\x5a\x15\x72\x22\xcd\xd4\x35\x9c\x40\xa8\x8d\x4f\xc9\xd6\xe8\x46\ +\xd6\x71\x04\x37\x09\x64\xc9\x3c\xc8\x8c\x1a\x7b\xc8\x40\x68\xa3\ +\xc6\xfa\x5a\x55\xfe\xa9\xb5\x6c\xfe\xa1\xc2\x51\x3a\xa8\x34\x4e\ +\xeb\x33\x2b\x70\xb4\xb9\x56\x1b\xa2\xc6\x35\xba\xe7\x40\x08\x0d\ +\xb3\xbe\x56\xd5\x53\x4b\xfe\x0c\x82\xde\x3d\x0c\x77\x88\x06\x14\ +\x23\x76\x65\xad\x6b\xe6\xff\x28\x8e\x4d\x75\xfc\x59\x7a\xea\xee\ +\x4a\x20\xad\x46\x58\x5f\xab\xa2\x38\x16\x7a\x22\x75\x35\x50\xba\ +\xf8\x99\x9f\x2a\xae\x63\x20\xbd\x16\x3d\x25\xbc\xbe\x68\x26\x90\ +\x64\xc1\xd6\xd7\xaa\x24\xea\x47\x4b\xa4\x66\x83\xd1\xb7\x1f\xa1\ +\xaa\xaf\x76\x07\xe2\xec\xff\x4a\xa0\xdd\x3f\xd5\x04\xe2\x2e\x0e\ +\xe9\x0c\x69\x26\x91\x10\xea\xd7\x4e\xa4\xaa\x00\x5c\x45\x71\x4c\ +\x8e\xa9\xa9\x75\x0c\x82\x71\xee\x90\xee\x33\xd1\x0b\x5a\x1c\xc2\ +\x7b\x9d\xa3\xad\x42\xad\x8b\xaa\x81\x3c\x9c\x95\x58\x32\xcf\x19\ +\xee\x7e\x9c\x9e\x38\xce\x1e\x90\x1a\xb4\xd3\x5a\x54\xb8\x2e\x88\ +\xb2\x18\xc8\xcb\xfe\x7f\x35\x76\x35\x52\xd9\xee\x37\x11\x56\x0e\ +\xa0\x21\xaa\xf6\xf5\x90\xdd\x8c\xc4\x62\x20\xef\xd7\x41\x7a\xd8\ +\xc9\x48\xe7\xb6\xfe\x6a\xf4\xe8\x97\x21\x88\x86\x62\xa0\x0c\x82\ +\x26\x33\x8c\xe8\xb8\x6c\x20\x24\x91\xd0\x0c\x94\x41\xd2\x68\x84\ +\x51\x0f\x34\x6f\xcc\xba\xfa\x27\x24\x50\x0d\x94\x41\xd4\xac\x87\ +\x96\xae\x43\x06\x42\x16\x01\xdd\x40\x19\x64\x0d\x6b\xb4\x7c\x51\ +\x5d\x47\xb1\xd0\x68\x2b\x06\xca\x58\xd0\xf4\x8a\xbb\x25\x9d\x4b\ +\x03\x59\x6a\xa4\x35\x03\x65\x2c\x69\x7c\xa6\xd4\xfb\xd7\xdb\x62\ +\x2c\x36\xca\xaa\x81\x32\x16\x35\x4f\xe9\x9f\xee\xff\x01\x8b\x65\ +\xc9\x17\x1c\x9e\xef\x70\x00\x00\x00\x00\x49\x45\x4e\x44\xae\x42\ +\x60\x82\ +\x00\x00\x02\xf0\ +\x89\ +\x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\ +\x00\x00\x10\x00\x00\x00\x10\x08\x06\x00\x00\x00\x1f\xf3\xff\x61\ +\x00\x00\x00\x19\x74\x45\x58\x74\x53\x6f\x66\x74\x77\x61\x72\x65\ +\x00\x41\x64\x6f\x62\x65\x20\x49\x6d\x61\x67\x65\x52\x65\x61\x64\ +\x79\x71\xc9\x65\x3c\x00\x00\x02\x92\x49\x44\x41\x54\x78\xda\x84\ +\x53\x5f\x48\x53\x61\x14\x3f\xf7\xee\x6e\x22\x0b\x4c\x6b\xac\x60\ +\x6a\xb5\x39\xdd\x06\x36\xd9\x70\x6b\xb8\x31\xd4\xb2\x35\x82\x3d\ +\x4c\x50\x50\x08\x1f\x24\x84\x24\x56\xea\xc3\xdc\x2a\x63\x36\x1d\ +\x4d\xa1\xb0\xf0\xc1\x17\x1f\x7c\xb0\x3f\x0f\x51\x96\x12\xe8\xa6\ +\x84\xa6\xeb\x45\xa9\x84\x22\x25\x08\xb7\x97\xec\x45\xe6\xee\xdd\ +\xed\x7c\x97\xb5\x36\x1a\x74\xe0\xc7\xe1\xfb\x9d\xf3\xfb\x9d\xf3\ +\xdd\x8f\x4b\x41\x4e\xd0\xc5\xc5\x20\xa9\xa8\x80\xc3\xdd\x5d\x48\ +\x1f\x1c\x40\x75\x43\x03\x68\x6d\x36\xa0\x45\x22\xa0\x69\x5a\x85\ +\x2d\x8d\x90\x1f\x3f\x18\x28\x10\x4a\xa3\x11\xaa\x4d\x26\x41\x98\ +\x89\xaa\x74\x3a\xdd\x38\x3b\x34\xf4\xf8\x0f\x41\x21\xdc\x7e\xff\ +\xd5\x3c\x83\x53\x7a\xbd\x20\x16\x31\x79\x74\x55\x9a\xe3\x9a\x66\ +\x03\x81\x47\xd1\xf5\x75\xf8\xb0\xb5\x05\x75\x3a\x1d\x58\xb1\x0f\ +\x79\x4a\xe8\x2c\xaf\xab\x83\xd3\x48\x30\xcc\x3f\x0b\x55\x71\x1c\ +\xd7\xfc\x34\x18\x9c\xc0\x0c\x89\xfd\x7d\x28\x2d\x2b\xa3\x30\xf3\ +\xa4\xc8\x11\x03\x53\x47\x07\x88\xc4\xe2\x42\x37\x51\xe3\x84\xf3\ +\xcf\x42\xa1\x87\xc9\x64\x12\x44\x78\x1d\x8d\x52\x09\xdf\xe3\x71\ +\xbe\x5c\x2e\x17\x1a\xb0\x4e\xd3\x50\x38\xd4\x2c\xc7\x5d\x78\x82\ +\xe2\x58\x2c\x06\x57\x70\xc8\xd6\xe6\x26\x9c\x51\x28\xc0\x6e\x30\ +\x80\xba\xb2\x12\x2e\x79\x3c\xd7\x70\x83\x85\x42\x06\xd5\x1c\xcb\ +\xb6\x3c\x0f\x87\x1f\xbc\x5f\x5b\x83\xbb\x7e\x3f\x1c\xe0\x8b\xdc\ +\x1a\x1c\x24\x2b\x0b\x1f\xd6\xd1\xdb\xdb\x8b\xd3\x17\xf0\x1e\xdb\ +\x4c\x01\xf1\xc5\x17\x13\x13\xe3\xef\x56\x56\xe0\x8e\xd7\x9b\x2d\ +\x04\x46\x47\x41\x52\x54\x04\x2d\x3d\x3d\xd7\x29\x8a\x9a\x47\xa3\ +\xcf\x84\xcf\x35\xa8\x61\x59\xd6\xf1\x6a\x72\x32\xbc\xbc\xb4\x04\ +\xbe\xfe\xfe\x6c\x61\x64\x6c\x0c\x8c\xf5\xf5\xd0\xdc\xdd\xed\x41\ +\xf1\x1b\x51\x46\x9c\x6b\xa0\x21\xe2\xd7\x53\x53\xf7\x23\x8b\x8b\ +\xe0\xef\xeb\xcb\x8a\xef\x85\xc3\x60\xb6\x58\xa0\xb1\xab\xeb\x06\ +\x8a\xe7\x50\xfc\x29\x77\x65\x62\xa0\xe1\x52\x29\xe7\xfc\xf4\x74\ +\x28\x8a\xe2\x00\xae\x2d\x91\x48\x84\xe2\xed\x60\x10\x2c\x56\x2b\ +\xd8\x3b\x3b\xfb\x80\x88\x19\x26\x2b\xfe\x8a\xdf\xe7\xcb\xea\x2a\ +\x30\x38\xf9\xf2\xdb\x99\x99\x91\xbd\x78\x1c\xc6\x87\x87\x41\x2a\ +\x95\x0a\x0d\x37\x7d\x3e\x41\x6c\x6b\x6f\x1f\xc0\xc9\x2f\x71\xf2\ +\x47\xc2\xef\xe0\xab\xec\x6c\x6c\xfc\x5d\x41\xdf\xda\xaa\x3d\xeb\ +\x76\x0f\xfc\xe4\x79\x7e\x2e\x12\xe1\x5d\x2e\x17\x3f\x1f\x8d\xf2\ +\xbf\xf0\x4c\x78\x52\x37\xb4\xb5\x81\xa2\xb6\xb6\xf0\x83\x9f\xd0\ +\x6a\xa1\xc6\xe9\xd4\xa9\x1d\x0e\x2f\x31\xd9\xde\xdb\xe3\xf7\x31\ +\x93\x33\xe1\x49\xfd\x7f\x41\xfe\x98\x92\x12\x95\xaa\x49\x6e\x36\ +\x0f\x11\x13\x92\x8f\xaa\x54\x76\xe4\x8f\x21\x4a\x49\x1d\x71\x04\ +\x51\x8c\x28\x42\x88\x33\x3a\x8a\xca\x1c\x4e\x22\x8e\x4b\x64\x32\ +\x85\x58\x26\x3b\x97\x4a\x24\x96\x0f\x13\x89\x6f\xc8\xa5\x10\x6c\ +\x26\x13\x1c\xe6\x70\x04\xdc\x6f\x01\x06\x00\x2d\x06\x04\x62\x7f\ +\xe8\x51\x71\x00\x00\x00\x00\x49\x45\x4e\x44\xae\x42\x60\x82\ +\x00\x00\x03\x37\ +\x89\ +\x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\ +\x00\x00\x10\x00\x00\x00\x10\x08\x06\x00\x00\x00\x1f\xf3\xff\x61\ +\x00\x00\x00\x19\x74\x45\x58\x74\x53\x6f\x66\x74\x77\x61\x72\x65\ +\x00\x41\x64\x6f\x62\x65\x20\x49\x6d\x61\x67\x65\x52\x65\x61\x64\ +\x79\x71\xc9\x65\x3c\x00\x00\x02\xd9\x49\x44\x41\x54\x78\xda\x6c\ +\x93\x5d\x48\x14\x51\x14\xc7\xff\xbb\xb3\xbb\xb6\xab\x95\x5b\x46\ +\xae\x99\xba\x7e\x40\x42\x46\x12\xa5\x4b\x49\xe8\x83\x60\x22\x11\ +\x3d\x94\xf6\xd6\x43\xe9\x6b\x81\x6f\x46\xd1\x43\x54\xd0\xf6\x49\ +\xf6\x24\x44\x81\x6f\x3d\x04\x8b\x08\x9a\x60\x59\xb6\x18\x65\xe9\ +\x22\xbb\xce\xb8\xea\xfa\xb1\x8b\xdf\xe3\x3a\xbb\xb3\xce\xed\xcc\ +\x75\x35\xad\x2e\x73\xb9\x77\xe6\x9c\xf3\xbf\xbf\x73\xee\x19\xc3\ +\xad\xf6\x76\x18\x0c\x06\x3e\x35\x4d\x83\x3e\x68\x5f\x47\x8b\x03\ +\xff\x1f\xdd\xeb\x89\x44\x40\x4d\x24\xc0\x18\x83\x69\xbb\xc5\x48\ +\x22\x09\x32\xd0\xc8\x6a\xa9\xaf\x6f\x9d\x8e\x44\x61\xb7\x5b\xb8\ +\xb0\x46\xce\x43\x81\x00\x3a\x07\x07\x1b\xf5\x33\x68\xfa\x79\xcc\ +\x0e\x6d\x12\x30\x0a\x02\x74\xf5\xc8\x9c\x02\x8a\x44\x24\xb2\x86\ +\x99\xd9\x28\xa6\x66\x56\x21\xcb\x32\xee\x36\x34\xb4\x92\xbd\x8a\ +\xbc\x8b\xf4\x90\x4d\x82\x3a\xc2\x71\x24\xf1\x21\x08\x42\xc5\xe4\ +\x62\x08\xcb\xb2\x8a\xe2\x9c\x83\xc8\xb0\x5a\xa1\xae\xaf\xe3\xc7\ +\xf0\x3c\xde\x7a\x3c\x28\xc9\xc8\x68\x7d\xd2\xde\x7e\x83\xdc\xdd\ +\x26\x8d\x0c\x9b\xc8\x23\x81\x15\xe4\xe6\x59\x77\x20\x0f\x07\xa7\ +\x91\x99\xbe\x1f\xa9\x29\x36\x9c\x38\xea\x42\x82\x6c\x66\xb3\x19\ +\xe5\xc1\xa0\xc2\x09\xd4\x8d\x9c\xe1\x17\x65\x3d\x03\x04\xc7\xd6\ +\x78\x71\xf4\x7a\xea\xc8\x35\xe5\xe5\xf8\xe8\xf3\xc1\xbe\xc7\x8c\ +\x0c\xbb\x8d\x93\x08\x24\x10\x8b\xc5\x0c\x1b\x02\xaa\xca\xc9\x8b\ +\x9c\xa9\xf0\x4b\xab\x70\x1e\xb6\xf0\x53\x74\xc7\x21\x71\x03\x59\ +\x1f\x83\xbf\xfc\xa8\xad\xa8\x24\x1b\xa3\xca\xa9\x88\x93\xc0\xc9\ +\xee\x6e\x12\x88\xc7\xb9\x80\x38\x1e\x85\xd1\x68\xc0\xd8\x64\x9c\ +\x13\xd0\x83\x92\xc2\xd3\x9c\x44\x7f\x5f\x54\xc7\x71\x60\x5f\x0a\ +\xdf\xc7\x07\x06\xd0\xe8\x76\x5f\xd3\xc2\xe1\x21\x23\xa1\x70\x9c\ +\xc2\x1c\x1b\x4f\xa1\x20\x67\x17\xf2\xf9\x4c\x41\x2e\xd1\x64\x67\ +\x0b\xc8\xcb\xb7\x52\x41\xe3\x98\x5f\x4a\x60\xc4\x1f\x42\xaf\xf7\ +\x3b\xca\x9a\x9a\x8e\x45\x80\x3b\x26\x42\xe1\x04\x52\x68\x8d\xdf\ +\xc0\x58\x28\xc6\x4f\xd7\x34\xb6\x45\xc2\x98\x02\x9b\x05\xb0\xa8\ +\xfd\x08\x8e\x2e\xa3\xe6\xfa\x55\xd4\xb9\x5c\x3d\x17\x19\xbb\x67\ +\x8a\x25\x05\x0a\xb2\x6d\x18\x9d\x8c\x22\x2f\xcb\xca\x6f\x80\x17\ +\x32\xb9\x1a\xa8\x37\xc4\x2e\x2f\x7c\xa1\xf7\xa8\x39\x75\x1c\xee\ +\xa7\x12\x66\x9d\xce\xaf\xdf\x04\xa1\xd3\xa4\x28\xca\x06\xc1\x54\ +\x92\x60\x4a\xd9\xca\x7b\x93\x24\xb6\xf8\x09\xc6\xb9\x37\x28\xab\ +\x3c\x8b\x8e\x8e\x7e\x5c\xba\xd0\x82\xd7\x7d\x3d\xe1\xb6\x89\x09\ +\xfc\x21\x38\x44\x04\xa1\x28\xf2\x75\x02\x60\x8b\x60\x61\xb6\x17\ +\xe2\xec\x73\x54\x53\xf0\xc3\x47\xee\xd1\x8e\x61\xc7\x87\xa1\x97\ +\xcd\x7e\x4d\x96\x97\xe5\x70\x38\xdf\x34\x23\x8a\xd8\xeb\x70\x18\ +\x44\x22\xd0\x5b\x5c\x9a\x56\x92\x79\x33\x44\x17\x46\x11\x09\x3c\ +\xc0\x19\x57\x29\x1e\x3f\x7b\x21\x05\x25\xa5\xb9\xcf\x23\x7d\x01\ +\xa4\xcd\xe6\xd7\x83\x90\x7e\xe4\xfc\xf9\x9b\x14\xc0\x88\x40\x5f\ +\x18\x9d\xcc\xd6\x89\xfd\xb3\xe7\x36\x63\xf2\x08\x7b\xd7\x56\xc5\ +\x6a\xaf\x94\xbe\x22\x5f\xfb\xdf\xbf\xa6\xde\x4d\xb9\xbb\x8b\x8b\ +\xab\x8d\x69\x69\xff\x18\x97\xbc\xde\xfb\x97\xcf\xa5\xfe\x1c\x5e\ +\xcd\xec\x93\xc2\x96\x81\x15\x9f\xaf\x8b\x3e\x8b\xdb\x7d\x7e\x0b\ +\x30\x00\x66\x8d\xa1\xfd\x87\x65\xe6\x27\x00\x00\x00\x00\x49\x45\ +\x4e\x44\xae\x42\x60\x82\ +\x00\x00\x02\xa1\ +\x89\ +\x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\ +\x00\x00\x10\x00\x00\x00\x10\x08\x06\x00\x00\x00\x1f\xf3\xff\x61\ +\x00\x00\x00\x19\x74\x45\x58\x74\x53\x6f\x66\x74\x77\x61\x72\x65\ +\x00\x41\x64\x6f\x62\x65\x20\x49\x6d\x61\x67\x65\x52\x65\x61\x64\ +\x79\x71\xc9\x65\x3c\x00\x00\x02\x43\x49\x44\x41\x54\x78\xda\xa4\ +\x93\xcf\x6b\x13\x51\x10\xc7\xbf\xfb\x23\x6b\xd3\x4d\x63\x53\x51\ +\x69\x93\x2d\x52\x62\xc5\xb6\x54\x08\x52\x28\x28\xb4\xa8\x08\xc5\ +\x5e\x84\xfe\x01\x3d\x49\x2f\x9e\x7a\x29\x96\x9e\xf2\x07\xf4\xa4\ +\x88\x17\x7f\x1c\x82\x22\x2a\xb9\x05\x5a\xe8\x49\x94\x0a\xb1\x58\ +\x24\x97\x68\x49\xa5\x31\x35\x90\xd2\xcd\x26\xd9\xcd\x6e\xd6\x99\ +\xad\x5b\x82\xb4\x50\x71\x60\xf6\xbd\x37\xcc\x7c\xdf\x7c\xde\xdb\ +\x27\xb8\xae\x8b\xff\x31\x79\x66\x69\xe9\x70\x21\x08\xc2\x34\x0d\ +\x7d\xff\x50\xbf\x23\xf3\xd7\x69\xb5\xfc\x40\xf4\x4d\x32\xf9\xe8\ +\x24\x3d\x09\xe4\x77\x17\x17\xe7\x64\xda\x15\x92\x28\xc2\x34\x4d\ +\x8e\x8b\x0e\x21\x7d\x2e\xba\xa8\x14\xbe\x42\x8b\x0f\x63\x20\xd2\ +\x3a\x52\x40\xa4\x1a\xbb\xd9\x14\xbd\x0e\x04\x5a\x28\x8a\x82\x9a\ +\x61\x88\x36\x09\xec\x15\xbe\xa0\x98\xdf\x84\x08\x07\x5a\xe2\x32\ +\x0a\xa5\x12\x82\xc1\x20\x6c\xdb\x46\x6f\x4f\x8f\x27\x20\xd1\xc6\ +\xbe\xc0\x34\x5c\xb7\x8f\x15\x03\x8a\x72\x6d\x65\x7d\x1d\xdb\xbb\ +\x3a\x8a\xe5\x32\x6a\xe1\x5f\xa8\x7e\x32\xd0\xa0\x42\xdf\xce\x77\ +\x77\xe3\x4a\x3c\x8e\x00\xe5\x37\x2d\x4b\x94\x6d\xc7\x39\x86\xfb\ +\xe6\x91\xdc\x4f\x33\x19\x9c\x56\x55\x5c\xd0\x34\x58\x96\x25\xc9\ +\xdc\x06\x73\x3f\xcb\xba\xf8\xfe\xfe\x35\xc6\x6f\xcf\xe0\xd6\xc0\ +\xf1\xdc\x6a\x67\x27\x62\xd1\x28\x6c\x3a\x78\xcb\x34\x45\x91\x05\ +\x98\xfb\xe7\x87\x57\xd8\x5c\x4d\x61\x63\xe5\x25\x9a\x8e\x83\xb5\ +\x6c\x16\x1b\x5b\x5b\xf8\x98\xcb\x79\x6b\x76\xce\x4b\x2e\x2f\xa7\ +\x9f\xa4\x52\xab\xcd\x03\x01\x49\x66\x0e\x56\x3b\xa3\x0d\xa1\x5a\ +\xad\xe2\x5c\xff\x10\x2c\x62\x8e\xc5\x62\xde\xae\x2a\xb5\x6b\xfd\ +\x39\x03\xe6\x56\x43\x21\x69\x6e\x76\xf6\x06\xd5\xc1\xd0\xf5\x80\ +\xcc\x1c\xac\xf6\xee\x6d\x1a\x86\x61\x60\x2d\x93\xc6\x9d\xeb\xf7\ +\x91\xa3\x9d\x7d\x2b\x45\x22\xa8\xd7\xeb\x18\x4f\x24\x50\xd3\xf5\ +\xca\xd9\x78\x7c\x21\x14\x0e\x77\x39\x86\x51\x96\x99\x83\x3b\x78\ +\xf1\x70\x9e\x52\xe7\xbd\x82\x7a\xad\x86\xab\xa3\xa3\xde\x3c\x48\ +\xcc\xbe\x71\x9e\x24\x49\xdf\xec\x7c\xfe\xf9\x1e\xc0\xe7\x5e\x11\ +\x99\x83\x3b\x60\xae\xde\x91\x91\x05\x1e\x2d\xe2\xf5\xbd\x3d\xce\ +\x79\xa4\x60\x5c\x9c\x9c\xdc\xa1\xe2\x22\x79\x03\x97\xa6\xa6\x1e\ +\xec\x9a\xa6\x5b\xa1\x57\xc5\x73\x1e\x7f\xe8\xfa\xa1\xb7\xc7\x39\ +\x8f\xe7\xe4\x88\x8d\x8d\x1d\x5c\x6d\xd7\xe0\xe0\x3d\x49\x55\xfb\ +\xab\xfb\xfb\xba\xd2\x68\x6c\x5b\x1d\x1d\x1a\xf3\xf9\x6d\xff\x1d\ +\x27\xee\x02\xf9\xe3\xf6\x7f\xe3\x14\x79\x84\xaf\xf9\x04\x6f\xc8\ +\xe3\xf6\x5a\x27\x1b\x9e\x98\xc0\x6f\x01\x06\x00\x48\xae\x45\x78\ +\x60\x4e\x1f\xe2\x00\x00\x00\x00\x49\x45\x4e\x44\xae\x42\x60\x82\ +\ +\x00\x00\x02\x75\ +\x89\ +\x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\ +\x00\x00\x10\x00\x00\x00\x10\x08\x06\x00\x00\x00\x1f\xf3\xff\x61\ +\x00\x00\x00\x19\x74\x45\x58\x74\x53\x6f\x66\x74\x77\x61\x72\x65\ +\x00\x41\x64\x6f\x62\x65\x20\x49\x6d\x61\x67\x65\x52\x65\x61\x64\ +\x79\x71\xc9\x65\x3c\x00\x00\x02\x17\x49\x44\x41\x54\x78\xda\xa4\ +\x93\x4f\x6b\x13\x51\x14\xc5\xcf\x9b\x37\x93\x49\xc6\xa9\x25\xa3\ +\x15\x63\x16\x2e\x02\x06\xad\x0b\x45\xc1\x95\x5f\xc0\x3f\x0b\x21\ +\x1b\xa5\x20\xb8\x31\xea\x7c\x82\xac\xf4\x2b\x54\x5b\x23\x88\x1b\ +\x4b\xb3\x70\xe1\x42\xb7\x82\x20\xe8\x42\xbb\x70\xa1\x62\x36\xa2\ +\xa0\x51\xda\x1a\xc2\x4c\xa6\x26\x93\xcc\x78\xef\x75\x5a\xb4\x2a\ +\x54\x7c\x70\xb8\x2f\x93\x7b\x7e\xf7\xbc\x37\x89\xaa\xd5\x6a\x50\ +\x4a\x9d\x06\xb0\x07\xff\xb6\x3e\xa5\x69\xfa\xc0\x4c\x92\x84\x3f\ +\x94\x9b\xcd\xe6\xcd\x38\x8e\xb7\xe4\xb4\x2c\x0b\xf5\x7a\xfd\x12\ +\xef\xcd\xd1\x68\xc4\xd5\x18\x0c\x06\xf0\x7d\x1f\x0c\x64\x11\x5d\ +\xea\x78\x3c\x46\x18\xf6\xa9\xa6\x62\xf4\x3c\x0f\xf3\xf3\xd7\x41\ +\x3e\xe3\x67\x80\xe2\xca\x86\x6a\xb5\x0a\x4e\xf2\xed\x68\x05\xa3\ +\xc7\x2f\xb1\xb2\xf2\x95\x9e\x6b\x32\xdb\xb0\xed\x3c\xa2\x28\x60\ +\x33\x4b\x09\x20\x8b\x6d\xf0\x43\x9e\xc6\x49\x58\x69\x79\x07\x56\ +\x57\xbb\x64\x88\x91\xcf\x6f\x13\xb3\x65\xe5\xa9\x27\x16\x00\xf9\ +\x8c\x0d\x23\x49\x33\x48\x00\x34\x39\x8a\x22\xa8\xbd\xbb\x08\x94\ +\x60\xf2\x60\x05\xe5\xd9\x3a\x26\x4f\x1c\x13\x90\x69\xda\x92\x90\ +\x3d\xec\x35\x86\xc3\x21\x48\x3f\x40\xa5\x22\x9d\x37\x84\x73\xed\ +\xbc\x5c\xd6\xf6\xe9\x0a\x3c\xff\x14\x7a\x8d\x16\x01\x8b\xa8\x35\ +\xaf\x12\xc0\x94\x04\xec\x61\xef\xc6\x11\xb8\x26\xef\xbf\xa0\xdf\ +\x5d\x43\xf7\xe1\x53\xb8\x07\xf6\xa1\x78\xf9\x24\xfa\xb7\x1f\xc1\ +\x75\x8b\x48\x5b\x4b\xb8\x77\xf7\x19\xbf\x72\x49\xb0\x7e\x04\x93\ +\x29\xb4\x24\x8e\xe3\x38\xe8\xf5\x7a\x30\x0c\x0b\xfa\xed\x07\x84\ +\xfe\x2c\x0a\x85\x09\x0c\x0c\x2d\x46\x5e\xb6\xad\xd7\x13\x68\x01\ +\xb4\xdb\x6d\x94\x4a\xa5\x1c\x37\x34\x1a\x8d\x2d\xfd\x0e\xb8\x37\ +\x08\x82\x5c\xa7\xd3\x01\x63\x3d\xd7\x75\x67\xb4\xd6\xbb\x37\x37\ +\xd2\xa4\xb2\x4c\x31\xcd\x8f\x9b\xbf\xa3\x0b\xff\x4c\xf7\xb5\xc0\ +\x80\x02\x69\x82\xfb\x7e\xe9\x98\xce\x01\xaf\x86\x7e\xb6\xbf\x41\ +\xfb\xdf\xf8\xa4\x40\xfd\x35\xe7\xe2\xd4\x2d\xbc\x89\x8f\xc8\x7e\ +\xbf\xb5\x84\x73\xcb\x17\xff\xd4\xc6\x53\x77\x92\x2a\xa4\x43\xa4\ +\xc3\xa4\xaa\x24\x5a\x0c\x71\xe6\xce\x59\x01\xdc\xbf\xd0\xe2\xf2\ +\x82\x93\x93\xde\x65\xfb\xe7\xa4\xd7\x9c\xc0\xca\x8e\xe1\x66\x72\ +\xf8\xad\xe0\x8a\x33\x83\x29\x75\x5c\xc6\x2c\xa7\x4f\x30\x17\x2d\ +\x64\x43\xd7\x38\x7a\xa6\x48\xf1\x9f\xe6\x7f\xd6\x77\x01\x06\x00\ +\xf9\x1f\x11\xa0\x42\x25\x9c\x34\x00\x00\x00\x00\x49\x45\x4e\x44\ +\xae\x42\x60\x82\ \x00\x00\x07\x62\ \x89\ \x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\ @@ -131,294 +620,6 @@ qt_resource_data = "\ \x22\xb5\x33\x33\xbc\x6e\xdb\x3c\x9d\x48\x10\xfc\x1f\x86\x93\xb9\ \x1a\xfd\x43\x9a\xa3\x00\x00\x00\x00\x49\x45\x4e\x44\xae\x42\x60\ \x82\ -\x00\x00\x02\x24\ -\x89\ -\x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\ -\x00\x00\x10\x00\x00\x00\x10\x08\x06\x00\x00\x00\x1f\xf3\xff\x61\ -\x00\x00\x00\x19\x74\x45\x58\x74\x53\x6f\x66\x74\x77\x61\x72\x65\ -\x00\x41\x64\x6f\x62\x65\x20\x49\x6d\x61\x67\x65\x52\x65\x61\x64\ -\x79\x71\xc9\x65\x3c\x00\x00\x01\xc6\x49\x44\x41\x54\x78\xda\x8c\ -\x53\x3d\x4b\x03\x41\x10\x7d\xd9\x3b\xf5\x44\x45\x8b\x44\x34\xa2\ -\xc4\x42\x49\x2b\xe8\xcf\xb0\xf3\x07\x28\xd6\x82\x9d\x20\x58\x88\ -\x0a\x36\x82\x8d\x95\xa0\x58\x0b\x16\xda\x8b\xd8\x08\x09\x44\x0b\ -\x8b\x54\x22\x7e\x44\x10\x41\x42\xce\xe4\x2e\xb7\x1f\xce\x9c\xe6\ -\xcb\x5c\xc0\xe5\x06\x6e\xdf\xbe\xf7\x66\x76\x76\x37\x96\xdd\x05\ -\x62\xc0\x12\x80\x14\xfe\x3f\x1e\x0d\x70\x3c\xbb\x66\x60\x1b\xfa\ -\xa3\x6f\x72\x6e\xf5\x62\x03\x5a\x03\x4a\x75\x96\x59\x16\x20\x04\ -\xb2\xfb\xf3\x5b\x35\x48\x84\x06\x06\xe2\x25\x93\x01\x1b\x18\x29\ -\x61\xaa\x7e\x7b\x10\xce\xeb\xcc\x63\x3e\xeb\x42\x03\xc5\x49\x35\ -\x44\x6f\x3c\x8e\xfb\xcb\x4b\xca\x22\x60\x44\x7b\x30\xce\xeb\xcc\ -\x63\x3e\xeb\x78\xd8\xfa\xc7\xc9\x1a\x1a\x4e\xa0\xe2\x96\x70\x73\ -\x7e\x51\xaf\xd8\xf3\x3c\x38\x8e\x53\x9f\x4f\x4c\x4f\x81\x79\xa4\ -\xb1\x6a\x98\xfd\xeb\x24\x0c\xed\x7d\x38\x39\x1a\x46\x08\x74\x75\ -\xe3\x29\x9f\xc7\x44\x3a\x0d\x1d\x54\xeb\x26\xcc\xe3\x0a\xfe\x1a\ -\x58\x5a\x05\x50\x32\x68\x34\x4c\xc4\x30\xd0\xd7\x87\x28\x9c\x34\ -\x56\xbb\x81\x54\xd0\xdc\xa8\xdf\x11\x13\x16\x1d\x08\x63\x11\x78\ -\x94\x81\x51\x92\xb2\x35\x88\x42\x59\x90\x94\x39\x0a\xef\x50\x41\ -\x00\xdd\x54\xaa\x1f\x28\x2c\xf6\x6c\xa2\xfa\xa6\xa8\x99\x92\x22\ -\x80\xef\x2b\x64\xa6\x8f\x5a\x0d\xa4\xaa\x19\x48\xda\x6b\x23\x53\ -\xd9\xf5\x70\x32\x53\x6e\xba\x45\x22\x0c\xf7\xae\x04\xd2\x44\x54\ -\x10\x96\xda\xa8\xc0\xfd\x2c\xc2\xae\x54\x90\xcb\xe5\x90\x48\x24\ -\xc2\x7e\xa4\x52\x29\xe8\x62\xa9\x53\x0f\xa8\x59\x4d\xd7\xd8\x25\ -\x62\x77\xb9\x8c\x34\x1d\x63\xbd\x2a\x9a\xeb\xd2\x57\xab\xc1\xdd\ -\x23\x90\x4e\xc2\x79\x79\x7a\xa5\x9b\xaa\x9a\x7a\xe0\xe3\xe3\x74\ -\xa5\xed\x39\x0c\xc6\x87\xe0\x55\xe1\xe4\x0b\xc0\x02\x1b\xec\x9c\ -\x61\xf0\x60\x19\xfd\xe3\xe3\xc9\xd6\xf3\x1e\x1b\x89\x7e\x4f\x76\ -\x17\x6e\xaf\xd1\xcf\xba\x6d\xa0\x68\xb3\xe9\xfd\x33\x0a\x87\x7b\ -\xeb\x57\xff\x7d\xcb\x0f\xef\x28\xb0\x8e\xa2\xf8\x2d\xc0\x00\x14\ -\x2c\x1a\x71\xde\xeb\xd2\x54\x00\x00\x00\x00\x49\x45\x4e\x44\xae\ -\x42\x60\x82\ -\x00\x00\x02\x7a\ -\x89\ -\x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\ -\x00\x00\x10\x00\x00\x00\x10\x08\x06\x00\x00\x00\x1f\xf3\xff\x61\ -\x00\x00\x00\x06\x62\x4b\x47\x44\x00\xff\x00\xff\x00\xff\xa0\xbd\ -\xa7\x93\x00\x00\x00\x09\x70\x48\x59\x73\x00\x00\x0b\x13\x00\x00\ -\x0b\x13\x01\x00\x9a\x9c\x18\x00\x00\x00\x07\x74\x49\x4d\x45\x07\ -\xdd\x06\x0d\x08\x1d\x33\x51\xf1\xd4\x9e\x00\x00\x02\x07\x49\x44\ -\x41\x54\x38\xcb\x65\x91\x3d\x6b\x14\x41\x18\xc7\x7f\x73\xb7\x2c\ -\x6c\x0c\xb9\x20\xa6\x0d\x58\x45\x90\x88\x1f\x40\xb1\x12\x2e\x8d\ -\x82\x5f\x41\x08\x58\x05\xb4\xb5\x0c\xd8\x09\xa2\x20\x08\x7e\x88\ -\x58\x05\xac\x04\xfb\x08\x56\x42\x40\x48\x9d\xdc\xe5\xf6\x66\xe7\ -\x75\x67\xc7\xe2\x19\x72\x07\x0e\xfc\x18\x06\xe6\xff\x32\xcf\xa8\ -\x9c\x1f\x00\xb7\x81\x09\x70\x0b\xa8\xf7\x40\x1d\x42\x7a\x02\xe1\ -\x21\x78\xc0\xfe\x82\xee\x07\x74\x9f\x41\x9f\x83\x41\xf0\xa8\x9c\ -\x1f\x17\x83\x4d\xa0\x7e\x0d\xea\x18\xfa\x46\x84\xae\xe0\x01\x0b\ -\x18\x0b\xe6\x2d\x98\xf7\x72\x0e\xa8\x9c\x0f\x80\x49\x0d\xf5\x09\ -\xa8\x29\xf4\xe5\x72\x57\x76\x0f\x44\x20\xac\x19\x9a\x53\x70\xcf\ -\x21\x84\x11\xd4\x00\x1f\xa1\x9f\x4a\xad\x05\x70\x05\x5c\x96\x7d\ -\x06\x5c\x03\xcb\x62\xda\x01\x66\x9a\xb3\x79\x17\x42\x8f\xca\xf9\ -\xd9\x3e\x54\x67\x90\xc6\x92\xb8\x28\xe8\x92\x9e\x80\x5c\x48\x80\ -\x23\xa5\x88\x31\xa4\x10\xb8\x5f\x41\x38\x84\x38\x96\x6a\x4b\x60\ -\x5e\x12\x6d\xa9\x9e\x91\xa5\x80\x9e\x10\x32\xd6\x82\x31\x8c\xbd\ -\xe7\x55\x05\x66\x2a\xce\xb6\x18\x2c\xcb\x84\x03\x30\xb0\xbe\x62\ -\x14\x71\xd7\x09\xd6\xf2\xa8\x02\xbd\xfb\xff\xe0\x62\x11\xe7\x1b\ -\x71\xce\x10\x23\x78\x0f\xc6\xc0\x72\x09\xc6\xb0\x5b\x49\x62\xcf\ -\xea\xdb\xe2\xda\xbb\x57\xe2\x94\xa0\xef\xb9\x69\x50\x0c\x18\xc1\ -\xf2\x02\xda\x32\x34\x49\xcf\x39\x93\x33\x37\x0c\x83\xa4\x5b\x0b\ -\x5a\x43\xdb\x0a\x5d\xc7\xc5\x08\xda\x53\x99\x7a\x4b\x4a\x96\x18\ -\x13\x21\x48\x5a\x4a\xab\xda\x5a\xc3\xf5\x35\xcc\x66\x42\xdb\x82\ -\xb5\xfc\x54\x29\xb1\xef\x1c\x67\x31\x32\xee\x7b\x49\x04\x50\x4a\ -\xf6\x94\xc0\x39\xa9\x7c\x79\x09\x57\x57\xb0\x58\x40\x08\xa4\xba\ -\xe6\x5e\x65\x0c\xbf\xad\xe5\x93\x73\x1c\xc5\x28\xc9\xc3\xb0\x12\ -\xf7\xbd\xbc\xb5\x6d\x61\x3e\x17\xb1\xf7\x30\x1a\xf1\xa1\x69\x38\ -\x57\xb3\x19\x68\x4d\xdd\x75\x9c\x58\xcb\x34\x04\x11\xae\xd7\xb7\ -\x56\x0c\xb4\x96\x33\xf0\x6d\x63\x83\x17\x77\xee\x90\xaa\x61\x80\ -\x61\x20\xc4\xc8\x81\x73\x1c\x19\xc3\xb1\x73\x6c\x7a\x0f\x21\x48\ -\x7d\x6b\x85\x18\xd1\x4a\xf1\xa6\x69\xf8\xb2\xb5\x05\xdb\xdb\xa0\ -\xe6\x73\xf9\x96\xb6\x95\x7a\x6d\xcb\x5d\xad\x79\xa9\x35\x4f\xad\ -\x65\xcf\x7b\x88\x91\x3f\x29\xf1\x7d\x3c\xe6\x6b\xd3\xf0\x77\x32\ -\x81\x9d\x1d\xe1\x1f\x3c\x20\x6c\x94\x65\x65\x77\x27\x00\x00\x00\ -\x00\x49\x45\x4e\x44\xae\x42\x60\x82\ -\x00\x00\x03\x37\ -\x89\ -\x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\ -\x00\x00\x10\x00\x00\x00\x10\x08\x06\x00\x00\x00\x1f\xf3\xff\x61\ -\x00\x00\x00\x19\x74\x45\x58\x74\x53\x6f\x66\x74\x77\x61\x72\x65\ -\x00\x41\x64\x6f\x62\x65\x20\x49\x6d\x61\x67\x65\x52\x65\x61\x64\ -\x79\x71\xc9\x65\x3c\x00\x00\x02\xd9\x49\x44\x41\x54\x78\xda\x6c\ -\x93\x5d\x48\x14\x51\x14\xc7\xff\xbb\xb3\xbb\xb6\xab\x95\x5b\x46\ -\xae\x99\xba\x7e\x40\x42\x46\x12\xa5\x4b\x49\xe8\x83\x60\x22\x11\ -\x3d\x94\xf6\xd6\x43\xe9\x6b\x81\x6f\x46\xd1\x43\x54\xd0\xf6\x49\ -\xf6\x24\x44\x81\x6f\x3d\x04\x8b\x08\x9a\x60\x59\xb6\x18\x65\xe9\ -\x22\xbb\xce\xb8\xea\xfa\xb1\x8b\xdf\xe3\x3a\xbb\xb3\xce\xed\xcc\ -\x75\x35\xad\x2e\x73\xb9\x77\xe6\x9c\xf3\xbf\xbf\x73\xee\x19\xc3\ -\xad\xf6\x76\x18\x0c\x06\x3e\x35\x4d\x83\x3e\x68\x5f\x47\x8b\x03\ -\xff\x1f\xdd\xeb\x89\x44\x40\x4d\x24\xc0\x18\x83\x69\xbb\xc5\x48\ -\x22\x09\x32\xd0\xc8\x6a\xa9\xaf\x6f\x9d\x8e\x44\x61\xb7\x5b\xb8\ -\xb0\x46\xce\x43\x81\x00\x3a\x07\x07\x1b\xf5\x33\x68\xfa\x79\xcc\ -\x0e\x6d\x12\x30\x0a\x02\x74\xf5\xc8\x9c\x02\x8a\x44\x24\xb2\x86\ -\x99\xd9\x28\xa6\x66\x56\x21\xcb\x32\xee\x36\x34\xb4\x92\xbd\x8a\ -\xbc\x8b\xf4\x90\x4d\x82\x3a\xc2\x71\x24\xf1\x21\x08\x42\xc5\xe4\ -\x62\x08\xcb\xb2\x8a\xe2\x9c\x83\xc8\xb0\x5a\xa1\xae\xaf\xe3\xc7\ -\xf0\x3c\xde\x7a\x3c\x28\xc9\xc8\x68\x7d\xd2\xde\x7e\x83\xdc\xdd\ -\x26\x8d\x0c\x9b\xc8\x23\x81\x15\xe4\xe6\x59\x77\x20\x0f\x07\xa7\ -\x91\x99\xbe\x1f\xa9\x29\x36\x9c\x38\xea\x42\x82\x6c\x66\xb3\x19\ -\xe5\xc1\xa0\xc2\x09\xd4\x8d\x9c\xe1\x17\x65\x3d\x03\x04\xc7\xd6\ -\x78\x71\xf4\x7a\xea\xc8\x35\xe5\xe5\xf8\xe8\xf3\xc1\xbe\xc7\x8c\ -\x0c\xbb\x8d\x93\x08\x24\x10\x8b\xc5\x0c\x1b\x02\xaa\xca\xc9\x8b\ -\x9c\xa9\xf0\x4b\xab\x70\x1e\xb6\xf0\x53\x74\xc7\x21\x71\x03\x59\ -\x1f\x83\xbf\xfc\xa8\xad\xa8\x24\x1b\xa3\xca\xa9\x88\x93\xc0\xc9\ -\xee\x6e\x12\x88\xc7\xb9\x80\x38\x1e\x85\xd1\x68\xc0\xd8\x64\x9c\ -\x13\xd0\x83\x92\xc2\xd3\x9c\x44\x7f\x5f\x54\xc7\x71\x60\x5f\x0a\ -\xdf\xc7\x07\x06\xd0\xe8\x76\x5f\xd3\xc2\xe1\x21\x23\xa1\x70\x9c\ -\xc2\x1c\x1b\x4f\xa1\x20\x67\x17\xf2\xf9\x4c\x41\x2e\xd1\x64\x67\ -\x0b\xc8\xcb\xb7\x52\x41\xe3\x98\x5f\x4a\x60\xc4\x1f\x42\xaf\xf7\ -\x3b\xca\x9a\x9a\x8e\x45\x80\x3b\x26\x42\xe1\x04\x52\x68\x8d\xdf\ -\xc0\x58\x28\xc6\x4f\xd7\x34\xb6\x45\xc2\x98\x02\x9b\x05\xb0\xa8\ -\xfd\x08\x8e\x2e\xa3\xe6\xfa\x55\xd4\xb9\x5c\x3d\x17\x19\xbb\x67\ -\x8a\x25\x05\x0a\xb2\x6d\x18\x9d\x8c\x22\x2f\xcb\xca\x6f\x80\x17\ -\x32\xb9\x1a\xa8\x37\xc4\x2e\x2f\x7c\xa1\xf7\xa8\x39\x75\x1c\xee\ -\xa7\x12\x66\x9d\xce\xaf\xdf\x04\xa1\xd3\xa4\x28\xca\x06\xc1\x54\ -\x92\x60\x4a\xd9\xca\x7b\x93\x24\xb6\xf8\x09\xc6\xb9\x37\x28\xab\ -\x3c\x8b\x8e\x8e\x7e\x5c\xba\xd0\x82\xd7\x7d\x3d\xe1\xb6\x89\x09\ -\xfc\x21\x38\x44\x04\xa1\x28\xf2\x75\x02\x60\x8b\x60\x61\xb6\x17\ -\xe2\xec\x73\x54\x53\xf0\xc3\x47\xee\xd1\x8e\x61\xc7\x87\xa1\x97\ -\xcd\x7e\x4d\x96\x97\xe5\x70\x38\xdf\x34\x23\x8a\xd8\xeb\x70\x18\ -\x44\x22\xd0\x5b\x5c\x9a\x56\x92\x79\x33\x44\x17\x46\x11\x09\x3c\ -\xc0\x19\x57\x29\x1e\x3f\x7b\x21\x05\x25\xa5\xb9\xcf\x23\x7d\x01\ -\xa4\xcd\xe6\xd7\x83\x90\x7e\xe4\xfc\xf9\x9b\x14\xc0\x88\x40\x5f\ -\x18\x9d\xcc\xd6\x89\xfd\xb3\xe7\x36\x63\xf2\x08\x7b\xd7\x56\xc5\ -\x6a\xaf\x94\xbe\x22\x5f\xfb\xdf\xbf\xa6\xde\x4d\xb9\xbb\x8b\x8b\ -\xab\x8d\x69\x69\xff\x18\x97\xbc\xde\xfb\x97\xcf\xa5\xfe\x1c\x5e\ -\xcd\xec\x93\xc2\x96\x81\x15\x9f\xaf\x8b\x3e\x8b\xdb\x7d\x7e\x0b\ -\x30\x00\x66\x8d\xa1\xfd\x87\x65\xe6\x27\x00\x00\x00\x00\x49\x45\ -\x4e\x44\xae\x42\x60\x82\ -\x00\x00\x02\xa1\ -\x89\ -\x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\ -\x00\x00\x10\x00\x00\x00\x10\x08\x06\x00\x00\x00\x1f\xf3\xff\x61\ -\x00\x00\x00\x19\x74\x45\x58\x74\x53\x6f\x66\x74\x77\x61\x72\x65\ -\x00\x41\x64\x6f\x62\x65\x20\x49\x6d\x61\x67\x65\x52\x65\x61\x64\ -\x79\x71\xc9\x65\x3c\x00\x00\x02\x43\x49\x44\x41\x54\x78\xda\xa4\ -\x93\xcf\x6b\x13\x51\x10\xc7\xbf\xfb\x23\x6b\xd3\x4d\x63\x53\x51\ -\x69\x93\x2d\x52\x62\xc5\xb6\x54\x08\x52\x28\x28\xb4\xa8\x08\xc5\ -\x5e\x84\xfe\x01\x3d\x49\x2f\x9e\x7a\x29\x96\x9e\xf2\x07\xf4\xa4\ -\x88\x17\x7f\x1c\x82\x22\x2a\xb9\x05\x5a\xe8\x49\x94\x0a\xb1\x58\ -\x24\x97\x68\x49\xa5\x31\x35\x90\xd2\xcd\x26\xd9\xcd\x6e\xd6\x99\ -\xad\x5b\x82\xb4\x50\x71\x60\xf6\xbd\x37\xcc\x7c\xdf\x7c\xde\xdb\ -\x27\xb8\xae\x8b\xff\x31\x79\x66\x69\xe9\x70\x21\x08\xc2\x34\x0d\ -\x7d\xff\x50\xbf\x23\xf3\xd7\x69\xb5\xfc\x40\xf4\x4d\x32\xf9\xe8\ -\x24\x3d\x09\xe4\x77\x17\x17\xe7\x64\xda\x15\x92\x28\xc2\x34\x4d\ -\x8e\x8b\x0e\x21\x7d\x2e\xba\xa8\x14\xbe\x42\x8b\x0f\x63\x20\xd2\ -\x3a\x52\x40\xa4\x1a\xbb\xd9\x14\xbd\x0e\x04\x5a\x28\x8a\x82\x9a\ -\x61\x88\x36\x09\xec\x15\xbe\xa0\x98\xdf\x84\x08\x07\x5a\xe2\x32\ -\x0a\xa5\x12\x82\xc1\x20\x6c\xdb\x46\x6f\x4f\x8f\x27\x20\xd1\xc6\ -\xbe\xc0\x34\x5c\xb7\x8f\x15\x03\x8a\x72\x6d\x65\x7d\x1d\xdb\xbb\ -\x3a\x8a\xe5\x32\x6a\xe1\x5f\xa8\x7e\x32\xd0\xa0\x42\xdf\xce\x77\ -\x77\xe3\x4a\x3c\x8e\x00\xe5\x37\x2d\x4b\x94\x6d\xc7\x39\x86\xfb\ -\xe6\x91\xdc\x4f\x33\x19\x9c\x56\x55\x5c\xd0\x34\x58\x96\x25\xc9\ -\xdc\x06\x73\x3f\xcb\xba\xf8\xfe\xfe\x35\xc6\x6f\xcf\xe0\xd6\xc0\ -\xf1\xdc\x6a\x67\x27\x62\xd1\x28\x6c\x3a\x78\xcb\x34\x45\x91\x05\ -\x98\xfb\xe7\x87\x57\xd8\x5c\x4d\x61\x63\xe5\x25\x9a\x8e\x83\xb5\ -\x6c\x16\x1b\x5b\x5b\xf8\x98\xcb\x79\x6b\x76\xce\x4b\x2e\x2f\xa7\ -\x9f\xa4\x52\xab\xcd\x03\x01\x49\x66\x0e\x56\x3b\xa3\x0d\xa1\x5a\ -\xad\xe2\x5c\xff\x10\x2c\x62\x8e\xc5\x62\xde\xae\x2a\xb5\x6b\xfd\ -\x39\x03\xe6\x56\x43\x21\x69\x6e\x76\xf6\x06\xd5\xc1\xd0\xf5\x80\ -\xcc\x1c\xac\xf6\xee\x6d\x1a\x86\x61\x60\x2d\x93\xc6\x9d\xeb\xf7\ -\x91\xa3\x9d\x7d\x2b\x45\x22\xa8\xd7\xeb\x18\x4f\x24\x50\xd3\xf5\ -\xca\xd9\x78\x7c\x21\x14\x0e\x77\x39\x86\x51\x96\x99\x83\x3b\x78\ -\xf1\x70\x9e\x52\xe7\xbd\x82\x7a\xad\x86\xab\xa3\xa3\xde\x3c\x48\ -\xcc\xbe\x71\x9e\x24\x49\xdf\xec\x7c\xfe\xf9\x1e\xc0\xe7\x5e\x11\ -\x99\x83\x3b\x60\xae\xde\x91\x91\x05\x1e\x2d\xe2\xf5\xbd\x3d\xce\ -\x79\xa4\x60\x5c\x9c\x9c\xdc\xa1\xe2\x22\x79\x03\x97\xa6\xa6\x1e\ -\xec\x9a\xa6\x5b\xa1\x57\xc5\x73\x1e\x7f\xe8\xfa\xa1\xb7\xc7\x39\ -\x8f\xe7\xe4\x88\x8d\x8d\x1d\x5c\x6d\xd7\xe0\xe0\x3d\x49\x55\xfb\ -\xab\xfb\xfb\xba\xd2\x68\x6c\x5b\x1d\x1d\x1a\xf3\xf9\x6d\xff\x1d\ -\x27\xee\x02\xf9\xe3\xf6\x7f\xe3\x14\x79\x84\xaf\xf9\x04\x6f\xc8\ -\xe3\xf6\x5a\x27\x1b\x9e\x98\xc0\x6f\x01\x06\x00\x48\xae\x45\x78\ -\x60\x4e\x1f\xe2\x00\x00\x00\x00\x49\x45\x4e\x44\xae\x42\x60\x82\ -\ -\x00\x00\x03\xef\ -\x89\ -\x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\ -\x00\x00\x10\x00\x00\x00\x10\x08\x06\x00\x00\x01\x68\xf4\xcf\xf7\ -\x00\x00\x00\x06\x62\x4b\x47\x44\x00\xff\x00\xff\x00\xff\xa0\xbd\ -\xa7\x93\x00\x00\x00\x09\x70\x48\x59\x73\x00\x00\x0b\x13\x00\x00\ -\x0b\x13\x01\x00\x9a\x9c\x18\x00\x00\x03\x8f\x49\x44\x41\x54\x38\ -\xcb\x3d\x91\xdf\x4f\x5b\x75\x00\xc5\xcf\xf7\x7e\x2f\xb4\xbd\xb7\ -\xdc\x76\x8c\xfe\x80\x5b\xe4\x87\x73\xcc\x84\x8c\x9f\x0d\x83\x25\ -\x2e\x6e\x3e\x8c\x44\xb3\x64\x66\x21\x6a\x62\x8c\xba\x68\xa6\x0f\ -\xea\x93\x0f\x26\x8b\x0f\xc6\x7f\xc0\x44\x97\xc5\x2c\x53\x5f\xdc\ -\xe2\xa3\xc8\xa0\xb0\x1f\x22\x14\xc1\x6d\x08\x6d\x2d\x90\x51\xda\ -\x4b\x27\x3f\xd6\x52\xee\xbd\x50\x7a\xbf\xbd\x5f\x1f\x10\x5e\x4f\ -\xce\x39\xf9\xe4\x1c\xca\x18\x43\x4f\x4f\xdf\x23\xc4\xe2\x09\x30\ -\xc6\x38\xb1\x2c\x0b\x00\x20\xe6\xb6\xf2\x7c\xf9\xc9\x0a\x08\x63\ -\x0c\xdb\xdb\x7a\x6f\xb1\x54\x9c\x30\x0c\x03\x88\x8c\xde\x43\xb8\ -\xbb\x8f\xf7\xf6\xbe\xc4\x1f\x8c\xff\x0e\x58\x96\x85\x72\xb9\x8c\ -\x8c\xa6\xfd\x64\x59\x16\xc0\x18\xc3\xf4\xcc\x43\x5e\x2e\x97\xf9\ -\x48\x64\xcc\xa4\xe1\x70\x8f\xd6\x15\xee\x54\xa2\xd1\x28\x8e\x54\ -\x7b\x2b\x71\xee\xec\x79\xb3\xed\x64\x37\x9f\x8b\xcd\x9b\x1b\xcf\ -\x36\xf7\x23\x96\x65\x29\x8c\x31\xcc\xfc\xf5\x68\x28\x91\x4c\xf2\ -\x7c\x61\x8b\xdb\xb6\xcd\x4d\xd3\xac\x21\xa9\x95\x34\x04\x41\x40\ -\x3c\x9e\xe4\x27\x4e\x1c\x43\xd0\x1f\x00\x07\xc7\xfd\x07\xe3\xe8\ -\xec\x6c\xbf\x86\xef\xbe\xbd\xfe\x59\x7b\x5b\x98\x07\x82\xf5\xfc\ -\xe2\xeb\x03\x5c\xd7\x75\x3e\x7a\xf7\x1e\x1f\x1e\x89\xb4\x65\xff\ -\x7d\x0a\x32\x3c\x3c\x06\x70\xe0\xe7\x5b\xb7\x22\xa9\xe5\xd4\x39\ -\xc3\x30\x71\xe3\x87\x6b\xde\x50\x5d\xa8\x20\xcb\xf2\x3e\xc3\xff\ -\x1c\xd0\xb4\xec\xab\xd3\x33\x0f\xe7\x0d\xd3\xec\x38\xd0\x0e\x0c\ -\x4e\x9b\x73\x8c\xdd\xbd\xcf\x57\x32\x69\x5e\x2e\x97\xb9\xb6\xaa\ -\x4d\x70\xce\x21\x02\x00\xa5\xb4\xf8\xeb\xe0\x90\xd5\xd2\xf2\x02\ -\x8e\x56\x1f\x85\x6d\x73\xc4\x13\xc9\xde\xbd\xa2\x75\x41\xd4\x56\ -\xb3\x58\x5b\x5f\xbf\xd1\xdc\xd4\x24\x2a\x1e\x05\x2e\xa7\x13\x8f\ -\x67\x67\xe1\x74\x38\xd0\xd4\xd4\x10\x11\x76\x4c\xb3\xc6\xd0\xf5\ -\x77\x1a\x9b\x1b\xa0\x54\x29\xd0\x75\x1d\x6b\x6b\x1b\xa8\xac\xac\ -\x7c\xf3\xf1\xdf\xb3\x26\xe9\xeb\x3d\xc3\xd7\x37\x36\x90\xcb\xe7\ -\x30\x15\x1d\xc7\xc2\xe2\x22\x82\xc1\x20\x37\x4c\x5d\x68\x6d\x6d\ -\x85\xf0\xd1\xc7\x57\xfa\x8e\x78\xbd\x68\xa8\x7f\x0e\xfd\xe7\x5f\ -\x83\xa2\x28\x30\x77\x8c\xe7\x5d\x2e\x17\x00\x40\xf0\xf9\x7c\x93\ -\xef\xbd\xff\xee\x8b\xb5\x75\xb5\x7b\xaa\xaa\x22\x12\x19\x4b\x29\ -\x8a\xb2\x1c\xee\xea\x86\x57\xf1\xec\x3f\x0e\x00\x9c\x73\x00\xa0\ -\x84\x10\x4e\x08\xb1\x4b\xa5\x52\xf3\x64\xf4\xcf\x39\x87\xc3\x21\ -\xf9\x03\x35\xf0\x7a\xbd\x90\x5d\x92\xb5\xbb\xbb\x7b\xdd\xed\x96\ -\x3f\xa1\x54\xb4\x38\xb7\x0f\x0b\x08\x00\x0a\x80\x51\x4a\xb1\xb0\ -\xb0\xf8\xf9\x93\xe5\xd4\xd7\x75\x75\xb5\x50\x3c\x55\x90\xdd\x32\ -\x14\xb7\x82\x4a\xb1\x02\x7b\xa5\x22\xe6\x62\x71\x80\xe3\xc7\x8e\ -\xf6\x93\x57\x0e\x09\x00\x80\x31\xe6\x9f\x98\x8c\xfe\x51\x51\xe1\ -\x38\xe6\xf3\xd7\x40\x92\x9c\xa8\xaa\xaa\x82\xec\x92\x40\x29\x45\ -\x2e\x9f\x43\x2c\xfe\x0f\xac\x92\x05\x8f\xc7\xb3\x13\x0c\x04\xde\ -\x12\xe7\x63\x09\x08\x44\x00\x21\xe4\xed\x8c\x96\xb9\x19\x0a\x85\ -\x20\x49\x4e\xb8\x24\x17\xdc\x6e\x37\x24\xa7\x0b\x84\x10\xa4\xd3\ -\x69\x24\x17\x96\x40\x29\x85\x3f\xe0\x5b\x57\xd5\xba\x16\x4a\xe9\ -\x96\x98\xcf\xe5\x4e\xdd\xbe\xfd\xcb\x37\x53\x53\xd3\x5d\x82\x40\ -\x50\xab\xd6\xe2\x8d\x81\x01\x5c\xba\x74\x11\x94\x52\x94\x4a\x25\ -\xc4\x12\x09\x68\x99\x55\x54\x57\x57\x83\x08\x18\x5a\x5d\xd5\xfa\ -\x4d\xd3\x40\x30\x18\x84\x90\xcf\x17\xb6\x0c\xdd\x14\x08\x00\x66\ -\x31\x2c\x25\x97\x70\xf5\xea\x97\x50\xd5\x46\x74\x74\xf6\xe0\xb7\ -\x3b\x77\xa0\x6f\x1b\x08\xd5\xab\x00\xe1\xdf\xeb\xba\xde\xaf\xaa\ -\x21\xb4\x1c\x3f\x0e\x49\x92\x40\x46\x22\x63\x10\x45\x11\xb1\xb9\ -\xc4\x85\xc1\xc1\xc1\x9b\xf9\x7c\xde\x23\x08\xc2\xc1\x26\x28\x14\ -\x0a\xf8\xe0\xc3\xcb\x38\xfb\xca\x99\x2f\x4c\x73\xe7\x2b\x59\x96\ -\xd1\xd4\xd0\x08\x49\x92\x20\x08\x02\xc8\xc8\xf0\x28\x00\x80\x08\ -\x84\x1a\xe6\x4e\x59\xd7\x0d\x64\xb3\xd9\xe6\xcd\xcd\x67\x97\x19\ -\xb3\x5e\xf6\xfb\xfd\x4f\x4f\x9f\x3e\xf5\xa9\xc7\xab\xa4\x82\x81\ -\x00\xfc\x3e\x3f\x6c\xdb\x3e\x1c\xfe\x3f\x11\x5f\xc4\xbb\xcd\x16\ -\x27\xa0\x00\x00\x00\x00\x49\x45\x4e\x44\xae\x42\x60\x82\ -\x00\x00\x02\xaf\ -\x89\ -\x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\ -\x00\x00\x10\x00\x00\x00\x10\x08\x06\x00\x00\x00\x1f\xf3\xff\x61\ -\x00\x00\x00\x19\x74\x45\x58\x74\x53\x6f\x66\x74\x77\x61\x72\x65\ -\x00\x41\x64\x6f\x62\x65\x20\x49\x6d\x61\x67\x65\x52\x65\x61\x64\ -\x79\x71\xc9\x65\x3c\x00\x00\x02\x51\x49\x44\x41\x54\x78\xda\x9c\ -\x53\xcf\x6b\x13\x51\x10\xfe\x36\xfb\x62\x8d\x69\x48\x62\x9b\x18\ -\x8d\xab\x5d\xd3\x84\xa2\x10\xbd\x48\x0f\x62\xa9\xd8\x93\xe0\x49\ -\x0f\xa5\x20\x52\xb0\xe0\x7f\xe0\x45\x3c\xf5\xa6\x77\x7b\xe8\x55\ -\x28\x41\x3d\x78\x28\x7a\xf0\x47\x51\xa4\xbd\x58\x0d\xa8\x60\x5a\ -\x13\x51\xd0\x43\x89\x69\xf3\x63\xb3\xc9\xee\x3e\x67\x9e\xd9\xa2\ -\x08\x4a\x1d\x18\xde\xdb\x99\xf9\xbe\xf7\xcd\x7b\xb3\xda\x8d\x85\ -\x05\xb0\x69\x9a\x76\x9e\x96\xfd\xf8\xbb\x3d\x71\x1c\x67\xad\xdb\ -\xe9\xe0\xdc\xf3\x19\x48\x0a\x08\xd7\x75\xfd\xe4\x81\xeb\x93\x93\ -\x73\x0e\x7d\x73\xc2\x95\x12\x5d\xda\x77\x3d\x4f\xed\x2b\x95\x0a\ -\x1e\x15\x8b\x57\xa5\x94\x1a\xa5\x4b\x3e\x28\x30\xf1\xf8\x32\xcc\ -\xb5\x7b\x20\x56\x4d\x72\xb1\xe3\xc0\xe9\x76\xe1\xf6\xbc\xd3\x6e\ -\xc3\x6a\x36\xd1\x68\x34\x30\x3b\x35\x35\x47\xb9\xb3\x44\x92\xf5\ -\x09\x04\xfb\xf0\xa7\x07\x57\x5a\x32\x78\x41\xd3\x2e\xe1\xc5\xea\ -\x2a\x3c\x22\x8a\xc5\x62\x68\xb5\x5a\x38\x3e\x32\xa2\x0a\xab\xd5\ -\x2a\xee\x2c\x2e\x22\x9f\x4c\xde\x5e\x29\xcc\x3e\x85\x8e\x02\x85\ -\xe7\x05\xa9\x1b\x44\x40\xcf\x65\x8f\x9e\x9c\x60\x6d\x99\x4c\x06\ -\x74\x82\x22\x89\xc7\xe3\x08\xea\xba\x22\x38\x35\x3a\x8a\x0e\xa9\ -\x0b\x85\xc3\x18\x68\x5d\x3c\x23\x1f\xbe\x7a\x2d\x3d\x77\x50\xb8\ -\x12\xc6\x5e\xe3\xd8\xf0\x26\x5d\x4c\x40\xd3\x54\xaf\xd1\x68\x54\ -\x9d\xc8\x24\x1f\x89\x8c\x09\x39\xc6\x8a\x4e\xe4\xf3\xb0\x6d\x1b\ -\x49\xc2\x54\x2b\x45\x43\xb8\x1e\x0e\xed\x8e\x26\xf7\x59\x56\x1b\ -\xbf\x2a\xe0\xd3\x7d\x25\xb2\x47\xe2\x2b\xe2\x5a\xc6\x30\x96\x14\ -\xc8\xa1\x60\x38\x16\x6a\x12\x3b\x3d\x25\xca\xe5\xf2\x36\xc0\x57\ -\xc2\x2b\x7f\xb3\x82\xc3\xa9\x14\xb8\x96\x31\x8c\x15\x8e\x87\x5c\ -\x24\x65\x26\xac\xf7\x75\x94\x0b\xd7\x30\x40\xb7\xde\x97\x1b\x47\ -\x5f\x76\xec\x37\x25\xf6\x87\x25\x04\x4b\x4b\xf8\xba\xbe\x07\x56\ -\xdb\x46\xc4\x34\x13\x8c\xe5\x16\x44\x24\x91\x4e\x4d\x27\x7e\x3e\ -\x0b\x4f\xd2\xca\xf2\x7d\x38\xc2\x50\x40\x7e\x0d\x6e\x63\x73\xf9\ -\x2e\x4e\x8f\x8d\xab\x9a\x69\x53\x2d\x29\xc6\xb2\x02\xb1\xb5\xb1\ -\x41\x7d\x59\x2a\xda\x4f\x00\x23\x9d\xc6\x97\x67\x37\x15\x41\x93\ -\x62\x3c\x58\xe6\x90\x89\x66\xbd\x8e\x46\xad\xa6\xea\x42\xa1\x10\ -\x1c\x45\xe0\x4a\xe1\xf0\xf0\x90\xb3\xd5\x88\xcc\xc8\x66\x71\xd0\ -\x3c\xf2\xc7\x1c\x7f\x2e\x6d\x0f\xa0\xaa\x67\xac\xe8\x7a\x08\x76\ -\x3a\x34\x71\xe4\xbe\xad\xbf\x7d\x87\x7f\x99\xae\x0b\x30\x56\x34\ -\x6c\xf4\x4b\xc9\x5a\x74\xec\xc4\x18\xc3\x58\xf1\xe6\x9b\xac\x6c\ -\xcd\xdf\x7a\x89\xff\xb0\xf2\x77\x54\x78\x76\x76\x91\xc7\x7a\xff\ -\xc5\x4e\x8c\x2f\xad\xf6\x43\x80\x01\x00\xc1\x52\x4e\xcc\x97\x5f\ -\x6c\x5a\x00\x00\x00\x00\x49\x45\x4e\x44\xae\x42\x60\x82\ \x00\x00\x02\x77\ \x89\ \x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\ @@ -461,6 +662,230 @@ qt_resource_data = "\ \x12\xe3\x03\xfa\xf1\xc0\xf9\xeb\x4d\xf9\x37\x2f\x04\xe0\x8f\x00\ \x03\x00\xe7\xe3\x7a\x6e\x30\xbb\xf3\xb7\x00\x00\x00\x00\x49\x45\ \x4e\x44\xae\x42\x60\x82\ +\x00\x00\x06\x71\ +\x89\ +\x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\ +\x00\x00\x90\x00\x00\x00\x90\x08\x02\x00\x00\x00\x68\x24\x75\xef\ +\x00\x00\x00\x03\x73\x42\x49\x54\x08\x08\x08\xdb\xe1\x4f\xe0\x00\ +\x00\x00\x09\x70\x48\x59\x73\x00\x00\x0e\xc4\x00\x00\x0e\xc4\x01\ +\x95\x2b\x0e\x1b\x00\x00\x06\x14\x49\x44\x41\x54\x78\x9c\xed\x9d\ +\x4b\x76\xa3\x30\x10\x45\x71\x8e\x07\xc9\x72\xb2\x9d\x64\x7d\xde\ +\x4e\xb2\x1c\x0f\xe9\x01\x69\x2c\x0b\xa1\x6f\x7d\x5e\x95\xf5\x66\ +\xdd\xb1\x41\xd4\xd5\xe5\x23\x0b\xb8\x7c\xbd\xff\x2c\xcb\x72\xbb\ +\x7f\x2e\x18\xf9\xfe\xf8\xd5\x6e\x42\x1c\xa8\xe2\x5c\x36\x60\x5b\ +\xa0\x5a\xa6\xdd\x84\x47\x10\xca\xb2\x17\xe4\xb2\xae\x6b\x54\x1d\ +\x84\xf6\x6d\x01\xc1\xa6\x5b\x90\xa8\x08\xd7\xb3\x4f\x20\x60\xdb\ +\xda\x00\x82\x4d\x3e\xc7\x0d\xbf\xdd\x3f\x2f\xeb\xba\x26\xff\xb6\ +\x7f\x82\xbd\x5d\x75\x51\xc4\x26\x5f\x84\x0c\x8e\x84\x61\xc7\x6f\ +\x22\x60\x7b\x11\xdb\x32\x1b\xb8\x55\xe0\xcf\xb0\xfc\x47\xc3\x2f\ +\x20\x44\x18\x9b\xcc\x86\x57\xd6\xbf\x60\xd8\x71\x89\x08\xd8\x9c\ +\xd9\x56\xb3\x21\x7b\xd9\x1f\x86\x55\x7e\x33\xfa\xbe\x7a\x04\xb0\ +\xf1\x6d\x6c\x47\xc1\x1b\x0c\x3b\xae\x09\x01\x9b\x51\xdb\x9a\x1a\ +\x1c\xd6\xf9\xc9\xb0\xd6\x05\x1d\x17\xa7\x1b\x26\x6c\xb4\x1b\x38\ +\x58\xe1\x4e\xc3\x8e\x2d\x40\xc0\x06\x6e\x5b\x5f\xc3\xa2\xc2\xbe\ +\xe5\xff\xdc\xd4\x1a\x90\x4a\x21\x74\x9d\x28\x84\xc5\x21\x30\x2c\ +\x8c\xba\x6d\x61\x5d\x6e\xf7\x4f\xf5\x3e\x34\xd8\x80\x63\x25\x13\ +\xc0\xc6\xb7\x53\x05\x5b\xb2\xcd\x8a\x3b\x49\xa6\x95\x12\x1b\x16\ +\x46\x0c\x5b\xe5\x25\xa7\x18\x36\xaa\x15\x25\x4b\x97\x06\x46\xb8\ +\x33\x61\xc5\xd6\x71\x72\xcc\x8a\x4d\xa0\x4f\x30\x1a\x16\x86\x1c\ +\x5b\x77\x69\x98\xb0\x91\x2f\xf0\xac\x56\xa7\xc0\x38\x8e\xd8\x24\ +\xd8\x48\x5a\x45\x88\x4d\xf8\x00\x29\x64\x58\x98\x6e\x6c\x4c\xbd\ +\xb8\x7b\xb1\x7c\xa8\x32\xc5\xc9\x01\x63\x3d\x2d\x6e\xc2\xc6\xda\ +\x8b\x3b\xb0\x29\x5e\x2d\x28\x18\x16\xa6\x88\x4d\xac\x34\x95\xd8\ +\xd4\xc7\x9a\x0b\xc0\x64\xae\x3d\x93\xd8\x54\x7a\x71\x06\x9b\xfa\ +\x35\xf8\x96\x78\xf0\xf7\x18\xf9\x5f\x0b\x59\xaf\x63\xea\xa3\xd8\ +\x63\x32\x89\xc7\x12\x3b\x16\x41\x1b\x90\x8e\xbc\x40\x8e\x49\x2e\ +\x35\xc0\xe4\x83\x50\x29\x95\xb1\xec\x9a\x0d\xaf\x02\x26\x5f\xc1\ +\xdb\xfd\x53\x0b\x1b\xce\xcf\x0e\xc9\x28\x9f\x25\xe6\x63\x74\x0c\ +\xb0\x2f\x95\x1d\xb4\x76\x97\xa8\xb8\x9b\x12\xb0\x0d\xdc\xaa\x30\ +\xd0\x86\x85\xb1\x32\x06\xd8\x97\xfa\x1e\xd9\x70\xd2\x81\x70\x2e\ +\x40\x68\x9b\x21\xab\xc2\x98\x31\x2c\x0c\xec\x18\x60\x5f\x9a\xba\ +\x60\xdb\x69\x3d\x82\x64\x7b\x3a\x6c\x33\x6a\x55\x18\x93\x86\x85\ +\xc1\x19\x03\xec\x4b\x6b\x9f\x6b\xbe\x70\x86\x92\x6c\x4f\xc6\x36\ +\x07\x56\x85\x31\x6f\x58\x98\xc8\x36\x7c\x4e\x1d\xbd\xbf\x67\x68\ +\x0a\x53\xb2\x3d\xe0\xcd\x1b\x8c\x2b\xc3\x16\x0b\x56\xed\xe9\xeb\ +\x58\x9d\x83\xbf\x80\xbd\xd8\xd9\xb1\xea\x2c\x1e\x0c\xb3\xc8\xa9\ +\xbb\xc7\xf7\xff\xbc\x82\x20\xd9\x8b\x58\x15\xc6\xaa\x61\xa6\x39\ +\x8d\xf4\xf5\xa1\x1f\x30\x55\x24\x7b\x41\xab\xc2\x58\x32\xcc\x07\ +\xa7\xc1\x5e\x3e\x3a\x45\x40\xec\x0e\x7b\x1f\xb4\xc6\x83\x6e\x98\ +\x33\x4e\xe3\xfd\x9b\x60\x12\x0e\xdf\x9d\x29\xce\x68\x91\x04\xd1\ +\x30\xaf\x9c\x48\x7a\xf6\xd5\x6b\x75\xbc\x06\xd1\x30\xb4\x8c\x9b\ +\x41\x78\x77\x24\xd9\x44\x52\x84\x81\x0f\xa6\xd0\xde\x8c\x3a\x18\ +\x1a\x60\x8e\x69\x8d\x87\x96\x37\xe5\x54\x6d\xc7\xd8\x70\x24\xc3\ +\x3d\xad\xf7\x11\x72\xd2\xc4\x37\x43\x38\x86\x07\x22\x99\x8d\xa1\ +\x29\xa3\xe1\x60\x4c\x7f\xbb\x91\x63\x84\x08\x92\xd9\xfb\x79\xc5\ +\x4a\x98\xe8\xb2\xdc\xd0\xe7\x18\xa4\xba\x64\xb6\xa7\x08\xc0\x86\ +\x8f\x2b\xd7\x2d\xb3\x8e\x71\xea\x4a\xe6\x67\x9a\x1b\x4e\x58\x89\ +\x32\xde\x94\xee\x18\xaa\xa2\x64\x0e\xa7\x6a\xeb\x86\x9b\x25\xef\ +\x63\x1f\x1c\xa3\xd5\x92\xcc\xc9\xed\x46\x20\x11\xa0\xc8\xfe\x60\ +\x15\xc7\x80\x55\x24\x33\x7c\xcb\x2c\x5a\x64\xf8\x49\x3c\xba\xc8\ +\x31\x66\x79\xc9\x8c\x3d\xf6\x01\x36\x62\xe4\x84\x1e\x0e\xe6\x18\ +\xb6\xb0\x64\x4f\x6f\x99\xcd\x04\x67\xe6\xd0\x8b\xa7\x16\xd8\x0c\ +\x48\xe6\xbc\x44\x9a\x88\xed\x81\x44\x9f\x97\x38\x8f\x64\xe3\x91\ +\x7b\x84\xac\x63\x5a\xe3\xa1\x3f\xad\x9f\xd8\x8a\x91\x91\xac\x00\ +\x6c\x72\x12\x08\xd7\xd0\xd4\x84\x57\x8c\x80\x64\x39\x60\x93\x90\ +\x40\x78\x7f\x5e\x99\x08\x8b\xe1\x96\xec\x14\xd8\x64\x23\x10\x89\ +\x29\x02\x13\x64\x31\xac\x92\xa5\x81\x4d\x2a\x02\x91\x9b\xe6\x36\ +\x71\x16\xc3\x27\x59\x02\xd8\xe4\x21\x10\xe9\xa9\xda\x13\x6a\x31\ +\x4c\x92\x91\xbd\xda\x9e\x69\x39\x2e\xa3\x73\xbb\xd1\x44\x5b\x0c\ +\x87\x64\x4f\xc0\x26\x03\x81\x68\xde\x32\x3b\x01\x17\x43\x2e\xd9\ +\x03\xd8\xac\xbe\x40\xf4\x1f\xfb\x30\x31\x17\x43\x2b\xd9\x1f\xb0\ +\x59\x77\x81\xa0\x3c\xba\x68\xc2\x2e\x86\x50\xb2\xb7\x65\x56\x5c\ +\x24\x54\xcc\x2e\x5f\xef\x3f\x24\x85\x9e\xf3\x44\x65\x52\x7e\x53\ +\x7a\x4d\xbc\xd2\x22\x7c\x6d\xfb\x42\xb4\x07\x42\x7c\xf1\x36\x42\ +\x38\x5e\x6d\x4b\xc2\x9e\x60\xe6\xaf\x33\xbd\xc0\x8f\xc4\xd3\xb0\ +\x47\x64\x5e\x18\x3d\xb8\x84\x51\xc3\x7c\xe8\x05\x6e\x55\x98\x57\ +\x37\x4c\xc0\xaa\x28\x83\x5d\x7c\xc8\x30\xd3\x7a\x19\xb2\x2a\xcc\ +\x2b\x1a\x26\x6f\x55\x94\x91\x8e\xde\x6f\x98\x45\xbd\x8c\x5a\x15\ +\xe6\x55\x0c\x53\xb7\x2a\x4a\x77\x77\xef\x34\xcc\x90\x5e\x50\x9c\ +\xc6\xe3\xdc\x30\x64\x5a\x72\x13\x49\xf1\xf5\xda\x39\xf9\x7b\xa7\ +\x95\x37\xc3\x92\xc7\x2a\x58\x6c\x1d\xad\x6a\x3e\x86\x61\x6e\xf9\ +\x52\xb1\xf7\xdb\x5a\x8e\xbc\x93\xac\x89\x07\xc3\x9a\xce\x00\xd1\ +\x6c\x6b\x6d\x4c\x9b\x61\x50\x9b\xba\x0c\xe8\x62\xd7\x36\xab\x86\ +\x91\x5c\x57\x81\xd8\xd6\xd4\x86\x06\xc3\x10\xb6\x6d\x61\xd0\xc2\ +\x96\x6d\x96\x0c\x63\x1d\xad\xd0\xb5\xad\x7e\xd5\xb5\x86\xe9\xea\ +\x25\xd6\xfd\xf1\x6d\x43\x37\x4c\x65\x0c\x50\xc5\xb6\xca\x35\x56\ +\x19\xa6\xa2\x97\x7a\x37\x07\x39\x66\x47\x01\x35\x4c\x9d\x96\x4a\ +\x6a\xba\x48\x19\x98\x64\x47\x43\x1b\x03\xdc\x76\xc8\x50\xbd\x07\ +\xe5\x01\x97\xc9\xa2\x28\x9e\x02\x44\x2b\xdd\xfe\x29\xd0\x87\xbe\ +\x3f\x7e\xf3\xdb\x5b\x00\x26\xd0\x44\xb4\x31\xc0\xcc\x8a\xc4\xb0\ +\x65\xa2\x69\x58\x13\x03\x01\x6c\x95\x0b\xe7\xc6\x96\x97\x2c\x07\ +\x8c\xaf\x4d\x68\x63\x80\x1d\x0b\xd4\xb2\x4d\xda\x30\xc2\x3b\x65\ +\x48\x16\x35\xb8\x10\x26\x6c\x19\xc9\x4e\x81\x91\x37\x02\x6d\x0c\ +\x90\xb0\x3d\x92\xb6\x49\x18\xc6\x7a\xe0\xe9\xc0\xc6\xd4\x1e\x5a\ +\x6c\x67\x92\xa5\x81\x51\xad\x15\x6d\x0c\x50\xa0\x3d\xdc\xb6\x71\ +\x19\xa6\x72\xf1\x94\xc1\x26\xdc\x1e\x12\x6c\x49\xc9\x12\xc0\x06\ +\x57\xa3\x3e\x2e\x10\xb5\x5f\xb1\x3d\x1c\xb6\x51\x8e\x25\xa2\x8d\ +\xe2\x2c\x00\xbd\x67\x19\x2b\xcb\x11\x76\x6c\x58\x5f\x77\x40\xa8\ +\x4b\x32\x38\xbf\x6f\x51\xd9\x16\xdf\x94\xde\xba\x44\xcc\x1b\x81\ +\x93\x41\xc0\xb6\x65\xa4\xc8\x4f\x86\x35\x2d\x08\x67\xfb\x2b\xe3\ +\xc3\xb6\x27\xc3\x2a\x17\x21\x70\x5d\xc5\x1d\x04\x6c\x5b\x5a\x6b\ +\xfe\x30\xac\xe6\x9b\x38\xdb\x39\x18\xbb\xb6\x3d\x0c\xcb\x7f\x47\ +\xf8\x12\x58\x32\x08\xd8\xb6\xd4\x20\xb8\x16\x3f\x8a\xb3\x3d\x4c\ +\xb1\x65\xdb\x9f\x61\xc9\x0f\x29\x8e\x56\x68\x05\x01\xdb\x96\x33\ +\x22\xd7\xe4\xdf\x70\xda\x2d\x1c\x7c\xdb\x2e\xeb\xba\x86\xff\xab\ +\xde\x56\x84\xb9\x37\x5b\xd4\x4b\xb1\x27\xac\xc9\xe3\xb5\xc0\x20\ +\xed\xc3\x01\xb6\x05\xa4\x2c\xcb\xff\xca\xfc\x03\x0c\x3a\xb7\xd7\ +\x9d\x1e\xca\x90\x00\x00\x00\x00\x49\x45\x4e\x44\xae\x42\x60\x82\ +\ +\x00\x00\x07\x3c\ +\x89\ +\x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\ +\x00\x00\x18\x00\x00\x00\x18\x08\x06\x00\x00\x01\x97\x70\x0d\x6e\ +\x00\x00\x00\x06\x62\x4b\x47\x44\x00\xff\x00\xff\x00\xff\xa0\xbd\ +\xa7\x93\x00\x00\x00\x09\x70\x48\x59\x73\x00\x00\x0b\x13\x00\x00\ +\x0b\x13\x01\x00\x9a\x9c\x18\x00\x00\x06\xdc\x49\x44\x41\x54\x48\ +\xc7\x55\x95\x59\x6c\x54\xd7\x19\xc7\xff\xe7\x2e\x73\x77\xcf\x6e\ +\x7b\xc6\x0e\x60\x30\x13\xb1\x04\x12\x07\x95\x94\x90\x1a\xda\x4a\ +\x49\x53\x35\x54\xb4\x52\x9a\x86\xb4\x4d\xe9\xf2\xd4\x28\x6d\x43\ +\x15\xa9\x2f\x51\x93\x80\xd2\x2c\x28\x52\x15\xa9\x8d\x68\x95\xbc\ +\x44\x6d\x1a\x51\xd2\x46\x4d\xd8\x8c\xed\xb8\x40\x69\x4b\xd8\xed\ +\x19\xb0\xc7\x60\x7b\x6c\x33\x1e\xcc\xc0\x8c\x67\xee\xb9\xe7\x7c\ +\x7d\x18\x6c\xc8\x79\x3c\xba\xff\xef\xf7\x2d\xff\xef\x1e\x46\x44\ +\x08\x82\x00\x35\xbf\xbe\x16\x44\x84\xcf\x4e\x9d\xa1\x6a\x6d\x8e\ +\x40\x44\xe8\xe8\xc8\xd0\xa9\x33\xa7\x09\x9c\x73\xf4\xf7\x0f\xbc\ +\xc2\x39\x07\xab\xd7\xeb\xb8\x59\xad\xd0\xa9\xcf\xce\x40\x79\x60\ +\xfd\x43\x64\x1a\x26\xda\xda\xd3\x0c\xb7\xa2\x85\xa7\xaf\x16\xbb\ +\x87\x72\x59\xf2\x7d\x9f\xfe\x75\xec\x18\x29\x00\xd0\xdb\xd7\x3f\ +\x1b\x08\x7e\x64\xe9\x92\x0e\x1c\x3c\x74\x08\xba\xa6\x6d\x54\x00\ +\xa0\xe7\x50\xdf\xef\xbf\xfb\xf8\xf7\xf0\xe0\x86\x4d\x88\x44\x23\ +\x6c\x5d\x57\xd7\x00\x82\x20\x40\x10\x04\x10\x42\xe0\xe4\xc9\x53\ +\xef\x07\x41\x00\xce\x39\x10\x04\x01\xe3\x9c\xe3\x70\x4f\x2f\x15\ +\xa6\x26\x89\x73\x4e\x44\x04\x85\x31\x46\xfd\xfd\x03\x57\x97\x74\ +\x2c\x42\x22\x16\x47\xff\xc0\x00\xc6\xc6\x26\xb6\xb3\x6c\x2e\x07\ +\xd7\x75\xc9\x75\x5d\x10\x49\x9c\x3c\x79\x1a\x89\x64\x8c\xb1\xf5\ +\x5f\xd8\x48\xb3\xb3\xb3\xf0\x03\x1f\x6f\xbd\xf5\x3b\x28\xaa\xa2\ +\xdc\x7b\xef\x1a\x52\x5e\xda\xf9\x1b\x16\x8f\xc7\x91\x6e\x49\x61\ +\xa6\x58\x44\x24\xd2\x44\xc9\x78\x02\x8c\x88\x20\x84\x00\x11\x31\ +\xc6\x98\x02\x40\x9c\x3b\x7f\x61\xa0\x52\x99\xdb\x90\x6a\x6b\x46\ +\xba\x35\x8d\xf2\xf5\xeb\xc5\x58\x2c\x96\x94\x52\x40\xc1\xad\xc3\ +\x18\x23\x55\x55\xc5\x3f\x3e\xfa\x27\x01\x6c\xc3\x5d\x8b\xd2\x48\ +\xc6\x93\xd0\x35\x0d\x60\x48\x1c\xea\x39\x42\xa3\xa3\x57\x9e\x66\ +\x44\x84\xb3\xe7\x06\x11\x08\xfe\x66\xf9\x7a\xf9\x99\x74\x3a\x05\ +\xaf\xc9\x45\xc4\x0b\x43\xd3\x35\x0c\x65\xb3\x18\x1f\x2f\x20\x95\ +\x6e\xc5\xdd\x99\x0c\x63\x44\x84\x07\xd6\x7f\x89\x0a\x93\x13\x10\ +\x42\x62\xd7\xae\x97\xf1\xd4\x53\x4f\x80\x73\x8e\x81\xa3\x47\xe1\ +\xd7\x39\xc2\x61\xef\x52\xe9\x5a\xa9\xb3\xab\xab\xab\x91\xd2\x93\ +\xdb\x9e\x78\x36\x9d\x4a\xa3\xb5\xa5\x05\xaf\xbd\xfa\x3a\x56\xaf\ +\xee\x42\x26\xb3\x0a\x9e\xe7\xc1\xf5\xec\xfd\x3e\xf7\x3b\xd3\xe9\ +\x74\x23\x75\x22\xc2\x81\xfd\x3d\xf0\xb9\xcf\xc6\xc6\xc7\x9b\x4e\ +\x9c\x38\xf1\xb7\x2b\x57\xc6\x36\xc9\x40\xe0\x97\x3b\x9e\xfd\x51\ +\xba\x2d\xb5\x67\xd5\x8a\x95\x60\x8c\x35\x8a\x9d\x9f\xdd\xfc\xcc\ +\x38\xe7\x98\x9e\x9e\x5e\xdf\xd3\xd3\x77\x64\x24\x9f\x7f\x4e\x92\ +\x9c\xbf\x67\x44\x74\xbb\x4b\x00\x34\xc6\x18\x14\x45\xd1\xff\x77\ +\xf2\xf4\xb1\x44\x32\xde\xed\x78\xee\xab\x22\x10\x54\x2a\x95\xbe\ +\x09\x80\x01\x58\x10\x38\x00\x02\x55\x55\xf1\xf1\x27\x07\x2b\xf3\ +\x9d\x32\x42\x21\x28\x8a\x82\x64\x32\xb9\xf7\xe2\xa5\xe1\x67\xee\ +\x14\x54\x1a\xed\x3d\xff\x46\x34\x1a\xd1\x4d\x2b\x04\xdb\xb6\x61\ +\x99\x16\x82\x20\xc0\xb1\x7f\x1f\xc7\xf0\x70\x7e\x37\x00\x28\x8c\ +\x31\x30\xc6\x30\x57\xab\x3f\x34\x3a\x7a\xf9\xe7\x91\x68\x04\x8e\ +\xe3\xc0\x73\x1c\xa8\x8a\x82\x89\xc2\x04\xaa\x95\x1a\x9a\x9b\x93\ +\x8d\xbc\xcf\x5f\xc8\x82\x31\x86\x5c\x2e\xdb\x77\x77\x26\x03\xc7\ +\xb1\xe0\xd8\x0e\x34\x4d\x47\xad\x56\x43\x36\x77\x11\x9a\xaa\x63\ +\x79\x66\xd9\x63\x00\xa0\x08\xc9\x01\x12\x1f\x64\x32\xcb\x11\x8d\ +\x47\x60\xdb\x36\x0c\xd3\x00\x63\x0c\x17\x86\x06\x21\x85\x84\xeb\ +\xba\xf5\x42\xa1\xf0\x77\x00\x50\x86\x2e\x64\x7f\xf6\xad\xad\x8f\ +\x6f\xdd\xbc\xe9\xab\xd8\xf6\xe4\xd3\xf0\x1c\x0f\x66\xc8\xc0\x4c\ +\x69\x06\xe3\x63\x05\xc4\x62\x71\x5c\x9b\x9d\x59\x5c\x2c\x16\x01\ +\x00\xca\xd4\xe4\x74\x53\x2c\x16\x43\x32\x99\x44\x3e\x9f\x47\x5b\ +\x7b\x07\xde\x7b\xef\xcf\xb8\x34\x3c\x8c\x58\x3c\x8e\x20\xe0\x7d\ +\x8c\xa9\x53\x2b\x57\xae\x68\x08\x56\xac\x5e\xf1\x32\x63\x0c\xae\ +\xe3\xa2\xc9\xf5\x90\x6a\x69\xc5\x0b\x2f\xbc\x88\xe3\x47\x4f\x20\ +\x91\x88\x22\x10\xbc\xdb\x30\x34\x08\x21\x1b\x02\x92\x12\x3b\x7e\ +\xb5\xc3\xf6\x3c\x0f\x9e\xe7\x21\x1a\x8d\x20\x16\x8d\xe1\x8f\x7b\ +\xde\x41\xad\x56\xfb\x83\xae\xeb\xb8\x6f\xed\x7d\x48\xc4\xe2\x0d\ +\x01\x24\x83\xe7\x3a\xf5\xed\x3f\xde\xce\xba\x37\x77\x6f\x8f\x27\ +\x13\x81\xeb\x79\x58\x75\xcf\xaa\xaa\xae\xeb\x3f\x4d\xb5\xa6\xe0\ +\x79\x1e\x88\xe8\xb6\xf9\x84\x10\x0b\xfe\x20\xa2\x79\xa3\x31\xba\ +\xf5\x95\xaa\xaa\x28\x16\x67\xbe\x76\xfa\xf4\x99\x0f\x6e\x56\x2a\ +\x56\x3c\x1e\x43\x3c\x11\x87\xeb\x3a\x50\x15\x65\x4e\x53\xb5\xb7\ +\x2d\xcb\xda\x6d\xdb\x76\x1e\x00\xa4\x24\x00\x04\x5d\xd7\x3f\x0f\ +\x20\x22\x0d\x40\x2b\x63\xac\x00\x40\xcc\x07\x1f\x1e\xc9\xbf\x74\ +\x61\x30\xfb\xeb\x48\xb8\x09\xae\xeb\xc2\x71\x6c\x84\x0c\x1d\x96\ +\x65\xc1\xb5\x1d\x18\x86\x01\x45\x51\x50\x2e\x97\xe9\xc6\x8d\x1b\ +\x6f\xa4\x52\xa9\xe7\x18\x08\x4c\x51\x3f\x07\x60\xb7\x8b\x68\x54\ +\xc1\x39\xf7\x8e\x1e\x3d\xfe\x69\xdd\xf7\xd7\xc4\xe3\x71\x58\xb6\ +\x09\xc7\xb1\x61\x18\x06\x4c\xd3\x84\x63\xdb\xd0\x35\x1d\x44\x04\ +\xdf\xf7\x31\x39\x35\x85\xfc\xe8\x28\xaa\x95\xb9\xe2\x97\x37\x6f\ +\xba\xdf\xb2\xcc\xcb\x77\x6e\x0f\x01\x20\xc6\x18\x34\x4d\x43\xa9\ +\x74\xed\x91\x8f\x3f\x39\x50\x56\x35\x6d\x4d\x7b\x7b\x3b\x22\x91\ +\x08\x3c\xcf\x85\x69\x99\x70\x1c\x07\xae\x6d\x43\x57\x35\x10\x11\ +\xaa\xd5\x2a\x86\xf3\x23\x18\x1e\xc9\x43\x04\x12\x9e\xe7\x25\x86\ +\x86\xb2\x3f\x04\x00\x0d\x00\xa6\xa6\xa7\xef\xa0\x50\xe8\xf2\xe8\ +\xd8\x87\x53\x53\x57\x1f\x6e\x6f\x6f\x87\x63\x3b\x30\xcc\x10\x42\ +\xa6\x0e\x33\x64\xc0\x32\x2d\x84\x42\x21\xa8\x8a\x02\x41\x84\x72\ +\xb9\x8c\x5c\xee\x22\x2a\x95\x2a\x84\x10\x30\x8c\x10\x16\x2f\x59\ +\x34\xd6\x96\x4a\xed\x59\x00\x14\xaf\xce\x34\x16\x49\xd3\xd6\x15\ +\x26\x27\x07\x38\xe7\xa1\x8e\x25\x8b\x61\x58\x06\x4c\x33\x84\x50\ +\x28\x04\xc3\x34\x61\x19\x06\x74\x4d\x87\xa2\x28\x08\x84\x40\x71\ +\xa6\x88\xa1\xa1\x1c\x38\x0f\x40\x44\x08\x87\xc3\x88\x46\x23\x7d\ +\x86\x11\xea\x9e\x29\x95\xd0\xd2\xdc\xdc\x00\x98\xa6\x89\xc1\xc1\ +\xec\xf3\xbd\xbd\xbd\xbb\x84\x94\x58\xbc\xe8\x2e\x68\xba\x8a\x75\ +\xeb\xee\x47\x93\xd7\x04\x92\x12\x44\x8d\x87\x4f\x61\x0a\x38\xe7\ +\x18\x9f\x2c\x20\x37\x94\x03\x11\x60\x9a\x16\xa2\xd1\x08\xca\x37\ +\xae\xbf\x52\x98\x9c\x78\x7e\xa2\x30\x0e\xcb\xb2\x6e\x03\xde\xff\ +\xcb\x5f\x3f\xdc\xbb\x77\xdf\x37\x18\x63\xa8\xd5\x6a\xf0\xb9\x0f\ +\x9f\x73\x54\x2a\x15\xd4\xeb\x75\x6c\xdc\xf8\x20\x76\xee\x7c\x11\ +\x6b\xd7\xdc\x03\x22\x42\x6e\xf0\x22\x2e\x5d\x1a\x5e\x08\x6c\xd9\ +\x26\x8a\xc5\xe2\x4f\x82\x80\xbf\x1d\x0a\x85\xd0\xd9\xd9\x09\xcb\ +\xb2\x6e\xef\xc1\xbe\x7d\x1f\xb5\xbf\xfb\xce\xbb\xff\x19\x1f\x9f\ +\x68\x99\x87\x08\x21\x20\x84\x80\x94\x12\x04\x42\xcd\xf7\x51\x9b\ +\x9b\xc3\xf7\x7f\xb0\x0d\x5b\xb6\x3c\xd6\xf8\xfd\x47\x9a\x00\x10\ +\x4a\xa5\x6b\x0f\x83\xb1\xfd\x52\x4a\x2c\x5d\xda\x81\x70\x53\x13\ +\xa4\x94\x68\x4e\x24\x1b\x80\x83\x87\x7a\x00\xc6\xd0\x7b\xb8\xf7\ +\xf5\x9e\xc3\x47\x7e\x21\xa4\x80\xaa\xaa\x90\x52\x2e\x40\xa4\x94\ +\x20\x49\x98\xab\xcd\x21\x95\x4e\x61\xf7\x9b\xaf\x41\xd7\x35\xbf\ +\x58\x2c\x7e\xc5\x34\xcd\x4f\x89\x08\xaa\xaa\xc2\x73\x3d\xa4\xd3\ +\x69\xa8\xaa\x0a\xdb\xb2\x6e\x3d\x23\x07\x7a\x1a\x0e\x92\x84\xd9\ +\xd9\x59\x0c\x8f\x8c\x7c\xe7\xec\xd9\xb3\xbf\x9d\x9a\x9c\xba\x2b\ +\x08\x02\xa8\xaa\x0a\xc6\xd8\xc2\x42\x56\xab\x73\xd8\xfa\xed\x2d\ +\x37\x1e\xfd\xfa\x23\x5f\x94\x52\x9e\xe3\x9c\xa3\xb5\xa5\x15\x6d\ +\xe9\xf4\xc2\xac\x00\x40\x51\x94\x5b\x15\x1c\x3c\x3c\xef\x52\x95\ +\x88\x70\xbd\x7c\x53\x08\x21\x50\xab\xd5\x50\x2a\x95\x50\xaf\xfb\ +\xeb\x54\x55\x79\x94\x29\xc8\x44\xc2\x91\xd9\x65\xcb\x96\xfe\x29\ +\x12\x0d\xff\x57\x51\x19\x34\x55\x43\xa6\x73\x39\x74\x5d\x87\x94\ +\x12\x77\x1e\x45\x51\xf0\x7f\x60\x84\x69\x65\x48\xcf\xfa\x14\x00\ +\x00\x00\x00\x49\x45\x4e\x44\xae\x42\x60\x82\ \x00\x00\x07\x65\ \x89\ \x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\ @@ -582,319 +1007,60 @@ qt_resource_data = "\ \x58\x7c\x1a\x1e\x38\x0b\x0d\x25\x78\x2f\x0c\x5b\x2c\xf0\xfe\x0f\ \xa4\xa4\xa5\x79\xe8\x4b\xcf\x5e\x00\x00\x00\x00\x49\x45\x4e\x44\ \xae\x42\x60\x82\ -\x00\x00\x07\x3c\ -\x89\ -\x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\ -\x00\x00\x18\x00\x00\x00\x18\x08\x06\x00\x00\x01\x97\x70\x0d\x6e\ -\x00\x00\x00\x06\x62\x4b\x47\x44\x00\xff\x00\xff\x00\xff\xa0\xbd\ -\xa7\x93\x00\x00\x00\x09\x70\x48\x59\x73\x00\x00\x0b\x13\x00\x00\ -\x0b\x13\x01\x00\x9a\x9c\x18\x00\x00\x06\xdc\x49\x44\x41\x54\x48\ -\xc7\x55\x95\x59\x6c\x54\xd7\x19\xc7\xff\xe7\x2e\x73\x77\xcf\x6e\ -\x7b\xc6\x0e\x60\x30\x13\xb1\x04\x12\x07\x95\x94\x90\x1a\xda\x4a\ -\x49\x53\x35\x54\xb4\x52\x9a\x86\xb4\x4d\xe9\xf2\xd4\x28\x6d\x43\ -\x15\xa9\x2f\x51\x93\x80\xd2\x2c\x28\x52\x15\xa9\x8d\x68\x95\xbc\ -\x44\x6d\x1a\x51\xd2\x46\x4d\xd8\x8c\xed\xb8\x40\x69\x4b\xd8\xed\ -\x19\xb0\xc7\x60\x7b\x6c\x33\x1e\xcc\xc0\x8c\x67\xee\xb9\xe7\x7c\ -\x7d\x18\x6c\xc8\x79\x3c\xba\xff\xef\xf7\x2d\xff\xef\x1e\x46\x44\ -\x08\x82\x00\x35\xbf\xbe\x16\x44\x84\xcf\x4e\x9d\xa1\x6a\x6d\x8e\ -\x40\x44\xe8\xe8\xc8\xd0\xa9\x33\xa7\x09\x9c\x73\xf4\xf7\x0f\xbc\ -\xc2\x39\x07\xab\xd7\xeb\xb8\x59\xad\xd0\xa9\xcf\xce\x40\x79\x60\ -\xfd\x43\x64\x1a\x26\xda\xda\xd3\x0c\xb7\xa2\x85\xa7\xaf\x16\xbb\ -\x87\x72\x59\xf2\x7d\x9f\xfe\x75\xec\x18\x29\x00\xd0\xdb\xd7\x3f\ -\x1b\x08\x7e\x64\xe9\x92\x0e\x1c\x3c\x74\x08\xba\xa6\x6d\x54\x00\ -\xa0\xe7\x50\xdf\xef\xbf\xfb\xf8\xf7\xf0\xe0\x86\x4d\x88\x44\x23\ -\x6c\x5d\x57\xd7\x00\x82\x20\x40\x10\x04\x10\x42\xe0\xe4\xc9\x53\ -\xef\x07\x41\x00\xce\x39\x10\x04\x01\xe3\x9c\xe3\x70\x4f\x2f\x15\ -\xa6\x26\x89\x73\x4e\x44\x04\x85\x31\x46\xfd\xfd\x03\x57\x97\x74\ -\x2c\x42\x22\x16\x47\xff\xc0\x00\xc6\xc6\x26\xb6\xb3\x6c\x2e\x07\ -\xd7\x75\xc9\x75\x5d\x10\x49\x9c\x3c\x79\x1a\x89\x64\x8c\xb1\xf5\ -\x5f\xd8\x48\xb3\xb3\xb3\xf0\x03\x1f\x6f\xbd\xf5\x3b\x28\xaa\xa2\ -\xdc\x7b\xef\x1a\x52\x5e\xda\xf9\x1b\x16\x8f\xc7\x91\x6e\x49\x61\ -\xa6\x58\x44\x24\xd2\x44\xc9\x78\x02\x8c\x88\x20\x84\x00\x11\x31\ -\xc6\x98\x02\x40\x9c\x3b\x7f\x61\xa0\x52\x99\xdb\x90\x6a\x6b\x46\ -\xba\x35\x8d\xf2\xf5\xeb\xc5\x58\x2c\x96\x94\x52\x40\xc1\xad\xc3\ -\x18\x23\x55\x55\xc5\x3f\x3e\xfa\x27\x01\x6c\xc3\x5d\x8b\xd2\x48\ -\xc6\x93\xd0\x35\x0d\x60\x48\x1c\xea\x39\x42\xa3\xa3\x57\x9e\x66\ -\x44\x84\xb3\xe7\x06\x11\x08\xfe\x66\xf9\x7a\xf9\x99\x74\x3a\x05\ -\xaf\xc9\x45\xc4\x0b\x43\xd3\x35\x0c\x65\xb3\x18\x1f\x2f\x20\x95\ -\x6e\xc5\xdd\x99\x0c\x63\x44\x84\x07\xd6\x7f\x89\x0a\x93\x13\x10\ -\x42\x62\xd7\xae\x97\xf1\xd4\x53\x4f\x80\x73\x8e\x81\xa3\x47\xe1\ -\xd7\x39\xc2\x61\xef\x52\xe9\x5a\xa9\xb3\xab\xab\xab\x91\xd2\x93\ -\xdb\x9e\x78\x36\x9d\x4a\xa3\xb5\xa5\x05\xaf\xbd\xfa\x3a\x56\xaf\ -\xee\x42\x26\xb3\x0a\x9e\xe7\xc1\xf5\xec\xfd\x3e\xf7\x3b\xd3\xe9\ -\x74\x23\x75\x22\xc2\x81\xfd\x3d\xf0\xb9\xcf\xc6\xc6\xc7\x9b\x4e\ -\x9c\x38\xf1\xb7\x2b\x57\xc6\x36\xc9\x40\xe0\x97\x3b\x9e\xfd\x51\ -\xba\x2d\xb5\x67\xd5\x8a\x95\x60\x8c\x35\x8a\x9d\x9f\xdd\xfc\xcc\ -\x38\xe7\x98\x9e\x9e\x5e\xdf\xd3\xd3\x77\x64\x24\x9f\x7f\x4e\x92\ -\x9c\xbf\x67\x44\x74\xbb\x4b\x00\x34\xc6\x18\x14\x45\xd1\xff\x77\ -\xf2\xf4\xb1\x44\x32\xde\xed\x78\xee\xab\x22\x10\x54\x2a\x95\xbe\ -\x09\x80\x01\x58\x10\x38\x00\x02\x55\x55\xf1\xf1\x27\x07\x2b\xf3\ -\x9d\x32\x42\x21\x28\x8a\x82\x64\x32\xb9\xf7\xe2\xa5\xe1\x67\xee\ -\x14\x54\x1a\xed\x3d\xff\x46\x34\x1a\xd1\x4d\x2b\x04\xdb\xb6\x61\ -\x99\x16\x82\x20\xc0\xb1\x7f\x1f\xc7\xf0\x70\x7e\x37\x00\x28\x8c\ -\x31\x30\xc6\x30\x57\xab\x3f\x34\x3a\x7a\xf9\xe7\x91\x68\x04\x8e\ -\xe3\xc0\x73\x1c\xa8\x8a\x82\x89\xc2\x04\xaa\x95\x1a\x9a\x9b\x93\ -\x8d\xbc\xcf\x5f\xc8\x82\x31\x86\x5c\x2e\xdb\x77\x77\x26\x03\xc7\ -\xb1\xe0\xd8\x0e\x34\x4d\x47\xad\x56\x43\x36\x77\x11\x9a\xaa\x63\ -\x79\x66\xd9\x63\x00\xa0\x08\xc9\x01\x12\x1f\x64\x32\xcb\x11\x8d\ -\x47\x60\xdb\x36\x0c\xd3\x00\x63\x0c\x17\x86\x06\x21\x85\x84\xeb\ -\xba\xf5\x42\xa1\xf0\x77\x00\x50\x86\x2e\x64\x7f\xf6\xad\xad\x8f\ -\x6f\xdd\xbc\xe9\xab\xd8\xf6\xe4\xd3\xf0\x1c\x0f\x66\xc8\xc0\x4c\ -\x69\x06\xe3\x63\x05\xc4\x62\x71\x5c\x9b\x9d\x59\x5c\x2c\x16\x01\ -\x00\xca\xd4\xe4\x74\x53\x2c\x16\x43\x32\x99\x44\x3e\x9f\x47\x5b\ -\x7b\x07\xde\x7b\xef\xcf\xb8\x34\x3c\x8c\x58\x3c\x8e\x20\xe0\x7d\ -\x8c\xa9\x53\x2b\x57\xae\x68\x08\x56\xac\x5e\xf1\x32\x63\x0c\xae\ -\xe3\xa2\xc9\xf5\x90\x6a\x69\xc5\x0b\x2f\xbc\x88\xe3\x47\x4f\x20\ -\x91\x88\x22\x10\xbc\xdb\x30\x34\x08\x21\x1b\x02\x92\x12\x3b\x7e\ -\xb5\xc3\xf6\x3c\x0f\x9e\xe7\x21\x1a\x8d\x20\x16\x8d\xe1\x8f\x7b\ -\xde\x41\xad\x56\xfb\x83\xae\xeb\xb8\x6f\xed\x7d\x48\xc4\xe2\x0d\ -\x01\x24\x83\xe7\x3a\xf5\xed\x3f\xde\xce\xba\x37\x77\x6f\x8f\x27\ -\x13\x81\xeb\x79\x58\x75\xcf\xaa\xaa\xae\xeb\x3f\x4d\xb5\xa6\xe0\ -\x79\x1e\x88\xe8\xb6\xf9\x84\x10\x0b\xfe\x20\xa2\x79\xa3\x31\xba\ -\xf5\x95\xaa\xaa\x28\x16\x67\xbe\x76\xfa\xf4\x99\x0f\x6e\x56\x2a\ -\x56\x3c\x1e\x43\x3c\x11\x87\xeb\x3a\x50\x15\x65\x4e\x53\xb5\xb7\ -\x2d\xcb\xda\x6d\xdb\x76\x1e\x00\xa4\x24\x00\x04\x5d\xd7\x3f\x0f\ -\x20\x22\x0d\x40\x2b\x63\xac\x00\x40\xcc\x07\x1f\x1e\xc9\xbf\x74\ -\x61\x30\xfb\xeb\x48\xb8\x09\xae\xeb\xc2\x71\x6c\x84\x0c\x1d\x96\ -\x65\xc1\xb5\x1d\x18\x86\x01\x45\x51\x50\x2e\x97\xe9\xc6\x8d\x1b\ -\x6f\xa4\x52\xa9\xe7\x18\x08\x4c\x51\x3f\x07\x60\xb7\x8b\x68\x54\ -\xc1\x39\xf7\x8e\x1e\x3d\xfe\x69\xdd\xf7\xd7\xc4\xe3\x71\x58\xb6\ -\x09\xc7\xb1\x61\x18\x06\x4c\xd3\x84\x63\xdb\xd0\x35\x1d\x44\x04\ -\xdf\xf7\x31\x39\x35\x85\xfc\xe8\x28\xaa\x95\xb9\xe2\x97\x37\x6f\ -\xba\xdf\xb2\xcc\xcb\x77\x6e\x0f\x01\x20\xc6\x18\x34\x4d\x43\xa9\ -\x74\xed\x91\x8f\x3f\x39\x50\x56\x35\x6d\x4d\x7b\x7b\x3b\x22\x91\ -\x08\x3c\xcf\x85\x69\x99\x70\x1c\x07\xae\x6d\x43\x57\x35\x10\x11\ -\xaa\xd5\x2a\x86\xf3\x23\x18\x1e\xc9\x43\x04\x12\x9e\xe7\x25\x86\ -\x86\xb2\x3f\x04\x00\x0d\x00\xa6\xa6\xa7\xef\xa0\x50\xe8\xf2\xe8\ -\xd8\x87\x53\x53\x57\x1f\x6e\x6f\x6f\x87\x63\x3b\x30\xcc\x10\x42\ -\xa6\x0e\x33\x64\xc0\x32\x2d\x84\x42\x21\xa8\x8a\x02\x41\x84\x72\ -\xb9\x8c\x5c\xee\x22\x2a\x95\x2a\x84\x10\x30\x8c\x10\x16\x2f\x59\ -\x34\xd6\x96\x4a\xed\x59\x00\x14\xaf\xce\x34\x16\x49\xd3\xd6\x15\ -\x26\x27\x07\x38\xe7\xa1\x8e\x25\x8b\x61\x58\x06\x4c\x33\x84\x50\ -\x28\x04\xc3\x34\x61\x19\x06\x74\x4d\x87\xa2\x28\x08\x84\x40\x71\ -\xa6\x88\xa1\xa1\x1c\x38\x0f\x40\x44\x08\x87\xc3\x88\x46\x23\x7d\ -\x86\x11\xea\x9e\x29\x95\xd0\xd2\xdc\xdc\x00\x98\xa6\x89\xc1\xc1\ -\xec\xf3\xbd\xbd\xbd\xbb\x84\x94\x58\xbc\xe8\x2e\x68\xba\x8a\x75\ -\xeb\xee\x47\x93\xd7\x04\x92\x12\x44\x8d\x87\x4f\x61\x0a\x38\xe7\ -\x18\x9f\x2c\x20\x37\x94\x03\x11\x60\x9a\x16\xa2\xd1\x08\xca\x37\ -\xae\xbf\x52\x98\x9c\x78\x7e\xa2\x30\x0e\xcb\xb2\x6e\x03\xde\xff\ -\xcb\x5f\x3f\xdc\xbb\x77\xdf\x37\x18\x63\xa8\xd5\x6a\xf0\xb9\x0f\ -\x9f\x73\x54\x2a\x15\xd4\xeb\x75\x6c\xdc\xf8\x20\x76\xee\x7c\x11\ -\x6b\xd7\xdc\x03\x22\x42\x6e\xf0\x22\x2e\x5d\x1a\x5e\x08\x6c\xd9\ -\x26\x8a\xc5\xe2\x4f\x82\x80\xbf\x1d\x0a\x85\xd0\xd9\xd9\x09\xcb\ -\xb2\x6e\xef\xc1\xbe\x7d\x1f\xb5\xbf\xfb\xce\xbb\xff\x19\x1f\x9f\ -\x68\x99\x87\x08\x21\x20\x84\x80\x94\x12\x04\x42\xcd\xf7\x51\x9b\ -\x9b\xc3\xf7\x7f\xb0\x0d\x5b\xb6\x3c\xd6\xf8\xfd\x47\x9a\x00\x10\ -\x4a\xa5\x6b\x0f\x83\xb1\xfd\x52\x4a\x2c\x5d\xda\x81\x70\x53\x13\ -\xa4\x94\x68\x4e\x24\x1b\x80\x83\x87\x7a\x00\xc6\xd0\x7b\xb8\xf7\ -\xf5\x9e\xc3\x47\x7e\x21\xa4\x80\xaa\xaa\x90\x52\x2e\x40\xa4\x94\ -\x20\x49\x98\xab\xcd\x21\x95\x4e\x61\xf7\x9b\xaf\x41\xd7\x35\xbf\ -\x58\x2c\x7e\xc5\x34\xcd\x4f\x89\x08\xaa\xaa\xc2\x73\x3d\xa4\xd3\ -\x69\xa8\xaa\x0a\xdb\xb2\x6e\x3d\x23\x07\x7a\x1a\x0e\x92\x84\xd9\ -\xd9\x59\x0c\x8f\x8c\x7c\xe7\xec\xd9\xb3\xbf\x9d\x9a\x9c\xba\x2b\ -\x08\x02\xa8\xaa\x0a\xc6\xd8\xc2\x42\x56\xab\x73\xd8\xfa\xed\x2d\ -\x37\x1e\xfd\xfa\x23\x5f\x94\x52\x9e\xe3\x9c\xa3\xb5\xa5\x15\x6d\ -\xe9\xf4\xc2\xac\x00\x40\x51\x94\x5b\x15\x1c\x3c\x3c\xef\x52\x95\ -\x88\x70\xbd\x7c\x53\x08\x21\x50\xab\xd5\x50\x2a\x95\x50\xaf\xfb\ -\xeb\x54\x55\x79\x94\x29\xc8\x44\xc2\x91\xd9\x65\xcb\x96\xfe\x29\ -\x12\x0d\xff\x57\x51\x19\x34\x55\x43\xa6\x73\x39\x74\x5d\x87\x94\ -\x12\x77\x1e\x45\x51\xf0\x7f\x60\x84\x69\x65\x48\xcf\xfa\x14\x00\ -\x00\x00\x00\x49\x45\x4e\x44\xae\x42\x60\x82\ -\x00\x00\x03\x66\ -\x89\ -\x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\ -\x00\x00\x10\x00\x00\x00\x10\x08\x06\x00\x00\x00\x1f\xf3\xff\x61\ -\x00\x00\x00\x19\x74\x45\x58\x74\x53\x6f\x66\x74\x77\x61\x72\x65\ -\x00\x41\x64\x6f\x62\x65\x20\x49\x6d\x61\x67\x65\x52\x65\x61\x64\ -\x79\x71\xc9\x65\x3c\x00\x00\x03\x08\x49\x44\x41\x54\x78\xda\x84\ -\x53\x6d\x48\x53\x51\x18\x7e\xce\xfd\xd8\x75\x9b\x8e\xdc\x2c\xdd\ -\x4c\x5d\x4e\xa7\xc9\xe6\xb7\xf6\x61\x61\x11\x14\x52\x16\xf5\xc7\ -\x0a\x0b\xa2\x3f\x41\x51\x41\x61\x7f\x0a\x84\xa2\x9f\xfd\xeb\x67\ -\x7f\xfa\x51\x44\x50\x91\x14\x15\x5a\x14\x41\x25\x6d\x44\x59\x68\ -\x69\xd9\x74\xa6\x6d\xd7\x7d\x38\xb6\xdd\x6d\x77\xa7\x73\x2d\x4d\ -\x84\xe8\x81\x87\xf7\x7d\xef\x7b\xde\xe7\xbe\xe7\x9c\xf7\x10\x30\ -\x48\x84\x20\x4f\xb3\xf8\x8b\xb3\x1b\xe9\xbc\xe5\x38\x14\xb3\x74\ -\x2f\x73\xab\x18\x47\x28\x45\x6f\x36\x0b\xff\xc2\x3a\xde\xc6\xb2\ -\x06\xcd\x61\x24\x4b\x04\xbe\x87\x09\x48\x82\x89\x8a\xb8\x62\xaf\ -\x76\x75\x5a\x4a\xcb\x9d\x31\x85\xae\x9d\x0d\xce\x15\x7c\xf1\xa3\ -\xef\x67\x18\xd0\xc8\xe1\x1f\xf0\xcf\x01\x43\x53\xc4\xf1\x33\x04\ -\x57\x20\x12\x29\xcc\x31\x5b\x84\x4d\x7b\xf6\x18\xb5\x78\xcc\x0f\ -\x07\x23\x34\x0a\xcb\xea\x0a\x19\x4f\x32\xda\x19\xc7\x53\x04\x91\ -\x99\x10\xc4\xde\xd3\xa7\x61\x30\x1a\xa1\xb2\xde\xb5\x98\xe7\xb0\ -\x85\xe5\xc7\xb4\x02\x81\x2e\xa9\x66\xfe\xb9\x86\xd6\xd6\xfd\xee\ -\xba\x3a\xcb\x3b\x8f\x47\x9e\x78\xe7\x8d\xc5\x13\x88\x4a\x3a\x1d\ -\x94\x78\x1c\x82\x28\x22\xae\x6d\x8b\x47\x23\x5b\x7e\x6d\x5e\xa0\ -\xdd\xf9\x77\xe7\xcf\x3e\xd3\x0d\xbd\xa7\x3a\xac\x2e\xa7\x15\x43\ -\x9f\x6d\xd6\xae\x43\xde\xb0\x51\x44\x74\x6c\x78\x18\xf6\x8a\x0a\ -\x68\x96\xc5\x1a\x4a\x16\x6a\x84\xad\xce\xc5\xfa\xae\xc1\x69\x53\ -\x65\xbd\xdb\x8e\x74\x32\x09\xcd\xea\xf2\x4c\xb9\x0e\x5b\x94\x0c\ -\xdc\xba\xe9\x6d\xda\xbe\xa3\xd1\xf3\xe4\xb1\x37\xf7\xb7\x40\xc1\ -\xa2\x40\x26\xbb\x28\xc0\x75\xd5\x29\x23\xc9\xb9\xb9\x8d\x99\x74\ -\x1a\x2a\xe3\xae\xfa\xf4\xc7\xf1\x92\xa2\x60\xce\xc4\x0f\x4b\x85\ -\xb3\x0a\xcf\xfb\x6e\xd2\x57\xdd\x35\x1f\x73\x43\xc9\x47\x33\x25\ -\x26\x4c\x15\xe7\x82\x27\xb5\x07\x41\x09\x87\x7c\x75\x66\xc8\x28\ -\x66\xaa\x4b\x2a\xdd\x4d\xec\x42\x85\xf0\x6c\x20\xf5\x32\x3c\xfa\ -\x4d\x3a\xd1\xe3\xd4\xd7\xb4\x54\xa5\x14\x17\xa6\xdb\xaa\x6d\x85\ -\x5b\xda\x0b\x9e\xe6\x04\x12\xe1\x3c\xc1\x8e\x2c\xfd\xc2\x7f\x6d\ -\xba\x8c\x41\x7d\x07\x1e\x99\x8e\x40\xa5\x24\xc0\x7d\xb8\xb1\x3e\ -\x96\x26\xb6\x57\xaf\x07\xfc\x74\x77\x77\x45\xc1\x6a\x87\x79\x2a\ -\x91\xc0\xd9\x8e\xa3\xb8\x3d\xe5\x41\xe9\xaa\x62\x93\xcb\x5c\x5e\ -\x6b\xa0\xba\x35\xdf\x02\x93\xe2\x92\x39\xa0\xcd\xfd\xa6\xc3\x3b\ -\x83\xf2\x2c\x69\x6c\x6e\x41\x24\x1a\x13\xef\x8f\xb4\xbe\x1f\xf7\ -\x49\x93\x49\x76\x26\xb2\x2c\x43\xb3\x1a\xd4\x54\x46\xaa\x36\x97\ -\xb9\x69\x54\x69\x23\x7c\x77\xdf\x0a\x70\xe2\x7e\x83\x24\xd4\x1c\ -\xeb\x74\xef\x5b\x19\x19\x2a\xb6\x4b\x32\xc6\x15\x0b\x82\xf9\x95\ -\xa1\xab\x0f\xfb\x3d\x49\xce\x17\x6b\x19\xf6\x0e\x0c\x6e\xf0\x6f\ -\xa3\x69\x55\x0f\x45\x35\xd0\x74\x36\x07\xa3\xd1\x27\x84\x3f\x70\ -\xe7\x4c\xe7\xfa\xf2\xee\xa6\x2a\xeb\x5a\x4b\x7e\x9e\xe4\xf3\x4d\ -\xe3\xd2\xde\x52\x9c\xbf\xeb\x43\x59\x99\x15\x72\x28\x9a\x7a\xfb\ -\xe9\xfb\x68\x5f\xff\xeb\x7b\xea\x83\x93\xd7\x97\x0d\x9e\xcc\x41\ -\x89\x36\xd7\xda\xcd\xf5\xd9\x4c\x76\xfe\x2d\x2d\x6f\x97\xaa\xd0\ -\xd5\x39\xac\x35\x90\x4c\xe5\xfc\xe6\x9e\x11\xed\x41\x2d\x61\x90\ -\xf0\xf5\x87\x2e\xc0\xda\xd0\x4e\x79\x29\x41\x05\x7d\x0c\x82\x3e\ -\xde\x36\x7d\xf5\xcd\xcb\xa2\xe3\xeb\x48\x26\x69\x20\x99\x84\x91\ -\xa8\x8a\x1e\x3f\xbc\x2f\xe8\xec\xe8\x45\x1a\x99\x04\x8d\x4c\x2c\ -\xb6\x40\xfe\x0c\x85\x05\xff\x87\xac\xfd\x71\xf9\xc7\x5f\x02\x0c\ -\x00\x00\x31\x44\x70\x94\xe4\x6d\xa8\x00\x00\x00\x00\x49\x45\x4e\ -\x44\xae\x42\x60\x82\ -\x00\x00\x02\xcc\ +\x00\x00\x03\x34\ \x89\ \x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\ \x00\x00\x10\x00\x00\x00\x10\x08\x06\x00\x00\x01\x68\xf4\xcf\xf7\ \x00\x00\x00\x06\x62\x4b\x47\x44\x00\xff\x00\xff\x00\xff\xa0\xbd\ \xa7\x93\x00\x00\x00\x09\x70\x48\x59\x73\x00\x00\x0b\x13\x00\x00\ -\x0b\x13\x01\x00\x9a\x9c\x18\x00\x00\x02\x6c\x49\x44\x41\x54\x38\ -\xcb\x4d\xd2\xbd\x6b\xdd\x65\x14\x07\xf0\xcf\xaf\xb9\x49\x9a\x10\ -\x69\x08\x06\x6a\x2c\x82\xf4\x25\x2e\xed\x50\x87\x4e\x85\x2e\x42\ -\xb2\x34\xe0\xe4\x58\x10\xea\x58\x69\x57\x47\x57\x83\xd2\x49\xc1\ -\xad\xfe\x01\xba\x28\xb8\xa8\x93\x83\xa0\x4b\x41\x89\x10\x1b\x43\ -\x20\x37\x37\xf7\xe6\xbe\xfe\xee\xef\x2d\xc7\xe1\xb9\x6a\x1f\x38\ -\x1c\x9e\xf3\x7c\xbf\xe7\x7c\x9f\x73\x4e\x16\xb7\x6e\x71\xf5\x6a\ -\x2d\xae\x5f\x2f\x82\x10\x0f\x1e\x6c\xc6\x8d\x1b\xc4\xc5\x8b\xf1\ -\x17\x21\xee\xdf\xbf\x19\x77\xee\x44\xac\xad\x45\xcc\xcf\x47\x36\ -\xc4\x11\x91\xe3\x67\x64\xb1\xb3\xc3\xa5\x4b\xbf\xd8\xdf\xff\xd1\ -\xf3\xe7\x4f\xc4\xce\x4e\xc4\x95\x2b\x11\xab\xab\x31\xa0\x16\x1b\ -\x1b\x11\x44\x45\xfc\x40\x64\x07\xc4\x18\x2f\xb0\xc7\x6e\x16\xdb\ -\xdb\xac\xaf\x7f\xab\x69\xb6\x74\x3a\x9c\x9d\x31\x1e\x27\xdf\xed\ -\x2e\xb6\x8c\xc7\x7b\x8e\x8f\xaf\x39\x3c\xe4\xf4\x94\xf3\x73\xd0\ -\x8f\xd0\xa6\x10\xcb\xcb\x4f\x83\x28\x67\x56\x10\x6d\xe2\x27\xe2\ -\x19\x91\x75\x92\x6e\x6d\x86\x7d\x56\x06\xe8\xa2\xe6\x83\xd7\xf9\ -\x22\x8b\x7b\xf7\xd8\xd8\x60\x71\xf1\x13\x79\xfe\xc8\xd9\xd9\x6f\ -\xda\xed\xf7\xb4\xdb\x7f\xea\xf5\xb4\x5c\xbe\xbc\x60\x7e\xbe\xd0\ -\xef\xd3\xe9\x30\x1a\xbd\x6d\x30\xd8\x33\x99\x7c\xa7\x28\xb6\x5b\ -\xca\x72\xa2\xdb\xe5\xe0\x20\x89\xac\x6b\xea\x5a\x33\x1c\x6e\x9d\ -\xb1\xd9\x72\x7c\x3c\xa7\xdd\xe6\xf0\x90\xe9\x14\x54\x11\x4e\xd0\ -\xe1\xab\x96\xa3\x23\xfa\xfd\xf4\x18\x21\x90\xe3\x24\x89\x7f\x23\ -\x8b\x56\x2b\x9a\xba\x56\x63\x0e\x25\xfe\xc6\xef\x18\xf0\x59\xd6\ -\xe6\xd3\x21\x8f\x4a\x34\x29\xe8\x45\xfa\xb6\x55\xb2\xd6\x84\x0f\ -\x8f\xd9\xef\x26\xa0\x5e\x02\x8d\x96\x79\xe5\x35\x64\x71\xf7\x2e\ -\x6b\x6b\xac\xac\xb0\xb0\xf0\x58\x96\x7d\xac\xae\x97\x14\x45\xd2\ -\x35\x9d\x52\x14\xe4\x39\x93\x49\x6e\x32\xf9\xc8\x64\xb2\x2b\xcf\ -\x29\xcb\xd9\x42\x2c\x2d\x7d\xee\xc2\x85\x87\xaa\x2a\x01\x87\x43\ -\x46\xa3\x44\x2e\x4b\x9a\x26\x59\x59\xa6\x58\x9e\x8b\xe9\x74\xb7\ -\xe2\x49\x4b\x51\x3c\x55\x96\x0f\x4d\xa7\x89\xd8\xeb\xa5\x4d\xc8\ -\x73\xaa\x8a\x08\x20\xcb\xa8\x6b\x65\x84\x1c\x13\x1e\x17\xcc\x65\ -\x71\xfb\x76\xa1\xae\x17\xe4\x79\x5a\xa3\xe1\x30\x91\x9b\xe6\x7f\ -\x32\xff\xb5\x77\x34\x6b\xd4\x20\x25\x39\x69\x39\x3a\x3a\x50\x55\ -\xd7\x54\x55\xaa\x58\x96\xa2\x69\xbc\x7c\xce\x67\xed\x1f\xa6\xe1\ -\xe9\xe2\x0c\x05\x07\x59\xc3\xcd\x29\xbf\x56\xcc\xd5\xb3\x4a\x90\ -\xcd\x7c\x83\xe9\x4b\xe4\x53\xf4\x53\xc2\x66\x81\xb7\xb2\x6e\x92\ -\xb5\x30\xe6\xeb\x9c\xad\x7f\xe7\xd9\xa0\x4a\x55\xe4\x33\xc9\xa3\ -\xd9\x1d\xdf\x2c\xf3\xee\xab\x34\x59\x0f\xe3\x19\xa0\x9f\xfc\x9b\ -\x23\xde\x1f\xf1\x4e\xce\x66\x91\x12\xfd\xd1\xf0\xfd\x1c\x5f\x2e\ -\xb1\x7f\x09\xeb\x33\xfb\x07\x6a\x4f\x76\xe7\x35\x05\x41\x4b\x00\ -\x00\x00\x00\x49\x45\x4e\x44\xae\x42\x60\x82\ -\x00\x00\x02\xf0\ -\x89\ -\x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\ -\x00\x00\x10\x00\x00\x00\x10\x08\x06\x00\x00\x00\x1f\xf3\xff\x61\ -\x00\x00\x00\x19\x74\x45\x58\x74\x53\x6f\x66\x74\x77\x61\x72\x65\ -\x00\x41\x64\x6f\x62\x65\x20\x49\x6d\x61\x67\x65\x52\x65\x61\x64\ -\x79\x71\xc9\x65\x3c\x00\x00\x02\x92\x49\x44\x41\x54\x78\xda\x84\ -\x53\x5f\x48\x53\x61\x14\x3f\xf7\xee\x6e\x22\x0b\x4c\x6b\xac\x60\ -\x6a\xb5\x39\xdd\x06\x36\xd9\x70\x6b\xb8\x31\xd4\xb2\x35\x82\x3d\ -\x4c\x50\x50\x08\x1f\x24\x84\x24\x56\xea\xc3\xdc\x2a\x63\x36\x1d\ -\x4d\xa1\xb0\xf0\xc1\x17\x1f\x7c\xb0\x3f\x0f\x51\x96\x12\xe8\xa6\ -\x84\xa6\xeb\x45\xa9\x84\x22\x25\x08\xb7\x97\xec\x45\xe6\xee\xdd\ -\xed\x7c\x97\xb5\x36\x1a\x74\xe0\xc7\xe1\xfb\x9d\xf3\xfb\x9d\xf3\ -\xdd\x8f\x4b\x41\x4e\xd0\xc5\xc5\x20\xa9\xa8\x80\xc3\xdd\x5d\x48\ -\x1f\x1c\x40\x75\x43\x03\x68\x6d\x36\xa0\x45\x22\xa0\x69\x5a\x85\ -\x2d\x8d\x90\x1f\x3f\x18\x28\x10\x4a\xa3\x11\xaa\x4d\x26\x41\x98\ -\x89\xaa\x74\x3a\xdd\x38\x3b\x34\xf4\xf8\x0f\x41\x21\xdc\x7e\xff\ -\xd5\x3c\x83\x53\x7a\xbd\x20\x16\x31\x79\x74\x55\x9a\xe3\x9a\x66\ -\x03\x81\x47\xd1\xf5\x75\xf8\xb0\xb5\x05\x75\x3a\x1d\x58\xb1\x0f\ -\x79\x4a\xe8\x2c\xaf\xab\x83\xd3\x48\x30\xcc\x3f\x0b\x55\x71\x1c\ -\xd7\xfc\x34\x18\x9c\xc0\x0c\x89\xfd\x7d\x28\x2d\x2b\xa3\x30\xf3\ -\xa4\xc8\x11\x03\x53\x47\x07\x88\xc4\xe2\x42\x37\x51\xe3\x84\xf3\ -\xcf\x42\xa1\x87\xc9\x64\x12\x44\x78\x1d\x8d\x52\x09\xdf\xe3\x71\ -\xbe\x5c\x2e\x17\x1a\xb0\x4e\xd3\x50\x38\xd4\x2c\xc7\x5d\x78\x82\ -\xe2\x58\x2c\x06\x57\x70\xc8\xd6\xe6\x26\x9c\x51\x28\xc0\x6e\x30\ -\x80\xba\xb2\x12\x2e\x79\x3c\xd7\x70\x83\x85\x42\x06\xd5\x1c\xcb\ -\xb6\x3c\x0f\x87\x1f\xbc\x5f\x5b\x83\xbb\x7e\x3f\x1c\xe0\x8b\xdc\ -\x1a\x1c\x24\x2b\x0b\x1f\xd6\xd1\xdb\xdb\x8b\xd3\x17\xf0\x1e\xdb\ -\x4c\x01\xf1\xc5\x17\x13\x13\xe3\xef\x56\x56\xe0\x8e\xd7\x9b\x2d\ -\x04\x46\x47\x41\x52\x54\x04\x2d\x3d\x3d\xd7\x29\x8a\x9a\x47\xa3\ -\xcf\x84\xcf\x35\xa8\x61\x59\xd6\xf1\x6a\x72\x32\xbc\xbc\xb4\x04\ -\xbe\xfe\xfe\x6c\x61\x64\x6c\x0c\x8c\xf5\xf5\xd0\xdc\xdd\xed\x41\ -\xf1\x1b\x51\x46\x9c\x6b\xa0\x21\xe2\xd7\x53\x53\xf7\x23\x8b\x8b\ -\xe0\xef\xeb\xcb\x8a\xef\x85\xc3\x60\xb6\x58\xa0\xb1\xab\xeb\x06\ -\x8a\xe7\x50\xfc\x29\x77\x65\x62\xa0\xe1\x52\x29\xe7\xfc\xf4\x74\ -\x28\x8a\xe2\x00\xae\x2d\x91\x48\x84\xe2\xed\x60\x10\x2c\x56\x2b\ -\xd8\x3b\x3b\xfb\x80\x88\x19\x26\x2b\xfe\x8a\xdf\xe7\xcb\xea\x2a\ -\x30\x38\xf9\xf2\xdb\x99\x99\x91\xbd\x78\x1c\xc6\x87\x87\x41\x2a\ -\x95\x0a\x0d\x37\x7d\x3e\x41\x6c\x6b\x6f\x1f\xc0\xc9\x2f\x71\xf2\ -\x47\xc2\xef\xe0\xab\xec\x6c\x6c\xfc\x5d\x41\xdf\xda\xaa\x3d\xeb\ -\x76\x0f\xfc\xe4\x79\x7e\x2e\x12\xe1\x5d\x2e\x17\x3f\x1f\x8d\xf2\ -\xbf\xf0\x4c\x78\x52\x37\xb4\xb5\x81\xa2\xb6\xb6\xf0\x83\x9f\xd0\ -\x6a\xa1\xc6\xe9\xd4\xa9\x1d\x0e\x2f\x31\xd9\xde\xdb\xe3\xf7\x31\ -\x93\x33\xe1\x49\xfd\x7f\x41\xfe\x98\x92\x12\x95\xaa\x49\x6e\x36\ -\x0f\x11\x13\x92\x8f\xaa\x54\x76\xe4\x8f\x21\x4a\x49\x1d\x71\x04\ -\x51\x8c\x28\x42\x88\x33\x3a\x8a\xca\x1c\x4e\x22\x8e\x4b\x64\x32\ -\x85\x58\x26\x3b\x97\x4a\x24\x96\x0f\x13\x89\x6f\xc8\xa5\x10\x6c\ -\x26\x13\x1c\xe6\x70\x04\xdc\x6f\x01\x06\x00\x2d\x06\x04\x62\x7f\ -\xe8\x51\x71\x00\x00\x00\x00\x49\x45\x4e\x44\xae\x42\x60\x82\ -\x00\x00\x02\x75\ -\x89\ -\x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\ -\x00\x00\x10\x00\x00\x00\x10\x08\x06\x00\x00\x00\x1f\xf3\xff\x61\ -\x00\x00\x00\x19\x74\x45\x58\x74\x53\x6f\x66\x74\x77\x61\x72\x65\ -\x00\x41\x64\x6f\x62\x65\x20\x49\x6d\x61\x67\x65\x52\x65\x61\x64\ -\x79\x71\xc9\x65\x3c\x00\x00\x02\x17\x49\x44\x41\x54\x78\xda\xa4\ -\x93\x4f\x6b\x13\x51\x14\xc5\xcf\x9b\x37\x93\x49\xc6\xa9\x25\xa3\ -\x15\x63\x16\x2e\x02\x06\xad\x0b\x45\xc1\x95\x5f\xc0\x3f\x0b\x21\ -\x1b\xa5\x20\xb8\x31\xea\x7c\x82\xac\xf4\x2b\x54\x5b\x23\x88\x1b\ -\x4b\xb3\x70\xe1\x42\xb7\x82\x20\xe8\x42\xbb\x70\xa1\x62\x36\xa2\ -\xa0\x51\xda\x1a\xc2\x4c\xa6\x26\x93\xcc\x78\xef\x75\x5a\xb4\x2a\ -\x54\x7c\x70\xb8\x2f\x93\x7b\x7e\xf7\xbc\x37\x89\xaa\xd5\x6a\x50\ -\x4a\x9d\x06\xb0\x07\xff\xb6\x3e\xa5\x69\xfa\xc0\x4c\x92\x84\x3f\ -\x94\x9b\xcd\xe6\xcd\x38\x8e\xb7\xe4\xb4\x2c\x0b\xf5\x7a\xfd\x12\ -\xef\xcd\xd1\x68\xc4\xd5\x18\x0c\x06\xf0\x7d\x1f\x0c\x64\x11\x5d\ -\xea\x78\x3c\x46\x18\xf6\xa9\xa6\x62\xf4\x3c\x0f\xf3\xf3\xd7\x41\ -\x3e\xe3\x67\x80\xe2\xca\x86\x6a\xb5\x0a\x4e\xf2\xed\x68\x05\xa3\ -\xc7\x2f\xb1\xb2\xf2\x95\x9e\x6b\x32\xdb\xb0\xed\x3c\xa2\x28\x60\ -\x33\x4b\x09\x20\x8b\x6d\xf0\x43\x9e\xc6\x49\x58\x69\x79\x07\x56\ -\x57\xbb\x64\x88\x91\xcf\x6f\x13\xb3\x65\xe5\xa9\x27\x16\x00\xf9\ -\x8c\x0d\x23\x49\x33\x48\x00\x34\x39\x8a\x22\xa8\xbd\xbb\x08\x94\ -\x60\xf2\x60\x05\xe5\xd9\x3a\x26\x4f\x1c\x13\x90\x69\xda\x92\x90\ -\x3d\xec\x35\x86\xc3\x21\x48\x3f\x40\xa5\x22\x9d\x37\x84\x73\xed\ -\xbc\x5c\xd6\xf6\xe9\x0a\x3c\xff\x14\x7a\x8d\x16\x01\x8b\xa8\x35\ -\xaf\x12\xc0\x94\x04\xec\x61\xef\xc6\x11\xb8\x26\xef\xbf\xa0\xdf\ -\x5d\x43\xf7\xe1\x53\xb8\x07\xf6\xa1\x78\xf9\x24\xfa\xb7\x1f\xc1\ -\x75\x8b\x48\x5b\x4b\xb8\x77\xf7\x19\xbf\x72\x49\xb0\x7e\x04\x93\ -\x29\xb4\x24\x8e\xe3\x38\xe8\xf5\x7a\x30\x0c\x0b\xfa\xed\x07\x84\ -\xfe\x2c\x0a\x85\x09\x0c\x0c\x2d\x46\x5e\xb6\xad\xd7\x13\x68\x01\ -\xb4\xdb\x6d\x94\x4a\xa5\x1c\x37\x34\x1a\x8d\x2d\xfd\x0e\xb8\x37\ -\x08\x82\x5c\xa7\xd3\x01\x63\x3d\xd7\x75\x67\xb4\xd6\xbb\x37\x37\ -\xd2\xa4\xb2\x4c\x31\xcd\x8f\x9b\xbf\xa3\x0b\xff\x4c\xf7\xb5\xc0\ -\x80\x02\x69\x82\xfb\x7e\xe9\x98\xce\x01\xaf\x86\x7e\xb6\xbf\x41\ -\xfb\xdf\xf8\xa4\x40\xfd\x35\xe7\xe2\xd4\x2d\xbc\x89\x8f\xc8\x7e\ -\xbf\xb5\x84\x73\xcb\x17\xff\xd4\xc6\x53\x77\x92\x2a\xa4\x43\xa4\ -\xc3\xa4\xaa\x24\x5a\x0c\x71\xe6\xce\x59\x01\xdc\xbf\xd0\xe2\xf2\ -\x82\x93\x93\xde\x65\xfb\xe7\xa4\xd7\x9c\xc0\xca\x8e\xe1\x66\x72\ -\xf8\xad\xe0\x8a\x33\x83\x29\x75\x5c\xc6\x2c\xa7\x4f\x30\x17\x2d\ -\x64\x43\xd7\x38\x7a\xa6\x48\xf1\x9f\xe6\x7f\xd6\x77\x01\x06\x00\ -\xf9\x1f\x11\xa0\x42\x25\x9c\x34\x00\x00\x00\x00\x49\x45\x4e\x44\ -\xae\x42\x60\x82\ +\x0b\x13\x01\x00\x9a\x9c\x18\x00\x00\x02\xd4\x49\x44\x41\x54\x38\ +\xcb\x55\x92\x4f\x68\x5d\x65\x10\xc5\x7f\xdf\xbb\x37\xaf\x49\x48\ +\xd5\x04\x23\x22\x22\x88\x85\x08\x12\x05\x0b\xa2\x0b\xc1\x8d\x90\ +\x6c\x14\x04\xa1\xee\x94\x42\x15\x14\x22\xed\x56\x77\x6e\x0d\x82\ +\x2b\x05\x97\xba\x15\xdd\x58\x10\x0a\x82\xab\x5a\x50\x2b\x41\x24\ +\xb1\x31\x31\x79\x6d\xfe\xbc\x97\xfb\xde\xfd\xf7\xdd\xef\x7e\x33\ +\xe3\xe2\x3e\x0a\xce\x76\xe6\x9c\x39\x67\xe6\xb8\x67\x77\xde\xe4\ +\xa9\xfe\xe3\xb1\x57\xab\x6f\xbe\x9d\xdc\x48\x92\xb7\x0f\x3e\x5e\ +\xa9\xde\x7b\x68\xe8\x66\xb7\x5e\x30\x7f\x98\xe3\x5e\xdb\xdb\x58\ +\x3d\x8a\xc3\xdb\xdb\x61\x9f\x5c\x4b\x1c\x37\x57\xe1\xb8\x35\x1a\ +\x85\xdf\x2b\xdc\xeb\x7b\x1b\x3c\x98\x9c\xbf\xb5\x1b\x0e\x7f\xda\ +\x6a\xfe\xbe\x96\x02\x76\xa3\xbc\x49\xa1\xd5\xc5\x6c\x32\xde\x48\ +\x7f\xa9\xb7\x18\xc4\x13\x10\x83\x3f\xab\x24\x1d\x1c\x0c\xa0\x56\ +\x18\x04\xd8\x6b\x36\xdd\xfa\x3f\xef\xb3\x9c\x2e\xfe\x20\x26\x6b\ +\xa7\x92\x91\x49\x4e\xa9\x35\x99\xe6\x8c\x64\x7c\x2e\x2d\xb5\xde\ +\x3e\xf2\xc3\x0b\x07\xf1\x88\xa1\x64\xa8\x19\x00\x56\x44\x18\xc6\ +\x26\xbd\xe5\xb7\xae\x57\xea\x3f\x20\x76\x0d\x0c\x28\x04\xee\x34\ +\x70\x37\xe0\xf8\xf9\x19\x38\x89\x30\x8c\x39\x85\x2c\x50\x08\x8c\ +\x05\xc4\xde\xe5\x91\x99\x2f\xdd\x2b\xbb\x97\x79\x2c\x5d\xe6\x9c\ +\xeb\x7f\x5a\x5b\xb3\x91\x49\xfe\xdb\x71\x1c\x5d\x3a\x96\xd1\xce\ +\x99\x4c\x48\x1f\x4d\x1f\xee\xcf\xb8\xb4\x19\x6b\xc1\x69\xcc\x28\ +\xb4\xba\x38\xd1\x62\xbb\x52\x7f\xbd\xb1\xb0\x9e\x06\x6b\xab\x91\ +\x8c\xd9\x6f\xef\x31\x94\x8c\x68\x42\x34\x21\x8f\xc5\x1a\x13\x59\ +\x49\x8f\xe2\x30\x39\x8e\x23\x0e\xe2\x11\x5e\x43\xa7\x33\x2a\x8c\ +\x22\x64\xf2\x75\x3a\x88\x27\x8c\xa5\xc0\x6b\xc0\xb0\xce\x85\x57\ +\x38\x8b\x70\x1c\x9f\x48\xff\x6d\xef\x11\x25\x42\x04\x12\xa0\x35\ +\x38\x8d\x70\x18\xa0\xd4\x6f\x12\x7d\x6b\x69\x91\x91\xbc\x48\x26\ +\x1d\xed\xdd\x00\xbb\x4d\x67\xbd\xdf\x7b\x29\xa5\xd6\x0f\x19\xb6\ +\xbb\x8c\xe5\x33\x4a\x85\x89\x40\x21\x05\xb3\xbd\xf3\x2c\xa7\xb8\ +\x97\xef\xbc\xc3\x52\xf2\x00\x0b\xbd\x79\xfa\x6e\xe6\xaa\x73\xee\ +\x93\x68\x32\xd7\x58\xc0\x6b\x83\xb7\x40\x63\x81\x5a\x1b\x2a\xf3\ +\x75\xa5\xfe\xa3\x4a\xeb\xcd\xda\x1a\x82\xb5\x5d\x20\xe6\x7a\xb3\ +\x5f\xf4\x70\x57\x5a\x8b\xd4\xd6\x90\x6b\x49\xa1\x35\x5e\xbb\x21\ +\x41\x11\x13\x82\xb5\xf8\x29\x99\xd7\x66\x93\x68\xd7\xd2\xc6\xda\ +\xcf\x83\xc4\x2b\x7e\x0a\x3c\x93\x9c\x4c\x26\xd4\xd6\xd0\x5a\xec\ +\x2e\x03\x38\x1c\xd1\x04\x6b\x15\x1a\x85\x5a\xaf\x12\x2c\x71\xcf\ +\xef\x5c\x6a\x22\xd2\xaf\xd5\x53\x6a\x4d\xae\x15\xb5\x79\xc4\xf4\ +\x3e\xf8\x7e\x48\x1a\x85\x4a\xbb\xb0\x14\x0a\x5e\x4f\xd2\x41\x3c\ +\xd9\x6f\xad\xbd\xd0\x5a\xa4\x25\x76\x92\x55\xf9\x5f\x99\x75\xe7\ +\x2f\xa7\xff\x19\x0b\xe4\x02\xc1\xf6\x1d\xb7\x9f\x5b\x25\xe8\xaf\ +\x44\x4b\x88\xd3\x47\x76\x9a\xbb\xd2\xe9\xe6\x52\x21\x8b\x90\x4d\ +\xc1\xad\x09\x33\xee\xe9\x94\x42\xfe\xa0\xd2\x79\x6a\xfd\x0e\xaf\ +\x6b\xb4\x06\x6a\x20\x40\x34\x08\xd6\x11\x14\xd2\xc9\x0f\x06\xf0\ +\x3d\x73\xbd\x37\x98\xef\x49\x8a\x03\x7a\x04\xcc\xd6\x09\x06\xa5\ +\x3c\x49\xa5\x97\xa9\xf4\x55\xbc\xae\xd0\x1a\xb4\xf6\x17\x6a\x3f\ +\xd2\x73\x5f\x31\xeb\x76\x59\x48\x60\x29\x85\xc5\x94\xff\x00\xe1\ +\x78\x1f\x4c\x73\x1c\xbc\x8b\x00\x00\x00\x00\x49\x45\x4e\x44\xae\ +\x42\x60\x82\ \x00\x00\x07\x6a\ \x89\ \x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\ @@ -1016,60 +1182,356 @@ qt_resource_data = "\ \xdf\xb5\x30\xe2\x5d\xcd\x84\xfe\x0a\xe5\xc0\xa4\x2e\xcf\x12\x55\ \x4f\x61\x1b\xfc\x1f\x0b\x03\xc8\x05\x59\x65\x3b\x42\x00\x00\x00\ \x00\x49\x45\x4e\x44\xae\x42\x60\x82\ -\x00\x00\x03\x34\ +\x00\x00\x00\xc5\ +\x89\ +\x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\ +\x00\x00\x90\x00\x00\x00\x90\x08\x06\x00\x00\x00\xe7\x46\xe2\xb8\ +\x00\x00\x00\x04\x73\x42\x49\x54\x08\x08\x08\x08\x7c\x08\x64\x88\ +\x00\x00\x00\x09\x70\x48\x59\x73\x00\x00\x0e\xc4\x00\x00\x0e\xc4\ +\x01\x95\x2b\x0e\x1b\x00\x00\x00\x67\x49\x44\x41\x54\x78\x9c\xed\ +\xc1\x31\x01\x00\x00\x00\xc2\xa0\xf5\x4f\xed\x69\x09\xa0\x00\x00\ +\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ +\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ +\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ +\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ +\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xe0\x06\ +\x44\x9f\x00\x01\xc3\xcd\x96\xea\x00\x00\x00\x00\x49\x45\x4e\x44\ +\xae\x42\x60\x82\ +\x00\x00\x02\x7a\ +\x89\ +\x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\ +\x00\x00\x10\x00\x00\x00\x10\x08\x06\x00\x00\x00\x1f\xf3\xff\x61\ +\x00\x00\x00\x06\x62\x4b\x47\x44\x00\xff\x00\xff\x00\xff\xa0\xbd\ +\xa7\x93\x00\x00\x00\x09\x70\x48\x59\x73\x00\x00\x0b\x13\x00\x00\ +\x0b\x13\x01\x00\x9a\x9c\x18\x00\x00\x00\x07\x74\x49\x4d\x45\x07\ +\xdd\x06\x0d\x08\x1d\x33\x51\xf1\xd4\x9e\x00\x00\x02\x07\x49\x44\ +\x41\x54\x38\xcb\x65\x91\x3d\x6b\x14\x41\x18\xc7\x7f\x73\xb7\x2c\ +\x6c\x0c\xb9\x20\xa6\x0d\x58\x45\x90\x88\x1f\x40\xb1\x12\x2e\x8d\ +\x82\x5f\x41\x08\x58\x05\xb4\xb5\x0c\xd8\x09\xa2\x20\x08\x7e\x88\ +\x58\x05\xac\x04\xfb\x08\x56\x42\x40\x48\x9d\xdc\xe5\xf6\x66\xe7\ +\x75\x67\xc7\xe2\x19\x72\x07\x0e\xfc\x18\x06\xe6\xff\x32\xcf\xa8\ +\x9c\x1f\x00\xb7\x81\x09\x70\x0b\xa8\xf7\x40\x1d\x42\x7a\x02\xe1\ +\x21\x78\xc0\xfe\x82\xee\x07\x74\x9f\x41\x9f\x83\x41\xf0\xa8\x9c\ +\x1f\x17\x83\x4d\xa0\x7e\x0d\xea\x18\xfa\x46\x84\xae\xe0\x01\x0b\ +\x18\x0b\xe6\x2d\x98\xf7\x72\x0e\xa8\x9c\x0f\x80\x49\x0d\xf5\x09\ +\xa8\x29\xf4\xe5\x72\x57\x76\x0f\x44\x20\xac\x19\x9a\x53\x70\xcf\ +\x21\x84\x11\xd4\x00\x1f\xa1\x9f\x4a\xad\x05\x70\x05\x5c\x96\x7d\ +\x06\x5c\x03\xcb\x62\xda\x01\x66\x9a\xb3\x79\x17\x42\x8f\xca\xf9\ +\xd9\x3e\x54\x67\x90\xc6\x92\xb8\x28\xe8\x92\x9e\x80\x5c\x48\x80\ +\x23\xa5\x88\x31\xa4\x10\xb8\x5f\x41\x38\x84\x38\x96\x6a\x4b\x60\ +\x5e\x12\x6d\xa9\x9e\x91\xa5\x80\x9e\x10\x32\xd6\x82\x31\x8c\xbd\ +\xe7\x55\x05\x66\x2a\xce\xb6\x18\x2c\xcb\x84\x03\x30\xb0\xbe\x62\ +\x14\x71\xd7\x09\xd6\xf2\xa8\x02\xbd\xfb\xff\xe0\x62\x11\xe7\x1b\ +\x71\xce\x10\x23\x78\x0f\xc6\xc0\x72\x09\xc6\xb0\x5b\x49\x62\xcf\ +\xea\xdb\xe2\xda\xbb\x57\xe2\x94\xa0\xef\xb9\x69\x50\x0c\x18\xc1\ +\xf2\x02\xda\x32\x34\x49\xcf\x39\x93\x33\x37\x0c\x83\xa4\x5b\x0b\ +\x5a\x43\xdb\x0a\x5d\xc7\xc5\x08\xda\x53\x99\x7a\x4b\x4a\x96\x18\ +\x13\x21\x48\x5a\x4a\xab\xda\x5a\xc3\xf5\x35\xcc\x66\x42\xdb\x82\ +\xb5\xfc\x54\x29\xb1\xef\x1c\x67\x31\x32\xee\x7b\x49\x04\x50\x4a\ +\xf6\x94\xc0\x39\xa9\x7c\x79\x09\x57\x57\xb0\x58\x40\x08\xa4\xba\ +\xe6\x5e\x65\x0c\xbf\xad\xe5\x93\x73\x1c\xc5\x28\xc9\xc3\xb0\x12\ +\xf7\xbd\xbc\xb5\x6d\x61\x3e\x17\xb1\xf7\x30\x1a\xf1\xa1\x69\x38\ +\x57\xb3\x19\x68\x4d\xdd\x75\x9c\x58\xcb\x34\x04\x11\xae\xd7\xb7\ +\x56\x0c\xb4\x96\x33\xf0\x6d\x63\x83\x17\x77\xee\x90\xaa\x61\x80\ +\x61\x20\xc4\xc8\x81\x73\x1c\x19\xc3\xb1\x73\x6c\x7a\x0f\x21\x48\ +\x7d\x6b\x85\x18\xd1\x4a\xf1\xa6\x69\xf8\xb2\xb5\x05\xdb\xdb\xa0\ +\xe6\x73\xf9\x96\xb6\x95\x7a\x6d\xcb\x5d\xad\x79\xa9\x35\x4f\xad\ +\x65\xcf\x7b\x88\x91\x3f\x29\xf1\x7d\x3c\xe6\x6b\xd3\xf0\x77\x32\ +\x81\x9d\x1d\xe1\x1f\x3c\x20\x6c\x94\x65\x65\x77\x27\x00\x00\x00\ +\x00\x49\x45\x4e\x44\xae\x42\x60\x82\ +\x00\x00\x06\xc9\ +\x89\ +\x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\ +\x00\x00\x90\x00\x00\x00\x90\x08\x02\x00\x00\x00\x68\x24\x75\xef\ +\x00\x00\x00\x03\x73\x42\x49\x54\x08\x08\x08\xdb\xe1\x4f\xe0\x00\ +\x00\x00\x09\x70\x48\x59\x73\x00\x00\x0e\xc4\x00\x00\x0e\xc4\x01\ +\x95\x2b\x0e\x1b\x00\x00\x06\x6c\x49\x44\x41\x54\x78\x9c\xed\x9d\ +\x3b\x7a\xd3\x40\x14\x46\xc7\x7c\x2e\x48\x9f\x02\x0a\x16\x91\x2d\ +\x50\xb2\x04\x52\x7a\x5b\xa4\xcc\x16\x52\x66\x09\x64\x11\x14\x2e\ +\x48\xef\x52\x14\x0a\xf2\x58\x96\x34\x8f\xfb\xfa\xe7\x7a\x4e\x05\ +\xc4\x1e\x8b\x39\x3e\x1e\x7b\x90\xc5\xee\xd7\xdf\x2f\x21\x84\xc3\ +\xfd\x31\x60\xf0\x78\xf7\x66\x7d\x08\x73\x9e\x4f\x0f\xd6\x87\xf0\ +\xc1\xd3\xfb\xd7\xfd\xf4\xab\x80\xa1\x6d\x9c\x1d\x40\x6d\xb6\x8c\ +\x82\x42\x08\xbb\x61\x18\xa6\xdf\x8c\x20\x68\x1b\x01\xd1\x66\x5b\ +\xd8\xcc\xce\x7e\xed\x16\x08\xda\x6e\xbc\xb6\x99\xaa\x10\xc2\xe1\ +\xfe\xb8\x1b\x86\x61\xf1\x67\xd3\x2d\xc4\x8f\x2b\x0f\x43\x6d\xfa\ +\x85\x6d\xe8\x58\x28\xec\xfa\x9e\x08\xda\x6e\xa4\xb6\x35\x55\xe1\ +\xbf\x85\x8f\xc2\xb6\x6f\x1a\xdf\x01\x01\x65\x6d\x3a\x85\x65\xce\ +\x7f\xa2\xb0\xeb\x11\x11\xb4\x39\xab\x2d\xa9\x2a\x44\xd3\x7e\x2e\ +\x2c\xf3\x9e\xb3\xfb\x9b\xa3\xa0\x4d\xae\xb0\x8a\x09\x2f\x28\xec\ +\xfa\x91\x10\xb4\x35\x5a\x5b\xbe\xaa\x70\x39\xcf\x17\x85\x95\x0e\ +\x74\x3d\x9c\x2d\x42\xda\x78\x0b\x23\xce\x70\x65\x61\xd7\x47\x80\ +\xa0\x0d\xbc\xb6\x0a\x55\xe1\x6a\x62\x3f\x6d\xff\xb8\xe8\x68\xea\ +\x0e\x88\x1d\x9c\xad\xbf\x09\xc6\xc9\x61\x28\x2c\xc6\xbc\xb6\x38\ +\xaf\xe7\xd3\x83\x79\x6d\x44\x4f\xd7\x33\xb9\x20\xec\x70\x7f\x24\ +\x3e\x8c\x89\xb6\x45\x37\x86\x2f\x92\x42\xaf\x37\xcc\x85\xc5\xa8\ +\x69\x4b\xfa\x50\xd6\xc6\xa5\x6a\x71\xea\x96\x85\xd1\x23\x9b\x10\ +\xd5\x56\xe4\x40\x41\x9b\xc2\x2a\x2e\x58\x58\x0c\xbb\xb6\xea\x79\ +\x17\xd2\xc6\xae\x6a\x6d\xae\x56\x85\x31\x46\x36\xc1\xa2\x8d\x65\ +\xae\x19\xb5\x29\xbf\x37\x56\x2a\x2c\xa6\x5a\x1b\x7b\x16\x44\x6d\ +\x72\xaa\x36\x26\x67\x4b\x98\x44\x64\x13\x45\xda\x44\x17\x9e\x0a\ +\x6d\x86\x9f\x38\x0d\x0a\x8b\x49\x6a\x53\x7b\x6b\x97\xa9\x4d\x41\ +\xd5\xf6\x93\x38\x21\x4c\x34\xb2\x89\x45\x6d\x26\x1f\x9e\x36\xb4\ +\x81\xec\xe3\x18\x17\x16\x33\x69\x33\xdf\x9e\x98\x69\xd3\x54\x95\ +\x5c\x23\xe6\x7b\x89\x15\x43\xf0\x02\xf2\x44\x0e\xff\xb5\x7d\xff\ +\xf3\xc3\xfa\x40\x2e\x48\x0b\xd3\x07\x61\xf7\xf6\xf1\xee\x4d\x3f\ +\xf4\x9c\x36\xb2\x5e\x12\x75\x56\xb2\x18\xc3\x3d\x40\xf3\x17\xe4\ +\x6d\x80\xd6\xb0\x6b\x94\xb5\xd9\xaa\xca\x5c\x7a\x72\x85\xe9\x47\ +\x36\xa1\xa0\x0d\xbc\xaa\x18\xe8\xc2\x62\x84\xb4\x81\xa8\xca\x7f\ +\x67\x57\x20\xcc\x30\xb2\x09\x46\x6d\x20\xaa\x4a\x69\xa6\xb0\x18\ +\xa2\x36\x34\x55\x45\x1f\x9c\xca\x84\x21\x44\x36\x51\xa1\x0d\x4d\ +\x55\x05\x4d\x16\x16\x93\xa9\x0d\x56\x55\xe9\xbe\x44\xb1\x30\xa8\ +\xc8\x26\x36\xb4\xc1\xaa\xaa\xa3\xf9\xc2\x62\x66\xda\xf0\x55\x55\ +\x6c\xfb\xd5\x6c\x4d\x21\x9c\x33\xba\x01\xc2\xce\x96\x1c\xae\x0a\ +\x0b\x2d\x54\x35\x51\xf7\xbc\xaf\x14\x06\xb8\x92\x35\xa4\x8a\x82\ +\x87\xc2\x5a\x54\x55\xbd\xac\xd4\x0b\x43\x88\xac\x45\x55\x44\x5a\ +\x2d\xac\x69\x55\x94\x77\x6d\x24\x61\x26\x91\x35\xad\x8a\x4e\x4b\ +\x85\xf9\x50\x45\xfc\x50\x44\x15\xa6\x13\x99\x0f\x55\x2c\xa0\x17\ +\xe6\x4c\x15\x7d\xcf\x81\x41\x98\x50\x64\xce\x54\x71\x81\x58\x98\ +\x57\x55\x2c\x5b\x7a\x7b\xa6\xd9\x79\x41\x3b\x7f\x0f\x8d\xd7\x6f\ +\x2f\x87\x13\xc3\x38\xbb\x9f\x9f\x7f\x33\x0c\xe3\x1a\xfa\x6e\xf2\ +\x58\x05\xcb\x38\x6c\x27\x92\x3a\xde\x23\xe7\x7a\x89\x66\x19\x87\ +\x47\x98\x63\x5b\x74\x78\x7d\x73\x9e\xaa\xed\x58\x1b\x4e\x64\x0c\ +\xc2\x1c\x7b\xa2\xc3\x6e\x9a\xf9\xcb\x10\x8e\xe5\x81\x44\x46\x15\ +\xe6\xd8\x10\x1d\x09\xc7\xfc\x5f\x37\x72\xac\x10\x21\x32\x92\x30\ +\xc7\x6e\xe8\x08\xd9\x15\xf9\x42\x9f\x63\x91\xe6\x91\xd5\x0b\x73\ +\x6c\x85\x8e\x9c\x57\xa9\xaf\xcc\x3a\xd6\x69\x1b\x59\xa5\x30\xc7\ +\x3e\xe8\x88\x1a\x15\xfc\x52\xba\x63\xa9\x86\x91\xd5\x08\x73\x6c\ +\x82\x8e\xb4\x4b\xd9\xcb\x3e\x38\x56\x6b\x15\x59\xb1\x30\xc7\x0e\ +\xe8\x28\x58\x14\xbf\xb0\x8a\x63\xc1\x26\x91\x95\x09\x73\x3c\xfb\ +\x74\x74\xfc\x69\x5c\xba\xc8\xb1\x66\xfd\xc8\x0a\x84\x39\x9e\x77\ +\x3a\x6a\xe6\x94\x2e\x0e\xe6\x58\xb6\x72\x64\xb9\x67\x4d\x71\x9d\ +\x39\xd4\x21\xd2\x4f\x73\x6b\x0c\xc4\x33\x7f\x5b\x44\xed\x15\x28\ +\x6b\x0d\xe3\x5a\x81\xfa\x4a\x46\x27\x2d\xac\xdb\x52\x80\xff\x6d\ +\x7d\xd7\x96\x44\x27\xb2\x84\xb0\xee\x49\x01\xa9\xad\xa9\x2e\x2f\ +\x89\x42\x64\x5b\xc2\xba\x21\x05\x64\xff\x79\xa5\x2b\x4c\x22\x1d\ +\xd9\xaa\xb0\xee\x46\x01\x8d\x53\x04\xba\xc8\x24\xa2\x91\x2d\x0b\ +\xeb\x56\x14\xd0\x3b\xcd\xad\xeb\x4c\x22\x17\xd9\x82\xb0\xee\x43\ +\x01\xed\x53\xb5\xbb\xd4\x24\x42\x91\xcd\x85\x75\x13\x0a\xd8\x7c\ +\xdd\xa8\xab\x4d\x22\x11\xd9\x85\xb0\xee\x40\x01\xcb\xaf\xcc\x76\ +\xc1\x49\xd8\x23\x3b\x0b\xeb\xb3\xaf\x80\xfd\x65\x1f\xba\xe6\x24\ +\xbc\x91\x7d\x08\xeb\xf3\xae\x00\xca\xa5\x8b\xba\xec\x24\x8c\x91\ +\x7d\x0a\x7d\xc6\x55\xe0\x72\xb6\xfb\xf9\xf9\x37\xcb\x44\x33\x5e\ +\x94\xf4\xf5\xdb\x0b\xd7\x50\x74\x18\xaf\x03\xc9\xf2\xf7\xda\xa3\ +\xd9\x82\xe2\xf9\xf4\xf0\xf4\xce\x39\x1a\x7d\x90\x7e\x22\xe9\x32\ +\x12\x2f\xef\x4f\xef\x5f\x21\x2e\xd2\xec\x2c\x2f\xf0\x95\xb8\x17\ +\x76\x46\x41\x15\x3d\x32\xaa\x30\x1f\x79\x81\x57\x15\x73\xeb\x85\ +\xe9\xab\x22\x46\x46\x12\xd6\x74\x5e\x0d\x55\x15\x73\x8b\x85\x99\ +\xab\xa2\x44\x56\x2f\xac\xc5\xbc\xcc\x55\xd1\xb9\x95\xc2\xd0\x54\ +\x55\x47\x56\x29\xac\xa1\xbc\xd0\x54\x11\x51\xba\x8a\x80\x15\xc8\ +\xb6\xea\x9e\xf4\x35\xc2\xf0\xf3\x9a\x3c\x3d\xde\xbd\x39\xbb\x7a\ +\x81\xb7\x35\x6c\x31\x29\xae\xff\xab\x86\x9d\x8a\x95\xac\x58\x18\ +\x6c\x5e\x49\x1f\xb0\xda\x8a\xf0\x50\x58\x91\x03\x34\x6d\xa5\x91\ +\x95\x09\x43\xcb\xab\x7a\xde\xd1\xb4\xe5\xd3\x6a\x61\x2c\x73\x0d\ +\xa2\xad\x28\xb2\x02\x61\x20\x79\xb1\xcf\x2f\x88\xb6\x4c\x5a\x2a\ +\x4c\x74\x4e\x6d\xb5\xe5\x47\x96\x2b\xcc\x36\x2f\xb5\x79\xc4\xaf\ +\x0d\xbd\x30\x93\xb9\x33\xd1\x96\x19\x59\x96\x30\x93\xbc\xcc\x9f\ +\xe6\xa3\xb6\xef\x7f\x6c\x8f\x62\x0e\xdc\x5e\xe2\x78\xf2\x9e\xb9\ +\x2d\x13\x72\xc2\x48\x17\xa6\x99\xd7\xe1\xfe\xf8\x1a\xde\x02\xcc\ +\x5a\xf2\x7c\x7a\x08\xe1\x18\xac\x97\xf0\x18\x94\x35\x6c\xf1\xe5\ +\xdb\x50\xdb\xec\x41\xc7\xc3\x53\xd0\x96\x5c\xc9\x12\xc2\x14\x0e\ +\x31\xb9\xd2\x2a\x6b\xdb\x78\x20\x35\x6d\x1b\x58\x16\x56\xb4\x87\ +\xa6\xa0\x2d\x73\x70\x69\x6d\xdb\x91\x6d\x09\x93\x3b\xa6\xea\x53\ +\x50\x84\xb4\x55\x0c\x68\x55\x9b\x76\x61\xf4\x93\xcb\x03\xab\x36\ +\xe2\x20\x42\xda\x36\x22\x5b\x15\xc6\x7e\x10\x2c\xaa\x62\x88\xda\ +\x18\x33\xd5\xac\x4d\xa3\x30\x76\x55\x31\x15\xda\x84\x16\x42\x5e\ +\x6d\x6b\x91\x2d\x0b\xe3\x7a\x54\x51\x55\x31\x99\xda\x14\xde\x6a\ +\x4a\xd7\x26\x55\x98\x9a\xaa\x98\x0d\x6d\xca\x1f\xe6\x58\xb4\x2d\ +\x46\xb6\x20\x8c\xf8\x30\x26\xaa\x62\x66\xa7\x49\x19\x6e\x97\x48\ +\xd4\xc6\xb9\x97\x78\xb8\x3f\x9a\xdb\x9a\x61\xbe\xb9\x15\x68\xd3\ +\x72\x2d\x7b\x5e\x58\xdd\xd3\x01\xcd\xd3\x04\xc8\x9e\x64\xe0\xab\ +\x6d\x37\x0c\x43\xfc\xfb\xd2\x11\x85\xde\xac\x4b\x80\xa0\x6d\x84\ +\x32\xc9\x17\x85\x15\x0d\x04\x5b\xd5\x1a\x3e\x6a\xbb\x28\x2c\x73\ +\x08\x85\xcf\x55\xd2\x20\x68\x1b\x29\x9d\xf3\x73\x61\x39\xf7\x6c\ +\xae\xaa\x35\xda\xad\xed\x5c\xd8\xf6\x7d\x94\x3f\x02\x6b\x82\xa0\ +\x6d\x24\x47\xc1\x3e\x79\x53\x37\x55\xad\xd1\x56\x6d\x1f\x85\x2d\ +\xde\xc8\x70\xb7\xc2\x0a\x04\x6d\x23\x6b\x46\xf6\x8b\x3f\x73\x5f\ +\xd5\x1a\xf8\xb5\xed\x86\x61\x88\xff\xd4\x5c\x15\xce\xf7\xef\x10\ +\xb4\x8d\xc4\x82\x76\xbf\xfe\x7e\x19\x7f\x65\xae\x6a\x04\x47\xd8\ +\x08\x9a\xb6\x7f\x3b\xcf\xca\x48\x61\xee\x5b\x97\x00\x00\x00\x00\ +\x49\x45\x4e\x44\xae\x42\x60\x82\ +\x00\x00\x03\xef\ \x89\ \x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\ \x00\x00\x10\x00\x00\x00\x10\x08\x06\x00\x00\x01\x68\xf4\xcf\xf7\ \x00\x00\x00\x06\x62\x4b\x47\x44\x00\xff\x00\xff\x00\xff\xa0\xbd\ \xa7\x93\x00\x00\x00\x09\x70\x48\x59\x73\x00\x00\x0b\x13\x00\x00\ -\x0b\x13\x01\x00\x9a\x9c\x18\x00\x00\x02\xd4\x49\x44\x41\x54\x38\ -\xcb\x55\x92\x4f\x68\x5d\x65\x10\xc5\x7f\xdf\xbb\x37\xaf\x49\x48\ -\xd5\x04\x23\x22\x22\x88\x85\x08\x12\x05\x0b\xa2\x0b\xc1\x8d\x90\ -\x6c\x14\x04\xa1\xee\x94\x42\x15\x14\x22\xed\x56\x77\x6e\x0d\x82\ -\x2b\x05\x97\xba\x15\xdd\x58\x10\x0a\x82\xab\x5a\x50\x2b\x41\x24\ -\xb1\x31\x31\x79\x6d\xfe\xbc\x97\xfb\xde\xfd\xf7\xdd\xef\x7e\x33\ -\xe3\xe2\x3e\x0a\xce\x76\xe6\x9c\x39\x67\xe6\xb8\x67\x77\xde\xe4\ -\xa9\xfe\xe3\xb1\x57\xab\x6f\xbe\x9d\xdc\x48\x92\xb7\x0f\x3e\x5e\ -\xa9\xde\x7b\x68\xe8\x66\xb7\x5e\x30\x7f\x98\xe3\x5e\xdb\xdb\x58\ -\x3d\x8a\xc3\xdb\xdb\x61\x9f\x5c\x4b\x1c\x37\x57\xe1\xb8\x35\x1a\ -\x85\xdf\x2b\xdc\xeb\x7b\x1b\x3c\x98\x9c\xbf\xb5\x1b\x0e\x7f\xda\ -\x6a\xfe\xbe\x96\x02\x76\xa3\xbc\x49\xa1\xd5\xc5\x6c\x32\xde\x48\ -\x7f\xa9\xb7\x18\xc4\x13\x10\x83\x3f\xab\x24\x1d\x1c\x0c\xa0\x56\ -\x18\x04\xd8\x6b\x36\xdd\xfa\x3f\xef\xb3\x9c\x2e\xfe\x20\x26\x6b\ -\xa7\x92\x91\x49\x4e\xa9\x35\x99\xe6\x8c\x64\x7c\x2e\x2d\xb5\xde\ -\x3e\xf2\xc3\x0b\x07\xf1\x88\xa1\x64\xa8\x19\x00\x56\x44\x18\xc6\ -\x26\xbd\xe5\xb7\xae\x57\xea\x3f\x20\x76\x0d\x0c\x28\x04\xee\x34\ -\x70\x37\xe0\xf8\xf9\x19\x38\x89\x30\x8c\x39\x85\x2c\x50\x08\x8c\ -\x05\xc4\xde\xe5\x91\x99\x2f\xdd\x2b\xbb\x97\x79\x2c\x5d\xe6\x9c\ -\xeb\x7f\x5a\x5b\xb3\x91\x49\xfe\xdb\x71\x1c\x5d\x3a\x96\xd1\xce\ -\x99\x4c\x48\x1f\x4d\x1f\xee\xcf\xb8\xb4\x19\x6b\xc1\x69\xcc\x28\ -\xb4\xba\x38\xd1\x62\xbb\x52\x7f\xbd\xb1\xb0\x9e\x06\x6b\xab\x91\ -\x8c\xd9\x6f\xef\x31\x94\x8c\x68\x42\x34\x21\x8f\xc5\x1a\x13\x59\ -\x49\x8f\xe2\x30\x39\x8e\x23\x0e\xe2\x11\x5e\x43\xa7\x33\x2a\x8c\ -\x22\x64\xf2\x75\x3a\x88\x27\x8c\xa5\xc0\x6b\xc0\xb0\xce\x85\x57\ -\x38\x8b\x70\x1c\x9f\x48\xff\x6d\xef\x11\x25\x42\x04\x12\xa0\x35\ -\x38\x8d\x70\x18\xa0\xd4\x6f\x12\x7d\x6b\x69\x91\x91\xbc\x48\x26\ -\x1d\xed\xdd\x00\xbb\x4d\x67\xbd\xdf\x7b\x29\xa5\xd6\x0f\x19\xb6\ -\xbb\x8c\xe5\x33\x4a\x85\x89\x40\x21\x05\xb3\xbd\xf3\x2c\xa7\xb8\ -\x97\xef\xbc\xc3\x52\xf2\x00\x0b\xbd\x79\xfa\x6e\xe6\xaa\x73\xee\ -\x93\x68\x32\xd7\x58\xc0\x6b\x83\xb7\x40\x63\x81\x5a\x1b\x2a\xf3\ -\x75\xa5\xfe\xa3\x4a\xeb\xcd\xda\x1a\x82\xb5\x5d\x20\xe6\x7a\xb3\ -\x5f\xf4\x70\x57\x5a\x8b\xd4\xd6\x90\x6b\x49\xa1\x35\x5e\xbb\x21\ -\x41\x11\x13\x82\xb5\xf8\x29\x99\xd7\x66\x93\x68\xd7\xd2\xc6\xda\ -\xcf\x83\xc4\x2b\x7e\x0a\x3c\x93\x9c\x4c\x26\xd4\xd6\xd0\x5a\xec\ -\x2e\x03\x38\x1c\xd1\x04\x6b\x15\x1a\x85\x5a\xaf\x12\x2c\x71\xcf\ -\xef\x5c\x6a\x22\xd2\xaf\xd5\x53\x6a\x4d\xae\x15\xb5\x79\xc4\xf4\ -\x3e\xf8\x7e\x48\x1a\x85\x4a\xbb\xb0\x14\x0a\x5e\x4f\xd2\x41\x3c\ -\xd9\x6f\xad\xbd\xd0\x5a\xa4\x25\x76\x92\x55\xf9\x5f\x99\x75\xe7\ -\x2f\xa7\xff\x19\x0b\xe4\x02\xc1\xf6\x1d\xb7\x9f\x5b\x25\xe8\xaf\ -\x44\x4b\x88\xd3\x47\x76\x9a\xbb\xd2\xe9\xe6\x52\x21\x8b\x90\x4d\ -\xc1\xad\x09\x33\xee\xe9\x94\x42\xfe\xa0\xd2\x79\x6a\xfd\x0e\xaf\ -\x6b\xb4\x06\x6a\x20\x40\x34\x08\xd6\x11\x14\xd2\xc9\x0f\x06\xf0\ -\x3d\x73\xbd\x37\x98\xef\x49\x8a\x03\x7a\x04\xcc\xd6\x09\x06\xa5\ -\x3c\x49\xa5\x97\xa9\xf4\x55\xbc\xae\xd0\x1a\xb4\xf6\x17\x6a\x3f\ -\xd2\x73\x5f\x31\xeb\x76\x59\x48\x60\x29\x85\xc5\x94\xff\x00\xe1\ -\x78\x1f\x4c\x73\x1c\xbc\x8b\x00\x00\x00\x00\x49\x45\x4e\x44\xae\ -\x42\x60\x82\ +\x0b\x13\x01\x00\x9a\x9c\x18\x00\x00\x03\x8f\x49\x44\x41\x54\x38\ +\xcb\x3d\x91\xdf\x4f\x5b\x75\x00\xc5\xcf\xf7\x7e\x2f\xb4\xbd\xb7\ +\xdc\x76\x8c\xfe\x80\x5b\xe4\x87\x73\xcc\x84\x8c\x9f\x0d\x83\x25\ +\x2e\x6e\x3e\x8c\x44\xb3\x64\x66\x21\x6a\x62\x8c\xba\x68\xa6\x0f\ +\xea\x93\x0f\x26\x8b\x0f\xc6\x7f\xc0\x44\x97\xc5\x2c\x53\x5f\xdc\ +\xe2\xa3\xc8\xa0\xb0\x1f\x22\x14\xc1\x6d\x08\x6d\x2d\x90\x51\xda\ +\x4b\x27\x3f\xd6\x52\xee\xbd\x50\x7a\xbf\xbd\x5f\x1f\x10\x5e\x4f\ +\xce\x39\xf9\xe4\x1c\xca\x18\x43\x4f\x4f\xdf\x23\xc4\xe2\x09\x30\ +\xc6\x38\xb1\x2c\x0b\x00\x20\xe6\xb6\xf2\x7c\xf9\xc9\x0a\x08\x63\ +\x0c\xdb\xdb\x7a\x6f\xb1\x54\x9c\x30\x0c\x03\x88\x8c\xde\x43\xb8\ +\xbb\x8f\xf7\xf6\xbe\xc4\x1f\x8c\xff\x0e\x58\x96\x85\x72\xb9\x8c\ +\x8c\xa6\xfd\x64\x59\x16\xc0\x18\xc3\xf4\xcc\x43\x5e\x2e\x97\xf9\ +\x48\x64\xcc\xa4\xe1\x70\x8f\xd6\x15\xee\x54\xa2\xd1\x28\x8e\x54\ +\x7b\x2b\x71\xee\xec\x79\xb3\xed\x64\x37\x9f\x8b\xcd\x9b\x1b\xcf\ +\x36\xf7\x23\x96\x65\x29\x8c\x31\xcc\xfc\xf5\x68\x28\x91\x4c\xf2\ +\x7c\x61\x8b\xdb\xb6\xcd\x4d\xd3\xac\x21\xa9\x95\x34\x04\x41\x40\ +\x3c\x9e\xe4\x27\x4e\x1c\x43\xd0\x1f\x00\x07\xc7\xfd\x07\xe3\xe8\ +\xec\x6c\xbf\x86\xef\xbe\xbd\xfe\x59\x7b\x5b\x98\x07\x82\xf5\xfc\ +\xe2\xeb\x03\x5c\xd7\x75\x3e\x7a\xf7\x1e\x1f\x1e\x89\xb4\x65\xff\ +\x7d\x0a\x32\x3c\x3c\x06\x70\xe0\xe7\x5b\xb7\x22\xa9\xe5\xd4\x39\ +\xc3\x30\x71\xe3\x87\x6b\xde\x50\x5d\xa8\x20\xcb\xf2\x3e\xc3\xff\ +\x1c\xd0\xb4\xec\xab\xd3\x33\x0f\xe7\x0d\xd3\xec\x38\xd0\x0e\x0c\ +\x4e\x9b\x73\x8c\xdd\xbd\xcf\x57\x32\x69\x5e\x2e\x97\xb9\xb6\xaa\ +\x4d\x70\xce\x21\x02\x00\xa5\xb4\xf8\xeb\xe0\x90\xd5\xd2\xf2\x02\ +\x8e\x56\x1f\x85\x6d\x73\xc4\x13\xc9\xde\xbd\xa2\x75\x41\xd4\x56\ +\xb3\x58\x5b\x5f\xbf\xd1\xdc\xd4\x24\x2a\x1e\x05\x2e\xa7\x13\x8f\ +\x67\x67\xe1\x74\x38\xd0\xd4\xd4\x10\x11\x76\x4c\xb3\xc6\xd0\xf5\ +\x77\x1a\x9b\x1b\xa0\x54\x29\xd0\x75\x1d\x6b\x6b\x1b\xa8\xac\xac\ +\x7c\xf3\xf1\xdf\xb3\x26\xe9\xeb\x3d\xc3\xd7\x37\x36\x90\xcb\xe7\ +\x30\x15\x1d\xc7\xc2\xe2\x22\x82\xc1\x20\x37\x4c\x5d\x68\x6d\x6d\ +\x85\xf0\xd1\xc7\x57\xfa\x8e\x78\xbd\x68\xa8\x7f\x0e\xfd\xe7\x5f\ +\x83\xa2\x28\x30\x77\x8c\xe7\x5d\x2e\x17\x00\x40\xf0\xf9\x7c\x93\ +\xef\xbd\xff\xee\x8b\xb5\x75\xb5\x7b\xaa\xaa\x22\x12\x19\x4b\x29\ +\x8a\xb2\x1c\xee\xea\x86\x57\xf1\xec\x3f\x0e\x00\x9c\x73\x00\xa0\ +\x84\x10\x4e\x08\xb1\x4b\xa5\x52\xf3\x64\xf4\xcf\x39\x87\xc3\x21\ +\xf9\x03\x35\xf0\x7a\xbd\x90\x5d\x92\xb5\xbb\xbb\x7b\xdd\xed\x96\ +\x3f\xa1\x54\xb4\x38\xb7\x0f\x0b\x08\x00\x0a\x80\x51\x4a\xb1\xb0\ +\xb0\xf8\xf9\x93\xe5\xd4\xd7\x75\x75\xb5\x50\x3c\x55\x90\xdd\x32\ +\x14\xb7\x82\x4a\xb1\x02\x7b\xa5\x22\xe6\x62\x71\x80\xe3\xc7\x8e\ +\xf6\x93\x57\x0e\x09\x00\x80\x31\xe6\x9f\x98\x8c\xfe\x51\x51\xe1\ +\x38\xe6\xf3\xd7\x40\x92\x9c\xa8\xaa\xaa\x82\xec\x92\x40\x29\x45\ +\x2e\x9f\x43\x2c\xfe\x0f\xac\x92\x05\x8f\xc7\xb3\x13\x0c\x04\xde\ +\x12\xe7\x63\x09\x08\x44\x00\x21\xe4\xed\x8c\x96\xb9\x19\x0a\x85\ +\x20\x49\x4e\xb8\x24\x17\xdc\x6e\x37\x24\xa7\x0b\x84\x10\xa4\xd3\ +\x69\x24\x17\x96\x40\x29\x85\x3f\xe0\x5b\x57\xd5\xba\x16\x4a\xe9\ +\x96\x98\xcf\xe5\x4e\xdd\xbe\xfd\xcb\x37\x53\x53\xd3\x5d\x82\x40\ +\x50\xab\xd6\xe2\x8d\x81\x01\x5c\xba\x74\x11\x94\x52\x94\x4a\x25\ +\xc4\x12\x09\x68\x99\x55\x54\x57\x57\x83\x08\x18\x5a\x5d\xd5\xfa\ +\x4d\xd3\x40\x30\x18\x84\x90\xcf\x17\xb6\x0c\xdd\x14\x08\x00\x66\ +\x31\x2c\x25\x97\x70\xf5\xea\x97\x50\xd5\x46\x74\x74\xf6\xe0\xb7\ +\x3b\x77\xa0\x6f\x1b\x08\xd5\xab\x00\xe1\xdf\xeb\xba\xde\xaf\xaa\ +\x21\xb4\x1c\x3f\x0e\x49\x92\x40\x46\x22\x63\x10\x45\x11\xb1\xb9\ +\xc4\x85\xc1\xc1\xc1\x9b\xf9\x7c\xde\x23\x08\xc2\xc1\x26\x28\x14\ +\x0a\xf8\xe0\xc3\xcb\x38\xfb\xca\x99\x2f\x4c\x73\xe7\x2b\x59\x96\ +\xd1\xd4\xd0\x08\x49\x92\x20\x08\x02\xc8\xc8\xf0\x28\x00\x80\x08\ +\x84\x1a\xe6\x4e\x59\xd7\x0d\x64\xb3\xd9\xe6\xcd\xcd\x67\x97\x19\ +\xb3\x5e\xf6\xfb\xfd\x4f\x4f\x9f\x3e\xf5\xa9\xc7\xab\xa4\x82\x81\ +\x00\xfc\x3e\x3f\x6c\xdb\x3e\x1c\xfe\x3f\x11\x5f\xc4\xbb\xcd\x16\ +\x27\xa0\x00\x00\x00\x00\x49\x45\x4e\x44\xae\x42\x60\x82\ +\x00\x00\x07\x22\ +\x89\ +\x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\ +\x00\x00\x90\x00\x00\x00\x90\x08\x06\x00\x00\x00\xe7\x46\xe2\xb8\ +\x00\x00\x00\x04\x73\x42\x49\x54\x08\x08\x08\x08\x7c\x08\x64\x88\ +\x00\x00\x00\x09\x70\x48\x59\x73\x00\x00\x0e\xc4\x00\x00\x0e\xc4\ +\x01\x95\x2b\x0e\x1b\x00\x00\x06\xc4\x49\x44\x41\x54\x78\x9c\xed\ +\x9d\xbb\x71\x1b\x31\x14\x45\xb1\x1e\x05\x6a\x80\x33\x4a\xdc\x04\ +\x4b\x70\xe8\x12\x14\xb3\x2c\xc5\x2a\xc1\xa1\x4b\x50\x13\x4e\x38\ +\xa3\x06\x94\xd1\x81\x06\x36\x04\xee\x2e\x7e\xef\x73\x1f\xf6\x9d\ +\x50\x23\xed\x82\x78\x87\x97\x58\x40\x00\x97\x97\xf7\xa7\x5b\x08\ +\x21\x5c\x4e\xd7\x25\x18\xe4\xf9\xf1\xed\xa6\xdd\x86\x51\x5e\x3f\ +\xce\x26\xfb\xfe\xe5\xfd\xe9\xb6\x44\x81\x22\x2e\x92\x3c\xd6\x04\ +\x4a\x9d\x59\xf2\x1f\x44\x5c\x24\x39\xac\x08\xb4\xe6\xc9\xa6\x40\ +\x11\x17\x89\x1f\x74\x81\xb6\xfc\xb8\x9c\xae\xcb\x52\xfa\xa5\xf4\ +\x97\xa9\x1b\x26\x81\x05\x91\x50\x05\xaa\x71\xa2\x5a\xa0\xf4\x8f\ +\x46\x1b\xa6\x01\xb2\x48\x68\x02\xd5\xb8\x10\x3d\xf8\xd2\xf0\x5a\ +\x89\xd2\x0b\x58\x03\x51\x24\x14\x81\x7a\xea\xdf\x2d\x50\x7e\x21\ +\x6b\x20\x89\xa4\x2d\x50\x6b\xdd\xd3\x9a\xdf\x35\xbc\x47\xa2\xfc\ +\xa2\x96\x40\x10\x49\x4b\x20\x8a\x5a\x93\x09\xb4\x76\x71\x4b\x68\ +\x8a\x24\x2d\xd0\x48\x8d\xf3\xfa\xae\x36\x7c\x54\xa2\xb5\x1b\x59\ +\x41\x43\x24\x29\x81\x38\xea\xca\x26\xd0\xd6\x0d\xad\x20\x29\x12\ +\xb7\x40\x54\xf5\x5c\xab\xe5\xb7\xda\x5f\xec\xe5\xe5\xfd\xe9\x46\ +\x29\xa4\x14\xda\x03\x5b\x0a\x24\xfa\xfe\x81\xf3\xe2\x29\x96\x16\ +\x6d\xf3\xf4\x79\xfd\x38\x2f\x08\x83\xed\x5a\x38\xa4\xd9\xaa\xdb\ +\xa6\x40\x97\xd3\xf5\x6e\xa1\x95\x02\x64\x91\xf6\x24\x89\x89\x84\ +\x2c\x92\x46\xd2\x8b\x25\x50\x0e\x92\x48\x2d\x52\x20\x8a\xc4\x2d\ +\xce\x5e\x8d\x76\x05\xe2\x4a\xa1\x14\x4d\x91\x46\x24\x40\x10\x09\ +\x61\x6c\xa9\x96\x40\x39\x92\x22\x51\x16\x5d\x43\x24\x49\x71\x4a\ +\xf5\x28\x0a\x24\x91\x42\x29\x9c\x22\x71\x16\x59\x42\x24\x84\xc4\ +\xc9\x81\x49\xa0\x1c\x4a\x91\x34\xe6\x74\x28\xef\xa9\x25\x4e\x4d\ +\xdf\x57\x09\x24\x9d\x42\x29\x23\x22\x21\x2c\x4f\x8c\xb4\x01\x31\ +\x71\x72\x60\x13\x28\xa7\x45\x24\xa4\x27\xa4\x1e\x91\x10\xc4\xa9\ +\x7d\xc3\x56\x0b\xa4\x99\x42\x29\x7b\x22\x21\x89\x93\x53\x23\x12\ +\x42\xff\xb6\x62\x26\x81\x72\x52\x91\x90\xc5\xc9\x59\x13\x09\x4d\ +\x9c\x96\xe1\xc2\xea\x5a\x18\xc5\x85\xa5\x40\xeb\xfc\x5a\xa2\x48\ +\x3f\xfe\xfc\xd4\x6e\xca\x10\x4d\x02\xa1\x62\x6d\xe1\xf3\xf9\xf1\ +\xed\x86\x9a\x9a\xad\x21\xd1\xfc\x11\x86\x32\x16\xca\x41\x98\x19\ +\x2e\x81\xdc\xb6\x5e\xcc\x8e\x81\xb6\x40\x14\x09\xa9\x2d\x7b\xf4\ +\x0c\x51\xba\x04\x42\x4d\xa1\x14\x04\x91\xac\x88\x33\xc2\x74\x09\ +\x94\xa3\x21\x92\x45\x71\x7a\x1f\x90\xba\x05\xb2\x90\x42\x29\x12\ +\x22\x59\x14\x67\x94\xe9\x13\x28\x87\x43\x24\xeb\xe2\x8c\x4c\xcf\ +\x0c\x09\x64\x2d\x85\x52\x28\x44\xb2\x2e\x0e\x05\x87\x4b\xa0\x9c\ +\x1e\x91\x66\x12\x67\x74\x72\x78\x58\x20\xcb\x29\x94\x52\x23\xd2\ +\x4c\xe2\x50\x71\xf8\x04\xca\x59\x13\x69\x56\x71\x28\x96\xa6\x48\ +\x96\x32\x10\xd7\xc8\x46\xb1\xb6\x3c\xa2\x85\x27\xd0\x0a\xb3\x26\ +\x4e\x0a\xd5\x9b\x9e\x4c\xa0\x19\xc6\x42\x47\x10\x87\x1a\x4f\xa0\ +\x70\x3c\x71\x28\x87\x1c\xa4\x02\x59\x4b\xa1\xa3\x89\xc3\xc1\x21\ +\x13\xe8\xc8\xe2\x50\x3f\xf0\x90\x0b\x84\x9c\x42\x47\x16\x87\x8b\ +\x43\x24\x90\x8b\xf3\x09\xc7\x74\x0b\x8b\x40\x28\x29\xe4\xe2\xf0\ +\x33\x65\x02\xb9\x38\xf7\x70\x4d\xf6\xb2\x09\xa4\x91\x42\x2e\x8e\ +\x3c\x53\x24\x90\x8b\xb3\x0f\xe7\x52\xd3\x03\x67\xe7\xff\xfe\x6e\ +\x7f\xdf\x93\x75\x7e\x7f\xff\x15\x42\x38\xb3\x5d\xdf\xd4\xae\xce\ +\x59\xe1\x5a\xb8\x8d\xb5\xe5\xbc\x3e\xfb\xc6\x42\x5f\xd5\x2e\xc3\ +\xfd\x26\xe6\xbc\x3e\xab\x40\x2e\x8f\x1e\x52\x52\x8a\x6c\x6d\x76\ +\x91\xca\x58\x4d\x21\x36\x81\x5c\x1a\x3d\x24\x65\x14\x3b\x5c\xc1\ +\x85\x2a\x63\x31\x85\x58\x04\x72\x59\xf4\x90\x96\x50\xf4\x78\x17\ +\x17\xab\x8c\xb5\x14\x22\x17\xc8\x25\xd1\x43\x43\x3e\xf1\x03\xa6\ +\x5c\xb0\x32\x96\x52\x88\x54\x20\x97\x43\x0f\x2d\xe9\x54\x8e\xb8\ +\x73\xd1\xca\x58\x49\x21\x32\x81\x5c\x0a\x3d\x34\x65\x53\x3b\x64\ +\xd3\x85\x2b\x63\x21\x85\x48\x04\x72\x19\xf4\xd0\x96\x4c\xf5\x98\ +\x5f\x17\xaf\x8c\xb6\x20\x25\x86\x05\x72\x09\xf4\x40\x90\x4b\xfd\ +\xa0\x71\x17\xb0\x0c\x82\x28\x5b\x0c\x09\xe4\xc5\xd7\x03\x45\x2a\ +\xf5\x04\x0a\xc1\x45\xac\x01\x45\x98\x9c\x6e\x81\xbc\xe8\x7a\x20\ +\xc9\x04\x91\x40\x21\xb8\x90\x35\x20\x89\x13\xe9\xda\x95\xc1\xbd\ +\x8b\xc0\xb1\x83\x6f\xeb\x71\x86\x98\x62\x67\xea\xd1\x40\xfa\x04\ +\x68\x1e\x03\x71\x8f\x55\x7c\x2c\x54\x06\xe9\x53\xa3\x49\x20\x97\ +\x67\x5e\x44\x1f\xe3\x5d\x24\x7d\x50\x52\xa8\x5a\x20\x97\x66\x5e\ +\x54\x96\x32\x5c\x28\x7d\x10\x52\xa8\x4a\x20\x97\x65\x5e\x54\xff\ +\x9d\xc3\xc5\xd2\x47\x3b\x85\x8a\x02\xb9\x24\xf3\x02\xf1\x2f\xad\ +\x2e\x98\x3e\x9a\x29\xb4\x2b\x90\xcb\x31\x2f\x50\xdb\x7a\x5c\x34\ +\x7d\xb4\x52\x68\x53\x20\x97\x62\x5e\x20\xb7\x36\xbb\x70\xfa\x68\ +\xa4\xd0\xaa\x40\x2e\xc3\xbc\x40\x1f\xef\xe2\xe2\xe9\x23\x9d\x42\ +\x77\x02\xb9\x04\xf3\x62\xe2\x88\x3b\x17\x50\x1f\xc9\x14\xfa\x22\ +\x90\x17\x7f\x5e\x4c\x1d\xf3\xeb\x22\xea\x23\x95\x42\xff\x04\xf2\ +\xa2\xcf\x8b\xc9\xaf\x3a\x70\x21\xf5\x91\x48\xa1\x6f\x21\x78\xb1\ +\x67\x86\x5b\xa2\xe5\xf9\xf1\xed\xc6\x59\x60\x89\x6f\x2d\xfc\xfc\ +\x4e\x2c\x9b\x48\x7c\x9f\x1a\x67\xff\x3c\x58\x97\xc7\x32\xaf\x1f\ +\xe7\xe5\x47\xe0\xef\x23\xce\x1a\xfb\xc6\x42\x05\xa4\x3f\xd2\x5f\ +\xde\x9f\x6e\xe6\xbe\x74\xd7\xd3\xe7\x9e\x19\xc7\x82\x9e\x40\x02\ +\x20\x88\xc3\x95\x42\x2c\x02\x79\xfa\x7c\x82\x20\x0e\x37\x9e\x40\ +\x0c\xa0\x8a\xc3\x91\x42\xe4\x02\x1d\x39\x7d\x50\xc5\xe1\xc4\x13\ +\x88\x00\x4b\xe2\x50\xa7\x10\xa9\x40\x47\x4b\x1f\x4b\xe2\x70\xe1\ +\x09\xd4\x81\x75\x71\x28\x53\x88\x4c\xa0\x23\xa4\x8f\x75\x71\x38\ +\x80\x39\xa5\x15\x9d\xd9\xe4\xa1\x7a\xc3\x93\x08\x34\x6b\xfa\xa4\ +\xd2\x3c\x3f\xbe\xdd\xb4\x0f\x32\x40\xc4\xc7\x40\x2b\xec\xa5\x4d\ +\x94\x68\x86\x44\xa2\x18\x0b\x0d\x0b\x34\x53\xfa\xb4\x48\x31\x93\ +\x48\x23\x78\x02\x85\x31\x09\xac\x8b\x34\x9a\x42\x43\x02\x59\x4f\ +\x1f\xca\xa2\x5b\x17\xa9\x97\x43\x26\x10\x67\x91\x2d\x8a\x34\x92\ +\x42\xdd\x02\x59\x4c\x1f\xc9\xa2\x5a\x14\xa9\x87\x43\x24\x90\x66\ +\x11\xad\x88\xd4\x9b\x42\x5d\x02\x59\x49\x1f\xa4\xa2\x59\x11\xa9\ +\x95\x29\x13\x08\xb9\x48\xc8\x22\xf5\xa4\x50\xb3\x40\xc8\xe9\x83\ +\x58\x94\x2d\xfe\xcf\x6a\xf3\x6f\xeb\xe1\xc4\xfc\x5a\x58\xdc\xf3\ +\x64\x49\x1e\x64\x5a\x03\xa2\x49\x20\xb4\xf4\x49\xe3\xd6\xe2\x5a\ +\xd5\xeb\xc7\x79\xb9\x9c\xae\x0b\xd7\x96\x1b\x09\x4c\x8e\x81\xf6\ +\x3a\x1c\x79\x8c\x11\x59\x6b\x5b\x7c\x4d\x08\x6f\xd2\x96\xb1\x50\ +\xb5\x40\x08\x2f\xac\xe5\x9d\x8a\x28\x52\x4d\x5b\x90\x44\xaa\xc1\ +\x44\x02\x8d\x44\x3c\x82\x48\x3d\xf7\xd6\x16\xa9\x36\x85\xaa\x04\ +\xd2\x7a\x11\x94\x63\x03\x0d\x91\x28\xee\xa5\x2d\x52\x09\xc8\x04\ +\xe2\x1c\x54\x4a\x88\xc4\x71\x6d\x0d\x91\x6a\x52\xa8\x28\x90\x64\ +\x83\x25\x9f\x46\x38\x44\x92\x48\x37\xb4\x44\x82\x48\x20\xcd\xc7\ +\x58\x0a\x91\x34\xc6\x57\x52\x22\x95\x52\x68\x57\x20\xee\xc6\x21\ +\xcd\x7f\xf4\x88\x84\xf0\x84\xa7\x9d\x48\x2a\x09\x84\x24\x4e\x4e\ +\x8d\x48\x08\xe2\xe4\x70\x8a\xb4\x97\x42\x9b\x1d\xc1\xd1\x10\x0e\ +\x71\x24\x67\x9f\x11\xc5\xd9\x82\xba\x7e\x5b\xb5\x13\x59\x0b\xb3\ +\x3e\x5d\x1f\x82\x2d\x79\x42\xa0\xef\xf3\x2d\x21\x57\x6f\x40\x65\ +\xaf\x84\x34\xd2\xeb\x5f\xd6\x44\x8a\x50\xd4\x74\xad\x9e\x2c\x02\ +\x69\x3c\x8e\x4b\x73\x54\x91\xf2\xda\xde\x75\xc2\xc8\x0d\x34\x3e\ +\xa6\xb4\x57\xe0\x8f\x26\x12\x8b\x40\x08\xf3\x38\xda\x1c\x49\xa4\ +\xb4\xde\x5f\x5e\x74\xeb\xc5\x10\x06\xc6\x28\x02\x45\x8e\x20\xd2\ +\xb0\x40\x08\xe2\x44\xd0\x04\x8a\xcc\x2e\x52\x74\xe0\xdf\x8b\xac\ +\xf9\x43\x24\x71\x22\xa8\x02\x45\x66\x15\xa9\x49\x20\x44\x71\x22\ +\xe8\x02\x45\x66\x14\xe9\x72\xba\x2e\xbb\xd3\xdf\xc8\xe2\x44\xac\ +\x08\x14\x99\x49\xa4\x4d\x81\x2c\x88\x13\xb1\x26\x50\x64\x16\x91\ +\x96\xf4\x07\x96\xc4\x89\x58\x15\x28\x62\x5d\xa4\xbf\xa8\xcc\xde\ +\x47\x76\xb8\xb3\xea\x00\x00\x00\x00\x49\x45\x4e\x44\xae\x42\x60\ +\x82\ " qt_resource_name = "\ @@ -1081,19 +1543,30 @@ qt_resource_name = "\ \x07\x03\x7d\xc3\ \x00\x69\ \x00\x6d\x00\x61\x00\x67\x00\x65\x00\x73\ -\x00\x18\ -\x02\x47\xd6\x47\ -\x00\x63\ -\x00\x61\x00\x6e\x00\x2d\x00\x69\x00\x63\x00\x6f\x00\x6e\x00\x2d\x00\x32\x00\x34\x00\x70\x00\x78\x00\x2d\x00\x79\x00\x65\x00\x6c\ -\x00\x6c\x00\x6f\x00\x77\x00\x2e\x00\x70\x00\x6e\x00\x67\ +\x00\x0e\ +\x0a\x51\x2d\xe7\ +\x00\x69\ +\x00\x64\x00\x65\x00\x6e\x00\x74\x00\x69\x00\x74\x00\x69\x00\x65\x00\x73\x00\x2e\x00\x70\x00\x6e\x00\x67\ +\x00\x09\ +\x09\x6b\xb7\xc7\ +\x00\x69\ +\x00\x6e\x00\x62\x00\x6f\x00\x78\x00\x2e\x00\x70\x00\x6e\x00\x67\ +\x00\x0b\ +\x0a\xd0\x22\xa7\ +\x00\x72\ +\x00\x65\x00\x64\x00\x69\x00\x63\x00\x6f\x00\x6e\x00\x2e\x00\x70\x00\x6e\x00\x67\ \x00\x08\ \x0c\x57\x58\x67\ \x00\x73\ \x00\x65\x00\x6e\x00\x74\x00\x2e\x00\x70\x00\x6e\x00\x67\ -\x00\x0e\ -\x02\x47\x93\x47\ -\x00\x79\ -\x00\x65\x00\x6c\x00\x6c\x00\x6f\x00\x77\x00\x69\x00\x63\x00\x6f\x00\x6e\x00\x2e\x00\x70\x00\x6e\x00\x67\ +\x00\x10\ +\x0c\xc3\x45\x27\ +\x00\x71\ +\x00\x69\x00\x64\x00\x65\x00\x6e\x00\x74\x00\x69\x00\x63\x00\x6f\x00\x6e\x00\x5f\x00\x78\x00\x2e\x00\x70\x00\x6e\x00\x67\ +\x00\x0d\ +\x02\xe8\x12\x87\ +\x00\x62\ +\x00\x6c\x00\x61\x00\x63\x00\x6b\x00\x6c\x00\x69\x00\x73\x00\x74\x00\x2e\x00\x70\x00\x6e\x00\x67\ \x00\x08\ \x0c\x47\x58\x67\ \x00\x73\ @@ -1103,77 +1576,94 @@ qt_resource_name = "\ \x00\x61\ \x00\x64\x00\x64\x00\x72\x00\x65\x00\x73\x00\x73\x00\x62\x00\x6f\x00\x6f\x00\x6b\x00\x2e\x00\x70\x00\x6e\x00\x67\ \x00\x11\ -\x03\x89\x73\x27\ -\x00\x63\ -\x00\x61\x00\x6e\x00\x2d\x00\x69\x00\x63\x00\x6f\x00\x6e\x00\x2d\x00\x31\x00\x36\x00\x70\x00\x78\x00\x2e\x00\x70\x00\x6e\x00\x67\ +\x07\x34\x2d\xc7\ +\x00\x6e\ +\x00\x65\x00\x74\x00\x77\x00\x6f\x00\x72\x00\x6b\x00\x73\x00\x74\x00\x61\x00\x74\x00\x75\x00\x73\x00\x2e\x00\x70\x00\x6e\x00\x67\ \ -\x00\x09\ -\x09\x6b\xb7\xc7\ -\x00\x69\ -\x00\x6e\x00\x62\x00\x6f\x00\x78\x00\x2e\x00\x70\x00\x6e\x00\x67\ +\x00\x18\ +\x02\x47\xd6\x47\ +\x00\x63\ +\x00\x61\x00\x6e\x00\x2d\x00\x69\x00\x63\x00\x6f\x00\x6e\x00\x2d\x00\x32\x00\x34\x00\x70\x00\x78\x00\x2d\x00\x79\x00\x65\x00\x6c\ +\x00\x6c\x00\x6f\x00\x77\x00\x2e\x00\x70\x00\x6e\x00\x67\ \x00\x11\ \x02\xa0\x44\xa7\ \x00\x73\ \x00\x75\x00\x62\x00\x73\x00\x63\x00\x72\x00\x69\x00\x70\x00\x74\x00\x69\x00\x6f\x00\x6e\x00\x73\x00\x2e\x00\x70\x00\x6e\x00\x67\ \ +\x00\x0e\ +\x09\x39\xff\x47\ +\x00\x71\ +\x00\x69\x00\x64\x00\x65\x00\x6e\x00\x74\x00\x69\x00\x63\x00\x6f\x00\x6e\x00\x2e\x00\x70\x00\x6e\x00\x67\ +\x00\x11\ +\x05\x89\x73\x07\ +\x00\x63\ +\x00\x61\x00\x6e\x00\x2d\x00\x69\x00\x63\x00\x6f\x00\x6e\x00\x2d\x00\x32\x00\x34\x00\x70\x00\x78\x00\x2e\x00\x70\x00\x6e\x00\x67\ +\ \x00\x15\ \x0c\xfc\x45\x87\ \x00\x63\ \x00\x61\x00\x6e\x00\x2d\x00\x69\x00\x63\x00\x6f\x00\x6e\x00\x2d\x00\x32\x00\x34\x00\x70\x00\x78\x00\x2d\x00\x72\x00\x65\x00\x64\ \x00\x2e\x00\x70\x00\x6e\x00\x67\ -\x00\x11\ -\x05\x89\x73\x07\ -\x00\x63\ -\x00\x61\x00\x6e\x00\x2d\x00\x69\x00\x63\x00\x6f\x00\x6e\x00\x2d\x00\x32\x00\x34\x00\x70\x00\x78\x00\x2e\x00\x70\x00\x6e\x00\x67\ -\ -\x00\x0e\ -\x0a\x51\x2d\xe7\ -\x00\x69\ -\x00\x64\x00\x65\x00\x6e\x00\x74\x00\x69\x00\x74\x00\x69\x00\x65\x00\x73\x00\x2e\x00\x70\x00\x6e\x00\x67\ -\x00\x0b\ -\x0a\xd0\x22\xa7\ -\x00\x72\ -\x00\x65\x00\x64\x00\x69\x00\x63\x00\x6f\x00\x6e\x00\x2e\x00\x70\x00\x6e\x00\x67\ \x00\x0d\ -\x02\xe8\x12\x87\ -\x00\x62\ -\x00\x6c\x00\x61\x00\x63\x00\x6b\x00\x6c\x00\x69\x00\x73\x00\x74\x00\x2e\x00\x70\x00\x6e\x00\x67\ -\x00\x11\ -\x07\x34\x2d\xc7\ -\x00\x6e\ -\x00\x65\x00\x74\x00\x77\x00\x6f\x00\x72\x00\x6b\x00\x73\x00\x74\x00\x61\x00\x74\x00\x75\x00\x73\x00\x2e\x00\x70\x00\x6e\x00\x67\ -\ +\x07\x76\xdf\x07\ +\x00\x67\ +\x00\x72\x00\x65\x00\x65\x00\x6e\x00\x69\x00\x63\x00\x6f\x00\x6e\x00\x2e\x00\x70\x00\x6e\x00\x67\ \x00\x17\ \x00\xd3\x62\xc7\ \x00\x63\ \x00\x61\x00\x6e\x00\x2d\x00\x69\x00\x63\x00\x6f\x00\x6e\x00\x2d\x00\x32\x00\x34\x00\x70\x00\x78\x00\x2d\x00\x67\x00\x72\x00\x65\ \x00\x65\x00\x6e\x00\x2e\x00\x70\x00\x6e\x00\x67\ -\x00\x0d\ -\x07\x76\xdf\x07\ +\x00\x11\ +\x02\x8c\x5e\x67\ +\x00\x6e\ +\x00\x6f\x00\x5f\x00\x69\x00\x64\x00\x65\x00\x6e\x00\x74\x00\x69\x00\x63\x00\x6f\x00\x6e\x00\x73\x00\x2e\x00\x70\x00\x6e\x00\x67\ +\ +\x00\x0e\ +\x02\x47\x93\x47\ +\x00\x79\ +\x00\x65\x00\x6c\x00\x6c\x00\x6f\x00\x77\x00\x69\x00\x63\x00\x6f\x00\x6e\x00\x2e\x00\x70\x00\x6e\x00\x67\ +\x00\x12\ +\x03\xf4\x2e\xc7\ +\x00\x71\ +\x00\x69\x00\x64\x00\x65\x00\x6e\x00\x74\x00\x69\x00\x63\x00\x6f\x00\x6e\x00\x5f\x00\x74\x00\x77\x00\x6f\x00\x2e\x00\x70\x00\x6e\ \x00\x67\ -\x00\x72\x00\x65\x00\x65\x00\x6e\x00\x69\x00\x63\x00\x6f\x00\x6e\x00\x2e\x00\x70\x00\x6e\x00\x67\ +\x00\x11\ +\x03\x89\x73\x27\ +\x00\x63\ +\x00\x61\x00\x6e\x00\x2d\x00\x69\x00\x63\x00\x6f\x00\x6e\x00\x2d\x00\x31\x00\x36\x00\x70\x00\x78\x00\x2e\x00\x70\x00\x6e\x00\x67\ +\ +\x00\x14\ +\x07\x12\xd0\xa7\ +\x00\x71\ +\x00\x69\x00\x64\x00\x65\x00\x6e\x00\x74\x00\x69\x00\x63\x00\x6f\x00\x6e\x00\x5f\x00\x74\x00\x77\x00\x6f\x00\x5f\x00\x78\x00\x2e\ +\x00\x70\x00\x6e\x00\x67\ " qt_resource_struct = "\ \x00\x00\x00\x00\x00\x02\x00\x00\x00\x01\x00\x00\x00\x01\ \x00\x00\x00\x00\x00\x02\x00\x00\x00\x01\x00\x00\x00\x02\ -\x00\x00\x00\x18\x00\x02\x00\x00\x00\x10\x00\x00\x00\x03\ -\x00\x00\x02\x18\x00\x00\x00\x00\x00\x01\x00\x00\x35\x5d\ -\x00\x00\x00\x76\x00\x00\x00\x00\x00\x01\x00\x00\x09\x8e\ +\x00\x00\x00\x18\x00\x02\x00\x00\x00\x15\x00\x00\x00\x03\ +\x00\x00\x02\x36\x00\x00\x00\x00\x00\x01\x00\x00\x3f\x80\ +\x00\x00\x02\x92\x00\x00\x00\x00\x00\x01\x00\x00\x47\xb7\ +\x00\x00\x01\x3e\x00\x00\x00\x00\x00\x01\x00\x00\x1d\x49\ +\x00\x00\x02\x6a\x00\x00\x00\x00\x00\x01\x00\x00\x46\xee\ +\x00\x00\x01\x74\x00\x00\x00\x00\x00\x01\x00\x00\x24\xaf\ +\x00\x00\x00\xbc\x00\x00\x00\x00\x00\x01\x00\x00\x11\xfc\ +\x00\x00\x02\xde\x00\x00\x00\x00\x00\x01\x00\x00\x51\x02\ +\x00\x00\x02\xb4\x00\x00\x00\x00\x00\x01\x00\x00\x4a\x35\ +\x00\x00\x00\xf2\x00\x00\x00\x00\x00\x01\x00\x00\x18\x2b\ +\x00\x00\x01\xbe\x00\x00\x00\x00\x00\x01\x00\x00\x2d\x9f\ +\x00\x00\x03\x06\x00\x00\x00\x00\x00\x01\x00\x00\x54\xf5\ +\x00\x00\x01\x16\x00\x00\x00\x00\x00\x01\x00\x00\x1a\xd0\ +\x00\x00\x02\x16\x00\x00\x00\x00\x00\x01\x00\x00\x3c\x48\ +\x00\x00\x01\x9c\x00\x00\x00\x00\x00\x01\x00\x00\x27\x2a\ +\x00\x00\x00\x4c\x00\x00\x00\x00\x00\x01\x00\x00\x03\x6a\ \x00\x00\x00\x2a\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\ -\x00\x00\x01\x12\x00\x00\x00\x00\x00\x01\x00\x00\x18\x92\ -\x00\x00\x01\xd0\x00\x00\x00\x00\x00\x01\x00\x00\x2f\xf0\ -\x00\x00\x00\xd2\x00\x00\x00\x00\x00\x01\x00\x00\x11\xec\ -\x00\x00\x00\xae\x00\x00\x00\x00\x00\x01\x00\x00\x0f\x47\ -\x00\x00\x01\x6a\x00\x00\x00\x00\x00\x01\x00\x00\x22\x76\ -\x00\x00\x01\xf0\x00\x00\x00\x00\x00\x01\x00\x00\x32\xe4\ -\x00\x00\x02\x4c\x00\x00\x00\x00\x00\x01\x00\x00\x3c\xcb\ -\x00\x00\x00\xfa\x00\x00\x00\x00\x00\x01\x00\x00\x15\xdf\ -\x00\x00\x01\x92\x00\x00\x00\x00\x00\x01\x00\x00\x29\xb6\ -\x00\x00\x01\xb4\x00\x00\x00\x00\x00\x01\x00\x00\x2d\x20\ -\x00\x00\x00\x98\x00\x00\x00\x00\x00\x01\x00\x00\x0c\x0c\ -\x00\x00\x00\x60\x00\x00\x00\x00\x00\x01\x00\x00\x07\x66\ -\x00\x00\x01\x3a\x00\x00\x00\x00\x00\x01\x00\x00\x1b\x0d\ +\x00\x00\x00\x64\x00\x00\x00\x00\x00\x01\x00\x00\x06\x1d\ +\x00\x00\x00\xdc\x00\x00\x00\x00\x00\x01\x00\x00\x14\xf0\ +\x00\x00\x00\x80\x00\x00\x00\x00\x00\x01\x00\x00\x08\xed\ +\x00\x00\x00\x96\x00\x00\x00\x00\x00\x01\x00\x00\x0b\x15\ +\x00\x00\x01\xe6\x00\x00\x00\x00\x00\x01\x00\x00\x34\xdf\ " def qInitResources(): diff --git a/src/bitmessageqt/bitmessageui.py b/src/bitmessageqt/bitmessageui.py index 8f8b8951..e186fba3 100644 --- a/src/bitmessageqt/bitmessageui.py +++ b/src/bitmessageqt/bitmessageui.py @@ -2,7 +2,7 @@ # Form implementation generated from reading ui file 'bitmessageui.ui' # -# Created: Wed Sep 18 17:38:54 2013 +# Created: Sat Sep 21 09:44:07 2013 # by: PyQt4 UI code generator 4.10.2 # # WARNING! All changes made in this file will be lost! diff --git a/src/bitmessageqt/settings.py b/src/bitmessageqt/settings.py index 290c0cb6..054aead7 100644 --- a/src/bitmessageqt/settings.py +++ b/src/bitmessageqt/settings.py @@ -2,7 +2,7 @@ # Form implementation generated from reading ui file 'settings.ui' # -# Created: Wed Sep 18 17:38:55 2013 +# Created: Sat Sep 21 09:44:07 2013 # by: PyQt4 UI code generator 4.10.2 # # WARNING! All changes made in this file will be lost! diff --git a/src/helper_startup.py b/src/helper_startup.py index 24dc0156..b9b264f6 100644 --- a/src/helper_startup.py +++ b/src/helper_startup.py @@ -71,6 +71,10 @@ def loadConfig(): 'bitmessagesettings', 'maxacceptablepayloadlengthextrabytes', '0') shared.config.set('bitmessagesettings', 'dontconnect', 'true') shared.config.set('bitmessagesettings', 'userlocale', 'system') + shared.config.set('bitmessagesettings', 'identicon', 'None') + import random, string + shared.config.set('bitmessagesettings', 'identiconsuffix', ''.join(random.choice("123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz") for x in range(12))) # a twelve character pseudo-password to salt the identicons + shared.config.set('bitmessagesettings', 'avatars', 'false') ensureNamecoinOptions() if storeConfigFilesInSameDirectoryAsProgramByDefault: From d6bd2b393827feb0f9958e842658ec18c458568c Mon Sep 17 00:00:00 2001 From: sendiulo Date: Sat, 21 Sep 2013 14:31:47 +0200 Subject: [PATCH 14/48] - GUI settings done --- src/bitmessageqt/__init__.py | 24 +++++++++++- src/bitmessageqt/bitmessage_icons_rc.py | 2 +- src/bitmessageqt/bitmessageui.py | 2 +- src/bitmessageqt/settings.py | 38 +++++-------------- src/bitmessageqt/settings.ui | 49 +------------------------ 5 files changed, 36 insertions(+), 79 deletions(-) diff --git a/src/bitmessageqt/__init__.py b/src/bitmessageqt/__init__.py index dd1c1a7a..5895c98f 100644 --- a/src/bitmessageqt/__init__.py +++ b/src/bitmessageqt/__init__.py @@ -2241,10 +2241,20 @@ class MyForm(QtGui.QMainWindow): self.settingsDialogInstance.ui.checkBoxStartInTray.isChecked())) shared.config.set('bitmessagesettings', 'willinglysendtomobile', str( self.settingsDialogInstance.ui.checkBoxWillinglySendToMobile.isChecked())) + lang_ind = int(self.settingsDialogInstance.ui.languageComboBox.currentIndex()) if not languages[lang_ind] == 'other': shared.config.set('bitmessagesettings', 'userlocale', languages[lang_ind]) + + ### + curr_index = self.settingsDialogInstance.ui.comboBoxIdenticonStyle.currentIndex() + shared.config.set('bitmessagesettings', 'identicon', str(self.settingsDialogInstance.ui.comboBoxIdenticonStyle.itemData( + curr_index , Qt.UserRole).toString())) + shared.config.set('bitmessagesettings', 'identiconsuffix', str( + self.settingsDialogInstance.ui.lineEditIdenticonSuffix.text())) + shared.config.set('bitmessagesettings', 'avatars', str( + self.settingsDialogInstance.ui.checkBoxLoadAvatars.isChecked())) if int(shared.config.get('bitmessagesettings', 'port')) != int(self.settingsDialogInstance.ui.lineEditTCPPort.text()): if not shared.safeConfigGetBoolean('bitmessagesettings', 'dontconnect'): @@ -3104,7 +3114,6 @@ class MyForm(QtGui.QMainWindow): # set the icon thisTableWidget.item( currentRow, 0).setIcon(avatarize(addressAtCurrentRow)) - ### shared.reloadBroadcastSendersForWhichImWatching() self.rerenderSubscriptions() self.rerenderComboBoxSendFrom() @@ -3349,6 +3358,19 @@ class settingsDialog(QtGui.QDialog): curr_index = languages.index('other') self.ui.languageComboBox.setCurrentIndex(curr_index) + ### + self.ui.comboBoxIdenticonStyle.addItem(QIcon(":/newPrefix/images/no_identicons.png"), _translate("settingsDialog", "None"), "none") + self.ui.comboBoxIdenticonStyle.addItem(QIcon(":/newPrefix/images/qidenticon.png"), _translate("settingsDialog", "QIdenticon"), "qidenticon") + self.ui.comboBoxIdenticonStyle.addItem(QIcon(":/newPrefix/images/qidenticon_x.png"), _translate("settingsDialog", "QIdenticon (transparent)"), "qidenticon_x") + self.ui.comboBoxIdenticonStyle.addItem(QIcon(":/newPrefix/images/qidenticon_two.png"), _translate("settingsDialog", "QIdenticon two"), "qidenticon_two") + self.ui.comboBoxIdenticonStyle.addItem(QIcon(":/newPrefix/images/qidenticon_two_x.png"), _translate("settingsDialog", "QIdenticon two (transparent)"), "qidenticon_two_x") + curr_index = self.ui.comboBoxIdenticonStyle.findData(str(shared.config.get('bitmessagesettings', 'identicon')), Qt.UserRole) + self.ui.comboBoxIdenticonStyle.setCurrentIndex(curr_index) + self.ui.lineEditIdenticonSuffix.setText( + str(shared.config.get('bitmessagesettings', 'identiconsuffix'))) + self.ui.checkBoxLoadAvatars.setChecked( + shared.safeConfigGetBoolean('bitmessagesettings', 'avatars')) + if shared.appdata == '': self.ui.checkBoxPortableMode.setChecked(True) if 'darwin' in sys.platform: diff --git a/src/bitmessageqt/bitmessage_icons_rc.py b/src/bitmessageqt/bitmessage_icons_rc.py index 2db046a4..bb0a02c0 100644 --- a/src/bitmessageqt/bitmessage_icons_rc.py +++ b/src/bitmessageqt/bitmessage_icons_rc.py @@ -2,7 +2,7 @@ # Resource object code # -# Created: Sa 21. Sep 09:44:10 2013 +# Created: Sa 21. Sep 13:45:58 2013 # by: The Resource Compiler for PyQt (Qt v4.8.4) # # WARNING! All changes made in this file will be lost! diff --git a/src/bitmessageqt/bitmessageui.py b/src/bitmessageqt/bitmessageui.py index e186fba3..10fb1f8f 100644 --- a/src/bitmessageqt/bitmessageui.py +++ b/src/bitmessageqt/bitmessageui.py @@ -2,7 +2,7 @@ # Form implementation generated from reading ui file 'bitmessageui.ui' # -# Created: Sat Sep 21 09:44:07 2013 +# Created: Sat Sep 21 13:55:14 2013 # by: PyQt4 UI code generator 4.10.2 # # WARNING! All changes made in this file will be lost! diff --git a/src/bitmessageqt/settings.py b/src/bitmessageqt/settings.py index 054aead7..d80f5e80 100644 --- a/src/bitmessageqt/settings.py +++ b/src/bitmessageqt/settings.py @@ -2,7 +2,7 @@ # Form implementation generated from reading ui file 'settings.ui' # -# Created: Sat Sep 21 09:44:07 2013 +# Created: Sat Sep 21 13:55:15 2013 # by: PyQt4 UI code generator 4.10.2 # # WARNING! All changes made in this file will be lost! @@ -83,31 +83,16 @@ class Ui_settingsDialog(object): self.gridLayout_5.addWidget(self.groupBox, 7, 1, 4, 1) self.groupBox_3 = QtGui.QGroupBox(self.tabUserInterface) self.groupBox_3.setObjectName(_fromUtf8("groupBox_3")) - self.comboBox = QtGui.QComboBox(self.groupBox_3) - self.comboBox.setGeometry(QtCore.QRect(20, 20, 251, 31)) - self.comboBox.setIconSize(QtCore.QSize(24, 24)) - self.comboBox.setObjectName(_fromUtf8("comboBox")) - icon = QtGui.QIcon() - icon.addPixmap(QtGui.QPixmap(_fromUtf8(":/newPrefix/images/no_identicons.png")), QtGui.QIcon.Normal, QtGui.QIcon.Off) - self.comboBox.addItem(icon, _fromUtf8("")) - icon1 = QtGui.QIcon() - icon1.addPixmap(QtGui.QPixmap(_fromUtf8(":/newPrefix/images/qidenticon.png")), QtGui.QIcon.Normal, QtGui.QIcon.Off) - self.comboBox.addItem(icon1, _fromUtf8("")) - icon2 = QtGui.QIcon() - icon2.addPixmap(QtGui.QPixmap(_fromUtf8(":/newPrefix/images/qidenticon_x.png")), QtGui.QIcon.Normal, QtGui.QIcon.Off) - self.comboBox.addItem(icon2, _fromUtf8("")) - icon3 = QtGui.QIcon() - icon3.addPixmap(QtGui.QPixmap(_fromUtf8(":/newPrefix/images/qidenticon_two.png")), QtGui.QIcon.Normal, QtGui.QIcon.Off) - self.comboBox.addItem(icon3, _fromUtf8("")) - icon4 = QtGui.QIcon() - icon4.addPixmap(QtGui.QPixmap(_fromUtf8(":/newPrefix/images/qidenticon_two_x.png")), QtGui.QIcon.Normal, QtGui.QIcon.Off) - self.comboBox.addItem(icon4, _fromUtf8("")) + self.comboBoxIdenticonStyle = QtGui.QComboBox(self.groupBox_3) + self.comboBoxIdenticonStyle.setGeometry(QtCore.QRect(20, 20, 251, 31)) + self.comboBoxIdenticonStyle.setIconSize(QtCore.QSize(24, 24)) + self.comboBoxIdenticonStyle.setObjectName(_fromUtf8("comboBoxIdenticonStyle")) self.checkBoxLoadAvatars = QtGui.QCheckBox(self.groupBox_3) self.checkBoxLoadAvatars.setGeometry(QtCore.QRect(20, 50, 121, 18)) self.checkBoxLoadAvatars.setObjectName(_fromUtf8("checkBoxLoadAvatars")) - self.lineEdit = QtGui.QLineEdit(self.groupBox_3) - self.lineEdit.setGeometry(QtCore.QRect(140, 50, 131, 16)) - self.lineEdit.setObjectName(_fromUtf8("lineEdit")) + self.lineEditIdenticonSuffix = QtGui.QLineEdit(self.groupBox_3) + self.lineEditIdenticonSuffix.setGeometry(QtCore.QRect(140, 50, 131, 16)) + self.lineEditIdenticonSuffix.setObjectName(_fromUtf8("lineEditIdenticonSuffix")) self.gridLayout_5.addWidget(self.groupBox_3, 7, 0, 4, 1) self.tabWidgetSettings.addTab(self.tabUserInterface, _fromUtf8("")) self.tabNetworkSettings = QtGui.QWidget() @@ -380,13 +365,8 @@ class Ui_settingsDialog(object): self.languageComboBox.setItemText(7, _translate("settingsDialog", "Pirate English", "en_pirate")) self.languageComboBox.setItemText(8, _translate("settingsDialog", "Other (set in keys.dat)", "other")) self.groupBox_3.setTitle(_translate("settingsDialog", "Identicons (with example image)", None)) - self.comboBox.setItemText(0, _translate("settingsDialog", "no identicons", None)) - self.comboBox.setItemText(1, _translate("settingsDialog", "qidenticon", None)) - self.comboBox.setItemText(2, _translate("settingsDialog", "qidenticon_x", None)) - self.comboBox.setItemText(3, _translate("settingsDialog", "qidenticon_two", None)) - self.comboBox.setItemText(4, _translate("settingsDialog", "qidenticon_two_x", None)) self.checkBoxLoadAvatars.setText(_translate("settingsDialog", "Load avatar images", None)) - self.lineEdit.setToolTip(_translate("settingsDialog", "

The content of this text field will be appended to the BM-address before creating the hash for the identicons. By default it is filled with a random string to make the identicons in your client unique, otherwise the identicon could be an attack vector if an adversary creates an address resulting in a similar identicon. If you keep this string (or any other random or non-random string) you will be able to keep the same identicons.

", None)) + self.lineEditIdenticonSuffix.setToolTip(_translate("settingsDialog", "

The content of this text field will be appended to the BM-address before creating the hash for the identicons. By default it is filled with a random string to make the identicons in your client unique, otherwise the identicon could be an attack vector if an adversary creates an address resulting in a similar identicon. If you keep this string (or any other random or non-random string) you will be able to keep the same identicons.

", None)) self.tabWidgetSettings.setTabText(self.tabWidgetSettings.indexOf(self.tabUserInterface), _translate("settingsDialog", "User Interface", None)) self.groupBox1.setTitle(_translate("settingsDialog", "Listening port", None)) self.label.setText(_translate("settingsDialog", "Listen for connections on port:", None)) diff --git a/src/bitmessageqt/settings.ui b/src/bitmessageqt/settings.ui index a5e6e4e6..dd330f2c 100644 --- a/src/bitmessageqt/settings.ui +++ b/src/bitmessageqt/settings.ui @@ -155,7 +155,7 @@ Identicons (with example image) - + 20 @@ -170,51 +170,6 @@ 24 - - - no identicons - - - - :/newPrefix/images/no_identicons.png:/newPrefix/images/no_identicons.png - - - - - qidenticon - - - - :/newPrefix/images/qidenticon.png:/newPrefix/images/qidenticon.png - - - - - qidenticon_x - - - - :/newPrefix/images/qidenticon_x.png:/newPrefix/images/qidenticon_x.png - - - - - qidenticon_two - - - - :/newPrefix/images/qidenticon_two.png:/newPrefix/images/qidenticon_two.png - - - - - qidenticon_two_x - - - - :/newPrefix/images/qidenticon_two_x.png:/newPrefix/images/qidenticon_two_x.png - - @@ -229,7 +184,7 @@ Load avatar images - + 140 From cd2c55dd2da9ed5a41812e9bd42aeb31cea6dd50 Mon Sep 17 00:00:00 2001 From: sendiulo Date: Sat, 21 Sep 2013 14:49:30 +0200 Subject: [PATCH 15/48] removed bug: sent icon wrongly put into column 0 instead of 1 --- src/bitmessageqt/__init__.py | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/src/bitmessageqt/__init__.py b/src/bitmessageqt/__init__.py index 5895c98f..7dd19764 100644 --- a/src/bitmessageqt/__init__.py +++ b/src/bitmessageqt/__init__.py @@ -112,9 +112,6 @@ def identiconize(address): idcon = QtGui.QIcon() idcon.addPixmap(pix, QtGui.QIcon.Normal, QtGui.QIcon.Off) return idcon - elif identicon_lib == 'empty': - idcon = QtGui.QIcon(":/newPrefix/images/no_identicons.png") - return idcon elif identicon_lib in ['False', 'false', 'None', 'none']: idcon = QtGui.QIcon() return idcon @@ -174,9 +171,6 @@ def avatarize(address, fallBackToIdenticon = False): except: # default to no identicons identicon_lib = False - if identicon_lib == 'empty': - idcon = QtGui.QIcon(":/newPrefix/images/no_identicons.png") - return idcon return idcon class MyForm(QtGui.QMainWindow): @@ -1690,7 +1684,7 @@ class MyForm(QtGui.QMainWindow): self.ui.tableWidgetSent.item( i, 1).setText(unicode(fromLabel, 'utf-8')) self.ui.tableWidgetSent.item( - i, 0).setIcon(avatarize(fromAddress, True)) + i, 1).setIcon(avatarize(fromAddress, True)) def rerenderSentToLabels(self): for i in range(self.ui.tableWidgetSent.rowCount()): @@ -3358,8 +3352,7 @@ class settingsDialog(QtGui.QDialog): curr_index = languages.index('other') self.ui.languageComboBox.setCurrentIndex(curr_index) - ### - self.ui.comboBoxIdenticonStyle.addItem(QIcon(":/newPrefix/images/no_identicons.png"), _translate("settingsDialog", "None"), "none") + self.ui.comboBoxIdenticonStyle.addItem(_translate("settingsDialog", "None"), "none") self.ui.comboBoxIdenticonStyle.addItem(QIcon(":/newPrefix/images/qidenticon.png"), _translate("settingsDialog", "QIdenticon"), "qidenticon") self.ui.comboBoxIdenticonStyle.addItem(QIcon(":/newPrefix/images/qidenticon_x.png"), _translate("settingsDialog", "QIdenticon (transparent)"), "qidenticon_x") self.ui.comboBoxIdenticonStyle.addItem(QIcon(":/newPrefix/images/qidenticon_two.png"), _translate("settingsDialog", "QIdenticon two"), "qidenticon_two") From 21005fb0a8f8f025c97e0220e9cbee946f697031 Mon Sep 17 00:00:00 2001 From: sendiulo Date: Sat, 21 Sep 2013 17:06:54 +0200 Subject: [PATCH 16/48] - add the settings also for old users --- src/bitmessageqt/__init__.py | 4 +--- src/class_sqlThread.py | 7 +++++++ 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/src/bitmessageqt/__init__.py b/src/bitmessageqt/__init__.py index 4c03378c..dc9627eb 100644 --- a/src/bitmessageqt/__init__.py +++ b/src/bitmessageqt/__init__.py @@ -2171,12 +2171,10 @@ class MyForm(QtGui.QMainWindow): shared.config.set('bitmessagesettings', 'willinglysendtomobile', str( self.settingsDialogInstance.ui.checkBoxWillinglySendToMobile.isChecked())) - lang_ind = int(self.settingsDialogInstance.ui.languageComboBox.currentIndex()) if not languages[lang_ind] == 'other': shared.config.set('bitmessagesettings', 'userlocale', languages[lang_ind]) - - ### + curr_index = self.settingsDialogInstance.ui.comboBoxIdenticonStyle.currentIndex() shared.config.set('bitmessagesettings', 'identicon', str(self.settingsDialogInstance.ui.comboBoxIdenticonStyle.itemData( curr_index , Qt.UserRole).toString())) diff --git a/src/class_sqlThread.py b/src/class_sqlThread.py index 34109172..9764e3c7 100644 --- a/src/class_sqlThread.py +++ b/src/class_sqlThread.py @@ -208,6 +208,13 @@ class sqlThread(threading.Thread): if not shared.config.has_option('bitmessagesettings', 'userlocale'): shared.config.set('bitmessagesettings', 'userlocale', 'system') + if not shared.config.has_option('bitmessagesettings', 'identicon'): + shared.config.set('bitmessagesettings', 'identicon', 'None') + if not shared.config.has_option('bitmessagesettings', 'identiconsuffix'): + import random, string + shared.config.set('bitmessagesettings', 'identiconsuffix', ''.join(random.choice("123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz") for x in range(12))) # a twelve character pseudo-password to salt the identicons + if not shared.config.has_option('bitmessagesettings', 'avatars'): + shared.config.set('bitmessagesettings', 'avatars', 'false') if not shared.config.has_option('bitmessagesettings', 'sendoutgoingconnections'): shared.config.set('bitmessagesettings', 'sendoutgoingconnections', 'True') From cdf4d5d75cbc663ad9ca47e9e631157f066aeec5 Mon Sep 17 00:00:00 2001 From: amos Date: Mon, 23 Sep 2013 21:05:11 -0700 Subject: [PATCH 17/48] Change the accepted address versions Update _verifyAddress to accept version 4. --- src/bitmessagemain.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/bitmessagemain.py b/src/bitmessagemain.py index 3d7f5c73..4e473c85 100755 --- a/src/bitmessagemain.py +++ b/src/bitmessagemain.py @@ -166,7 +166,7 @@ class MySimpleXMLRPCRequestHandler(SimpleXMLRPCRequestHandler): raise APIError(10, 'Address version number too high (or zero) in address: ' + address) raise APIError(7, 'Could not decode address: ' + address + ' : ' + status) if addressVersionNumber < 2 or addressVersionNumber > 3: - raise APIError(11, 'The address version number currently must be 2 or 3. Others aren\'t supported. Check the address.') + raise APIError(11, 'The address version number currently must be 2, 3 or 4. Others aren\'t supported. Check the address.') if streamNumber != 1: raise APIError(12, 'The stream number must be 1. Others aren\'t supported. Check the address.') From e878fb4c96f983b9b6fa426b3bc15a5aacd56a9c Mon Sep 17 00:00:00 2001 From: amos Date: Mon, 23 Sep 2013 22:35:20 -0700 Subject: [PATCH 18/48] Add api method decodeAddress Add decodeAddress as an api call. Like the addresses.decodeAddress function it return status, addressVersion, streamNumber, and ripe. ripe is base64 encoded. --- src/bitmessagemain.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/bitmessagemain.py b/src/bitmessagemain.py index 47925a46..2e7bb9af 100755 --- a/src/bitmessagemain.py +++ b/src/bitmessagemain.py @@ -818,6 +818,15 @@ class MySimpleXMLRPCRequestHandler(SimpleXMLRPCRequestHandler): else: networkStatus = 'connectedAndReceivingIncomingConnections' return json.dumps({'networkConnections':len(shared.connectedHostsList),'numberOfMessagesProcessed':shared.numberOfMessagesProcessed, 'numberOfBroadcastsProcessed':shared.numberOfBroadcastsProcessed, 'numberOfPubkeysProcessed':shared.numberOfPubkeysProcessed, 'networkStatus':networkStatus, 'softwareName':'PyBitmessage','softwareVersion':shared.softwareVersion}, indent=4, separators=(',', ': ')) + elif method == 'decodeAddress': + #decode an address + if len(params) != 1: + raise APIError(0, 'I need 1 parameter!') + address, = params + status, addressVersion, streamNumber, ripe = decodeAddress(address) + return json.dumps({'status':status, 'addressVersion':addressVersion, + 'streamNumber':streamNumber, 'ripe':ripe.encode('base64')}, indent=4, + separators=(',', ': ')) else: raise APIError(20, 'Invalid method: %s' % method) From 379d27b5d8ce2593c4979e8219cb609de8ab44f6 Mon Sep 17 00:00:00 2001 From: amos Date: Mon, 23 Sep 2013 23:00:50 -0700 Subject: [PATCH 19/48] update address versions in addChan --- src/bitmessagemain.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/bitmessagemain.py b/src/bitmessagemain.py index 76a4e53c..98760deb 100755 --- a/src/bitmessagemain.py +++ b/src/bitmessagemain.py @@ -418,9 +418,9 @@ class MySimpleXMLRPCRequestHandler(SimpleXMLRPCRequestHandler): if label == '': label = '[chan] ' + passphrase if addressVersionNumber == 0: # 0 means "just use the proper addressVersionNumber" - addressVersionNumber = 3 - if addressVersionNumber != 3: - raise APIError(2,'The address version number currently must be 3 (or 0 which means auto-select). ' + addressVersionNumber + ' isn\'t supported.') + addressVersionNumber = 4 + if addressVersionNumber != 3 and addressVersionNumber != 4: + raise APIError(2,'The address version number currently must be 3 or 4 (or 0 which means auto-select). ' + addressVersionNumber + ' isn\'t supported.') if streamNumber == 0: # 0 means "just use the most available stream" streamNumber = 1 if streamNumber != 1: From f6b9c234f70695ccbdb0e5d1c03e284555ba7c38 Mon Sep 17 00:00:00 2001 From: Jonathan Warren Date: Sat, 5 Oct 2013 12:23:34 -0400 Subject: [PATCH 20/48] pull translations properly when running from Windows EXE --- src/bitmessageqt/__init__.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/bitmessageqt/__init__.py b/src/bitmessageqt/__init__.py index 2bd2bb6b..768e681b 100644 --- a/src/bitmessageqt/__init__.py +++ b/src/bitmessageqt/__init__.py @@ -3234,7 +3234,10 @@ def run(): locale_lang = locale_countrycode[0:2] user_countrycode = str(shared.config.get('bitmessagesettings', 'userlocale')) user_lang = user_countrycode[0:2] - translation_path = "translations/bitmessage_" + try: + translation_path = os.path.join(sys._MEIPASS, "translations/bitmessage_") + except Exception, e: + translation_path = "translations/bitmessage_" if shared.config.get('bitmessagesettings', 'userlocale') == 'system': # try to detect the users locale otherwise fallback to English From a20711c2abab2694ee43bddea878538ceccaf988 Mon Sep 17 00:00:00 2001 From: Jonathan Warren Date: Sat, 5 Oct 2013 13:39:53 -0400 Subject: [PATCH 21/48] Fix whitelist functionality --- src/class_receiveDataThread.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/class_receiveDataThread.py b/src/class_receiveDataThread.py index 59597711..b1a62261 100644 --- a/src/class_receiveDataThread.py +++ b/src/class_receiveDataThread.py @@ -1120,7 +1120,7 @@ class receiveDataThread(threading.Thread): else: # We're using a whitelist queryreturn = sqlQuery( '''SELECT label FROM whitelist where address=? and enabled='1' ''', - toAddress) + fromAddress) if queryreturn == []: print 'Message ignored because address not in whitelist.' blockMessage = True From 44bd876a40d668e386087dca798cbf77b25b5f47 Mon Sep 17 00:00:00 2001 From: ikarakatsanis Date: Sun, 6 Oct 2013 12:32:14 +0400 Subject: [PATCH 22/48] feature1_v4 --- src/bitmessageqt/__init__.py | 38 ++++++++++++++++++------------------ 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/src/bitmessageqt/__init__.py b/src/bitmessageqt/__init__.py index 27338fd1..3280f2d8 100644 --- a/src/bitmessageqt/__init__.py +++ b/src/bitmessageqt/__init__.py @@ -2027,34 +2027,34 @@ class MyForm(QtGui.QMainWindow): if float(self.settingsDialogInstance.ui.lineEditMaxAcceptableSmallMessageDifficulty.text()) >= 1 or float(self.settingsDialogInstance.ui.lineEditMaxAcceptableSmallMessageDifficulty.text()) == 0: shared.config.set('bitmessagesettings', 'maxacceptablepayloadlengthextrabytes', str(int(float( self.settingsDialogInstance.ui.lineEditMaxAcceptableSmallMessageDifficulty.text()) * shared.networkDefaultPayloadLengthExtraBytes))) - #my implementation starts here,it was a line here.AQWA + #my implementation starts here,it was a line here.AQWA if ((self.settingsDialogInstance.ui.lineEditHours.text()=='') and (self.settingsDialogInstance.ui.lineEditDays.text()=='') and (self.settingsDialogInstance.ui.lineEditMonths.text()=='')): if (((shared.config.get('bitmessagesettings', 'hours')) != str(self.settingsDialogInstance.ui.lineEditHours.text())) or #if we update the time period from one time period(f.e 1/0/0) to default -/-/,inform the user that restart is needed in order his changes to take effect.AQWA - ((shared.config.get('bitmessagesettings', 'days')) != str(self.settingsDialogInstance.ui.lineEditDays.text())) or - ((shared.config.get('bitmessagesettings', 'months')) != str(self.settingsDialogInstance.ui.lineEditMonths.text()))) : - QMessageBox.about(self, _translate("MainWindow", "Restart"), _translate( - "MainWindow", "You must restart Bitmessage for the time period change to take effect.")) - + ((shared.config.get('bitmessagesettings', 'days')) != str(self.settingsDialogInstance.ui.lineEditDays.text())) or + ((shared.config.get('bitmessagesettings', 'months')) != str(self.settingsDialogInstance.ui.lineEditMonths.text()))): + QMessageBox.about(self, _translate("MainWindow", "Restart"), _translate( + "MainWindow", "You must restart Bitmessage for the time period change to take effect.")) shared.config.set('bitmessagesettings', 'hours', '')#give default values to fields..this default situation is special and thats why its needs special treatment ;).AQWA shared.config.set('bitmessagesettings', 'days', '')#these commands update each field in the keys.dat file.AQWA shared.config.set('bitmessagesettings', 'months', '') shared.config.set('bitmessagesettings', 'timeperiod', '-1') else:#So,if all time period fields(hours,months,days) have valid values(valid values---> 0/0/0, 0/3/2, -/-/-, no valid--> -/0/5, 5/-/-), you can calculate the time period + if (int(str(self.settingsDialogInstance.ui.lineEditHours.text())) >=0 and int(str(self.settingsDialogInstance.ui.lineEditDays.text())) >=0 and + int(str(self.settingsDialogInstance.ui.lineEditMonths.text())) >=0): + if (((shared.config.get('bitmessagesettings', 'hours')) != str(self.settingsDialogInstance.ui.lineEditHours.text())) or #if we update the time period from one time period(f.e 1/0/0) to default -/-/,inform the user that restart is needed in order his changes to take effect.AQWA + ((shared.config.get('bitmessagesettings', 'days')) != str(self.settingsDialogInstance.ui.lineEditDays.text())) or + ((shared.config.get('bitmessagesettings', 'months')) != str(self.settingsDialogInstance.ui.lineEditMonths.text()))): + QMessageBox.about(self, _translate("MainWindow", "Restart"), _translate( + "MainWindow", "You must restart Bitmessage for the time period change to take effect.")) + shared.config.set('bitmessagesettings', 'hours', str(#this three commands update the fields of this new setting in keys.dat file.AQWA + self.settingsDialogInstance.ui.lineEditHours.text())) + shared.config.set('bitmessagesettings', 'days', str( + self.settingsDialogInstance.ui.lineEditDays.text())) + shared.config.set('bitmessagesettings', 'months', str( + self.settingsDialogInstance.ui.lineEditMonths.text())) shared.config.set('bitmessagesettings', 'timeperiod', str(int(str(self.settingsDialogInstance.ui.lineEditHours.text())) * 60 * 60 + int(str(self.settingsDialogInstance.ui.lineEditDays.text())) * 24 * 60 * 60 + int(str(self.settingsDialogInstance.ui.lineEditMonths.text())) * (60 * 60 * 24 *365)/12)) - - if (((shared.config.get('bitmessagesettings', 'hours')) != str(self.settingsDialogInstance.ui.lineEditHours.text())) or#inform user tha he needs to restart bitmessage in order his changes to take effect.AQWA - ((shared.config.get('bitmessagesettings', 'days')) != str(self.settingsDialogInstance.ui.lineEditDays.text())) or - ((shared.config.get('bitmessagesettings', 'months')) != str(self.settingsDialogInstance.ui.lineEditMonths.text()))) : - QMessageBox.about(self, _translate("MainWindow", "Restart"), _translate( - "MainWindow", "You must restart Bitmessage for the time period change to take effect.")) - shared.config.set('bitmessagesettings', 'hours', str(#this three commands update the fields of this new setting in keys.dat file.AQWA - self.settingsDialogInstance.ui.lineEditHours.text())) - shared.config.set('bitmessagesettings', 'days', str( - self.settingsDialogInstance.ui.lineEditDays.text())) - shared.config.set('bitmessagesettings', 'months', str( - self.settingsDialogInstance.ui.lineEditMonths.text())) - #my implementation stops here, there is a line.AQWA + #my implementation stops here, it was a line.AQWA # if str(self.settingsDialogInstance.ui.comboBoxMaxCores.currentText()) == 'All': # shared.config.set('bitmessagesettings', 'maxcores', '99999') From 6787e4b932ff95fdc932445c6ea4f4a9dd72e531 Mon Sep 17 00:00:00 2001 From: John Kozan Date: Sun, 6 Oct 2013 22:04:09 -0600 Subject: [PATCH 23/48] == should be = --- src/bitmessagemain.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/bitmessagemain.py b/src/bitmessagemain.py index 90e02cc1..ef2a2ddd 100755 --- a/src/bitmessagemain.py +++ b/src/bitmessagemain.py @@ -643,7 +643,7 @@ class MySimpleXMLRPCRequestHandler(SimpleXMLRPCRequestHandler): raise APIError(0, 'I need parameters!') if len(params) == 1: address, = params - label == '' + label = '' if len(params) == 2: address, label = params label = self._decode(label, "base64") From 9066dad5e3e4e97549a0f727a437b3b8fe941acf Mon Sep 17 00:00:00 2001 From: ikarakatsanis Date: Thu, 10 Oct 2013 09:10:46 +0400 Subject: [PATCH 24/48] AQWA feature: UI setting for Bitmessage to stop trying to send messages after X hours/days/months --- src/bitmessageqt/__init__.py | 65 +++++++++++++++++++++--------------- src/bitmessageqt/settings.py | 21 ++++++------ src/bitmessageqt/settings.ui | 4 +-- src/class_singleCleaner.py | 23 +++++++------ src/class_sqlThread.py | 21 ++++++------ src/helper_startup.py | 18 +++++----- 6 files changed, 82 insertions(+), 70 deletions(-) diff --git a/src/bitmessageqt/__init__.py b/src/bitmessageqt/__init__.py index 3280f2d8..f8563a9c 100644 --- a/src/bitmessageqt/__init__.py +++ b/src/bitmessageqt/__init__.py @@ -2027,34 +2027,47 @@ class MyForm(QtGui.QMainWindow): if float(self.settingsDialogInstance.ui.lineEditMaxAcceptableSmallMessageDifficulty.text()) >= 1 or float(self.settingsDialogInstance.ui.lineEditMaxAcceptableSmallMessageDifficulty.text()) == 0: shared.config.set('bitmessagesettings', 'maxacceptablepayloadlengthextrabytes', str(int(float( self.settingsDialogInstance.ui.lineEditMaxAcceptableSmallMessageDifficulty.text()) * shared.networkDefaultPayloadLengthExtraBytes))) - #my implementation starts here,it was a line here.AQWA - if ((self.settingsDialogInstance.ui.lineEditHours.text()=='') and (self.settingsDialogInstance.ui.lineEditDays.text()=='') and (self.settingsDialogInstance.ui.lineEditMonths.text()=='')): - if (((shared.config.get('bitmessagesettings', 'hours')) != str(self.settingsDialogInstance.ui.lineEditHours.text())) or #if we update the time period from one time period(f.e 1/0/0) to default -/-/,inform the user that restart is needed in order his changes to take effect.AQWA + #start:UI setting to stop trying to send messages after X hours/days/months + if ((self.settingsDialogInstance.ui.lineEditHours.text()=='') and (self.settingsDialogInstance.ui.lineEditDays.text()=='') and (self.settingsDialogInstance.ui.lineEditMonths.text()=='')):#We need to handle this special case. Bitmessage has its default behavior. The input is blank/blank/blank + if (((shared.config.get('bitmessagesettings', 'hours')) != str(self.settingsDialogInstance.ui.lineEditHours.text())) or #the user updated the input, restart is needed ((shared.config.get('bitmessagesettings', 'days')) != str(self.settingsDialogInstance.ui.lineEditDays.text())) or - ((shared.config.get('bitmessagesettings', 'months')) != str(self.settingsDialogInstance.ui.lineEditMonths.text()))): - QMessageBox.about(self, _translate("MainWindow", "Restart"), _translate( - "MainWindow", "You must restart Bitmessage for the time period change to take effect.")) - shared.config.set('bitmessagesettings', 'hours', '')#give default values to fields..this default situation is special and thats why its needs special treatment ;).AQWA - shared.config.set('bitmessagesettings', 'days', '')#these commands update each field in the keys.dat file.AQWA - shared.config.set('bitmessagesettings', 'months', '') - shared.config.set('bitmessagesettings', 'timeperiod', '-1') - else:#So,if all time period fields(hours,months,days) have valid values(valid values---> 0/0/0, 0/3/2, -/-/-, no valid--> -/0/5, 5/-/-), you can calculate the time period - if (int(str(self.settingsDialogInstance.ui.lineEditHours.text())) >=0 and int(str(self.settingsDialogInstance.ui.lineEditDays.text())) >=0 and - int(str(self.settingsDialogInstance.ui.lineEditMonths.text())) >=0): - if (((shared.config.get('bitmessagesettings', 'hours')) != str(self.settingsDialogInstance.ui.lineEditHours.text())) or #if we update the time period from one time period(f.e 1/0/0) to default -/-/,inform the user that restart is needed in order his changes to take effect.AQWA - ((shared.config.get('bitmessagesettings', 'days')) != str(self.settingsDialogInstance.ui.lineEditDays.text())) or ((shared.config.get('bitmessagesettings', 'months')) != str(self.settingsDialogInstance.ui.lineEditMonths.text()))): QMessageBox.about(self, _translate("MainWindow", "Restart"), _translate( - "MainWindow", "You must restart Bitmessage for the time period change to take effect.")) - shared.config.set('bitmessagesettings', 'hours', str(#this three commands update the fields of this new setting in keys.dat file.AQWA - self.settingsDialogInstance.ui.lineEditHours.text())) - shared.config.set('bitmessagesettings', 'days', str( - self.settingsDialogInstance.ui.lineEditDays.text())) - shared.config.set('bitmessagesettings', 'months', str( - self.settingsDialogInstance.ui.lineEditMonths.text())) + "MainWindow", "You must restart Bitmessage for the time period change to take effect.")) + shared.config.set('bitmessagesettings', 'hours', '') + shared.config.set('bitmessagesettings', 'days', '') + shared.config.set('bitmessagesettings', 'months', '') + shared.config.set('bitmessagesettings', 'timeperiod', '-1')#when bitmessage has its default resending behavior, we set timeperiod to -1. + else:#So,if all time period's variables (hours,days,months) have valid values, we calculate the time period + if (int(self.settingsDialogInstance.ui.lineEditHours.text()) >=0 and int(self.settingsDialogInstance.ui.lineEditDays.text()) >=0 and + int(self.settingsDialogInstance.ui.lineEditMonths.text()) >=0): shared.config.set('bitmessagesettings', 'timeperiod', str(int(str(self.settingsDialogInstance.ui.lineEditHours.text())) * 60 * 60 + int(str(self.settingsDialogInstance.ui.lineEditDays.text())) * 24 * 60 * 60 + int(str(self.settingsDialogInstance.ui.lineEditMonths.text())) * (60 * 60 * 24 *365)/12)) - #my implementation stops here, it was a line.AQWA + if int(shared.config.get('bitmessagesettings', 'timeperiod')) < 432000:#if the time period is less than 5 hours, we give zero values to all fields. No message will be sent again. + if ((shared.config.get('bitmessagesettings', 'hours')) != str(int(self.settingsDialogInstance.ui.lineEditHours.text())) or #if the user has given an input bigger than 5 days and he tries now to give an input less than 5 days, restart is needed + shared.config.get('bitmessagesettings', 'days') != str(int(self.settingsDialogInstance.ui.lineEditDays.text())) or + shared.config.get('bitmessagesettings', 'months') != str(int(self.settingsDialogInstance.ui.lineEditMonths.text()))): + if((shared.config.get('bitmessagesettings', 'hours')) != '0' or (shared.config.get('bitmessagesettings', 'days')) != '0' or#if the user has already given an input less than 5 days and he tries now to give again an input less than 5 days, there is no need for restart. Input values will remain zero + (shared.config.get('bitmessagesettings', 'months')) != '0'): + QMessageBox.about(self, _translate("MainWindow", "Restart"), _translate( + "MainWindow", "You must restart Bitmessage for the time period change to take effect.")) + shared.config.set('bitmessagesettings', 'hours', '0') + shared.config.set('bitmessagesettings', 'days', '0') + shared.config.set('bitmessagesettings', 'months', '0') + shared.config.set('bitmessagesettings', 'timeperiod', '0') + else: + if ((shared.config.get('bitmessagesettings', 'hours')) != str(int(self.settingsDialogInstance.ui.lineEditHours.text())) or + shared.config.get('bitmessagesettings', 'days') != str(int(self.settingsDialogInstance.ui.lineEditDays.text())) or + shared.config.get('bitmessagesettings', 'months') != str(int(self.settingsDialogInstance.ui.lineEditMonths.text()))): + QMessageBox.about(self, _translate("MainWindow", "Restart"), _translate(#the user updated the input, restart is needed + "MainWindow", "You must restart Bitmessage for the time period change to take effect.")) + shared.config.set('bitmessagesettings', 'hours', str(int( + self.settingsDialogInstance.ui.lineEditHours.text()))) + shared.config.set('bitmessagesettings', 'days', str(int( + self.settingsDialogInstance.ui.lineEditDays.text()))) + shared.config.set('bitmessagesettings', 'months', str(int( + self.settingsDialogInstance.ui.lineEditMonths.text()))) + #end # if str(self.settingsDialogInstance.ui.comboBoxMaxCores.currentText()) == 'All': # shared.config.set('bitmessagesettings', 'maxcores', '99999') @@ -2992,13 +3005,13 @@ class settingsDialog(QtGui.QDialog): QtCore.QObject.connect(self.ui.pushButtonNamecoinTest, QtCore.SIGNAL( "clicked()"), self.click_pushButtonNamecoinTest) - #Adjusting time period for resending messages tab.AQWA - self.ui.lineEditHours.setText(str(#Giving values to edit boxes in the UI.AQWA + #Adjusting time period to stop sending messages tab + self.ui.lineEditHours.setText(str( shared.config.get('bitmessagesettings', 'hours'))) self.ui.lineEditDays.setText(str( shared.config.get('bitmessagesettings', 'days'))) self.ui.lineEditMonths.setText(str( - shared.config.get('bitmessagesettings', 'months')))#AQWA. + shared.config.get('bitmessagesettings', 'months'))) #'System' tab removed for now. diff --git a/src/bitmessageqt/settings.py b/src/bitmessageqt/settings.py index f9c3518a..4667021b 100644 --- a/src/bitmessageqt/settings.py +++ b/src/bitmessageqt/settings.py @@ -318,10 +318,9 @@ class Ui_settingsDialog(object): self.gridLayout_8.addLayout(self.horizontalLayout, 1, 0, 1, 3) self.tabWidgetSettings.addTab(self.tabNamecoin, _fromUtf8("")) self.gridLayout.addWidget(self.tabWidgetSettings, 0, 0, 1, 1) -#this line existed before -#my new implementation starts here - self.tabResendingMessagesAdjustment=QtGui.QWidget()#all these lines are for the UI implementation, I dont think that you need special explanation about them.AQWA - self.tabResendingMessagesAdjustment.setObjectName(_fromUtf8("tabResendingMessagesAdjustment"))#Please note that approximately 58 line codes added to setting.ui also responsible for the UI.AQWA + #start:UI setting to stop trying to send messages after X hours/days/months + self.tabResendingMessagesAdjustment=QtGui.QWidget() + self.tabResendingMessagesAdjustment.setObjectName(_fromUtf8("tabResendingMessagesAdjustment")) self.gridLayout_9 = QtGui.QGridLayout(self.tabResendingMessagesAdjustment) self.gridLayout_9.setObjectName(_fromUtf8("gridLayout_9")) self.label_19 = QtGui.QLabel(self.tabResendingMessagesAdjustment) @@ -335,11 +334,11 @@ class Ui_settingsDialog(object): self.label_20 = QtGui.QLabel(self.tabResendingMessagesAdjustment) self.label_20.setLayoutDirection(QtCore.Qt.LeftToRight) self.label_20.setAlignment(QtCore.Qt.AlignRight|QtCore.Qt.AlignTrailing|QtCore.Qt.AlignVCenter) - self.label_20.setObjectName(_fromUtf8("label_20"))#This are Qt code..I just add a label to my tab.AQWA + self.label_20.setObjectName(_fromUtf8("label_20")) self.gridLayout_9.addWidget(self.label_20, 2, 0, 1, 1) self.lineEditHours = QtGui.QLineEdit(self.tabResendingMessagesAdjustment) self.lineEditHours.setMaximumSize(QtCore.QSize(33, 16777)) - self.lineEditHours.setObjectName(_fromUtf8("lineEditHours"))##This are Qt code..I just add a edit box to my tab.AQWA + self.lineEditHours.setObjectName(_fromUtf8("lineEditHours")) self.gridLayout_9.addWidget(self.lineEditHours, 2, 1, 1, 1) self.label_22 = QtGui.QLabel(self.tabResendingMessagesAdjustment) self.label_22.setAlignment(QtCore.Qt.AlignRight|QtCore.Qt.AlignTrailing|QtCore.Qt.AlignVCenter) @@ -360,7 +359,7 @@ class Ui_settingsDialog(object): spacerItem15 = QtGui.QSpacerItem(20, 147, QtGui.QSizePolicy.Minimum, QtGui.QSizePolicy.Expanding) self.gridLayout_9.addItem(spacerItem15, 3, 1, 1, 1) self.tabWidgetSettings.addTab(self.tabResendingMessagesAdjustment, _fromUtf8("")) - #my new implementation stops here, it wasn't line here + #end self.retranslateUi(settingsDialog) self.tabWidgetSettings.setCurrentIndex(0) QtCore.QObject.connect(self.buttonBox, QtCore.SIGNAL(_fromUtf8("accepted()")), settingsDialog.accept) @@ -424,13 +423,13 @@ class Ui_settingsDialog(object): self.tabWidgetSettings.setTabText(self.tabWidgetSettings.indexOf(self.tab), _translate("settingsDialog", "Demanded difficulty", None)) self.label_15.setText(_translate("settingsDialog", "Here you may set the maximum amount of work you are willing to do to send a message to another person. Setting these values to 0 means that any value is acceptable.", None)) self.label_13.setText(_translate("settingsDialog", "Maximum acceptable total difficulty:", None)) - #my new implementation starts here,it wasn't line here.Too simple to explain :D..AQWA - self.tabWidgetSettings.setTabText(self.tabWidgetSettings.indexOf(self.tabResendingMessagesAdjustment), _translate("settingsDialog", "Adjusting time period for resending messages", None)) - self.label_19.setText(_translate("settingsDialog", "

If you send a message to someone and he is offline for more than two days, Bitmessage will send the message again after an additional two days. This will be continued with exponential backoff forever. Messages will continue to be sent after 4, 8,16 days and so on until the receiver get them.

Here you can adjust Bitmessage to stop trying to send messages after X hours/days/months.

", None)) + #start:UI setting to stop trying to send messages after X hours/days/months + self.tabWidgetSettings.setTabText(self.tabWidgetSettings.indexOf(self.tabResendingMessagesAdjustment), _translate("settingsDialog", "Adjusting time period to stop sending messages", None)) + self.label_19.setText(_translate("settingsDialog", "

If you send a message to someone and he is offline for more than two and a half days, Bitmessage will send the message again after an additional two and a half days. This will be continued with exponential backoff forever. Μessages will continue to be sent after 5, 10, 20 days etc. until the receiver gets them.

Leaving all the input fields blank means the default behavior which will continue the resending with exponential backoff.

Setting these values to 0/0/0 or less than 5 days mean that the client will not resend any messages.

Here you can adjust Bitmessage to stop trying to send messages after X hours/days/months.

", None)) self.label_20.setText(_translate("settingsDialog", "Time in hours/days/months:", None)) self.label_22.setText(_translate("settingsDialog", "/", None)) self.label_23.setText(_translate("settingsDialog", "/", None)) - #my new implementation stops here, it wasn't line here + #end self.label_14.setText(_translate("settingsDialog", "Maximum acceptable small message difficulty:", None)) self.tabWidgetSettings.setTabText(self.tabWidgetSettings.indexOf(self.tab_2), _translate("settingsDialog", "Max acceptable difficulty", None)) self.label_16.setText(_translate("settingsDialog", "

Bitmessage can utilize a different Bitcoin-based program called Namecoin to make addresses human-friendly. For example, instead of having to tell your friend your long Bitmessage address, you can simply tell him to send a message to test.

(Getting your own Bitmessage address into Namecoin is still rather difficult).

Bitmessage can use either namecoind directly or a running nmcontrol instance.

", None)) diff --git a/src/bitmessageqt/settings.ui b/src/bitmessageqt/settings.ui index 94610c03..9ecdfa11 100644 --- a/src/bitmessageqt/settings.ui +++ b/src/bitmessageqt/settings.ui @@ -758,13 +758,13 @@
- Adjusting time period for resending messages + Adjusting time period to stop sending messages - If you send a message to someone and he is offline for more than two days, Bitmessage will send the message again after an additional two days. This will be continued with exponential backoff forever. Μessages will continue to be sent after 4, 8,16 days etc. until the receiver get them. Here you can adjust Bitmessage to stop trying to send messages after X hours/days/months. This time period needs to be longer than 5 days./p></body></html> + If you send a message to someone and he is offline for more than two and a half days, Bitmessage will send the message again after an additional two and a half days. This will be continued with exponential backoff forever. Μessages will continue to be sent after 5, 10, 20 days etc. until the receiver gets them. Leaving all the input fields blank means the default behavior which will continue the resending with exponential backoff. Setting these values to 0/0/0 or less than 5 days mean that the client will not resend any messages. Here you can adjust Bitmessage to stop trying to send messages after X hours/days/months./p></body></html> true diff --git a/src/class_singleCleaner.py b/src/class_singleCleaner.py index dfeaf4a1..2d80c6ce 100644 --- a/src/class_singleCleaner.py +++ b/src/class_singleCleaner.py @@ -85,21 +85,21 @@ class singleCleaner(threading.Thread): break toaddress, toripe, fromaddress, subject, message, ackdata, lastactiontime, status, pubkeyretrynumber, msgretrynumber = row - if status == 'awaitingpubkey': - if int(shared.config.get('bitmessagesettings', 'timeperiod'))> -1:#My implemenentation starts here.This if statement would become very big with my new code,so I created two function(see at the end of this file) to reduce code.I did.The default value of timeperiod is -1.This means that bitmessage resends messages every 5 days(they say 4 but actually is 5)for ever. If user changes the time period, timeperiod variable will have a specific value so the next if will be executed.AQWA - if (int(time.time()) - lastactiontime) > (shared.maximumAgeOfAnObjectThatIAmWillingToAccept * (2 ** (pubkeyretrynumber))) and ((int(time.time()) - lastactiontime) < int(shared.config.get('bitmessagesettings', 'timeperiod'))):#This line does the magic.This if checks if the time that the public key was sent is longer than 5 days. Then it sends the it again. But with this extra AND it will not send it if this time is shorter than timeperiod.AQWA - resendPubkey(pubkeyretrynumber,toripe) - else:#first it wasn't an else statement here, I put it for this setting. I just copy-paste the code again. If someone has any suggestion how we can do this without this if-else just say it.AQWA + if status == 'awaitingpubkey':#start:UI setting to stop trying to send messages after X hours/days/months + if int(shared.config.get('bitmessagesettings', 'timeperiod'))> -1:#The default value of timeperiod is -1. + if (int(time.time()) - lastactiontime) > (shared.maximumAgeOfAnObjectThatIAmWillingToAccept * (2 ** (pubkeyretrynumber))) and ((int(time.time()) - lastactiontime) < int(shared.config.get('bitmessagesettings', 'timeperiod'))): + resendPubkey(pubkeyretrynumber,toripe)#This will be executed if the user has adjusted the time period with some value + else: if int(time.time()) - lastactiontime > (shared.maximumAgeOfAnObjectThatIAmWillingToAccept * (2 ** (pubkeyretrynumber))): - resendPubkey(pubkeyretrynumber,toripe) + resendPubkey(pubkeyretrynumber,toripe)#This will be executed if the time period has its default value -1. Input (blank/blank/blank) else: # status == msgsent - if int(shared.config.get('bitmessagesettings', 'timeperiod'))> -1:#same thing here but for the message.Actually this is the most important thing in the whole feature!.AQWA - if (int(time.time()) - lastactiontime) > (shared.maximumAgeOfAnObjectThatIAmWillingToAccept * (2 ** (msgretrynumber))) and ((int(time.time()) - lastactiontime) < int(shared.config.get('bitmessagesettings', 'timeperiod'))):#same thing here.My implementation in this file stops here.AQWA + if int(shared.config.get('bitmessagesettings', 'timeperiod'))> -1: + if (int(time.time()) - lastactiontime) > (shared.maximumAgeOfAnObjectThatIAmWillingToAccept * (2 ** (msgretrynumber))) and ((int(time.time()) - lastactiontime) < int(shared.config.get('bitmessagesettings', 'timeperiod'))): resendMsg(msgretrynumber,ackdata) else: if int(time.time()) - lastactiontime > (shared.maximumAgeOfAnObjectThatIAmWillingToAccept * (2 ** (msgretrynumber))): resendMsg(msgretrynumber,ackdata) - + #end # Let's also clear and reload shared.inventorySets to keep it from # taking up an unnecessary amount of memory. @@ -130,8 +130,8 @@ class singleCleaner(threading.Thread): shared.knownNodesLock.release() shared.needToWriteKnownNodesToDisk = False time.sleep(300) - -def resendPubkey(pubkeyretrynumber,toripe):#I just structured the code with these two functions. The code inside existed.It is not mine.AQWA + #start:UI setting to stop trying to send messages after X hours/days/months +def resendPubkey(pubkeyretrynumber,toripe):#We just structured the code with these two functions to reduce code redundancy print 'It has been a long time and we haven\'t heard a response to our getpubkey request. Sending again.' try: del shared.neededPubkeys[ @@ -160,6 +160,7 @@ def resendMsg(msgretrynumber,ackdata): shared.workerQueue.put(('sendmessage', '')) shared.UISignalQueue.put(( 'updateStatusBar', 'Doing work necessary to again attempt to deliver a message...')) + #end \ No newline at end of file diff --git a/src/class_sqlThread.py b/src/class_sqlThread.py index 228bd3ad..e6c9b38a 100644 --- a/src/class_sqlThread.py +++ b/src/class_sqlThread.py @@ -268,24 +268,23 @@ class sqlThread(threading.Thread): item = '''update settings set value=? WHERE key='version';''' parameters = (5,) self.cur.execute(item, parameters) - - # Are you hoping to add a new option to the keys.dat file of existing - # Bitmessage users? Add it right above this line! - - #my new implementation starts here, the most of these comment will be deleted, they are just for documentation - if shared.config.getint('bitmessagesettings', 'settingsversion') == 7:#this is the version that all we have,if you see your keys.dat file this is your version.AQWA - shared.config.set(#in order to not have to change your keys.dat file I update it with the new lines. I add to your keys.dat three new default fields.AQWA - 'bitmessagesettings', 'hours', '')# hours, days, months have no value.This means that bitmessage works as before. It re-sends mails every 4,8,16 days..forever.AQWA + + #Adjusting time period to stop sending messages + if shared.config.getint('bitmessagesettings', 'settingsversion') == 7: + shared.config.set( + 'bitmessagesettings', 'hours', '') shared.config.set( 'bitmessagesettings', 'days', '') shared.config.set( 'bitmessagesettings', 'months', '') shared.config.set( - 'bitmessagesettings', 'timeperiod', '-1')#time period has default value -1. This is used for checking in class_singleCleaner. If you leave default the time period or after you change it(f.i 1/0/0), again you set it with its default value(-/-/-) this variable will be -1.AQWA - shared.config.set('bitmessagesettings', 'settingsversion', '8') #We update the version.If I leave it 7 every time that Bitmessage starts your setting will be lost.The default values(-/-/-) will be loaded all the time ;).That was juicy.AQWA + 'bitmessagesettings', 'timeperiod', '-1') + shared.config.set('bitmessagesettings', 'settingsversion', '8') with open(shared.appdata + 'keys.dat', 'wb') as configfile: shared.config.write(configfile) - #my new implementation in this file stops here + + # Are you hoping to add a new option to the keys.dat file of existing + # Bitmessage users? Add it right above this line! try: testpayload = '\x00\x00' diff --git a/src/helper_startup.py b/src/helper_startup.py index 2469f82e..de92e456 100644 --- a/src/helper_startup.py +++ b/src/helper_startup.py @@ -85,22 +85,22 @@ def loadConfig(): shared.config.set('bitmessagesettings', 'dontconnect', 'true') shared.config.set('bitmessagesettings', 'userlocale', 'system') - # Are you hoping to add a new option to the keys.dat file? You're in - # the right place for adding it to users who install the software for - # the first time. But you must also add it to the keys.dat file of - # existing users. To do that, search the class_sqlThread.py file for the - # text: "right above this line!" - - #my implementation starts here. AQWA + #start:UI setting to stop trying to send messages after X hours/days/months shared.config.set( - 'bitmessagesettings', 'hours', '')#here I am adding the new default settings. The first time that the program is going to run these values will be loaded(UI and keys.dat also :) ).AQWA + 'bitmessagesettings', 'hours', '') shared.config.set( 'bitmessagesettings', 'days', '') shared.config.set( 'bitmessagesettings', 'months', '') shared.config.set( 'bitmessagesettings', 'timeperiod', '-1') - #my implementation in this file stops here.AQWA + #end + + # Are you hoping to add a new option to the keys.dat file? You're in + # the right place for adding it to users who install the software for + # the first time. But you must also add it to the keys.dat file of + # existing users. To do that, search the class_sqlThread.py file for the + # text: "right above this line!" ensureNamecoinOptions() From ed0a57d99886a8c89bbb7120ec3036b27a6b7019 Mon Sep 17 00:00:00 2001 From: Joshua Noble Date: Sat, 12 Oct 2013 00:33:19 -0400 Subject: [PATCH 25/48] Fixed typo in getInboxMessagesByAddress --- src/bitmessagemain.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/bitmessagemain.py b/src/bitmessagemain.py index ef2a2ddd..26a2a92d 100755 --- a/src/bitmessagemain.py +++ b/src/bitmessagemain.py @@ -461,7 +461,7 @@ class MySimpleXMLRPCRequestHandler(SimpleXMLRPCRequestHandler): if len(params) == 0: raise APIError(0, 'I need parameters!') toAddress = params[0] - queryReturn = sqlQuery('''SELECT msgid, toaddress, fromaddress, subject, received, message, encodingtype FROM inbox WHERE folder='inbox' AND toAddress=?''', toAddress) + queryreturn = sqlQuery('''SELECT msgid, toaddress, fromaddress, subject, received, message, encodingtype FROM inbox WHERE folder='inbox' AND toAddress=?''', toAddress) data = '{"inboxMessages":[' for row in queryreturn: msgid, toAddress, fromAddress, subject, received, message, encodingtype = row From 55568fa242e50a7dbc73400d9e60fe4e34875bf7 Mon Sep 17 00:00:00 2001 From: Jonathan Warren Date: Sun, 13 Oct 2013 13:45:30 -0400 Subject: [PATCH 26/48] Don't store messages in UI table (and thus in memory), pull from SQL inventory as needed --- src/bitmessageqt/__init__.py | 71 +++++++++++++++++++++++------------- 1 file changed, 45 insertions(+), 26 deletions(-) diff --git a/src/bitmessageqt/__init__.py b/src/bitmessageqt/__init__.py index 768e681b..b0193e33 100644 --- a/src/bitmessageqt/__init__.py +++ b/src/bitmessageqt/__init__.py @@ -560,7 +560,7 @@ class MyForm(QtGui.QMainWindow): where = "toaddress || fromaddress || subject || message" sqlStatement = ''' - SELECT toaddress, fromaddress, subject, message, status, ackdata, lastactiontime + SELECT toaddress, fromaddress, subject, status, ackdata, lastactiontime FROM sent WHERE folder="sent" AND %s LIKE ? ORDER BY lastactiontime ''' % (where,) @@ -570,9 +570,9 @@ class MyForm(QtGui.QMainWindow): queryreturn = sqlQuery(sqlStatement, what) for row in queryreturn: - toAddress, fromAddress, subject, message, status, ackdata, lastactiontime = row + toAddress, fromAddress, subject, status, ackdata, lastactiontime = row subject = shared.fixPotentiallyInvalidUTF8Data(subject) - message = shared.fixPotentiallyInvalidUTF8Data(message) + #message = shared.fixPotentiallyInvalidUTF8Data(message) try: fromLabel = shared.config.get(fromAddress, 'label') except: @@ -612,7 +612,7 @@ class MyForm(QtGui.QMainWindow): self.ui.tableWidgetSent.setItem(0, 1, newItem) newItem = QtGui.QTableWidgetItem(unicode(subject, 'utf-8')) newItem.setToolTip(unicode(subject, 'utf-8')) - newItem.setData(Qt.UserRole, unicode(message, 'utf-8)')) + #newItem.setData(Qt.UserRole, unicode(message, 'utf-8)')) # No longer hold the message in the table; we'll use a SQL query to display it as needed. newItem.setFlags( QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled) self.ui.tableWidgetSent.setItem(0, 2, newItem) @@ -680,7 +680,7 @@ class MyForm(QtGui.QMainWindow): where = "toaddress || fromaddress || subject || message" sqlStatement = ''' - SELECT msgid, toaddress, fromaddress, subject, received, message, read + SELECT msgid, toaddress, fromaddress, subject, received, read FROM inbox WHERE folder="inbox" AND %s LIKE ? ORDER BY received ''' % (where,) @@ -692,9 +692,9 @@ class MyForm(QtGui.QMainWindow): font.setBold(True) queryreturn = sqlQuery(sqlStatement, what) for row in queryreturn: - msgid, toAddress, fromAddress, subject, received, message, read = row + msgid, toAddress, fromAddress, subject, received, read = row subject = shared.fixPotentiallyInvalidUTF8Data(subject) - message = shared.fixPotentiallyInvalidUTF8Data(message) + #message = shared.fixPotentiallyInvalidUTF8Data(message) try: if toAddress == self.str_broadcast_subscribers: toLabel = self.str_broadcast_subscribers @@ -750,7 +750,7 @@ class MyForm(QtGui.QMainWindow): self.ui.tableWidgetInbox.setItem(0, 1, newItem) newItem = QtGui.QTableWidgetItem(unicode(subject, 'utf-8')) newItem.setToolTip(unicode(subject, 'utf-8')) - newItem.setData(Qt.UserRole, unicode(message, 'utf-8)')) + #newItem.setData(Qt.UserRole, unicode(message, 'utf-8)')) # No longer hold the message in the table (and thus in memory); we'll use a SQL query when we need to display it. newItem.setFlags( QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled) if not read: @@ -1765,7 +1765,7 @@ class MyForm(QtGui.QMainWindow): self.ui.tableWidgetSent.setItem(0, 1, newItem) newItem = QtGui.QTableWidgetItem(unicode(subject, 'utf-8)')) newItem.setToolTip(unicode(subject, 'utf-8)')) - newItem.setData(Qt.UserRole, unicode(message, 'utf-8)')) + #newItem.setData(Qt.UserRole, unicode(message, 'utf-8)')) # No longer hold the message in the table; we'll use a SQL query to display it as needed. self.ui.tableWidgetSent.setItem(0, 2, newItem) # newItem = QtGui.QTableWidgetItem('Doing work necessary to send # broadcast...'+ @@ -1778,13 +1778,12 @@ class MyForm(QtGui.QMainWindow): newItem.setData(Qt.UserRole, QByteArray(ackdata)) newItem.setData(33, int(time.time())) self.ui.tableWidgetSent.setItem(0, 3, newItem) - self.ui.textEditSentMessage.setPlainText( - self.ui.tableWidgetSent.item(0, 2).data(Qt.UserRole).toPyObject()) + self.ui.textEditSentMessage.setPlainText(message) self.ui.tableWidgetSent.setSortingEnabled(True) def displayNewInboxMessage(self, inventoryHash, toAddress, fromAddress, subject, message): subject = shared.fixPotentiallyInvalidUTF8Data(subject) - message = shared.fixPotentiallyInvalidUTF8Data(message) + #message = shared.fixPotentiallyInvalidUTF8Data(message) fromLabel = '' queryreturn = sqlQuery( '''select label from addressbook where address=?''', fromAddress) @@ -1838,7 +1837,7 @@ class MyForm(QtGui.QMainWindow): self.ui.tableWidgetInbox.setItem(0, 1, newItem) newItem = QtGui.QTableWidgetItem(unicode(subject, 'utf-8)')) newItem.setToolTip(unicode(subject, 'utf-8)')) - newItem.setData(Qt.UserRole, unicode(message, 'utf-8)')) + #newItem.setData(Qt.UserRole, unicode(message, 'utf-8)')) # No longer hold the message in the table; we'll use a SQL query to display it as needed. newItem.setFont(font) self.ui.tableWidgetInbox.setItem(0, 2, newItem) newItem = myTableWidgetItem(unicode(strftime(shared.config.get( @@ -2292,6 +2291,13 @@ class MyForm(QtGui.QMainWindow): currentInboxRow, 0).data(Qt.UserRole).toPyObject()) fromAddressAtCurrentInboxRow = str(self.ui.tableWidgetInbox.item( currentInboxRow, 1).data(Qt.UserRole).toPyObject()) + msgid = str(self.ui.tableWidgetInbox.item( + currentInboxRow, 3).data(Qt.UserRole).toPyObject()) + queryreturn = sqlQuery( + '''select message from inbox where msgid=?''', msgid) + if queryreturn != []: + for row in queryreturn: + messageAtCurrentInboxRow, = row if toAddressAtCurrentInboxRow == self.str_broadcast_subscribers: self.ui.labelFrom.setText('') elif not shared.config.has_section(toAddressAtCurrentInboxRow): @@ -2315,9 +2321,7 @@ class MyForm(QtGui.QMainWindow): self.ui.lineEditTo.setText(str(toAddressAtCurrentInboxRow)) self.ui.comboBoxSendFrom.setCurrentIndex(0) - # self.ui.comboBoxSendFrom.setEditText(str(self.ui.tableWidgetInbox.item(currentInboxRow,0).text)) - self.ui.textEditMessage.setText('\n\n------------------------------------------------------\n' + self.ui.tableWidgetInbox.item( - currentInboxRow, 2).data(Qt.UserRole).toPyObject()) + self.ui.textEditMessage.setText('\n\n------------------------------------------------------\n' + unicode(messageAtCurrentInboxRow, 'utf-8)')) if self.ui.tableWidgetInbox.item(currentInboxRow, 2).text()[0:3] in ['Re:', 'RE:']: self.ui.lineEditSubject.setText( self.ui.tableWidgetInbox.item(currentInboxRow, 2).text()) @@ -2716,21 +2720,27 @@ class MyForm(QtGui.QMainWindow): fromAddress = str(self.ui.tableWidgetInbox.item( currentRow, 1).data(Qt.UserRole).toPyObject()) + msgid = str(self.ui.tableWidgetInbox.item( + currentRow, 3).data(Qt.UserRole).toPyObject()) + queryreturn = sqlQuery( + '''select message from inbox where msgid=?''', msgid) + if queryreturn != []: + for row in queryreturn: + message, = row + message = unicode(message, 'utf-8)') # If we have received this message from either a broadcast address # or from someone in our address book, display as HTML if decodeAddress(fromAddress)[3] in shared.broadcastSendersForWhichImWatching or shared.isAddressInMyAddressBook(fromAddress): - if len(self.ui.tableWidgetInbox.item(currentRow, 2).data(Qt.UserRole).toPyObject()) < 30000: - self.ui.textEditInboxMessage.setText(self.ui.tableWidgetInbox.item( - currentRow, 2).data(Qt.UserRole).toPyObject()) # Only show the first 30K characters + if len(message) < 30000: + self.ui.textEditInboxMessage.setText(message) # Only show the first 30K characters else: - self.ui.textEditInboxMessage.setText(self.ui.tableWidgetInbox.item(currentRow, 2).data(Qt.UserRole).toPyObject()[ + self.ui.textEditInboxMessage.setText(message[ :30000] + '\n\nDisplay of the remainder of the message truncated because it is too long.') # Only show the first 30K characters else: - if len(self.ui.tableWidgetInbox.item(currentRow, 2).data(Qt.UserRole).toPyObject()) < 30000: - self.ui.textEditInboxMessage.setPlainText(self.ui.tableWidgetInbox.item( - currentRow, 2).data(Qt.UserRole).toPyObject()) # Only show the first 30K characters + if len(message) < 30000: + self.ui.textEditInboxMessage.setPlainText(message) # Only show the first 30K characters else: - self.ui.textEditInboxMessage.setPlainText(self.ui.tableWidgetInbox.item(currentRow, 2).data(Qt.UserRole).toPyObject()[ + self.ui.textEditInboxMessage.setPlainText(message[ :30000] + '\n\nDisplay of the remainder of the message truncated because it is too long.') # Only show the first 30K characters self.ui.tableWidgetInbox.item(currentRow, 0).setFont(font) @@ -2746,8 +2756,17 @@ class MyForm(QtGui.QMainWindow): def tableWidgetSentItemClicked(self): currentRow = self.ui.tableWidgetSent.currentRow() if currentRow >= 0: - self.ui.textEditSentMessage.setPlainText(self.ui.tableWidgetSent.item( - currentRow, 2).data(Qt.UserRole).toPyObject()) + ackdata = str(self.ui.tableWidgetSent.item( + currentRow, 3).data(Qt.UserRole).toPyObject()) + queryreturn = sqlQuery( + '''select message from sent where ackdata=?''', ackdata) + if queryreturn != []: + for row in queryreturn: + message, = row + else: + message = "Error occurred: could not load message from disk." + message = unicode(message, 'utf-8)') + self.ui.textEditSentMessage.setPlainText(message) def tableWidgetYourIdentitiesItemChanged(self): currentRow = self.ui.tableWidgetYourIdentities.currentRow() From 24452cddb21376ac474ad41601c99a5a25b6040a Mon Sep 17 00:00:00 2001 From: Jonathan Warren Date: Sun, 13 Oct 2013 14:08:12 -0400 Subject: [PATCH 27/48] check return value of RAND_bytes --- src/pyelliptic/openssl.py | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/src/pyelliptic/openssl.py b/src/pyelliptic/openssl.py index ee75f90a..f83630d4 100644 --- a/src/pyelliptic/openssl.py +++ b/src/pyelliptic/openssl.py @@ -268,7 +268,7 @@ class _OpenSSL: self.EVP_MD_CTX_destroy.argtypes = [ctypes.c_void_p] self.RAND_bytes = self._lib.RAND_bytes - self.RAND_bytes.restype = None + self.RAND_bytes.restype = ctypes.c_int self.RAND_bytes.argtypes = [ctypes.c_void_p, ctypes.c_int] @@ -394,7 +394,15 @@ class _OpenSSL: OpenSSL random function """ buffer = self.malloc(0, size) - self.RAND_bytes(buffer, size) + # This pyelliptic library, by default, didn't check the return value of RAND_bytes. It is + # evidently possible that it returned an error and not-actually-random data. However, in + # tests on various operating systems, while generating hundreds of gigabytes of random + # strings of various sizes I could not get an error to occur. Also Bitcoin doesn't check + # the return value of RAND_bytes either. + # Fixed in Bitmessage version 0.4.2 (in source code on 2013-10-13) + while self.RAND_bytes(buffer, size) != 1: + import time + time.sleep(1) return buffer.raw def malloc(self, data, size): From 7a30db75f1a3d37691519fc9f17f08151f44e29d Mon Sep 17 00:00:00 2001 From: Jonathan Warren Date: Wed, 16 Oct 2013 01:08:22 -0400 Subject: [PATCH 28/48] Use SQL-stored message when using 'Display as HTML' feature --- src/bitmessageqt/__init__.py | 69 ++++++++++++++---------------------- src/shared.py | 4 +-- 2 files changed, 29 insertions(+), 44 deletions(-) diff --git a/src/bitmessageqt/__init__.py b/src/bitmessageqt/__init__.py index b0193e33..f8ba57a8 100644 --- a/src/bitmessageqt/__init__.py +++ b/src/bitmessageqt/__init__.py @@ -1632,44 +1632,12 @@ class MyForm(QtGui.QMainWindow): sqlExecute( '''INSERT INTO sent VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?)''', *t) - shared.workerQueue.put(('sendbroadcast', '')) - - try: - fromLabel = shared.config.get(fromAddress, 'label') - except: - fromLabel = '' - if fromLabel == '': - fromLabel = fromAddress - toLabel = self.str_broadcast_subscribers + + self.displayNewSentMessage( + toAddress, toLabel, fromAddress, subject, message, ackdata) - self.ui.tableWidgetSent.insertRow(0) - newItem = QtGui.QTableWidgetItem(unicode(toLabel, 'utf-8')) - newItem.setData(Qt.UserRole, str(toAddress)) - self.ui.tableWidgetSent.setItem(0, 0, newItem) - - if fromLabel == '': - newItem = QtGui.QTableWidgetItem( - unicode(fromAddress, 'utf-8')) - else: - newItem = QtGui.QTableWidgetItem( - unicode(fromLabel, 'utf-8')) - newItem.setData(Qt.UserRole, str(fromAddress)) - self.ui.tableWidgetSent.setItem(0, 1, newItem) - newItem = QtGui.QTableWidgetItem(unicode(subject, 'utf-8)')) - newItem.setData(Qt.UserRole, unicode(message, 'utf-8)')) - self.ui.tableWidgetSent.setItem(0, 2, newItem) - # newItem = QtGui.QTableWidgetItem('Doing work necessary to - # send broadcast...'+ - # unicode(strftime(config.get('bitmessagesettings', - # 'timeformat'),localtime(int(time.time()))),'utf-8')) - newItem = myTableWidgetItem(_translate("MainWindow", "Work is queued.")) - newItem.setData(Qt.UserRole, QByteArray(ackdata)) - newItem.setData(33, int(time.time())) - self.ui.tableWidgetSent.setItem(0, 3, newItem) - - self.ui.textEditSentMessage.setPlainText( - self.ui.tableWidgetSent.item(0, 2).data(Qt.UserRole).toPyObject()) + shared.workerQueue.put(('sendbroadcast', '')) self.ui.comboBoxSendFrom.setCurrentIndex(0) self.ui.labelFrom.setText('') @@ -1778,7 +1746,7 @@ class MyForm(QtGui.QMainWindow): newItem.setData(Qt.UserRole, QByteArray(ackdata)) newItem.setData(33, int(time.time())) self.ui.tableWidgetSent.setItem(0, 3, newItem) - self.ui.textEditSentMessage.setPlainText(message) + self.ui.textEditSentMessage.setPlainText(unicode(message, 'utf-8)')) self.ui.tableWidgetSent.setSortingEnabled(True) def displayNewInboxMessage(self, inventoryHash, toAddress, fromAddress, subject, message): @@ -2254,10 +2222,18 @@ class MyForm(QtGui.QMainWindow): def on_action_InboxMessageForceHtml(self): currentInboxRow = self.ui.tableWidgetInbox.currentRow() - lines = self.ui.tableWidgetInbox.item( - currentInboxRow, 2).data(Qt.UserRole).toPyObject().split('\n') + + msgid = str(self.ui.tableWidgetInbox.item( + currentInboxRow, 3).data(Qt.UserRole).toPyObject()) + queryreturn = sqlQuery( + '''select message from inbox where msgid=?''', msgid) + if queryreturn != []: + for row in queryreturn: + messageAtCurrentInboxRow, = row + + lines = messageAtCurrentInboxRow.split('\n') for i in xrange(len(lines)): - if lines[i].contains('Message ostensibly from '): + if 'Message ostensibly from ' in lines[i]: lines[i] = '

%s

' % ( lines[i]) elif lines[i] == '------------------------------------------------------': @@ -2381,14 +2357,23 @@ class MyForm(QtGui.QMainWindow): subjectAtCurrentInboxRow = str(self.ui.tableWidgetInbox.item(currentInboxRow,2).text()) except: subjectAtCurrentInboxRow = '' + + # Retrieve the message data out of the SQL database + msgid = str(self.ui.tableWidgetInbox.item( + currentInboxRow, 3).data(Qt.UserRole).toPyObject()) + queryreturn = sqlQuery( + '''select message from inbox where msgid=?''', msgid) + if queryreturn != []: + for row in queryreturn: + message, = row + defaultFilename = "".join(x for x in subjectAtCurrentInboxRow if x.isalnum()) + '.txt' - data = self.ui.tableWidgetInbox.item(currentInboxRow,2).data(Qt.UserRole).toPyObject() filename = QFileDialog.getSaveFileName(self, _translate("MainWindow","Save As..."), defaultFilename, "Text files (*.txt);;All files (*.*)") if filename == '': return try: f = open(filename, 'w') - f.write( self.ui.tableWidgetInbox.item(currentInboxRow,2).data(Qt.UserRole).toPyObject() ) + f.write(message) f.close() except Exception, e: sys.stderr.write('Write error: '+ e) diff --git a/src/shared.py b/src/shared.py index 54acc014..2c07f7d0 100644 --- a/src/shared.py +++ b/src/shared.py @@ -279,6 +279,8 @@ def reloadBroadcastSendersForWhichImWatching(): def doCleanShutdown(): global shutdown shutdown = 1 #Used to tell proof of work worker threads to exit. + broadcastToSendDataQueues((0, 'shutdown', 'all')) + knownNodesLock.acquire() UISignalQueue.put(('updateStatusBar','Saving the knownNodes list of peers to disk...')) output = open(appdata + 'knownnodes.dat', 'wb') @@ -290,8 +292,6 @@ def doCleanShutdown(): logger.info('Finished closing knownnodes.dat output file.') UISignalQueue.put(('updateStatusBar','Done saving the knownNodes list of peers to disk.')) - broadcastToSendDataQueues((0, 'shutdown', 'all')) - logger.info('Flushing inventory in memory out to disk...') UISignalQueue.put(( 'updateStatusBar', From 12edee4ac41a85485033730849094eaefeaf8687 Mon Sep 17 00:00:00 2001 From: Jonathan Warren Date: Fri, 25 Oct 2013 19:35:59 -0400 Subject: [PATCH 29/48] added API commands: createChan, joinChan, leaveChan, deleteAddress --- src/bitmessagemain.py | 139 +++++++++++++++++++++++++------------- src/class_singleWorker.py | 13 ++-- 2 files changed, 101 insertions(+), 51 deletions(-) diff --git a/src/bitmessagemain.py b/src/bitmessagemain.py index 29f1b812..2294d398 100755 --- a/src/bitmessagemain.py +++ b/src/bitmessagemain.py @@ -38,6 +38,8 @@ from debug import logger import helper_bootstrap import proofofwork +str_chan = '[chan]' + import sys if sys.platform == 'darwin': if float("{1}.{2}".format(*sys.version_info)) < 7.5: @@ -150,8 +152,8 @@ class MySimpleXMLRPCRequestHandler(SimpleXMLRPCRequestHandler): def _decode(self, text, decode_type): try: return text.decode(decode_type) - except TypeError as e: - raise APIError(22, "Decode error - " + str(e)) + except Exception as e: + raise APIError(22, "Decode error - " + str(e) + ". Had trouble while decoding string: " + repr(text)) def _verifyAddress(self, address): status, addressVersionNumber, streamNumber, ripe = decodeAddress(address) @@ -387,70 +389,113 @@ class MySimpleXMLRPCRequestHandler(SimpleXMLRPCRequestHandler): ('getDeterministicAddress', addressVersionNumber, streamNumber, 'unused API address', numberOfAddresses, passphrase, eighteenByteRipe)) return shared.apiAddressGeneratorReturnQueue.get() - elif method == 'addChan': - #get passphrase, addressVersionNumber, and streamNumber + + elif method == 'createChan': if len(params) == 0: raise APIError(0, 'I need parameters.') elif len(params) == 1: passphrase, = params - address = '' - addressVersionNumber = 0 - streamNumber = 0 - label = '' - elif len(params) == 2: - passphrase, address = params - status, addressVersionNumber, streamNumber, ripe = decodeAddress(address) - label = '' - elif len(params) == 3: - passphrase, addressVersionNumber, streamNumber = params - address = '' - label = '' - elif len(params) == 4: - passphrase, addressVersionNumber, streamNumber, label = params - address = '' - label = self._decode(label, "base64") - else: - raise APIError(0, 'Too many parameters!') - + passphrase = self._decode(passphrase, "base64") if len(passphrase) == 0: raise APIError(1, 'The specified passphrase is blank.') - passphrase = self._decode(passphrase, "base64") - if label == '': - label = '[chan] ' + passphrase - if addressVersionNumber == 0: # 0 means "just use the proper addressVersionNumber" - addressVersionNumber = 4 - if addressVersionNumber != 3 and addressVersionNumber != 4: - raise APIError(2,'The address version number currently must be 3 or 4 (or 0 which means auto-select). ' + addressVersionNumber + ' isn\'t supported.') - if streamNumber == 0: # 0 means "just use the most available stream" - streamNumber = 1 - if streamNumber != 1: - raise APIError(3,'The stream number must be 1 (or 0 which means auto-select). Others aren\'t supported.') + # It would be nice to make the label the passphrase but it is + # possible that the passphrase contains non-utf-8 characters. + try: + unicode(passphrase, 'utf-8') + label = str_chan + ' ' + passphrase + except: + label = str_chan + ' ' + repr(passphrase) - #create identity + addressVersionNumber = 4 + streamNumber = 1 shared.apiAddressGeneratorReturnQueue.queue.clear() logger.debug('Requesting that the addressGenerator create chan %s.', passphrase) shared.addressGeneratorQueue.put(('createChan', addressVersionNumber, streamNumber, label, passphrase)) queueReturn = shared.apiAddressGeneratorReturnQueue.get() if len(queueReturn) == 0: - raise APIError(24, 'Chan address already present.') - createdAddress = queueReturn[0] - if address == '': - address = createdAddress - elif createdAddress != address: - raise APIError(18, 'Chan name does not match address.') + raise APIError(24, 'Chan address is already present.') + address = queueReturn[0] #add address to addressbook - address = addBMIfNotPresent(address) - self._verifyAddress(address) queryreturn = sqlQuery("SELECT address FROM addressbook WHERE address=?", address) - if queryreturn != []: - raise APIError(16, 'You already have this address in your address book.') + if queryreturn == []: + sqlExecute("INSERT INTO addressbook VALUES(?,?)", label, address) + shared.UISignalQueue.put(('rerenderInboxFromLabels','')) + shared.UISignalQueue.put(('rerenderSentToLabels','')) + shared.UISignalQueue.put(('rerenderAddressBook','')) + return address + elif method == 'joinChan': + if len(params) < 2: + raise APIError(0, 'I need two parameters.') + elif len(params) == 2: + passphrase, suppliedAddress= params + passphrase = self._decode(passphrase, "base64") + if len(passphrase) == 0: + raise APIError(1, 'The specified passphrase is blank.') + # It would be nice to make the label the passphrase but it is + # possible that the passphrase contains non-utf-8 characters. + try: + unicode(passphrase, 'utf-8') + label = str_chan + ' ' + passphrase + except: + label = str_chan + ' ' + repr(passphrase) - sqlExecute("INSERT INTO addressbook VALUES(?,?)", label, address) + status, addressVersionNumber, streamNumber, toRipe = self._verifyAddress(suppliedAddress) + suppliedAddress = addBMIfNotPresent(suppliedAddress) + shared.apiAddressGeneratorReturnQueue.queue.clear() + shared.addressGeneratorQueue.put(('joinChan', suppliedAddress, label, passphrase)) + addressGeneratorReturnValue = shared.apiAddressGeneratorReturnQueue.get() + + if addressGeneratorReturnValue == 'chan name does not match address': + raise APIError(18, 'Chan name does not match address.') + if len(addressGeneratorReturnValue) == 0: + raise APIError(24, 'Chan address is already present.') + createdAddress = addressGeneratorReturnValue[0] + #add address to addressbook + queryreturn = sqlQuery("SELECT address FROM addressbook WHERE address=?", createdAddress) + if queryreturn == []: + sqlExecute("INSERT INTO addressbook VALUES(?,?)", label, createdAddress) + shared.UISignalQueue.put(('rerenderInboxFromLabels','')) + shared.UISignalQueue.put(('rerenderSentToLabels','')) + shared.UISignalQueue.put(('rerenderAddressBook','')) + return "success" + elif method == 'leaveChan': + if len(params) == 0: + raise APIError(0, 'I need parameters.') + elif len(params) == 1: + address, = params + status, addressVersionNumber, streamNumber, toRipe = self._verifyAddress(address) + address = addBMIfNotPresent(address) + if not shared.config.has_section(address): + raise APIError(13, 'Could not find this address in your keys.dat file.') + if not shared.safeConfigGetBoolean(address, 'chan'): + raise APIError(25, 'Specified address is not a chan address. Use deleteAddress API call instead.') + shared.config.remove_section(address) + with open(shared.appdata + 'keys.dat', 'wb') as configfile: + shared.config.write(configfile) + sqlExecute('''DELETE FROM addressbook WHERE address=?''', address) shared.UISignalQueue.put(('rerenderInboxFromLabels','')) shared.UISignalQueue.put(('rerenderSentToLabels','')) shared.UISignalQueue.put(('rerenderAddressBook','')) - return "Added chan %s with address %s and label %s." %(passphrase, address, label) + return 'success' + + elif method == 'deleteAddress': + if len(params) == 0: + raise APIError(0, 'I need parameters.') + elif len(params) == 1: + address, = params + status, addressVersionNumber, streamNumber, toRipe = self._verifyAddress(address) + address = addBMIfNotPresent(address) + if not shared.config.has_section(address): + raise APIError(13, 'Could not find this address in your keys.dat file.') + shared.config.remove_section(address) + with open(shared.appdata + 'keys.dat', 'wb') as configfile: + shared.config.write(configfile) + shared.UISignalQueue.put(('rerenderInboxFromLabels','')) + shared.UISignalQueue.put(('rerenderSentToLabels','')) + shared.reloadMyAddressHashes() + return 'success' + elif method == 'getAllInboxMessages': queryreturn = sqlQuery( '''SELECT msgid, toaddress, fromaddress, subject, received, message, encodingtype, read FROM inbox where folder='inbox' ORDER BY received''') diff --git a/src/class_singleWorker.py b/src/class_singleWorker.py index b10e1919..2680f307 100644 --- a/src/class_singleWorker.py +++ b/src/class_singleWorker.py @@ -315,10 +315,15 @@ class singleWorker(threading.Thread): shared.broadcastToSendDataQueues(( streamNumber, 'advertiseobject', inventoryHash)) shared.UISignalQueue.put(('updateStatusBar', '')) - shared.config.set( - myAddress, 'lastpubkeysendtime', str(int(time.time()))) - with open(shared.appdata + 'keys.dat', 'wb') as configfile: - shared.config.write(configfile) + try: + shared.config.set( + myAddress, 'lastpubkeysendtime', str(int(time.time()))) + with open(shared.appdata + 'keys.dat', 'wb') as configfile: + shared.config.write(configfile) + except: + # The user deleted the address out of the keys.dat file before this + # finished. + pass def sendBroadcast(self): queryreturn = sqlQuery( From 422f47fae3ed7ed9e30ca538f6bebcbbc08207f6 Mon Sep 17 00:00:00 2001 From: Jonathan Warren Date: Fri, 25 Oct 2013 19:49:18 -0400 Subject: [PATCH 30/48] Modify readme.md --- README.md | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index c164c03a..c725c3bc 100644 --- a/README.md +++ b/README.md @@ -12,9 +12,10 @@ warrantless wiretapping programs. Development ---------- -If you plan to put a non-trivial amount of work into coding new features, it -is recommended that you first solicit feedback on the DevTalk pseudo-mailing -list: +Bitmessage is a collaborative project. You are welcome to submit pull requests +although if you plan to put a non-trivial amount of work into coding new +features, it is recommended that you first solicit feedback on the DevTalk +pseudo-mailing list: BM-2D9QKN4teYRvoq2fyzpiftPh9WP9qggtzh From 4ec91b6ed0de0d99fa82f771529c757498e63272 Mon Sep 17 00:00:00 2001 From: Jonathan Warren Date: Fri, 25 Oct 2013 19:57:06 -0400 Subject: [PATCH 31/48] modified addresses.decodeAddress so that API decodeAddress works properly --- src/addresses.py | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/addresses.py b/src/addresses.py index 5b092ca5..5f666543 100644 --- a/src/addresses.py +++ b/src/addresses.py @@ -139,7 +139,7 @@ def decodeAddress(address): integer = decodeBase58(address) if integer == 0: status = 'invalidcharacters' - return status,0,0,0 + return status,0,0,"" #after converting to hex, the string will be prepended with a 0x and appended with a L hexdata = hex(integer)[2:-1] @@ -161,7 +161,7 @@ def decodeAddress(address): if checksum != sha.digest()[0:4]: status = 'checksumfailed' - return status,0,0,0 + return status,0,0,"" #else: # print 'checksum PASSED' @@ -172,11 +172,11 @@ def decodeAddress(address): if addressVersionNumber > 4: print 'cannot decode address version numbers this high' status = 'versiontoohigh' - return status,0,0,0 + return status,0,0,"" elif addressVersionNumber == 0: print 'cannot decode address version numbers of zero.' status = 'versiontoohigh' - return status,0,0,0 + return status,0,0,"" streamNumber, bytesUsedByStreamNumber = decodeVarint(data[bytesUsedByVersionNumber:]) #print streamNumber @@ -191,16 +191,16 @@ def decodeAddress(address): elif len(data[bytesUsedByVersionNumber+bytesUsedByStreamNumber:-4]) == 18: return status,addressVersionNumber,streamNumber,'\x00\x00'+data[bytesUsedByVersionNumber+bytesUsedByStreamNumber:-4] elif len(data[bytesUsedByVersionNumber+bytesUsedByStreamNumber:-4]) < 18: - return 'ripetooshort',0,0,0 + return 'ripetooshort',0,0,"" elif len(data[bytesUsedByVersionNumber+bytesUsedByStreamNumber:-4]) > 20: - return 'ripetoolong',0,0,0 + return 'ripetoolong',0,0,"" else: - return 'otherproblem',0,0,0 + return 'otherproblem',0,0,"" elif addressVersionNumber == 4: if len(data[bytesUsedByVersionNumber+bytesUsedByStreamNumber:-4]) > 20: - return 'ripetoolong',0,0,0 + return 'ripetoolong',0,0,"" elif len(data[bytesUsedByVersionNumber+bytesUsedByStreamNumber:-4]) < 4: - return 'ripetooshort',0,0,0 + return 'ripetooshort',0,0,"" else: x00string = '\x00' * (20 - len(data[bytesUsedByVersionNumber+bytesUsedByStreamNumber:-4])) return status,addressVersionNumber,streamNumber,x00string+data[bytesUsedByVersionNumber+bytesUsedByStreamNumber:-4] From 41c3b73343e713ae404d489f71d8e4392ed0cf74 Mon Sep 17 00:00:00 2001 From: Jonathan Warren Date: Sat, 26 Oct 2013 18:49:22 -0400 Subject: [PATCH 32/48] comment --- src/bitmessagemain.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/bitmessagemain.py b/src/bitmessagemain.py index 2294d398..3173981a 100755 --- a/src/bitmessagemain.py +++ b/src/bitmessagemain.py @@ -928,7 +928,7 @@ class MySimpleXMLRPCRequestHandler(SimpleXMLRPCRequestHandler): networkStatus = 'connectedAndReceivingIncomingConnections' return json.dumps({'networkConnections':len(shared.connectedHostsList),'numberOfMessagesProcessed':shared.numberOfMessagesProcessed, 'numberOfBroadcastsProcessed':shared.numberOfBroadcastsProcessed, 'numberOfPubkeysProcessed':shared.numberOfPubkeysProcessed, 'networkStatus':networkStatus, 'softwareName':'PyBitmessage','softwareVersion':shared.softwareVersion}, indent=4, separators=(',', ': ')) elif method == 'decodeAddress': - #decode an address + # Return a meaningful decoding of an address. if len(params) != 1: raise APIError(0, 'I need 1 parameter!') address, = params From f7ef2b4e051fcd0cc2d15818235ac769f8484818 Mon Sep 17 00:00:00 2001 From: Jonathan Warren Date: Fri, 1 Nov 2013 19:25:24 -0400 Subject: [PATCH 33/48] various changes to Identicon code --- src/bitmessageqt/__init__.py | 315 ++++++++++++++--------------------- src/bitmessageqt/settings.py | 183 ++++++++------------ src/bitmessageqt/settings.ui | 216 ++++++++---------------- src/class_sqlThread.py | 16 +- src/helper_startup.py | 6 +- 5 files changed, 288 insertions(+), 448 deletions(-) diff --git a/src/bitmessageqt/__init__.py b/src/bitmessageqt/__init__.py index ce3160aa..910f73ff 100644 --- a/src/bitmessageqt/__init__.py +++ b/src/bitmessageqt/__init__.py @@ -29,6 +29,7 @@ import sys from time import strftime, localtime, gmtime import time import os +import hashlib from pyelliptic.openssl import OpenSSL import pickle import platform @@ -64,20 +65,19 @@ def identiconize(address): # 3fd4bf901b9d4ea1394f0fb358725b28 try: - identicon_lib = shared.config.get('bitmessagesettings', 'identicon') + identicon_lib = shared.config.get('bitmessagesettings', 'identiconlib') except: - # default to no identicons - identicon_lib = False + # default to qidenticon_two_x + identicon_lib = 'qidenticon_two_x' + + # As an 'identiconsuffix' you could put "@bitmessge.ch" or "@bm.addr" to make it compatible with other identicon generators. (Note however, that E-Mail programs might convert the BM-address to lowercase first.) + # It can be used as a pseudo-password to salt the generation of the identicons to decrease the risk + # of attacks where someone creates an address to mimic someone else's identicon. + identiconsuffix = shared.config.get('bitmessagesettings', 'identiconsuffix') - try: - # As an 'identiconsuffix' you could put "@bitmessge.ch" or "@bm.addr" to make it compatible with other identicon generators. (Note however, that E-Mail programs might convert the BM-address to lowercase first.) - # It can be used as a pseudo-password to salt the generation of the identicons to decrease the risk - # of attacks where someone creates an address to mimic someone else's identicon. - # If not set yet it will be filled by a random string. - # Another good idea would be to fill it with the private key of one of your addresses (because you don't need to memorize it then). - identiconsuffix = shared.config.get('bitmessagesettings', 'identiconsuffix') - except: - identiconsuffix = '' + if not shared.config.getboolean('bitmessagesettings', 'useidenticons'): + idcon = QtGui.QIcon() + return idcon if (identicon_lib[:len('qidenticon')] == 'qidenticon'): # print identicon_lib @@ -86,7 +86,6 @@ def identiconize(address): # Licesensed under FreeBSD License. # stripped from PIL and uses QT instead (by sendiulo, same license) import qidenticon - import hashlib hash = hashlib.md5(addBMIfNotPresent(address)+identiconsuffix).hexdigest() use_two_colors = (identicon_lib[:len('qidenticon_two')] == 'qidenticon_two') opacity = int(not((identicon_lib == 'qidenticon_x') | (identicon_lib == 'qidenticon_two_x') | (identicon_lib == 'qidenticon_b') | (identicon_lib == 'qidenticon_two_b')))*255 @@ -113,66 +112,50 @@ def identiconize(address): idcon = QtGui.QIcon() idcon.addPixmap(pix, QtGui.QIcon.Normal, QtGui.QIcon.Off) return idcon - elif identicon_lib in ['False', 'false', 'None', 'none']: - idcon = QtGui.QIcon() - return idcon - else: - # default to no identicons - idcon = QtGui.QIcon() - return idcon -def avatarize(address, fallBackToIdenticon = False): +def avatarize(address): """ loads a supported image for the given address' hash form 'avatars' folder falls back to default avatar if 'default.*' file exists - falls back to identiconize(address) if second argument fallBackToIdenticon == True + falls back to identiconize(address) """ idcon = QtGui.QIcon() - if shared.safeConfigGetBoolean('bitmessagesettings', 'avatars'): - import hashlib - hash = hashlib.md5(addBMIfNotPresent(address)).hexdigest() - str_broadcast_subscribers = '[Broadcast subscribers]' - if address == str_broadcast_subscribers: - # don't hash [Broadcast subscribers] - hash = address - # http://pyqt.sourceforge.net/Docs/PyQt4/qimagereader.html#supportedImageFormats - # print QImageReader.supportedImageFormats () - # QImageReader.supportedImageFormats () - extensions = ['PNG', 'GIF', 'JPG', 'JPEG', 'SVG', 'BMP', 'MNG', 'PBM', 'PGM', 'PPM', 'TIFF', 'XBM', 'XPM', 'TGA'] - # try to find a specific avatar - for ext in extensions: - lower_hash = shared.appdata + 'avatars/' + hash + '.' + ext.lower() - upper_hash = shared.appdata + 'avatars/' + hash + '.' + ext.upper() - if os.path.isfile(lower_hash): - # print 'found avatar of ', address - idcon.addFile(lower_hash) - return idcon - elif os.path.isfile(upper_hash): - # print 'found avatar of ', address - idcon.addFile(upper_hash) - return idcon - # if we haven't found any, try to find a default avatar - for ext in extensions: - lower_default = shared.appdata + 'avatars/' + 'default.' + ext.lower() - upper_default = shared.appdata + 'avatars/' + 'default.' + ext.upper() - if os.path.isfile(lower_default): - default = lower_default - idcon.addFile(lower_default) - return idcon - elif os.path.isfile(upper_default): - default = upper_default - idcon.addFile(upper_default) - return idcon - # if avatars are deactivated or none found - if fallBackToIdenticon: - return identiconize(address) - else: - try: - identicon_lib = shared.config.get('bitmessagesettings', 'identicon') - except: - # default to no identicons - identicon_lib = False - return idcon + hash = hashlib.md5(addBMIfNotPresent(address)).hexdigest() + str_broadcast_subscribers = '[Broadcast subscribers]' + if address == str_broadcast_subscribers: + # don't hash [Broadcast subscribers] + hash = address + # http://pyqt.sourceforge.net/Docs/PyQt4/qimagereader.html#supportedImageFormats + # print QImageReader.supportedImageFormats () + # QImageReader.supportedImageFormats () + extensions = ['PNG', 'GIF', 'JPG', 'JPEG', 'SVG', 'BMP', 'MNG', 'PBM', 'PGM', 'PPM', 'TIFF', 'XBM', 'XPM', 'TGA'] + # try to find a specific avatar + for ext in extensions: + lower_hash = shared.appdata + 'avatars/' + hash + '.' + ext.lower() + upper_hash = shared.appdata + 'avatars/' + hash + '.' + ext.upper() + if os.path.isfile(lower_hash): + # print 'found avatar of ', address + idcon.addFile(lower_hash) + return idcon + elif os.path.isfile(upper_hash): + # print 'found avatar of ', address + idcon.addFile(upper_hash) + return idcon + # if we haven't found any, try to find a default avatar + for ext in extensions: + lower_default = shared.appdata + 'avatars/' + 'default.' + ext.lower() + upper_default = shared.appdata + 'avatars/' + 'default.' + ext.upper() + if os.path.isfile(lower_default): + default = lower_default + idcon.addFile(lower_default) + return idcon + elif os.path.isfile(upper_default): + default = upper_default + idcon.addFile(upper_default) + return idcon + # If no avatar is found + return identiconize(address) + class MyForm(QtGui.QMainWindow): @@ -462,7 +445,6 @@ class MyForm(QtGui.QMainWindow): newItem.setTextColor(QtGui.QColor(128, 128, 128)) if shared.safeConfigGetBoolean(addressInKeysFile, 'mailinglist'): newItem.setTextColor(QtGui.QColor(137, 04, 177)) # magenta - newItem.setIcon(identiconize(addressInKeysFile)) self.ui.tableWidgetYourIdentities.setItem(0, 1, newItem) newItem = QtGui.QTableWidgetItem(str( decodeAddress(addressInKeysFile)[2])) @@ -531,7 +513,6 @@ class MyForm(QtGui.QMainWindow): self.ui.tableWidgetYourIdentities.setIconSize(QtCore.QSize(identicon_size, identicon_size)) self.ui.tableWidgetSubscriptions.setIconSize(QtCore.QSize(identicon_size, identicon_size)) self.ui.tableWidgetAddressBook.setIconSize(QtCore.QSize(identicon_size, identicon_size)) - #self.ui.tableWidgetWhitelist.setIconSize(QtCore.QSize(identicon_size, identicon_size)) self.ui.tableWidgetBlacklist.setIconSize(QtCore.QSize(identicon_size, identicon_size)) self.UISignalThread = UISignaler() @@ -713,30 +694,29 @@ class MyForm(QtGui.QMainWindow): for row in queryreturn: toAddress, fromAddress, subject, status, ackdata, lastactiontime = row subject = shared.fixPotentiallyInvalidUTF8Data(subject) - #message = shared.fixPotentiallyInvalidUTF8Data(message) - try: + + if shared.config.has_section(fromAddress): fromLabel = shared.config.get(fromAddress, 'label') - except: - fromLabel = '' if fromLabel == '': fromLabel = fromAddress toLabel = '' queryreturn = sqlQuery( '''select label from addressbook where address=?''', toAddress) - if queryreturn != []: for row in queryreturn: toLabel, = row + + if toLabel == '': + if shared.config.has_section(toAddress): + toLabel = shared.config.get(toAddress, 'label') + if toLabel == '': + toLabel = toAddress self.ui.tableWidgetSent.insertRow(0) - if toLabel == '': - newItem = QtGui.QTableWidgetItem(unicode(toAddress, 'utf-8')) - newItem.setToolTip(unicode(toAddress, 'utf-8')) - else: - newItem = QtGui.QTableWidgetItem(unicode(toLabel, 'utf-8')) - newItem.setToolTip(unicode(toLabel, 'utf-8')) - newItem.setIcon(avatarize(toAddress, True)) + newItem = QtGui.QTableWidgetItem(unicode(toLabel, 'utf-8')) + newItem.setToolTip(unicode(toLabel, 'utf-8')) + newItem.setIcon(avatarize(toAddress)) newItem.setData(Qt.UserRole, str(toAddress)) newItem.setFlags( QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled) @@ -748,7 +728,7 @@ class MyForm(QtGui.QMainWindow): else: newItem = QtGui.QTableWidgetItem(unicode(fromLabel, 'utf-8')) newItem.setToolTip(unicode(fromLabel, 'utf-8')) - newItem.setIcon(avatarize(fromAddress, True)) + newItem.setIcon(avatarize(fromAddress)) newItem.setData(Qt.UserRole, str(fromAddress)) newItem.setFlags( QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled) @@ -837,7 +817,6 @@ class MyForm(QtGui.QMainWindow): for row in queryreturn: msgid, toAddress, fromAddress, subject, received, read = row subject = shared.fixPotentiallyInvalidUTF8Data(subject) - #message = shared.fixPotentiallyInvalidUTF8Data(message) try: if toAddress == self.str_broadcast_subscribers: toLabel = self.str_broadcast_subscribers @@ -848,17 +827,13 @@ class MyForm(QtGui.QMainWindow): if toLabel == '': toLabel = toAddress - try: # try to get the from label fom YourIdentites (for chan messages) + fromLabel = '' + if shared.config.has_section(fromAddress): fromLabel = shared.config.get(fromAddress, 'label') - checkIfChan = True - except: - fromLabel = '' - checkIfChan = False - if fromLabel == '': # If this address wasn't in our address book... + if fromLabel == '': # If the fromAddress isn't one of our addresses queryreturn = sqlQuery( '''select label from addressbook where address=?''', fromAddress) - if queryreturn != []: for row in queryreturn: fromLabel, = row @@ -866,16 +841,17 @@ class MyForm(QtGui.QMainWindow): if fromLabel == '': # If this address wasn't in our address book... queryReturn = sqlQuery( '''select label from subscriptions where address=?''', fromAddress) - if queryreturn != []: for row in queryreturn: fromLabel, = row + if fromLabel == '': + fromLabel = fromAddress # message row self.ui.tableWidgetInbox.insertRow(0) + # to newItem = QtGui.QTableWidgetItem(unicode(toLabel, 'utf-8')) newItem.setToolTip(unicode(toLabel, 'utf-8')) - # to newItem.setFlags( QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled) if not read: @@ -885,24 +861,19 @@ class MyForm(QtGui.QMainWindow): newItem.setTextColor(QtGui.QColor(137, 04, 177)) # magenta if shared.safeConfigGetBoolean(str(toAddress), 'chan'): newItem.setTextColor(QtGui.QColor(216, 119, 0)) # orange - newItem.setIcon(avatarize(toAddress, True)) + newItem.setIcon(avatarize(toAddress)) self.ui.tableWidgetInbox.setItem(0, 0, newItem) # from - if fromLabel == '': - newItem = QtGui.QTableWidgetItem( - unicode(fromAddress, 'utf-8')) - newItem.setToolTip(unicode(fromAddress, 'utf-8')) - else: - newItem = QtGui.QTableWidgetItem(unicode(fromLabel, 'utf-8')) - newItem.setToolTip(unicode(fromLabel, 'utf-8')) + newItem = QtGui.QTableWidgetItem(unicode(fromLabel, 'utf-8')) + newItem.setToolTip(unicode(fromLabel, 'utf-8')) newItem.setFlags( QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled) if not read: newItem.setFont(font) newItem.setData(Qt.UserRole, str(fromAddress)) - if checkIfChan & shared.safeConfigGetBoolean(str(toAddress), 'chan'): + if shared.safeConfigGetBoolean(str(fromAddress), 'chan'): newItem.setTextColor(QtGui.QColor(216, 119, 0)) # orange - newItem.setIcon(avatarize(fromAddress, True)) + newItem.setIcon(avatarize(fromAddress)) self.ui.tableWidgetInbox.setItem(0, 1, newItem) # subject newItem = QtGui.QTableWidgetItem(unicode(subject, 'utf-8')) @@ -1585,11 +1556,8 @@ class MyForm(QtGui.QMainWindow): if queryreturn != []: for row in queryreturn: fromLabel, = row - self.ui.tableWidgetInbox.item( - i, 1).setText(unicode(fromLabel, 'utf-8')) - self.ui.tableWidgetInbox.item( - i, 1).setIcon(avatarize(addressToLookup, True)) - else: + + if fromLabel == '': # It might be a broadcast message. We should check for that # label. queryreturn = sqlQuery( @@ -1598,46 +1566,38 @@ class MyForm(QtGui.QMainWindow): if queryreturn != []: for row in queryreturn: fromLabel, = row - self.ui.tableWidgetInbox.item( - i, 1).setText(unicode(fromLabel, 'utf-8')) - self.ui.tableWidgetInbox.item( - i, 1).setIcon(avatarize(addressToLookup, True)) - else: - # It might be a chan message. We should check for that - # label. - try: - fromLabel = shared.config.get(addressToLookup, 'label') - except: - fromLabel = '' - if fromLabel == '': - fromLabel = addressToLookup - self.ui.tableWidgetInbox.item( - i, 1).setText(unicode(fromLabel, 'utf-8')) - self.ui.tableWidgetInbox.item( - i, 1).setIcon(avatarize(addressToLookup, True)) - # Set the color according to whether it is the address of a mailing - # list or not. - if shared.safeConfigGetBoolean(addressToLookup, 'chan'): - self.ui.tableWidgetInbox.item(i, 1).setTextColor(QtGui.QColor(216, 119, 0)) # orange - else: - self.ui.tableWidgetInbox.item( - i, 1).setTextColor(QApplication.palette().text().color()) + if fromLabel == '': + # Message might be from an address we own like a chan address. Let's look for that label. + if shared.config.has_section(addressToLookup): + fromLabel = shared.config.get(addressToLookup, 'label') + if fromLabel == '': + fromLabel = addressToLookup + self.ui.tableWidgetInbox.item( + i, 1).setText(unicode(fromLabel, 'utf-8')) + self.ui.tableWidgetInbox.item( + i, 1).setIcon(avatarize(addressToLookup)) + # Set the color according to whether it is the address of a mailing + # list or not. + if shared.safeConfigGetBoolean(addressToLookup, 'chan'): + self.ui.tableWidgetInbox.item(i, 1).setTextColor(QtGui.QColor(216, 119, 0)) # orange + else: + self.ui.tableWidgetInbox.item( + i, 1).setTextColor(QApplication.palette().text().color()) def rerenderInboxToLabels(self): for i in range(self.ui.tableWidgetInbox.rowCount()): toAddress = str(self.ui.tableWidgetInbox.item( i, 0).data(Qt.UserRole).toPyObject()) - try: + # Message might be to an address we own like a chan address. Let's look for that label. + if shared.config.has_section(toAddress): toLabel = shared.config.get(toAddress, 'label') - except: - toLabel = '' - if toLabel == '': + else: toLabel = toAddress self.ui.tableWidgetInbox.item( i, 0).setText(unicode(toLabel, 'utf-8')) self.ui.tableWidgetInbox.item( - i, 0).setIcon(avatarize(toAddress, True)) + i, 0).setIcon(avatarize(toAddress)) # Set the color according to whether it is the address of a mailing # list, a chan or neither. if shared.safeConfigGetBoolean(toAddress, 'chan'): @@ -1652,16 +1612,15 @@ class MyForm(QtGui.QMainWindow): for i in range(self.ui.tableWidgetSent.rowCount()): fromAddress = str(self.ui.tableWidgetSent.item( i, 1).data(Qt.UserRole).toPyObject()) - try: + # Message might be from an address we own like a chan address. Let's look for that label. + if shared.config.has_section(fromAddress): fromLabel = shared.config.get(fromAddress, 'label') - except: - fromLabel = '' - if fromLabel == '': + else: fromLabel = fromAddress self.ui.tableWidgetSent.item( i, 1).setText(unicode(fromLabel, 'utf-8')) self.ui.tableWidgetSent.item( - i, 1).setIcon(avatarize(fromAddress, True)) + i, 1).setIcon(avatarize(fromAddress)) def rerenderSentToLabels(self): for i in range(self.ui.tableWidgetSent.rowCount()): @@ -1670,12 +1629,17 @@ class MyForm(QtGui.QMainWindow): toLabel = '' queryreturn = sqlQuery( '''select label from addressbook where address=?''', addressToLookup) - if queryreturn != []: for row in queryreturn: toLabel, = row - self.ui.tableWidgetSent.item( - i, 0).setText(unicode(toLabel, 'utf-8')) + + if toLabel == '': + # Message might be to an address we own like a chan address. Let's look for that label. + if shared.config.has_section(addressToLookup): + toLabel = shared.config.get(addressToLookup, 'label') + toLabel = addressToLookup + self.ui.tableWidgetSent.item( + i, 0).setText(unicode(toLabel, 'utf-8')) def rerenderAddressBook(self): self.ui.tableWidgetAddressBook.setRowCount(0) @@ -1684,6 +1648,7 @@ class MyForm(QtGui.QMainWindow): label, address = row self.ui.tableWidgetAddressBook.insertRow(0) newItem = QtGui.QTableWidgetItem(unicode(label, 'utf-8')) + newItem.setIcon(avatarize(address)) self.ui.tableWidgetAddressBook.setItem(0, 0, newItem) newItem = QtGui.QTableWidgetItem(address) newItem.setFlags( @@ -1706,7 +1671,6 @@ class MyForm(QtGui.QMainWindow): QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled) if not enabled: newItem.setTextColor(QtGui.QColor(128, 128, 128)) - newItem.setIcon(identiconize(address)) self.ui.tableWidgetSubscriptions.setItem(0, 1, newItem) def click_pushButtonSend(self): @@ -1881,7 +1845,7 @@ class MyForm(QtGui.QMainWindow): isEnabled = shared.config.getboolean( addressInKeysFile, 'enabled') # I realize that this is poor programming practice but I don't care. It's easier for others to read. if isEnabled: - self.ui.comboBoxSendFrom.insertItem(0, avatarize(addressInKeysFile, True), unicode(shared.config.get( + self.ui.comboBoxSendFrom.insertItem(0, avatarize(addressInKeysFile), unicode(shared.config.get( addressInKeysFile, 'label'), 'utf-8'), addressInKeysFile) self.ui.comboBoxSendFrom.insertItem(0, '', '') if(self.ui.comboBoxSendFrom.count() == 2): @@ -1913,7 +1877,7 @@ class MyForm(QtGui.QMainWindow): newItem = QtGui.QTableWidgetItem(unicode(toLabel, 'utf-8')) newItem.setToolTip(unicode(toLabel, 'utf-8')) newItem.setData(Qt.UserRole, str(toAddress)) - newItem.setIcon(avatarize(toAddress, True)) + newItem.setIcon(avatarize(toAddress)) self.ui.tableWidgetSent.setItem(0, 0, newItem) if fromLabel == '': newItem = QtGui.QTableWidgetItem(unicode(fromAddress, 'utf-8')) @@ -1922,7 +1886,7 @@ class MyForm(QtGui.QMainWindow): newItem = QtGui.QTableWidgetItem(unicode(fromLabel, 'utf-8')) newItem.setToolTip(unicode(fromLabel, 'utf-8')) newItem.setData(Qt.UserRole, str(fromAddress)) - newItem.setIcon(avatarize(fromAddress, True)) + newItem.setIcon(avatarize(fromAddress)) self.ui.tableWidgetSent.setItem(0, 1, newItem) newItem = QtGui.QTableWidgetItem(unicode(subject, 'utf-8)')) newItem.setToolTip(unicode(subject, 'utf-8)')) @@ -1944,7 +1908,6 @@ class MyForm(QtGui.QMainWindow): def displayNewInboxMessage(self, inventoryHash, toAddress, fromAddress, subject, message): subject = shared.fixPotentiallyInvalidUTF8Data(subject) - #message = shared.fixPotentiallyInvalidUTF8Data(message) fromLabel = '' queryreturn = sqlQuery( '''select label from addressbook where address=?''', fromAddress) @@ -1981,7 +1944,7 @@ class MyForm(QtGui.QMainWindow): if shared.safeConfigGetBoolean(str(toAddress), 'chan'): newItem.setTextColor(QtGui.QColor(216, 119, 0)) # orange self.ui.tableWidgetInbox.insertRow(0) - newItem.setIcon(avatarize(toAddress, True)) + newItem.setIcon(avatarize(toAddress)) self.ui.tableWidgetInbox.setItem(0, 0, newItem) if fromLabel == '': @@ -1996,7 +1959,7 @@ class MyForm(QtGui.QMainWindow): self.notifierShow(unicode(_translate("MainWindow",'New Message').toUtf8(),'utf-8'), unicode(_translate("MainWindow",'From ').toUtf8(),'utf-8') + unicode(fromLabel, 'utf-8'), self.SOUND_KNOWN, unicode(fromLabel, 'utf-8')) newItem.setData(Qt.UserRole, str(fromAddress)) newItem.setFont(font) - newItem.setIcon(avatarize(fromAddress, True)) + newItem.setIcon(avatarize(fromAddress)) self.ui.tableWidgetInbox.setItem(0, 1, newItem) newItem = QtGui.QTableWidgetItem(unicode(subject, 'utf-8)')) newItem.setToolTip(unicode(subject, 'utf-8)')) @@ -2038,7 +2001,6 @@ class MyForm(QtGui.QMainWindow): newItem.setIcon(avatarize(address)) self.ui.tableWidgetAddressBook.setItem(0, 0, newItem) newItem = QtGui.QTableWidgetItem(address) - newItem.setIcon(identiconize(address)) newItem.setFlags( QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled) self.ui.tableWidgetAddressBook.setItem(0, 1, newItem) @@ -2062,7 +2024,6 @@ class MyForm(QtGui.QMainWindow): newItem.setIcon(avatarize(address)) self.ui.tableWidgetSubscriptions.setItem(0,0,newItem) newItem = QtGui.QTableWidgetItem(address) - newItem.setIcon(identiconize(address)) newItem.setFlags( QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled ) self.ui.tableWidgetSubscriptions.setItem(0,1,newItem) self.ui.tableWidgetSubscriptions.setSortingEnabled(True) @@ -2101,7 +2062,6 @@ class MyForm(QtGui.QMainWindow): newItem.setIcon(avatarize(address)) self.ui.tableWidgetBlacklist.setItem(0, 0, newItem) newItem = QtGui.QTableWidgetItem(address) - newItem.setIcon(identiconize(address)) newItem.setFlags( QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled) if not enabled: @@ -2135,19 +2095,13 @@ class MyForm(QtGui.QMainWindow): self.settingsDialogInstance.ui.checkBoxStartInTray.isChecked())) shared.config.set('bitmessagesettings', 'willinglysendtomobile', str( self.settingsDialogInstance.ui.checkBoxWillinglySendToMobile.isChecked())) + shared.config.set('bitmessagesettings', 'useidenticons', str( + self.settingsDialogInstance.ui.checkBoxUseIdenticons.isChecked())) lang_ind = int(self.settingsDialogInstance.ui.languageComboBox.currentIndex()) if not languages[lang_ind] == 'other': shared.config.set('bitmessagesettings', 'userlocale', languages[lang_ind]) - - curr_index = self.settingsDialogInstance.ui.comboBoxIdenticonStyle.currentIndex() - shared.config.set('bitmessagesettings', 'identicon', str(self.settingsDialogInstance.ui.comboBoxIdenticonStyle.itemData( - curr_index , Qt.UserRole).toString())) - shared.config.set('bitmessagesettings', 'identiconsuffix', str( - self.settingsDialogInstance.ui.lineEditIdenticonSuffix.text())) - shared.config.set('bitmessagesettings', 'avatars', str( - self.settingsDialogInstance.ui.checkBoxLoadAvatars.isChecked())) - + if int(shared.config.get('bitmessagesettings', 'port')) != int(self.settingsDialogInstance.ui.lineEditTCPPort.text()): if not shared.safeConfigGetBoolean('bitmessagesettings', 'dontconnect'): QMessageBox.about(self, _translate("MainWindow", "Restart"), _translate( @@ -2297,11 +2251,12 @@ class MyForm(QtGui.QMainWindow): self.NewBlacklistDialogInstance = NewSubscriptionDialog(self) if self.NewBlacklistDialogInstance.exec_(): if self.NewBlacklistDialogInstance.ui.labelSubscriptionAddressCheck.text() == _translate("MainWindow", "Address is valid."): + address = addBMIfNotPresent(str( + self.NewBlacklistDialogInstance.ui.lineEditSubscriptionAddress.text())) # First we must check to see if the address is already in the # address book. The user cannot add it again or else it will # cause problems when updating and deleting the entry. - t = (addBMIfNotPresent(str( - self.NewBlacklistDialogInstance.ui.lineEditSubscriptionAddress.text())),) + t = (address,) if shared.config.get('bitmessagesettings', 'blackwhitelist') == 'black': sql = '''select * from blacklist where address=?''' else: @@ -2312,15 +2267,14 @@ class MyForm(QtGui.QMainWindow): self.ui.tableWidgetBlacklist.insertRow(0) newItem = QtGui.QTableWidgetItem(unicode( self.NewBlacklistDialogInstance.ui.newsubscriptionlabel.text().toUtf8(), 'utf-8')) + newItem.setIcon(avatarize(address)) self.ui.tableWidgetBlacklist.setItem(0, 0, newItem) - newItem = QtGui.QTableWidgetItem(addBMIfNotPresent( - self.NewBlacklistDialogInstance.ui.lineEditSubscriptionAddress.text())) + newItem = QtGui.QTableWidgetItem(address) newItem.setFlags( QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled) self.ui.tableWidgetBlacklist.setItem(0, 1, newItem) self.ui.tableWidgetBlacklist.setSortingEnabled(True) - t = (str(self.NewBlacklistDialogInstance.ui.newsubscriptionlabel.text().toUtf8()), addBMIfNotPresent( - str(self.NewBlacklistDialogInstance.ui.lineEditSubscriptionAddress.text())), True) + t = (str(self.NewBlacklistDialogInstance.ui.newsubscriptionlabel.text().toUtf8()), address, True) if shared.config.get('bitmessagesettings', 'blackwhitelist') == 'black': sql = '''INSERT INTO blacklist VALUES (?,?,?)''' else: @@ -2529,6 +2483,7 @@ class MyForm(QtGui.QMainWindow): newItem = QtGui.QTableWidgetItem( '--New entry. Change label in Address Book.--') self.ui.tableWidgetAddressBook.setItem(0, 0, newItem) + newItem.setIcon(avatarize(addressAtCurrentInboxRow)) newItem = QtGui.QTableWidgetItem(addressAtCurrentInboxRow) newItem.setFlags( QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled) @@ -2880,10 +2835,11 @@ class MyForm(QtGui.QMainWindow): def on_action_SetAvatar(self, thisTableWidget): # thisTableWidget = self.ui.tableWidgetYourIdentities + if not os.path.exists(shared.appdata + 'avatars/'): + os.makedirs(shared.appdata + 'avatars/') currentRow = thisTableWidget.currentRow() addressAtCurrentRow = thisTableWidget.item( currentRow, 1).text() - import hashlib hash = hashlib.md5(addBMIfNotPresent(addressAtCurrentRow)).hexdigest() extensions = ['PNG', 'GIF', 'JPG', 'JPEG', 'SVG', 'BMP', 'MNG', 'PBM', 'PGM', 'PPM', 'TIFF', 'XBM', 'XPM', 'TGA'] # http://pyqt.sourceforge.net/Docs/PyQt4/qimagereader.html#supportedImageFormats @@ -2938,7 +2894,6 @@ class MyForm(QtGui.QMainWindow): # set the icon thisTableWidget.item( currentRow, 0).setIcon(avatarize(addressAtCurrentRow)) - shared.reloadBroadcastSendersForWhichImWatching() self.rerenderSubscriptions() self.rerenderComboBoxSendFrom() self.rerenderInboxFromLabels() @@ -3001,6 +2956,7 @@ class MyForm(QtGui.QMainWindow): if queryreturn != []: for row in queryreturn: messageText, = row + messageText = shared.fixPotentiallyInvalidUTF8Data(messageText) messageText = unicode(messageText, 'utf-8)') if len(messageText) > 30000: messageText = ( @@ -3090,7 +3046,6 @@ class MyForm(QtGui.QMainWindow): self.ui.tableWidgetYourIdentities.setItem( 0, 0, newItem) newItem = QtGui.QTableWidgetItem(address) - newItem.setIcon(identiconize(address)) newItem.setFlags( QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled) if shared.safeConfigGetBoolean(address, 'chan'): @@ -3166,6 +3121,8 @@ class settingsDialog(QtGui.QDialog): shared.config.getboolean('bitmessagesettings', 'startintray')) self.ui.checkBoxWillinglySendToMobile.setChecked( shared.safeConfigGetBoolean('bitmessagesettings', 'willinglysendtomobile')) + self.ui.checkBoxUseIdenticons.setChecked( + shared.safeConfigGetBoolean('bitmessagesettings', 'useidenticons')) global languages languages = ['system','en','eo','fr','de','es','ru','en_pirate','other'] @@ -3176,18 +3133,6 @@ class settingsDialog(QtGui.QDialog): curr_index = languages.index('other') self.ui.languageComboBox.setCurrentIndex(curr_index) - self.ui.comboBoxIdenticonStyle.addItem(_translate("settingsDialog", "None"), "none") - self.ui.comboBoxIdenticonStyle.addItem(QIcon(":/newPrefix/images/qidenticon.png"), _translate("settingsDialog", "QIdenticon"), "qidenticon") - self.ui.comboBoxIdenticonStyle.addItem(QIcon(":/newPrefix/images/qidenticon_x.png"), _translate("settingsDialog", "QIdenticon (transparent)"), "qidenticon_x") - self.ui.comboBoxIdenticonStyle.addItem(QIcon(":/newPrefix/images/qidenticon_two.png"), _translate("settingsDialog", "QIdenticon bicolored"), "qidenticon_two") - self.ui.comboBoxIdenticonStyle.addItem(QIcon(":/newPrefix/images/qidenticon_two_x.png"), _translate("settingsDialog", "QIdenticon bicolored (transparent)"), "qidenticon_two_x") - curr_index = self.ui.comboBoxIdenticonStyle.findData(str(shared.config.get('bitmessagesettings', 'identicon')), Qt.UserRole) - self.ui.comboBoxIdenticonStyle.setCurrentIndex(curr_index) - self.ui.lineEditIdenticonSuffix.setText( - str(shared.config.get('bitmessagesettings', 'identiconsuffix'))) - self.ui.checkBoxLoadAvatars.setChecked( - shared.safeConfigGetBoolean('bitmessagesettings', 'avatars')) - if shared.appdata == '': self.ui.checkBoxPortableMode.setChecked(True) if 'darwin' in sys.platform: @@ -3199,8 +3144,6 @@ class settingsDialog(QtGui.QDialog): elif 'linux' in sys.platform: self.ui.checkBoxStartOnLogon.setDisabled(True) self.ui.checkBoxMinimizeToTray.setDisabled(True) - self.ui.labelSettingsNote.setText(_translate( - "MainWindow", "Options have been disabled because they either aren\'t applicable or because they haven\'t yet been implemented for your operating system.")) # On the Network settings tab: self.ui.lineEditTCPPort.setText(str( shared.config.get('bitmessagesettings', 'port'))) diff --git a/src/bitmessageqt/settings.py b/src/bitmessageqt/settings.py index 205f7799..152ab0b7 100644 --- a/src/bitmessageqt/settings.py +++ b/src/bitmessageqt/settings.py @@ -2,8 +2,8 @@ # Form implementation generated from reading ui file 'settings.ui' # -# Created: Sat Sep 21 16:16:22 2013 -# by: PyQt4 UI code generator 4.10.2 +# Created: Fri Nov 1 19:14:45 2013 +# by: PyQt4 UI code generator 4.10 # # WARNING! All changes made in this file will be lost! @@ -26,7 +26,7 @@ except AttributeError: class Ui_settingsDialog(object): def setupUi(self, settingsDialog): settingsDialog.setObjectName(_fromUtf8("settingsDialog")) - settingsDialog.resize(600, 407) + settingsDialog.resize(646, 473) self.gridLayout = QtGui.QGridLayout(settingsDialog) self.gridLayout.setObjectName(_fromUtf8("gridLayout")) self.buttonBox = QtGui.QDialogButtonBox(settingsDialog) @@ -39,12 +39,43 @@ class Ui_settingsDialog(object): self.tabUserInterface = QtGui.QWidget() self.tabUserInterface.setEnabled(True) self.tabUserInterface.setObjectName(_fromUtf8("tabUserInterface")) - self.gridLayout_5 = QtGui.QGridLayout(self.tabUserInterface) - self.gridLayout_5.setObjectName(_fromUtf8("gridLayout_5")) + self.formLayout = QtGui.QFormLayout(self.tabUserInterface) + self.formLayout.setObjectName(_fromUtf8("formLayout")) + self.checkBoxStartOnLogon = QtGui.QCheckBox(self.tabUserInterface) + self.checkBoxStartOnLogon.setObjectName(_fromUtf8("checkBoxStartOnLogon")) + self.formLayout.setWidget(0, QtGui.QFormLayout.LabelRole, self.checkBoxStartOnLogon) + self.checkBoxStartInTray = QtGui.QCheckBox(self.tabUserInterface) + self.checkBoxStartInTray.setObjectName(_fromUtf8("checkBoxStartInTray")) + self.formLayout.setWidget(1, QtGui.QFormLayout.SpanningRole, self.checkBoxStartInTray) + self.checkBoxMinimizeToTray = QtGui.QCheckBox(self.tabUserInterface) + self.checkBoxMinimizeToTray.setChecked(True) + self.checkBoxMinimizeToTray.setObjectName(_fromUtf8("checkBoxMinimizeToTray")) + self.formLayout.setWidget(2, QtGui.QFormLayout.LabelRole, self.checkBoxMinimizeToTray) + self.checkBoxShowTrayNotifications = QtGui.QCheckBox(self.tabUserInterface) + self.checkBoxShowTrayNotifications.setObjectName(_fromUtf8("checkBoxShowTrayNotifications")) + self.formLayout.setWidget(3, QtGui.QFormLayout.LabelRole, self.checkBoxShowTrayNotifications) + self.checkBoxPortableMode = QtGui.QCheckBox(self.tabUserInterface) + self.checkBoxPortableMode.setObjectName(_fromUtf8("checkBoxPortableMode")) + self.formLayout.setWidget(4, QtGui.QFormLayout.LabelRole, self.checkBoxPortableMode) + self.PortableModeDescription = QtGui.QLabel(self.tabUserInterface) + sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.Minimum, QtGui.QSizePolicy.Fixed) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.PortableModeDescription.sizePolicy().hasHeightForWidth()) + self.PortableModeDescription.setSizePolicy(sizePolicy) + self.PortableModeDescription.setWordWrap(True) + self.PortableModeDescription.setObjectName(_fromUtf8("PortableModeDescription")) + self.formLayout.setWidget(5, QtGui.QFormLayout.SpanningRole, self.PortableModeDescription) + self.checkBoxWillinglySendToMobile = QtGui.QCheckBox(self.tabUserInterface) + self.checkBoxWillinglySendToMobile.setObjectName(_fromUtf8("checkBoxWillinglySendToMobile")) + self.formLayout.setWidget(6, QtGui.QFormLayout.SpanningRole, self.checkBoxWillinglySendToMobile) + self.checkBoxUseIdenticons = QtGui.QCheckBox(self.tabUserInterface) + self.checkBoxUseIdenticons.setObjectName(_fromUtf8("checkBoxUseIdenticons")) + self.formLayout.setWidget(7, QtGui.QFormLayout.LabelRole, self.checkBoxUseIdenticons) self.groupBox = QtGui.QGroupBox(self.tabUserInterface) self.groupBox.setObjectName(_fromUtf8("groupBox")) - self.horizontalLayout_2 = QtGui.QHBoxLayout(self.groupBox) - self.horizontalLayout_2.setObjectName(_fromUtf8("horizontalLayout_2")) + self.formLayout_2 = QtGui.QFormLayout(self.groupBox) + self.formLayout_2.setObjectName(_fromUtf8("formLayout_2")) self.languageComboBox = QtGui.QComboBox(self.groupBox) self.languageComboBox.setMinimumSize(QtCore.QSize(100, 0)) self.languageComboBox.setObjectName(_fromUtf8("languageComboBox")) @@ -57,63 +88,8 @@ class Ui_settingsDialog(object): self.languageComboBox.addItem(_fromUtf8("")) self.languageComboBox.addItem(_fromUtf8("")) self.languageComboBox.addItem(_fromUtf8("")) - self.horizontalLayout_2.addWidget(self.languageComboBox) - self.gridLayout_5.addWidget(self.groupBox, 7, 1, 4, 1) - self.checkBoxMinimizeToTray = QtGui.QCheckBox(self.tabUserInterface) - self.checkBoxMinimizeToTray.setChecked(True) - self.checkBoxMinimizeToTray.setObjectName(_fromUtf8("checkBoxMinimizeToTray")) - self.gridLayout_5.addWidget(self.checkBoxMinimizeToTray, 2, 0, 1, 1) - self.checkBoxStartOnLogon = QtGui.QCheckBox(self.tabUserInterface) - self.checkBoxStartOnLogon.setObjectName(_fromUtf8("checkBoxStartOnLogon")) - self.gridLayout_5.addWidget(self.checkBoxStartOnLogon, 0, 0, 1, 1) - self.checkBoxShowTrayNotifications = QtGui.QCheckBox(self.tabUserInterface) - self.checkBoxShowTrayNotifications.setObjectName(_fromUtf8("checkBoxShowTrayNotifications")) - self.gridLayout_5.addWidget(self.checkBoxShowTrayNotifications, 3, 0, 1, 1) - self.checkBoxPortableMode = QtGui.QCheckBox(self.tabUserInterface) - self.checkBoxPortableMode.setObjectName(_fromUtf8("checkBoxPortableMode")) - self.gridLayout_5.addWidget(self.checkBoxPortableMode, 4, 0, 1, 1) - self.checkBoxStartInTray = QtGui.QCheckBox(self.tabUserInterface) - self.checkBoxStartInTray.setObjectName(_fromUtf8("checkBoxStartInTray")) - self.gridLayout_5.addWidget(self.checkBoxStartInTray, 1, 0, 1, 1) - self.PortableModeDescription = QtGui.QLabel(self.tabUserInterface) - sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.Minimum, QtGui.QSizePolicy.Fixed) - sizePolicy.setHorizontalStretch(0) - sizePolicy.setVerticalStretch(0) - sizePolicy.setHeightForWidth(self.PortableModeDescription.sizePolicy().hasHeightForWidth()) - self.PortableModeDescription.setSizePolicy(sizePolicy) - self.PortableModeDescription.setWordWrap(True) - self.PortableModeDescription.setObjectName(_fromUtf8("PortableModeDescription")) - self.gridLayout_5.addWidget(self.PortableModeDescription, 5, 0, 1, 2) - self.checkBoxWillinglySendToMobile = QtGui.QCheckBox(self.tabUserInterface) - self.checkBoxWillinglySendToMobile.setObjectName(_fromUtf8("checkBoxWillinglySendToMobile")) - self.gridLayout_5.addWidget(self.checkBoxWillinglySendToMobile, 6, 0, 1, 2) - self.groupBox_3 = QtGui.QGroupBox(self.tabUserInterface) - self.groupBox_3.setAlignment(QtCore.Qt.AlignLeading|QtCore.Qt.AlignLeft|QtCore.Qt.AlignTop) - self.groupBox_3.setFlat(False) - self.groupBox_3.setCheckable(False) - self.groupBox_3.setObjectName(_fromUtf8("groupBox_3")) - self.gridLayout_9 = QtGui.QGridLayout(self.groupBox_3) - self.gridLayout_9.setObjectName(_fromUtf8("gridLayout_9")) - self.checkBoxLoadAvatars = QtGui.QCheckBox(self.groupBox_3) - self.checkBoxLoadAvatars.setObjectName(_fromUtf8("checkBoxLoadAvatars")) - self.gridLayout_9.addWidget(self.checkBoxLoadAvatars, 1, 0, 1, 1) - self.lineEditIdenticonSuffix = QtGui.QLineEdit(self.groupBox_3) - self.lineEditIdenticonSuffix.setObjectName(_fromUtf8("lineEditIdenticonSuffix")) - self.gridLayout_9.addWidget(self.lineEditIdenticonSuffix, 1, 1, 1, 1) - self.comboBoxIdenticonStyle = QtGui.QComboBox(self.groupBox_3) - sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Expanding) - sizePolicy.setHorizontalStretch(0) - sizePolicy.setVerticalStretch(0) - sizePolicy.setHeightForWidth(self.comboBoxIdenticonStyle.sizePolicy().hasHeightForWidth()) - self.comboBoxIdenticonStyle.setSizePolicy(sizePolicy) - self.comboBoxIdenticonStyle.setIconSize(QtCore.QSize(24, 24)) - self.comboBoxIdenticonStyle.setObjectName(_fromUtf8("comboBoxIdenticonStyle")) - self.gridLayout_9.addWidget(self.comboBoxIdenticonStyle, 0, 0, 1, 2) - self.gridLayout_5.addWidget(self.groupBox_3, 7, 0, 5, 1) - spacerItem = QtGui.QSpacerItem(20, 40, QtGui.QSizePolicy.Minimum, QtGui.QSizePolicy.Expanding) - self.gridLayout_5.addItem(spacerItem, 12, 0, 1, 1) - spacerItem1 = QtGui.QSpacerItem(20, 40, QtGui.QSizePolicy.Minimum, QtGui.QSizePolicy.Expanding) - self.gridLayout_5.addItem(spacerItem1, 11, 1, 2, 1) + self.formLayout_2.setWidget(0, QtGui.QFormLayout.LabelRole, self.languageComboBox) + self.formLayout.setWidget(8, QtGui.QFormLayout.FieldRole, self.groupBox) self.tabWidgetSettings.addTab(self.tabUserInterface, _fromUtf8("")) self.tabNetworkSettings = QtGui.QWidget() self.tabNetworkSettings.setObjectName(_fromUtf8("tabNetworkSettings")) @@ -123,8 +99,8 @@ class Ui_settingsDialog(object): self.groupBox1.setObjectName(_fromUtf8("groupBox1")) self.gridLayout_3 = QtGui.QGridLayout(self.groupBox1) self.gridLayout_3.setObjectName(_fromUtf8("gridLayout_3")) - spacerItem2 = QtGui.QSpacerItem(125, 20, QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Minimum) - self.gridLayout_3.addItem(spacerItem2, 0, 0, 1, 1) + spacerItem = QtGui.QSpacerItem(125, 20, QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Minimum) + self.gridLayout_3.addItem(spacerItem, 0, 0, 1, 1) self.label = QtGui.QLabel(self.groupBox1) self.label.setObjectName(_fromUtf8("label")) self.gridLayout_3.addWidget(self.label, 0, 1, 1, 1) @@ -181,8 +157,8 @@ class Ui_settingsDialog(object): self.comboBoxProxyType.addItem(_fromUtf8("")) self.gridLayout_2.addWidget(self.comboBoxProxyType, 0, 1, 1, 1) self.gridLayout_4.addWidget(self.groupBox_2, 1, 0, 1, 1) - spacerItem3 = QtGui.QSpacerItem(20, 70, QtGui.QSizePolicy.Minimum, QtGui.QSizePolicy.Expanding) - self.gridLayout_4.addItem(spacerItem3, 2, 0, 1, 1) + spacerItem1 = QtGui.QSpacerItem(20, 70, QtGui.QSizePolicy.Minimum, QtGui.QSizePolicy.Expanding) + self.gridLayout_4.addItem(spacerItem1, 2, 0, 1, 1) self.tabWidgetSettings.addTab(self.tabNetworkSettings, _fromUtf8("")) self.tab = QtGui.QWidget() self.tab.setObjectName(_fromUtf8("tab")) @@ -192,8 +168,8 @@ class Ui_settingsDialog(object): self.label_8.setWordWrap(True) self.label_8.setObjectName(_fromUtf8("label_8")) self.gridLayout_6.addWidget(self.label_8, 0, 0, 1, 3) - spacerItem4 = QtGui.QSpacerItem(203, 20, QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Minimum) - self.gridLayout_6.addItem(spacerItem4, 1, 0, 1, 1) + spacerItem2 = QtGui.QSpacerItem(203, 20, QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Minimum) + self.gridLayout_6.addItem(spacerItem2, 1, 0, 1, 1) self.label_9 = QtGui.QLabel(self.tab) self.label_9.setAlignment(QtCore.Qt.AlignRight|QtCore.Qt.AlignTrailing|QtCore.Qt.AlignVCenter) self.label_9.setObjectName(_fromUtf8("label_9")) @@ -207,8 +183,8 @@ class Ui_settingsDialog(object): self.lineEditTotalDifficulty.setMaximumSize(QtCore.QSize(70, 16777215)) self.lineEditTotalDifficulty.setObjectName(_fromUtf8("lineEditTotalDifficulty")) self.gridLayout_6.addWidget(self.lineEditTotalDifficulty, 1, 2, 1, 1) - spacerItem5 = QtGui.QSpacerItem(203, 20, QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Minimum) - self.gridLayout_6.addItem(spacerItem5, 3, 0, 1, 1) + spacerItem3 = QtGui.QSpacerItem(203, 20, QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Minimum) + self.gridLayout_6.addItem(spacerItem3, 3, 0, 1, 1) self.label_11 = QtGui.QLabel(self.tab) self.label_11.setAlignment(QtCore.Qt.AlignRight|QtCore.Qt.AlignTrailing|QtCore.Qt.AlignVCenter) self.label_11.setObjectName(_fromUtf8("label_11")) @@ -239,8 +215,8 @@ class Ui_settingsDialog(object): self.label_15.setWordWrap(True) self.label_15.setObjectName(_fromUtf8("label_15")) self.gridLayout_7.addWidget(self.label_15, 0, 0, 1, 3) - spacerItem6 = QtGui.QSpacerItem(102, 20, QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Minimum) - self.gridLayout_7.addItem(spacerItem6, 1, 0, 1, 1) + spacerItem4 = QtGui.QSpacerItem(102, 20, QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Minimum) + self.gridLayout_7.addItem(spacerItem4, 1, 0, 1, 1) self.label_13 = QtGui.QLabel(self.tab_2) self.label_13.setLayoutDirection(QtCore.Qt.LeftToRight) self.label_13.setAlignment(QtCore.Qt.AlignRight|QtCore.Qt.AlignTrailing|QtCore.Qt.AlignVCenter) @@ -255,8 +231,8 @@ class Ui_settingsDialog(object): self.lineEditMaxAcceptableTotalDifficulty.setMaximumSize(QtCore.QSize(70, 16777215)) self.lineEditMaxAcceptableTotalDifficulty.setObjectName(_fromUtf8("lineEditMaxAcceptableTotalDifficulty")) self.gridLayout_7.addWidget(self.lineEditMaxAcceptableTotalDifficulty, 1, 2, 1, 1) - spacerItem7 = QtGui.QSpacerItem(102, 20, QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Minimum) - self.gridLayout_7.addItem(spacerItem7, 2, 0, 1, 1) + spacerItem5 = QtGui.QSpacerItem(102, 20, QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Minimum) + self.gridLayout_7.addItem(spacerItem5, 2, 0, 1, 1) self.label_14 = QtGui.QLabel(self.tab_2) self.label_14.setAlignment(QtCore.Qt.AlignRight|QtCore.Qt.AlignTrailing|QtCore.Qt.AlignVCenter) self.label_14.setObjectName(_fromUtf8("label_14")) @@ -270,15 +246,15 @@ class Ui_settingsDialog(object): self.lineEditMaxAcceptableSmallMessageDifficulty.setMaximumSize(QtCore.QSize(70, 16777215)) self.lineEditMaxAcceptableSmallMessageDifficulty.setObjectName(_fromUtf8("lineEditMaxAcceptableSmallMessageDifficulty")) self.gridLayout_7.addWidget(self.lineEditMaxAcceptableSmallMessageDifficulty, 2, 2, 1, 1) - spacerItem8 = QtGui.QSpacerItem(20, 147, QtGui.QSizePolicy.Minimum, QtGui.QSizePolicy.Expanding) - self.gridLayout_7.addItem(spacerItem8, 3, 1, 1, 1) + spacerItem6 = QtGui.QSpacerItem(20, 147, QtGui.QSizePolicy.Minimum, QtGui.QSizePolicy.Expanding) + self.gridLayout_7.addItem(spacerItem6, 3, 1, 1, 1) self.tabWidgetSettings.addTab(self.tab_2, _fromUtf8("")) self.tabNamecoin = QtGui.QWidget() self.tabNamecoin.setObjectName(_fromUtf8("tabNamecoin")) self.gridLayout_8 = QtGui.QGridLayout(self.tabNamecoin) self.gridLayout_8.setObjectName(_fromUtf8("gridLayout_8")) - spacerItem9 = QtGui.QSpacerItem(40, 20, QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Minimum) - self.gridLayout_8.addItem(spacerItem9, 2, 0, 1, 1) + spacerItem7 = QtGui.QSpacerItem(40, 20, QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Minimum) + self.gridLayout_8.addItem(spacerItem7, 2, 0, 1, 1) self.label_16 = QtGui.QLabel(self.tabNamecoin) self.label_16.setWordWrap(True) self.label_16.setObjectName(_fromUtf8("label_16")) @@ -290,10 +266,10 @@ class Ui_settingsDialog(object): self.lineEditNamecoinHost = QtGui.QLineEdit(self.tabNamecoin) self.lineEditNamecoinHost.setObjectName(_fromUtf8("lineEditNamecoinHost")) self.gridLayout_8.addWidget(self.lineEditNamecoinHost, 2, 2, 1, 1) - spacerItem10 = QtGui.QSpacerItem(40, 20, QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Minimum) - self.gridLayout_8.addItem(spacerItem10, 3, 0, 1, 1) - spacerItem11 = QtGui.QSpacerItem(40, 20, QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Minimum) - self.gridLayout_8.addItem(spacerItem11, 4, 0, 1, 1) + spacerItem8 = QtGui.QSpacerItem(40, 20, QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Minimum) + self.gridLayout_8.addItem(spacerItem8, 3, 0, 1, 1) + spacerItem9 = QtGui.QSpacerItem(40, 20, QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Minimum) + self.gridLayout_8.addItem(spacerItem9, 4, 0, 1, 1) self.label_18 = QtGui.QLabel(self.tabNamecoin) self.label_18.setEnabled(True) self.label_18.setAlignment(QtCore.Qt.AlignRight|QtCore.Qt.AlignTrailing|QtCore.Qt.AlignVCenter) @@ -302,8 +278,8 @@ class Ui_settingsDialog(object): self.lineEditNamecoinPort = QtGui.QLineEdit(self.tabNamecoin) self.lineEditNamecoinPort.setObjectName(_fromUtf8("lineEditNamecoinPort")) self.gridLayout_8.addWidget(self.lineEditNamecoinPort, 3, 2, 1, 1) - spacerItem12 = QtGui.QSpacerItem(20, 40, QtGui.QSizePolicy.Minimum, QtGui.QSizePolicy.Expanding) - self.gridLayout_8.addItem(spacerItem12, 8, 1, 1, 1) + spacerItem10 = QtGui.QSpacerItem(20, 40, QtGui.QSizePolicy.Minimum, QtGui.QSizePolicy.Expanding) + self.gridLayout_8.addItem(spacerItem10, 8, 1, 1, 1) self.labelNamecoinUser = QtGui.QLabel(self.tabNamecoin) self.labelNamecoinUser.setAlignment(QtCore.Qt.AlignRight|QtCore.Qt.AlignTrailing|QtCore.Qt.AlignVCenter) self.labelNamecoinUser.setObjectName(_fromUtf8("labelNamecoinUser")) @@ -311,8 +287,8 @@ class Ui_settingsDialog(object): self.lineEditNamecoinUser = QtGui.QLineEdit(self.tabNamecoin) self.lineEditNamecoinUser.setObjectName(_fromUtf8("lineEditNamecoinUser")) self.gridLayout_8.addWidget(self.lineEditNamecoinUser, 4, 2, 1, 1) - spacerItem13 = QtGui.QSpacerItem(40, 20, QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Minimum) - self.gridLayout_8.addItem(spacerItem13, 5, 0, 1, 1) + spacerItem11 = QtGui.QSpacerItem(40, 20, QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Minimum) + self.gridLayout_8.addItem(spacerItem11, 5, 0, 1, 1) self.labelNamecoinPassword = QtGui.QLabel(self.tabNamecoin) self.labelNamecoinPassword.setAlignment(QtCore.Qt.AlignRight|QtCore.Qt.AlignTrailing|QtCore.Qt.AlignVCenter) self.labelNamecoinPassword.setObjectName(_fromUtf8("labelNamecoinPassword")) @@ -354,8 +330,7 @@ class Ui_settingsDialog(object): settingsDialog.setTabOrder(self.tabWidgetSettings, self.checkBoxStartOnLogon) settingsDialog.setTabOrder(self.checkBoxStartOnLogon, self.checkBoxStartInTray) settingsDialog.setTabOrder(self.checkBoxStartInTray, self.checkBoxMinimizeToTray) - settingsDialog.setTabOrder(self.checkBoxMinimizeToTray, self.checkBoxShowTrayNotifications) - settingsDialog.setTabOrder(self.checkBoxShowTrayNotifications, self.lineEditTCPPort) + settingsDialog.setTabOrder(self.checkBoxMinimizeToTray, self.lineEditTCPPort) settingsDialog.setTabOrder(self.lineEditTCPPort, self.comboBoxProxyType) settingsDialog.setTabOrder(self.comboBoxProxyType, self.lineEditSocksHostname) settingsDialog.setTabOrder(self.lineEditSocksHostname, self.lineEditSocksPort) @@ -367,6 +342,14 @@ class Ui_settingsDialog(object): def retranslateUi(self, settingsDialog): settingsDialog.setWindowTitle(_translate("settingsDialog", "Settings", None)) + self.checkBoxStartOnLogon.setText(_translate("settingsDialog", "Start Bitmessage on user login", None)) + self.checkBoxStartInTray.setText(_translate("settingsDialog", "Start Bitmessage in the tray (don\'t show main window)", None)) + self.checkBoxMinimizeToTray.setText(_translate("settingsDialog", "Minimize to tray", None)) + self.checkBoxShowTrayNotifications.setText(_translate("settingsDialog", "Show notification when message received", None)) + self.checkBoxPortableMode.setText(_translate("settingsDialog", "Run in Portable Mode", None)) + self.PortableModeDescription.setText(_translate("settingsDialog", "In Portable Mode, messages and config files are stored in the same directory as the program rather than the normal application-data folder. This makes it convenient to run Bitmessage from a USB thumb drive.", None)) + self.checkBoxWillinglySendToMobile.setText(_translate("settingsDialog", "Willingly include unencrypted destination address when sending to a mobile device", None)) + self.checkBoxUseIdenticons.setText(_translate("settingsDialog", "Use Identicons", None)) self.groupBox.setTitle(_translate("settingsDialog", "Interface Language", None)) self.languageComboBox.setItemText(0, _translate("settingsDialog", "System Settings", "system")) self.languageComboBox.setItemText(1, _translate("settingsDialog", "English", "en")) @@ -377,16 +360,6 @@ class Ui_settingsDialog(object): self.languageComboBox.setItemText(6, _translate("settingsDialog", "русский язык", "ru")) self.languageComboBox.setItemText(7, _translate("settingsDialog", "Pirate English", "en_pirate")) self.languageComboBox.setItemText(8, _translate("settingsDialog", "Other (set in keys.dat)", "other")) - self.checkBoxMinimizeToTray.setText(_translate("settingsDialog", "Minimize to tray", None)) - self.checkBoxStartOnLogon.setText(_translate("settingsDialog", "Start Bitmessage on user login", None)) - self.checkBoxShowTrayNotifications.setText(_translate("settingsDialog", "Show notification when message received", None)) - self.checkBoxPortableMode.setText(_translate("settingsDialog", "Run in Portable Mode", None)) - self.checkBoxStartInTray.setText(_translate("settingsDialog", "Start Bitmessage in the tray (don\'t show main window)", None)) - self.PortableModeDescription.setText(_translate("settingsDialog", "In Portable Mode, messages and config files are stored in the same directory as the program rather than the normal application-data folder. This makes it convenient to run Bitmessage from a USB thumb drive.", None)) - self.checkBoxWillinglySendToMobile.setText(_translate("settingsDialog", "Willingly include unencrypted destination address when sending to a mobile device", None)) - self.groupBox_3.setTitle(_translate("settingsDialog", "Identicons (with example image)", None)) - self.checkBoxLoadAvatars.setText(_translate("settingsDialog", "Load avatar images", None)) - self.lineEditIdenticonSuffix.setToolTip(_translate("settingsDialog", "

The content of this text field will be appended to the BM-address before creating the hash for the identicons. By default it is filled with a random string to make the identicons in your client unique, otherwise the identicon could be an attack vector if an adversary creates an address resulting in a similar identicon. If you keep this string (or any other random or non-random string) you will be able to keep the same identicons.

", None)) self.tabWidgetSettings.setTabText(self.tabWidgetSettings.indexOf(self.tabUserInterface), _translate("settingsDialog", "User Interface", None)) self.groupBox1.setTitle(_translate("settingsDialog", "Listening port", None)) self.label.setText(_translate("settingsDialog", "Listen for connections on port:", None)) @@ -424,13 +397,3 @@ class Ui_settingsDialog(object): self.tabWidgetSettings.setTabText(self.tabWidgetSettings.indexOf(self.tabNamecoin), _translate("settingsDialog", "Namecoin integration", None)) import bitmessage_icons_rc - -if __name__ == "__main__": - import sys - app = QtGui.QApplication(sys.argv) - settingsDialog = QtGui.QDialog() - ui = Ui_settingsDialog() - ui.setupUi(settingsDialog) - settingsDialog.show() - sys.exit(app.exec_()) - diff --git a/src/bitmessageqt/settings.ui b/src/bitmessageqt/settings.ui index cce28f96..b7d7ae22 100644 --- a/src/bitmessageqt/settings.ui +++ b/src/bitmessageqt/settings.ui @@ -6,8 +6,8 @@ 0 0 - 600 - 407 + 646 + 473
@@ -36,14 +36,82 @@ User Interface - - + + + + + Start Bitmessage on user login + + + + + + + Start Bitmessage in the tray (don't show main window) + + + + + + + Minimize to tray + + + true + + + + + + + Show notification when message received + + + + + + + Run in Portable Mode + + + + + + + + 0 + 0 + + + + In Portable Mode, messages and config files are stored in the same directory as the program rather than the normal application-data folder. This makes it convenient to run Bitmessage from a USB thumb drive. + + + true + + + + + + + Willingly include unencrypted destination address when sending to a mobile device + + + + + + + Use Identicons + + + + Interface Language - - + + @@ -101,141 +169,6 @@ - - - - Minimize to tray - - - true - - - - - - - Start Bitmessage on user login - - - - - - - Show notification when message received - - - - - - - Run in Portable Mode - - - - - - - Start Bitmessage in the tray (don't show main window) - - - - - - - - 0 - 0 - - - - In Portable Mode, messages and config files are stored in the same directory as the program rather than the normal application-data folder. This makes it convenient to run Bitmessage from a USB thumb drive. - - - true - - - - - - - Willingly include unencrypted destination address when sending to a mobile device - - - - - - - Identicons (with example image) - - - Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop - - - false - - - false - - - - - - Load avatar images - - - - - - - <html><head/><body><p>The content of this text field will be appended to the BM-address before creating the hash for the identicons. By default it is filled with a random string to make the identicons in your client unique, otherwise the identicon could be an attack vector if an adversary creates an address resulting in a similar identicon. If you keep this string (or any other random or non-random string) you will be able to keep the same identicons.</p></body></html> - - - - - - - - 0 - 0 - - - - - 24 - 24 - - - - - - - - - - - Qt::Vertical - - - - 20 - 40 - - - - - - - - Qt::Vertical - - - - 20 - 40 - - - -
@@ -818,7 +751,6 @@ checkBoxStartOnLogon checkBoxStartInTray checkBoxMinimizeToTray - checkBoxShowTrayNotifications lineEditTCPPort comboBoxProxyType lineEditSocksHostname diff --git a/src/class_sqlThread.py b/src/class_sqlThread.py index e09385fd..24679acb 100644 --- a/src/class_sqlThread.py +++ b/src/class_sqlThread.py @@ -7,6 +7,8 @@ import sys import os from debug import logger from namecoin import ensureNamecoinOptions +import random +import string import tr#anslate # This thread exists because SQLITE3 is so un-threadsafe that we must @@ -242,13 +244,6 @@ class sqlThread(threading.Thread): if not shared.config.has_option('bitmessagesettings', 'userlocale'): shared.config.set('bitmessagesettings', 'userlocale', 'system') - if not shared.config.has_option('bitmessagesettings', 'identicon'): - shared.config.set('bitmessagesettings', 'identicon', 'None') - if not shared.config.has_option('bitmessagesettings', 'identiconsuffix'): - import random, string - shared.config.set('bitmessagesettings', 'identiconsuffix', ''.join(random.choice("123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz") for x in range(12))) # a twelve character pseudo-password to salt the identicons - if not shared.config.has_option('bitmessagesettings', 'avatars'): - shared.config.set('bitmessagesettings', 'avatars', 'false') if not shared.config.has_option('bitmessagesettings', 'sendoutgoingconnections'): shared.config.set('bitmessagesettings', 'sendoutgoingconnections', 'True') @@ -275,6 +270,13 @@ class sqlThread(threading.Thread): item = '''update settings set value=? WHERE key='version';''' parameters = (5,) self.cur.execute(item, parameters) + + if not shared.config.has_option('bitmessagesettings', 'useidenticons'): + shared.config.set('bitmessagesettings', 'useidenticons', 'True') + if not shared.config.has_option('bitmessagesettings', 'identiconsuffix'): # acts as a salt + shared.config.set('bitmessagesettings', 'identiconsuffix', ''.join(random.choice("123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz") for x in range(12))) # a twelve character pseudo-password to salt the identicons + with open(shared.appdata + 'keys.dat', 'wb') as configfile: + shared.config.write(configfile) # Are you hoping to add a new option to the keys.dat file of existing # Bitmessage users? Add it right above this line! diff --git a/src/helper_startup.py b/src/helper_startup.py index 17aafd55..164348eb 100644 --- a/src/helper_startup.py +++ b/src/helper_startup.py @@ -3,6 +3,8 @@ import ConfigParser import sys import os import locale +import random +import string from namecoin import ensureNamecoinOptions @@ -84,10 +86,8 @@ def loadConfig(): 'bitmessagesettings', 'maxacceptablepayloadlengthextrabytes', '0') shared.config.set('bitmessagesettings', 'dontconnect', 'true') shared.config.set('bitmessagesettings', 'userlocale', 'system') - shared.config.set('bitmessagesettings', 'identicon', 'None') - import random, string + shared.config.set('bitmessagesettings', 'useidenticons', 'True') shared.config.set('bitmessagesettings', 'identiconsuffix', ''.join(random.choice("123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz") for x in range(12))) # a twelve character pseudo-password to salt the identicons - shared.config.set('bitmessagesettings', 'avatars', 'false') # Are you hoping to add a new option to the keys.dat file? You're in # the right place for adding it to users who install the software for From a9898feab8146917d3647fb3ec657b093c111c49 Mon Sep 17 00:00:00 2001 From: Jonathan Warren Date: Fri, 1 Nov 2013 19:28:44 -0400 Subject: [PATCH 34/48] removed images/can-icon-24px_2.png --- src/images/can-icon-24px_2.png | Bin 1852 -> 0 bytes 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 src/images/can-icon-24px_2.png diff --git a/src/images/can-icon-24px_2.png b/src/images/can-icon-24px_2.png deleted file mode 100644 index 30f7313eb378b69c4e2f6cb66264c2ae678059e1..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1852 zcmV-C2gCS@P)M^!E?E!Z`=4*Xy`ht_>fhjLl+eV_hy>=-Ek(A(EP8wh@6>5>i{Jah=UrfpOJpyyEE@4x%__we9`O^8Gz zY+YB^0D>Su5CjlH;N;0u?*~Bu&N&bS0ppzGa8EB4rY4DVPDBKSF-HCU16P-HEJ7j{ zNB_V8#>OVLvurL0*LBHtT@Xo}Jb4<4WQ?)(U)V^qv$OC6A8)<&J1DB6+N!HAqgS40M1-^F1`!DUX8C&gnRGe@ zugOKk3q#X23}v$zA1@%4ZpGc13}Zxu2iAXy3X>BMLSomhm+{n7Pk?idful#^*Ezyr z?^5Ynsk5uAtC7-^+n#tfol2v%r3J6O`Z`vx?m{NB3ZCb|_3ZsV_dC<+bR%^l!h!ug z@VU>%#>bmZoH+5fD_6!g$w1(jJD&Yfx-GS5)rys1j5Ug#pWXb-IOmw2o?hS6(|2Sf zpMOr0oWEy8bh}FcG{zVdMbZCv^7OGpGT!ZY?yDjYR4SFe34j4u5I6t=RaNovCkHF@ zoiaipD2jq)GP(ESrQv7p6jU1RJ^w~D8qrNF1lzV@nHGW|z_EWH$MA4|HvlM%F)+q3 zSF1lXI(qr%k!S>tSt5A%%qNy7fY#@OJ=EhAw1( z_N}dtZ{57*k%%G? zyxTA|2q7B+k`g<9wZry251!{C8jT8 z-p;x=cW;j;6M^e_Sar{;s;=ulO|_=rc^-)9w)un*3;rOYd80A9^_8lsC>CeFcKY<0 z4{lZ}Ry-a%wttgbW>)Wx=%_7={5wQBW?I>Bfy4Z=_PG=NJe~Q9lP@w~J_0!8!Mi z9zFhP-S^kT<8fHF3CFQv7zRw!gk#&#H626*-}f;&HHG}>D5{mY;+HpXy5F+Q%Xe-M z0U*X0G)+UPbnTIkKRHlVHEm6MdpjbL2t3b)X_;^w2d-^HS2Yk3s?{on^CK7@$wLGZ zp64ZohO$2d01W`9rl;?qQ0VgL*oRY7S08R~Z--+$Fw77_rVcY?z%ng_LLsP%0zrgw zxr|)yA}WL9CMO3t=b?@zi(yy>Of!T~C+VOB*94LhM2&|}VG20s7@sU4HnuD?>4oOphsFb>PI zZUf%`%U?g-yLaC=7-Oi_YVf%apL0|y71Zl>Y~1)DcJ6!;YuDTZB0_HPA}(DTUI=X2 zCW^)4PlABI9SVie+1UxpvTpCezI`9Hzx&R+{}~^jXqkryAs~c+loA9&&G%88o5S~i zunk+bJl6RAN16Z-N~LQLgR%WmN-SO4fpAk3q?Bk*BpZN(hk5|U(0jP|^`65=ej%iQ zs;ZDuEGZ;^06r_>FlqtwW!jai!%a;Br5)J=X qQ5iHQ#>b From a4b5ded803ca42ba88de9de52ed084852fc8f9dc Mon Sep 17 00:00:00 2001 From: Jonathan Warren Date: Sat, 2 Nov 2013 00:02:46 -0400 Subject: [PATCH 35/48] we no longer need to add chans to our address book --- src/bitmessagemain.py | 21 +-------------------- src/bitmessageqt/__init__.py | 5 ++--- 2 files changed, 3 insertions(+), 23 deletions(-) diff --git a/src/bitmessagemain.py b/src/bitmessagemain.py index 3900b99d..c05b724f 100755 --- a/src/bitmessagemain.py +++ b/src/bitmessagemain.py @@ -415,14 +415,6 @@ class MySimpleXMLRPCRequestHandler(SimpleXMLRPCRequestHandler): if len(queueReturn) == 0: raise APIError(24, 'Chan address is already present.') address = queueReturn[0] - - #add address to addressbook - queryreturn = sqlQuery("SELECT address FROM addressbook WHERE address=?", address) - if queryreturn == []: - sqlExecute("INSERT INTO addressbook VALUES(?,?)", label, address) - shared.UISignalQueue.put(('rerenderInboxFromLabels','')) - shared.UISignalQueue.put(('rerenderSentToLabels','')) - shared.UISignalQueue.put(('rerenderAddressBook','')) return address elif method == 'joinChan': if len(params) < 2: @@ -450,14 +442,7 @@ class MySimpleXMLRPCRequestHandler(SimpleXMLRPCRequestHandler): raise APIError(18, 'Chan name does not match address.') if len(addressGeneratorReturnValue) == 0: raise APIError(24, 'Chan address is already present.') - createdAddress = addressGeneratorReturnValue[0] - #add address to addressbook - queryreturn = sqlQuery("SELECT address FROM addressbook WHERE address=?", createdAddress) - if queryreturn == []: - sqlExecute("INSERT INTO addressbook VALUES(?,?)", label, createdAddress) - shared.UISignalQueue.put(('rerenderInboxFromLabels','')) - shared.UISignalQueue.put(('rerenderSentToLabels','')) - shared.UISignalQueue.put(('rerenderAddressBook','')) + createdAddress = addressGeneratorReturnValue[0] # in case we ever want it for anything. return "success" elif method == 'leaveChan': if len(params) == 0: @@ -473,10 +458,6 @@ class MySimpleXMLRPCRequestHandler(SimpleXMLRPCRequestHandler): shared.config.remove_section(address) with open(shared.appdata + 'keys.dat', 'wb') as configfile: shared.config.write(configfile) - sqlExecute('''DELETE FROM addressbook WHERE address=?''', address) - shared.UISignalQueue.put(('rerenderInboxFromLabels','')) - shared.UISignalQueue.put(('rerenderSentToLabels','')) - shared.UISignalQueue.put(('rerenderAddressBook','')) return 'success' elif method == 'deleteAddress': diff --git a/src/bitmessageqt/__init__.py b/src/bitmessageqt/__init__.py index 910f73ff..bc2d80d5 100644 --- a/src/bitmessageqt/__init__.py +++ b/src/bitmessageqt/__init__.py @@ -1297,7 +1297,6 @@ class MyForm(QtGui.QMainWindow): "MainWindow", "Could not add chan because it appears to already be one of your identities.")) return createdAddress = addressGeneratorReturnValue[0] - self.addEntryToAddressBook(createdAddress, self.str_chan + ' ' + str(self.newChanDialogInstance.ui.lineEditChanNameCreate.text().toUtf8())) QMessageBox.about(self, _translate("MainWindow", "Success"), _translate( "MainWindow", "Successfully created chan. To let others join your chan, give them the chan name and this Bitmessage address: %1. This address also appears in 'Your Identities'.").arg(createdAddress)) self.ui.tabWidget.setCurrentIndex(3) @@ -1327,7 +1326,6 @@ class MyForm(QtGui.QMainWindow): "MainWindow", "Could not add chan because it appears to already be one of your identities.")) return createdAddress = addressGeneratorReturnValue[0] - self.addEntryToAddressBook(createdAddress, self.str_chan + ' ' + str(self.newChanDialogInstance.ui.lineEditChanNameJoin.text())) QMessageBox.about(self, _translate("MainWindow", "Success"), _translate( "MainWindow", "Successfully joined chan. ")) self.ui.tabWidget.setCurrentIndex(3) @@ -1637,7 +1635,8 @@ class MyForm(QtGui.QMainWindow): # Message might be to an address we own like a chan address. Let's look for that label. if shared.config.has_section(addressToLookup): toLabel = shared.config.get(addressToLookup, 'label') - toLabel = addressToLookup + if toLabel == '': + toLabel = addressToLookup self.ui.tableWidgetSent.item( i, 0).setText(unicode(toLabel, 'utf-8')) From f4fd5fd5bd7b5aa7c22e8412bec99ddb65ea50a1 Mon Sep 17 00:00:00 2001 From: Jonathan Warren Date: Sat, 2 Nov 2013 00:19:54 -0400 Subject: [PATCH 36/48] Improve verbage in UI --- src/bitmessageqt/__init__.py | 12 ++++++++++-- src/class_sqlThread.py | 1 + 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/src/bitmessageqt/__init__.py b/src/bitmessageqt/__init__.py index bc2d80d5..6331bbb6 100644 --- a/src/bitmessageqt/__init__.py +++ b/src/bitmessageqt/__init__.py @@ -3136,13 +3136,21 @@ class settingsDialog(QtGui.QDialog): self.ui.checkBoxPortableMode.setChecked(True) if 'darwin' in sys.platform: self.ui.checkBoxStartOnLogon.setDisabled(True) + self.ui.checkBoxStartOnLogon.setText(_translate( + "MainWindow", "Start-on-login not yet supported on your OS.")) self.ui.checkBoxMinimizeToTray.setDisabled(True) + self.ui.checkBoxMinimizeToTray.setText(_translate( + "MainWindow", "Minimize-to-tray not yet supported on your OS.")) self.ui.checkBoxShowTrayNotifications.setDisabled(True) - self.ui.labelSettingsNote.setText(_translate( - "MainWindow", "Options have been disabled because they either aren\'t applicable or because they haven\'t yet been implemented for your operating system.")) + self.ui.checkBoxShowTrayNotifications.setText(_translate( + "MainWindow", "Tray notifications not yet supported on your OS.")) elif 'linux' in sys.platform: self.ui.checkBoxStartOnLogon.setDisabled(True) + self.ui.checkBoxStartOnLogon.setText(_translate( + "MainWindow", "Start-on-login not yet supported on your OS.")) self.ui.checkBoxMinimizeToTray.setDisabled(True) + self.ui.checkBoxMinimizeToTray.setText(_translate( + "MainWindow", "Minimize-to-tray not yet supported on your OS.")) # On the Network settings tab: self.ui.lineEditTCPPort.setText(str( shared.config.get('bitmessagesettings', 'port'))) diff --git a/src/class_sqlThread.py b/src/class_sqlThread.py index 24679acb..7e1349d4 100644 --- a/src/class_sqlThread.py +++ b/src/class_sqlThread.py @@ -275,6 +275,7 @@ class sqlThread(threading.Thread): shared.config.set('bitmessagesettings', 'useidenticons', 'True') if not shared.config.has_option('bitmessagesettings', 'identiconsuffix'): # acts as a salt shared.config.set('bitmessagesettings', 'identiconsuffix', ''.join(random.choice("123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz") for x in range(12))) # a twelve character pseudo-password to salt the identicons + # Since we've added a new config entry, let's write keys.dat to disk. with open(shared.appdata + 'keys.dat', 'wb') as configfile: shared.config.write(configfile) From 162114ab28d84c9dc75a38da55aea976e9f9b1df Mon Sep 17 00:00:00 2001 From: Jonathan Warren Date: Sat, 2 Nov 2013 17:34:46 -0400 Subject: [PATCH 37/48] load Sent To label from subscriptions if available --- src/bitmessageqt/__init__.py | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/src/bitmessageqt/__init__.py b/src/bitmessageqt/__init__.py index bc2d80d5..3412b828 100644 --- a/src/bitmessageqt/__init__.py +++ b/src/bitmessageqt/__init__.py @@ -706,6 +706,15 @@ class MyForm(QtGui.QMainWindow): if queryreturn != []: for row in queryreturn: toLabel, = row + if toLabel == '': + # It might be a broadcast message. We should check for that + # label. + queryreturn = sqlQuery( + '''select label from subscriptions where address=?''', toAddress) + + if queryreturn != []: + for row in queryreturn: + toLabel, = row if toLabel == '': if shared.config.has_section(toAddress): @@ -831,7 +840,7 @@ class MyForm(QtGui.QMainWindow): if shared.config.has_section(fromAddress): fromLabel = shared.config.get(fromAddress, 'label') - if fromLabel == '': # If the fromAddress isn't one of our addresses + if fromLabel == '': # If the fromAddress isn't one of our addresses and isn't a chan queryreturn = sqlQuery( '''select label from addressbook where address=?''', fromAddress) if queryreturn != []: @@ -839,7 +848,7 @@ class MyForm(QtGui.QMainWindow): fromLabel, = row if fromLabel == '': # If this address wasn't in our address book... - queryReturn = sqlQuery( + queryreturn = sqlQuery( '''select label from subscriptions where address=?''', fromAddress) if queryreturn != []: for row in queryreturn: From 3b41eafa7b3d6e71322e2f3c9176cd166827d188 Mon Sep 17 00:00:00 2001 From: Jonathan Warren Date: Sat, 2 Nov 2013 18:01:36 -0400 Subject: [PATCH 38/48] revert main window to previous size --- src/bitmessageqt/bitmessageui.py | 22 ++++++---------------- src/bitmessageqt/bitmessageui.ui | 10 +++++----- 2 files changed, 11 insertions(+), 21 deletions(-) diff --git a/src/bitmessageqt/bitmessageui.py b/src/bitmessageqt/bitmessageui.py index d8ac9484..b80367dd 100644 --- a/src/bitmessageqt/bitmessageui.py +++ b/src/bitmessageqt/bitmessageui.py @@ -2,8 +2,8 @@ # Form implementation generated from reading ui file 'bitmessageui.ui' # -# Created: Sat Sep 21 15:59:39 2013 -# by: PyQt4 UI code generator 4.10.2 +# Created: Sat Nov 2 18:01:09 2013 +# by: PyQt4 UI code generator 4.10 # # WARNING! All changes made in this file will be lost! @@ -26,7 +26,7 @@ except AttributeError: class Ui_MainWindow(object): def setupUi(self, MainWindow): MainWindow.setObjectName(_fromUtf8("MainWindow")) - MainWindow.resize(795, 580) + MainWindow.resize(885, 580) icon = QtGui.QIcon() icon.addPixmap(QtGui.QPixmap(_fromUtf8(":/newPrefix/images/can-icon-24px.png")), QtGui.QIcon.Normal, QtGui.QIcon.Off) MainWindow.setWindowIcon(icon) @@ -439,7 +439,7 @@ class Ui_MainWindow(object): self.gridLayout.addWidget(self.tabWidget, 0, 0, 1, 1) MainWindow.setCentralWidget(self.centralwidget) self.menubar = QtGui.QMenuBar(MainWindow) - self.menubar.setGeometry(QtCore.QRect(0, 0, 795, 18)) + self.menubar.setGeometry(QtCore.QRect(0, 0, 885, 27)) self.menubar.setObjectName(_fromUtf8("menubar")) self.menuFile = QtGui.QMenu(self.menubar) self.menuFile.setObjectName(_fromUtf8("menuFile")) @@ -557,8 +557,8 @@ class Ui_MainWindow(object): self.textEditMessage.setHtml(_translate("MainWindow", "\n" "\n" -"


", None)) +"\n" +"


", None)) self.label.setText(_translate("MainWindow", "To:", None)) self.label_2.setText(_translate("MainWindow", "From:", None)) self.radioButtonBroadcast.setText(_translate("MainWindow", "Broadcast to everyone who is subscribed to your address", None)) @@ -642,13 +642,3 @@ class Ui_MainWindow(object): self.actionJoinChan.setText(_translate("MainWindow", "Join / Create chan", None)) import bitmessage_icons_rc - -if __name__ == "__main__": - import sys - app = QtGui.QApplication(sys.argv) - MainWindow = QtGui.QMainWindow() - ui = Ui_MainWindow() - ui.setupUi(MainWindow) - MainWindow.show() - sys.exit(app.exec_()) - diff --git a/src/bitmessageqt/bitmessageui.ui b/src/bitmessageqt/bitmessageui.ui index a802cdac..e5148ec1 100644 --- a/src/bitmessageqt/bitmessageui.ui +++ b/src/bitmessageqt/bitmessageui.ui @@ -6,7 +6,7 @@ 0 0 - 795 + 885 580 @@ -278,8 +278,8 @@ <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:9pt; font-weight:400; font-style:normal;"> -<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><br /></p></body></html> +</style></head><body style=" font-family:'Sans'; font-size:9pt; font-weight:400; font-style:normal;"> +<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:'MS Shell Dlg 2';"><br /></p></body></html>
@@ -1054,8 +1054,8 @@ p, li { white-space: pre-wrap; } 0 0 - 795 - 18 + 885 + 27 From e5fce78fc38d951707a5e3dbf6885d94b7387eae Mon Sep 17 00:00:00 2001 From: Jonathan Warren Date: Sat, 2 Nov 2013 18:05:39 -0400 Subject: [PATCH 39/48] revert settings window as close as possible to previous size --- src/bitmessageqt/settings.py | 4 ++-- src/bitmessageqt/settings.ui | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/bitmessageqt/settings.py b/src/bitmessageqt/settings.py index 152ab0b7..b246b5ce 100644 --- a/src/bitmessageqt/settings.py +++ b/src/bitmessageqt/settings.py @@ -2,7 +2,7 @@ # Form implementation generated from reading ui file 'settings.ui' # -# Created: Fri Nov 1 19:14:45 2013 +# Created: Sat Nov 2 18:03:44 2013 # by: PyQt4 UI code generator 4.10 # # WARNING! All changes made in this file will be lost! @@ -26,7 +26,7 @@ except AttributeError: class Ui_settingsDialog(object): def setupUi(self, settingsDialog): settingsDialog.setObjectName(_fromUtf8("settingsDialog")) - settingsDialog.resize(646, 473) + settingsDialog.resize(585, 437) self.gridLayout = QtGui.QGridLayout(settingsDialog) self.gridLayout.setObjectName(_fromUtf8("gridLayout")) self.buttonBox = QtGui.QDialogButtonBox(settingsDialog) diff --git a/src/bitmessageqt/settings.ui b/src/bitmessageqt/settings.ui index b7d7ae22..78a67aa5 100644 --- a/src/bitmessageqt/settings.ui +++ b/src/bitmessageqt/settings.ui @@ -6,8 +6,8 @@ 0 0 - 646 - 473 + 585 + 437 From c0f953166555fd6d2b7e28509ec141d642c68696 Mon Sep 17 00:00:00 2001 From: neko259 Date: Tue, 5 Nov 2013 23:36:15 +0200 Subject: [PATCH 40/48] Refactored some table items to remove the universal unreadable variable 'new_item' --- src/bitmessageqt/__init__.py | 41 ++++++++++++++++++------------------ 1 file changed, 20 insertions(+), 21 deletions(-) diff --git a/src/bitmessageqt/__init__.py b/src/bitmessageqt/__init__.py index de175def..3774d069 100644 --- a/src/bitmessageqt/__init__.py +++ b/src/bitmessageqt/__init__.py @@ -723,31 +723,30 @@ class MyForm(QtGui.QMainWindow): toLabel = toAddress self.ui.tableWidgetSent.insertRow(0) - newItem = QtGui.QTableWidgetItem(unicode(toLabel, 'utf-8')) - newItem.setToolTip(unicode(toLabel, 'utf-8')) - newItem.setIcon(avatarize(toAddress)) - newItem.setData(Qt.UserRole, str(toAddress)) - newItem.setFlags( + toAddressItem = QtGui.QTableWidgetItem(unicode(toLabel, 'utf-8')) + toAddressItem.setToolTip(unicode(toLabel, 'utf-8')) + toAddressItem.setIcon(avatarize(toAddress)) + toAddressItem.setData(Qt.UserRole, str(toAddress)) + toAddressItem.setFlags( QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled) - self.ui.tableWidgetSent.setItem(0, 0, newItem) + self.ui.tableWidgetSent.setItem(0, 0, toAddressItem) + if fromLabel == '': - newItem = QtGui.QTableWidgetItem( - unicode(fromAddress, 'utf-8')) - newItem.setToolTip(unicode(fromAddress, 'utf-8')) - else: - newItem = QtGui.QTableWidgetItem(unicode(fromLabel, 'utf-8')) - newItem.setToolTip(unicode(fromLabel, 'utf-8')) - newItem.setIcon(avatarize(fromAddress)) - newItem.setData(Qt.UserRole, str(fromAddress)) - newItem.setFlags( + fromLabel = fromAddress + fromAddressItem = QtGui.QTableWidgetItem(unicode(fromLabel, 'utf-8')) + fromAddressItem.setToolTip(unicode(fromLabel, 'utf-8')) + fromAddressItem.setIcon(avatarize(fromAddress)) + fromAddressItem.setData(Qt.UserRole, str(fromAddress)) + fromAddressItem.setFlags( QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled) - self.ui.tableWidgetSent.setItem(0, 1, newItem) - newItem = QtGui.QTableWidgetItem(unicode(subject, 'utf-8')) - newItem.setToolTip(unicode(subject, 'utf-8')) - #newItem.setData(Qt.UserRole, unicode(message, 'utf-8)')) # No longer hold the message in the table; we'll use a SQL query to display it as needed. - newItem.setFlags( + self.ui.tableWidgetSent.setItem(0, 1, fromAddressItem) + + subjectItem = QtGui.QTableWidgetItem(unicode(subject, 'utf-8')) + subjectItem.setToolTip(unicode(subject, 'utf-8')) + subjectItem.setFlags( QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled) - self.ui.tableWidgetSent.setItem(0, 2, newItem) + self.ui.tableWidgetSent.setItem(0, 2, subjectItem) + if status == 'awaitingpubkey': statusText = _translate( "MainWindow", "Waiting on their encryption key. Will request it again soon.") From c490f63170b7ad16aa475328814fc1b21c9aed73 Mon Sep 17 00:00:00 2001 From: Jonathan Warren Date: Tue, 5 Nov 2013 23:22:51 -0500 Subject: [PATCH 41/48] further work on 'messages expire' feature --- src/bitmessageqt/__init__.py | 41 ++++-- src/bitmessageqt/settings.py | 213 +++++++++++++++--------------- src/bitmessageqt/settings.ui | 244 ++++++++++++++++++++++++++++------- src/class_singleCleaner.py | 10 +- 4 files changed, 332 insertions(+), 176 deletions(-) diff --git a/src/bitmessageqt/__init__.py b/src/bitmessageqt/__init__.py index f873be43..55887b7a 100644 --- a/src/bitmessageqt/__init__.py +++ b/src/bitmessageqt/__init__.py @@ -2171,19 +2171,34 @@ class MyForm(QtGui.QMainWindow): shared.config.set('bitmessagesettings', 'stopresendingafterxdays', '') shared.config.set('bitmessagesettings', 'stopresendingafterxmonths', '') shared.maximumLengthOfTimeToBotherResendingMessages = float('inf') - elif (float(self.settingsDialogInstance.ui.lineEditDays.text()) >=0 and float(self.settingsDialogInstance.ui.lineEditMonths.text()) >=0): - shared.maximumLengthOfTimeToBotherResendingMessages = (float(str(self.settingsDialogInstance.ui.lineEditDays.text())) * 24 * 60 * 60) + (float(str(self.settingsDialogInstance.ui.lineEditMonths.text())) * (60 * 60 * 24 *365)/12) - if shared.maximumLengthOfTimeToBotherResendingMessages < 432000: # If the time period is less than 5 hours, we give zero values to all fields. No message will be sent again. - QMessageBox.about(self, _translate("MainWindow", "Will not resend ever"), _translate( - "MainWindow", "Note that the time limit you entered is less than the amount of time Bitmessage waits for the first resend attempt therefore your messages will never be resent.")) - shared.config.set('bitmessagesettings', 'stopresendingafterxdays', '0') - shared.config.set('bitmessagesettings', 'stopresendingafterxmonths', '0') - shared.maximumLengthOfTimeToBotherResendingMessages = 0 - else: - shared.config.set('bitmessagesettings', 'stopresendingafterxdays', str(float( - self.settingsDialogInstance.ui.lineEditDays.text()))) - shared.config.set('bitmessagesettings', 'stopresendingafterxmonths', str(float( - self.settingsDialogInstance.ui.lineEditMonths.text()))) + try: + float(self.settingsDialogInstance.ui.lineEditDays.text()) + lineEditDaysIsValidFloat = True + except: + lineEditDaysIsValidFloat = False + try: + float(self.settingsDialogInstance.ui.lineEditMonths.text()) + lineEditMonthsIsValidFloat = True + except: + lineEditMonthsIsValidFloat = False + if lineEditDaysIsValidFloat and not lineEditMonthsIsValidFloat: + self.settingsDialogInstance.ui.lineEditMonths.setText("0") + if lineEditMonthsIsValidFloat and not lineEditDaysIsValidFloat: + self.settingsDialogInstance.ui.lineEditDays.setText("0") + if lineEditDaysIsValidFloat or lineEditMonthsIsValidFloat: + if (float(self.settingsDialogInstance.ui.lineEditDays.text()) >=0 and float(self.settingsDialogInstance.ui.lineEditMonths.text()) >=0): + shared.maximumLengthOfTimeToBotherResendingMessages = (float(str(self.settingsDialogInstance.ui.lineEditDays.text())) * 24 * 60 * 60) + (float(str(self.settingsDialogInstance.ui.lineEditMonths.text())) * (60 * 60 * 24 *365)/12) + if shared.maximumLengthOfTimeToBotherResendingMessages < 432000: # If the time period is less than 5 hours, we give zero values to all fields. No message will be sent again. + QMessageBox.about(self, _translate("MainWindow", "Will not resend ever"), _translate( + "MainWindow", "Note that the time limit you entered is less than the amount of time Bitmessage waits for the first resend attempt therefore your messages will never be resent.")) + shared.config.set('bitmessagesettings', 'stopresendingafterxdays', '0') + shared.config.set('bitmessagesettings', 'stopresendingafterxmonths', '0') + shared.maximumLengthOfTimeToBotherResendingMessages = 0 + else: + shared.config.set('bitmessagesettings', 'stopresendingafterxdays', str(float( + self.settingsDialogInstance.ui.lineEditDays.text()))) + shared.config.set('bitmessagesettings', 'stopresendingafterxmonths', str(float( + self.settingsDialogInstance.ui.lineEditMonths.text()))) #end # if str(self.settingsDialogInstance.ui.comboBoxMaxCores.currentText()) == 'All': diff --git a/src/bitmessageqt/settings.py b/src/bitmessageqt/settings.py index e2bc7242..9b045e38 100644 --- a/src/bitmessageqt/settings.py +++ b/src/bitmessageqt/settings.py @@ -2,8 +2,8 @@ # Form implementation generated from reading ui file 'settings.ui' # -# Created: Sat Nov 2 18:03:44 2013 -# by: PyQt4 UI code generator 4.10 +# Created: Tue Nov 05 22:56:35 2013 +# by: PyQt4 UI code generator 4.10.2 # # WARNING! All changes made in this file will be lost! @@ -26,7 +26,7 @@ except AttributeError: class Ui_settingsDialog(object): def setupUi(self, settingsDialog): settingsDialog.setObjectName(_fromUtf8("settingsDialog")) - settingsDialog.resize(585, 437) + settingsDialog.resize(521, 399) self.gridLayout = QtGui.QGridLayout(settingsDialog) self.gridLayout.setObjectName(_fromUtf8("gridLayout")) self.buttonBox = QtGui.QDialogButtonBox(settingsDialog) @@ -160,21 +160,42 @@ class Ui_settingsDialog(object): spacerItem1 = QtGui.QSpacerItem(20, 70, QtGui.QSizePolicy.Minimum, QtGui.QSizePolicy.Expanding) self.gridLayout_4.addItem(spacerItem1, 2, 0, 1, 1) self.tabWidgetSettings.addTab(self.tabNetworkSettings, _fromUtf8("")) - self.tab = QtGui.QWidget() - self.tab.setObjectName(_fromUtf8("tab")) - self.gridLayout_6 = QtGui.QGridLayout(self.tab) + self.tabDemandedDifficulty = QtGui.QWidget() + self.tabDemandedDifficulty.setObjectName(_fromUtf8("tabDemandedDifficulty")) + self.gridLayout_6 = QtGui.QGridLayout(self.tabDemandedDifficulty) self.gridLayout_6.setObjectName(_fromUtf8("gridLayout_6")) - self.label_8 = QtGui.QLabel(self.tab) + self.label_9 = QtGui.QLabel(self.tabDemandedDifficulty) + self.label_9.setAlignment(QtCore.Qt.AlignRight|QtCore.Qt.AlignTrailing|QtCore.Qt.AlignVCenter) + self.label_9.setObjectName(_fromUtf8("label_9")) + self.gridLayout_6.addWidget(self.label_9, 1, 1, 1, 1) + self.label_10 = QtGui.QLabel(self.tabDemandedDifficulty) + self.label_10.setWordWrap(True) + self.label_10.setObjectName(_fromUtf8("label_10")) + self.gridLayout_6.addWidget(self.label_10, 2, 0, 1, 3) + self.label_11 = QtGui.QLabel(self.tabDemandedDifficulty) + self.label_11.setAlignment(QtCore.Qt.AlignRight|QtCore.Qt.AlignTrailing|QtCore.Qt.AlignVCenter) + self.label_11.setObjectName(_fromUtf8("label_11")) + self.gridLayout_6.addWidget(self.label_11, 3, 1, 1, 1) + self.label_8 = QtGui.QLabel(self.tabDemandedDifficulty) self.label_8.setWordWrap(True) self.label_8.setObjectName(_fromUtf8("label_8")) self.gridLayout_6.addWidget(self.label_8, 0, 0, 1, 3) spacerItem2 = QtGui.QSpacerItem(203, 20, QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Minimum) self.gridLayout_6.addItem(spacerItem2, 1, 0, 1, 1) - self.label_9 = QtGui.QLabel(self.tab) - self.label_9.setAlignment(QtCore.Qt.AlignRight|QtCore.Qt.AlignTrailing|QtCore.Qt.AlignVCenter) - self.label_9.setObjectName(_fromUtf8("label_9")) - self.gridLayout_6.addWidget(self.label_9, 1, 1, 1, 1) - self.lineEditTotalDifficulty = QtGui.QLineEdit(self.tab) + self.label_12 = QtGui.QLabel(self.tabDemandedDifficulty) + self.label_12.setWordWrap(True) + self.label_12.setObjectName(_fromUtf8("label_12")) + self.gridLayout_6.addWidget(self.label_12, 4, 0, 1, 3) + self.lineEditSmallMessageDifficulty = QtGui.QLineEdit(self.tabDemandedDifficulty) + sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.Fixed, QtGui.QSizePolicy.Fixed) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.lineEditSmallMessageDifficulty.sizePolicy().hasHeightForWidth()) + self.lineEditSmallMessageDifficulty.setSizePolicy(sizePolicy) + self.lineEditSmallMessageDifficulty.setMaximumSize(QtCore.QSize(70, 16777215)) + self.lineEditSmallMessageDifficulty.setObjectName(_fromUtf8("lineEditSmallMessageDifficulty")) + self.gridLayout_6.addWidget(self.lineEditSmallMessageDifficulty, 3, 2, 1, 1) + self.lineEditTotalDifficulty = QtGui.QLineEdit(self.tabDemandedDifficulty) sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.Fixed, QtGui.QSizePolicy.Fixed) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) @@ -185,44 +206,25 @@ class Ui_settingsDialog(object): self.gridLayout_6.addWidget(self.lineEditTotalDifficulty, 1, 2, 1, 1) spacerItem3 = QtGui.QSpacerItem(203, 20, QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Minimum) self.gridLayout_6.addItem(spacerItem3, 3, 0, 1, 1) - self.label_11 = QtGui.QLabel(self.tab) - self.label_11.setAlignment(QtCore.Qt.AlignRight|QtCore.Qt.AlignTrailing|QtCore.Qt.AlignVCenter) - self.label_11.setObjectName(_fromUtf8("label_11")) - self.gridLayout_6.addWidget(self.label_11, 3, 1, 1, 1) - self.lineEditSmallMessageDifficulty = QtGui.QLineEdit(self.tab) - sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.Fixed, QtGui.QSizePolicy.Fixed) - sizePolicy.setHorizontalStretch(0) - sizePolicy.setVerticalStretch(0) - sizePolicy.setHeightForWidth(self.lineEditSmallMessageDifficulty.sizePolicy().hasHeightForWidth()) - self.lineEditSmallMessageDifficulty.setSizePolicy(sizePolicy) - self.lineEditSmallMessageDifficulty.setMaximumSize(QtCore.QSize(70, 16777215)) - self.lineEditSmallMessageDifficulty.setObjectName(_fromUtf8("lineEditSmallMessageDifficulty")) - self.gridLayout_6.addWidget(self.lineEditSmallMessageDifficulty, 3, 2, 1, 1) - self.label_12 = QtGui.QLabel(self.tab) - self.label_12.setWordWrap(True) - self.label_12.setObjectName(_fromUtf8("label_12")) - self.gridLayout_6.addWidget(self.label_12, 4, 0, 1, 3) - self.label_10 = QtGui.QLabel(self.tab) - self.label_10.setWordWrap(True) - self.label_10.setObjectName(_fromUtf8("label_10")) - self.gridLayout_6.addWidget(self.label_10, 2, 0, 1, 3) - self.tabWidgetSettings.addTab(self.tab, _fromUtf8("")) - self.tab_2 = QtGui.QWidget() - self.tab_2.setObjectName(_fromUtf8("tab_2")) - self.gridLayout_7 = QtGui.QGridLayout(self.tab_2) + spacerItem4 = QtGui.QSpacerItem(20, 40, QtGui.QSizePolicy.Minimum, QtGui.QSizePolicy.Expanding) + self.gridLayout_6.addItem(spacerItem4, 5, 0, 1, 1) + self.tabWidgetSettings.addTab(self.tabDemandedDifficulty, _fromUtf8("")) + self.tabMaxAcceptableDifficulty = QtGui.QWidget() + self.tabMaxAcceptableDifficulty.setObjectName(_fromUtf8("tabMaxAcceptableDifficulty")) + self.gridLayout_7 = QtGui.QGridLayout(self.tabMaxAcceptableDifficulty) self.gridLayout_7.setObjectName(_fromUtf8("gridLayout_7")) - self.label_15 = QtGui.QLabel(self.tab_2) + self.label_15 = QtGui.QLabel(self.tabMaxAcceptableDifficulty) self.label_15.setWordWrap(True) self.label_15.setObjectName(_fromUtf8("label_15")) self.gridLayout_7.addWidget(self.label_15, 0, 0, 1, 3) - spacerItem4 = QtGui.QSpacerItem(102, 20, QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Minimum) - self.gridLayout_7.addItem(spacerItem4, 1, 0, 1, 1) - self.label_13 = QtGui.QLabel(self.tab_2) + spacerItem5 = QtGui.QSpacerItem(102, 20, QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Minimum) + self.gridLayout_7.addItem(spacerItem5, 1, 0, 1, 1) + self.label_13 = QtGui.QLabel(self.tabMaxAcceptableDifficulty) self.label_13.setLayoutDirection(QtCore.Qt.LeftToRight) self.label_13.setAlignment(QtCore.Qt.AlignRight|QtCore.Qt.AlignTrailing|QtCore.Qt.AlignVCenter) self.label_13.setObjectName(_fromUtf8("label_13")) self.gridLayout_7.addWidget(self.label_13, 1, 1, 1, 1) - self.lineEditMaxAcceptableTotalDifficulty = QtGui.QLineEdit(self.tab_2) + self.lineEditMaxAcceptableTotalDifficulty = QtGui.QLineEdit(self.tabMaxAcceptableDifficulty) sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.Fixed, QtGui.QSizePolicy.Fixed) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) @@ -231,13 +233,13 @@ class Ui_settingsDialog(object): self.lineEditMaxAcceptableTotalDifficulty.setMaximumSize(QtCore.QSize(70, 16777215)) self.lineEditMaxAcceptableTotalDifficulty.setObjectName(_fromUtf8("lineEditMaxAcceptableTotalDifficulty")) self.gridLayout_7.addWidget(self.lineEditMaxAcceptableTotalDifficulty, 1, 2, 1, 1) - spacerItem5 = QtGui.QSpacerItem(102, 20, QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Minimum) - self.gridLayout_7.addItem(spacerItem5, 2, 0, 1, 1) - self.label_14 = QtGui.QLabel(self.tab_2) + spacerItem6 = QtGui.QSpacerItem(102, 20, QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Minimum) + self.gridLayout_7.addItem(spacerItem6, 2, 0, 1, 1) + self.label_14 = QtGui.QLabel(self.tabMaxAcceptableDifficulty) self.label_14.setAlignment(QtCore.Qt.AlignRight|QtCore.Qt.AlignTrailing|QtCore.Qt.AlignVCenter) self.label_14.setObjectName(_fromUtf8("label_14")) self.gridLayout_7.addWidget(self.label_14, 2, 1, 1, 1) - self.lineEditMaxAcceptableSmallMessageDifficulty = QtGui.QLineEdit(self.tab_2) + self.lineEditMaxAcceptableSmallMessageDifficulty = QtGui.QLineEdit(self.tabMaxAcceptableDifficulty) sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.Fixed, QtGui.QSizePolicy.Fixed) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) @@ -246,15 +248,15 @@ class Ui_settingsDialog(object): self.lineEditMaxAcceptableSmallMessageDifficulty.setMaximumSize(QtCore.QSize(70, 16777215)) self.lineEditMaxAcceptableSmallMessageDifficulty.setObjectName(_fromUtf8("lineEditMaxAcceptableSmallMessageDifficulty")) self.gridLayout_7.addWidget(self.lineEditMaxAcceptableSmallMessageDifficulty, 2, 2, 1, 1) - spacerItem6 = QtGui.QSpacerItem(20, 147, QtGui.QSizePolicy.Minimum, QtGui.QSizePolicy.Expanding) - self.gridLayout_7.addItem(spacerItem6, 3, 1, 1, 1) - self.tabWidgetSettings.addTab(self.tab_2, _fromUtf8("")) + spacerItem7 = QtGui.QSpacerItem(20, 147, QtGui.QSizePolicy.Minimum, QtGui.QSizePolicy.Expanding) + self.gridLayout_7.addItem(spacerItem7, 3, 1, 1, 1) + self.tabWidgetSettings.addTab(self.tabMaxAcceptableDifficulty, _fromUtf8("")) self.tabNamecoin = QtGui.QWidget() self.tabNamecoin.setObjectName(_fromUtf8("tabNamecoin")) self.gridLayout_8 = QtGui.QGridLayout(self.tabNamecoin) self.gridLayout_8.setObjectName(_fromUtf8("gridLayout_8")) - spacerItem7 = QtGui.QSpacerItem(40, 20, QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Minimum) - self.gridLayout_8.addItem(spacerItem7, 2, 0, 1, 1) + spacerItem8 = QtGui.QSpacerItem(40, 20, QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Minimum) + self.gridLayout_8.addItem(spacerItem8, 2, 0, 1, 1) self.label_16 = QtGui.QLabel(self.tabNamecoin) self.label_16.setWordWrap(True) self.label_16.setObjectName(_fromUtf8("label_16")) @@ -266,10 +268,10 @@ class Ui_settingsDialog(object): self.lineEditNamecoinHost = QtGui.QLineEdit(self.tabNamecoin) self.lineEditNamecoinHost.setObjectName(_fromUtf8("lineEditNamecoinHost")) self.gridLayout_8.addWidget(self.lineEditNamecoinHost, 2, 2, 1, 1) - spacerItem8 = QtGui.QSpacerItem(40, 20, QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Minimum) - self.gridLayout_8.addItem(spacerItem8, 3, 0, 1, 1) spacerItem9 = QtGui.QSpacerItem(40, 20, QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Minimum) - self.gridLayout_8.addItem(spacerItem9, 4, 0, 1, 1) + self.gridLayout_8.addItem(spacerItem9, 3, 0, 1, 1) + spacerItem10 = QtGui.QSpacerItem(40, 20, QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Minimum) + self.gridLayout_8.addItem(spacerItem10, 4, 0, 1, 1) self.label_18 = QtGui.QLabel(self.tabNamecoin) self.label_18.setEnabled(True) self.label_18.setAlignment(QtCore.Qt.AlignRight|QtCore.Qt.AlignTrailing|QtCore.Qt.AlignVCenter) @@ -278,8 +280,8 @@ class Ui_settingsDialog(object): self.lineEditNamecoinPort = QtGui.QLineEdit(self.tabNamecoin) self.lineEditNamecoinPort.setObjectName(_fromUtf8("lineEditNamecoinPort")) self.gridLayout_8.addWidget(self.lineEditNamecoinPort, 3, 2, 1, 1) - spacerItem10 = QtGui.QSpacerItem(20, 40, QtGui.QSizePolicy.Minimum, QtGui.QSizePolicy.Expanding) - self.gridLayout_8.addItem(spacerItem10, 8, 1, 1, 1) + spacerItem11 = QtGui.QSpacerItem(20, 40, QtGui.QSizePolicy.Minimum, QtGui.QSizePolicy.Expanding) + self.gridLayout_8.addItem(spacerItem11, 8, 1, 1, 1) self.labelNamecoinUser = QtGui.QLabel(self.tabNamecoin) self.labelNamecoinUser.setAlignment(QtCore.Qt.AlignRight|QtCore.Qt.AlignTrailing|QtCore.Qt.AlignVCenter) self.labelNamecoinUser.setObjectName(_fromUtf8("labelNamecoinUser")) @@ -287,8 +289,8 @@ class Ui_settingsDialog(object): self.lineEditNamecoinUser = QtGui.QLineEdit(self.tabNamecoin) self.lineEditNamecoinUser.setObjectName(_fromUtf8("lineEditNamecoinUser")) self.gridLayout_8.addWidget(self.lineEditNamecoinUser, 4, 2, 1, 1) - spacerItem11 = QtGui.QSpacerItem(40, 20, QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Minimum) - self.gridLayout_8.addItem(spacerItem11, 5, 0, 1, 1) + spacerItem12 = QtGui.QSpacerItem(40, 20, QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Minimum) + self.gridLayout_8.addItem(spacerItem12, 5, 0, 1, 1) self.labelNamecoinPassword = QtGui.QLabel(self.tabNamecoin) self.labelNamecoinPassword.setAlignment(QtCore.Qt.AlignRight|QtCore.Qt.AlignTrailing|QtCore.Qt.AlignVCenter) self.labelNamecoinPassword.setObjectName(_fromUtf8("labelNamecoinPassword")) @@ -318,49 +320,45 @@ class Ui_settingsDialog(object): self.horizontalLayout.addWidget(self.radioButtonNamecoinNmcontrol) self.gridLayout_8.addLayout(self.horizontalLayout, 1, 0, 1, 3) self.tabWidgetSettings.addTab(self.tabNamecoin, _fromUtf8("")) - self.gridLayout.addWidget(self.tabWidgetSettings, 0, 0, 1, 1) - #start:UI setting to stop trying to send messages after X hours/days/months - self.tabResendingMessagesAdjustment=QtGui.QWidget() - self.tabResendingMessagesAdjustment.setObjectName(_fromUtf8("tabResendingMessagesAdjustment")) - self.gridLayout_9 = QtGui.QGridLayout(self.tabResendingMessagesAdjustment) - self.gridLayout_9.setObjectName(_fromUtf8("gridLayout_9")) - self.label_19 = QtGui.QLabel(self.tabResendingMessagesAdjustment) - self.label_19.setWordWrap(True) + self.tabResendsExpire = QtGui.QWidget() + self.tabResendsExpire.setObjectName(_fromUtf8("tabResendsExpire")) + self.gridLayout_5 = QtGui.QGridLayout(self.tabResendsExpire) + self.gridLayout_5.setObjectName(_fromUtf8("gridLayout_5")) + self.label_7 = QtGui.QLabel(self.tabResendsExpire) + self.label_7.setWordWrap(True) + self.label_7.setObjectName(_fromUtf8("label_7")) + self.gridLayout_5.addWidget(self.label_7, 0, 0, 1, 3) + spacerItem13 = QtGui.QSpacerItem(212, 20, QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Minimum) + self.gridLayout_5.addItem(spacerItem13, 1, 0, 1, 1) + self.widget = QtGui.QWidget(self.tabResendsExpire) + self.widget.setMinimumSize(QtCore.QSize(231, 75)) + self.widget.setObjectName(_fromUtf8("widget")) + self.label_19 = QtGui.QLabel(self.widget) + self.label_19.setGeometry(QtCore.QRect(10, 20, 101, 20)) + self.label_19.setAlignment(QtCore.Qt.AlignRight|QtCore.Qt.AlignTrailing|QtCore.Qt.AlignVCenter) self.label_19.setObjectName(_fromUtf8("label_19")) - self.gridLayout_9.addWidget(self.label_19, 0, 0, 1, 0) - spacerItem13 = QtGui.QSpacerItem(102, 20, QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Minimum) - self.gridLayout_9.addItem(spacerItem13, 1, 0, 1, 1) - self.horizontalLayout = QtGui.QHBoxLayout() - self.horizontalLayout.setObjectName(_fromUtf8("horizontalLayout")) - self.label_20 = QtGui.QLabel(self.tabResendingMessagesAdjustment) - self.label_20.setLayoutDirection(QtCore.Qt.LeftToRight) + self.label_20 = QtGui.QLabel(self.widget) + self.label_20.setGeometry(QtCore.QRect(30, 40, 80, 16)) self.label_20.setAlignment(QtCore.Qt.AlignRight|QtCore.Qt.AlignTrailing|QtCore.Qt.AlignVCenter) self.label_20.setObjectName(_fromUtf8("label_20")) - self.gridLayout_9.addWidget(self.label_20, 2, 0, 1, 1) - self.lineEditHours = QtGui.QLineEdit(self.tabResendingMessagesAdjustment) - self.lineEditHours.setMaximumSize(QtCore.QSize(33, 16777)) - self.lineEditHours.setObjectName(_fromUtf8("lineEditHours")) - self.gridLayout_9.addWidget(self.lineEditHours, 2, 1, 1, 1) - self.label_22 = QtGui.QLabel(self.tabResendingMessagesAdjustment) - self.label_22.setAlignment(QtCore.Qt.AlignRight|QtCore.Qt.AlignTrailing|QtCore.Qt.AlignVCenter) - self.label_22.setObjectName(_fromUtf8("label_22")) - self.gridLayout_9.addWidget(self.label_22, 2, 2, 1, 1) - self.lineEditDays = QtGui.QLineEdit(self.tabResendingMessagesAdjustment) - self.lineEditDays.setMaximumSize(QtCore.QSize(33, 16777)) + self.lineEditDays = QtGui.QLineEdit(self.widget) + self.lineEditDays.setGeometry(QtCore.QRect(113, 20, 51, 20)) self.lineEditDays.setObjectName(_fromUtf8("lineEditDays")) - self.gridLayout_9.addWidget(self.lineEditDays, 2, 3, 1, 1) - self.label_23 = QtGui.QLabel(self.tabResendingMessagesAdjustment) - self.label_23.setAlignment(QtCore.Qt.AlignRight|QtCore.Qt.AlignTrailing|QtCore.Qt.AlignVCenter) - self.label_23.setObjectName(_fromUtf8("label_23")) - self.gridLayout_9.addWidget(self.label_23, 2, 4, 1, 1) - self.lineEditMonths = QtGui.QLineEdit(self.tabResendingMessagesAdjustment) - self.lineEditMonths.setMaximumSize(QtCore.QSize(33, 16777)) + self.lineEditMonths = QtGui.QLineEdit(self.widget) + self.lineEditMonths.setGeometry(QtCore.QRect(113, 40, 51, 20)) self.lineEditMonths.setObjectName(_fromUtf8("lineEditMonths")) - self.gridLayout_9.addWidget(self.lineEditMonths, 2, 5, 1, 1) - spacerItem15 = QtGui.QSpacerItem(20, 147, QtGui.QSizePolicy.Minimum, QtGui.QSizePolicy.Expanding) - self.gridLayout_9.addItem(spacerItem15, 3, 1, 1, 1) - self.tabWidgetSettings.addTab(self.tabResendingMessagesAdjustment, _fromUtf8("")) - #end + self.label_22 = QtGui.QLabel(self.widget) + self.label_22.setGeometry(QtCore.QRect(169, 23, 61, 16)) + self.label_22.setObjectName(_fromUtf8("label_22")) + self.label_23 = QtGui.QLabel(self.widget) + self.label_23.setGeometry(QtCore.QRect(170, 41, 71, 16)) + self.label_23.setObjectName(_fromUtf8("label_23")) + self.gridLayout_5.addWidget(self.widget, 1, 2, 1, 1) + spacerItem14 = QtGui.QSpacerItem(20, 129, QtGui.QSizePolicy.Minimum, QtGui.QSizePolicy.Expanding) + self.gridLayout_5.addItem(spacerItem14, 2, 1, 1, 1) + self.tabWidgetSettings.addTab(self.tabResendsExpire, _fromUtf8("")) + self.gridLayout.addWidget(self.tabWidgetSettings, 0, 0, 1, 1) + self.retranslateUi(settingsDialog) self.tabWidgetSettings.setCurrentIndex(0) QtCore.QObject.connect(self.buttonBox, QtCore.SIGNAL(_fromUtf8("accepted()")), settingsDialog.accept) @@ -416,23 +414,16 @@ class Ui_settingsDialog(object): self.comboBoxProxyType.setItemText(1, _translate("settingsDialog", "SOCKS4a", None)) self.comboBoxProxyType.setItemText(2, _translate("settingsDialog", "SOCKS5", None)) self.tabWidgetSettings.setTabText(self.tabWidgetSettings.indexOf(self.tabNetworkSettings), _translate("settingsDialog", "Network Settings", None)) - self.label_8.setText(_translate("settingsDialog", "When someone sends you a message, their computer must first complete some work. The difficulty of this work, by default, is 1. You may raise this default for new addresses you create by changing the values here. Any new addresses you create will require senders to meet the higher difficulty. There is one exception: if you add a friend or acquaintance to your address book, Bitmessage will automatically notify them when you next send a message that they need only complete the minimum amount of work: difficulty 1. ", None)) self.label_9.setText(_translate("settingsDialog", "Total difficulty:", None)) - self.label_11.setText(_translate("settingsDialog", "Small message difficulty:", None)) - self.label_12.setText(_translate("settingsDialog", "The \'Small message difficulty\' mostly only affects the difficulty of sending small messages. Doubling this value makes it almost twice as difficult to send a small message but doesn\'t really affect large messages.", None)) self.label_10.setText(_translate("settingsDialog", "The \'Total difficulty\' affects the absolute amount of work the sender must complete. Doubling this value doubles the amount of work.", None)) - self.tabWidgetSettings.setTabText(self.tabWidgetSettings.indexOf(self.tab), _translate("settingsDialog", "Demanded difficulty", None)) + self.label_11.setText(_translate("settingsDialog", "Small message difficulty:", None)) + self.label_8.setText(_translate("settingsDialog", "When someone sends you a message, their computer must first complete some work. The difficulty of this work, by default, is 1. You may raise this default for new addresses you create by changing the values here. Any new addresses you create will require senders to meet the higher difficulty. There is one exception: if you add a friend or acquaintance to your address book, Bitmessage will automatically notify them when you next send a message that they need only complete the minimum amount of work: difficulty 1. ", None)) + self.label_12.setText(_translate("settingsDialog", "The \'Small message difficulty\' mostly only affects the difficulty of sending small messages. Doubling this value makes it almost twice as difficult to send a small message but doesn\'t really affect large messages.", None)) + self.tabWidgetSettings.setTabText(self.tabWidgetSettings.indexOf(self.tabDemandedDifficulty), _translate("settingsDialog", "Demanded difficulty", None)) self.label_15.setText(_translate("settingsDialog", "Here you may set the maximum amount of work you are willing to do to send a message to another person. Setting these values to 0 means that any value is acceptable.", None)) self.label_13.setText(_translate("settingsDialog", "Maximum acceptable total difficulty:", None)) - #start:UI setting to stop trying to send messages after X hours/days/months - self.tabWidgetSettings.setTabText(self.tabWidgetSettings.indexOf(self.tabResendingMessagesAdjustment), _translate("settingsDialog", "Adjusting time period to stop sending messages", None)) - self.label_19.setText(_translate("settingsDialog", "

If you send a message to someone and he is offline for more than two and a half days, Bitmessage will send the message again after an additional two and a half days. This will be continued with exponential backoff forever. Μessages will continue to be sent after 5, 10, 20 days etc. until the receiver gets them.

Leaving all the input fields blank means the default behavior which will continue the resending with exponential backoff.

Setting these values to 0/0/0 or less than 5 days mean that the client will not resend any messages.

Here you can adjust Bitmessage to stop trying to send messages after X hours/days/months.

", None)) - self.label_20.setText(_translate("settingsDialog", "Time in hours/days/months:", None)) - self.label_22.setText(_translate("settingsDialog", "/", None)) - self.label_23.setText(_translate("settingsDialog", "/", None)) - #end self.label_14.setText(_translate("settingsDialog", "Maximum acceptable small message difficulty:", None)) - self.tabWidgetSettings.setTabText(self.tabWidgetSettings.indexOf(self.tab_2), _translate("settingsDialog", "Max acceptable difficulty", None)) + self.tabWidgetSettings.setTabText(self.tabWidgetSettings.indexOf(self.tabMaxAcceptableDifficulty), _translate("settingsDialog", "Max acceptable difficulty", None)) self.label_16.setText(_translate("settingsDialog", "

Bitmessage can utilize a different Bitcoin-based program called Namecoin to make addresses human-friendly. For example, instead of having to tell your friend your long Bitmessage address, you can simply tell him to send a message to test.

(Getting your own Bitmessage address into Namecoin is still rather difficult).

Bitmessage can use either namecoind directly or a running nmcontrol instance.

", None)) self.label_17.setText(_translate("settingsDialog", "Host:", None)) self.label_18.setText(_translate("settingsDialog", "Port:", None)) @@ -443,5 +434,11 @@ class Ui_settingsDialog(object): self.radioButtonNamecoinNamecoind.setText(_translate("settingsDialog", "Namecoind", None)) self.radioButtonNamecoinNmcontrol.setText(_translate("settingsDialog", "NMControl", None)) self.tabWidgetSettings.setTabText(self.tabWidgetSettings.indexOf(self.tabNamecoin), _translate("settingsDialog", "Namecoin integration", None)) + self.label_7.setText(_translate("settingsDialog", "

By default, if you send a message to someone and he is offline for more than two days, Bitmessage will send the message again after an additional two days. This will be continued with exponential backoff forever; messages will be resent after 5, 10, 20 days ect. until the receiver acknowledges them. Here you may change that behavior by having Bitmessage give up after a certain number of days or months.

Leave these input fields blank for the default behavior.

", None)) + self.label_19.setText(_translate("settingsDialog", "Give up after", None)) + self.label_20.setText(_translate("settingsDialog", "and", None)) + self.label_22.setText(_translate("settingsDialog", "days", None)) + self.label_23.setText(_translate("settingsDialog", "months.", None)) + self.tabWidgetSettings.setTabText(self.tabWidgetSettings.indexOf(self.tabResendsExpire), _translate("settingsDialog", "Resends Expire", None)) import bitmessage_icons_rc diff --git a/src/bitmessageqt/settings.ui b/src/bitmessageqt/settings.ui index 78a67aa5..d2628f8f 100644 --- a/src/bitmessageqt/settings.ui +++ b/src/bitmessageqt/settings.ui @@ -6,8 +6,8 @@ 0 0 - 585 - 437 + 521 + 399
@@ -333,11 +333,41 @@
- + Demanded difficulty + + + + Total difficulty: + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + The 'Total difficulty' affects the absolute amount of work the sender must complete. Doubling this value doubles the amount of work. + + + true + + + + + + + Small message difficulty: + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + @@ -361,13 +391,29 @@ - - + + - Total difficulty: + The 'Small message difficulty' mostly only affects the difficulty of sending small messages. Doubling this value makes it almost twice as difficult to send a small message but doesn't really affect large messages. - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + true + + + + + + + + 0 + 0 + + + + + 70 + 16777215 + @@ -400,55 +446,22 @@ - - - - Small message difficulty: + + + + Qt::Vertical - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - - - - - - 0 - 0 - - - + - 70 - 16777215 + 20 + 40 - - - - - - The 'Small message difficulty' mostly only affects the difficulty of sending small messages. Doubling this value makes it almost twice as difficult to send a small message but doesn't really affect large messages. - - - true - - - - - - - The 'Total difficulty' affects the absolute amount of work the sender must complete. Doubling this value doubles the amount of work. - - - true - - + - + Max acceptable difficulty @@ -742,6 +755,137 @@ + + + Resends Expire + + + + + + <html><head/><body><p>By default, if you send a message to someone and he is offline for more than two days, Bitmessage will send the message again after an additional two days. This will be continued with exponential backoff forever; messages will be resent after 5, 10, 20 days ect. until the receiver acknowledges them. Here you may change that behavior by having Bitmessage give up after a certain number of days or months.</p><p>Leave these input fields blank for the default behavior. </p></body></html> + + + true + + + + + + + Qt::Horizontal + + + + 212 + 20 + + + + + + + + + 231 + 75 + + + + + + 10 + 20 + 101 + 20 + + + + Give up after + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + 30 + 40 + 80 + 16 + + + + and + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + 113 + 20 + 51 + 20 + + + + + + + 113 + 40 + 51 + 20 + + + + + + + 169 + 23 + 61 + 16 + + + + days + + + + + + 170 + 41 + 71 + 16 + + + + months. + + + + + + + + Qt::Vertical + + + + 20 + 129 + + + + + + diff --git a/src/class_singleCleaner.py b/src/class_singleCleaner.py index bd7a4907..ca52b8e9 100644 --- a/src/class_singleCleaner.py +++ b/src/class_singleCleaner.py @@ -34,13 +34,13 @@ class singleCleaner(threading.Thread): try: shared.maximumLengthOfTimeToBotherResendingMessages = (float(shared.config.get('bitmessagesettings', 'stopresendingafterxdays')) * 24 * 60 * 60) + (float(shared.config.get('bitmessagesettings', 'stopresendingafterxmonths')) * (60 * 60 * 24 *365)/12) except: + # Either the user hasn't set stopresendingafterxdays and stopresendingafterxmonths yet or the options are missing from the config file. shared.maximumLengthOfTimeToBotherResendingMessages = float('inf') - print 'problem calculating maximumLengthOfTimeToBotherResendingMessages ' while True: shared.UISignalQueue.put(( 'updateStatusBar', 'Doing housekeeping (Flushing inventory in memory to disk...)')) - + with shared.inventoryLock: # If you use both the inventoryLock and the sqlLock, always use the inventoryLock OUTSIDE of the sqlLock. with SqlBulkExecute() as sql: for hash, storedValue in shared.inventory.items(): @@ -96,8 +96,8 @@ class singleCleaner(threading.Thread): resendPubkey(pubkeyretrynumber,toripe) else: # status == msgsent if (int(time.time()) - lastactiontime) > (shared.maximumAgeOfAnObjectThatIAmWillingToAccept * (2 ** (msgretrynumber))): - resendMsg(msgretrynumber,ackdata) - + resendMsg(msgretrynumber,ackdata) + # Let's also clear and reload shared.inventorySets to keep it from # taking up an unnecessary amount of memory. for streamNumber in shared.inventorySets: @@ -136,7 +136,7 @@ def resendPubkey(pubkeyretrynumber,toripe): toripe] # We need to take this entry out of the shared.neededPubkeys structure because the shared.workerQueue checks to see whether the entry is already present and will not do the POW and send the message because it assumes that it has already done it recently. except: pass - + shared.UISignalQueue.put(( 'updateStatusBar', 'Doing work necessary to again attempt to request a public key...')) t = () From b3f0fd981fecf433c55c99c3457af766b851cedf Mon Sep 17 00:00:00 2001 From: Jonathan Warren Date: Tue, 5 Nov 2013 23:42:16 -0500 Subject: [PATCH 42/48] delete class_sqlThread.py.orig --- src/class_sqlThread.py.orig | 436 ------------------------------------ 1 file changed, 436 deletions(-) delete mode 100644 src/class_sqlThread.py.orig diff --git a/src/class_sqlThread.py.orig b/src/class_sqlThread.py.orig deleted file mode 100644 index 48e3fa08..00000000 --- a/src/class_sqlThread.py.orig +++ /dev/null @@ -1,436 +0,0 @@ -import threading -import shared -import sqlite3 -import time -import shutil # used for moving the messages.dat file -import sys -import os -from debug import logger -from namecoin import ensureNamecoinOptions -import tr#anslate - -# This thread exists because SQLITE3 is so un-threadsafe that we must -# submit queries to it and it puts results back in a different queue. They -# won't let us just use locks. - - -class sqlThread(threading.Thread): - - def __init__(self): - threading.Thread.__init__(self) - - def run(self): - self.conn = sqlite3.connect(shared.appdata + 'messages.dat') - self.conn.text_factory = str - self.cur = self.conn.cursor() - try: - self.cur.execute( - '''CREATE TABLE inbox (msgid blob, toaddress text, fromaddress text, subject text, received text, message text, folder text, encodingtype int, read bool, UNIQUE(msgid) ON CONFLICT REPLACE)''' ) - self.cur.execute( - '''CREATE TABLE sent (msgid blob, toaddress text, toripe blob, fromaddress text, subject text, message text, ackdata blob, lastactiontime integer, status text, pubkeyretrynumber integer, msgretrynumber integer, folder text, encodingtype int)''' ) - self.cur.execute( - '''CREATE TABLE subscriptions (label text, address text, enabled bool)''' ) - self.cur.execute( - '''CREATE TABLE addressbook (label text, address text)''' ) - self.cur.execute( - '''CREATE TABLE blacklist (label text, address text, enabled bool)''' ) - self.cur.execute( - '''CREATE TABLE whitelist (label text, address text, enabled bool)''' ) - # Explanation of what is in the pubkeys table: - # The hash is the RIPEMD160 hash that is encoded in the Bitmessage address. - # transmitdata is literally the data that was included in the Bitmessage pubkey message when it arrived, except for the 24 byte protocol header- ie, it starts with the POW nonce. - # time is the time that the pubkey was broadcast on the network same as with every other type of Bitmessage object. - # usedpersonally is set to "yes" if we have used the key - # personally. This keeps us from deleting it because we may want to - # reply to a message in the future. This field is not a bool - # because we may need more flexability in the future and it doesn't - # take up much more space anyway. - self.cur.execute( - '''CREATE TABLE pubkeys (hash blob, addressversion int, transmitdata blob, time int, usedpersonally text, UNIQUE(hash, addressversion) ON CONFLICT REPLACE)''' ) - self.cur.execute( - '''CREATE TABLE inventory (hash blob, objecttype text, streamnumber int, payload blob, receivedtime integer, tag blob, UNIQUE(hash) ON CONFLICT REPLACE)''' ) - self.cur.execute( - '''CREATE TABLE knownnodes (timelastseen int, stream int, services blob, host blob, port blob, UNIQUE(host, stream, port) ON CONFLICT REPLACE)''' ) - # This table isn't used in the program yet but I - # have a feeling that we'll need it. - self.cur.execute( - '''INSERT INTO subscriptions VALUES('Bitmessage new releases/announcements','BM-GtovgYdgs7qXPkoYaRgrLFuFKz1SFpsw',1)''') - self.cur.execute( - '''CREATE TABLE settings (key blob, value blob, UNIQUE(key) ON CONFLICT REPLACE)''' ) - self.cur.execute( '''INSERT INTO settings VALUES('version','5')''') - self.cur.execute( '''INSERT INTO settings VALUES('lastvacuumtime',?)''', ( - int(time.time()),)) - self.conn.commit() - logger.info('Created messages database file') - except Exception as err: - if str(err) == 'table inbox already exists': - logger.debug('Database file already exists.') - - else: - sys.stderr.write( - 'ERROR trying to create database file (message.dat). Error message: %s\n' % str(err)) - os._exit(0) - - if shared.config.getint('bitmessagesettings', 'settingsversion') == 1: - shared.config.set('bitmessagesettings', 'settingsversion', '2') - # If the settings version is equal to 2 or 3 then the - # sqlThread will modify the pubkeys table and change - # the settings version to 4. - shared.config.set('bitmessagesettings', 'socksproxytype', 'none') - shared.config.set('bitmessagesettings', 'sockshostname', 'localhost') - shared.config.set('bitmessagesettings', 'socksport', '9050') - shared.config.set('bitmessagesettings', 'socksauthentication', 'false') - shared.config.set('bitmessagesettings', 'socksusername', '') - shared.config.set('bitmessagesettings', 'sockspassword', '') - shared.config.set('bitmessagesettings', 'sockslisten', 'false') - shared.config.set('bitmessagesettings', 'keysencrypted', 'false') - shared.config.set('bitmessagesettings', 'messagesencrypted', 'false') - with open(shared.appdata + 'keys.dat', 'wb') as configfile: - shared.config.write(configfile) - - # People running earlier versions of PyBitmessage do not have the - # usedpersonally field in their pubkeys table. Let's add it. - if shared.config.getint('bitmessagesettings', 'settingsversion') == 2: - item = '''ALTER TABLE pubkeys ADD usedpersonally text DEFAULT 'no' ''' - parameters = '' - self.cur.execute(item, parameters) - self.conn.commit() - - shared.config.set('bitmessagesettings', 'settingsversion', '3') - with open(shared.appdata + 'keys.dat', 'wb') as configfile: - shared.config.write(configfile) - - # People running earlier versions of PyBitmessage do not have the - # encodingtype field in their inbox and sent tables or the read field - # in the inbox table. Let's add them. - if shared.config.getint('bitmessagesettings', 'settingsversion') == 3: - item = '''ALTER TABLE inbox ADD encodingtype int DEFAULT '2' ''' - parameters = '' - self.cur.execute(item, parameters) - - item = '''ALTER TABLE inbox ADD read bool DEFAULT '1' ''' - parameters = '' - self.cur.execute(item, parameters) - - item = '''ALTER TABLE sent ADD encodingtype int DEFAULT '2' ''' - parameters = '' - self.cur.execute(item, parameters) - self.conn.commit() - - shared.config.set('bitmessagesettings', 'settingsversion', '4') - with open(shared.appdata + 'keys.dat', 'wb') as configfile: - shared.config.write(configfile) - - if shared.config.getint('bitmessagesettings', 'settingsversion') == 4: - shared.config.set('bitmessagesettings', 'defaultnoncetrialsperbyte', str( - shared.networkDefaultProofOfWorkNonceTrialsPerByte)) - shared.config.set('bitmessagesettings', 'defaultpayloadlengthextrabytes', str( - shared.networkDefaultPayloadLengthExtraBytes)) - shared.config.set('bitmessagesettings', 'settingsversion', '5') - - if shared.config.getint('bitmessagesettings', 'settingsversion') == 5: - shared.config.set( - 'bitmessagesettings', 'maxacceptablenoncetrialsperbyte', '0') - shared.config.set( - 'bitmessagesettings', 'maxacceptablepayloadlengthextrabytes', '0') - shared.config.set('bitmessagesettings', 'settingsversion', '6') - with open(shared.appdata + 'keys.dat', 'wb') as configfile: - shared.config.write(configfile) - - # From now on, let us keep a 'version' embedded in the messages.dat - # file so that when we make changes to the database, the database - # version we are on can stay embedded in the messages.dat file. Let us - # check to see if the settings table exists yet. - item = '''SELECT name FROM sqlite_master WHERE type='table' AND name='settings';''' - parameters = '' - self.cur.execute(item, parameters) - if self.cur.fetchall() == []: - # The settings table doesn't exist. We need to make it. - logger.debug('In messages.dat database, creating new \'settings\' table.') - self.cur.execute( - '''CREATE TABLE settings (key text, value blob, UNIQUE(key) ON CONFLICT REPLACE)''' ) - self.cur.execute( '''INSERT INTO settings VALUES('version','1')''') - self.cur.execute( '''INSERT INTO settings VALUES('lastvacuumtime',?)''', ( - int(time.time()),)) - logger.debug('In messages.dat database, removing an obsolete field from the pubkeys table.') - self.cur.execute( - '''CREATE TEMPORARY TABLE pubkeys_backup(hash blob, transmitdata blob, time int, usedpersonally text, UNIQUE(hash) ON CONFLICT REPLACE);''') - self.cur.execute( - '''INSERT INTO pubkeys_backup SELECT hash, transmitdata, time, usedpersonally FROM pubkeys;''') - self.cur.execute( '''DROP TABLE pubkeys''') - self.cur.execute( - '''CREATE TABLE pubkeys (hash blob, transmitdata blob, time int, usedpersonally text, UNIQUE(hash) ON CONFLICT REPLACE)''' ) - self.cur.execute( - '''INSERT INTO pubkeys SELECT hash, transmitdata, time, usedpersonally FROM pubkeys_backup;''') - self.cur.execute( '''DROP TABLE pubkeys_backup;''') - logger.debug('Deleting all pubkeys from inventory. They will be redownloaded and then saved with the correct times.') - self.cur.execute( - '''delete from inventory where objecttype = 'pubkey';''') - logger.debug('replacing Bitmessage announcements mailing list with a new one.') - self.cur.execute( - '''delete from subscriptions where address='BM-BbkPSZbzPwpVcYZpU4yHwf9ZPEapN5Zx' ''') - self.cur.execute( - '''INSERT INTO subscriptions VALUES('Bitmessage new releases/announcements','BM-GtovgYdgs7qXPkoYaRgrLFuFKz1SFpsw',1)''') - logger.debug('Commiting.') - self.conn.commit() - logger.debug('Vacuuming message.dat. You might notice that the file size gets much smaller.') - self.cur.execute( ''' VACUUM ''') - - # After code refactoring, the possible status values for sent messages - # have changed. - self.cur.execute( - '''update sent set status='doingmsgpow' where status='doingpow' ''') - self.cur.execute( - '''update sent set status='msgsent' where status='sentmessage' ''') - self.cur.execute( - '''update sent set status='doingpubkeypow' where status='findingpubkey' ''') - self.cur.execute( - '''update sent set status='broadcastqueued' where status='broadcastpending' ''') - self.conn.commit() - - if not shared.config.has_option('bitmessagesettings', 'sockslisten'): - shared.config.set('bitmessagesettings', 'sockslisten', 'false') - - ensureNamecoinOptions() - - """# Add a new column to the inventory table to store the first 20 bytes of encrypted messages to support Android app - item = '''SELECT value FROM settings WHERE key='version';''' - parameters = '' - self.cur.execute(item, parameters) - if int(self.cur.fetchall()[0][0]) == 1: - print 'upgrading database' - item = '''ALTER TABLE inventory ADD first20bytesofencryptedmessage blob DEFAULT '' ''' - parameters = '' - self.cur.execute(item, parameters) - item = '''update settings set value=? WHERE key='version';''' - parameters = (2,) - self.cur.execute(item, parameters)""" - - # Let's get rid of the first20bytesofencryptedmessage field in the inventory table. - item = '''SELECT value FROM settings WHERE key='version';''' - parameters = '' - self.cur.execute(item, parameters) - if int(self.cur.fetchall()[0][0]) == 2: - logger.debug('In messages.dat database, removing an obsolete field from the inventory table.') - self.cur.execute( - '''CREATE TEMPORARY TABLE inventory_backup(hash blob, objecttype text, streamnumber int, payload blob, receivedtime integer, UNIQUE(hash) ON CONFLICT REPLACE);''') - self.cur.execute( - '''INSERT INTO inventory_backup SELECT hash, objecttype, streamnumber, payload, receivedtime FROM inventory;''') - self.cur.execute( '''DROP TABLE inventory''') - self.cur.execute( - '''CREATE TABLE inventory (hash blob, objecttype text, streamnumber int, payload blob, receivedtime integer, UNIQUE(hash) ON CONFLICT REPLACE)''' ) - self.cur.execute( - '''INSERT INTO inventory SELECT hash, objecttype, streamnumber, payload, receivedtime FROM inventory_backup;''') - self.cur.execute( '''DROP TABLE inventory_backup;''') - item = '''update settings set value=? WHERE key='version';''' - parameters = (3,) - self.cur.execute(item, parameters) - - # Add a new column to the inventory table to store tags. - item = '''SELECT value FROM settings WHERE key='version';''' - parameters = '' - self.cur.execute(item, parameters) - currentVersion = int(self.cur.fetchall()[0][0]) - if currentVersion == 1 or currentVersion == 3: - logger.debug('In messages.dat database, adding tag field to the inventory table.') - item = '''ALTER TABLE inventory ADD tag blob DEFAULT '' ''' - parameters = '' - self.cur.execute(item, parameters) - item = '''update settings set value=? WHERE key='version';''' - parameters = (4,) - self.cur.execute(item, parameters) - - if not shared.config.has_option('bitmessagesettings', 'userlocale'): - shared.config.set('bitmessagesettings', 'userlocale', 'system') - if not shared.config.has_option('bitmessagesettings', 'sendoutgoingconnections'): - shared.config.set('bitmessagesettings', 'sendoutgoingconnections', 'True') - - # Raise the default required difficulty from 1 to 2 - if shared.config.getint('bitmessagesettings', 'settingsversion') == 6: - if int(shared.config.get('bitmessagesettings','defaultnoncetrialsperbyte')) == shared.networkDefaultProofOfWorkNonceTrialsPerByte: - shared.config.set('bitmessagesettings','defaultnoncetrialsperbyte', str(shared.networkDefaultProofOfWorkNonceTrialsPerByte * 2)) - shared.config.set('bitmessagesettings', 'settingsversion', '7') - with open(shared.appdata + 'keys.dat', 'wb') as configfile: - shared.config.write(configfile) -<<<<<<< HEAD - - # Add a new column to the pubkeys table to store the address version. - # We're going to trash all of our pubkeys and let them be redownloaded. - item = '''SELECT value FROM settings WHERE key='version';''' - parameters = '' - self.cur.execute(item, parameters) - currentVersion = int(self.cur.fetchall()[0][0]) - if currentVersion == 4: - self.cur.execute( '''DROP TABLE pubkeys''') - self.cur.execute( - '''CREATE TABLE pubkeys (hash blob, addressversion int, transmitdata blob, time int, usedpersonally text, UNIQUE(hash, addressversion) ON CONFLICT REPLACE)''' ) - self.cur.execute( - '''delete from inventory where objecttype = 'pubkey';''') - item = '''update settings set value=? WHERE key='version';''' - parameters = (5,) - self.cur.execute(item, parameters) - -======= - ->>>>>>> c16d9787d289b1f6ae05358e55ad46cd4b8d2a30 - # Are you hoping to add a new option to the keys.dat file of existing - # Bitmessage users? Add it right above this line! - - #my new implementation starts here, the most of these comment will be deleted, they are just for documentation - if shared.config.getint('bitmessagesettings', 'settingsversion') == 7:#this is the version that all we have,if you see your keys.dat file this is your version.AQWA - shared.config.set(#in order to not have to change your keys.dat file I update it with the new lines. I add to your keys.dat three new default fields.AQWA - 'bitmessagesettings', 'hours', '')# hours, days, months have no value.This means that bitmessage works as before. It re-sends mails every 4,8,16 days..forever.AQWA - shared.config.set( - 'bitmessagesettings', 'days', '') - shared.config.set( - 'bitmessagesettings', 'months', '') - shared.config.set( - 'bitmessagesettings', 'timeperiod', '-1')#time period has default value -1. This is used for checking in class_singleCleaner. If you leave default the time period or after you change it(f.i 1/0/0), again you set it with its default value(-/-/-) this variable will be -1.AQWA - shared.config.set('bitmessagesettings', 'settingsversion', '8') #We update the version.If I leave it 7 every time that Bitmessage starts your setting will be lost.The default values(-/-/-) will be loaded all the time ;).That was juicy.AQWA - with open(shared.appdata + 'keys.dat', 'wb') as configfile: - shared.config.write(configfile) - #my new implementation in this file stops here - - try: - testpayload = '\x00\x00' - t = ('1234', 1, testpayload, '12345678', 'no') - self.cur.execute( '''INSERT INTO pubkeys VALUES(?,?,?,?,?)''', t) - self.conn.commit() - self.cur.execute( - '''SELECT transmitdata FROM pubkeys WHERE hash='1234' ''') - queryreturn = self.cur.fetchall() - for row in queryreturn: - transmitdata, = row - self.cur.execute('''DELETE FROM pubkeys WHERE hash='1234' ''') - self.conn.commit() - if transmitdata == '': - logger.fatal('Problem: The version of SQLite you have cannot store Null values. Please download and install the latest revision of your version of Python (for example, the latest Python 2.7 revision) and try again.\n') - logger.fatal('PyBitmessage will now exit very abruptly. You may now see threading errors related to this abrupt exit but the problem you need to solve is related to SQLite.\n\n') - os._exit(0) - except Exception as err: - if str(err) == 'database or disk is full': - logger.fatal('(While null value test) Alert: Your disk or data storage volume is full. sqlThread will now exit.') - shared.UISignalQueue.put(('alert', (tr.translateText("MainWindow", "Disk full"), tr.translateText("MainWindow", 'Alert: Your disk or data storage volume is full. Bitmessage will now exit.'), True))) - if shared.daemon: - os._exit(0) - else: - return - else: - logger.error(err) - - # Let us check to see the last time we vaccumed the messages.dat file. - # If it has been more than a month let's do it now. - item = '''SELECT value FROM settings WHERE key='lastvacuumtime';''' - parameters = '' - self.cur.execute(item, parameters) - queryreturn = self.cur.fetchall() - for row in queryreturn: - value, = row - if int(value) < int(time.time()) - 2592000: - logger.info('It has been a long time since the messages.dat file has been vacuumed. Vacuuming now...') - try: - self.cur.execute( ''' VACUUM ''') - except Exception as err: - if str(err) == 'database or disk is full': - logger.fatal('(While VACUUM) Alert: Your disk or data storage volume is full. sqlThread will now exit.') - shared.UISignalQueue.put(('alert', (tr.translateText("MainWindow", "Disk full"), tr.translateText("MainWindow", 'Alert: Your disk or data storage volume is full. Bitmessage will now exit.'), True))) - if shared.daemon: - os._exit(0) - else: - return - item = '''update settings set value=? WHERE key='lastvacuumtime';''' - parameters = (int(time.time()),) - self.cur.execute(item, parameters) - - while True: - item = shared.sqlSubmitQueue.get() - if item == 'commit': - try: - self.conn.commit() - except Exception as err: - if str(err) == 'database or disk is full': - logger.fatal('(While committing) Alert: Your disk or data storage volume is full. sqlThread will now exit.') - shared.UISignalQueue.put(('alert', (tr.translateText("MainWindow", "Disk full"), tr.translateText("MainWindow", 'Alert: Your disk or data storage volume is full. Bitmessage will now exit.'), True))) - if shared.daemon: - os._exit(0) - else: - return - elif item == 'exit': - self.conn.close() - logger.info('sqlThread exiting gracefully.') - - return - elif item == 'movemessagstoprog': - logger.debug('the sqlThread is moving the messages.dat file to the local program directory.') - - try: - self.conn.commit() - except Exception as err: - if str(err) == 'database or disk is full': - logger.fatal('(while movemessagstoprog) Alert: Your disk or data storage volume is full. sqlThread will now exit.') - shared.UISignalQueue.put(('alert', (tr.translateText("MainWindow", "Disk full"), tr.translateText("MainWindow", 'Alert: Your disk or data storage volume is full. Bitmessage will now exit.'), True))) - if shared.daemon: - os._exit(0) - else: - return - self.conn.close() - shutil.move( - shared.lookupAppdataFolder() + 'messages.dat', 'messages.dat') - self.conn = sqlite3.connect('messages.dat') - self.conn.text_factory = str - self.cur = self.conn.cursor() - elif item == 'movemessagstoappdata': - logger.debug('the sqlThread is moving the messages.dat file to the Appdata folder.') - - try: - self.conn.commit() - except Exception as err: - if str(err) == 'database or disk is full': - logger.fatal('(while movemessagstoappdata) Alert: Your disk or data storage volume is full. sqlThread will now exit.') - shared.UISignalQueue.put(('alert', (tr.translateText("MainWindow", "Disk full"), tr.translateText("MainWindow", 'Alert: Your disk or data storage volume is full. Bitmessage will now exit.'), True))) - if shared.daemon: - os._exit(0) - else: - return - self.conn.close() - shutil.move( - 'messages.dat', shared.lookupAppdataFolder() + 'messages.dat') - self.conn = sqlite3.connect(shared.appdata + 'messages.dat') - self.conn.text_factory = str - self.cur = self.conn.cursor() - elif item == 'deleteandvacuume': - self.cur.execute('''delete from inbox where folder='trash' ''') - self.cur.execute('''delete from sent where folder='trash' ''') - self.conn.commit() - try: - self.cur.execute( ''' VACUUM ''') - except Exception as err: - if str(err) == 'database or disk is full': - logger.fatal('(while deleteandvacuume) Alert: Your disk or data storage volume is full. sqlThread will now exit.') - shared.UISignalQueue.put(('alert', (tr.translateText("MainWindow", "Disk full"), tr.translateText("MainWindow", 'Alert: Your disk or data storage volume is full. Bitmessage will now exit.'), True))) - if shared.daemon: - os._exit(0) - else: - return - else: - parameters = shared.sqlSubmitQueue.get() - # print 'item', item - # print 'parameters', parameters - try: - self.cur.execute(item, parameters) - except Exception as err: - if str(err) == 'database or disk is full': - logger.fatal('(while cur.execute) Alert: Your disk or data storage volume is full. sqlThread will now exit.') - shared.UISignalQueue.put(('alert', (tr.translateText("MainWindow", "Disk full"), tr.translateText("MainWindow", 'Alert: Your disk or data storage volume is full. Bitmessage will now exit.'), True))) - if shared.daemon: - os._exit(0) - else: - return - else: - logger.fatal('Major error occurred when trying to execute a SQL statement within the sqlThread. Please tell Atheros about this error message or post it in the forum! Error occurred while trying to execute statement: "%s" Here are the parameters; you might want to censor this data with asterisks (***) as it can contain private information: %s. Here is the actual error message thrown by the sqlThread: %s', str(item), str(repr(parameters)), str(err)) - logger.fatal('This program shall now abruptly exit!') - - os._exit(0) - - shared.sqlReturnQueue.put(self.cur.fetchall()) - # shared.sqlSubmitQueue.task_done() From 7a7385496e5fbffc2a3fe3435295b2625b80d26c Mon Sep 17 00:00:00 2001 From: Jonathan Warren Date: Wed, 6 Nov 2013 23:03:36 -0500 Subject: [PATCH 43/48] Fix #472 --- COPYING | 3 ++- LICENSE | 3 ++- src/bitmessageqt/about.py | 31 ++++++++++++++++++++----------- src/bitmessageqt/about.ui | 6 +++--- 4 files changed, 27 insertions(+), 16 deletions(-) diff --git a/COPYING b/COPYING index e386f371..547f5487 100644 --- a/COPYING +++ b/COPYING @@ -1,4 +1,5 @@ -Copyright (c) 2012 Jonathan Warren +Copyright (c) 2012-2013 Jonathan Warren +Copyright (c) 2013 The Bitmessage Developers Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/LICENSE b/LICENSE index de7d6159..cac11fcf 100644 --- a/LICENSE +++ b/LICENSE @@ -1,5 +1,6 @@ The MIT License (MIT) -Copyright (c) 2013 Bitmessage +Copyright (c) 2012-2013 Jonathan Warren +Copyright (c) 2013 The Bitmessage Developers Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in diff --git a/src/bitmessageqt/about.py b/src/bitmessageqt/about.py index 3ede6a15..34f4c27b 100644 --- a/src/bitmessageqt/about.py +++ b/src/bitmessageqt/about.py @@ -2,8 +2,8 @@ # Form implementation generated from reading ui file 'about.ui' # -# Created: Mon Mar 11 11:19:35 2013 -# by: PyQt4 UI code generator 4.9.4 +# Created: Wed Nov 06 23:01:43 2013 +# by: PyQt4 UI code generator 4.10.2 # # WARNING! All changes made in this file will be lost! @@ -12,7 +12,16 @@ from PyQt4 import QtCore, QtGui try: _fromUtf8 = QtCore.QString.fromUtf8 except AttributeError: - _fromUtf8 = lambda s: s + def _fromUtf8(s): + return s + +try: + _encoding = QtGui.QApplication.UnicodeUTF8 + def _translate(context, text, disambig): + return QtGui.QApplication.translate(context, text, disambig, _encoding) +except AttributeError: + def _translate(context, text, disambig): + return QtGui.QApplication.translate(context, text, disambig) class Ui_aboutDialog(object): def setupUi(self, aboutDialog): @@ -35,7 +44,7 @@ class Ui_aboutDialog(object): self.labelVersion.setGeometry(QtCore.QRect(190, 126, 161, 20)) self.labelVersion.setObjectName(_fromUtf8("labelVersion")) self.label_2 = QtGui.QLabel(aboutDialog) - self.label_2.setGeometry(QtCore.QRect(10, 150, 341, 20)) + self.label_2.setGeometry(QtCore.QRect(10, 150, 341, 41)) self.label_2.setAlignment(QtCore.Qt.AlignCenter) self.label_2.setObjectName(_fromUtf8("label_2")) self.label_3 = QtGui.QLabel(aboutDialog) @@ -44,7 +53,7 @@ class Ui_aboutDialog(object): self.label_3.setOpenExternalLinks(True) self.label_3.setObjectName(_fromUtf8("label_3")) self.label_5 = QtGui.QLabel(aboutDialog) - self.label_5.setGeometry(QtCore.QRect(10, 180, 341, 20)) + self.label_5.setGeometry(QtCore.QRect(10, 190, 341, 20)) self.label_5.setAlignment(QtCore.Qt.AlignCenter) self.label_5.setObjectName(_fromUtf8("label_5")) @@ -54,10 +63,10 @@ class Ui_aboutDialog(object): QtCore.QMetaObject.connectSlotsByName(aboutDialog) def retranslateUi(self, aboutDialog): - aboutDialog.setWindowTitle(QtGui.QApplication.translate("aboutDialog", "About", None, QtGui.QApplication.UnicodeUTF8)) - self.label.setText(QtGui.QApplication.translate("aboutDialog", "PyBitmessage", None, QtGui.QApplication.UnicodeUTF8)) - self.labelVersion.setText(QtGui.QApplication.translate("aboutDialog", "version ?", None, QtGui.QApplication.UnicodeUTF8)) - self.label_2.setText(QtGui.QApplication.translate("aboutDialog", "Copyright © 2013 Jonathan Warren", None, QtGui.QApplication.UnicodeUTF8)) - self.label_3.setText(QtGui.QApplication.translate("aboutDialog", "

Distributed under the MIT/X11 software license; see http://www.opensource.org/licenses/mit-license.php

", None, QtGui.QApplication.UnicodeUTF8)) - self.label_5.setText(QtGui.QApplication.translate("aboutDialog", "This is Beta software.", None, QtGui.QApplication.UnicodeUTF8)) + aboutDialog.setWindowTitle(_translate("aboutDialog", "About", None)) + self.label.setText(_translate("aboutDialog", "PyBitmessage", None)) + self.labelVersion.setText(_translate("aboutDialog", "version ?", None)) + self.label_2.setText(_translate("aboutDialog", "

Copyright © 2012-2013 Jonathan Warren
Copyright © 2013 The Bitmessage Developers

", None)) + self.label_3.setText(_translate("aboutDialog", "

Distributed under the MIT/X11 software license; see http://www.opensource.org/licenses/mit-license.php

", None)) + self.label_5.setText(_translate("aboutDialog", "This is Beta software.", None)) diff --git a/src/bitmessageqt/about.ui b/src/bitmessageqt/about.ui index 58a0bc36..1bf89fd9 100644 --- a/src/bitmessageqt/about.ui +++ b/src/bitmessageqt/about.ui @@ -70,11 +70,11 @@ 10 150 341 - 20 + 41 - Copyright © 2013 Jonathan Warren + <html><head/><body><p>Copyright © 2012-2013 Jonathan Warren<br/>Copyright © 2013 The Bitmessage Developers</p></body></html> Qt::AlignCenter @@ -103,7 +103,7 @@ 10 - 180 + 190 341 20 From d34114d14c8cf2595199c2fab03f8b4b7d518de1 Mon Sep 17 00:00:00 2001 From: Jonathan Warren Date: Wed, 6 Nov 2013 23:38:19 -0500 Subject: [PATCH 44/48] Fix #541 --- src/class_singleWorker.py | 35 ++++++++++++++++++++++++++--------- 1 file changed, 26 insertions(+), 9 deletions(-) diff --git a/src/class_singleWorker.py b/src/class_singleWorker.py index 2680f307..59d72ac3 100644 --- a/src/class_singleWorker.py +++ b/src/class_singleWorker.py @@ -147,17 +147,26 @@ class singleWorker(threading.Thread): shared.broadcastToSendDataQueues(( streamNumber, 'advertiseobject', inventoryHash)) shared.UISignalQueue.put(('updateStatusBar', '')) - shared.config.set( - myAddress, 'lastpubkeysendtime', str(int(time.time()))) - with open(shared.appdata + 'keys.dat', 'wb') as configfile: - shared.config.write(configfile) + try: + shared.config.set( + myAddress, 'lastpubkeysendtime', str(int(time.time()))) + with open(shared.appdata + 'keys.dat', 'wb') as configfile: + shared.config.write(configfile) + except: + # The user deleted the address out of the keys.dat file before this + # finished. + pass # If this isn't a chan address, this function assembles the pubkey data, # does the necessary POW and sends it out. If it *is* a chan then it # assembles the pubkey and stores is in the pubkey table so that we can # send messages to "ourselves". def sendOutOrStoreMyV3Pubkey(self, hash): - myAddress = shared.myAddressesByHash[hash] + try: + myAddress = shared.myAddressesByHash[hash] + except: + #The address has been deleted. + return if shared.safeConfigGetBoolean(myAddress, 'chan'): with shared.printLock: print 'This is a chan address. Not sending pubkey.' @@ -224,14 +233,22 @@ class singleWorker(threading.Thread): shared.broadcastToSendDataQueues(( streamNumber, 'advertiseobject', inventoryHash)) shared.UISignalQueue.put(('updateStatusBar', '')) - shared.config.set( - myAddress, 'lastpubkeysendtime', str(int(time.time()))) - with open(shared.appdata + 'keys.dat', 'wb') as configfile: - shared.config.write(configfile) + try: + shared.config.set( + myAddress, 'lastpubkeysendtime', str(int(time.time()))) + with open(shared.appdata + 'keys.dat', 'wb') as configfile: + shared.config.write(configfile) + except: + # The user deleted the address out of the keys.dat file before this + # finished. + pass # If this isn't a chan address, this function assembles the pubkey data, # does the necessary POW and sends it out. def sendOutOrStoreMyV4Pubkey(self, myAddress): + if not shared.config.has_section(myAddress): + #The address has been deleted. + return if shared.safeConfigGetBoolean(myAddress, 'chan'): with shared.printLock: print 'This is a chan address. Not sending pubkey.' From 74c7c99511bead99c5b58ba5a03d444e8240418f Mon Sep 17 00:00:00 2001 From: Jonathan Warren Date: Wed, 6 Nov 2013 23:41:37 -0500 Subject: [PATCH 45/48] Fix #544 --- src/bitmessageqt/__init__.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/bitmessageqt/__init__.py b/src/bitmessageqt/__init__.py index 55887b7a..96172742 100644 --- a/src/bitmessageqt/__init__.py +++ b/src/bitmessageqt/__init__.py @@ -1218,7 +1218,14 @@ class MyForm(QtGui.QMainWindow): if withMessagingMenu: n = Notify.Notification.new( title, subtitle, 'notification-message-email') - n.show() + try: + n.show() + except: + # n.show() has been known to throw this exception: + # gi._glib.GError: GDBus.Error:org.freedesktop.Notifications. + # MaxNotificationsExceeded: Exceeded maximum number of + # notifications + pass return else: self.tray.showMessage(title, subtitle, 1, 2000) From d150193f433a19869ae2394b506e6ef990a3fbf5 Mon Sep 17 00:00:00 2001 From: Jonathan Warren Date: Wed, 6 Nov 2013 23:51:42 -0500 Subject: [PATCH 46/48] Fix #545 --- src/defaultKnownNodes.py | 21 +++++++++------------ 1 file changed, 9 insertions(+), 12 deletions(-) diff --git a/src/defaultKnownNodes.py b/src/defaultKnownNodes.py index 92e53339..201e913d 100644 --- a/src/defaultKnownNodes.py +++ b/src/defaultKnownNodes.py @@ -11,19 +11,16 @@ def createDefaultKnownNodes(appdata): ############## Stream 1 ################ stream1 = {} - stream1[shared.Peer('98.233.84.134', 8444)] = int(time.time()) - stream1[shared.Peer('76.24.18.44', 8444)] = int(time.time()) stream1[shared.Peer('176.31.246.114', 8444)] = int(time.time()) - stream1[shared.Peer('66.108.210.240', 8080)] = int(time.time()) - stream1[shared.Peer('76.22.240.163', 8445)] = int(time.time()) - stream1[shared.Peer('46.21.99.29', 20982)] = int(time.time()) - stream1[shared.Peer('109.227.72.36', 8444)] = int(time.time()) - stream1[shared.Peer('176.31.246.114', 8444)] = int(time.time()) - stream1[shared.Peer('188.110.3.133', 8444)] = int(time.time()) - stream1[shared.Peer('174.0.45.163', 8444)] = int(time.time()) - stream1[shared.Peer('134.2.182.92', 8444)] = int(time.time()) - stream1[shared.Peer('24.143.60.183', 8444)] = int(time.time()) - + stream1[shared.Peer('109.229.197.133', 8444)] = int(time.time()) + stream1[shared.Peer('174.3.101.111', 8444)] = int(time.time()) + stream1[shared.Peer('90.188.238.79', 7829)] = int(time.time()) + stream1[shared.Peer('184.75.69.2', 8444)] = int(time.time()) + stream1[shared.Peer('60.225.209.243', 8444)] = int(time.time()) + stream1[shared.Peer('5.145.140.218', 8444)] = int(time.time()) + stream1[shared.Peer('5.19.255.216', 8444)] = int(time.time()) + stream1[shared.Peer('193.159.162.189', 8444)] = int(time.time()) + stream1[shared.Peer('86.26.15.171', 8444)] = int(time.time()) ############# Stream 2 ################# stream2 = {} From 7a2115b8892d7582a680205489d63061382b06be Mon Sep 17 00:00:00 2001 From: neko259 Date: Thu, 7 Nov 2013 21:35:11 +0200 Subject: [PATCH 47/48] Refactoring main qt module. Moved popup menus initialization to separate methods, simplified inbox loading and moved magic strings to the model scope constants so they won't be created every time. --- src/bitmessageqt/__init__.py | 561 ++++++++++++++++++++--------------- 1 file changed, 318 insertions(+), 243 deletions(-) diff --git a/src/bitmessageqt/__init__.py b/src/bitmessageqt/__init__.py index dae47e85..f6d32df1 100644 --- a/src/bitmessageqt/__init__.py +++ b/src/bitmessageqt/__init__.py @@ -54,9 +54,24 @@ try: except AttributeError: print 'QtGui.QApplication.UnicodeUTF8 error:', err +FIELD_COLUMNS = { + 'To': 'toaddress', + 'From': 'fromaddress', + 'Subject': 'subject', + 'Message': 'message,' +} + +SELECT_INBOX_QUERY = ''' + SELECT msgid, toaddress, fromaddress, subject, received, read + FROM inbox WHERE folder="inbox" AND %s LIKE ? + ORDER BY received + ''' + + def _translate(context, text): return QtGui.QApplication.translate(context, text) + def identiconize(address): size = 48 @@ -176,6 +191,269 @@ class MyForm(QtGui.QMainWindow): str_broadcast_subscribers = '[Broadcast subscribers]' str_chan = '[chan]' + def init_file_menu(self): + QtCore.QObject.connect(self.ui.actionExit, QtCore.SIGNAL( + "triggered()"), self.quit) + QtCore.QObject.connect(self.ui.actionManageKeys, QtCore.SIGNAL( + "triggered()"), self.click_actionManageKeys) + QtCore.QObject.connect(self.ui.actionDeleteAllTrashedMessages, + QtCore.SIGNAL( + "triggered()"), + self.click_actionDeleteAllTrashedMessages) + QtCore.QObject.connect(self.ui.actionRegenerateDeterministicAddresses, + QtCore.SIGNAL( + "triggered()"), + self.click_actionRegenerateDeterministicAddresses) + QtCore.QObject.connect(self.ui.actionJoinChan, QtCore.SIGNAL( + "triggered()"), + self.click_actionJoinChan) # also used for creating chans. + QtCore.QObject.connect(self.ui.pushButtonNewAddress, QtCore.SIGNAL( + "clicked()"), self.click_NewAddressDialog) + QtCore.QObject.connect(self.ui.comboBoxSendFrom, QtCore.SIGNAL( + "activated(int)"), self.redrawLabelFrom) + QtCore.QObject.connect(self.ui.pushButtonAddAddressBook, QtCore.SIGNAL( + "clicked()"), self.click_pushButtonAddAddressBook) + QtCore.QObject.connect(self.ui.pushButtonAddSubscription, QtCore.SIGNAL( + "clicked()"), self.click_pushButtonAddSubscription) + QtCore.QObject.connect(self.ui.pushButtonAddBlacklist, QtCore.SIGNAL( + "clicked()"), self.click_pushButtonAddBlacklist) + QtCore.QObject.connect(self.ui.pushButtonSend, QtCore.SIGNAL( + "clicked()"), self.click_pushButtonSend) + QtCore.QObject.connect(self.ui.pushButtonLoadFromAddressBook, + QtCore.SIGNAL( + "clicked()"), + self.click_pushButtonLoadFromAddressBook) + QtCore.QObject.connect(self.ui.pushButtonFetchNamecoinID, QtCore.SIGNAL( + "clicked()"), self.click_pushButtonFetchNamecoinID) + QtCore.QObject.connect(self.ui.radioButtonBlacklist, QtCore.SIGNAL( + "clicked()"), self.click_radioButtonBlacklist) + QtCore.QObject.connect(self.ui.radioButtonWhitelist, QtCore.SIGNAL( + "clicked()"), self.click_radioButtonWhitelist) + QtCore.QObject.connect(self.ui.pushButtonStatusIcon, QtCore.SIGNAL( + "clicked()"), self.click_pushButtonStatusIcon) + QtCore.QObject.connect(self.ui.actionSettings, QtCore.SIGNAL( + "triggered()"), self.click_actionSettings) + QtCore.QObject.connect(self.ui.actionAbout, QtCore.SIGNAL( + "triggered()"), self.click_actionAbout) + QtCore.QObject.connect(self.ui.actionHelp, QtCore.SIGNAL( + "triggered()"), self.click_actionHelp) + + def init_inbox_popup_menu(self): + # Popup menu for the Inbox tab + self.ui.inboxContextMenuToolbar = QtGui.QToolBar() + # Actions + self.actionReply = self.ui.inboxContextMenuToolbar.addAction(_translate( + "MainWindow", "Reply"), self.on_action_InboxReply) + self.actionAddSenderToAddressBook = self.ui.inboxContextMenuToolbar.addAction( + _translate( + "MainWindow", "Add sender to your Address Book"), + self.on_action_InboxAddSenderToAddressBook) + self.actionTrashInboxMessage = self.ui.inboxContextMenuToolbar.addAction( + _translate("MainWindow", "Move to Trash"), + self.on_action_InboxTrash) + self.actionForceHtml = self.ui.inboxContextMenuToolbar.addAction( + _translate( + "MainWindow", "View HTML code as formatted text"), + self.on_action_InboxMessageForceHtml) + self.actionSaveMessageAs = self.ui.inboxContextMenuToolbar.addAction( + _translate( + "MainWindow", "Save message as..."), + self.on_action_InboxSaveMessageAs) + self.actionMarkUnread = self.ui.inboxContextMenuToolbar.addAction( + _translate( + "MainWindow", "Mark Unread"), self.on_action_InboxMarkUnread) + self.ui.tableWidgetInbox.setContextMenuPolicy( + QtCore.Qt.CustomContextMenu) + self.connect(self.ui.tableWidgetInbox, QtCore.SIGNAL( + 'customContextMenuRequested(const QPoint&)'), + self.on_context_menuInbox) + self.popMenuInbox = QtGui.QMenu(self) + self.popMenuInbox.addAction(self.actionForceHtml) + self.popMenuInbox.addAction(self.actionMarkUnread) + self.popMenuInbox.addSeparator() + self.popMenuInbox.addAction(self.actionReply) + self.popMenuInbox.addAction(self.actionAddSenderToAddressBook) + self.popMenuInbox.addSeparator() + self.popMenuInbox.addAction(self.actionSaveMessageAs) + self.popMenuInbox.addAction(self.actionTrashInboxMessage) + + def init_identities_popup_menu(self): + # Popup menu for the Your Identities tab + self.ui.addressContextMenuToolbar = QtGui.QToolBar() + # Actions + self.actionNew = self.ui.addressContextMenuToolbar.addAction(_translate( + "MainWindow", "New"), self.on_action_YourIdentitiesNew) + self.actionEnable = self.ui.addressContextMenuToolbar.addAction( + _translate( + "MainWindow", "Enable"), self.on_action_YourIdentitiesEnable) + self.actionDisable = self.ui.addressContextMenuToolbar.addAction( + _translate( + "MainWindow", "Disable"), self.on_action_YourIdentitiesDisable) + self.actionSetAvatar = self.ui.addressContextMenuToolbar.addAction( + _translate( + "MainWindow", "Set avatar..."), + self.on_action_YourIdentitiesSetAvatar) + self.actionClipboard = self.ui.addressContextMenuToolbar.addAction( + _translate( + "MainWindow", "Copy address to clipboard"), + self.on_action_YourIdentitiesClipboard) + self.actionSpecialAddressBehavior = self.ui.addressContextMenuToolbar.addAction( + _translate( + "MainWindow", "Special address behavior..."), + self.on_action_SpecialAddressBehaviorDialog) + self.ui.tableWidgetYourIdentities.setContextMenuPolicy( + QtCore.Qt.CustomContextMenu) + self.connect(self.ui.tableWidgetYourIdentities, QtCore.SIGNAL( + 'customContextMenuRequested(const QPoint&)'), + self.on_context_menuYourIdentities) + self.popMenu = QtGui.QMenu(self) + self.popMenu.addAction(self.actionNew) + self.popMenu.addSeparator() + self.popMenu.addAction(self.actionClipboard) + self.popMenu.addSeparator() + self.popMenu.addAction(self.actionEnable) + self.popMenu.addAction(self.actionDisable) + self.popMenu.addAction(self.actionSetAvatar) + self.popMenu.addAction(self.actionSpecialAddressBehavior) + + def init_addressbook_popup_menu(self): + # Popup menu for the Address Book page + self.ui.addressBookContextMenuToolbar = QtGui.QToolBar() + # Actions + self.actionAddressBookSend = self.ui.addressBookContextMenuToolbar.addAction( + _translate( + "MainWindow", "Send message to this address"), + self.on_action_AddressBookSend) + self.actionAddressBookClipboard = self.ui.addressBookContextMenuToolbar.addAction( + _translate( + "MainWindow", "Copy address to clipboard"), + self.on_action_AddressBookClipboard) + self.actionAddressBookSubscribe = self.ui.addressBookContextMenuToolbar.addAction( + _translate( + "MainWindow", "Subscribe to this address"), + self.on_action_AddressBookSubscribe) + self.actionAddressBookSetAvatar = self.ui.addressBookContextMenuToolbar.addAction( + _translate( + "MainWindow", "Set avatar..."), + self.on_action_AddressBookSetAvatar) + self.actionAddressBookNew = self.ui.addressBookContextMenuToolbar.addAction( + _translate( + "MainWindow", "Add New Address"), self.on_action_AddressBookNew) + self.actionAddressBookDelete = self.ui.addressBookContextMenuToolbar.addAction( + _translate( + "MainWindow", "Delete"), self.on_action_AddressBookDelete) + self.ui.tableWidgetAddressBook.setContextMenuPolicy( + QtCore.Qt.CustomContextMenu) + self.connect(self.ui.tableWidgetAddressBook, QtCore.SIGNAL( + 'customContextMenuRequested(const QPoint&)'), + self.on_context_menuAddressBook) + self.popMenuAddressBook = QtGui.QMenu(self) + self.popMenuAddressBook.addAction(self.actionAddressBookSend) + self.popMenuAddressBook.addAction(self.actionAddressBookClipboard) + self.popMenuAddressBook.addAction(self.actionAddressBookSubscribe) + self.popMenuAddressBook.addAction(self.actionAddressBookSetAvatar) + self.popMenuAddressBook.addSeparator() + self.popMenuAddressBook.addAction(self.actionAddressBookNew) + self.popMenuAddressBook.addAction(self.actionAddressBookDelete) + + def init_subscriptions_popup_menu(self): + # Popup menu for the Subscriptions page + self.ui.subscriptionsContextMenuToolbar = QtGui.QToolBar() + # Actions + self.actionsubscriptionsNew = self.ui.subscriptionsContextMenuToolbar.addAction( + _translate("MainWindow", "New"), self.on_action_SubscriptionsNew) + self.actionsubscriptionsDelete = self.ui.subscriptionsContextMenuToolbar.addAction( + _translate("MainWindow", "Delete"), + self.on_action_SubscriptionsDelete) + self.actionsubscriptionsClipboard = self.ui.subscriptionsContextMenuToolbar.addAction( + _translate("MainWindow", "Copy address to clipboard"), + self.on_action_SubscriptionsClipboard) + self.actionsubscriptionsEnable = self.ui.subscriptionsContextMenuToolbar.addAction( + _translate("MainWindow", "Enable"), + self.on_action_SubscriptionsEnable) + self.actionsubscriptionsDisable = self.ui.subscriptionsContextMenuToolbar.addAction( + _translate("MainWindow", "Disable"), + self.on_action_SubscriptionsDisable) + self.actionsubscriptionsSetAvatar = self.ui.subscriptionsContextMenuToolbar.addAction( + _translate("MainWindow", "Set avatar..."), + self.on_action_SubscriptionsSetAvatar) + self.ui.tableWidgetSubscriptions.setContextMenuPolicy( + QtCore.Qt.CustomContextMenu) + self.connect(self.ui.tableWidgetSubscriptions, QtCore.SIGNAL( + 'customContextMenuRequested(const QPoint&)'), + self.on_context_menuSubscriptions) + self.popMenuSubscriptions = QtGui.QMenu(self) + self.popMenuSubscriptions.addAction(self.actionsubscriptionsNew) + self.popMenuSubscriptions.addAction(self.actionsubscriptionsDelete) + self.popMenuSubscriptions.addSeparator() + self.popMenuSubscriptions.addAction(self.actionsubscriptionsEnable) + self.popMenuSubscriptions.addAction(self.actionsubscriptionsDisable) + self.popMenuSubscriptions.addAction(self.actionsubscriptionsSetAvatar) + self.popMenuSubscriptions.addSeparator() + self.popMenuSubscriptions.addAction(self.actionsubscriptionsClipboard) + + def init_sent_popup_menu(self): + # Popup menu for the Sent page + self.ui.sentContextMenuToolbar = QtGui.QToolBar() + # Actions + self.actionTrashSentMessage = self.ui.sentContextMenuToolbar.addAction( + _translate( + "MainWindow", "Move to Trash"), self.on_action_SentTrash) + self.actionSentClipboard = self.ui.sentContextMenuToolbar.addAction( + _translate( + "MainWindow", "Copy destination address to clipboard"), + self.on_action_SentClipboard) + self.actionForceSend = self.ui.sentContextMenuToolbar.addAction( + _translate( + "MainWindow", "Force send"), self.on_action_ForceSend) + self.ui.tableWidgetSent.setContextMenuPolicy( + QtCore.Qt.CustomContextMenu) + self.connect(self.ui.tableWidgetSent, QtCore.SIGNAL( + 'customContextMenuRequested(const QPoint&)'), + self.on_context_menuSent) + # self.popMenuSent = QtGui.QMenu( self ) + # self.popMenuSent.addAction( self.actionSentClipboard ) + # self.popMenuSent.addAction( self.actionTrashSentMessage ) + + def init_blacklist_popup_menu(self): + # Popup menu for the Blacklist page + self.ui.blacklistContextMenuToolbar = QtGui.QToolBar() + # Actions + self.actionBlacklistNew = self.ui.blacklistContextMenuToolbar.addAction( + _translate( + "MainWindow", "Add new entry"), self.on_action_BlacklistNew) + self.actionBlacklistDelete = self.ui.blacklistContextMenuToolbar.addAction( + _translate( + "MainWindow", "Delete"), self.on_action_BlacklistDelete) + self.actionBlacklistClipboard = self.ui.blacklistContextMenuToolbar.addAction( + _translate( + "MainWindow", "Copy address to clipboard"), + self.on_action_BlacklistClipboard) + self.actionBlacklistEnable = self.ui.blacklistContextMenuToolbar.addAction( + _translate( + "MainWindow", "Enable"), self.on_action_BlacklistEnable) + self.actionBlacklistDisable = self.ui.blacklistContextMenuToolbar.addAction( + _translate( + "MainWindow", "Disable"), self.on_action_BlacklistDisable) + self.actionBlacklistSetAvatar = self.ui.blacklistContextMenuToolbar.addAction( + _translate( + "MainWindow", "Set avatar..."), + self.on_action_BlacklistSetAvatar) + self.ui.tableWidgetBlacklist.setContextMenuPolicy( + QtCore.Qt.CustomContextMenu) + self.connect(self.ui.tableWidgetBlacklist, QtCore.SIGNAL( + 'customContextMenuRequested(const QPoint&)'), + self.on_context_menuBlacklist) + self.popMenuBlacklist = QtGui.QMenu(self) + # self.popMenuBlacklist.addAction( self.actionBlacklistNew ) + self.popMenuBlacklist.addAction(self.actionBlacklistDelete) + self.popMenuBlacklist.addSeparator() + self.popMenuBlacklist.addAction(self.actionBlacklistClipboard) + self.popMenuBlacklist.addSeparator() + self.popMenuBlacklist.addAction(self.actionBlacklistEnable) + self.popMenuBlacklist.addAction(self.actionBlacklistDisable) + self.popMenuBlacklist.addAction(self.actionBlacklistSetAvatar) + def __init__(self, parent=None): QtGui.QWidget.__init__(self, parent) self.ui = Ui_MainWindow() @@ -222,206 +500,13 @@ class MyForm(QtGui.QMainWindow): self.timer.start(2000) # milliseconds QtCore.QObject.connect(self.timer, QtCore.SIGNAL("timeout()"), self.runEveryTwoSeconds) - # FILE MENU and other buttons - QtCore.QObject.connect(self.ui.actionExit, QtCore.SIGNAL( - "triggered()"), self.quit) - QtCore.QObject.connect(self.ui.actionManageKeys, QtCore.SIGNAL( - "triggered()"), self.click_actionManageKeys) - QtCore.QObject.connect(self.ui.actionDeleteAllTrashedMessages, QtCore.SIGNAL( - "triggered()"), self.click_actionDeleteAllTrashedMessages) - QtCore.QObject.connect(self.ui.actionRegenerateDeterministicAddresses, QtCore.SIGNAL( - "triggered()"), self.click_actionRegenerateDeterministicAddresses) - QtCore.QObject.connect(self.ui.actionJoinChan, QtCore.SIGNAL( - "triggered()"), self.click_actionJoinChan) # also used for creating chans. - QtCore.QObject.connect(self.ui.pushButtonNewAddress, QtCore.SIGNAL( - "clicked()"), self.click_NewAddressDialog) - QtCore.QObject.connect(self.ui.comboBoxSendFrom, QtCore.SIGNAL( - "activated(int)"), self.redrawLabelFrom) - QtCore.QObject.connect(self.ui.pushButtonAddAddressBook, QtCore.SIGNAL( - "clicked()"), self.click_pushButtonAddAddressBook) - QtCore.QObject.connect(self.ui.pushButtonAddSubscription, QtCore.SIGNAL( - "clicked()"), self.click_pushButtonAddSubscription) - QtCore.QObject.connect(self.ui.pushButtonAddBlacklist, QtCore.SIGNAL( - "clicked()"), self.click_pushButtonAddBlacklist) - QtCore.QObject.connect(self.ui.pushButtonSend, QtCore.SIGNAL( - "clicked()"), self.click_pushButtonSend) - QtCore.QObject.connect(self.ui.pushButtonLoadFromAddressBook, QtCore.SIGNAL( - "clicked()"), self.click_pushButtonLoadFromAddressBook) - QtCore.QObject.connect(self.ui.pushButtonFetchNamecoinID, QtCore.SIGNAL( - "clicked()"), self.click_pushButtonFetchNamecoinID) - QtCore.QObject.connect(self.ui.radioButtonBlacklist, QtCore.SIGNAL( - "clicked()"), self.click_radioButtonBlacklist) - QtCore.QObject.connect(self.ui.radioButtonWhitelist, QtCore.SIGNAL( - "clicked()"), self.click_radioButtonWhitelist) - QtCore.QObject.connect(self.ui.pushButtonStatusIcon, QtCore.SIGNAL( - "clicked()"), self.click_pushButtonStatusIcon) - QtCore.QObject.connect(self.ui.actionSettings, QtCore.SIGNAL( - "triggered()"), self.click_actionSettings) - QtCore.QObject.connect(self.ui.actionAbout, QtCore.SIGNAL( - "triggered()"), self.click_actionAbout) - QtCore.QObject.connect(self.ui.actionHelp, QtCore.SIGNAL( - "triggered()"), self.click_actionHelp) - - # Popup menu for the Inbox tab - self.ui.inboxContextMenuToolbar = QtGui.QToolBar() - # Actions - self.actionReply = self.ui.inboxContextMenuToolbar.addAction(_translate( - "MainWindow", "Reply"), self.on_action_InboxReply) - self.actionAddSenderToAddressBook = self.ui.inboxContextMenuToolbar.addAction(_translate( - "MainWindow", "Add sender to your Address Book"), self.on_action_InboxAddSenderToAddressBook) - self.actionTrashInboxMessage = self.ui.inboxContextMenuToolbar.addAction( - _translate("MainWindow", "Move to Trash"), self.on_action_InboxTrash) - self.actionForceHtml = self.ui.inboxContextMenuToolbar.addAction(_translate( - "MainWindow", "View HTML code as formatted text"), self.on_action_InboxMessageForceHtml) - self.actionSaveMessageAs = self.ui.inboxContextMenuToolbar.addAction(_translate( - "MainWindow", "Save message as..."), self.on_action_InboxSaveMessageAs) - self.actionMarkUnread = self.ui.inboxContextMenuToolbar.addAction(_translate( - "MainWindow", "Mark Unread"), self.on_action_InboxMarkUnread) - self.ui.tableWidgetInbox.setContextMenuPolicy( - QtCore.Qt.CustomContextMenu) - self.connect(self.ui.tableWidgetInbox, QtCore.SIGNAL( - 'customContextMenuRequested(const QPoint&)'), self.on_context_menuInbox) - self.popMenuInbox = QtGui.QMenu(self) - self.popMenuInbox.addAction(self.actionForceHtml) - self.popMenuInbox.addAction(self.actionMarkUnread) - self.popMenuInbox.addSeparator() - self.popMenuInbox.addAction(self.actionReply) - self.popMenuInbox.addAction(self.actionAddSenderToAddressBook) - self.popMenuInbox.addSeparator() - self.popMenuInbox.addAction( self.actionSaveMessageAs ) - self.popMenuInbox.addAction( self.actionTrashInboxMessage ) - - # Popup menu for the Your Identities tab - self.ui.addressContextMenuToolbar = QtGui.QToolBar() - # Actions - self.actionNew = self.ui.addressContextMenuToolbar.addAction(_translate( - "MainWindow", "New"), self.on_action_YourIdentitiesNew) - self.actionEnable = self.ui.addressContextMenuToolbar.addAction(_translate( - "MainWindow", "Enable"), self.on_action_YourIdentitiesEnable) - self.actionDisable = self.ui.addressContextMenuToolbar.addAction(_translate( - "MainWindow", "Disable"), self.on_action_YourIdentitiesDisable) - self.actionSetAvatar = self.ui.addressContextMenuToolbar.addAction(_translate( - "MainWindow", "Set avatar..."), self.on_action_YourIdentitiesSetAvatar) - self.actionClipboard = self.ui.addressContextMenuToolbar.addAction(_translate( - "MainWindow", "Copy address to clipboard"), self.on_action_YourIdentitiesClipboard) - self.actionSpecialAddressBehavior = self.ui.addressContextMenuToolbar.addAction(_translate( - "MainWindow", "Special address behavior..."), self.on_action_SpecialAddressBehaviorDialog) - self.ui.tableWidgetYourIdentities.setContextMenuPolicy( - QtCore.Qt.CustomContextMenu) - self.connect(self.ui.tableWidgetYourIdentities, QtCore.SIGNAL( - 'customContextMenuRequested(const QPoint&)'), self.on_context_menuYourIdentities) - self.popMenu = QtGui.QMenu(self) - self.popMenu.addAction(self.actionNew) - self.popMenu.addSeparator() - self.popMenu.addAction(self.actionClipboard) - self.popMenu.addSeparator() - self.popMenu.addAction(self.actionEnable) - self.popMenu.addAction(self.actionDisable) - self.popMenu.addAction(self.actionSetAvatar) - self.popMenu.addAction(self.actionSpecialAddressBehavior) - - # Popup menu for the Address Book page - self.ui.addressBookContextMenuToolbar = QtGui.QToolBar() - # Actions - self.actionAddressBookSend = self.ui.addressBookContextMenuToolbar.addAction(_translate( - "MainWindow", "Send message to this address"), self.on_action_AddressBookSend) - self.actionAddressBookClipboard = self.ui.addressBookContextMenuToolbar.addAction(_translate( - "MainWindow", "Copy address to clipboard"), self.on_action_AddressBookClipboard) - self.actionAddressBookSubscribe = self.ui.addressBookContextMenuToolbar.addAction(_translate( - "MainWindow", "Subscribe to this address"), self.on_action_AddressBookSubscribe) - self.actionAddressBookSetAvatar = self.ui.addressBookContextMenuToolbar.addAction(_translate( - "MainWindow", "Set avatar..."), self.on_action_AddressBookSetAvatar) - self.actionAddressBookNew = self.ui.addressBookContextMenuToolbar.addAction(_translate( - "MainWindow", "Add New Address"), self.on_action_AddressBookNew) - self.actionAddressBookDelete = self.ui.addressBookContextMenuToolbar.addAction(_translate( - "MainWindow", "Delete"), self.on_action_AddressBookDelete) - self.ui.tableWidgetAddressBook.setContextMenuPolicy( - QtCore.Qt.CustomContextMenu) - self.connect(self.ui.tableWidgetAddressBook, QtCore.SIGNAL( - 'customContextMenuRequested(const QPoint&)'), self.on_context_menuAddressBook) - self.popMenuAddressBook = QtGui.QMenu(self) - self.popMenuAddressBook.addAction(self.actionAddressBookSend) - self.popMenuAddressBook.addAction(self.actionAddressBookClipboard) - self.popMenuAddressBook.addAction(self.actionAddressBookSubscribe) - self.popMenuAddressBook.addAction(self.actionAddressBookSetAvatar) - self.popMenuAddressBook.addSeparator() - self.popMenuAddressBook.addAction(self.actionAddressBookNew) - self.popMenuAddressBook.addAction(self.actionAddressBookDelete) - - # Popup menu for the Subscriptions page - self.ui.subscriptionsContextMenuToolbar = QtGui.QToolBar() - # Actions - self.actionsubscriptionsNew = self.ui.subscriptionsContextMenuToolbar.addAction( - _translate("MainWindow", "New"), self.on_action_SubscriptionsNew) - self.actionsubscriptionsDelete = self.ui.subscriptionsContextMenuToolbar.addAction( - _translate("MainWindow", "Delete"), self.on_action_SubscriptionsDelete) - self.actionsubscriptionsClipboard = self.ui.subscriptionsContextMenuToolbar.addAction( - _translate("MainWindow", "Copy address to clipboard"), self.on_action_SubscriptionsClipboard) - self.actionsubscriptionsEnable = self.ui.subscriptionsContextMenuToolbar.addAction( - _translate("MainWindow", "Enable"), self.on_action_SubscriptionsEnable) - self.actionsubscriptionsDisable = self.ui.subscriptionsContextMenuToolbar.addAction( - _translate("MainWindow", "Disable"), self.on_action_SubscriptionsDisable) - self.actionsubscriptionsSetAvatar = self.ui.subscriptionsContextMenuToolbar.addAction( - _translate("MainWindow", "Set avatar..."), self.on_action_SubscriptionsSetAvatar) - self.ui.tableWidgetSubscriptions.setContextMenuPolicy( - QtCore.Qt.CustomContextMenu) - self.connect(self.ui.tableWidgetSubscriptions, QtCore.SIGNAL( - 'customContextMenuRequested(const QPoint&)'), self.on_context_menuSubscriptions) - self.popMenuSubscriptions = QtGui.QMenu(self) - self.popMenuSubscriptions.addAction(self.actionsubscriptionsNew) - self.popMenuSubscriptions.addAction(self.actionsubscriptionsDelete) - self.popMenuSubscriptions.addSeparator() - self.popMenuSubscriptions.addAction(self.actionsubscriptionsEnable) - self.popMenuSubscriptions.addAction(self.actionsubscriptionsDisable) - self.popMenuSubscriptions.addAction(self.actionsubscriptionsSetAvatar) - self.popMenuSubscriptions.addSeparator() - self.popMenuSubscriptions.addAction(self.actionsubscriptionsClipboard) - - # Popup menu for the Sent page - self.ui.sentContextMenuToolbar = QtGui.QToolBar() - # Actions - self.actionTrashSentMessage = self.ui.sentContextMenuToolbar.addAction(_translate( - "MainWindow", "Move to Trash"), self.on_action_SentTrash) - self.actionSentClipboard = self.ui.sentContextMenuToolbar.addAction(_translate( - "MainWindow", "Copy destination address to clipboard"), self.on_action_SentClipboard) - self.actionForceSend = self.ui.sentContextMenuToolbar.addAction(_translate( - "MainWindow", "Force send"), self.on_action_ForceSend) - self.ui.tableWidgetSent.setContextMenuPolicy( - QtCore.Qt.CustomContextMenu) - self.connect(self.ui.tableWidgetSent, QtCore.SIGNAL( - 'customContextMenuRequested(const QPoint&)'), self.on_context_menuSent) - # self.popMenuSent = QtGui.QMenu( self ) - # self.popMenuSent.addAction( self.actionSentClipboard ) - # self.popMenuSent.addAction( self.actionTrashSentMessage ) - - # Popup menu for the Blacklist page - self.ui.blacklistContextMenuToolbar = QtGui.QToolBar() - # Actions - self.actionBlacklistNew = self.ui.blacklistContextMenuToolbar.addAction(_translate( - "MainWindow", "Add new entry"), self.on_action_BlacklistNew) - self.actionBlacklistDelete = self.ui.blacklistContextMenuToolbar.addAction(_translate( - "MainWindow", "Delete"), self.on_action_BlacklistDelete) - self.actionBlacklistClipboard = self.ui.blacklistContextMenuToolbar.addAction(_translate( - "MainWindow", "Copy address to clipboard"), self.on_action_BlacklistClipboard) - self.actionBlacklistEnable = self.ui.blacklistContextMenuToolbar.addAction(_translate( - "MainWindow", "Enable"), self.on_action_BlacklistEnable) - self.actionBlacklistDisable = self.ui.blacklistContextMenuToolbar.addAction(_translate( - "MainWindow", "Disable"), self.on_action_BlacklistDisable) - self.actionBlacklistSetAvatar = self.ui.blacklistContextMenuToolbar.addAction(_translate( - "MainWindow", "Set avatar..."), self.on_action_BlacklistSetAvatar) - self.ui.tableWidgetBlacklist.setContextMenuPolicy( - QtCore.Qt.CustomContextMenu) - self.connect(self.ui.tableWidgetBlacklist, QtCore.SIGNAL( - 'customContextMenuRequested(const QPoint&)'), self.on_context_menuBlacklist) - self.popMenuBlacklist = QtGui.QMenu(self) - # self.popMenuBlacklist.addAction( self.actionBlacklistNew ) - self.popMenuBlacklist.addAction(self.actionBlacklistDelete) - self.popMenuBlacklist.addSeparator() - self.popMenuBlacklist.addAction(self.actionBlacklistClipboard) - self.popMenuBlacklist.addSeparator() - self.popMenuBlacklist.addAction(self.actionBlacklistEnable) - self.popMenuBlacklist.addAction(self.actionBlacklistDisable) - self.popMenuBlacklist.addAction(self.actionBlacklistSetAvatar) + self.init_file_menu() + self.init_inbox_popup_menu() + self.init_identities_popup_menu() + self.init_addressbook_popup_menu() + self.init_subscriptions_popup_menu() + self.init_sent_popup_menu() + self.init_blacklist_popup_menu() # Initialize the user's list of addresses on the 'Your Identities' tab. configSections = shared.config.sections() @@ -799,22 +884,12 @@ class MyForm(QtGui.QMainWindow): # Load inbox from messages database file def loadInbox(self, where="", what=""): what = "%" + what + "%" - if where == "To": - where = "toaddress" - elif where == "From": - where = "fromaddress" - elif where == "Subject": - where = "subject" - elif where == "Message": - where = "message" + if where in FIELD_COLUMNS: + where = FIELD_COLUMNS[where] else: where = "toaddress || fromaddress || subject || message" - sqlStatement = ''' - SELECT msgid, toaddress, fromaddress, subject, received, read - FROM inbox WHERE folder="inbox" AND %s LIKE ? - ORDER BY received - ''' % (where,) + sqlStatement = SELECT_INBOX_QUERY % (where,) while self.ui.tableWidgetInbox.rowCount() > 0: self.ui.tableWidgetInbox.removeRow(0) @@ -858,52 +933,52 @@ class MyForm(QtGui.QMainWindow): # message row self.ui.tableWidgetInbox.insertRow(0) # to - newItem = QtGui.QTableWidgetItem(unicode(toLabel, 'utf-8')) - newItem.setToolTip(unicode(toLabel, 'utf-8')) - newItem.setFlags( + to_item = QtGui.QTableWidgetItem(unicode(toLabel, 'utf-8')) + to_item.setToolTip(unicode(toLabel, 'utf-8')) + to_item.setFlags( QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled) if not read: - newItem.setFont(font) - newItem.setData(Qt.UserRole, str(toAddress)) + to_item.setFont(font) + to_item.setData(Qt.UserRole, str(toAddress)) if shared.safeConfigGetBoolean(toAddress, 'mailinglist'): - newItem.setTextColor(QtGui.QColor(137, 04, 177)) # magenta + to_item.setTextColor(QtGui.QColor(137, 04, 177)) # magenta if shared.safeConfigGetBoolean(str(toAddress), 'chan'): - newItem.setTextColor(QtGui.QColor(216, 119, 0)) # orange - newItem.setIcon(avatarize(toAddress)) - self.ui.tableWidgetInbox.setItem(0, 0, newItem) + to_item.setTextColor(QtGui.QColor(216, 119, 0)) # orange + to_item.setIcon(avatarize(toAddress)) + self.ui.tableWidgetInbox.setItem(0, 0, to_item) # from - newItem = QtGui.QTableWidgetItem(unicode(fromLabel, 'utf-8')) - newItem.setToolTip(unicode(fromLabel, 'utf-8')) - newItem.setFlags( + from_item = QtGui.QTableWidgetItem(unicode(fromLabel, 'utf-8')) + from_item.setToolTip(unicode(fromLabel, 'utf-8')) + from_item.setFlags( QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled) if not read: - newItem.setFont(font) - newItem.setData(Qt.UserRole, str(fromAddress)) + from_item.setFont(font) + from_item.setData(Qt.UserRole, str(fromAddress)) if shared.safeConfigGetBoolean(str(fromAddress), 'chan'): - newItem.setTextColor(QtGui.QColor(216, 119, 0)) # orange - newItem.setIcon(avatarize(fromAddress)) - self.ui.tableWidgetInbox.setItem(0, 1, newItem) + from_item.setTextColor(QtGui.QColor(216, 119, 0)) # orange + from_item.setIcon(avatarize(fromAddress)) + self.ui.tableWidgetInbox.setItem(0, 1, from_item) # subject - newItem = QtGui.QTableWidgetItem(unicode(subject, 'utf-8')) - newItem.setToolTip(unicode(subject, 'utf-8')) - #newItem.setData(Qt.UserRole, unicode(message, 'utf-8)')) # No longer hold the message in the table (and thus in memory); we'll use a SQL query when we need to display it. - newItem.setFlags( + subject_item = QtGui.QTableWidgetItem(unicode(subject, 'utf-8')) + subject_item.setToolTip(unicode(subject, 'utf-8')) + subject_item.setFlags( QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled) if not read: - newItem.setFont(font) - self.ui.tableWidgetInbox.setItem(0, 2, newItem) + subject_item.setFont(font) + self.ui.tableWidgetInbox.setItem(0, 2, subject_item) # time received - newItem = myTableWidgetItem(unicode(strftime(shared.config.get( + time_item = myTableWidgetItem(unicode(strftime(shared.config.get( 'bitmessagesettings', 'timeformat'), localtime(int(received))), 'utf-8')) - newItem.setToolTip(unicode(strftime(shared.config.get( + time_item.setToolTip(unicode(strftime(shared.config.get( 'bitmessagesettings', 'timeformat'), localtime(int(received))), 'utf-8')) - newItem.setData(Qt.UserRole, QByteArray(msgid)) - newItem.setData(33, int(received)) - newItem.setFlags( + time_item.setData(Qt.UserRole, QByteArray(msgid)) + time_item.setData(33, int(received)) + time_item.setFlags( QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled) if not read: - newItem.setFont(font) - self.ui.tableWidgetInbox.setItem(0, 3, newItem) + time_item.setFont(font) + self.ui.tableWidgetInbox.setItem(0, 3, time_item) + self.ui.tableWidgetInbox.sortItems(3, Qt.DescendingOrder) self.ui.tableWidgetInbox.keyPressEvent = self.tableWidgetInboxKeyPressEvent From 90fd189fa8f8f25d8ca596042c72eb12a63450a5 Mon Sep 17 00:00:00 2001 From: Jonathan Warren Date: Thu, 7 Nov 2013 16:36:40 -0500 Subject: [PATCH 48/48] minor style changes to previous commit --- src/bitmessageqt/__init__.py | 30 +++++++++++++----------------- 1 file changed, 13 insertions(+), 17 deletions(-) diff --git a/src/bitmessageqt/__init__.py b/src/bitmessageqt/__init__.py index f6d32df1..a3bf7ec3 100644 --- a/src/bitmessageqt/__init__.py +++ b/src/bitmessageqt/__init__.py @@ -54,20 +54,6 @@ try: except AttributeError: print 'QtGui.QApplication.UnicodeUTF8 error:', err -FIELD_COLUMNS = { - 'To': 'toaddress', - 'From': 'fromaddress', - 'Subject': 'subject', - 'Message': 'message,' -} - -SELECT_INBOX_QUERY = ''' - SELECT msgid, toaddress, fromaddress, subject, received, read - FROM inbox WHERE folder="inbox" AND %s LIKE ? - ORDER BY received - ''' - - def _translate(context, text): return QtGui.QApplication.translate(context, text) @@ -884,12 +870,22 @@ class MyForm(QtGui.QMainWindow): # Load inbox from messages database file def loadInbox(self, where="", what=""): what = "%" + what + "%" - if where in FIELD_COLUMNS: - where = FIELD_COLUMNS[where] + if where == "To": + where = "toaddress" + elif where == "From": + where = "fromaddress" + elif where == "Subject": + where = "subject" + elif where == "Message": + where = "message" else: where = "toaddress || fromaddress || subject || message" - sqlStatement = SELECT_INBOX_QUERY % (where,) + sqlStatement = ''' + SELECT msgid, toaddress, fromaddress, subject, received, read + FROM inbox WHERE folder="inbox" AND %s LIKE ? + ORDER BY received + ''' % (where,) while self.ui.tableWidgetInbox.rowCount() > 0: self.ui.tableWidgetInbox.removeRow(0)