From 3bcd7184f4374434531f0b983191d2567dffca40 Mon Sep 17 00:00:00 2001 From: fekt Date: Mon, 17 Oct 2022 23:49:45 -0400 Subject: [PATCH] Testing Updates --- doc/release-process.md | 8 + doc/relnotes/README.md | 15 + res/css/dark.css | 2 +- res/css/light.css | 2 +- res/css/midnight.css | 2 +- res/lock_closed.png | Bin 0 -> 1197 bytes res/lock_open.png | Bin 0 -> 1257 bytes res/remove.png | Bin 0 -> 1723 bytes res/send.png | Bin 0 -> 1750 bytes res/synced.png | Bin 0 -> 1619 bytes res/transaction0.png | Bin 0 -> 1310 bytes res/transaction2.png | Bin 0 -> 1619 bytes res/transaction_abandoned.png | Bin 0 -> 1473 bytes res/transaction_conflicted.png | Bin 0 -> 1091 bytes res/tx_inout.png | Bin 0 -> 1655 bytes res/tx_input.png | Bin 0 -> 1783 bytes res/tx_mined.png | Bin 0 -> 1578 bytes res/tx_output.png | Bin 0 -> 1771 bytes res/verify.png | Bin 0 -> 2034 bytes res/warning.png | Bin 0 -> 2801 bytes silentdragon.pro | 3 + src/balancestablemodel.cpp | 23 +- src/bannedpeerstablemodel.cpp | 7 + src/bannedpeerstablemodel.h | 1 + src/connection.cpp | 255 +++++++++++++---- src/connection.h | 3 + src/connection.ui | 32 ++- src/guiconstants.h | 10 + src/mainwindow.cpp | 510 ++++++++++++++++++++++++++++++--- src/mainwindow.h | 26 +- src/mainwindow.ui | 248 ++++++++-------- src/mobileappconnector.ui | 10 +- src/peerstablemodel.cpp | 40 +-- src/qrcode.ui | 71 +++++ src/qrcodelabel.cpp | 8 +- src/rescandialog.ui | 91 ++++++ src/rpc.cpp | 124 ++++++-- src/rpc.h | 16 +- src/scripts/make-deb.sh | 84 ++++-- src/scripts/mkmacdmg.sh | 2 - src/scripts/mkrelease.sh | 46 +-- src/scripts/silentdragon.wxs | 81 ++++++ src/sendtab.cpp | 72 +++-- src/senttxstore.cpp | 3 +- src/settings.cpp | 31 +- src/settings.h | 13 +- src/settings.ui | 89 ++++-- src/txtablemodel.cpp | 106 +++++-- src/txtablemodel.h | 6 +- src/version.h | 2 +- src/websockets.cpp | 6 +- 51 files changed, 1611 insertions(+), 437 deletions(-) create mode 100644 doc/release-process.md create mode 100644 res/lock_closed.png create mode 100644 res/lock_open.png create mode 100644 res/remove.png create mode 100644 res/send.png create mode 100644 res/synced.png create mode 100644 res/transaction0.png create mode 100644 res/transaction2.png create mode 100644 res/transaction_abandoned.png create mode 100644 res/transaction_conflicted.png create mode 100644 res/tx_inout.png create mode 100644 res/tx_input.png create mode 100644 res/tx_mined.png create mode 100644 res/tx_output.png create mode 100644 res/verify.png create mode 100644 res/warning.png create mode 100644 src/guiconstants.h mode change 100644 => 100755 src/mainwindow.cpp create mode 100644 src/qrcode.ui create mode 100644 src/rescandialog.ui mode change 100644 => 100755 src/rpc.h create mode 100644 src/scripts/silentdragon.wxs diff --git a/doc/release-process.md b/doc/release-process.md new file mode 100644 index 0000000..60ad5da --- /dev/null +++ b/doc/release-process.md @@ -0,0 +1,8 @@ +# SilentDragon Release Process + +## High-Level Philosophy + +Beware of making high-risk changes too close to a new release, because they will not get as much testing as they should. Don't merge large branches which haven't undergone lots of testing just before a release. + +It is best to keep doc/relnotes/README.md up to date as changes and bug fixes are made. It's more work to summarize all changes and bugfixes just before the release. + diff --git a/doc/relnotes/README.md b/doc/relnotes/README.md index bb76980..9010c33 100644 --- a/doc/relnotes/README.md +++ b/doc/relnotes/README.md @@ -10,6 +10,21 @@ and no longer on Github, since they banned Duke Leto and also because they censor many people around the world and work with evil organizations. +# SilentDragon 1.3.1 "XXX" + +``` +... +``` + * Change language at run-time via Settings + * This takes effect immediately, no restart needed + * New icons for different transactions in transaction tab + * Fix coredump when going to Settings during a rescan + * Make it easier to reply to a memo + * Ability to manually ban a node, unban a node, or unban all nodes + * More efficiently check for new transactions + * Add HUSH logo to QR codes + * "Report a Bug" menu item now goes to our Telegram Support group + # SilentDragon 1.3.0 "Berserk Bonnacon" ``` diff --git a/res/css/dark.css b/res/css/dark.css index 7846b25..9a30723 100644 --- a/res/css/dark.css +++ b/res/css/dark.css @@ -1,5 +1,5 @@ -QWidget, QMainWindow, QMenuBar, QMenu, QDialog, QTabWidget, QTableView, QTableView::item, QScrollArea, QGroupBox, QPlainTextEdit, QLineEdit, QLabel, MainWindow +QWidget, QMainWindow, QMenuBar, QMenu, QDialog, QTabWidget, QTableView, QScrollArea, QGroupBox, QPlainTextEdit, QLineEdit, QLabel, MainWindow { background-color: #303335; color: #ffffff; diff --git a/res/css/light.css b/res/css/light.css index 16b9a0b..fce0c1b 100644 --- a/res/css/light.css +++ b/res/css/light.css @@ -1,4 +1,4 @@ -QWidget, QMainWindow, QMenuBar, QMenu, QDialog, QTabWidget, QTableView, QTableView::item, QScrollArea, QGroupBox, QWidget, QPlainTextEdit, QLineEdit, QLabel, MainWindow +QWidget, QMainWindow, QMenuBar, QMenu, QDialog, QTabWidget, QTableView, QScrollArea, QGroupBox, QWidget, QPlainTextEdit, QLineEdit, QLabel, MainWindow { background-color: #dadada; color: #000000; diff --git a/res/css/midnight.css b/res/css/midnight.css index 754fbc6..f48a1b4 100644 --- a/res/css/midnight.css +++ b/res/css/midnight.css @@ -9,7 +9,7 @@ Website: https://www.csharpe.me License: https://opensource.org/licenses/MIT */ -QWidget, QMainWindow, QMenuBar, QMenu, QDialog, QTabWidget, QTableView, QTableView::item, QScrollArea, QGroupBox, QPlainTextEdit, QLineEdit, QLabel, MainWindow, QPixmap, QHBoxLayout, QVBoxLayout, QGridLayout, QAbstractItemView, QFrame +QWidget, QMainWindow, QMenuBar, QMenu, QDialog, QTabWidget, QTableView, QScrollArea, QGroupBox, QPlainTextEdit, QLineEdit, QLabel, MainWindow, QPixmap, QHBoxLayout, QVBoxLayout, QGridLayout, QAbstractItemView, QFrame { background-color: #111; color: #fff; diff --git a/res/lock_closed.png b/res/lock_closed.png new file mode 100644 index 0000000000000000000000000000000000000000..1bd98b21a6d641c833a26af7a20f701dbd47529d GIT binary patch literal 1197 zcmeAS@N?(olHy`uVBq!ia0vp^4Is?H1|$#LC7xzrU@7r*aSW-r^>(hKhe)7I`}P#q zz%NP>5;qUdW_jdj;GD9zBx2zjpH~`BRM$5tBvy0_v?#n#SR}VgLZzZxugQZg*2%`> zL8g8wC%0qn@BJ0t3#WcwR{1&YUJd`g)9>E=xnI3Mzx4UrGdb&G^qwp1HsItqprXJ) z1a$btHv9BePS=O~3{-8d`@B4Ur8t-4f&YWhL!l2hZqS{=ylHLx;<)HmhL_y`dJI(F zNVNPjFZ;6ZoPvYZq4&b~+ywp_ba7EII%4|J<)4oE!eN z|I>fDaiyV|;(_&@d$ntNewgH5XJT>e{h61>I^mkmo-HNw>ZDj!EO@rSGMLF@w^Gff ziiK{oSTtVm+%buhamm%_ACu>oF>3u6dbL^Mz-9@(tFx{)x}MD8`CIpFE5oHux zK`*~YHn1}JUCVr0$i2b!!1DW*N4y!owC`7D;w!O?-rLXgMJO+!s#fH{>8&NrALIQW z{Sahe6o8`x?|$SfFfc)pgBq)Z3j>P-95uY*ED%Og&cG<+-|#Q-<>^_gBwpQhYGCnc z*y8u_>&E+Rw=VaWDm5Hu@|Z2g_b)Kq*1VUY?%u=XD+~Twt83f)YnpHQm{NY%Ks?mI!yWMF{O`BRWhdXa*7&92z`ebm+oyYdIhU^ByOmRF z_JzLTVm$uawB!YvC7IuGTPZLF^6nH^M6{Gy84%-H#u6 z%u?4>s2*t%Be7GxM(xM^;)3&$e0|}^&vC9dlG!Kq+j6<2O3KVF-W3XGxaC`%`48J& sNuGT@`K{NHJHNL*+M`e50OBb_UkU5 zB6~SRyUIQ+VR_^j;OwL?8>V5*ZOe62aeb3QVg#XzB@1+&(jA6UPJ-30Ogo%lP z2-uLBo^Isp6t+XvhqK=#_?O|=7-1$A^$$T07Zt?c5GrE4dFl3wTyMbzKbrIf*;;d( zFZ3rxzhBM5*roi(`_OETrvKtw&wFz`$p5hX%k8prMyrl#vV73~la*-N_Tlr2sH+?g z+<$yMl-Bd`d7$ZQ!2`KJL=Q!?136o@1rE$*@a8*wesc>$-l6#)6#m!W z&k}I_QU}9F&J6-aD{TLA{+o9}ea_`CFOP?PwMzW9w}Ihf%YCgsT!I(0AJ#PdoqzrH ztatIU!P-nW#1n2B%*a19<7B`(IWM`(w`X06a%0&2Fr4c?FO#LfF28{F-tUz&Y)f;d zawPaS)Ey3g;=^K^&XfI3xxtq48Snl}wz7_AFSo_3o(Bn(@ie{Y+>qDrcvqHXgK*u> zgUjnn-o z|GVX%r`}WkweDtu{Mmh62L1;g>b<@eeBlk-jk@b8Tr+pD-cWh)IQ^f-h5GHxIrguf zrat_2_&y^e`>wfbRvoZy?&r?=eWS!`rv>W^wL1q&pvC&aoq@#xjvBsj7Vsh|XJC{mZ}^w^oO{+K ziC4S|4eYWC-m^b^nt%V<&CC5;6&;#bC!|LGh+MF)Do=`WlI7uJD;)m@vUp5j*kr;H z&--e1skD+^+iQ*oehnuD%+r089Ew;XI28|YF{!vW^eeLb=wLW0n4sIjkj*hchB3{V z!$A6@A&*jnHDi?%$AxBwlTz#zNdf;mTYqsph-i>{7~60wYpxWF$FqQf$i{~c=KtDh z_Ogig}Mdgx)@Hb@ssI z#7kul`#ywJ=)X}ncvE|K+wxs&-^P8Zy>#pjBO?PIsPI|(Q}FG}Yx)Y80E;jNPgg&e IbxsLQ02S^C4FCWD literal 0 HcmV?d00001 diff --git a/res/remove.png b/res/remove.png new file mode 100644 index 0000000000000000000000000000000000000000..8e738d6301fe8666a4de6495fc1bdda87c2dcce0 GIT binary patch literal 1723 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 literal 0 HcmV?d00001 diff --git a/res/synced.png b/res/synced.png new file mode 100644 index 0000000000000000000000000000000000000000..5ac28d36a39f62252745a5005d60b4677994facf GIT binary patch literal 1619 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 JPDHLkV1liL{y_i$ literal 0 HcmV?d00001 diff --git a/res/tx_inout.png b/res/tx_inout.png new file mode 100644 index 0000000000000000000000000000000000000000..0a6e72a898966855e4c8ffa565c2298370e5ba8b GIT binary patch literal 1655 zcmYk7X;f2p8i)V)-h^y~B!~n7qXA@_5Hf&DFh-UrPz<{qkS)+r!Y1J$$mVzh!w#oT z889qb#DcX&2t^$zs1XHQ2&)w$id?B27!d?&TPO$v-+Iowobx;%e&61=$k*GQN;V_| z0H~fGBLD4;?le(rdp;IM^#hgz0Z`|aXwJjx|VYyH=CxD-=s(?2cp5~U#> z4V@1rYs-2PBWKmMRebx|3(K#FD}#dBwhu>A22-xNHTb(Q&RLS+Ix`~b3O3gX3Ub$i zUl;Bb;p8}a2EknGrlX9Zp!nf#JdaLskVPRH+RxIZZoDrv_#DflVyCGQ8c=;O{SCZI z`DU9?BzaogFpvbL8n#b^)d83H_}5^(LFsdQoQWUlmbgp`9fo!)#}1m) z&rw1gLJ-kK15UvE$S!q2y;X(FUN{0v1TwyiA|vrm0As-0O8UNqhz6jkkDE@tVgntp zTCCa-EqvcWg9=G1cbsaHA#7tRtanU4NVlDi;pzf+knIXK43~esrV9!{wu@vr1*7S>OhG zgHfJVmRnRE$RIkGa-&p>MAAHB?o{0@rpmz+glFN}?PT0C(U9W|qJJmhDxpiXD;(^t z-q_+*Q#y((Gm)E14l1b{NT8$k>`3Uhf+0)EO^dig#DQ)Rh2zO@%c1XrTu6~g<}FV6MLx`Pd3nyIXFZ7{!m=jUMTn% zsaM!OB}>5pFE)+M@XE)_F~7m_@&LSQAMQEWP>jZ~z#`sT7u{e4B1_4AQ^G0aHhkGO zCk~9oD9=1#wx0qkZoX92n(Ek|-(15erF8T?qgKkoxGB=(LDat+;kJ|f&cZQpjofzN zZ{gKMA)&WI2k@xrFJ56IrJMEjPrj@C!!I7(tN!1cG%OZ7QZW7z;(hA?)DJcyeAej~ zoVtdp8dLf#beiMCt$@N@w?J@CG+8xzG4c)Q{?ZPlYkocS@v7D9EUN7DoY@MY0qt9z0!c>MQ?#-kmNHQ`KlZl&1%6n9 ztu^X|*+T|jUU?ZEHeYf4D#4Mau>x)@D~g%Bi)pEuaje6kh#Cokjl@MAv{o~BU}{qx zLVZGg)Pn!dw*n(B_|pb-nS&X(rFuN-rjkOIOw`_H{Ih0#bcL`k3-4=-_tlcM?2cCQ?iO zeP>z!Sp$-sLSk(c-{=3FJk2T`>D-DQT4dKQys%jyjFcGsWWim5`(J77<(bd-gEz=D#rK=DvOXRozkbx{7nK zm?O+7Bdz1^lhm?oaop~jdIQ;NgKy*Yw_3_z4OdBHycJu!HI??^1ViYw0!=Hf4*$j zQs>RwmRwVNSDE3GC_e_gn+x_~*CJife&MyfGRDJv@4ol7MdOAh)5{FdI zBWxNHD$f(9+3iBF8yF5q5~0mf{@hWOmPE+wonL=m=MIl(s}IhE_RB5AqzeS4wiG#w xh(pBK&T-a*>*yd4SmK=7fDHNm6?q$M!AFcEqZvIv9@>5=!1K7bsM%GL^MA%9qx}E? literal 0 HcmV?d00001 diff --git a/res/tx_input.png b/res/tx_input.png new file mode 100644 index 0000000000000000000000000000000000000000..9e9ee92932c47eec1e34719cc090f2a76f28964e GIT binary patch literal 1783 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 Y6#leVY4!fXFN{9`2*bpJx)3?`H;TrvTmS$7 literal 0 HcmV?d00001 diff --git a/res/tx_output.png b/res/tx_output.png new file mode 100644 index 0000000000000000000000000000000000000000..6f66ab654791acecf1f24d5b95335155dfbd7852 GIT binary patch literal 1771 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 BlwAM- literal 0 HcmV?d00001 diff --git a/res/warning.png b/res/warning.png new file mode 100644 index 0000000000000000000000000000000000000000..6bc5ac78952b42e9c340c8888d382c775671ca35 GIT binary patch literal 2801 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 + QBrush b; + if (loading) { if (role == Qt::DisplayRole) return "Loading..."; @@ -69,21 +74,23 @@ QVariant BalancesTableModel::data(const QModelIndex &index, int role) const if (role == Qt::TextAlignmentRole && index.column() == 1) return QVariant(Qt::AlignRight | Qt::AlignVCenter); - if (role == Qt::ForegroundRole) { + if (role == Qt::ForegroundRole) { // If any of the UTXOs for this address has zero confirmations, paint it in red const auto& addr = std::get<0>(modeldata->at(index.row())); for (auto utxo : *utxos) { if (utxo.address == addr && utxo.confirmations == 0) { - QBrush b; - b.setColor(Qt::red); + b.setColor(COLOR_UNCONFIRMED_TX); return b; } } - - // Else, just return the default brush - QBrush b; - b.setColor(Qt::black); - return b; + if (theme_name == "dark" || theme_name == "midnight") { + b.setColor(COLOR_WHITE); + return b; + }else{ + b.setColor(COLOR_BLACK); + return b; + } + return b; } if (role == Qt::DisplayRole) { diff --git a/src/bannedpeerstablemodel.cpp b/src/bannedpeerstablemodel.cpp index c25cae9..58f60ca 100644 --- a/src/bannedpeerstablemodel.cpp +++ b/src/bannedpeerstablemodel.cpp @@ -1,5 +1,6 @@ // Copyright 2019-2021 The Hush developers // Released under the GPLv3 +#include "bannedpeerstablemodel.h" #include "settings.h" #include "rpc.h" @@ -59,6 +60,7 @@ int BannedPeersTableModel::columnCount(const QModelIndex&) const case 0: return dat.address; case 1: return dat.subnet; case 2: return QDateTime::fromSecsSinceEpoch(dat.banned_until).toLocalTime().toString(); + case 3: return "AS" + QString::number(dat.asn); } } @@ -68,6 +70,7 @@ int BannedPeersTableModel::columnCount(const QModelIndex&) const case 0: return "Network Address"; case 1: return "Subnet Mask"; case 2: return "Banned Until " + QDateTime::fromSecsSinceEpoch(dat.banned_until).toUTC().toString(); + case 3: return "Autonomous System Number"; } } @@ -119,6 +122,10 @@ QString BannedPeersTableModel::getAddress(int row) const { return modeldata->at(row).address.trimmed(); } +qint64 BannedPeersTableModel::getASN(int row) const { + return modeldata->at(row).asn; +} + QString BannedPeersTableModel::getSubnet(int row) const { return modeldata->at(row).subnet; } diff --git a/src/bannedpeerstablemodel.h b/src/bannedpeerstablemodel.h index a317469..d6d046d 100644 --- a/src/bannedpeerstablemodel.h +++ b/src/bannedpeerstablemodel.h @@ -15,6 +15,7 @@ public: QString getSubnet(int row) const; QString getAddress(int row) const; + qint64 getASN(int row) const; qint64 getBannedUntil(int row) const; int rowCount(const QModelIndex &parent) const; diff --git a/src/connection.cpp b/src/connection.cpp index 701b491..518cbc7 100644 --- a/src/connection.cpp +++ b/src/connection.cpp @@ -15,24 +15,59 @@ ConnectionLoader::ConnectionLoader(MainWindow* main, RPC* rpc) { d = new QDialog(main); d->setWindowFlags(d->windowFlags() & ~(Qt::WindowCloseButtonHint | Qt::WindowContextHelpButtonHint)); + connD = new Ui_ConnectionDialog(); connD->setupUi(d); - QMovie *movie1 = new QMovie(":/img/res/silentdragon-animated-startup.gif");; - QMovie *movie2 = new QMovie(":/img/res/silentdragon-animated-startup-dark.gif");; + QMovie *movie1 = new QMovie(":/img/res/silentdragon-animated-startup-dark.gif");; auto theme = Settings::getInstance()->get_theme_name(); auto size = QSize(512,512); - if (theme == "dark" || theme == "midnight") { - movie2->setScaledSize(size); - connD->topIcon->setMovie(movie2); - movie2->start(); - } else { - movie1->setScaledSize(size); - connD->topIcon->setMovie(movie1); - movie1->start(); - } + movie1->setScaledSize(size); + connD->topIcon->setMovie(movie1); + movie1->start(); + main->logger->write("set animation"); qDebug() << "set animation"; + + // TODO: Check for rescan + rpc->getRescanInfo([=] (QJsonValue response){ + qDebug() << "getrescaninfo"; + }); + + + /* + // Set up timer to check rescan + rescanTimer = new QTimer(main); + + QObject::connect(rescanTimer, &QTimer::timeout, [=]() { + QString progress = rescanProgress(); + + qDebug() << "Rescan progress = " +progress; + + if(progress=="Start"){ + this->showInformation(QObject::tr("Rescanning. This may take several hours, grab some popcorn")); + } + + if(progress!="Start" && progress!="Stop"){ + QStringList progressParts = progress.split(" "); + QString percentStr="unknown %"; + QString height="unknown"; + bool isNumeric = false; + + if (progressParts[0].toDouble(&isNumeric) && progressParts[1].toDouble(&isNumeric)){ + double percent = progressParts[0].toDouble()*100; + percentStr = QString::number(percent)+"%"; + height = progressParts[1]; + } + this->showInformation(QObject::tr("Rescanning. This may take several hours, grab some popcorn"), percentStr+ QObject::tr(" at height ") +height); + } + + if(progress=="Stop"){ + qDebug() << "Stop rescanTimer"; + } + }); + rescanTimer->start(10000); //debug.log is written to every 1 minute for rescan update but might want to check quicker to show rescanning sooner + */ } ConnectionLoader::~ConnectionLoader() { @@ -74,12 +109,12 @@ void ConnectionLoader::doAutoConnect(bool tryEhushdStart) { } else { if (config->hushDaemon) { // hushd is configured to run as a daemon, so we must wait for a few seconds - // to let it start up. + // to let it start up. main->logger->write("hushd is daemon=1. Waiting for it to start up"); this->showInformation(QObject::tr("hushd is set to run as daemon"), QObject::tr("Waiting for hushd")); QTimer::singleShot(5000, [=]() { doAutoConnect(/* don't attempt to start ehushd */ false); }); } else { - // Something is wrong. + // Something is wrong. // We're going to attempt to connect to the one in the background one last time // and see if that works, else throw an error main->logger->write("Unknown problem while trying to start hushd!"); @@ -87,7 +122,7 @@ void ConnectionLoader::doAutoConnect(bool tryEhushdStart) { } } } else { - // We tried to start ehushd previously, and it didn't work. So, show the error. + // We tried to start ehushd previously, and it didn't work. So, show the error. main->logger->write("Couldn't start embedded hushd for unknown reason"); QString explanation; if (config->hushDaemon) { @@ -96,18 +131,18 @@ void ConnectionLoader::doAutoConnect(bool tryEhushdStart) { "Please remove the following line from your HUSH3.conf and restart SilentDragon\n" "daemon=1"); } else { - explanation = QString() % QObject::tr("Couldn't start the embedded hushd.\n\n" - "Please try restarting.\n\nIf you previously started hushd with custom arguments, you might need to reset HUSH3.conf.\n\n" - "If all else fails, please run hushd manually.") % + explanation = QString() % QObject::tr("Couldn't start the embedded hushd.\n\n" + "Please try restarting.\n\nIf you previously started hushd with custom arguments, you might need to reset HUSH3.conf.\n\n" + "If all else fails, please run hushd manually.") % (ehushd ? QObject::tr("The process returned") + ":\n\n" % ehushd->errorString() : QString("")); } - + this->showError(explanation); - } + } } else { // HUSH3.conf exists, there's no connection, and the user asked us not to start hushd. Error! main->logger->write("Not using embedded and couldn't connect to hushd"); - QString explanation = QString() % QObject::tr("Couldn't connect to hushd configured in HUSH3.conf.\n\n" + QString explanation = QString() % QObject::tr("Couldn't connect to hushd configured in HUSH3.conf.\n\n" "Not starting embedded hushd because --no-embedded was passed"); this->showError(explanation); } @@ -120,7 +155,7 @@ void ConnectionLoader::doAutoConnect(bool tryEhushdStart) { // Fall back to manual connect doManualConnect(); } - } + } } QString randomPassword() { @@ -142,7 +177,7 @@ QString randomPassword() { /** * This will create a new HUSH3.conf and download params if they cannot be found - */ + */ void ConnectionLoader::createHushConf() { main->logger->write(__func__); @@ -266,7 +301,7 @@ void ConnectionLoader::doNextDownload(std::function cb) { } // The downloaded file is written to a new name, and then renamed when the operation completes. - currentOutput = new QFile(QDir(paramsDir).filePath(filename + ".part")); + currentOutput = new QFile(QDir(paramsDir).filePath(filename + ".part")); if (!currentOutput->open(QIODevice::WriteOnly)) { main->logger->write("Couldn't open " + currentOutput->fileName() + " for writing"); @@ -274,12 +309,12 @@ void ConnectionLoader::doNextDownload(std::function cb) { } main->logger->write("Downloading to " + filename); qDebug() << "Downloading " << url << " to " << filename; - + QNetworkRequest request(url); request.setAttribute(QNetworkRequest::FollowRedirectsAttribute, true); currentDownload = client->get(request); downloadTime.start(); - + // Download Progress QObject::connect(currentDownload, &QNetworkReply::downloadProgress, [=] (auto done, auto total) { // calculate the download speed @@ -299,7 +334,7 @@ void ConnectionLoader::doNextDownload(std::function cb) { QObject::tr("Downloading ") % filename % (filesRemaining > 1 ? " ( +" % QString::number(filesRemaining) % QObject::tr(" more remaining )") : QString("")), QString::number(done/1024/1024, 'f', 0) % QObject::tr("MB of ") % QString::number(total/1024/1024, 'f', 0) + QObject::tr("MB at ") % QString::number(speed, 'f', 2) % unit); }); - + // Download Finished QObject::connect(currentDownload, &QNetworkReply::finished, [=] () { // Rename file @@ -312,22 +347,22 @@ void ConnectionLoader::doNextDownload(std::function cb) { if (currentDownload->error()) { main->logger->write("Downloading " + filename + " failed"); - this->showError(QObject::tr("Downloading ") + filename + QObject::tr(" failed. Please check the help site for more info")); + this->showError(QObject::tr("Downloading ") + filename + QObject::tr(" failed. Please check the help site for more info")); } else { doNextDownload(cb); } }); - // Download new data available. + // Download new data available. QObject::connect(currentDownload, &QNetworkReply::readyRead, [=] () { currentOutput->write(currentDownload->readAll()); - }); + }); } bool ConnectionLoader::startEmbeddedHushd() { - if (!Settings::getInstance()->useEmbedded()) + if (!Settings::getInstance()->useEmbedded()) return false; - + main->logger->write("Trying to start embedded hushd"); // Static because it needs to survive even after this method returns. @@ -336,13 +371,13 @@ bool ConnectionLoader::startEmbeddedHushd() { if (ehushd != nullptr) { if (ehushd->state() == QProcess::NotRunning) { if (!processStdErrOutput.isEmpty()) { - QMessageBox::critical(main, QObject::tr("hushd error"), "hushd said: " + processStdErrOutput, + QMessageBox::critical(main, QObject::tr("hushd error"), "hushd said: " + processStdErrOutput, QMessageBox::Ok); } return false; } else { return true; - } + } } QDir appPath(QCoreApplication::applicationDirPath()); @@ -352,7 +387,7 @@ bool ConnectionLoader::startEmbeddedHushd() { #else auto hushdProgram = appPath.absoluteFilePath("hushd"); #endif - + //if (!QFile(hushdProgram).exists()) { if (!QFile::exists(hushdProgram)) { qDebug() << "Can't find hushd at " << hushdProgram; @@ -441,7 +476,7 @@ void ConnectionLoader::doManualConnect() { auto connection = makeConnection(config); refreshHushdState(connection, [=] () { QString explanation = QString() - % QObject::tr("Could not connect to hushd configured in settings.\n\n" + % QObject::tr("Could not connect to hushd configured in settings.\n\n" "Please set the host/port and user/password in the Edit->Settings menu."); showError(explanation); @@ -454,7 +489,7 @@ void ConnectionLoader::doManualConnect() { void ConnectionLoader::doRPCSetConnection(Connection* conn) { rpc->setEHushd(ehushd); rpc->setConnection(conn); - + d->accept(); delete this; @@ -462,7 +497,7 @@ void ConnectionLoader::doRPCSetConnection(Connection* conn) { Connection* ConnectionLoader::makeConnection(std::shared_ptr config) { QNetworkAccessManager* client = new QNetworkAccessManager(main); - + QUrl myurl; myurl.setScheme("http"); myurl.setHost(config.get()->host); @@ -471,10 +506,10 @@ Connection* ConnectionLoader::makeConnection(std::shared_ptr c QNetworkRequest* request = new QNetworkRequest(); request->setUrl(myurl); request->setHeader(QNetworkRequest::ContentTypeHeader, "text/plain"); - + QString userpass = config.get()->rpcuser % ":" % config.get()->rpcpassword; QString headerData = "Basic " + userpass.toLocal8Bit().toBase64(); - request->setRawHeader("Authorization", headerData.toLocal8Bit()); + request->setRawHeader("Authorization", headerData.toLocal8Bit()); return new Connection(main, client, request, config); } @@ -484,7 +519,7 @@ void ConnectionLoader::refreshHushdState(Connection* connection, std::functiondoRPC(payload, @@ -495,20 +530,20 @@ void ConnectionLoader::refreshHushdState(Connection* connection, std::functiondoRPCSetConnection(connection); }); }, [=] (QNetworkReply* reply, const QJsonValue &res) { - // Failed, see what it is. + // Failed, see what it is. auto err = reply->error(); //qDebug() << err << res; - if (err == QNetworkReply::NetworkError::ConnectionRefusedError) { + if (err == QNetworkReply::NetworkError::ConnectionRefusedError) { refused(); } else if (err == QNetworkReply::NetworkError::AuthenticationRequiredError) { main->logger->write("Authentication failed"); - QString explanation = QString() % + QString explanation = QString() % QObject::tr("Authentication failed. The username / password you specified was " "not accepted by hushd. Try changing it in the Edit->Settings menu"); this->showError(explanation); - } else if (err == QNetworkReply::NetworkError::InternalServerError && + } else if (err == QNetworkReply::NetworkError::InternalServerError && !res.isNull()) { // The server is loading, so just poll until it succeeds QString status = res["error"].toObject()["message"].toString(); @@ -532,9 +567,10 @@ void ConnectionLoader::refreshHushdState(Connection* connection, std::functionsetEHushd(nullptr); rpc->noConnection(); @@ -665,8 +701,8 @@ bool ConnectionLoader::verifyParams() { /** * Try to automatically detect a HUSH3/HUSH3.conf file in the correct location and load parameters - */ -std::shared_ptr ConnectionLoader::autoDetectHushConf() { + */ +std::shared_ptr ConnectionLoader::autoDetectHushConf() { auto confLocation = locateHushConfFile(); if (confLocation.isNull()) { @@ -688,7 +724,7 @@ std::shared_ptr ConnectionLoader::autoDetectHushConf() { hushconf->usingHushConf = true; hushconf->hushDir = QFileInfo(confLocation).absoluteDir().absolutePath(); hushconf->hushDaemon = false; - + Settings::getInstance()->setUsingHushConf(confLocation); while (!in.atEnd()) { @@ -732,21 +768,28 @@ std::shared_ptr ConnectionLoader::autoDetectHushConf() { if (hushconf->port.isEmpty()) hushconf->port = "18031"; file.close(); - // In addition to the HUSH3/HUSH3.conf file, also double check the params. + // Save to Qsettings + Settings::getInstance()->saveSettings( + hushconf->host, + hushconf->port, + hushconf->rpcuser, + hushconf->rpcpassword); + + // In addition to the HUSH3/HUSH3.conf file, also double check the params. return std::shared_ptr(hushconf); } // Load connection settings from the UI, which indicates an unknown, external hushd std::shared_ptr ConnectionLoader::loadFromSettings() { - // Load from the QT Settings. + // Load from the QT Settings. QSettings s; - + auto host = s.value("connection/host").toString(); auto port = s.value("connection/port").toString(); auto username = s.value("connection/rpcuser").toString(); auto password = s.value("connection/rpcpassword").toString(); - + if (username.isEmpty() || password.isEmpty()) return nullptr; @@ -761,8 +804,8 @@ std::shared_ptr ConnectionLoader::loadFromSettings() { /*********************************************************************************** * Connection Class - ************************************************************************************/ -Connection::Connection(MainWindow* m, QNetworkAccessManager* c, QNetworkRequest* r, + ************************************************************************************/ +Connection::Connection(MainWindow* m, QNetworkAccessManager* c, QNetworkRequest* r, std::shared_ptr conf) { this->restclient = c; this->request = r; @@ -795,7 +838,7 @@ void Connection::doRPC(const QJsonValue& payload, const std::functionreadAll()); QJsonValue parsed; @@ -807,13 +850,13 @@ void Connection::doRPC(const QJsonValue& payload, const std::functionerror() != QNetworkReply::NoError) { ne(reply, parsed); return; - } - + } + if (parsed.isNull()) { ne(reply, "Unknown error"); } - - cb(parsed["result"]); + + cb(parsed["result"]); }); } @@ -847,9 +890,97 @@ void Connection::showTxError(const QString& error) { shown = false; } +/* +QString ConnectionLoader::rescanProgress() { + +#ifdef Q_OS_LINUX + auto debugLocation = QStandardPaths::locate(QStandardPaths::HomeLocation, ".hush/HUSH3/debug.log"); + if(!QFile(debugLocation).exists()) { + // legacy location + debugLocation = QStandardPaths::locate(QStandardPaths::HomeLocation, ".komodo/HUSH3/debug.log"); + } +#elif defined(Q_OS_DARWIN) + auto debugLocation = QStandardPaths::locate(QStandardPaths::HomeLocation, "Library/Application Support/Hush/HUSH3/debug.log"); + if(!QFile(debugLocation).exists()) { + // legacy location + debugLocation = QStandardPaths::locate(QStandardPaths::HomeLocation, "Library/Application Support/Komodo/HUSH3/debug.log"); + } +#else + auto debugLocation = QStandardPaths::locate(QStandardPaths::AppDataLocation, "../../Hush/HUSH3/debug.log"); + if(!QFile(debugLocation).exists()) { + // legacy location + debugLocation = QStandardPaths::locate(QStandardPaths::AppDataLocation, "../../Komodo/HUSH3/debug.log"); + } +#endif + + if(QFile(debugLocation).exists()){ + qDebug() << "Found debug.log at "+ debugLocation; + QFile file(debugLocation); + + if (file.open(QIODevice::ReadOnly)){ + + // TODO: Handle this more efficiently + + int lineNumber = -1; + QString line; + qint64 chars = 2056; // Change this - If too small, may only read junk data with no rescan info. Too large and progress will update with older rescan info until out of range and new match found. + qint64 fileSize = file.size(); + qDebug() << "debug.log size = " << fileSize; + file.seek(fileSize - chars); + QTextStream textStream( &file ); + QRegularExpression rxStart("Rescanning from height=(\\d*[.]?\\d+)"); + QRegularExpression rxBlock("block ([0-9]+)"); + QRegularExpression rxProgress("Progress=(\\d*[.]?\\d+)"); + QRegularExpression rxDone("Done rescanning"); + QJsonObject rescanProgress; + + while (!textStream.atEnd()){ + line = textStream.readLine(); + lineNumber++; + + QRegularExpressionMatch matchStart = rxStart.match(line); + QRegularExpressionMatch matchBlock = rxBlock.match(line); + QRegularExpressionMatch matchProgress = rxProgress.match(line); + QRegularExpressionMatch matchDone = rxDone.match(line); + QString start; + QString block; + QString progress; + QString done; + + if (matchStart.hasMatch()){ + start = matchStart.captured(0); + qDebug() << "Start Rescan = " +start; + return "Start"; + } + + if (matchBlock.hasMatch() && matchProgress.hasMatch()){ + block = matchBlock.captured(0).replace("block ",""); + progress = matchProgress.captured(0).replace("Progress=",""); + + double percent = progress.toDouble()*100; + QString percentStr = QString::number(percent); + + qDebug() << "debug.log still rescanning - " << "Block = " + block + " " + progress; + return progress+" "+block; + } + + // This is difficult to catch unless reading lots of log + if (matchDone.hasMatch()){ + done = matchDone.captured(0); + qDebug() << "debug.log rescan done? " + done; + return "Stop"; + } + } + file.close(); + } + } + return "Stop"; +} +*/ + /** * Prevent all future calls from going through - */ + */ void Connection::shutdown() { shutdownInProgress = true; } diff --git a/src/connection.h b/src/connection.h index 1f381cf..e3846b1 100644 --- a/src/connection.h +++ b/src/connection.h @@ -65,6 +65,8 @@ private: void showError(QString explanation); void showInformation(QString info, QString detail = ""); + QString rescanProgress(); + void doRPCSetConnection(Connection* conn); std::shared_ptr ehushd; @@ -81,6 +83,7 @@ private: QNetworkAccessManager* client = nullptr; QTime downloadTime; + QTimer* rescanTimer; }; /** diff --git a/src/connection.ui b/src/connection.ui index 70082d2..e70e076 100644 --- a/src/connection.ui +++ b/src/connection.ui @@ -9,10 +9,28 @@ 0 0 - 513 - 513 + 512 + 512 + + + 0 + 0 + + + + + 512 + 512 + + + + + 512 + 512 + + SilentDragon @@ -20,6 +38,9 @@ true + + QLayout::SetFixedSize + 0 @@ -84,13 +105,6 @@ - - - FilledIconLabel - QLabel -
fillediconlabel.h
-
-
diff --git a/src/guiconstants.h b/src/guiconstants.h new file mode 100644 index 0000000..ac025a3 --- /dev/null +++ b/src/guiconstants.h @@ -0,0 +1,10 @@ +// Copyright 2019-2022 The Hush developers +// Released under the GPLv3 +#ifndef GUICONSTANTS_H +#define GUICONSTANTS_H + +#define COLOR_BLACK QColor(0, 0, 0) +#define COLOR_WHITE QColor(255, 255, 255) +#define COLOR_UNCONFIRMED_TX QColor(255, 0, 0) + +#endif // GUICONSTANTS_H diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp old mode 100644 new mode 100755 index 2348241..3859268 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -1,4 +1,4 @@ -// Copyright 2019-2021 The Hush Developers +// Copyright 2019-2021 The Hush Developers // Released under the GPLv3 #include "mainwindow.h" #include "addressbook.h" @@ -8,11 +8,13 @@ #include "ui_mobileappconnector.h" #include "ui_addressbook.h" #include "ui_privkey.h" +#include "ui_qrcode.h" #include "ui_viewkey.h" #include "ui_about.h" #include "ui_settings.h" #include "ui_viewalladdresses.h" #include "ui_validateaddress.h" +#include "ui_rescandialog.h" #include "rpc.h" #include "balancestablemodel.h" #include "settings.h" @@ -33,7 +35,8 @@ MainWindow::MainWindow(QWidget *parent) : theme_name = Settings::getInstance()->get_theme_name(); } catch (...) { - theme_name = "default"; + qDebug() << __func__ << ": exception!"; + theme_name = "dark"; } this->slot_change_theme(theme_name); @@ -112,6 +115,7 @@ MainWindow::MainWindow(QWidget *parent) : // Initialize to the balances tab ui->tabWidget->setCurrentIndex(0); + setupSendTab(); setupTransactionsTab(); setupReceiveTab(); @@ -185,6 +189,119 @@ void MainWindow::doClose() { closeEvent(nullptr); } +// Called every time, when a menu entry of the language menu is called +void MainWindow::slotLanguageChanged(QString lang) +{ + qDebug() << __func__ << ": lang=" << lang; + if(lang != "") { + // load the language + loadLanguage(lang); + + QDialog settingsDialog(this); + qDebug() << __func__ << ": retranslating settingsDialog"; + settings.retranslateUi(&settingsDialog); + } +} + +void switchTranslator(QTranslator& translator, const QString& filename) { + qDebug() << __func__ << ": filename=" << filename; + // remove the old translator + qApp->removeTranslator(&translator); + + // load the new translator + QString path = QApplication::applicationDirPath(); + path.append("/res/"); + qDebug() << __func__ << ": attempting to load " << path + filename; + if(translator.load(path + filename)) { + qApp->installTranslator(&translator); + } else { + qDebug() << __func__ << ": translation path does not exist! " << path + filename; + } +} + +void MainWindow::loadLanguage(QString& rLanguage) { + qDebug() << __func__ << ": currLang=" << m_currLang << " rLanguage=" << rLanguage; + + QString lang = rLanguage; + + // this allows us to call this function with just a locale such as "zh" + if(lang.right(1) == ")") { + lang.chop(1); // remove trailing ) + } + + // remove everything up to and including the first ( + lang = lang.remove(0, lang.indexOf("(") + 1); + + // NOTE: language codes can be 2 or 3 letters + // https://www.loc.gov/standards/iso639-2/php/code_list.php + + QString languageName; + if(m_currLang != lang) { + qDebug() << __func__ << ": changing language to lang=" << lang; + m_currLang = lang; + QLocale locale = QLocale(m_currLang); + + qDebug() << __func__ << ": locale nativeLanguage=" << locale.nativeLanguageName(); + + // an invalid locale such as "zz" will give the C locale which has no native language name + if (locale.nativeLanguageName() == "") { + qDebug() << __func__ << ": detected invalid language in config file, defaulting to en"; + locale = QLocale("en"); + Settings::getInstance()->set_language("en"); + m_currLang = "en"; + lang = "en"; + } + qDebug() << __func__ << ": locale=" << locale; + QLocale::setDefault(locale); + qDebug() << __func__ << ": setDefault locale=" << locale; + languageName = locale.nativeLanguageName(); //locale.language()); + qDebug() << __func__ << ": languageName=" << languageName; + + switchTranslator(m_translator, QString("silentdragon_%1.qm").arg(lang)); + switchTranslator(m_translatorQt, QString("qt_%1.qm").arg(lang)); + + // TODO: this likely wont work for RTL languages like Arabic + auto first = QString(languageName.at(0)).toUpper(); + languageName = first + languageName.right(languageName.size()-1); + if( lang == "en" ) { + languageName.replace("American ",""); + } + ui->statusBar->showMessage(tr("Language changed to") + " " + languageName + " (" + lang + ")"); + } + + // write this language (the locale shortcode) out to config file + if (lang != "") { + // only write valid languages to config file + Settings::getInstance()->set_language(lang); + } +} + +void MainWindow::changeEvent(QEvent* event) { + if(0 != event) { + switch(event->type()) { + // this event is sent if a translator is loaded + case QEvent::LanguageChange: + qDebug() << __func__ << ": QEvent::LanguageChange changeEvent"; + ui->retranslateUi(this); + break; + + // this event is sent, if the system, language changes + case QEvent::LocaleChange: + { + QString locale = QLocale::system().name(); + locale.truncate(locale.lastIndexOf('_')); + qDebug() << __func__ << ": QEvent::LocaleChange changeEvent locale=" << locale; + loadLanguage(locale); + } + break; + default: + qDebug() << __func__ << ": " << event->type(); + } + } + QMainWindow::changeEvent(event); +} + + void MainWindow::closeEvent(QCloseEvent* event) { QSettings s; @@ -265,7 +382,7 @@ void MainWindow::setupSettingsModal() { // Set up File -> Settings action QObject::connect(ui->actionSettings, &QAction::triggered, [=]() { QDialog settingsDialog(this); - Ui_Settings settings; + //Ui_Settings settings; settings.setupUi(&settingsDialog); Settings::saveRestore(&settingsDialog); @@ -295,14 +412,22 @@ void MainWindow::setupSettingsModal() { } }); + // Setup rescan button + QObject::connect(settings.rescanButton, &QPushButton::clicked, [=] () { + this->rescanButtonClicked(1); + }); + int theme_index = settings.comboBoxTheme->findText(Settings::getInstance()->get_theme_name(), Qt::MatchExactly); settings.comboBoxTheme->setCurrentIndex(theme_index); QObject::connect(settings.comboBoxTheme, &QComboBox::currentTextChanged, [=] (QString theme_name) { this->slot_change_theme(theme_name); - QMessageBox::information(this, tr("Theme Change"), tr("This change can take a few seconds."), QMessageBox::Ok); + // QMessageBox::information(this, tr("Theme Change"), tr("This change can take a few seconds."), QMessageBox::Ok); + // For some reason, changing language also triggers this + //ui->statusBar->showMessage(tr("Theme changed to ") + theme_name); }); + // Set local currency QString ticker = Settings::getInstance()->get_currency_name(); int currency_index = settings.comboBoxCurrency->findText(ticker, Qt::MatchExactly); @@ -310,7 +435,8 @@ void MainWindow::setupSettingsModal() { QObject::connect(settings.comboBoxCurrency, &QComboBox::currentTextChanged, [=] (QString ticker) { this->slot_change_currency(ticker); rpc->refresh(true); - QMessageBox::information(this, tr("Currency Change"), tr("This change can take a few seconds."), QMessageBox::Ok); + ui->statusBar->showMessage(tr("Currency changed to") + " " + ticker); + // QMessageBox::information(this, tr("Currency Change"), tr("This change can take a few seconds."), QMessageBox::Ok); }); // Save sent transactions @@ -342,18 +468,24 @@ void MainWindow::setupSettingsModal() { settings.lblTor->setToolTip(tooltip); } - //Use Consolidation + // Wallet Size + if (rpc->getConnection() != nullptr) { + int size = 0; + qDebug() << __func__ << ": settings hushDir=" << rpc->getConnection()->config->hushDir; + + QDir hushdir(rpc->getConnection()->config->hushDir); + QFile WalletSize(hushdir.filePath("wallet.dat")); + + if (WalletSize.open(QIODevice::ReadOnly)){ + size = WalletSize.size() / 1000000; //when file does open. + //QString size1 = QString::number(size) ; + settings.WalletSize->setText(QString::number(size)); + WalletSize.close(); + } + } + // Use Consolidation bool isUsingConsolidation = false; - int size = 0; - QDir hushdir(rpc->getConnection()->config->hushDir); - QFile WalletSize(hushdir.filePath("wallet.dat")); - if (WalletSize.open(QIODevice::ReadOnly)){ - size = WalletSize.size() / 1000000; //when file does open. - //QString size1 = QString::number(size) ; - settings.WalletSize->setText(QString::number(size)); - WalletSize.close(); - } if (rpc->getConnection() != nullptr) { isUsingConsolidation = !rpc->getConnection()->config->consolidation.isEmpty() == true; } @@ -362,8 +494,7 @@ void MainWindow::setupSettingsModal() { settings.chkConso->setEnabled(false); } - //Use Deletetx - + // Use Deletetx bool isUsingDeletetx = false; if (rpc->getConnection() != nullptr) { isUsingDeletetx = !rpc->getConnection()->config->deletetx.isEmpty() == true; @@ -373,8 +504,7 @@ void MainWindow::setupSettingsModal() { settings.chkDeletetx->setEnabled(false); } - //Use Zindex - + // Use Zindex bool isUsingZindex = false; if (rpc->getConnection() != nullptr) { isUsingZindex = !rpc->getConnection()->config->zindex.isEmpty() == true; @@ -392,10 +522,10 @@ void MainWindow::setupSettingsModal() { auto hushConfLocation = Settings::getInstance()->getHushdConfLocation(); if (!hushConfLocation.isEmpty()) { settings.confMsg->setText("Settings are being read from \n" + hushConfLocation); - settings.hostname->setEnabled(false); - settings.port->setEnabled(false); - settings.rpcuser->setEnabled(false); - settings.rpcpassword->setEnabled(false); + settings.hostname->setReadOnly(true); + settings.port->setReadOnly(true); + settings.rpcuser->setReadOnly(true); + settings.rpcpassword->setReadOnly(true); } else { settings.confMsg->setText("No local HUSH3.conf found. Please configure connection manually."); settings.hostname->setEnabled(true); @@ -418,13 +548,94 @@ void MainWindow::setupSettingsModal() { settings.testnetTxExplorerUrl->setText(explorer.testnetTxExplorerUrl); settings.testnetAddressExplorerUrl->setText(explorer.testnetAddressExplorerUrl); - // Connection tab by default - settings.tabWidget->setCurrentIndex(0); + // format systems language + QString defaultLocale = QLocale::system().name(); // e.g. "de_DE" + defaultLocale.truncate(defaultLocale.lastIndexOf('_')); // e.g. "de" + + // Set the current language to the default system language + // TODO: this will need to change when we read/write selected language to config on disk + //m_currLang = defaultLocale; + //qDebug() << __func__ << ": changed m_currLang to " << defaultLocale; + + m_currLang = Settings::getInstance()->get_language(); + qDebug() << __func__ << ": got a currLang=" << m_currLang << " from config file"; + + //QString defaultLang = QLocale::languageToString(QLocale("en").language()); + settings.comboBoxLanguage->addItem("English (en)"); + + m_langPath = QApplication::applicationDirPath(); + m_langPath.append("/res"); + + qDebug() << __func__ <<": defaultLocale=" << defaultLocale << " m_langPath=" << m_langPath;; + + QDir dir(m_langPath); + QStringList fileNames = dir.entryList(QStringList("silentdragon_*.qm")); + + qDebug() << __func__ <<": found " << fileNames.size() << " translations"; + + + // create language drop down dynamically + for (int i = 0; i < fileNames.size(); ++i) { + // get locale extracted by filename + QString locale; + locale = fileNames[i]; // "silentdragon_de.qm" + locale.truncate(locale.lastIndexOf('.')); // "silentdragon_de" + locale.remove(0, locale.lastIndexOf('_') + 1); // "de" + + QString lang = QLocale(locale).nativeLanguageName(); //locale.language()); + + // TODO: this likely wont work for RTL languages like Arabic + // uppercase the first letter of all languages + auto first = QString(lang.at(0)).toUpper(); + lang = first + lang.right(lang.size()-1); + + //settings.comboBoxLanguage->addItem(action); + settings.comboBoxLanguage->addItem(lang + " (" + locale + ")"); + qDebug() << __func__ << ": added lang=" << lang << " locale=" << locale << " defaultLocale=" << defaultLocale << " m_currLang=" << m_currLang; + qDebug() << __func__ << ": m_currLang=" << m_currLang << " ?= locale=" << locale; + + //if (defaultLocale == locale) { + if (m_currLang == locale) { + settings.comboBoxLanguage->setCurrentIndex(i+1); + qDebug() << " set defaultLocale=" << locale << " to checked!!!"; + } + } + + settings.comboBoxLanguage->model()->sort(0,Qt::AscendingOrder); + qDebug() << __func__ <<": sorted translations"; + + //QString lang = QLocale::languageToString(QLocale(m_currLang).language()); + QString lang = QLocale(m_currLang).nativeLanguageName(); //locale.language()); + + auto first = QString(lang.at(0)).toUpper(); + lang = first + lang.right(lang.size()-1); + + if (m_currLang == "en") { + // we have just 1 English translation + // en_US will render as "American English", so fix that + lang.replace("American ",""); + } + + qDebug() << __func__ << ": looking for " << lang + " (" + m_currLang + ")"; + //qDebug() << __func__ << ": looking for " << m_currLang; + int lang_index = settings.comboBoxLanguage->findText(lang + " (" + m_currLang + ")", Qt::MatchExactly); + + qDebug() << __func__ << ": setting comboBoxLanguage index to " << lang_index; + settings.comboBoxLanguage->setCurrentIndex(lang_index); + + QObject::connect(settings.comboBoxLanguage, &QComboBox::currentTextChanged, [=] (QString lang) { + qDebug() << "comboBoxLanguage.currentTextChanged lang=" << lang; + this->slotLanguageChanged(lang); + //QMessageBox::information(this, tr("Language Changed"), tr("This change can take a few seconds."), QMessageBox::Ok); + }); + + // Options tab by default + settings.tabWidget->setCurrentIndex(1); // Enable the troubleshooting options only if using embedded hushd if (!rpc->isEmbedded()) { - settings.chkRescan->setEnabled(false); - settings.chkRescan->setToolTip(tr("You're using an external hushd. Please restart hushd with -rescan")); + //settings.chkRescan->setEnabled(false); + //settings.chkRescan->setToolTip(tr("You're using an external hushd. Please restart hushd with -rescan")); settings.chkReindex->setEnabled(false); settings.chkReindex->setToolTip(tr("You're using an external hushd. Please restart hushd with -reindex")); @@ -490,10 +701,12 @@ void MainWindow::setupSettingsModal() { // Check to see if rescan or reindex have been enabled bool showRestartInfo = false; bool showReindexInfo = false; + + /* if (settings.chkRescan->isChecked()) { Settings::addToHushConf(hushConfLocation, "rescan=1"); showRestartInfo = true; - } + }*/ if (settings.chkReindex->isChecked()) { Settings::addToHushConf(hushConfLocation, "reindex=1"); @@ -580,7 +793,7 @@ void MainWindow::telegram() { } void MainWindow::reportbug() { - QString url = "https://git.hush.is/hush/SilentDragon/issues/new"; + QString url = "https://hush.is/tg_support"; QDesktopServices::openUrl(QUrl(url)); } @@ -928,6 +1141,20 @@ void MainWindow::getViewKey(QString addr) { *isDialogAlive = false; } +void MainWindow::getQRCode(QString addr) { + QDialog d(this); + Ui_QRCode qrui; + qrui.setupUi(&d); + + // Display QR Code for address + qrui.qrcodeDisplayAddr->setQrcodeString(addr); + + auto isDialogAlive = std::make_shared(true); + + d.exec(); + *isDialogAlive = false; +} + void MainWindow::exportKeys(QString addr) { bool allKeys = addr.isEmpty() ? true : false; @@ -1132,6 +1359,11 @@ void MainWindow::setupBalancesTab() { menu.addAction(tr("Get viewing key"), [=] () { this->getViewKey(addr); }); + + // QR Code for zaddrs only + menu.addAction(tr("Get QR code"), [=] () { + this->getQRCode(addr); + }); } menu.addAction("Send from " % addr.left(40) % (addr.size() > 40 ? "..." : ""), [=]() { @@ -1206,21 +1438,23 @@ QString peer2ip(QString peer) { void MainWindow::setupPeersTab() { qDebug() << __FUNCTION__; - // Set up context menu on transactions tab + // Set up context menu on peers tab ui->peersTable->setContextMenuPolicy(Qt::CustomContextMenu); ui->bannedPeersTable->setContextMenuPolicy(Qt::CustomContextMenu); // Table right click QObject::connect(ui->bannedPeersTable, &QTableView::customContextMenuRequested, [=] (QPoint pos) { - QModelIndex index = ui->peersTable->indexAt(pos); + QModelIndex index = ui->bannedPeersTable->indexAt(pos); if (index.row() < 0) return; QMenu menu(this); auto bannedPeerModel = dynamic_cast(ui->bannedPeersTable->model()); QString addr = bannedPeerModel->getAddress(index.row()); + qint64 asn = bannedPeerModel->getASN(index.row()); QString ip = peer2ip(addr); QString subnet = bannedPeerModel->getSubnet(index.row()); + QString as = QString::number(asn); //qint64 banned_until = bannedPeerModel->getBannedUntil(index.row()); if(!ip.isEmpty()) { @@ -1240,6 +1474,46 @@ void MainWindow::setupPeersTab() { }); } + if(!as.isEmpty()) { + menu.addAction(tr("View ASN on bgpview.io (3rd party service)"), [=] () { + QString url = "https://bgpview.io/asn/" + as; + qDebug() << "opening " << url; + QDesktopServices::openUrl(QUrl(url)); + }); + } + + if(!ip.isEmpty()) { + menu.addAction(tr("Unban this peer"), [=] () { + ui->statusBar->showMessage(tr("Unbanning peer...")); + + // Hide single banned peer + ui->bannedPeersTable->hideRow(index.row()); + + // Call setban + rpc->setban(ip, "remove", [=] (QJsonValue response){ + qDebug() << "setban remove " << response; + ui->statusBar->showMessage(tr("Peer unbanned"), 3 * 1000); + rpc->refreshPeers(); + }); + }); + + menu.addAction(tr("Unban all peers"), [=] () { + ui->statusBar->showMessage(tr("Unbanning all peers...")); + + // Hide all banned peers + for (int i=0; i < bannedPeerModel->rowCount(index); i++){ + ui->bannedPeersTable->hideRow(i); + } + + // Call clearBanned + rpc->clearBanned([=] (QJsonValue response){ + qDebug() << "clearBanned " << response; + ui->statusBar->showMessage(tr("All peers unbanned"), 3 * 1000); + rpc->refreshPeers(); + }); + }); + } + menu.exec(ui->bannedPeersTable->viewport()->mapToGlobal(pos)); }); @@ -1295,6 +1569,20 @@ void MainWindow::setupPeersTab() { }); } + menu.addAction(tr("Ban this peer"), [=] () { + ui->statusBar->showMessage(tr("Banning peer...")); + + // Hide single peer + ui->peersTable->hideRow(index.row()); + + // Call setban + rpc->setban(ip, "add", [=] (QJsonValue response){ + qDebug() << "setban add " << response; + ui->statusBar->showMessage(tr("Peer banned"), 3 * 1000); + rpc->refreshPeers(); + }); + }); + menu.exec(ui->peersTable->viewport()->mapToGlobal(pos)); }); @@ -1383,10 +1671,52 @@ void MainWindow::setupTransactionsTab() { QString memo = txModel->getMemo(index.row()); if (!memo.isEmpty()) { - QMessageBox mb(QMessageBox::Information, tr("Memo"), memo, QMessageBox::Ok, this); + QMessageBox mb; + mb.setText(memo); + mb.setWindowTitle(tr("Memo")); + mb.setIcon(QMessageBox::Information); + + QAbstractButton* buttonMemoReply = mb.addButton(tr("Reply"), QMessageBox::YesRole); mb.addButton(tr("OK"), QMessageBox::NoRole); + mb.setTextFormat(Qt::PlainText); mb.setTextInteractionFlags(Qt::TextSelectableByMouse | Qt::TextSelectableByKeyboard); mb.exec(); + + if (mb.clickedButton()==buttonMemoReply) { + qDebug() << "Reply clicked"; + + int lastPost = memo.trimmed().lastIndexOf(QRegExp("[\r\n]+")); + QString lastWord = memo.right(memo.length() - lastPost - 1); + + if (Settings::getInstance()->isSaplingAddress(lastWord)) { + + // First, cancel any pending stuff in the send tab by pretending to click + // the cancel button + cancelButton(); + + // Then set up the fields in the send tab + ui->Address1->setText(lastWord); + ui->Address1->setCursorPosition(0); + ui->Amount1->setText("0.0001"); + + // And switch to the send tab. + ui->tabWidget->setCurrentIndex(1); + + qApp->processEvents(); + + // Click the memo button + this->memoButtonClicked(1, true); + }else{ + // TODO: This memo has no reply to address. Show alert or don't show button to begin with. + QMessageBox mb; + mb.setText(tr("Sorry! This memo has no reply to address.")); + mb.setWindowTitle(tr("Error")); + + mb.setTextFormat(Qt::PlainText); + mb.setTextInteractionFlags(Qt::TextSelectableByMouse | Qt::TextSelectableByKeyboard); + mb.exec(); + } + } } }); @@ -1440,6 +1770,14 @@ void MainWindow::setupTransactionsTab() { QGuiApplication::clipboard()->setText(url); }); + /* TODO: Decide whether to use this or not. + menu.addAction(tr("Look for new transactions"), [=] () { + QGuiApplication::clipboard()->setText(addr); + ui->statusBar->showMessage(tr("Looking for new transactions"), 3 * 1000); + rpc->watchTxStatus(); + }); + */ + // Payment Request if (!memo.isEmpty() && memo.startsWith("hush:")) { menu.addAction(tr("View Payment Request"), [=] () { @@ -1450,10 +1788,57 @@ void MainWindow::setupTransactionsTab() { // View Memo if (!memo.isEmpty()) { menu.addAction(tr("View Memo"), [=] () { + /* QMessageBox mb(QMessageBox::Information, tr("Memo"), memo, QMessageBox::Ok, this); + mb.setTextFormat(Qt::PlainText); + mb.setTextInteractionFlags(Qt::TextSelectableByMouse | Qt::TextSelectableByKeyboard); + mb.exec();*/ + QMessageBox mb; + mb.setText(memo); + mb.setWindowTitle(tr("Memo")); + mb.setIcon(QMessageBox::Information); + + QAbstractButton* buttonMemoReply = mb.addButton(tr("Reply"), QMessageBox::YesRole); mb.addButton(tr("OK"), QMessageBox::NoRole); + mb.setTextFormat(Qt::PlainText); mb.setTextInteractionFlags(Qt::TextSelectableByMouse | Qt::TextSelectableByKeyboard); mb.exec(); + + if (mb.clickedButton()==buttonMemoReply) { + qDebug() << "Reply clicked"; + + int lastPost = memo.trimmed().lastIndexOf(QRegExp("[\r\n]+")); + QString lastWord = memo.right(memo.length() - lastPost - 1); + + if (Settings::getInstance()->isSaplingAddress(lastWord)) { + + // First, cancel any pending stuff in the send tab by pretending to click + // the cancel button + cancelButton(); + + // Then set up the fields in the send tab + ui->Address1->setText(lastWord); + ui->Address1->setCursorPosition(0); + ui->Amount1->setText("0.0001"); + + // And switch to the send tab. + ui->tabWidget->setCurrentIndex(1); + + qApp->processEvents(); + + // Click the memo button + this->memoButtonClicked(1, true); + }else{ + // TODO: This memo has no reply to address. Show alert or don't show button to begin with. + QMessageBox mb; + mb.setText(tr("Sorry! This memo has no reply to address.")); + mb.setWindowTitle(tr("Error")); + + mb.setTextFormat(Qt::PlainText); + mb.setTextInteractionFlags(Qt::TextSelectableByMouse | Qt::TextSelectableByKeyboard); + mb.exec(); + } + } }); } @@ -1658,6 +2043,7 @@ void MainWindow::setupReceiveTab() { ui->rcvBal->setText(Settings::getHUSHUSDDisplayFormat(rpc->getAllBalances()->value(addr))); ui->txtReceive->setPlainText(addr); ui->qrcodeDisplay->setQrcodeString(addr); + if (rpc->getUsedAddresses()->value(addr, false)) { ui->rcvBal->setToolTip(tr("Address has been previously used")); } else { @@ -1747,7 +2133,7 @@ void MainWindow::updateLabels() { void MainWindow::slot_change_currency(const QString& currency_name) { - qDebug() << "slot_change_currency"; //<< ": " << currency_name; + qDebug() << __func__ << ": " << currency_name; Settings::getInstance()->set_currency_name(currency_name); qDebug() << "Refreshing price stats after currency change"; rpc->refreshPrice(); @@ -1762,9 +2148,17 @@ void MainWindow::slot_change_currency(const QString& currency_name) } } -void MainWindow::slot_change_theme(const QString& theme_name) +void MainWindow::slot_change_theme(QString& theme_name) { - Settings::getInstance()->set_theme_name(theme_name); + qDebug() << __func__ << ": theme_name=" << theme_name; + + if (theme_name == "dark" || theme_name == "default" || theme_name == "light" || + theme_name == "midnight" || theme_name == "blue") { + Settings::getInstance()->set_theme_name(theme_name); + } else { + qDebug() << __func__ << ": ignoring invalid theme_name=" << theme_name; + Settings::getInstance()->set_theme_name("dark"); + } // Include css QString saved_theme_name; @@ -1772,10 +2166,12 @@ void MainWindow::slot_change_theme(const QString& theme_name) saved_theme_name = Settings::getInstance()->get_theme_name(); } catch (const std::exception& e) { qDebug() << QString("Ignoring theme change Exception! : "); - saved_theme_name = "default"; + saved_theme_name = "dark"; } - QFile qFile(":/css/res/css/" + saved_theme_name +".css"); + QString filename = ":/css/res/css/" + saved_theme_name +".css"; + QFile qFile(filename); + qDebug() << __func__ << ": attempting to open filename=" << filename; if (qFile.open(QFile::ReadOnly)) { QString styleSheet = QLatin1String(qFile.readAll()); @@ -1785,6 +2181,46 @@ void MainWindow::slot_change_theme(const QString& theme_name) } +void MainWindow::rescanButtonClicked(int number) { + + qDebug() << "rescanButtonClicked" << number; + + // Setup rescan dialog + Ui_RescanDialog rescanDialog; + QDialog dialog(this); + rescanDialog.setupUi(&dialog); + + // TODO: Maybe set to current blockheight by default + rescanDialog.rescanBlockheight->setFocus(); + + // Add validator for block height + QRegExpValidator* heightValidator = new QRegExpValidator(QRegExp("\\d*"), this); + rescanDialog.rescanBlockheight->setValidator(heightValidator); + + // Check if OK clicked + if (dialog.exec() == QDialog::Accepted) { + + // Show message in status bar + ui->statusBar->showMessage(tr("Rescanning...")); + + // Close settings + QWidget *modalWidget = QApplication::activeModalWidget(); + if (modalWidget) + modalWidget->close(); + + // Get submitted rescan height + int rescanHeight = rescanDialog.rescanBlockheight->text().toInt(); + qDebug() << "rescan height = " << rescanHeight; + + // Call rescan RPC + rpc->rescan(rescanHeight, [=] (QJsonValue response){ + qDebug() << "rescanning finished" << response; + ui->statusBar->showMessage(tr("Rescanning finished")); + }); + + } +} + MainWindow::~MainWindow() { delete ui; diff --git a/src/mainwindow.h b/src/mainwindow.h index ecf9edd..faf7d52 100644 --- a/src/mainwindow.h +++ b/src/mainwindow.h @@ -6,6 +6,7 @@ #include "precompiled.h" #include "logger.h" #include +#include "ui_settings.h" // Forward declare to break circular dependency. class RPC; @@ -64,6 +65,7 @@ public: void updateFromCombo(); Ui::MainWindow* ui; + Ui_Settings settings; QLabel* statusLabel; QLabel* statusIcon; @@ -72,10 +74,21 @@ public: Logger* logger; void doClose(); + // loads a language by the given language shortcode (e.g. de, en) + void loadLanguage(QString& rLanguage); + +protected: + // this event is called, when a new translator is loaded or the system language is changed + void changeEvent(QEvent* event); + +protected slots: + // this slot is called by the language menu actions + void slotLanguageChanged(QString lang); private: void closeEvent(QCloseEvent* event); + void checkRescan(); void setupSendTab(); void setupPeersTab(); void setupTransactionsTab(); @@ -85,8 +98,9 @@ private: void setupChatTab(); void setupMarketTab(); - void slot_change_theme(const QString& themeName); + void slot_change_theme(QString& themeName); void slot_change_currency(const QString& currencyName); + void setupTurnstileDialog(); void setupSettingsModal(); void setupStatusBar(); @@ -113,6 +127,8 @@ private: void memoButtonClicked(int number, bool includeReplyTo = false); void fileUploadButtonClicked(int number); void setMemoEnabled(int number, bool enabled); + + void rescanButtonClicked(int number); void donate(); void website(); @@ -124,6 +140,7 @@ private: void exportAllKeys(); void exportKeys(QString addr = ""); void getViewKey(QString addr = ""); + void getQRCode(QString addr = ""); void backupWalletDat(); void exportTransactions(); @@ -144,6 +161,13 @@ private: QRegExpValidator* feesValidator = nullptr; QMovie* loadingMovie; + // creates the language menu dynamically from the content of m_langPath + void createLanguageMenu(void); + + QTranslator m_translator; // contains the translations for this application + QTranslator m_translatorQt; // contains the translations for qt + QString m_currLang; // contains the currently loaded language + QString m_langPath; // Path of language files }; #endif // MAINWINDOW_H diff --git a/src/mainwindow.ui b/src/mainwindow.ui index e03874a..092ccdc 100644 --- a/src/mainwindow.ui +++ b/src/mainwindow.ui @@ -22,7 +22,7 @@ - 5 + 0 @@ -222,7 +222,7 @@ - + 0 @@ -250,6 +250,9 @@ + + + QAbstractItemView::SingleSelection @@ -385,8 +388,8 @@ 0 0 - 1403 - 619 + 1447 + 860 @@ -395,33 +398,65 @@ Recipient - - - - - + + + + + 0 + + + QLayout::SetDefaultConstraint + + + 0 + + + + + + 0 + 0 + + - Address + - - - - - - Address + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop + + + true - - + + + + true + + + + 0 + 0 + + + + + 0 + 0 + + + + + - Address Book + Memo - + @@ -432,6 +467,18 @@ + + + 0 + 0 + + + + + 200 + 0 + + 200 @@ -458,6 +505,9 @@ Max Available + + true + @@ -465,6 +515,9 @@ Qt::Horizontal + + QSizePolicy::MinimumExpanding + 40 @@ -486,36 +539,33 @@ + + + + - - - true + + + Address - - + + + + + + Address + + + + - Memo + Address Book - - - - - 10 - - - - - - - true - - - @@ -893,16 +943,37 @@ - + - + 0 0 + + + 228 + 228 + + + + + 0 + 0 + + + + + 0 + 0 + + + + false + - background-color: #fff + @@ -915,7 +986,6 @@ - Transactions @@ -933,15 +1003,11 @@ - - Peers - - @@ -951,7 +1017,6 @@ - @@ -961,8 +1026,7 @@ QAbstractItemView::SelectRows - - + @@ -972,7 +1036,6 @@ - @@ -982,24 +1045,9 @@ QAbstractItemView::SelectRows - - - - - - - Market @@ -1082,42 +1130,6 @@ - hushd @@ -1148,9 +1160,9 @@ - + - You are currently not mining + @@ -1244,15 +1256,6 @@ - @@ -1456,7 +1459,6 @@ - @@ -1478,7 +1480,6 @@ - @@ -1500,7 +1501,6 @@ - @@ -1587,7 +1587,7 @@ 0 0 1487 - 42 + 30 @@ -1742,6 +1742,11 @@ + + FilledIconLabel + QLabel +
fillediconlabel.h
+
AddressCombo QComboBox @@ -1752,11 +1757,6 @@ QLabel
qrcodelabel.h
- - FilledIconLabel - QLabel -
fillediconlabel.h
-
inputsCombo diff --git a/src/mobileappconnector.ui b/src/mobileappconnector.ui index bf09f36..2ddc598 100644 --- a/src/mobileappconnector.ui +++ b/src/mobileappconnector.ui @@ -62,14 +62,20 @@
- + - + 0 0 + + + 228 + 228 + + background-color: #fff diff --git a/src/peerstablemodel.cpp b/src/peerstablemodel.cpp index 0be6076..39ce258 100644 --- a/src/peerstablemodel.cpp +++ b/src/peerstablemodel.cpp @@ -1,8 +1,9 @@ // Copyright 2019-2021 The Hush developers // Released under the GPLv3 -#include "txtablemodel.h" +#include "peerstablemodel.h" #include "settings.h" #include "rpc.h" +#include "guiconstants.h" PeersTableModel::PeersTableModel(QObject *parent) : QAbstractTableModel(parent) { @@ -52,23 +53,26 @@ int PeersTableModel::columnCount(const QModelIndex&) const } - QVariant PeersTableModel::data(const QModelIndex &index, int role) const - { - // Align column 4 (amount) right - //if (role == Qt::TextAlignmentRole && index.column() == 3) return QVariant(Qt::AlignRight | Qt::AlignVCenter); - +QVariant PeersTableModel::data(const QModelIndex &index, int role) const +{ + // Get current theme name + QString theme_name = Settings::getInstance()->get_theme_name(); + QBrush b; + if (role == Qt::ForegroundRole) { // peers with banscore >=50 will likely be banned soon, color them red if (modeldata->at(index.row()).banscore >= 50) { - QBrush b; - b.setColor(Qt::red); + b.setColor(COLOR_UNCONFIRMED_TX); return b; } - - // Else, just return the default brush - QBrush b; - b.setColor(Qt::black); - return b; + if (theme_name == "dark" || theme_name == "midnight") { + b.setColor(COLOR_WHITE); + return b; + }else{ + b.setColor(COLOR_BLACK); + return b; + } + return b; } auto dat = modeldata->at(index.row()); @@ -86,7 +90,7 @@ int PeersTableModel::columnCount(const QModelIndex&) const case 9: return dat.bytes_received; case 10: return dat.bytes_sent; } - } + } if (role == Qt::ToolTipRole) { switch (index.column()) { @@ -101,12 +105,12 @@ int PeersTableModel::columnCount(const QModelIndex&) const case 8: return "Banscore"; case 9: return "Bytes received"; case 10: return "Bytes sent"; - } + } } //TODO: show different icons for IP vs Tor vs other kinds of connections - /* + /* if (role == Qt::DecorationRole && index.column() == 0) { if (!dat.memo.isEmpty()) { // If the memo is a Payment URI, then show a payment request icon @@ -115,7 +119,7 @@ int PeersTableModel::columnCount(const QModelIndex&) const return QVariant(icon.pixmap(16, 16)); } else { // Return the info pixmap to indicate memo - QIcon icon = QApplication::style()->standardIcon(QStyle::SP_MessageBoxInformation); + QIcon icon = QApplication::style()->standardIcon(QStyle::SP_MessageBoxInformation); return QVariant(icon.pixmap(16, 16)); } } else { @@ -128,7 +132,7 @@ int PeersTableModel::columnCount(const QModelIndex&) const */ return QVariant(); - } +} QVariant PeersTableModel::headerData(int section, Qt::Orientation orientation, int role) const diff --git a/src/qrcode.ui b/src/qrcode.ui new file mode 100644 index 0000000..81f7eaf --- /dev/null +++ b/src/qrcode.ui @@ -0,0 +1,71 @@ + + + QRCode + + + + 0 + 0 + 320 + 320 + + + + QR Code + + + + QLayout::SetFixedSize + + + + + + 0 + 0 + + + + + 300 + 300 + + + + + 0 + 0 + + + + + 0 + 0 + + + + false + + + + + + + + + Qt::AlignHCenter|Qt::AlignTop + + + + + + + + QRCodeLabel + QLabel +
qrcodelabel.h
+
+
+ + +
diff --git a/src/qrcodelabel.cpp b/src/qrcodelabel.cpp index 2283785..e52afc5 100644 --- a/src/qrcodelabel.cpp +++ b/src/qrcodelabel.cpp @@ -26,7 +26,7 @@ QPixmap QRCodeLabel::scaledPixmap() const { pm.fill(Qt::white); QPainter painter(&pm); - qrcodegen::QrCode qr = qrcodegen::QrCode::encodeText(str.toUtf8().constData(), qrcodegen::QrCode::Ecc::LOW); + qrcodegen::QrCode qr = qrcodegen::QrCode::encodeText(str.toUtf8().constData(), qrcodegen::QrCode::Ecc::HIGH); const int s = qr.getSize()>0?qr.getSize():1; const double w = pm.width(); const double h = pm.height(); @@ -49,7 +49,11 @@ QPixmap QRCodeLabel::scaledPixmap() const { } } } - + + // TODO: Maybe add logo if it doesn't break QR code - requires setting Ecc to HIGH + painter.drawPixmap((w/2)-50, (h/2)-50, 100, 100, QPixmap(":/img/res/logobig.gif")); + painter.end(); + return pm; } diff --git a/src/rescandialog.ui b/src/rescandialog.ui new file mode 100644 index 0000000..65e1ea1 --- /dev/null +++ b/src/rescandialog.ui @@ -0,0 +1,91 @@ + + + RescanDialog + + + + 0 + 0 + 542 + 108 + + + + Rescan + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + Qt::Horizontal + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + + + + + + + + Enter block height to rescan from: + + + + + + + + + buttonBox + accepted() + RescanDialog + accept() + + + 248 + 254 + + + 157 + 274 + + + + + buttonBox + rejected() + RescanDialog + reject() + + + 316 + 260 + + + 286 + 274 + + + + + diff --git a/src/rpc.cpp b/src/rpc.cpp index 3619bdb..2eef349 100644 --- a/src/rpc.cpp +++ b/src/rpc.cpp @@ -52,17 +52,22 @@ RPC::RPC(MainWindow* main) { }); timer->start(Settings::updateSpeed); + // Set up the timer to watch for tx status txTimer = new QTimer(main); QObject::connect(txTimer, &QTimer::timeout, [=]() { //qDebug() << "Watching tx status"; watchTxStatus(); }); - - txTimer->start(Settings::updateSpeed); qDebug() << __func__ << "Done settings up all timers"; usedAddresses = new QMap(); + + auto lang = Settings::getInstance()->get_language(); + qDebug() << __func__ << ": found lang="<< lang << " in config file"; + + main->loadLanguage(lang); + qDebug() << __func__ << ": setting UI to lang="<< lang << " found in config file"; } RPC::~RPC() { @@ -110,12 +115,32 @@ void RPC::setConnection(Connection* c) { refresh(true); } -QJsonValue RPC::makePayload(QString method, QString params) { +QJsonValue RPC::makePayload(QString method, QString param, QString param2) { + QJsonObject payload = { + {"jsonrpc", "1.0"}, + {"id", "42" }, + {"method", method }, + {"params", QJsonArray {param, param2}} + }; + return payload; +} + +QJsonValue RPC::makePayload(QString method, QString param) { + QJsonObject payload = { + {"jsonrpc", "1.0"}, + {"id", "42" }, + {"method", method }, + {"params", QJsonArray {param}} + }; + return payload; +} + +QJsonValue RPC::makePayload(QString method, int param) { QJsonObject payload = { {"jsonrpc", "1.0"}, {"id", "42" }, {"method", method }, - {"params", QJsonArray {params}} + {"params", QJsonArray{param}} }; return payload; } @@ -129,12 +154,47 @@ QJsonValue RPC::makePayload(QString method) { return payload; } +//TODO: we can use listaddresses void RPC::getTAddresses(const std::function& cb) { QString method = "getaddressesbyaccount"; QString params = ""; conn->doRPCWithDefaultErrorHandling(makePayload(method, ""), cb); } +// full or partial rescan +void RPC::rescan(qint64 height, const std::function& cb) { + QString method = "rescan"; + conn->doRPCWithDefaultErrorHandling(makePayload(method, height), cb); +} + +// get rescan info +void RPC::getRescanInfo(const std::function& cb){ + QString method = "getrescaninfo"; + conn->doRPCWithDefaultErrorHandling(makePayload(method), cb); +} + +// add/remove a banned node. ip can include an optional netmask +void RPC::setban(QString ip, QString command, const std::function& cb) { + QString method = "setban"; + conn->doRPCWithDefaultErrorHandling(makePayload(method, ip, command), cb); +} + +//unban all banned peer nodes +void RPC::clearBanned(const std::function& cb) { + QString method = "clearbanned"; + conn->doRPCWithDefaultErrorHandling(makePayload(method), cb); +} + +void RPC::z_sweepstatus(const std::function& cb) { + QString method = "z_sweepstatus"; + conn->doRPCWithDefaultErrorHandling(makePayload(method), cb); +} + +void RPC::z_consolidationstatus(const std::function& cb) { + QString method = "z_consolidationstatus"; + conn->doRPCWithDefaultErrorHandling(makePayload(method), cb); +} + void RPC::getZAddresses(const std::function& cb) { QString method = "z_listaddresses"; conn->doRPCWithDefaultErrorHandling(makePayload(method), cb); @@ -143,7 +203,7 @@ void RPC::getZAddresses(const std::function& cb) { void RPC::getTransparentUnspent(const std::function& cb) { QJsonObject payload = { {"jsonrpc", "1.0"}, - {"id", "someid"}, + {"id", "42"}, {"method", "listunspent"}, {"params", QJsonArray {0}} // Get UTXOs with 0 confirmations as well. }; @@ -154,7 +214,7 @@ void RPC::getTransparentUnspent(const std::function& cb) { void RPC::getZUnspent(const std::function& cb) { QJsonObject payload = { {"jsonrpc", "1.0"}, - {"id", "someid"}, + {"id", "42"}, {"method", "z_listunspent"}, {"params", QJsonArray {0}} // Get UTXOs with 0 confirmations as well. }; @@ -165,7 +225,7 @@ void RPC::getZUnspent(const std::function& cb) { void RPC::newZaddr(const std::function& cb) { QJsonObject payload = { {"jsonrpc", "1.0"}, - {"id", "someid"}, + {"id", "42"}, {"method", "z_getnewaddress"}, {"params", QJsonArray { "sapling" }}, }; @@ -197,7 +257,7 @@ void RPC::getTPrivKey(QString addr, const std::function& cb) { void RPC::importZPrivKey(QString privkey, bool rescan, const std::function& cb) { QJsonObject payload = { {"jsonrpc", "1.0"}, - {"id", "someid"}, + {"id", "42"}, {"method", "z_importkey"}, {"params", QJsonArray { privkey, (rescan ? "yes" : "no") }}, }; @@ -215,7 +275,7 @@ void RPC::importTPrivKey(QString privkey, bool rescan, const std::function void RPC::getBalance(const std::function& cb) { QJsonObject payload = { {"jsonrpc", "1.0"}, - {"id", "someid"}, + {"id", "42"}, {"method", "z_gettotalbalance"}, {"params", QJsonArray {0}} // Get Unconfirmed balance as well. }; @@ -305,7 +365,7 @@ void RPC::sendZTransaction(QJsonValue params, const std::function& err) { QJsonObject payload = { {"jsonrpc", "1.0"}, - {"id", "someid"}, + {"id", "42"}, {"method", "z_sendmany"}, {"params", params} }; @@ -366,7 +426,7 @@ void RPC::getAllPrivKeys(const std::function> [=] (auto addr) { QJsonObject payload = { {"jsonrpc", "1.0"}, - {"id", "someid"}, + {"id", "42"}, {"method", privKeyDumpMethodName}, {"params", QJsonArray { addr }}, }; @@ -391,14 +451,14 @@ void RPC::getAllPrivKeys(const std::function> // First get all the t and z addresses. QJsonObject payloadT = { {"jsonrpc", "1.0"}, - {"id", "someid"}, + {"id", "42"}, {"method", "getaddressesbyaccount"}, {"params", QJsonArray {""} } }; QJsonObject payloadZ = { {"jsonrpc", "1.0"}, - {"id", "someid"}, + {"id", "42"}, {"method", "z_listaddresses"} }; @@ -597,7 +657,6 @@ void RPC::refresh(bool force) { getInfoThenRefresh(force); } - void RPC::getInfoThenRefresh(bool force) { //qDebug() << "getinfo"; if (conn == nullptr) @@ -928,11 +987,12 @@ void RPC::refreshPeers() { QList peerdata; for (const auto& it : reply.toArray()) { auto addr = it.toObject()["address"].toString(); + auto asn = (qint64)it.toObject()["mapped_as"].toInt(); auto bantime = (qint64)it.toObject()["banned_until"].toInt(); auto parts = addr.split("/"); auto ip = parts[0]; auto subnet = parts[1]; - BannedPeerItem peer { ip, subnet, bantime }; + BannedPeerItem peer { ip, subnet, bantime, asn }; qDebug() << "Adding banned peer with address=" << addr; peerdata.push_back(peer); } @@ -980,6 +1040,9 @@ void RPC::refreshTransactions() { if (conn == nullptr) return noConnection(); + // Show statusBar message + ui->statusBar->showMessage(QObject::tr("Transaction data is loading...")); + getTransactions([=] (QJsonValue reply) { QList txdata; @@ -1006,7 +1069,11 @@ void RPC::refreshTransactions() { } // Update model data, which updates the table view - transactionsTableModel->addTData(txdata); + qDebug() << "refreshTransactions"; + transactionsTableModel->addTData(txdata); + + // Update statusBar message + ui->statusBar->showMessage(QObject::tr("Transaction data loaded"), 3 * 1000); }); } @@ -1056,7 +1123,7 @@ void RPC::refreshSentZTrans() { if (!error) sentTx.confirmations = j["confirmations"].toInt(); } - + transactionsTableModel->addZSentData(newSentZTxs); delete txidList; } @@ -1135,8 +1202,10 @@ void RPC::watchTxStatus() { } if (watchingOps.isEmpty()) { - txTimer->start(Settings::updateSpeed); + // Stop the timer + txTimer->stop(); } else { + // Keep polling for updates txTimer->start(Settings::quickUpdateSpeed); } } @@ -1359,18 +1428,11 @@ void RPC::shutdownHushd() { connD.setupUi(&d); //connD.topIcon->setBasePixmap(QIcon(":/icons/res/icon.ico").pixmap(256, 256)); - QMovie *movie1 = new QMovie(":/img/res/silentdragon-animated.gif");; - QMovie *movie2 = new QMovie(":/img/res/silentdragon-animated-dark.gif");; + QMovie *movie1 = new QMovie(":/img/res/silentdragon-animated-dark.gif");; auto theme = Settings::getInstance()->get_theme_name(); - if (theme == "dark" || theme == "midnight") { - movie2->setScaledSize(QSize(512,512)); - connD.topIcon->setMovie(movie2); - movie2->start(); - } else { - movie1->setScaledSize(QSize(512,512)); - connD.topIcon->setMovie(movie1); - movie1->start(); - } + movie1->setScaledSize(QSize(512,512)); + connD.topIcon->setMovie(movie1); + movie1->start(); connD.status->setText(QObject::tr("Please enhance your calm and wait for SilentDragon to exit")); connD.statusDetail->setText(QObject::tr("Waiting for hushd to exit, y'all")); diff --git a/src/rpc.h b/src/rpc.h old mode 100644 new mode 100755 index c5ede99..01a73b3 --- a/src/rpc.h +++ b/src/rpc.h @@ -27,6 +27,7 @@ struct BannedPeerItem { QString address; QString subnet; qint64 banned_until; + qint64 asn; }; struct PeerItem { @@ -66,8 +67,10 @@ public: void refresh(bool force = false); void refreshAddresses(); - void refreshPeers(); - + void refreshPeers(); + void setban(QString ip, QString command, const std::function& cb); + void clearBanned(const std::function& cb); + void checkForUpdate(bool silent = true); void refreshPrice(); void getZboardTopics(std::function)> cb); @@ -115,6 +118,9 @@ public: Connection* getConnection() { return conn; } + void rescan(qint64 height, const std::function& cb); + void getRescanInfo(const std::function& cb); + private: void refreshBalances(); @@ -128,7 +134,9 @@ private: void getInfoThenRefresh(bool force); void getBalance(const std::function& cb); - QJsonValue makePayload(QString method, QString params); + QJsonValue makePayload(QString method, QString param, QString param2); + QJsonValue makePayload(QString method, QString param); + QJsonValue makePayload(QString method, int param); QJsonValue makePayload(QString method); void getTransparentUnspent (const std::function& cb); @@ -138,6 +146,8 @@ private: void getPeerInfo (const std::function& cb); void getZAddresses (const std::function& cb); void getTAddresses (const std::function& cb); + void z_sweepstatus (const std::function& cb); + void z_consolidationstatus (const std::function& cb); Connection* conn = nullptr; std::shared_ptr ehushd = nullptr; diff --git a/src/scripts/make-deb.sh b/src/scripts/make-deb.sh index b1e6b70..be3916c 100755 --- a/src/scripts/make-deb.sh +++ b/src/scripts/make-deb.sh @@ -6,7 +6,7 @@ DEBLOG=deb.log.$$ if [ -z $QT_STATIC ]; then - echo "QT_STATIC is not set. Please set it to the base directory of a statically compiled Qt"; + echo "QT_STATIC is not set; to set it use -static for QT configuration. Please set it to the base directory of a statically compiled Qt"; exit 1; fi @@ -15,7 +15,7 @@ if [ -z $APP_VERSION ]; then echo "APP_VERSION is not set"; exit 1; fi #if [ -z $PREV_VERSION ]; then echo "PREV_VERSION is not set"; exit 1; fi if [ -z $HUSH_DIR ]; then - echo "HUSH_DIR is not set. Please set it to the base directory of hush3.git" + echo "HUSH_DIR is not set. Please set it to the src directory of hush3.git" exit 1; fi @@ -34,7 +34,12 @@ if [ ! -f $HUSH_DIR/hush-tx ]; then exit 1; fi -echo -n "Cleaning..............." +if [ ! -f $HUSH_DIR/hush-smart-chain ]; then + echo "Couldn't find hush-smart-chain in $HUSH_DIR . Please build hush-smart-chain." + exit 1; +fi + +echo "Cleaning..............." rm -rf bin/* rm -rf artifacts/* make distclean >/dev/null 2>&1 @@ -43,7 +48,7 @@ echo "[OK]" echo "" echo "[Building $APP_VERSION on" `lsb_release -r`" logging to $DEBLOG ]" -echo -n "Translations............" +echo "Translations............" QT_STATIC=$QT_STATIC bash src/scripts/dotranslations.sh >/dev/null echo -n "Configuring............" $QT_STATIC/bin/qmake silentdragon.pro -spec linux-clang CONFIG+=release > /dev/null @@ -58,7 +63,7 @@ echo "[OK]" # Test for Qt -echo -n "Static link............" +echo "Static link............" if [[ $(ldd silentdragon | grep -i "Qt") ]]; then echo "FOUND QT; ABORT"; exit 1 @@ -66,34 +71,39 @@ fi echo "[OK]" -echo -n "Packaging.............." +echo "Packaging.............." APP=SilentDragon-v$APP_VERSION DIR=bin/$APP mkdir $DIR > /dev/null -strip silentdragon -cp silentdragon $DIR > /dev/null -cp $HUSH_DIR/artifacts/hushd $DIR > /dev/null -cp $HUSH_DIR/artifacts/hush-cli $DIR > /dev/null -cp $HUSH_DIR/artifacts/hush-tx $DIR > /dev/null -cp README.md $DIR > /dev/null -cp LICENSE $DIR > /dev/null +#Organizing all bins & essentials to centralized folder for tar.gz +echo "Organizing binaries & essentials.............." +cp silentdragon $DIR > /dev/null +cp README.md $DIR > /dev/null +cp LICENSE $DIR > /dev/null -cd bin && tar czf $APP.tar.gz $DIR/ > /dev/null -cd .. +echo "Stripping silentdragon.............." +cd $DIR +strip silentdragon +cd ../.. -mkdir artifacts >/dev/null 2>&1 -cp $DIR.tar.gz ./artifacts/$APP-linux.tar.gz -echo "[OK]" +echo "Compressing files.............." +cd bin/ +tar -czf $APP.tar.gz ./$APP > /dev/null +echo "Copy compressed file.............." +mkdir artifacts >/dev/null 2>&1 +cp $APP.tar.gz ./artifacts/$APP-linux.tar.gz +echo -n "[OK]" +echo "Verify Compressed File.............." if [ -f artifacts/$APP-linux.tar.gz ] ; then echo -n "Package contents......." # Test if the package is built OK - if tar tf "artifacts/$APP-linux.tar.gz" | wc -l | grep -q "9"; then + if tar -tf "artifacts/$APP-linux.tar.gz" | wc -l | grep -q "4"; then echo "[OK]" else - echo "[ERROR] Wrong number of files does not match 9" + echo "[ERROR] Wrong number of files does not match 11" exit 1 fi else @@ -101,25 +111,37 @@ else exit 1 fi -echo -n "Building deb..........." -debdir=bin/deb/silentdragon-v$APP_VERSION +echo "Building package..........." +debdir=deb/silentdragon-v$APP_VERSION mkdir -p $debdir > /dev/null mkdir $debdir/DEBIAN mkdir -p $debdir/usr/local/bin -cat src/scripts/control | sed "s/RELEASE_VERSION/$APP_VERSION/g" > $debdir/DEBIAN/control +mkdir -p $debdir/usr/lib +mkdir -p $debdir/usr/share/pixmaps/ + +cat ../src/scripts/control | sed "s/RELEASE_VERSION/$APP_VERSION/g" > $debdir/DEBIAN/control -cp silentdragon $debdir/usr/local/bin/ -cp $HUSH_DIR/artifacts/hushd $debdir/usr/local/bin/hushd +echo "Copying silentdragon bin..........." +cp ../silentdragon $debdir/usr/local/bin/ -mkdir -p $debdir/usr/share/pixmaps/ -cp res/silentdragon.xpm $debdir/usr/share/pixmaps/ +echo "Copying core libraries from silentdragon binary..........." +# copy the required shared libs to the target folder +# create directories if required +for lib in `ldd $debdir/usr/local/bin/silentdragon | cut -d'>' -f2 | awk '{print $1}'` ; do + if [ -f "$lib" ] ; then + cp -v "$lib" $debdir/usr/lib/ + fi +done + +echo "Copying SilentDragon icon..........." +cp ../res/silentdragon.xpm $debdir/usr/share/pixmaps/ mkdir -p $debdir/usr/share/applications -cp src/scripts/desktopentry $debdir/usr/share/applications/silentdragon.desktop +cp ../src/scripts/desktopentry $debdir/usr/share/applications/silentdragon.desktop -dpkg-deb --build $debdir >/dev/null -cp $debdir.deb artifacts/$DIR.deb -echo "[OK]" +dpkg-deb --build --root-owner-group $debdir >/dev/null + +echo "[Success! $APP .deb has been created in $APP/bin/deb]" exit 0 diff --git a/src/scripts/mkmacdmg.sh b/src/scripts/mkmacdmg.sh index f738999..127b25f 100644 --- a/src/scripts/mkmacdmg.sh +++ b/src/scripts/mkmacdmg.sh @@ -114,8 +114,6 @@ rm -f artifcats/silentdragon.dmg >/dev/null 2>&1 rm -f artifacts/rw* >/dev/null 2>&1 cp $HUSH_DIR/src/hushd silentdragon.app/Contents/MacOS/ cp $HUSH_DIR/src/hush-cli silentdragon.app/Contents/MacOS/ -cp $HUSH_DIR/src/komodod silentdragon.app/Contents/MacOS/ -cp $HUSH_DIR/src/komodo-cli silentdragon.app/Contents/MacOS/ cp $HUSH_DIR/sapling-output.params silentdragon.app/Contents/MacOS/ cp $HUSH_DIR/sapling-spend.params silentdragon.app/Contents/MacOS/ $QT_PATH/bin/macdeployqt silentdragon.app diff --git a/src/scripts/mkrelease.sh b/src/scripts/mkrelease.sh index cbc3192..65e8037 100755 --- a/src/scripts/mkrelease.sh +++ b/src/scripts/mkrelease.sh @@ -21,18 +21,18 @@ if [ -z $HUSH_DIR ]; then exit 1; fi -if [ ! -f $HUSH_DIR/komodod ]; then - echo "Couldn't find komodod in $HUSH_DIR . Please build komodod." +if [ ! -f $HUSH_DIR/hushd ]; then + echo "Couldn't find hushd in $HUSH_DIR . Please build hushd." exit 1; fi -if [ ! -f $HUSH_DIR/komodo-cli ]; then - echo "Couldn't find komodo-cli in $HUSH_DIR . Please build komodo-cli." +if [ ! -f $HUSH_DIR/hush-cli ]; then + echo "Couldn't find hush-cli in $HUSH_DIR . Please build hush-cli." exit 1; fi -if [ ! -f $HUSH_DIR/komodo-tx ]; then - echo "Couldn't find komodo-tx in $HUSH_DIR . Please build komodo-tx." +if [ ! -f $HUSH_DIR/hush-tx ]; then + echo "Couldn't find hush-tx in $HUSH_DIR . Please build hush-tx." exit 1; fi @@ -80,8 +80,8 @@ RELEASEFILE2=$RELEASEDIR-$OS-$ARCH.tar.gz # this is equal to the number of files we package plus 1, for the directory # that is created -NUM_FILES1=10 -NUM_FILES2=12 # 2 additional param files +NUM_FILES1=6 +NUM_FILES2=8 # 2 additional param files echo "Packaging.............." mkdir bin/$RELEASEDIR @@ -92,18 +92,18 @@ strip silentdragon ls -la silentdragon cp silentdragon bin/$RELEASEDIR > /dev/null -cp $HUSH_DIR/komodod bin/$RELEASEDIR > /dev/null -cp $HUSH_DIR/komodo-cli bin/$RELEASEDIR > /dev/null -cp $HUSH_DIR/komodo-tx bin/$RELEASEDIR > /dev/null +strip $HUSH_DIR/hushd cp $HUSH_DIR/hushd bin/$RELEASEDIR > /dev/null +strip $HUSH_DIR/hush-cli cp $HUSH_DIR/hush-cli bin/$RELEASEDIR > /dev/null -cp $HUSH_DIR/hush-tx bin/$RELEASEDIR > /dev/null +# I have yet to hear of somebody using this binary, it just bloats our archives +#cp $HUSH_DIR/hush-tx bin/$RELEASEDIR > /dev/null cp README.md bin/$RELEASEDIR > /dev/null cp LICENSE bin/$RELEASEDIR > /dev/null cd bin && tar czf $RELEASEFILE1 $RELEASEDIR/ #> /dev/null -#ls -la $RELEASEDIR/ +ls -la $RELEASEDIR/ echo "Created $RELEASEFILE1 [OK]" cd .. @@ -121,7 +121,7 @@ else fi cd bin && tar czf $RELEASEFILE2 $RELEASEDIR/ -#ls -la $RELEASEDIR/ +ls -la $RELEASEDIR/ echo "Created $RELEASEFILE2 [OK]" cd .. @@ -157,6 +157,8 @@ else fi cd bin +du -sh $RELEASEFILE1 +du -sh $RELEASEFILE2 echo "DONE! Checksums:" sha256sum $RELEASEFILE1 sha256sum $RELEASEFILE2 @@ -177,7 +179,7 @@ cat src/scripts/control | sed "s/RELEASE_VERSION/$APP_VERSION/g" > $debdir/DEBIA cp silentdragon $debdir/usr/local/bin/ # TODO: how does this interact with hushd deb ? -cp $HUSH_DIR/artifacts/komodod $debdir/usr/local/bin/hush-komodod +cp $HUSH_DIR/artifacts/hushd $debdir/usr/local/bin/hushd mkdir -p $debdir/usr/share/pixmaps/ cp res/silentdragon.xpm $debdir/usr/share/pixmaps/ @@ -202,14 +204,14 @@ if [ -z $MXE_PATH ]; then exit 0; fi -if [ ! -f $HUSH_DIR/artifacts/komodod.exe ]; then - echo "Couldn't find komodod.exe in $HUSH_DIR/artifacts/. Please build komodod.exe" +if [ ! -f $HUSH_DIR/artifacts/hushd.exe ]; then + echo "Couldn't find hushd.exe in $HUSH_DIR/artifacts/. Please build hushd.exe" exit 1; fi -if [ ! -f $HUSH_DIR/artifacts/komodo-cli.exe ]; then - echo "Couldn't find komodo-cli.exe in $HUSH_DIR/artifacts/. Please build komodod-cli.exe" +if [ ! -f $HUSH_DIR/artifacts/hush-cli.exe ]; then + echo "Couldn't find hush-cli.exe in $HUSH_DIR/artifacts/. Please build hushd-cli.exe" exit 1; fi @@ -234,10 +236,8 @@ echo "[OK]" echo -n "Packaging.............." mkdir release/silentdragon-v$APP_VERSION cp release/silentdragon.exe release/silentdragon-v$APP_VERSION -cp $HUSH_DIR/artifacts/komodod.exe release/silentdragon-v$APP_VERSION > /dev/null -cp $HUSH_DIR/artifacts/komodo-cli.exe release/silentdragon-v$APP_VERSION > /dev/null -cp $HUSH_DIR/artifacts/hushd.bat release/silentdragon-v$APP_VERSION > /dev/null -cp $HUSH_DIR/artifacts/hush-cli.bat release/silentdragon-v$APP_VERSION > /dev/null +cp $HUSH_DIR/artifacts/hushd.exe release/silentdragon-v$APP_VERSION > /dev/null +cp $HUSH_DIR/artifacts/hush-cli.exe release/silentdragon-v$APP_VERSION > /dev/null cp README.md release/silentdragon-v$APP_VERSION cp LICENSE release/silentdragon-v$APP_VERSION cd release && zip -r Windows-binaries-silentdragon-v$APP_VERSION.zip silentdragon-v$APP_VERSION/ > /dev/null diff --git a/src/scripts/silentdragon.wxs b/src/scripts/silentdragon.wxs new file mode 100644 index 0000000..987632a --- /dev/null +++ b/src/scripts/silentdragon.wxs @@ -0,0 +1,81 @@ + + + + + + + + + + + + + + + + + + + + + + + + 1 + 1 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/sendtab.cpp b/src/sendtab.cpp index 6c5e11f..c734d7c 100644 --- a/src/sendtab.cpp +++ b/src/sendtab.cpp @@ -14,7 +14,7 @@ void MainWindow::setupSendTab() { // Create the validator for send to/amount fields - amtValidator = new QRegExpValidator(QRegExp("[0-9]{0,8}\\.?[0-9]{0,8}")); + amtValidator = new QRegExpValidator(QRegExp("[0-9]{0,9}\\.?[0-9]{0,8}")); ui->Amount1->setValidator(amtValidator); @@ -251,51 +251,69 @@ void MainWindow::addAddressSection() { Amount1->setPlaceholderText(tr("Amount")); Amount1->setObjectName(QString("Amount") % QString::number(itemNumber)); Amount1->setBaseSize(QSize(200, 0)); - Amount1->setAlignment(Qt::AlignRight); + Amount1->setMinimumWidth(200); + Amount1->setSizePolicy(QSizePolicy ::Preferred , QSizePolicy ::Preferred ); + Amount1->setAlignment(Qt::AlignRight); + // Create the validator for send to/amount fields Amount1->setValidator(amtValidator); QObject::connect(Amount1, &QLineEdit::textChanged, [=] (auto text) { this->amountChanged(itemNumber, text); }); - horizontalLayout_13->addWidget(Amount1); auto AmtUSD1 = new QLabel(verticalGroupBox); - AmtUSD1->setObjectName(QString("AmtUSD") % QString::number(itemNumber)); + AmtUSD1->setObjectName(QString("AmtUSD") % QString::number(itemNumber)); horizontalLayout_13->addWidget(AmtUSD1); + /* TODO: Fix so it updates amount on correct recipient row...or just remove. Added for UI consistency. + auto Max1 = new QCheckBox(verticalGroupBox); + Max1->setText(tr("Max Available")); + // Connect Max Available checkbox + QObject::connect(Max1, &QCheckBox::stateChanged, [=] () { + this->maxAmountChecked(Max1->checkState()); + }); + horizontalLayout_13->addWidget(Max1); + */ + auto horizontalSpacer_4 = new QSpacerItem(40, 20, QSizePolicy::Expanding, QSizePolicy::Minimum); horizontalLayout_13->addItem(horizontalSpacer_4); - auto MemoBtn1 = new QPushButton(verticalGroupBox); - MemoBtn1->setObjectName(QString("MemoBtn") % QString::number(itemNumber)); - MemoBtn1->setText(tr("Memo")); - // Connect Memo Clicked button - QObject::connect(MemoBtn1, &QPushButton::clicked, [=] () { - this->memoButtonClicked(itemNumber); - }); - horizontalLayout_13->addWidget(MemoBtn1); - setMemoEnabled(itemNumber, false); - auto FileBtn = new QPushButton(verticalGroupBox); FileBtn->setObjectName(QString("FileBtn") % QString::number(itemNumber)); - FileBtn->setText(tr("File Upload")); + FileBtn->setText(tr("Upload File")); // Connect File Upload button QObject::connect(FileBtn, &QPushButton::clicked, [=] () { this->fileUploadButtonClicked(itemNumber); }); horizontalLayout_13->addWidget(FileBtn); - sendAddressLayout->addLayout(horizontalLayout_13); + auto horizontalLayout_20 = new QHBoxLayout(); + horizontalLayout_20->setSpacing(6); + horizontalLayout_20->setAlignment(Qt::AlignTop); + auto MemoTxt1 = new QLabel(verticalGroupBox); MemoTxt1->setObjectName(QString("MemoTxt") % QString::number(itemNumber)); + MemoTxt1->setSizePolicy(QSizePolicy ::MinimumExpanding , QSizePolicy ::MinimumExpanding ); QFont font1 = Address1->font(); font1.setPointSize(font1.pointSize()-1); MemoTxt1->setFont(font1); MemoTxt1->setWordWrap(true); - sendAddressLayout->addWidget(MemoTxt1); + horizontalLayout_20->addWidget(MemoTxt1, 0, Qt::AlignTop); + + auto MemoBtn1 = new QPushButton(verticalGroupBox); + MemoBtn1->setObjectName(QString("MemoBtn") % QString::number(itemNumber)); + MemoBtn1->setText(tr("Memo")); + // Connect Memo Clicked button + QObject::connect(MemoBtn1, &QPushButton::clicked, [=] () { + this->memoButtonClicked(itemNumber); + }); + horizontalLayout_20->addWidget(MemoBtn1, 0, Qt::AlignTop); + setMemoEnabled(itemNumber, false); + + sendAddressLayout->addLayout(horizontalLayout_20); ui->sendToLayout->insertWidget(itemNumber-1, verticalGroupBox); @@ -702,18 +720,11 @@ void MainWindow::sendButton() { auto connD = new Ui_ConnectionDialog(); connD->setupUi(d); - QMovie *movie1 = new QMovie(":/img/res/silentdragon-animated.gif");; - QMovie *movie2 = new QMovie(":/img/res/silentdragon-animated-dark.gif");; + QMovie *movie1 = new QMovie(":/img/res/silentdragon-animated-dark.gif");; auto theme = Settings::getInstance()->get_theme_name(); - if (theme == "dark" || theme == "midnight") { - movie2->setScaledSize(QSize(512,512)); - connD->topIcon->setMovie(movie2); - movie2->start(); - } else { - movie1->setScaledSize(QSize(512,512)); - connD->topIcon->setMovie(movie1); - movie1->start(); - } + movie1->setScaledSize(QSize(512,512)); + connD->topIcon->setMovie(movie1); + movie1->start(); //connD->topIcon->setBasePixmap(logo.scaled(256, 256, Qt::KeepAspectRatio, Qt::SmoothTransformation)); @@ -729,7 +740,7 @@ void MainWindow::sendButton() { qDebug() << "Computing opid: " << opid; }, - [=] (QString, QString txid) { + [=] (QString, QString txid) { ui->statusBar->showMessage(Settings::txidStatusMessage + " " + txid); connD->status->setText(tr("Done!")); @@ -744,7 +755,8 @@ void MainWindow::sendButton() { // And switch to the balances tab ui->tabWidget->setCurrentIndex(0); }); - // Force a UI update so we get the unconfirmed Tx + + // Force a UI update so we get the unconfirmed Tx rpc->refresh(true); }, [=] (QString opid, QString errStr) { diff --git a/src/senttxstore.cpp b/src/senttxstore.cpp index 91bf78a..75cb10a 100644 --- a/src/senttxstore.cpp +++ b/src/senttxstore.cpp @@ -14,6 +14,7 @@ QString SentTxStore::writeableFile() { if (Settings::getInstance()->isTestnet()) { return dir.filePath("testnet-" % filename); } else { + qDebug() << "senttxstore file = " + dir.filePath(filename); return dir.filePath(filename); } } @@ -45,7 +46,7 @@ QList SentTxStore::readSentTxFile() { sentTx["address"].toString(), sentTx["txid"].toString(), sentTx["amount"].toDouble() + sentTx["fee"].toDouble(), - 0, sentTx["from"].toString(), ""}; + 0, sentTx["from"].toString(), sentTx["memo"].toString()}; items.push_back(t); } diff --git a/src/settings.cpp b/src/settings.cpp index 07eb0b1..2cf5524 100644 --- a/src/settings.cpp +++ b/src/settings.cpp @@ -40,9 +40,20 @@ Explorer Settings::getExplorer() { auto txExplorerUrl = s.value("explorer/txExplorerUrl", explorer + "/tx/").toString(); auto addressExplorerUrl = s.value("explorer/addressExplorerUrl", explorer + "/address/").toString(); + auto testnetTxExplorerUrl = s.value("explorer/testnetTxExplorerUrl").toString(); auto testnetAddressExplorerUrl = s.value("explorer/testnetAddressExplorerUrl").toString(); + // Some users have the old malicious explorer URL saved in their config file, help them out + if (txExplorerUrl == "https://explorer.myhush.org/tx/") { + txExplorerUrl = explorer + "/tx/"; + saveExplorer(txExplorerUrl, addressExplorerUrl, testnetTxExplorerUrl, testnetAddressExplorerUrl); + } + if (addressExplorerUrl == "https://explorer.myhush.org/address/") { + addressExplorerUrl = explorer + "/address/"; + saveExplorer(txExplorerUrl, addressExplorerUrl, testnetTxExplorerUrl, testnetAddressExplorerUrl); + } + return Explorer{txExplorerUrl, addressExplorerUrl, testnetTxExplorerUrl, testnetAddressExplorerUrl}; } @@ -246,10 +257,13 @@ void Settings::setAllowCustomFees(bool allow) { QString Settings::get_theme_name() { // Load from the QT Settings. - return QSettings().value("options/theme_name", false).toString(); + QString theme_name = QSettings().value("options/theme_name", false).toString(); + //qDebug() << __func__ << ": theme_name=" << theme_name; + return theme_name; } void Settings::set_theme_name(QString theme_name) { + qDebug() << __func__ << ": settings theme_name=" << theme_name; QSettings().setValue("options/theme_name", theme_name); } @@ -351,6 +365,21 @@ void Settings::set_currency_name(QString currency_name) { QSettings().setValue("options/currency_name", currency_name); } +QString Settings::get_language() { + // use the default system language if none is set + QString locale = QLocale::system().name(); + // remove country data, i.e. en_US => en + locale.truncate( locale.lastIndexOf("_")); + auto lang = QSettings().value("options/language", locale).toString(); + qDebug() << __func__ << ": found lang=" << lang << " in config file"; + return lang; +} + +void Settings::set_language(QString lang) { + qDebug() << __func__ << ": setting lang=" << lang << " in config file"; + QSettings().setValue("options/language", lang); +} + bool Settings::removeFromHushConf(QString confLocation, QString option) { if (confLocation.isEmpty()) diff --git a/src/settings.h b/src/settings.h index d71d26f..32909d5 100644 --- a/src/settings.h +++ b/src/settings.h @@ -89,6 +89,9 @@ public: QString get_currency_name(); void set_currency_name(QString currency_name); + QString get_language(); + void set_language(QString lang); + void setUsingHushConf(QString confLocation); const QString& getHushdConfLocation() { return _confLocation; } @@ -132,6 +135,7 @@ public: static double getZboardAmount(); static QString getZboardAddr(); + //TODO: this could be an advanced setting too static int getMaxMobileAppTxns() { return 30; } static bool isValidAddress(QString addr); @@ -146,6 +150,10 @@ public: static const int quickUpdateSpeed = 3 * 1000; // 3 sec static const int priceRefreshSpeed = 15 * 60 * 1000; // 15 mins +protected: + // this event is called, when a new translator is loaded or the system language is changed + // void changeEvent(QEvent* event); + private: // This class can only be accessed through Settings::getInstance() Settings() = default; @@ -158,12 +166,11 @@ private: bool _isTestnet = false; bool _isSyncing = false; int _blockNumber = 0; - int _hushdVersion = 0; + int _hushdVersion = 0; bool _useEmbedded = false; bool _headless = false; int _peerConnections = 0; - - double hushPrice = 0.0; + double hushPrice = 0.0; double fiat_price = 0.0; unsigned int btcPrice = 0; std::map prices; diff --git a/src/settings.ui b/src/settings.ui index 0850bce..b00a11f 100644 --- a/src/settings.ui +++ b/src/settings.ui @@ -22,8 +22,8 @@ true - - + + 3 @@ -177,6 +177,32 @@ + + + + + 0 + 0 + + + + Language + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + + 0 + 0 + + + + @@ -412,27 +438,27 @@ - default + default - blue + blue - light + light - dark + dark - midnight + midnight @@ -698,6 +724,9 @@
+ + Qt::NoFocus + Troubleshooting @@ -717,32 +746,19 @@ - 9 - 38 + 10 + 10 583 51 - Rescan the blockchain for any missing wallet transactions and to correct your wallet balance. This may take several hours. You need to restart SilentDragon for this to take effect + Rescan the blockchain for any missing wallet transactions and to correct your wallet balance. Click rescan to enter block height to rescan from. This may take several hours depending on submitted block height. true - - - - 9 - 9 - 73 - 23 - - - - Rescan - - @@ -963,10 +979,35 @@ MB + + + + 310 + 60 + 281 + 41 + + + + Qt::StrongFocus + + + Rescan + + + false + + + true + + + false + +
- + Qt::Horizontal diff --git a/src/txtablemodel.cpp b/src/txtablemodel.cpp index d139aae..b213751 100644 --- a/src/txtablemodel.cpp +++ b/src/txtablemodel.cpp @@ -3,6 +3,7 @@ #include "txtablemodel.h" #include "settings.h" #include "rpc.h" +#include "guiconstants.h" TxTableModel::TxTableModel(QObject *parent) : QAbstractTableModel(parent) { @@ -56,7 +57,7 @@ bool TxTableModel::exportToCsv(QString fileName) const { out << "\"" << headers[i] << "\","; } out << "\"Memo\""; - out << endl; + out << Qt::endl; // Write out each row for (int row = 0; row < modeldata->length(); row++) { @@ -65,14 +66,14 @@ bool TxTableModel::exportToCsv(QString fileName) const { } // Memo out << "\"" << modeldata->at(row).memo << "\""; - out << endl; + out << Qt::endl; } file.close(); return true; } -void TxTableModel::updateAllData() { +void TxTableModel::updateAllData() { auto newmodeldata = new QList(); if (tTrans != nullptr) std::copy( tTrans->begin(), tTrans->end(), std::back_inserter(*newmodeldata)); @@ -92,33 +93,61 @@ void TxTableModel::updateAllData() { layoutChanged(); } - int TxTableModel::rowCount(const QModelIndex&) const - { + +QImage TxTableModel::colorizeIcon(QIcon icon, QColor color) const{ + QImage img(icon.pixmap(16, 16).toImage()); + img = img.convertToFormat(QImage::Format_ARGB32); + for (int x = img.width(); x--; ) + { + for (int y = img.height(); y--; ) + { + const QRgb rgb = img.pixel(x, y); + img.setPixel(x, y, qRgba(color.red(), color.green(), color.blue(), qAlpha(rgb))); + } + } + return img; +} + + +int TxTableModel::rowCount(const QModelIndex&) const +{ if (modeldata == nullptr) return 0; return modeldata->size(); - } +} - int TxTableModel::columnCount(const QModelIndex&) const - { +int TxTableModel::columnCount(const QModelIndex&) const +{ return headers.size(); - } +} QVariant TxTableModel::data(const QModelIndex &index, int role) const { - // Align column 4 (amount) right + // Get current theme name + QString theme_name = Settings::getInstance()->get_theme_name(); + QBrush b; + QColor color; + if (theme_name == "dark" || theme_name == "midnight") { + color = COLOR_WHITE; + }else{ + color = COLOR_BLACK; + } + + // Align column 4 (amount) right if (role == Qt::TextAlignmentRole && index.column() == 3) return QVariant(Qt::AlignRight | Qt::AlignVCenter); if (role == Qt::ForegroundRole) { if (modeldata->at(index.row()).confirmations == 0) { - QBrush b; - b.setColor(Qt::red); + b.setColor(COLOR_UNCONFIRMED_TX); + return b; + } + if (theme_name == "dark" || theme_name == "midnight") { + b.setColor(color); + return b; + }else{ + b.setColor(color); return b; } - - // Else, just return the default brush - QBrush b; - b.setColor(Qt::black); return b; } @@ -161,6 +190,9 @@ void TxTableModel::updateAllData() { } if (role == Qt::DecorationRole && index.column() == 0) { + + //qDebug() << "TX Type = " + dat.type; + if (!dat.memo.isEmpty()) { // If the memo is a Payment URI, then show a payment request icon if (dat.memo.startsWith("hush:")) { @@ -172,7 +204,43 @@ void TxTableModel::updateAllData() { return QVariant(icon.pixmap(16, 16)); } } else { - // Empty pixmap to make it align + // TODO: Add appropriate icons for types of txs instead of empty pixmap + //qDebug() << "Type = " +getType(index.row()) + "Address = " +getAddr(index.row()) + "From Address = " +getFromAddr(index.row()); + + // Send + if(this->getType(index.row()) == "send"){ + QImage image = colorizeIcon(QIcon(":/icons/res/tx_output.png"), color); + QIcon icon; + icon.addPixmap(QPixmap::fromImage(image)); + return QVariant(icon.pixmap(16, 16)); + + } + + // Send T->Z - Untested + if(this->getType(index.row()) == "send" && !this->getFromAddr(index.row()).startsWith("zs1")){ + QImage image = colorizeIcon(QIcon(":/icons/res/lock_closed.png"), color); + QIcon icon; + icon.addPixmap(QPixmap::fromImage(image)); + return QVariant(icon.pixmap(16, 16)); + } + + // Receive + if(this->getType(index.row()) == "receive"){ + QImage image = colorizeIcon(QIcon(":/icons/res/tx_input.png"), color); + QIcon icon; + icon.addPixmap(QPixmap::fromImage(image)); + return QVariant(icon.pixmap(16, 16)); + } + + // Mined + if(this->getType(index.row()) == "generate"){ + QImage image = colorizeIcon(QIcon(":/icons/res/tx_mined.png"), color); + QIcon icon; + icon.addPixmap(QPixmap::fromImage(image)); + return QVariant(icon.pixmap(16, 16)); + } + + // Empty pixmap to make it align (old behavior) QPixmap p(16, 16); p.fill(Qt::white); return QVariant(p); @@ -216,6 +284,10 @@ QString TxTableModel::getAddr(int row) const { return modeldata->at(row).address.trimmed(); } +QString TxTableModel::getFromAddr(int row) const { + return modeldata->at(row).fromAddr.trimmed(); +} + qint64 TxTableModel::getDate(int row) const { return modeldata->at(row).datetime; } diff --git a/src/txtablemodel.h b/src/txtablemodel.h index 0ef7262..c7e05a4 100644 --- a/src/txtablemodel.h +++ b/src/txtablemodel.h @@ -10,16 +10,17 @@ struct TransactionItem; class TxTableModel: public QAbstractTableModel { public: - TxTableModel(QObject* parent); + TxTableModel(QObject* parent); ~TxTableModel(); void addTData (const QList& data); void addZSentData(const QList& data); - void addZRecvData(const QList& data); + void addZRecvData(const QList& data); QString getTxId(int row) const; QString getMemo(int row) const; QString getAddr(int row) const; + QString getFromAddr(int row) const; qint64 getDate(int row) const; QString getType(int row) const; qint64 getConfirmations(int row) const; @@ -31,6 +32,7 @@ public: int columnCount(const QModelIndex &parent) const; QVariant data(const QModelIndex &index, int role) const; QVariant headerData(int section, Qt::Orientation orientation, int role) const; + QImage colorizeIcon(const QIcon icon, const QColor color) const; private: void updateAllData(); diff --git a/src/version.h b/src/version.h index 65de366..77c5e0a 100644 --- a/src/version.h +++ b/src/version.h @@ -1 +1 @@ -#define APP_VERSION "1.3.0" +#define APP_VERSION "1.3.1" diff --git a/src/websockets.cpp b/src/websockets.cpp index 743f5c5..8ec377c 100644 --- a/src/websockets.cpp +++ b/src/websockets.cpp @@ -153,6 +153,7 @@ void WormholeClient::connect() { void WormholeClient::retryConnect() { QTimer::singleShot(5 * 1000 * pow(2, retryCount), [=]() { + qDebug() << __func__ << ": retryCount=" << retryCount; if (retryCount < 10) { qDebug() << "Retrying websocket connection, count=" << this->retryCount; this->retryCount++; @@ -210,10 +211,10 @@ void WormholeClient::onConnected() // On connected, we'll also create a timer to ping it every 4 minutes, since the websocket // will timeout after 5 minutes timer = new QTimer(parent); - qDebug() << "Created QTimer"; + qDebug() << __func__ << ": Created QTimer"; QObject::connect(timer, &QTimer::timeout, [=]() { - qDebug() << "Timer timeout!"; try { + qDebug() << __func__ << ": Timer timeout! shuttingDown=" << shuttingDown << " m_webSocket=" << m_webSocket << " isValid=" << m_webSocket->isValid(); if (!shuttingDown && m_webSocket && m_webSocket->isValid()) { auto payload = QJsonDocument(QJsonObject { {"ping", "ping"} }).toJson(); qint64 bytes = m_webSocket->sendTextMessage(payload); @@ -276,6 +277,7 @@ QString AppDataServer::getSecretHex() { } void AppDataServer::saveNewSecret(QString secretHex) { + qDebug() << __func__ << ": saving secretHex to config file"; QSettings().setValue("mobileapp/secret", secretHex); if (secretHex.isEmpty())