From 31affd438f132177b98e58498c01078898a93d95 Mon Sep 17 00:00:00 2001 From: sendiulo Date: Thu, 19 Sep 2013 21:28:22 +0200 Subject: [PATCH] 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 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 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