Browse Source

Testing Updates

master
fekt 2 years ago
parent
commit
3bcd7184f4
  1. 8
      doc/release-process.md
  2. 15
      doc/relnotes/README.md
  3. 2
      res/css/dark.css
  4. 2
      res/css/light.css
  5. 2
      res/css/midnight.css
  6. BIN
      res/lock_closed.png
  7. BIN
      res/lock_open.png
  8. BIN
      res/remove.png
  9. BIN
      res/send.png
  10. BIN
      res/synced.png
  11. BIN
      res/transaction0.png
  12. BIN
      res/transaction2.png
  13. BIN
      res/transaction_abandoned.png
  14. BIN
      res/transaction_conflicted.png
  15. BIN
      res/tx_inout.png
  16. BIN
      res/tx_input.png
  17. BIN
      res/tx_mined.png
  18. BIN
      res/tx_output.png
  19. BIN
      res/verify.png
  20. BIN
      res/warning.png
  21. 3
      silentdragon.pro
  22. 23
      src/balancestablemodel.cpp
  23. 7
      src/bannedpeerstablemodel.cpp
  24. 1
      src/bannedpeerstablemodel.h
  25. 255
      src/connection.cpp
  26. 3
      src/connection.h
  27. 32
      src/connection.ui
  28. 10
      src/guiconstants.h
  29. 510
      src/mainwindow.cpp
  30. 26
      src/mainwindow.h
  31. 248
      src/mainwindow.ui
  32. 10
      src/mobileappconnector.ui
  33. 40
      src/peerstablemodel.cpp
  34. 71
      src/qrcode.ui
  35. 8
      src/qrcodelabel.cpp
  36. 91
      src/rescandialog.ui
  37. 124
      src/rpc.cpp
  38. 16
      src/rpc.h
  39. 84
      src/scripts/make-deb.sh
  40. 2
      src/scripts/mkmacdmg.sh
  41. 46
      src/scripts/mkrelease.sh
  42. 81
      src/scripts/silentdragon.wxs
  43. 72
      src/sendtab.cpp
  44. 3
      src/senttxstore.cpp
  45. 31
      src/settings.cpp
  46. 13
      src/settings.h
  47. 89
      src/settings.ui
  48. 106
      src/txtablemodel.cpp
  49. 6
      src/txtablemodel.h
  50. 2
      src/version.h
  51. 6
      src/websockets.cpp

8
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.

15
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 also because they censor many people around the world and work with
evil organizations. 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" # SilentDragon 1.3.0 "Berserk Bonnacon"
``` ```

2
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; background-color: #303335;
color: #ffffff; color: #ffffff;

2
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; background-color: #dadada;
color: #000000; color: #000000;

2
res/css/midnight.css

@ -9,7 +9,7 @@ Website: https://www.csharpe.me
License: https://opensource.org/licenses/MIT 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; background-color: #111;
color: #fff; color: #fff;

BIN
res/lock_closed.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

BIN
res/lock_open.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

BIN
res/remove.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

BIN
res/send.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

BIN
res/synced.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

BIN
res/transaction0.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

BIN
res/transaction2.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

BIN
res/transaction_abandoned.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

BIN
res/transaction_conflicted.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

BIN
res/tx_inout.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

BIN
res/tx_input.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

BIN
res/tx_mined.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

BIN
res/tx_output.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

BIN
res/verify.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

BIN
res/warning.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

3
silentdragon.pro

@ -66,6 +66,7 @@ SOURCES += \
src/viewalladdresses.cpp src/viewalladdresses.cpp
HEADERS += \ HEADERS += \
src/guiconstants.h \
src/mainwindow.h \ src/mainwindow.h \
src/precompiled.h \ src/precompiled.h \
src/rpc.h \ src/rpc.h \
@ -94,6 +95,8 @@ HEADERS += \
FORMS += \ FORMS += \
src/mainwindow.ui \ src/mainwindow.ui \
src/qrcode.ui \
src/rescandialog.ui \
src/settings.ui \ src/settings.ui \
src/about.ui \ src/about.ui \
src/confirm.ui \ src/confirm.ui \

23
src/balancestablemodel.cpp

@ -3,6 +3,7 @@
#include "balancestablemodel.h" #include "balancestablemodel.h"
#include "addressbook.h" #include "addressbook.h"
#include "settings.h" #include "settings.h"
#include "guiconstants.h"
BalancesTableModel::BalancesTableModel(QObject *parent) BalancesTableModel::BalancesTableModel(QObject *parent)
@ -60,6 +61,10 @@ int BalancesTableModel::columnCount(const QModelIndex&) const
QVariant BalancesTableModel::data(const QModelIndex &index, int role) const QVariant BalancesTableModel::data(const QModelIndex &index, int role) const
{ {
// Get current theme name
QString theme_name = Settings::getInstance()->get_theme_name();
QBrush b;
if (loading) { if (loading) {
if (role == Qt::DisplayRole) if (role == Qt::DisplayRole)
return "Loading..."; 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::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 // 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())); const auto& addr = std::get<0>(modeldata->at(index.row()));
for (auto utxo : *utxos) { for (auto utxo : *utxos) {
if (utxo.address == addr && utxo.confirmations == 0) { if (utxo.address == addr && utxo.confirmations == 0) {
QBrush b; b.setColor(COLOR_UNCONFIRMED_TX);
b.setColor(Qt::red);
return b; return b;
} }
} }
if (theme_name == "dark" || theme_name == "midnight") {
// Else, just return the default brush b.setColor(COLOR_WHITE);
QBrush b; return b;
b.setColor(Qt::black); }else{
return b; b.setColor(COLOR_BLACK);
return b;
}
return b;
} }
if (role == Qt::DisplayRole) { if (role == Qt::DisplayRole) {

7
src/bannedpeerstablemodel.cpp

@ -1,5 +1,6 @@
// Copyright 2019-2021 The Hush developers // Copyright 2019-2021 The Hush developers
// Released under the GPLv3 // Released under the GPLv3
#include "bannedpeerstablemodel.h"
#include "settings.h" #include "settings.h"
#include "rpc.h" #include "rpc.h"
@ -59,6 +60,7 @@ int BannedPeersTableModel::columnCount(const QModelIndex&) const
case 0: return dat.address; case 0: return dat.address;
case 1: return dat.subnet; case 1: return dat.subnet;
case 2: return QDateTime::fromSecsSinceEpoch(dat.banned_until).toLocalTime().toString(); 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 0: return "Network Address";
case 1: return "Subnet Mask"; case 1: return "Subnet Mask";
case 2: return "Banned Until " + QDateTime::fromSecsSinceEpoch(dat.banned_until).toUTC().toString(); 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(); return modeldata->at(row).address.trimmed();
} }
qint64 BannedPeersTableModel::getASN(int row) const {
return modeldata->at(row).asn;
}
QString BannedPeersTableModel::getSubnet(int row) const { QString BannedPeersTableModel::getSubnet(int row) const {
return modeldata->at(row).subnet; return modeldata->at(row).subnet;
} }

1
src/bannedpeerstablemodel.h

@ -15,6 +15,7 @@ public:
QString getSubnet(int row) const; QString getSubnet(int row) const;
QString getAddress(int row) const; QString getAddress(int row) const;
qint64 getASN(int row) const;
qint64 getBannedUntil(int row) const; qint64 getBannedUntil(int row) const;
int rowCount(const QModelIndex &parent) const; int rowCount(const QModelIndex &parent) const;

255
src/connection.cpp

@ -15,24 +15,59 @@ ConnectionLoader::ConnectionLoader(MainWindow* main, RPC* rpc) {
d = new QDialog(main); d = new QDialog(main);
d->setWindowFlags(d->windowFlags() & ~(Qt::WindowCloseButtonHint | Qt::WindowContextHelpButtonHint)); d->setWindowFlags(d->windowFlags() & ~(Qt::WindowCloseButtonHint | Qt::WindowContextHelpButtonHint));
connD = new Ui_ConnectionDialog(); connD = new Ui_ConnectionDialog();
connD->setupUi(d); connD->setupUi(d);
QMovie *movie1 = new QMovie(":/img/res/silentdragon-animated-startup.gif");; QMovie *movie1 = new QMovie(":/img/res/silentdragon-animated-startup-dark.gif");;
QMovie *movie2 = new QMovie(":/img/res/silentdragon-animated-startup-dark.gif");;
auto theme = Settings::getInstance()->get_theme_name(); auto theme = Settings::getInstance()->get_theme_name();
auto size = QSize(512,512); auto size = QSize(512,512);
if (theme == "dark" || theme == "midnight") { movie1->setScaledSize(size);
movie2->setScaledSize(size); connD->topIcon->setMovie(movie1);
connD->topIcon->setMovie(movie2); movie1->start();
movie2->start();
} else {
movie1->setScaledSize(size);
connD->topIcon->setMovie(movie1);
movie1->start();
}
main->logger->write("set animation"); main->logger->write("set animation");
qDebug() << "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() { ConnectionLoader::~ConnectionLoader() {
@ -74,12 +109,12 @@ void ConnectionLoader::doAutoConnect(bool tryEhushdStart) {
} else { } else {
if (config->hushDaemon) { if (config->hushDaemon) {
// hushd is configured to run as a daemon, so we must wait for a few seconds // 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"); 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")); 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); }); QTimer::singleShot(5000, [=]() { doAutoConnect(/* don't attempt to start ehushd */ false); });
} else { } else {
// Something is wrong. // Something is wrong.
// We're going to attempt to connect to the one in the background one last time // 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 // and see if that works, else throw an error
main->logger->write("Unknown problem while trying to start hushd!"); main->logger->write("Unknown problem while trying to start hushd!");
@ -87,7 +122,7 @@ void ConnectionLoader::doAutoConnect(bool tryEhushdStart) {
} }
} }
} else { } 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"); main->logger->write("Couldn't start embedded hushd for unknown reason");
QString explanation; QString explanation;
if (config->hushDaemon) { 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" "Please remove the following line from your HUSH3.conf and restart SilentDragon\n"
"daemon=1"); "daemon=1");
} else { } else {
explanation = QString() % QObject::tr("Couldn't start the embedded hushd.\n\n" 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" "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.") % "If all else fails, please run hushd manually.") %
(ehushd ? QObject::tr("The process returned") + ":\n\n" % ehushd->errorString() : QString("")); (ehushd ? QObject::tr("The process returned") + ":\n\n" % ehushd->errorString() : QString(""));
} }
this->showError(explanation); this->showError(explanation);
} }
} else { } else {
// HUSH3.conf exists, there's no connection, and the user asked us not to start hushd. Error! // 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"); 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"); "Not starting embedded hushd because --no-embedded was passed");
this->showError(explanation); this->showError(explanation);
} }
@ -120,7 +155,7 @@ void ConnectionLoader::doAutoConnect(bool tryEhushdStart) {
// Fall back to manual connect // Fall back to manual connect
doManualConnect(); doManualConnect();
} }
} }
} }
QString randomPassword() { QString randomPassword() {
@ -142,7 +177,7 @@ QString randomPassword() {
/** /**
* This will create a new HUSH3.conf and download params if they cannot be found * This will create a new HUSH3.conf and download params if they cannot be found
*/ */
void ConnectionLoader::createHushConf() { void ConnectionLoader::createHushConf() {
main->logger->write(__func__); main->logger->write(__func__);
@ -266,7 +301,7 @@ void ConnectionLoader::doNextDownload(std::function<void(void)> cb) {
} }
// The downloaded file is written to a new name, and then renamed when the operation completes. // 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)) { if (!currentOutput->open(QIODevice::WriteOnly)) {
main->logger->write("Couldn't open " + currentOutput->fileName() + " for writing"); main->logger->write("Couldn't open " + currentOutput->fileName() + " for writing");
@ -274,12 +309,12 @@ void ConnectionLoader::doNextDownload(std::function<void(void)> cb) {
} }
main->logger->write("Downloading to " + filename); main->logger->write("Downloading to " + filename);
qDebug() << "Downloading " << url << " to " << filename; qDebug() << "Downloading " << url << " to " << filename;
QNetworkRequest request(url); QNetworkRequest request(url);
request.setAttribute(QNetworkRequest::FollowRedirectsAttribute, true); request.setAttribute(QNetworkRequest::FollowRedirectsAttribute, true);
currentDownload = client->get(request); currentDownload = client->get(request);
downloadTime.start(); downloadTime.start();
// Download Progress // Download Progress
QObject::connect(currentDownload, &QNetworkReply::downloadProgress, [=] (auto done, auto total) { QObject::connect(currentDownload, &QNetworkReply::downloadProgress, [=] (auto done, auto total) {
// calculate the download speed // calculate the download speed
@ -299,7 +334,7 @@ void ConnectionLoader::doNextDownload(std::function<void(void)> cb) {
QObject::tr("Downloading ") % filename % (filesRemaining > 1 ? " ( +" % QString::number(filesRemaining) % QObject::tr(" more remaining )") : QString("")), 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); 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 // Download Finished
QObject::connect(currentDownload, &QNetworkReply::finished, [=] () { QObject::connect(currentDownload, &QNetworkReply::finished, [=] () {
// Rename file // Rename file
@ -312,22 +347,22 @@ void ConnectionLoader::doNextDownload(std::function<void(void)> cb) {
if (currentDownload->error()) { if (currentDownload->error()) {
main->logger->write("Downloading " + filename + " failed"); 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 { } else {
doNextDownload(cb); doNextDownload(cb);
} }
}); });
// Download new data available. // Download new data available.
QObject::connect(currentDownload, &QNetworkReply::readyRead, [=] () { QObject::connect(currentDownload, &QNetworkReply::readyRead, [=] () {
currentOutput->write(currentDownload->readAll()); currentOutput->write(currentDownload->readAll());
}); });
} }
bool ConnectionLoader::startEmbeddedHushd() { bool ConnectionLoader::startEmbeddedHushd() {
if (!Settings::getInstance()->useEmbedded()) if (!Settings::getInstance()->useEmbedded())
return false; return false;
main->logger->write("Trying to start embedded hushd"); main->logger->write("Trying to start embedded hushd");
// Static because it needs to survive even after this method returns. // Static because it needs to survive even after this method returns.
@ -336,13 +371,13 @@ bool ConnectionLoader::startEmbeddedHushd() {
if (ehushd != nullptr) { if (ehushd != nullptr) {
if (ehushd->state() == QProcess::NotRunning) { if (ehushd->state() == QProcess::NotRunning) {
if (!processStdErrOutput.isEmpty()) { 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); QMessageBox::Ok);
} }
return false; return false;
} else { } else {
return true; return true;
} }
} }
QDir appPath(QCoreApplication::applicationDirPath()); QDir appPath(QCoreApplication::applicationDirPath());
@ -352,7 +387,7 @@ bool ConnectionLoader::startEmbeddedHushd() {
#else #else
auto hushdProgram = appPath.absoluteFilePath("hushd"); auto hushdProgram = appPath.absoluteFilePath("hushd");
#endif #endif
//if (!QFile(hushdProgram).exists()) { //if (!QFile(hushdProgram).exists()) {
if (!QFile::exists(hushdProgram)) { if (!QFile::exists(hushdProgram)) {
qDebug() << "Can't find hushd at " << hushdProgram; qDebug() << "Can't find hushd at " << hushdProgram;
@ -441,7 +476,7 @@ void ConnectionLoader::doManualConnect() {
auto connection = makeConnection(config); auto connection = makeConnection(config);
refreshHushdState(connection, [=] () { refreshHushdState(connection, [=] () {
QString explanation = QString() 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."); "Please set the host/port and user/password in the Edit->Settings menu.");
showError(explanation); showError(explanation);
@ -454,7 +489,7 @@ void ConnectionLoader::doManualConnect() {
void ConnectionLoader::doRPCSetConnection(Connection* conn) { void ConnectionLoader::doRPCSetConnection(Connection* conn) {
rpc->setEHushd(ehushd); rpc->setEHushd(ehushd);
rpc->setConnection(conn); rpc->setConnection(conn);
d->accept(); d->accept();
delete this; delete this;
@ -462,7 +497,7 @@ void ConnectionLoader::doRPCSetConnection(Connection* conn) {
Connection* ConnectionLoader::makeConnection(std::shared_ptr<ConnectionConfig> config) { Connection* ConnectionLoader::makeConnection(std::shared_ptr<ConnectionConfig> config) {
QNetworkAccessManager* client = new QNetworkAccessManager(main); QNetworkAccessManager* client = new QNetworkAccessManager(main);
QUrl myurl; QUrl myurl;
myurl.setScheme("http"); myurl.setScheme("http");
myurl.setHost(config.get()->host); myurl.setHost(config.get()->host);
@ -471,10 +506,10 @@ Connection* ConnectionLoader::makeConnection(std::shared_ptr<ConnectionConfig> c
QNetworkRequest* request = new QNetworkRequest(); QNetworkRequest* request = new QNetworkRequest();
request->setUrl(myurl); request->setUrl(myurl);
request->setHeader(QNetworkRequest::ContentTypeHeader, "text/plain"); request->setHeader(QNetworkRequest::ContentTypeHeader, "text/plain");
QString userpass = config.get()->rpcuser % ":" % config.get()->rpcpassword; QString userpass = config.get()->rpcuser % ":" % config.get()->rpcpassword;
QString headerData = "Basic " + userpass.toLocal8Bit().toBase64(); QString headerData = "Basic " + userpass.toLocal8Bit().toBase64();
request->setRawHeader("Authorization", headerData.toLocal8Bit()); request->setRawHeader("Authorization", headerData.toLocal8Bit());
return new Connection(main, client, request, config); return new Connection(main, client, request, config);
} }
@ -484,7 +519,7 @@ void ConnectionLoader::refreshHushdState(Connection* connection, std::function<v
QJsonObject payload = { QJsonObject payload = {
{"jsonrpc", "1.0"}, {"jsonrpc", "1.0"},
{"id", "someid"}, {"id", "42"},
{"method", "getinfo"} {"method", "getinfo"}
}; };
connection->doRPC(payload, connection->doRPC(payload,
@ -495,20 +530,20 @@ void ConnectionLoader::refreshHushdState(Connection* connection, std::function<v
QTimer::singleShot(1000, [=]() { this->doRPCSetConnection(connection); }); QTimer::singleShot(1000, [=]() { this->doRPCSetConnection(connection); });
}, },
[=] (QNetworkReply* reply, const QJsonValue &res) { [=] (QNetworkReply* reply, const QJsonValue &res) {
// Failed, see what it is. // Failed, see what it is.
auto err = reply->error(); auto err = reply->error();
//qDebug() << err << res; //qDebug() << err << res;
if (err == QNetworkReply::NetworkError::ConnectionRefusedError) { if (err == QNetworkReply::NetworkError::ConnectionRefusedError) {
refused(); refused();
} else if (err == QNetworkReply::NetworkError::AuthenticationRequiredError) { } else if (err == QNetworkReply::NetworkError::AuthenticationRequiredError) {
main->logger->write("Authentication failed"); main->logger->write("Authentication failed");
QString explanation = QString() % QString explanation = QString() %
QObject::tr("Authentication failed. The username / password you specified was " QObject::tr("Authentication failed. The username / password you specified was "
"not accepted by hushd. Try changing it in the Edit->Settings menu"); "not accepted by hushd. Try changing it in the Edit->Settings menu");
this->showError(explanation); this->showError(explanation);
} else if (err == QNetworkReply::NetworkError::InternalServerError && } else if (err == QNetworkReply::NetworkError::InternalServerError &&
!res.isNull()) { !res.isNull()) {
// The server is loading, so just poll until it succeeds // The server is loading, so just poll until it succeeds
QString status = res["error"].toObject()["message"].toString(); QString status = res["error"].toObject()["message"].toString();
@ -532,9 +567,10 @@ void ConnectionLoader::refreshHushdState(Connection* connection, std::function<v
void ConnectionLoader::showInformation(QString info, QString detail) { void ConnectionLoader::showInformation(QString info, QString detail) {
static int rescanCount = 0; static int rescanCount = 0;
if (detail.toLower().startsWith("rescan")) { if (detail.toLower().startsWith("rescan")) {
qDebug() << "showInformation detail = " +detail.toLower();
rescanCount++; rescanCount++;
} }
if (rescanCount > 10) { if (rescanCount > 10) {
detail = detail + "\n" + QObject::tr("This may take several hours, grab some popcorn"); detail = detail + "\n" + QObject::tr("This may take several hours, grab some popcorn");
} }
@ -547,9 +583,9 @@ void ConnectionLoader::showInformation(QString info, QString detail) {
} }
/** /**
* Show error will close the loading dialog and show an error. * Show error will close the loading dialog and show an error.
*/ */
void ConnectionLoader::showError(QString explanation) { void ConnectionLoader::showError(QString explanation) {
rpc->setEHushd(nullptr); rpc->setEHushd(nullptr);
rpc->noConnection(); 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 * Try to automatically detect a HUSH3/HUSH3.conf file in the correct location and load parameters
*/ */
std::shared_ptr<ConnectionConfig> ConnectionLoader::autoDetectHushConf() { std::shared_ptr<ConnectionConfig> ConnectionLoader::autoDetectHushConf() {
auto confLocation = locateHushConfFile(); auto confLocation = locateHushConfFile();
if (confLocation.isNull()) { if (confLocation.isNull()) {
@ -688,7 +724,7 @@ std::shared_ptr<ConnectionConfig> ConnectionLoader::autoDetectHushConf() {
hushconf->usingHushConf = true; hushconf->usingHushConf = true;
hushconf->hushDir = QFileInfo(confLocation).absoluteDir().absolutePath(); hushconf->hushDir = QFileInfo(confLocation).absoluteDir().absolutePath();
hushconf->hushDaemon = false; hushconf->hushDaemon = false;
Settings::getInstance()->setUsingHushConf(confLocation); Settings::getInstance()->setUsingHushConf(confLocation);
while (!in.atEnd()) { while (!in.atEnd()) {
@ -732,21 +768,28 @@ std::shared_ptr<ConnectionConfig> ConnectionLoader::autoDetectHushConf() {
if (hushconf->port.isEmpty()) hushconf->port = "18031"; if (hushconf->port.isEmpty()) hushconf->port = "18031";
file.close(); 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<ConnectionConfig>(hushconf); return std::shared_ptr<ConnectionConfig>(hushconf);
} }
// Load connection settings from the UI, which indicates an unknown, external hushd // Load connection settings from the UI, which indicates an unknown, external hushd
std::shared_ptr<ConnectionConfig> ConnectionLoader::loadFromSettings() { std::shared_ptr<ConnectionConfig> ConnectionLoader::loadFromSettings() {
// Load from the QT Settings. // Load from the QT Settings.
QSettings s; QSettings s;
auto host = s.value("connection/host").toString(); auto host = s.value("connection/host").toString();
auto port = s.value("connection/port").toString(); auto port = s.value("connection/port").toString();
auto username = s.value("connection/rpcuser").toString(); auto username = s.value("connection/rpcuser").toString();
auto password = s.value("connection/rpcpassword").toString(); auto password = s.value("connection/rpcpassword").toString();
if (username.isEmpty() || password.isEmpty()) if (username.isEmpty() || password.isEmpty())
return nullptr; return nullptr;
@ -761,8 +804,8 @@ std::shared_ptr<ConnectionConfig> ConnectionLoader::loadFromSettings() {
/*********************************************************************************** /***********************************************************************************
* Connection Class * Connection Class
************************************************************************************/ ************************************************************************************/
Connection::Connection(MainWindow* m, QNetworkAccessManager* c, QNetworkRequest* r, Connection::Connection(MainWindow* m, QNetworkAccessManager* c, QNetworkRequest* r,
std::shared_ptr<ConnectionConfig> conf) { std::shared_ptr<ConnectionConfig> conf) {
this->restclient = c; this->restclient = c;
this->request = r; this->request = r;
@ -795,7 +838,7 @@ void Connection::doRPC(const QJsonValue& payload, const std::function<void(QJson
// Ignoring callback because shutdown in progress // Ignoring callback because shutdown in progress
return; return;
} }
QJsonDocument jd_reply = QJsonDocument::fromJson(reply->readAll()); QJsonDocument jd_reply = QJsonDocument::fromJson(reply->readAll());
QJsonValue parsed; QJsonValue parsed;
@ -807,13 +850,13 @@ void Connection::doRPC(const QJsonValue& payload, const std::function<void(QJson
if (reply->error() != QNetworkReply::NoError) { if (reply->error() != QNetworkReply::NoError) {
ne(reply, parsed); ne(reply, parsed);
return; return;
} }
if (parsed.isNull()) { if (parsed.isNull()) {
ne(reply, "Unknown error"); ne(reply, "Unknown error");
} }
cb(parsed["result"]); cb(parsed["result"]);
}); });
} }
@ -847,9 +890,97 @@ void Connection::showTxError(const QString& error) {
shown = false; 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 * Prevent all future calls from going through
*/ */
void Connection::shutdown() { void Connection::shutdown() {
shutdownInProgress = true; shutdownInProgress = true;
} }

3
src/connection.h

@ -65,6 +65,8 @@ private:
void showError(QString explanation); void showError(QString explanation);
void showInformation(QString info, QString detail = ""); void showInformation(QString info, QString detail = "");
QString rescanProgress();
void doRPCSetConnection(Connection* conn); void doRPCSetConnection(Connection* conn);
std::shared_ptr<QProcess> ehushd; std::shared_ptr<QProcess> ehushd;
@ -81,6 +83,7 @@ private:
QNetworkAccessManager* client = nullptr; QNetworkAccessManager* client = nullptr;
QTime downloadTime; QTime downloadTime;
QTimer* rescanTimer;
}; };
/** /**

32
src/connection.ui

@ -9,10 +9,28 @@
<rect> <rect>
<x>0</x> <x>0</x>
<y>0</y> <y>0</y>
<width>513</width> <width>512</width>
<height>513</height> <height>512</height>
</rect> </rect>
</property> </property>
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>512</width>
<height>512</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>512</width>
<height>512</height>
</size>
</property>
<property name="windowTitle"> <property name="windowTitle">
<string>SilentDragon</string> <string>SilentDragon</string>
</property> </property>
@ -20,6 +38,9 @@
<bool>true</bool> <bool>true</bool>
</property> </property>
<layout class="QVBoxLayout" name="verticalLayout"> <layout class="QVBoxLayout" name="verticalLayout">
<property name="sizeConstraint">
<enum>QLayout::SetFixedSize</enum>
</property>
<property name="leftMargin"> <property name="leftMargin">
<number>0</number> <number>0</number>
</property> </property>
@ -84,13 +105,6 @@
</item> </item>
</layout> </layout>
</widget> </widget>
<customwidgets>
<customwidget>
<class>FilledIconLabel</class>
<extends>QLabel</extends>
<header>fillediconlabel.h</header>
</customwidget>
</customwidgets>
<resources/> <resources/>
<connections/> <connections/>
</ui> </ui>

10
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

510
src/mainwindow.cpp

@ -1,4 +1,4 @@
// Copyright 2019-2021 The Hush Developers // Copyright 2019-2021 The Hush Developers
// Released under the GPLv3 // Released under the GPLv3
#include "mainwindow.h" #include "mainwindow.h"
#include "addressbook.h" #include "addressbook.h"
@ -8,11 +8,13 @@
#include "ui_mobileappconnector.h" #include "ui_mobileappconnector.h"
#include "ui_addressbook.h" #include "ui_addressbook.h"
#include "ui_privkey.h" #include "ui_privkey.h"
#include "ui_qrcode.h"
#include "ui_viewkey.h" #include "ui_viewkey.h"
#include "ui_about.h" #include "ui_about.h"
#include "ui_settings.h" #include "ui_settings.h"
#include "ui_viewalladdresses.h" #include "ui_viewalladdresses.h"
#include "ui_validateaddress.h" #include "ui_validateaddress.h"
#include "ui_rescandialog.h"
#include "rpc.h" #include "rpc.h"
#include "balancestablemodel.h" #include "balancestablemodel.h"
#include "settings.h" #include "settings.h"
@ -33,7 +35,8 @@ MainWindow::MainWindow(QWidget *parent) :
theme_name = Settings::getInstance()->get_theme_name(); theme_name = Settings::getInstance()->get_theme_name();
} catch (...) } catch (...)
{ {
theme_name = "default"; qDebug() << __func__ << ": exception!";
theme_name = "dark";
} }
this->slot_change_theme(theme_name); this->slot_change_theme(theme_name);
@ -112,6 +115,7 @@ MainWindow::MainWindow(QWidget *parent) :
// Initialize to the balances tab // Initialize to the balances tab
ui->tabWidget->setCurrentIndex(0); ui->tabWidget->setCurrentIndex(0);
setupSendTab(); setupSendTab();
setupTransactionsTab(); setupTransactionsTab();
setupReceiveTab(); setupReceiveTab();
@ -185,6 +189,119 @@ void MainWindow::doClose() {
closeEvent(nullptr); 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) { void MainWindow::closeEvent(QCloseEvent* event) {
QSettings s; QSettings s;
@ -265,7 +382,7 @@ void MainWindow::setupSettingsModal() {
// Set up File -> Settings action // Set up File -> Settings action
QObject::connect(ui->actionSettings, &QAction::triggered, [=]() { QObject::connect(ui->actionSettings, &QAction::triggered, [=]() {
QDialog settingsDialog(this); QDialog settingsDialog(this);
Ui_Settings settings; //Ui_Settings settings;
settings.setupUi(&settingsDialog); settings.setupUi(&settingsDialog);
Settings::saveRestore(&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); int theme_index = settings.comboBoxTheme->findText(Settings::getInstance()->get_theme_name(), Qt::MatchExactly);
settings.comboBoxTheme->setCurrentIndex(theme_index); settings.comboBoxTheme->setCurrentIndex(theme_index);
QObject::connect(settings.comboBoxTheme, &QComboBox::currentTextChanged, [=] (QString theme_name) { QObject::connect(settings.comboBoxTheme, &QComboBox::currentTextChanged, [=] (QString theme_name) {
this->slot_change_theme(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 // Set local currency
QString ticker = Settings::getInstance()->get_currency_name(); QString ticker = Settings::getInstance()->get_currency_name();
int currency_index = settings.comboBoxCurrency->findText(ticker, Qt::MatchExactly); int currency_index = settings.comboBoxCurrency->findText(ticker, Qt::MatchExactly);
@ -310,7 +435,8 @@ void MainWindow::setupSettingsModal() {
QObject::connect(settings.comboBoxCurrency, &QComboBox::currentTextChanged, [=] (QString ticker) { QObject::connect(settings.comboBoxCurrency, &QComboBox::currentTextChanged, [=] (QString ticker) {
this->slot_change_currency(ticker); this->slot_change_currency(ticker);
rpc->refresh(true); 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 // Save sent transactions
@ -342,18 +468,24 @@ void MainWindow::setupSettingsModal() {
settings.lblTor->setToolTip(tooltip); 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; 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) { if (rpc->getConnection() != nullptr) {
isUsingConsolidation = !rpc->getConnection()->config->consolidation.isEmpty() == true; isUsingConsolidation = !rpc->getConnection()->config->consolidation.isEmpty() == true;
} }
@ -362,8 +494,7 @@ void MainWindow::setupSettingsModal() {
settings.chkConso->setEnabled(false); settings.chkConso->setEnabled(false);
} }
//Use Deletetx // Use Deletetx
bool isUsingDeletetx = false; bool isUsingDeletetx = false;
if (rpc->getConnection() != nullptr) { if (rpc->getConnection() != nullptr) {
isUsingDeletetx = !rpc->getConnection()->config->deletetx.isEmpty() == true; isUsingDeletetx = !rpc->getConnection()->config->deletetx.isEmpty() == true;
@ -373,8 +504,7 @@ void MainWindow::setupSettingsModal() {
settings.chkDeletetx->setEnabled(false); settings.chkDeletetx->setEnabled(false);
} }
//Use Zindex // Use Zindex
bool isUsingZindex = false; bool isUsingZindex = false;
if (rpc->getConnection() != nullptr) { if (rpc->getConnection() != nullptr) {
isUsingZindex = !rpc->getConnection()->config->zindex.isEmpty() == true; isUsingZindex = !rpc->getConnection()->config->zindex.isEmpty() == true;
@ -392,10 +522,10 @@ void MainWindow::setupSettingsModal() {
auto hushConfLocation = Settings::getInstance()->getHushdConfLocation(); auto hushConfLocation = Settings::getInstance()->getHushdConfLocation();
if (!hushConfLocation.isEmpty()) { if (!hushConfLocation.isEmpty()) {
settings.confMsg->setText("Settings are being read from \n" + hushConfLocation); settings.confMsg->setText("Settings are being read from \n" + hushConfLocation);
settings.hostname->setEnabled(false); settings.hostname->setReadOnly(true);
settings.port->setEnabled(false); settings.port->setReadOnly(true);
settings.rpcuser->setEnabled(false); settings.rpcuser->setReadOnly(true);
settings.rpcpassword->setEnabled(false); settings.rpcpassword->setReadOnly(true);
} else { } else {
settings.confMsg->setText("No local HUSH3.conf found. Please configure connection manually."); settings.confMsg->setText("No local HUSH3.conf found. Please configure connection manually.");
settings.hostname->setEnabled(true); settings.hostname->setEnabled(true);
@ -418,13 +548,94 @@ void MainWindow::setupSettingsModal() {
settings.testnetTxExplorerUrl->setText(explorer.testnetTxExplorerUrl); settings.testnetTxExplorerUrl->setText(explorer.testnetTxExplorerUrl);
settings.testnetAddressExplorerUrl->setText(explorer.testnetAddressExplorerUrl); settings.testnetAddressExplorerUrl->setText(explorer.testnetAddressExplorerUrl);
// Connection tab by default // format systems language
settings.tabWidget->setCurrentIndex(0); 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 // Enable the troubleshooting options only if using embedded hushd
if (!rpc->isEmbedded()) { if (!rpc->isEmbedded()) {
settings.chkRescan->setEnabled(false); //settings.chkRescan->setEnabled(false);
settings.chkRescan->setToolTip(tr("You're using an external hushd. Please restart hushd with -rescan")); //settings.chkRescan->setToolTip(tr("You're using an external hushd. Please restart hushd with -rescan"));
settings.chkReindex->setEnabled(false); settings.chkReindex->setEnabled(false);
settings.chkReindex->setToolTip(tr("You're using an external hushd. Please restart hushd with -reindex")); 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 // Check to see if rescan or reindex have been enabled
bool showRestartInfo = false; bool showRestartInfo = false;
bool showReindexInfo = false; bool showReindexInfo = false;
/*
if (settings.chkRescan->isChecked()) { if (settings.chkRescan->isChecked()) {
Settings::addToHushConf(hushConfLocation, "rescan=1"); Settings::addToHushConf(hushConfLocation, "rescan=1");
showRestartInfo = true; showRestartInfo = true;
} }*/
if (settings.chkReindex->isChecked()) { if (settings.chkReindex->isChecked()) {
Settings::addToHushConf(hushConfLocation, "reindex=1"); Settings::addToHushConf(hushConfLocation, "reindex=1");
@ -580,7 +793,7 @@ void MainWindow::telegram() {
} }
void MainWindow::reportbug() { void MainWindow::reportbug() {
QString url = "https://git.hush.is/hush/SilentDragon/issues/new"; QString url = "https://hush.is/tg_support";
QDesktopServices::openUrl(QUrl(url)); QDesktopServices::openUrl(QUrl(url));
} }
@ -928,6 +1141,20 @@ void MainWindow::getViewKey(QString addr) {
*isDialogAlive = false; *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<bool>(true);
d.exec();
*isDialogAlive = false;
}
void MainWindow::exportKeys(QString addr) { void MainWindow::exportKeys(QString addr) {
bool allKeys = addr.isEmpty() ? true : false; bool allKeys = addr.isEmpty() ? true : false;
@ -1132,6 +1359,11 @@ void MainWindow::setupBalancesTab() {
menu.addAction(tr("Get viewing key"), [=] () { menu.addAction(tr("Get viewing key"), [=] () {
this->getViewKey(addr); 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 ? "..." : ""), [=]() { menu.addAction("Send from " % addr.left(40) % (addr.size() > 40 ? "..." : ""), [=]() {
@ -1206,21 +1438,23 @@ QString peer2ip(QString peer) {
void MainWindow::setupPeersTab() { void MainWindow::setupPeersTab() {
qDebug() << __FUNCTION__; qDebug() << __FUNCTION__;
// Set up context menu on transactions tab // Set up context menu on peers tab
ui->peersTable->setContextMenuPolicy(Qt::CustomContextMenu); ui->peersTable->setContextMenuPolicy(Qt::CustomContextMenu);
ui->bannedPeersTable->setContextMenuPolicy(Qt::CustomContextMenu); ui->bannedPeersTable->setContextMenuPolicy(Qt::CustomContextMenu);
// Table right click // Table right click
QObject::connect(ui->bannedPeersTable, &QTableView::customContextMenuRequested, [=] (QPoint pos) { 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; if (index.row() < 0) return;
QMenu menu(this); QMenu menu(this);
auto bannedPeerModel = dynamic_cast<BannedPeersTableModel *>(ui->bannedPeersTable->model()); auto bannedPeerModel = dynamic_cast<BannedPeersTableModel *>(ui->bannedPeersTable->model());
QString addr = bannedPeerModel->getAddress(index.row()); QString addr = bannedPeerModel->getAddress(index.row());
qint64 asn = bannedPeerModel->getASN(index.row());
QString ip = peer2ip(addr); QString ip = peer2ip(addr);
QString subnet = bannedPeerModel->getSubnet(index.row()); QString subnet = bannedPeerModel->getSubnet(index.row());
QString as = QString::number(asn);
//qint64 banned_until = bannedPeerModel->getBannedUntil(index.row()); //qint64 banned_until = bannedPeerModel->getBannedUntil(index.row());
if(!ip.isEmpty()) { 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)); 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)); menu.exec(ui->peersTable->viewport()->mapToGlobal(pos));
}); });
@ -1383,10 +1671,52 @@ void MainWindow::setupTransactionsTab() {
QString memo = txModel->getMemo(index.row()); QString memo = txModel->getMemo(index.row());
if (!memo.isEmpty()) { 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.setTextFormat(Qt::PlainText);
mb.setTextInteractionFlags(Qt::TextSelectableByMouse | Qt::TextSelectableByKeyboard); mb.setTextInteractionFlags(Qt::TextSelectableByMouse | Qt::TextSelectableByKeyboard);
mb.exec(); 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); 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 // Payment Request
if (!memo.isEmpty() && memo.startsWith("hush:")) { if (!memo.isEmpty() && memo.startsWith("hush:")) {
menu.addAction(tr("View Payment Request"), [=] () { menu.addAction(tr("View Payment Request"), [=] () {
@ -1450,10 +1788,57 @@ void MainWindow::setupTransactionsTab() {
// View Memo // View Memo
if (!memo.isEmpty()) { if (!memo.isEmpty()) {
menu.addAction(tr("View Memo"), [=] () { menu.addAction(tr("View Memo"), [=] () {
/*
QMessageBox mb(QMessageBox::Information, tr("Memo"), memo, QMessageBox::Ok, this); 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.setTextFormat(Qt::PlainText);
mb.setTextInteractionFlags(Qt::TextSelectableByMouse | Qt::TextSelectableByKeyboard); mb.setTextInteractionFlags(Qt::TextSelectableByMouse | Qt::TextSelectableByKeyboard);
mb.exec(); 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->rcvBal->setText(Settings::getHUSHUSDDisplayFormat(rpc->getAllBalances()->value(addr)));
ui->txtReceive->setPlainText(addr); ui->txtReceive->setPlainText(addr);
ui->qrcodeDisplay->setQrcodeString(addr); ui->qrcodeDisplay->setQrcodeString(addr);
if (rpc->getUsedAddresses()->value(addr, false)) { if (rpc->getUsedAddresses()->value(addr, false)) {
ui->rcvBal->setToolTip(tr("Address has been previously used")); ui->rcvBal->setToolTip(tr("Address has been previously used"));
} else { } else {
@ -1747,7 +2133,7 @@ void MainWindow::updateLabels() {
void MainWindow::slot_change_currency(const QString& currency_name) 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); Settings::getInstance()->set_currency_name(currency_name);
qDebug() << "Refreshing price stats after currency change"; qDebug() << "Refreshing price stats after currency change";
rpc->refreshPrice(); 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 // Include css
QString saved_theme_name; 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(); saved_theme_name = Settings::getInstance()->get_theme_name();
} catch (const std::exception& e) { } catch (const std::exception& e) {
qDebug() << QString("Ignoring theme change Exception! : "); 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)) if (qFile.open(QFile::ReadOnly))
{ {
QString styleSheet = QLatin1String(qFile.readAll()); 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() MainWindow::~MainWindow()
{ {
delete ui; delete ui;

26
src/mainwindow.h

@ -6,6 +6,7 @@
#include "precompiled.h" #include "precompiled.h"
#include "logger.h" #include "logger.h"
#include <memory> #include <memory>
#include "ui_settings.h"
// Forward declare to break circular dependency. // Forward declare to break circular dependency.
class RPC; class RPC;
@ -64,6 +65,7 @@ public:
void updateFromCombo(); void updateFromCombo();
Ui::MainWindow* ui; Ui::MainWindow* ui;
Ui_Settings settings;
QLabel* statusLabel; QLabel* statusLabel;
QLabel* statusIcon; QLabel* statusIcon;
@ -72,10 +74,21 @@ public:
Logger* logger; Logger* logger;
void doClose(); 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: private:
void closeEvent(QCloseEvent* event); void closeEvent(QCloseEvent* event);
void checkRescan();
void setupSendTab(); void setupSendTab();
void setupPeersTab(); void setupPeersTab();
void setupTransactionsTab(); void setupTransactionsTab();
@ -85,8 +98,9 @@ private:
void setupChatTab(); void setupChatTab();
void setupMarketTab(); void setupMarketTab();
void slot_change_theme(const QString& themeName); void slot_change_theme(QString& themeName);
void slot_change_currency(const QString& currencyName); void slot_change_currency(const QString& currencyName);
void setupTurnstileDialog(); void setupTurnstileDialog();
void setupSettingsModal(); void setupSettingsModal();
void setupStatusBar(); void setupStatusBar();
@ -113,6 +127,8 @@ private:
void memoButtonClicked(int number, bool includeReplyTo = false); void memoButtonClicked(int number, bool includeReplyTo = false);
void fileUploadButtonClicked(int number); void fileUploadButtonClicked(int number);
void setMemoEnabled(int number, bool enabled); void setMemoEnabled(int number, bool enabled);
void rescanButtonClicked(int number);
void donate(); void donate();
void website(); void website();
@ -124,6 +140,7 @@ private:
void exportAllKeys(); void exportAllKeys();
void exportKeys(QString addr = ""); void exportKeys(QString addr = "");
void getViewKey(QString addr = ""); void getViewKey(QString addr = "");
void getQRCode(QString addr = "");
void backupWalletDat(); void backupWalletDat();
void exportTransactions(); void exportTransactions();
@ -144,6 +161,13 @@ private:
QRegExpValidator* feesValidator = nullptr; QRegExpValidator* feesValidator = nullptr;
QMovie* loadingMovie; 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 #endif // MAINWINDOW_H

248
src/mainwindow.ui

@ -22,7 +22,7 @@
<item row="0" column="0"> <item row="0" column="0">
<widget class="QTabWidget" name="tabWidget"> <widget class="QTabWidget" name="tabWidget">
<property name="currentIndex"> <property name="currentIndex">
<number>5</number> <number>0</number>
</property> </property>
<widget class="QWidget" name="tab"> <widget class="QWidget" name="tab">
<attribute name="title"> <attribute name="title">
@ -222,7 +222,7 @@
</widget> </widget>
</item> </item>
<item> <item>
<widget class="QGroupBox" name="groupBox_2"> <widget class="QGroupBox" name="addressBalances">
<property name="sizePolicy"> <property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Preferred"> <sizepolicy hsizetype="Expanding" vsizetype="Preferred">
<horstretch>0</horstretch> <horstretch>0</horstretch>
@ -250,6 +250,9 @@
</property> </property>
<item> <item>
<widget class="QTableView" name="balancesTable"> <widget class="QTableView" name="balancesTable">
<property name="styleSheet">
<string notr="true"/>
</property>
<property name="selectionMode"> <property name="selectionMode">
<enum>QAbstractItemView::SingleSelection</enum> <enum>QAbstractItemView::SingleSelection</enum>
</property> </property>
@ -385,8 +388,8 @@
<rect> <rect>
<x>0</x> <x>0</x>
<y>0</y> <y>0</y>
<width>1403</width> <width>1447</width>
<height>619</height> <height>860</height>
</rect> </rect>
</property> </property>
<layout class="QVBoxLayout" name="sendToLayout"> <layout class="QVBoxLayout" name="sendToLayout">
@ -395,33 +398,65 @@
<property name="title"> <property name="title">
<string>Recipient</string> <string>Recipient</string>
</property> </property>
<layout class="QVBoxLayout" name="sendAddressLayout"> <layout class="QGridLayout" name="sendAddressLayout">
<item> <item row="2" column="0">
<layout class="QHBoxLayout" name="horizontalLayout_12"> <layout class="QHBoxLayout" name="horizontalLayout_20" stretch="0,0">
<item> <property name="spacing">
<widget class="QLabel" name="label_4"> <number>0</number>
</property>
<property name="sizeConstraint">
<enum>QLayout::SetDefaultConstraint</enum>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item alignment="Qt::AlignTop">
<widget class="QLabel" name="MemoTxt1">
<property name="sizePolicy">
<sizepolicy hsizetype="MinimumExpanding" vsizetype="MinimumExpanding">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text"> <property name="text">
<string>Address</string> <string/>
</property> </property>
</widget> <property name="alignment">
</item> <set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop</set>
<item> </property>
<widget class="QLineEdit" name="Address1"> <property name="wordWrap">
<property name="placeholderText"> <bool>true</bool>
<string>Address</string>
</property> </property>
</widget> </widget>
</item> </item>
<item> <item alignment="Qt::AlignTop">
<widget class="QPushButton" name="AddressBook1"> <widget class="QPushButton" name="MemoBtn1">
<property name="enabled">
<bool>true</bool>
</property>
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>0</width>
<height>0</height>
</size>
</property>
<property name="toolTip">
<string/>
</property>
<property name="text"> <property name="text">
<string>Address Book</string> <string>Memo</string>
</property> </property>
</widget> </widget>
</item> </item>
</layout> </layout>
</item> </item>
<item> <item row="1" column="0">
<layout class="QHBoxLayout" name="horizontalLayout_13"> <layout class="QHBoxLayout" name="horizontalLayout_13">
<item> <item>
<widget class="QLabel" name="label_6"> <widget class="QLabel" name="label_6">
@ -432,6 +467,18 @@
</item> </item>
<item> <item>
<widget class="QLineEdit" name="Amount1"> <widget class="QLineEdit" name="Amount1">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>200</width>
<height>0</height>
</size>
</property>
<property name="baseSize"> <property name="baseSize">
<size> <size>
<width>200</width> <width>200</width>
@ -458,6 +505,9 @@
<property name="text"> <property name="text">
<string>Max Available</string> <string>Max Available</string>
</property> </property>
<property name="checkable">
<bool>true</bool>
</property>
</widget> </widget>
</item> </item>
<item> <item>
@ -465,6 +515,9 @@
<property name="orientation"> <property name="orientation">
<enum>Qt::Horizontal</enum> <enum>Qt::Horizontal</enum>
</property> </property>
<property name="sizeType">
<enum>QSizePolicy::MinimumExpanding</enum>
</property>
<property name="sizeHint" stdset="0"> <property name="sizeHint" stdset="0">
<size> <size>
<width>40</width> <width>40</width>
@ -486,36 +539,33 @@
</property> </property>
</widget> </widget>
</item> </item>
</layout>
</item>
<item row="0" column="0">
<layout class="QHBoxLayout" name="horizontalLayout_12">
<item> <item>
<widget class="QPushButton" name="MemoBtn1"> <widget class="QLabel" name="label_4">
<property name="enabled"> <property name="text">
<bool>true</bool> <string>Address</string>
</property> </property>
<property name="toolTip"> </widget>
<string/> </item>
<item>
<widget class="QLineEdit" name="Address1">
<property name="placeholderText">
<string>Address</string>
</property> </property>
</widget>
</item>
<item>
<widget class="QPushButton" name="AddressBook1">
<property name="text"> <property name="text">
<string>Memo</string> <string>Address Book</string>
</property> </property>
</widget> </widget>
</item> </item>
</layout> </layout>
</item> </item>
<item>
<widget class="QLabel" name="MemoTxt1">
<property name="font">
<font>
<pointsize>10</pointsize>
</font>
</property>
<property name="text">
<string/>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
</widget>
</item>
</layout> </layout>
</widget> </widget>
</item> </item>
@ -893,16 +943,37 @@
</item> </item>
</layout> </layout>
</item> </item>
<item> <item alignment="Qt::AlignTop">
<widget class="QRCodeLabel" name="qrcodeDisplay"> <widget class="QRCodeLabel" name="qrcodeDisplay">
<property name="sizePolicy"> <property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Expanding"> <sizepolicy hsizetype="Fixed" vsizetype="Fixed">
<horstretch>0</horstretch> <horstretch>0</horstretch>
<verstretch>0</verstretch> <verstretch>0</verstretch>
</sizepolicy> </sizepolicy>
</property> </property>
<property name="minimumSize">
<size>
<width>228</width>
<height>228</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>0</width>
<height>0</height>
</size>
</property>
<property name="baseSize">
<size>
<width>0</width>
<height>0</height>
</size>
</property>
<property name="autoFillBackground">
<bool>false</bool>
</property>
<property name="styleSheet"> <property name="styleSheet">
<string notr="true">background-color: #fff</string> <string notr="true"/>
</property> </property>
<property name="text"> <property name="text">
<string/> <string/>
@ -915,7 +986,6 @@
</item> </item>
</layout> </layout>
</widget> </widget>
<widget class="QWidget" name="tab_4"> <widget class="QWidget" name="tab_4">
<attribute name="title"> <attribute name="title">
<string>Transactions</string> <string>Transactions</string>
@ -933,15 +1003,11 @@
</item> </item>
</layout> </layout>
</widget> </widget>
<widget class="QWidget" name="peer_tab"> <widget class="QWidget" name="peer_tab">
<attribute name="title"> <attribute name="title">
<string>Peers</string> <string>Peers</string>
</attribute> </attribute>
<layout class="QVBoxLayout" name="verticalLayout_8"> <layout class="QVBoxLayout" name="verticalLayout_8">
<item> <item>
<widget class="QLabel" name="currentPeersLabel"> <widget class="QLabel" name="currentPeersLabel">
<property name="text"> <property name="text">
@ -951,7 +1017,6 @@
</property> </property>
</widget> </widget>
</item> </item>
<item> <item>
<widget class="QTableView" name="peersTable"> <widget class="QTableView" name="peersTable">
<property name="selectionMode"> <property name="selectionMode">
@ -961,8 +1026,7 @@
<enum>QAbstractItemView::SelectRows</enum> <enum>QAbstractItemView::SelectRows</enum>
</property> </property>
</widget> </widget>
</item> </item>
<item> <item>
<widget class="QLabel" name="bannedPeersLabel"> <widget class="QLabel" name="bannedPeersLabel">
<property name="text"> <property name="text">
@ -972,7 +1036,6 @@
</property> </property>
</widget> </widget>
</item> </item>
<item> <item>
<widget class="QTableView" name="bannedPeersTable"> <widget class="QTableView" name="bannedPeersTable">
<property name="selectionMode"> <property name="selectionMode">
@ -982,24 +1045,9 @@
<enum>QAbstractItemView::SelectRows</enum> <enum>QAbstractItemView::SelectRows</enum>
</property> </property>
</widget> </widget>
</item>
<!--
<widget class="QLabel" name="recentlyDisconnectedPeers">
<property name="text">
<string>
Looking For Recently Disconnected Peers ...
</string>
</property>
</widget>
</item> </item>
-->
</layout> </layout>
</widget> </widget>
<widget class="QWidget" name="tab_6"> <widget class="QWidget" name="tab_6">
<attribute name="title"> <attribute name="title">
<string>Market</string> <string>Market</string>
@ -1082,42 +1130,6 @@
</item> </item>
</layout> </layout>
</widget> </widget>
<!--
<widget class="QWidget" name="chat_tab">
<attribute name="title">
<string>Chat</string>
</attribute>
<layout class="QVBoxLayout" name="verticalLayout_7">
<item>
<layout class="QGridLayout" name="gridLayout_6">
<item row="0" column="0">
<widget class="QListView" name="contactsView"/>
</item>
<item row="1" column="1">
<widget class="QTextEdit" name="textEdit"/>
</item>
<item row="0" column="1">
<widget class="QListView" name="chatView"/>
</item>
<item row="1" column="2">
<widget class="QPushButton" name="pushButton">
<property name="text">
<string>Send</string>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QPushButton" name="pushButton_2">
<property name="text">
<string>New HushChat</string>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
-->
<widget class="QWidget" name="tab_5"> <widget class="QWidget" name="tab_5">
<attribute name="title"> <attribute name="title">
<string>hushd</string> <string>hushd</string>
@ -1148,9 +1160,9 @@
</property> </property>
<layout class="QGridLayout" name="gridLayout_51"> <layout class="QGridLayout" name="gridLayout_51">
<item row="5" column="0" colspan="3"> <item row="5" column="0" colspan="3">
<widget class="QLabel" name="label_14"> <widget class="QLabel" name="mining">
<property name="text"> <property name="text">
<string>You are currently not mining</string> <string/>
</property> </property>
</widget> </widget>
</item> </item>
@ -1244,15 +1256,6 @@
</property> </property>
</widget> </widget>
</item> </item>
<!--
<item row="10" column="0">
<widget class="QLabel" name="kmdversionlabel">
<property name="text">
<string>KMD Version</string>
</property>
</widget>
</item>
-->
<item row="10" column="2"> <item row="10" column="2">
<widget class="QLabel" name="kmdversion"> <widget class="QLabel" name="kmdversion">
<property name="text"> <property name="text">
@ -1456,7 +1459,6 @@
</property> </property>
</widget> </widget>
</item> </item>
<item row="20" column="0"> <item row="20" column="0">
<widget class="QLabel" name="chaintxcountlabel"> <widget class="QLabel" name="chaintxcountlabel">
<property name="text"> <property name="text">
@ -1478,7 +1480,6 @@
</property> </property>
</widget> </widget>
</item> </item>
<item row="21" column="0"> <item row="21" column="0">
<widget class="QLabel" name="tlssupportlabel"> <widget class="QLabel" name="tlssupportlabel">
<property name="text"> <property name="text">
@ -1500,7 +1501,6 @@
</property> </property>
</widget> </widget>
</item> </item>
<item row="3" column="2"> <item row="3" column="2">
<widget class="QLabel" name="solrate"> <widget class="QLabel" name="solrate">
<property name="text"> <property name="text">
@ -1587,7 +1587,7 @@
<x>0</x> <x>0</x>
<y>0</y> <y>0</y>
<width>1487</width> <width>1487</width>
<height>42</height> <height>30</height>
</rect> </rect>
</property> </property>
<widget class="QMenu" name="menuFile"> <widget class="QMenu" name="menuFile">
@ -1742,6 +1742,11 @@
</widget> </widget>
<layoutdefault spacing="6" margin="11"/> <layoutdefault spacing="6" margin="11"/>
<customwidgets> <customwidgets>
<customwidget>
<class>FilledIconLabel</class>
<extends>QLabel</extends>
<header>fillediconlabel.h</header>
</customwidget>
<customwidget> <customwidget>
<class>AddressCombo</class> <class>AddressCombo</class>
<extends>QComboBox</extends> <extends>QComboBox</extends>
@ -1752,11 +1757,6 @@
<extends>QLabel</extends> <extends>QLabel</extends>
<header>qrcodelabel.h</header> <header>qrcodelabel.h</header>
</customwidget> </customwidget>
<customwidget>
<class>FilledIconLabel</class>
<extends>QLabel</extends>
<header>fillediconlabel.h</header>
</customwidget>
</customwidgets> </customwidgets>
<tabstops> <tabstops>
<tabstop>inputsCombo</tabstop> <tabstop>inputsCombo</tabstop>

10
src/mobileappconnector.ui

@ -62,14 +62,20 @@
</property> </property>
</widget> </widget>
</item> </item>
<item row="0" column="0"> <item row="0" column="0" alignment="Qt::AlignHCenter">
<widget class="QRCodeLabel" name="qrcode"> <widget class="QRCodeLabel" name="qrcode">
<property name="sizePolicy"> <property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="MinimumExpanding"> <sizepolicy hsizetype="Fixed" vsizetype="Fixed">
<horstretch>0</horstretch> <horstretch>0</horstretch>
<verstretch>0</verstretch> <verstretch>0</verstretch>
</sizepolicy> </sizepolicy>
</property> </property>
<property name="minimumSize">
<size>
<width>228</width>
<height>228</height>
</size>
</property>
<property name="styleSheet"> <property name="styleSheet">
<string notr="true">background-color: #fff</string> <string notr="true">background-color: #fff</string>
</property> </property>

40
src/peerstablemodel.cpp

@ -1,8 +1,9 @@
// Copyright 2019-2021 The Hush developers // Copyright 2019-2021 The Hush developers
// Released under the GPLv3 // Released under the GPLv3
#include "txtablemodel.h" #include "peerstablemodel.h"
#include "settings.h" #include "settings.h"
#include "rpc.h" #include "rpc.h"
#include "guiconstants.h"
PeersTableModel::PeersTableModel(QObject *parent) PeersTableModel::PeersTableModel(QObject *parent)
: QAbstractTableModel(parent) { : QAbstractTableModel(parent) {
@ -52,23 +53,26 @@ int PeersTableModel::columnCount(const QModelIndex&) const
} }
QVariant PeersTableModel::data(const QModelIndex &index, int role) const QVariant PeersTableModel::data(const QModelIndex &index, int role) const
{ {
// Align column 4 (amount) right // Get current theme name
//if (role == Qt::TextAlignmentRole && index.column() == 3) return QVariant(Qt::AlignRight | Qt::AlignVCenter); QString theme_name = Settings::getInstance()->get_theme_name();
QBrush b;
if (role == Qt::ForegroundRole) { if (role == Qt::ForegroundRole) {
// peers with banscore >=50 will likely be banned soon, color them red // peers with banscore >=50 will likely be banned soon, color them red
if (modeldata->at(index.row()).banscore >= 50) { if (modeldata->at(index.row()).banscore >= 50) {
QBrush b; b.setColor(COLOR_UNCONFIRMED_TX);
b.setColor(Qt::red);
return b; return b;
} }
if (theme_name == "dark" || theme_name == "midnight") {
// Else, just return the default brush b.setColor(COLOR_WHITE);
QBrush b; return b;
b.setColor(Qt::black); }else{
return b; b.setColor(COLOR_BLACK);
return b;
}
return b;
} }
auto dat = modeldata->at(index.row()); auto dat = modeldata->at(index.row());
@ -86,7 +90,7 @@ int PeersTableModel::columnCount(const QModelIndex&) const
case 9: return dat.bytes_received; case 9: return dat.bytes_received;
case 10: return dat.bytes_sent; case 10: return dat.bytes_sent;
} }
} }
if (role == Qt::ToolTipRole) { if (role == Qt::ToolTipRole) {
switch (index.column()) { switch (index.column()) {
@ -101,12 +105,12 @@ int PeersTableModel::columnCount(const QModelIndex&) const
case 8: return "Banscore"; case 8: return "Banscore";
case 9: return "Bytes received"; case 9: return "Bytes received";
case 10: return "Bytes sent"; case 10: return "Bytes sent";
} }
} }
//TODO: show different icons for IP vs Tor vs other kinds of connections //TODO: show different icons for IP vs Tor vs other kinds of connections
/* /*
if (role == Qt::DecorationRole && index.column() == 0) { if (role == Qt::DecorationRole && index.column() == 0) {
if (!dat.memo.isEmpty()) { if (!dat.memo.isEmpty()) {
// If the memo is a Payment URI, then show a payment request icon // 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)); return QVariant(icon.pixmap(16, 16));
} else { } else {
// Return the info pixmap to indicate memo // 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)); return QVariant(icon.pixmap(16, 16));
} }
} else { } else {
@ -128,7 +132,7 @@ int PeersTableModel::columnCount(const QModelIndex&) const
*/ */
return QVariant(); return QVariant();
} }
QVariant PeersTableModel::headerData(int section, Qt::Orientation orientation, int role) const QVariant PeersTableModel::headerData(int section, Qt::Orientation orientation, int role) const

71
src/qrcode.ui

@ -0,0 +1,71 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>QRCode</class>
<widget class="QDialog" name="QRCode">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>320</width>
<height>320</height>
</rect>
</property>
<property name="windowTitle">
<string>QR Code</string>
</property>
<layout class="QFormLayout" name="formLayout">
<property name="sizeConstraint">
<enum>QLayout::SetFixedSize</enum>
</property>
<item row="0" column="0">
<widget class="QRCodeLabel" name="qrcodeDisplayAddr">
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>300</width>
<height>300</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>0</width>
<height>0</height>
</size>
</property>
<property name="baseSize">
<size>
<width>0</width>
<height>0</height>
</size>
</property>
<property name="autoFillBackground">
<bool>false</bool>
</property>
<property name="styleSheet">
<string notr="true"/>
</property>
<property name="text">
<string/>
</property>
<property name="alignment">
<set>Qt::AlignHCenter|Qt::AlignTop</set>
</property>
</widget>
</item>
</layout>
</widget>
<customwidgets>
<customwidget>
<class>QRCodeLabel</class>
<extends>QLabel</extends>
<header>qrcodelabel.h</header>
</customwidget>
</customwidgets>
<resources/>
<connections/>
</ui>

8
src/qrcodelabel.cpp

@ -26,7 +26,7 @@ QPixmap QRCodeLabel::scaledPixmap() const {
pm.fill(Qt::white); pm.fill(Qt::white);
QPainter painter(&pm); 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 int s = qr.getSize()>0?qr.getSize():1;
const double w = pm.width(); const double w = pm.width();
const double h = pm.height(); 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; return pm;
} }

91
src/rescandialog.ui

@ -0,0 +1,91 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>RescanDialog</class>
<widget class="QDialog" name="RescanDialog">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>542</width>
<height>108</height>
</rect>
</property>
<property name="windowTitle">
<string>Rescan</string>
</property>
<layout class="QGridLayout" name="gridLayout">
<item row="3" column="0">
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<spacer name="horizontalSpacer">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
</layout>
</item>
<item row="3" column="1" colspan="2">
<widget class="QDialogButtonBox" name="buttonBox">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="standardButtons">
<set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
</property>
</widget>
</item>
<item row="2" column="0" colspan="3">
<widget class="QLineEdit" name="rescanBlockheight"/>
</item>
<item row="0" column="0" colspan="3">
<widget class="QLabel" name="label">
<property name="text">
<string>Enter block height to rescan from:</string>
</property>
</widget>
</item>
</layout>
</widget>
<resources/>
<connections>
<connection>
<sender>buttonBox</sender>
<signal>accepted()</signal>
<receiver>RescanDialog</receiver>
<slot>accept()</slot>
<hints>
<hint type="sourcelabel">
<x>248</x>
<y>254</y>
</hint>
<hint type="destinationlabel">
<x>157</x>
<y>274</y>
</hint>
</hints>
</connection>
<connection>
<sender>buttonBox</sender>
<signal>rejected()</signal>
<receiver>RescanDialog</receiver>
<slot>reject()</slot>
<hints>
<hint type="sourcelabel">
<x>316</x>
<y>260</y>
</hint>
<hint type="destinationlabel">
<x>286</x>
<y>274</y>
</hint>
</hints>
</connection>
</connections>
</ui>

124
src/rpc.cpp

@ -52,17 +52,22 @@ RPC::RPC(MainWindow* main) {
}); });
timer->start(Settings::updateSpeed); timer->start(Settings::updateSpeed);
// Set up the timer to watch for tx status // Set up the timer to watch for tx status
txTimer = new QTimer(main); txTimer = new QTimer(main);
QObject::connect(txTimer, &QTimer::timeout, [=]() { QObject::connect(txTimer, &QTimer::timeout, [=]() {
//qDebug() << "Watching tx status"; //qDebug() << "Watching tx status";
watchTxStatus(); watchTxStatus();
}); });
txTimer->start(Settings::updateSpeed);
qDebug() << __func__ << "Done settings up all timers"; qDebug() << __func__ << "Done settings up all timers";
usedAddresses = new QMap<QString, bool>(); usedAddresses = new QMap<QString, bool>();
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() { RPC::~RPC() {
@ -110,12 +115,32 @@ void RPC::setConnection(Connection* c) {
refresh(true); 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 = { QJsonObject payload = {
{"jsonrpc", "1.0"}, {"jsonrpc", "1.0"},
{"id", "42" }, {"id", "42" },
{"method", method }, {"method", method },
{"params", QJsonArray {params}} {"params", QJsonArray{param}}
}; };
return payload; return payload;
} }
@ -129,12 +154,47 @@ QJsonValue RPC::makePayload(QString method) {
return payload; return payload;
} }
//TODO: we can use listaddresses
void RPC::getTAddresses(const std::function<void(QJsonValue)>& cb) { void RPC::getTAddresses(const std::function<void(QJsonValue)>& cb) {
QString method = "getaddressesbyaccount"; QString method = "getaddressesbyaccount";
QString params = ""; QString params = "";
conn->doRPCWithDefaultErrorHandling(makePayload(method, ""), cb); conn->doRPCWithDefaultErrorHandling(makePayload(method, ""), cb);
} }
// full or partial rescan
void RPC::rescan(qint64 height, const std::function<void(QJsonValue)>& cb) {
QString method = "rescan";
conn->doRPCWithDefaultErrorHandling(makePayload(method, height), cb);
}
// get rescan info
void RPC::getRescanInfo(const std::function<void(QJsonValue)>& 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<void(QJsonValue)>& cb) {
QString method = "setban";
conn->doRPCWithDefaultErrorHandling(makePayload(method, ip, command), cb);
}
//unban all banned peer nodes
void RPC::clearBanned(const std::function<void(QJsonValue)>& cb) {
QString method = "clearbanned";
conn->doRPCWithDefaultErrorHandling(makePayload(method), cb);
}
void RPC::z_sweepstatus(const std::function<void(QJsonValue)>& cb) {
QString method = "z_sweepstatus";
conn->doRPCWithDefaultErrorHandling(makePayload(method), cb);
}
void RPC::z_consolidationstatus(const std::function<void(QJsonValue)>& cb) {
QString method = "z_consolidationstatus";
conn->doRPCWithDefaultErrorHandling(makePayload(method), cb);
}
void RPC::getZAddresses(const std::function<void(QJsonValue)>& cb) { void RPC::getZAddresses(const std::function<void(QJsonValue)>& cb) {
QString method = "z_listaddresses"; QString method = "z_listaddresses";
conn->doRPCWithDefaultErrorHandling(makePayload(method), cb); conn->doRPCWithDefaultErrorHandling(makePayload(method), cb);
@ -143,7 +203,7 @@ void RPC::getZAddresses(const std::function<void(QJsonValue)>& cb) {
void RPC::getTransparentUnspent(const std::function<void(QJsonValue)>& cb) { void RPC::getTransparentUnspent(const std::function<void(QJsonValue)>& cb) {
QJsonObject payload = { QJsonObject payload = {
{"jsonrpc", "1.0"}, {"jsonrpc", "1.0"},
{"id", "someid"}, {"id", "42"},
{"method", "listunspent"}, {"method", "listunspent"},
{"params", QJsonArray {0}} // Get UTXOs with 0 confirmations as well. {"params", QJsonArray {0}} // Get UTXOs with 0 confirmations as well.
}; };
@ -154,7 +214,7 @@ void RPC::getTransparentUnspent(const std::function<void(QJsonValue)>& cb) {
void RPC::getZUnspent(const std::function<void(QJsonValue)>& cb) { void RPC::getZUnspent(const std::function<void(QJsonValue)>& cb) {
QJsonObject payload = { QJsonObject payload = {
{"jsonrpc", "1.0"}, {"jsonrpc", "1.0"},
{"id", "someid"}, {"id", "42"},
{"method", "z_listunspent"}, {"method", "z_listunspent"},
{"params", QJsonArray {0}} // Get UTXOs with 0 confirmations as well. {"params", QJsonArray {0}} // Get UTXOs with 0 confirmations as well.
}; };
@ -165,7 +225,7 @@ void RPC::getZUnspent(const std::function<void(QJsonValue)>& cb) {
void RPC::newZaddr(const std::function<void(QJsonValue)>& cb) { void RPC::newZaddr(const std::function<void(QJsonValue)>& cb) {
QJsonObject payload = { QJsonObject payload = {
{"jsonrpc", "1.0"}, {"jsonrpc", "1.0"},
{"id", "someid"}, {"id", "42"},
{"method", "z_getnewaddress"}, {"method", "z_getnewaddress"},
{"params", QJsonArray { "sapling" }}, {"params", QJsonArray { "sapling" }},
}; };
@ -197,7 +257,7 @@ void RPC::getTPrivKey(QString addr, const std::function<void(QJsonValue)>& cb) {
void RPC::importZPrivKey(QString privkey, bool rescan, const std::function<void(QJsonValue)>& cb) { void RPC::importZPrivKey(QString privkey, bool rescan, const std::function<void(QJsonValue)>& cb) {
QJsonObject payload = { QJsonObject payload = {
{"jsonrpc", "1.0"}, {"jsonrpc", "1.0"},
{"id", "someid"}, {"id", "42"},
{"method", "z_importkey"}, {"method", "z_importkey"},
{"params", QJsonArray { privkey, (rescan ? "yes" : "no") }}, {"params", QJsonArray { privkey, (rescan ? "yes" : "no") }},
}; };
@ -215,7 +275,7 @@ void RPC::importTPrivKey(QString privkey, bool rescan, const std::function<void(
qDebug() << "Detected old-style HUSH WIF"; qDebug() << "Detected old-style HUSH WIF";
payload = { payload = {
{"jsonrpc", "1.0"}, {"jsonrpc", "1.0"},
{"id", "someid"}, {"id", "42"},
{"method", "importprivkey"}, {"method", "importprivkey"},
{"params", QJsonArray { privkey, "", "false", "0", "128" }}, {"params", QJsonArray { privkey, "", "false", "0", "128" }},
}; };
@ -223,7 +283,7 @@ void RPC::importTPrivKey(QString privkey, bool rescan, const std::function<void(
qDebug() << "Detected new-style HUSH WIF"; qDebug() << "Detected new-style HUSH WIF";
payload = { payload = {
{"jsonrpc", "1.0"}, {"jsonrpc", "1.0"},
{"id", "someid"}, {"id", "42"},
{"method", "importprivkey"}, {"method", "importprivkey"},
{"params", QJsonArray { privkey, (rescan? "yes" : "no") }}, {"params", QJsonArray { privkey, (rescan? "yes" : "no") }},
}; };
@ -242,7 +302,7 @@ void RPC::validateAddress(QString address, const std::function<void(QJsonValue)>
void RPC::getBalance(const std::function<void(QJsonValue)>& cb) { void RPC::getBalance(const std::function<void(QJsonValue)>& cb) {
QJsonObject payload = { QJsonObject payload = {
{"jsonrpc", "1.0"}, {"jsonrpc", "1.0"},
{"id", "someid"}, {"id", "42"},
{"method", "z_gettotalbalance"}, {"method", "z_gettotalbalance"},
{"params", QJsonArray {0}} // Get Unconfirmed balance as well. {"params", QJsonArray {0}} // Get Unconfirmed balance as well.
}; };
@ -305,7 +365,7 @@ void RPC::sendZTransaction(QJsonValue params, const std::function<void(QJsonValu
const std::function<void(QString)>& err) { const std::function<void(QString)>& err) {
QJsonObject payload = { QJsonObject payload = {
{"jsonrpc", "1.0"}, {"jsonrpc", "1.0"},
{"id", "someid"}, {"id", "42"},
{"method", "z_sendmany"}, {"method", "z_sendmany"},
{"params", params} {"params", params}
}; };
@ -366,7 +426,7 @@ void RPC::getAllPrivKeys(const std::function<void(QList<QPair<QString, QString>>
[=] (auto addr) { [=] (auto addr) {
QJsonObject payload = { QJsonObject payload = {
{"jsonrpc", "1.0"}, {"jsonrpc", "1.0"},
{"id", "someid"}, {"id", "42"},
{"method", privKeyDumpMethodName}, {"method", privKeyDumpMethodName},
{"params", QJsonArray { addr }}, {"params", QJsonArray { addr }},
}; };
@ -391,14 +451,14 @@ void RPC::getAllPrivKeys(const std::function<void(QList<QPair<QString, QString>>
// First get all the t and z addresses. // First get all the t and z addresses.
QJsonObject payloadT = { QJsonObject payloadT = {
{"jsonrpc", "1.0"}, {"jsonrpc", "1.0"},
{"id", "someid"}, {"id", "42"},
{"method", "getaddressesbyaccount"}, {"method", "getaddressesbyaccount"},
{"params", QJsonArray {""} } {"params", QJsonArray {""} }
}; };
QJsonObject payloadZ = { QJsonObject payloadZ = {
{"jsonrpc", "1.0"}, {"jsonrpc", "1.0"},
{"id", "someid"}, {"id", "42"},
{"method", "z_listaddresses"} {"method", "z_listaddresses"}
}; };
@ -597,7 +657,6 @@ void RPC::refresh(bool force) {
getInfoThenRefresh(force); getInfoThenRefresh(force);
} }
void RPC::getInfoThenRefresh(bool force) { void RPC::getInfoThenRefresh(bool force) {
//qDebug() << "getinfo"; //qDebug() << "getinfo";
if (conn == nullptr) if (conn == nullptr)
@ -928,11 +987,12 @@ void RPC::refreshPeers() {
QList<BannedPeerItem> peerdata; QList<BannedPeerItem> peerdata;
for (const auto& it : reply.toArray()) { for (const auto& it : reply.toArray()) {
auto addr = it.toObject()["address"].toString(); auto addr = it.toObject()["address"].toString();
auto asn = (qint64)it.toObject()["mapped_as"].toInt();
auto bantime = (qint64)it.toObject()["banned_until"].toInt(); auto bantime = (qint64)it.toObject()["banned_until"].toInt();
auto parts = addr.split("/"); auto parts = addr.split("/");
auto ip = parts[0]; auto ip = parts[0];
auto subnet = parts[1]; auto subnet = parts[1];
BannedPeerItem peer { ip, subnet, bantime }; BannedPeerItem peer { ip, subnet, bantime, asn };
qDebug() << "Adding banned peer with address=" << addr; qDebug() << "Adding banned peer with address=" << addr;
peerdata.push_back(peer); peerdata.push_back(peer);
} }
@ -980,6 +1040,9 @@ void RPC::refreshTransactions() {
if (conn == nullptr) if (conn == nullptr)
return noConnection(); return noConnection();
// Show statusBar message
ui->statusBar->showMessage(QObject::tr("Transaction data is loading..."));
getTransactions([=] (QJsonValue reply) { getTransactions([=] (QJsonValue reply) {
QList<TransactionItem> txdata; QList<TransactionItem> txdata;
@ -1006,7 +1069,11 @@ void RPC::refreshTransactions() {
} }
// Update model data, which updates the table view // 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) if (!error)
sentTx.confirmations = j["confirmations"].toInt(); sentTx.confirmations = j["confirmations"].toInt();
} }
transactionsTableModel->addZSentData(newSentZTxs); transactionsTableModel->addZSentData(newSentZTxs);
delete txidList; delete txidList;
} }
@ -1135,8 +1202,10 @@ void RPC::watchTxStatus() {
} }
if (watchingOps.isEmpty()) { if (watchingOps.isEmpty()) {
txTimer->start(Settings::updateSpeed); // Stop the timer
txTimer->stop();
} else { } else {
// Keep polling for updates
txTimer->start(Settings::quickUpdateSpeed); txTimer->start(Settings::quickUpdateSpeed);
} }
} }
@ -1359,18 +1428,11 @@ void RPC::shutdownHushd() {
connD.setupUi(&d); connD.setupUi(&d);
//connD.topIcon->setBasePixmap(QIcon(":/icons/res/icon.ico").pixmap(256, 256)); //connD.topIcon->setBasePixmap(QIcon(":/icons/res/icon.ico").pixmap(256, 256));
QMovie *movie1 = new QMovie(":/img/res/silentdragon-animated.gif");; QMovie *movie1 = new QMovie(":/img/res/silentdragon-animated-dark.gif");;
QMovie *movie2 = new QMovie(":/img/res/silentdragon-animated-dark.gif");;
auto theme = Settings::getInstance()->get_theme_name(); auto theme = Settings::getInstance()->get_theme_name();
if (theme == "dark" || theme == "midnight") { movie1->setScaledSize(QSize(512,512));
movie2->setScaledSize(QSize(512,512)); connD.topIcon->setMovie(movie1);
connD.topIcon->setMovie(movie2); movie1->start();
movie2->start();
} else {
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.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")); connD.statusDetail->setText(QObject::tr("Waiting for hushd to exit, y'all"));

16
src/rpc.h

@ -27,6 +27,7 @@ struct BannedPeerItem {
QString address; QString address;
QString subnet; QString subnet;
qint64 banned_until; qint64 banned_until;
qint64 asn;
}; };
struct PeerItem { struct PeerItem {
@ -66,8 +67,10 @@ public:
void refresh(bool force = false); void refresh(bool force = false);
void refreshAddresses(); void refreshAddresses();
void refreshPeers(); void refreshPeers();
void setban(QString ip, QString command, const std::function<void(QJsonValue)>& cb);
void clearBanned(const std::function<void(QJsonValue)>& cb);
void checkForUpdate(bool silent = true); void checkForUpdate(bool silent = true);
void refreshPrice(); void refreshPrice();
void getZboardTopics(std::function<void(QMap<QString, QString>)> cb); void getZboardTopics(std::function<void(QMap<QString, QString>)> cb);
@ -115,6 +118,9 @@ public:
Connection* getConnection() { return conn; } Connection* getConnection() { return conn; }
void rescan(qint64 height, const std::function<void(QJsonValue)>& cb);
void getRescanInfo(const std::function<void(QJsonValue)>& cb);
private: private:
void refreshBalances(); void refreshBalances();
@ -128,7 +134,9 @@ private:
void getInfoThenRefresh(bool force); void getInfoThenRefresh(bool force);
void getBalance(const std::function<void(QJsonValue)>& cb); void getBalance(const std::function<void(QJsonValue)>& 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); QJsonValue makePayload(QString method);
void getTransparentUnspent (const std::function<void(QJsonValue)>& cb); void getTransparentUnspent (const std::function<void(QJsonValue)>& cb);
@ -138,6 +146,8 @@ private:
void getPeerInfo (const std::function<void(QJsonValue)>& cb); void getPeerInfo (const std::function<void(QJsonValue)>& cb);
void getZAddresses (const std::function<void(QJsonValue)>& cb); void getZAddresses (const std::function<void(QJsonValue)>& cb);
void getTAddresses (const std::function<void(QJsonValue)>& cb); void getTAddresses (const std::function<void(QJsonValue)>& cb);
void z_sweepstatus (const std::function<void(QJsonValue)>& cb);
void z_consolidationstatus (const std::function<void(QJsonValue)>& cb);
Connection* conn = nullptr; Connection* conn = nullptr;
std::shared_ptr<QProcess> ehushd = nullptr; std::shared_ptr<QProcess> ehushd = nullptr;

84
src/scripts/make-deb.sh

@ -6,7 +6,7 @@
DEBLOG=deb.log.$$ DEBLOG=deb.log.$$
if [ -z $QT_STATIC ]; then 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; exit 1;
fi 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 $PREV_VERSION ]; then echo "PREV_VERSION is not set"; exit 1; fi
if [ -z $HUSH_DIR ]; then 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; exit 1;
fi fi
@ -34,7 +34,12 @@ if [ ! -f $HUSH_DIR/hush-tx ]; then
exit 1; exit 1;
fi 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 bin/*
rm -rf artifacts/* rm -rf artifacts/*
make distclean >/dev/null 2>&1 make distclean >/dev/null 2>&1
@ -43,7 +48,7 @@ echo "[OK]"
echo "" echo ""
echo "[Building $APP_VERSION on" `lsb_release -r`" logging to $DEBLOG ]" 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 QT_STATIC=$QT_STATIC bash src/scripts/dotranslations.sh >/dev/null
echo -n "Configuring............" echo -n "Configuring............"
$QT_STATIC/bin/qmake silentdragon.pro -spec linux-clang CONFIG+=release > /dev/null $QT_STATIC/bin/qmake silentdragon.pro -spec linux-clang CONFIG+=release > /dev/null
@ -58,7 +63,7 @@ echo "[OK]"
# Test for Qt # Test for Qt
echo -n "Static link............" echo "Static link............"
if [[ $(ldd silentdragon | grep -i "Qt") ]]; then if [[ $(ldd silentdragon | grep -i "Qt") ]]; then
echo "FOUND QT; ABORT"; echo "FOUND QT; ABORT";
exit 1 exit 1
@ -66,34 +71,39 @@ fi
echo "[OK]" echo "[OK]"
echo -n "Packaging.............." echo "Packaging.............."
APP=SilentDragon-v$APP_VERSION APP=SilentDragon-v$APP_VERSION
DIR=bin/$APP DIR=bin/$APP
mkdir $DIR > /dev/null mkdir $DIR > /dev/null
strip silentdragon
cp silentdragon $DIR > /dev/null #Organizing all bins & essentials to centralized folder for tar.gz
cp $HUSH_DIR/artifacts/hushd $DIR > /dev/null echo "Organizing binaries & essentials.............."
cp $HUSH_DIR/artifacts/hush-cli $DIR > /dev/null cp silentdragon $DIR > /dev/null
cp $HUSH_DIR/artifacts/hush-tx $DIR > /dev/null cp README.md $DIR > /dev/null
cp README.md $DIR > /dev/null cp LICENSE $DIR > /dev/null
cp LICENSE $DIR > /dev/null
cd bin && tar czf $APP.tar.gz $DIR/ > /dev/null echo "Stripping silentdragon.............."
cd .. cd $DIR
strip silentdragon
cd ../..
mkdir artifacts >/dev/null 2>&1 echo "Compressing files.............."
cp $DIR.tar.gz ./artifacts/$APP-linux.tar.gz cd bin/
echo "[OK]" 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 if [ -f artifacts/$APP-linux.tar.gz ] ; then
echo -n "Package contents......." echo -n "Package contents......."
# Test if the package is built OK # 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]" echo "[OK]"
else else
echo "[ERROR] Wrong number of files does not match 9" echo "[ERROR] Wrong number of files does not match 11"
exit 1 exit 1
fi fi
else else
@ -101,25 +111,37 @@ else
exit 1 exit 1
fi fi
echo -n "Building deb..........." echo "Building package..........."
debdir=bin/deb/silentdragon-v$APP_VERSION debdir=deb/silentdragon-v$APP_VERSION
mkdir -p $debdir > /dev/null mkdir -p $debdir > /dev/null
mkdir $debdir/DEBIAN mkdir $debdir/DEBIAN
mkdir -p $debdir/usr/local/bin 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/ echo "Copying silentdragon bin..........."
cp $HUSH_DIR/artifacts/hushd $debdir/usr/local/bin/hushd cp ../silentdragon $debdir/usr/local/bin/
mkdir -p $debdir/usr/share/pixmaps/ echo "Copying core libraries from silentdragon binary..........."
cp res/silentdragon.xpm $debdir/usr/share/pixmaps/ # 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 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 dpkg-deb --build --root-owner-group $debdir >/dev/null
cp $debdir.deb artifacts/$DIR.deb
echo "[OK]" echo "[Success! $APP .deb has been created in $APP/bin/deb]"
exit 0 exit 0

2
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 rm -f artifacts/rw* >/dev/null 2>&1
cp $HUSH_DIR/src/hushd silentdragon.app/Contents/MacOS/ cp $HUSH_DIR/src/hushd silentdragon.app/Contents/MacOS/
cp $HUSH_DIR/src/hush-cli 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-output.params silentdragon.app/Contents/MacOS/
cp $HUSH_DIR/sapling-spend.params silentdragon.app/Contents/MacOS/ cp $HUSH_DIR/sapling-spend.params silentdragon.app/Contents/MacOS/
$QT_PATH/bin/macdeployqt silentdragon.app $QT_PATH/bin/macdeployqt silentdragon.app

46
src/scripts/mkrelease.sh

@ -21,18 +21,18 @@ if [ -z $HUSH_DIR ]; then
exit 1; exit 1;
fi fi
if [ ! -f $HUSH_DIR/komodod ]; then if [ ! -f $HUSH_DIR/hushd ]; then
echo "Couldn't find komodod in $HUSH_DIR . Please build komodod." echo "Couldn't find hushd in $HUSH_DIR . Please build hushd."
exit 1; exit 1;
fi fi
if [ ! -f $HUSH_DIR/komodo-cli ]; then if [ ! -f $HUSH_DIR/hush-cli ]; then
echo "Couldn't find komodo-cli in $HUSH_DIR . Please build komodo-cli." echo "Couldn't find hush-cli in $HUSH_DIR . Please build hush-cli."
exit 1; exit 1;
fi fi
if [ ! -f $HUSH_DIR/komodo-tx ]; then if [ ! -f $HUSH_DIR/hush-tx ]; then
echo "Couldn't find komodo-tx in $HUSH_DIR . Please build komodo-tx." echo "Couldn't find hush-tx in $HUSH_DIR . Please build hush-tx."
exit 1; exit 1;
fi 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 # this is equal to the number of files we package plus 1, for the directory
# that is created # that is created
NUM_FILES1=10 NUM_FILES1=6
NUM_FILES2=12 # 2 additional param files NUM_FILES2=8 # 2 additional param files
echo "Packaging.............." echo "Packaging.............."
mkdir bin/$RELEASEDIR mkdir bin/$RELEASEDIR
@ -92,18 +92,18 @@ strip silentdragon
ls -la silentdragon ls -la silentdragon
cp silentdragon bin/$RELEASEDIR > /dev/null cp silentdragon bin/$RELEASEDIR > /dev/null
cp $HUSH_DIR/komodod bin/$RELEASEDIR > /dev/null strip $HUSH_DIR/hushd
cp $HUSH_DIR/komodo-cli bin/$RELEASEDIR > /dev/null
cp $HUSH_DIR/komodo-tx bin/$RELEASEDIR > /dev/null
cp $HUSH_DIR/hushd bin/$RELEASEDIR > /dev/null 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-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 README.md bin/$RELEASEDIR > /dev/null
cp LICENSE bin/$RELEASEDIR > /dev/null cp LICENSE bin/$RELEASEDIR > /dev/null
cd bin && tar czf $RELEASEFILE1 $RELEASEDIR/ #> /dev/null cd bin && tar czf $RELEASEFILE1 $RELEASEDIR/ #> /dev/null
#ls -la $RELEASEDIR/ ls -la $RELEASEDIR/
echo "Created $RELEASEFILE1 [OK]" echo "Created $RELEASEFILE1 [OK]"
cd .. cd ..
@ -121,7 +121,7 @@ else
fi fi
cd bin && tar czf $RELEASEFILE2 $RELEASEDIR/ cd bin && tar czf $RELEASEFILE2 $RELEASEDIR/
#ls -la $RELEASEDIR/ ls -la $RELEASEDIR/
echo "Created $RELEASEFILE2 [OK]" echo "Created $RELEASEFILE2 [OK]"
cd .. cd ..
@ -157,6 +157,8 @@ else
fi fi
cd bin cd bin
du -sh $RELEASEFILE1
du -sh $RELEASEFILE2
echo "DONE! Checksums:" echo "DONE! Checksums:"
sha256sum $RELEASEFILE1 sha256sum $RELEASEFILE1
sha256sum $RELEASEFILE2 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/ cp silentdragon $debdir/usr/local/bin/
# TODO: how does this interact with hushd deb ? # 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/ mkdir -p $debdir/usr/share/pixmaps/
cp res/silentdragon.xpm $debdir/usr/share/pixmaps/ cp res/silentdragon.xpm $debdir/usr/share/pixmaps/
@ -202,14 +204,14 @@ if [ -z $MXE_PATH ]; then
exit 0; exit 0;
fi fi
if [ ! -f $HUSH_DIR/artifacts/komodod.exe ]; then if [ ! -f $HUSH_DIR/artifacts/hushd.exe ]; then
echo "Couldn't find komodod.exe in $HUSH_DIR/artifacts/. Please build komodod.exe" echo "Couldn't find hushd.exe in $HUSH_DIR/artifacts/. Please build hushd.exe"
exit 1; exit 1;
fi fi
if [ ! -f $HUSH_DIR/artifacts/komodo-cli.exe ]; then if [ ! -f $HUSH_DIR/artifacts/hush-cli.exe ]; then
echo "Couldn't find komodo-cli.exe in $HUSH_DIR/artifacts/. Please build komodod-cli.exe" echo "Couldn't find hush-cli.exe in $HUSH_DIR/artifacts/. Please build hushd-cli.exe"
exit 1; exit 1;
fi fi
@ -234,10 +236,8 @@ echo "[OK]"
echo -n "Packaging.............." echo -n "Packaging.............."
mkdir release/silentdragon-v$APP_VERSION mkdir release/silentdragon-v$APP_VERSION
cp release/silentdragon.exe 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/hushd.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/hush-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 README.md release/silentdragon-v$APP_VERSION cp README.md release/silentdragon-v$APP_VERSION
cp LICENSE 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 cd release && zip -r Windows-binaries-silentdragon-v$APP_VERSION.zip silentdragon-v$APP_VERSION/ > /dev/null

81
src/scripts/silentdragon.wxs

@ -0,0 +1,81 @@
<?xml version="1.0" encoding="utf-8"?>
<Wix xmlns="http://schemas.microsoft.com/wix/2006/wi">
<Product Id="*" Name="silentdragon vRELEASE_VERSION" Language="1033" Version="RELEASE_VERSION" Manufacturer="hush-org" UpgradeCode="fb9bf166-b55f-46b5-a990-9189bdf64533">
<Package InstallerVersion="200" Compressed="yes" InstallScope="perMachine" />
<MajorUpgrade DowngradeErrorMessage="A newer version of [ProductName] is already installed." />
<MediaTemplate EmbedCab="yes"/>
<Icon Id="silentdragonicon.exe" SourceFile="res/icon.ico"/>
<Property Id="ARPPRODUCTICON" Value="silentdragonicon.exe" />
<Feature Id="ProductFeature" Title="silent-dragon" Level="1">
<ComponentGroupRef Id="ProductComponents" />
<ComponentRef Id="ProgramMenuDir"/>
</Feature>
<Property Id="WIXUI_INSTALLDIR" Value="INSTALLFOLDER" />
<WixVariable Id="WixUIBannerBmp" Value="res/wxsbanner.bmp" />
<WixVariable Id="WixUIDialogBmp" Value="res/wxsdialog.bmp" />
<UI>
<UIRef Id="WixUI_InstallDir" />
<Publish Dialog="WelcomeDlg"
Control="Next"
Event="NewDialog"
Value="InstallDirDlg"
Order="2">1</Publish>
<Publish Dialog="InstallDirDlg"
Control="Back"
Event="NewDialog"
Value="WelcomeDlg"
Order="2">1</Publish>
</UI>
</Product>
<Fragment>
<Directory Id="TARGETDIR" Name="SourceDir">
<Directory Id="ProgramFilesFolder">
<Directory Id="INSTALLFOLDER" Name="silentdragon" />
</Directory>
<Directory Id="ProgramMenuFolder" Name="Programs">
<Directory Id="ApplicationProgramsFolder" Name="silentdragon">
<Component Id="ProgramMenuDir" Guid="0D560F5A-53E0-4E7E-ADDA-15A26995505E">
<RemoveFolder Id="ProgramMenuDir" On="uninstall"/>
<RegistryValue Root="HKCU" Key="Software\[Manufacturer]\silentdragon\WixSetup"
Type="integer" Value="1" Name="installed" KeyPath="yes" />
</Component>
</Directory>
</Directory>
</Directory>
</Fragment>
<Fragment>
<ComponentGroup Id="ProductComponents" Directory="INSTALLFOLDER">
<Component Id="ProductComponent" Guid="0D210F5A-53E0-4E7E-CAAD-15A26995505E">
<File Source="silentdragon.exe" KeyPath="yes">
<Shortcut Id="startMenuShotcut" Advertise="yes" Directory="ApplicationProgramsFolder"
Name="silentdragon" WorkingDirectory="INSTALLFOLDER" Icon="silentdragonicon.exe" >
</Shortcut>
</File>
<File Source="LICENSE" />
<File Source="hushd.exe" />
<File Source="hush-cli.exe" />
<File Source="README.md" />
<RegistryKey Root="HKCR" Key="hush">
<RegistryValue Type="string" Name="URL Protocol" Value=""/>
<RegistryValue Type="string" Value="URL:hush URI protocol"/>
<RegistryKey Key="DefaultIcon">
<RegistryValue Type="string" Value="silentdragon.exe" />
</RegistryKey>
<RegistryKey Key="shell\open\command">
<RegistryValue Type="string" Value="&quot;[INSTALLFOLDER]silentdragon.exe&quot; &quot;%1&quot;" />
</RegistryKey>
</RegistryKey>
</Component>
</ComponentGroup>
</Fragment>
</Wix>

72
src/sendtab.cpp

@ -14,7 +14,7 @@
void MainWindow::setupSendTab() { void MainWindow::setupSendTab() {
// Create the validator for send to/amount fields // 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); ui->Amount1->setValidator(amtValidator);
@ -251,51 +251,69 @@ void MainWindow::addAddressSection() {
Amount1->setPlaceholderText(tr("Amount")); Amount1->setPlaceholderText(tr("Amount"));
Amount1->setObjectName(QString("Amount") % QString::number(itemNumber)); Amount1->setObjectName(QString("Amount") % QString::number(itemNumber));
Amount1->setBaseSize(QSize(200, 0)); 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 // Create the validator for send to/amount fields
Amount1->setValidator(amtValidator); Amount1->setValidator(amtValidator);
QObject::connect(Amount1, &QLineEdit::textChanged, [=] (auto text) { QObject::connect(Amount1, &QLineEdit::textChanged, [=] (auto text) {
this->amountChanged(itemNumber, text); this->amountChanged(itemNumber, text);
}); });
horizontalLayout_13->addWidget(Amount1); horizontalLayout_13->addWidget(Amount1);
auto AmtUSD1 = new QLabel(verticalGroupBox); auto AmtUSD1 = new QLabel(verticalGroupBox);
AmtUSD1->setObjectName(QString("AmtUSD") % QString::number(itemNumber)); AmtUSD1->setObjectName(QString("AmtUSD") % QString::number(itemNumber));
horizontalLayout_13->addWidget(AmtUSD1); 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); auto horizontalSpacer_4 = new QSpacerItem(40, 20, QSizePolicy::Expanding, QSizePolicy::Minimum);
horizontalLayout_13->addItem(horizontalSpacer_4); 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); auto FileBtn = new QPushButton(verticalGroupBox);
FileBtn->setObjectName(QString("FileBtn") % QString::number(itemNumber)); FileBtn->setObjectName(QString("FileBtn") % QString::number(itemNumber));
FileBtn->setText(tr("File Upload")); FileBtn->setText(tr("Upload File"));
// Connect File Upload button // Connect File Upload button
QObject::connect(FileBtn, &QPushButton::clicked, [=] () { QObject::connect(FileBtn, &QPushButton::clicked, [=] () {
this->fileUploadButtonClicked(itemNumber); this->fileUploadButtonClicked(itemNumber);
}); });
horizontalLayout_13->addWidget(FileBtn); horizontalLayout_13->addWidget(FileBtn);
sendAddressLayout->addLayout(horizontalLayout_13); sendAddressLayout->addLayout(horizontalLayout_13);
auto horizontalLayout_20 = new QHBoxLayout();
horizontalLayout_20->setSpacing(6);
horizontalLayout_20->setAlignment(Qt::AlignTop);
auto MemoTxt1 = new QLabel(verticalGroupBox); auto MemoTxt1 = new QLabel(verticalGroupBox);
MemoTxt1->setObjectName(QString("MemoTxt") % QString::number(itemNumber)); MemoTxt1->setObjectName(QString("MemoTxt") % QString::number(itemNumber));
MemoTxt1->setSizePolicy(QSizePolicy ::MinimumExpanding , QSizePolicy ::MinimumExpanding );
QFont font1 = Address1->font(); QFont font1 = Address1->font();
font1.setPointSize(font1.pointSize()-1); font1.setPointSize(font1.pointSize()-1);
MemoTxt1->setFont(font1); MemoTxt1->setFont(font1);
MemoTxt1->setWordWrap(true); 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); ui->sendToLayout->insertWidget(itemNumber-1, verticalGroupBox);
@ -702,18 +720,11 @@ void MainWindow::sendButton() {
auto connD = new Ui_ConnectionDialog(); auto connD = new Ui_ConnectionDialog();
connD->setupUi(d); connD->setupUi(d);
QMovie *movie1 = new QMovie(":/img/res/silentdragon-animated.gif");; QMovie *movie1 = new QMovie(":/img/res/silentdragon-animated-dark.gif");;
QMovie *movie2 = new QMovie(":/img/res/silentdragon-animated-dark.gif");;
auto theme = Settings::getInstance()->get_theme_name(); auto theme = Settings::getInstance()->get_theme_name();
if (theme == "dark" || theme == "midnight") { movie1->setScaledSize(QSize(512,512));
movie2->setScaledSize(QSize(512,512)); connD->topIcon->setMovie(movie1);
connD->topIcon->setMovie(movie2); movie1->start();
movie2->start();
} else {
movie1->setScaledSize(QSize(512,512));
connD->topIcon->setMovie(movie1);
movie1->start();
}
//connD->topIcon->setBasePixmap(logo.scaled(256, 256, Qt::KeepAspectRatio, Qt::SmoothTransformation)); //connD->topIcon->setBasePixmap(logo.scaled(256, 256, Qt::KeepAspectRatio, Qt::SmoothTransformation));
@ -729,7 +740,7 @@ void MainWindow::sendButton() {
qDebug() << "Computing opid: " << opid; qDebug() << "Computing opid: " << opid;
}, },
[=] (QString, QString txid) { [=] (QString, QString txid) {
ui->statusBar->showMessage(Settings::txidStatusMessage + " " + txid); ui->statusBar->showMessage(Settings::txidStatusMessage + " " + txid);
connD->status->setText(tr("Done!")); connD->status->setText(tr("Done!"));
@ -744,7 +755,8 @@ void MainWindow::sendButton() {
// And switch to the balances tab // And switch to the balances tab
ui->tabWidget->setCurrentIndex(0); 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); rpc->refresh(true);
}, },
[=] (QString opid, QString errStr) { [=] (QString opid, QString errStr) {

3
src/senttxstore.cpp

@ -14,6 +14,7 @@ QString SentTxStore::writeableFile() {
if (Settings::getInstance()->isTestnet()) { if (Settings::getInstance()->isTestnet()) {
return dir.filePath("testnet-" % filename); return dir.filePath("testnet-" % filename);
} else { } else {
qDebug() << "senttxstore file = " + dir.filePath(filename);
return dir.filePath(filename); return dir.filePath(filename);
} }
} }
@ -45,7 +46,7 @@ QList<TransactionItem> SentTxStore::readSentTxFile() {
sentTx["address"].toString(), sentTx["address"].toString(),
sentTx["txid"].toString(), sentTx["txid"].toString(),
sentTx["amount"].toDouble() + sentTx["fee"].toDouble(), sentTx["amount"].toDouble() + sentTx["fee"].toDouble(),
0, sentTx["from"].toString(), ""}; 0, sentTx["from"].toString(), sentTx["memo"].toString()};
items.push_back(t); items.push_back(t);
} }

31
src/settings.cpp

@ -40,9 +40,20 @@ Explorer Settings::getExplorer() {
auto txExplorerUrl = s.value("explorer/txExplorerUrl", explorer + "/tx/").toString(); auto txExplorerUrl = s.value("explorer/txExplorerUrl", explorer + "/tx/").toString();
auto addressExplorerUrl = s.value("explorer/addressExplorerUrl", explorer + "/address/").toString(); auto addressExplorerUrl = s.value("explorer/addressExplorerUrl", explorer + "/address/").toString();
auto testnetTxExplorerUrl = s.value("explorer/testnetTxExplorerUrl").toString(); auto testnetTxExplorerUrl = s.value("explorer/testnetTxExplorerUrl").toString();
auto testnetAddressExplorerUrl = s.value("explorer/testnetAddressExplorerUrl").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}; return Explorer{txExplorerUrl, addressExplorerUrl, testnetTxExplorerUrl, testnetAddressExplorerUrl};
} }
@ -246,10 +257,13 @@ void Settings::setAllowCustomFees(bool allow) {
QString Settings::get_theme_name() { QString Settings::get_theme_name() {
// Load from the QT Settings. // 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) { void Settings::set_theme_name(QString theme_name) {
qDebug() << __func__ << ": settings theme_name=" << theme_name;
QSettings().setValue("options/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); 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) { bool Settings::removeFromHushConf(QString confLocation, QString option) {
if (confLocation.isEmpty()) if (confLocation.isEmpty())

13
src/settings.h

@ -89,6 +89,9 @@ public:
QString get_currency_name(); QString get_currency_name();
void set_currency_name(QString currency_name); void set_currency_name(QString currency_name);
QString get_language();
void set_language(QString lang);
void setUsingHushConf(QString confLocation); void setUsingHushConf(QString confLocation);
const QString& getHushdConfLocation() { return _confLocation; } const QString& getHushdConfLocation() { return _confLocation; }
@ -132,6 +135,7 @@ public:
static double getZboardAmount(); static double getZboardAmount();
static QString getZboardAddr(); static QString getZboardAddr();
//TODO: this could be an advanced setting too
static int getMaxMobileAppTxns() { return 30; } static int getMaxMobileAppTxns() { return 30; }
static bool isValidAddress(QString addr); static bool isValidAddress(QString addr);
@ -146,6 +150,10 @@ public:
static const int quickUpdateSpeed = 3 * 1000; // 3 sec static const int quickUpdateSpeed = 3 * 1000; // 3 sec
static const int priceRefreshSpeed = 15 * 60 * 1000; // 15 mins 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: private:
// This class can only be accessed through Settings::getInstance() // This class can only be accessed through Settings::getInstance()
Settings() = default; Settings() = default;
@ -158,12 +166,11 @@ private:
bool _isTestnet = false; bool _isTestnet = false;
bool _isSyncing = false; bool _isSyncing = false;
int _blockNumber = 0; int _blockNumber = 0;
int _hushdVersion = 0; int _hushdVersion = 0;
bool _useEmbedded = false; bool _useEmbedded = false;
bool _headless = false; bool _headless = false;
int _peerConnections = 0; int _peerConnections = 0;
double hushPrice = 0.0;
double hushPrice = 0.0;
double fiat_price = 0.0; double fiat_price = 0.0;
unsigned int btcPrice = 0; unsigned int btcPrice = 0;
std::map<QString, double> prices; std::map<QString, double> prices;

89
src/settings.ui

@ -22,8 +22,8 @@
<property name="modal"> <property name="modal">
<bool>true</bool> <bool>true</bool>
</property> </property>
<layout class="QVBoxLayout" name="verticalLayout"> <layout class="QGridLayout" name="gridLayout_2">
<item> <item row="0" column="0">
<widget class="QTabWidget" name="tabWidget"> <widget class="QTabWidget" name="tabWidget">
<property name="currentIndex"> <property name="currentIndex">
<number>3</number> <number>3</number>
@ -177,6 +177,32 @@
</property> </property>
</widget> </widget>
</item> </item>
<item row="17" column="0">
<widget class="QLabel" name="langlabel">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>Language</string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
</widget>
</item>
<item row="17" column="1">
<widget class="QComboBox" name="comboBoxLanguage">
<property name="sizePolicy">
<sizepolicy hsizetype="MinimumExpanding" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
</widget>
</item>
<item row="16" column="1"> <item row="16" column="1">
<widget class="QComboBox" name="comboBoxCurrency"> <widget class="QComboBox" name="comboBoxCurrency">
<property name="sizePolicy"> <property name="sizePolicy">
@ -412,27 +438,27 @@
</property> </property>
<item> <item>
<property name="text"> <property name="text">
<string>default</string> <string notr="true">default</string>
</property> </property>
</item> </item>
<item> <item>
<property name="text"> <property name="text">
<string>blue</string> <string notr="true">blue</string>
</property> </property>
</item> </item>
<item> <item>
<property name="text"> <property name="text">
<string>light</string> <string notr="true">light</string>
</property> </property>
</item> </item>
<item> <item>
<property name="text"> <property name="text">
<string>dark</string> <string notr="true">dark</string>
</property> </property>
</item> </item>
<item> <item>
<property name="text"> <property name="text">
<string>midnight</string> <string notr="true">midnight</string>
</property> </property>
</item> </item>
</widget> </widget>
@ -698,6 +724,9 @@
</widget> </widget>
</widget> </widget>
<widget class="QWidget" name="tab_3"> <widget class="QWidget" name="tab_3">
<property name="focusPolicy">
<enum>Qt::NoFocus</enum>
</property>
<attribute name="title"> <attribute name="title">
<string>Troubleshooting</string> <string>Troubleshooting</string>
</attribute> </attribute>
@ -717,32 +746,19 @@
<widget class="QLabel" name="label_9"> <widget class="QLabel" name="label_9">
<property name="geometry"> <property name="geometry">
<rect> <rect>
<x>9</x> <x>10</x>
<y>38</y> <y>10</y>
<width>583</width> <width>583</width>
<height>51</height> <height>51</height>
</rect> </rect>
</property> </property>
<property name="text"> <property name="text">
<string>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</string> <string>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.</string>
</property> </property>
<property name="wordWrap"> <property name="wordWrap">
<bool>true</bool> <bool>true</bool>
</property> </property>
</widget> </widget>
<widget class="QCheckBox" name="chkRescan">
<property name="geometry">
<rect>
<x>9</x>
<y>9</y>
<width>73</width>
<height>23</height>
</rect>
</property>
<property name="text">
<string>Rescan</string>
</property>
</widget>
<widget class="Line" name="line_3"> <widget class="Line" name="line_3">
<property name="geometry"> <property name="geometry">
<rect> <rect>
@ -963,10 +979,35 @@
<string>MB</string> <string>MB</string>
</property> </property>
</widget> </widget>
<widget class="QPushButton" name="rescanButton">
<property name="geometry">
<rect>
<x>310</x>
<y>60</y>
<width>281</width>
<height>41</height>
</rect>
</property>
<property name="focusPolicy">
<enum>Qt::StrongFocus</enum>
</property>
<property name="text">
<string>Rescan</string>
</property>
<property name="autoDefault">
<bool>false</bool>
</property>
<property name="default">
<bool>true</bool>
</property>
<property name="flat">
<bool>false</bool>
</property>
</widget>
</widget> </widget>
</widget> </widget>
</item> </item>
<item> <item row="1" column="0">
<widget class="QDialogButtonBox" name="buttonBox"> <widget class="QDialogButtonBox" name="buttonBox">
<property name="orientation"> <property name="orientation">
<enum>Qt::Horizontal</enum> <enum>Qt::Horizontal</enum>

106
src/txtablemodel.cpp

@ -3,6 +3,7 @@
#include "txtablemodel.h" #include "txtablemodel.h"
#include "settings.h" #include "settings.h"
#include "rpc.h" #include "rpc.h"
#include "guiconstants.h"
TxTableModel::TxTableModel(QObject *parent) TxTableModel::TxTableModel(QObject *parent)
: QAbstractTableModel(parent) { : QAbstractTableModel(parent) {
@ -56,7 +57,7 @@ bool TxTableModel::exportToCsv(QString fileName) const {
out << "\"" << headers[i] << "\","; out << "\"" << headers[i] << "\",";
} }
out << "\"Memo\""; out << "\"Memo\"";
out << endl; out << Qt::endl;
// Write out each row // Write out each row
for (int row = 0; row < modeldata->length(); row++) { for (int row = 0; row < modeldata->length(); row++) {
@ -65,14 +66,14 @@ bool TxTableModel::exportToCsv(QString fileName) const {
} }
// Memo // Memo
out << "\"" << modeldata->at(row).memo << "\""; out << "\"" << modeldata->at(row).memo << "\"";
out << endl; out << Qt::endl;
} }
file.close(); file.close();
return true; return true;
} }
void TxTableModel::updateAllData() { void TxTableModel::updateAllData() {
auto newmodeldata = new QList<TransactionItem>(); auto newmodeldata = new QList<TransactionItem>();
if (tTrans != nullptr) std::copy( tTrans->begin(), tTrans->end(), std::back_inserter(*newmodeldata)); if (tTrans != nullptr) std::copy( tTrans->begin(), tTrans->end(), std::back_inserter(*newmodeldata));
@ -92,33 +93,61 @@ void TxTableModel::updateAllData() {
layoutChanged(); 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; if (modeldata == nullptr) return 0;
return modeldata->size(); return modeldata->size();
} }
int TxTableModel::columnCount(const QModelIndex&) const int TxTableModel::columnCount(const QModelIndex&) const
{ {
return headers.size(); return headers.size();
} }
QVariant TxTableModel::data(const QModelIndex &index, int role) const 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::TextAlignmentRole && index.column() == 3) return QVariant(Qt::AlignRight | Qt::AlignVCenter);
if (role == Qt::ForegroundRole) { if (role == Qt::ForegroundRole) {
if (modeldata->at(index.row()).confirmations == 0) { if (modeldata->at(index.row()).confirmations == 0) {
QBrush b; b.setColor(COLOR_UNCONFIRMED_TX);
b.setColor(Qt::red); return b;
}
if (theme_name == "dark" || theme_name == "midnight") {
b.setColor(color);
return b;
}else{
b.setColor(color);
return b; return b;
} }
// Else, just return the default brush
QBrush b;
b.setColor(Qt::black);
return b; return b;
} }
@ -161,6 +190,9 @@ void TxTableModel::updateAllData() {
} }
if (role == Qt::DecorationRole && index.column() == 0) { if (role == Qt::DecorationRole && index.column() == 0) {
//qDebug() << "TX Type = " + dat.type;
if (!dat.memo.isEmpty()) { if (!dat.memo.isEmpty()) {
// If the memo is a Payment URI, then show a payment request icon // If the memo is a Payment URI, then show a payment request icon
if (dat.memo.startsWith("hush:")) { if (dat.memo.startsWith("hush:")) {
@ -172,7 +204,43 @@ void TxTableModel::updateAllData() {
return QVariant(icon.pixmap(16, 16)); return QVariant(icon.pixmap(16, 16));
} }
} else { } 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); QPixmap p(16, 16);
p.fill(Qt::white); p.fill(Qt::white);
return QVariant(p); return QVariant(p);
@ -216,6 +284,10 @@ QString TxTableModel::getAddr(int row) const {
return modeldata->at(row).address.trimmed(); return modeldata->at(row).address.trimmed();
} }
QString TxTableModel::getFromAddr(int row) const {
return modeldata->at(row).fromAddr.trimmed();
}
qint64 TxTableModel::getDate(int row) const { qint64 TxTableModel::getDate(int row) const {
return modeldata->at(row).datetime; return modeldata->at(row).datetime;
} }

6
src/txtablemodel.h

@ -10,16 +10,17 @@ struct TransactionItem;
class TxTableModel: public QAbstractTableModel class TxTableModel: public QAbstractTableModel
{ {
public: public:
TxTableModel(QObject* parent); TxTableModel(QObject* parent);
~TxTableModel(); ~TxTableModel();
void addTData (const QList<TransactionItem>& data); void addTData (const QList<TransactionItem>& data);
void addZSentData(const QList<TransactionItem>& data); void addZSentData(const QList<TransactionItem>& data);
void addZRecvData(const QList<TransactionItem>& data); void addZRecvData(const QList<TransactionItem>& data);
QString getTxId(int row) const; QString getTxId(int row) const;
QString getMemo(int row) const; QString getMemo(int row) const;
QString getAddr(int row) const; QString getAddr(int row) const;
QString getFromAddr(int row) const;
qint64 getDate(int row) const; qint64 getDate(int row) const;
QString getType(int row) const; QString getType(int row) const;
qint64 getConfirmations(int row) const; qint64 getConfirmations(int row) const;
@ -31,6 +32,7 @@ public:
int columnCount(const QModelIndex &parent) const; int columnCount(const QModelIndex &parent) const;
QVariant data(const QModelIndex &index, int role) const; QVariant data(const QModelIndex &index, int role) const;
QVariant headerData(int section, Qt::Orientation orientation, int role) const; QVariant headerData(int section, Qt::Orientation orientation, int role) const;
QImage colorizeIcon(const QIcon icon, const QColor color) const;
private: private:
void updateAllData(); void updateAllData();

2
src/version.h

@ -1 +1 @@
#define APP_VERSION "1.3.0" #define APP_VERSION "1.3.1"

6
src/websockets.cpp

@ -153,6 +153,7 @@ void WormholeClient::connect() {
void WormholeClient::retryConnect() { void WormholeClient::retryConnect() {
QTimer::singleShot(5 * 1000 * pow(2, retryCount), [=]() { QTimer::singleShot(5 * 1000 * pow(2, retryCount), [=]() {
qDebug() << __func__ << ": retryCount=" << retryCount;
if (retryCount < 10) { if (retryCount < 10) {
qDebug() << "Retrying websocket connection, count=" << this->retryCount; qDebug() << "Retrying websocket connection, count=" << this->retryCount;
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 // On connected, we'll also create a timer to ping it every 4 minutes, since the websocket
// will timeout after 5 minutes // will timeout after 5 minutes
timer = new QTimer(parent); timer = new QTimer(parent);
qDebug() << "Created QTimer"; qDebug() << __func__ << ": Created QTimer";
QObject::connect(timer, &QTimer::timeout, [=]() { QObject::connect(timer, &QTimer::timeout, [=]() {
qDebug() << "Timer timeout!";
try { try {
qDebug() << __func__ << ": Timer timeout! shuttingDown=" << shuttingDown << " m_webSocket=" << m_webSocket << " isValid=" << m_webSocket->isValid();
if (!shuttingDown && m_webSocket && m_webSocket->isValid()) { if (!shuttingDown && m_webSocket && m_webSocket->isValid()) {
auto payload = QJsonDocument(QJsonObject { {"ping", "ping"} }).toJson(); auto payload = QJsonDocument(QJsonObject { {"ping", "ping"} }).toJson();
qint64 bytes = m_webSocket->sendTextMessage(payload); qint64 bytes = m_webSocket->sendTextMessage(payload);
@ -276,6 +277,7 @@ QString AppDataServer::getSecretHex() {
} }
void AppDataServer::saveNewSecret(QString secretHex) { void AppDataServer::saveNewSecret(QString secretHex) {
qDebug() << __func__ << ": saving secretHex to config file";
QSettings().setValue("mobileapp/secret", secretHex); QSettings().setValue("mobileapp/secret", secretHex);
if (secretHex.isEmpty()) if (secretHex.isEmpty())

Loading…
Cancel
Save