Compare commits

...

1 Commits

Author SHA1 Message Date
fekt 3bcd7184f4 Testing Updates 2 years ago
  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
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"
```

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

2
res/css/midnight.css

@ -9,7 +9,7 @@ Website: https://www.csharpe.me
License: https://opensource.org/licenses/MIT
*/
QWidget, QMainWindow, QMenuBar, QMenu, QDialog, QTabWidget, QTableView, QTableView::item, QScrollArea, QGroupBox, QPlainTextEdit, QLineEdit, QLabel, MainWindow, QPixmap, QHBoxLayout, QVBoxLayout, QGridLayout, QAbstractItemView, QFrame
QWidget, QMainWindow, QMenuBar, QMenu, QDialog, QTabWidget, QTableView, QScrollArea, QGroupBox, QPlainTextEdit, QLineEdit, QLabel, MainWindow, QPixmap, QHBoxLayout, QVBoxLayout, QGridLayout, QAbstractItemView, QFrame
{
background-color: #111;
color: #fff;

BIN
res/lock_closed.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 KiB

After

Width:  |  Height:  |  Size: 1.2 KiB

BIN
res/lock_open.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 KiB

After

Width:  |  Height:  |  Size: 1.2 KiB

BIN
res/remove.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.7 KiB

After

Width:  |  Height:  |  Size: 1.7 KiB

BIN
res/send.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.7 KiB

After

Width:  |  Height:  |  Size: 1.7 KiB

BIN
res/synced.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.6 KiB

After

Width:  |  Height:  |  Size: 1.6 KiB

BIN
res/transaction0.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 KiB

After

Width:  |  Height:  |  Size: 1.3 KiB

BIN
res/transaction2.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.6 KiB

After

Width:  |  Height:  |  Size: 1.6 KiB

BIN
res/transaction_abandoned.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

After

Width:  |  Height:  |  Size: 1.4 KiB

BIN
res/transaction_conflicted.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

After

Width:  |  Height:  |  Size: 1.1 KiB

BIN
res/tx_inout.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.6 KiB

After

Width:  |  Height:  |  Size: 1.6 KiB

BIN
res/tx_input.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.7 KiB

After

Width:  |  Height:  |  Size: 1.7 KiB

BIN
res/tx_mined.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 KiB

After

Width:  |  Height:  |  Size: 1.5 KiB

BIN
res/tx_output.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.7 KiB

After

Width:  |  Height:  |  Size: 1.7 KiB

BIN
res/verify.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.0 KiB

After

Width:  |  Height:  |  Size: 2.0 KiB

BIN
res/warning.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.7 KiB

After

Width:  |  Height:  |  Size: 2.7 KiB

3
silentdragon.pro

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

23
src/balancestablemodel.cpp

@ -3,6 +3,7 @@
#include "balancestablemodel.h"
#include "addressbook.h"
#include "settings.h"
#include "guiconstants.h"
BalancesTableModel::BalancesTableModel(QObject *parent)
@ -60,6 +61,10 @@ int BalancesTableModel::columnCount(const QModelIndex&) 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 (role == Qt::DisplayRole)
return "Loading...";
@ -69,21 +74,23 @@ QVariant BalancesTableModel::data(const QModelIndex &index, int role) const
if (role == Qt::TextAlignmentRole && index.column() == 1) return QVariant(Qt::AlignRight | Qt::AlignVCenter);
if (role == Qt::ForegroundRole) {
if (role == Qt::ForegroundRole) {
// If any of the UTXOs for this address has zero confirmations, paint it in red
const auto& addr = std::get<0>(modeldata->at(index.row()));
for (auto utxo : *utxos) {
if (utxo.address == addr && utxo.confirmations == 0) {
QBrush b;
b.setColor(Qt::red);
b.setColor(COLOR_UNCONFIRMED_TX);
return b;
}
}
// Else, just return the default brush
QBrush b;
b.setColor(Qt::black);
return b;
if (theme_name == "dark" || theme_name == "midnight") {
b.setColor(COLOR_WHITE);
return b;
}else{
b.setColor(COLOR_BLACK);
return b;
}
return b;
}
if (role == Qt::DisplayRole) {

7
src/bannedpeerstablemodel.cpp

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

1
src/bannedpeerstablemodel.h

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

255
src/connection.cpp

@ -15,24 +15,59 @@ ConnectionLoader::ConnectionLoader(MainWindow* main, RPC* rpc) {
d = new QDialog(main);
d->setWindowFlags(d->windowFlags() & ~(Qt::WindowCloseButtonHint | Qt::WindowContextHelpButtonHint));
connD = new Ui_ConnectionDialog();
connD->setupUi(d);
QMovie *movie1 = new QMovie(":/img/res/silentdragon-animated-startup.gif");;
QMovie *movie2 = new QMovie(":/img/res/silentdragon-animated-startup-dark.gif");;
QMovie *movie1 = new QMovie(":/img/res/silentdragon-animated-startup-dark.gif");;
auto theme = Settings::getInstance()->get_theme_name();
auto size = QSize(512,512);
if (theme == "dark" || theme == "midnight") {
movie2->setScaledSize(size);
connD->topIcon->setMovie(movie2);
movie2->start();
} else {
movie1->setScaledSize(size);
connD->topIcon->setMovie(movie1);
movie1->start();
}
movie1->setScaledSize(size);
connD->topIcon->setMovie(movie1);
movie1->start();
main->logger->write("set animation");
qDebug() << "set animation";
// TODO: Check for rescan
rpc->getRescanInfo([=] (QJsonValue response){
qDebug() << "getrescaninfo";
});
/*
// Set up timer to check rescan
rescanTimer = new QTimer(main);
QObject::connect(rescanTimer, &QTimer::timeout, [=]() {
QString progress = rescanProgress();
qDebug() << "Rescan progress = " +progress;
if(progress=="Start"){
this->showInformation(QObject::tr("Rescanning. This may take several hours, grab some popcorn"));
}
if(progress!="Start" && progress!="Stop"){
QStringList progressParts = progress.split(" ");
QString percentStr="unknown %";
QString height="unknown";
bool isNumeric = false;
if (progressParts[0].toDouble(&isNumeric) && progressParts[1].toDouble(&isNumeric)){
double percent = progressParts[0].toDouble()*100;
percentStr = QString::number(percent)+"%";
height = progressParts[1];
}
this->showInformation(QObject::tr("Rescanning. This may take several hours, grab some popcorn"), percentStr+ QObject::tr(" at height ") +height);
}
if(progress=="Stop"){
qDebug() << "Stop rescanTimer";
}
});
rescanTimer->start(10000); //debug.log is written to every 1 minute for rescan update but might want to check quicker to show rescanning sooner
*/
}
ConnectionLoader::~ConnectionLoader() {
@ -74,12 +109,12 @@ void ConnectionLoader::doAutoConnect(bool tryEhushdStart) {
} else {
if (config->hushDaemon) {
// hushd is configured to run as a daemon, so we must wait for a few seconds
// to let it start up.
// to let it start up.
main->logger->write("hushd is daemon=1. Waiting for it to start up");
this->showInformation(QObject::tr("hushd is set to run as daemon"), QObject::tr("Waiting for hushd"));
QTimer::singleShot(5000, [=]() { doAutoConnect(/* don't attempt to start ehushd */ false); });
} else {
// Something is wrong.
// Something is wrong.
// We're going to attempt to connect to the one in the background one last time
// and see if that works, else throw an error
main->logger->write("Unknown problem while trying to start hushd!");
@ -87,7 +122,7 @@ void ConnectionLoader::doAutoConnect(bool tryEhushdStart) {
}
}
} else {
// We tried to start ehushd previously, and it didn't work. So, show the error.
// We tried to start ehushd previously, and it didn't work. So, show the error.
main->logger->write("Couldn't start embedded hushd for unknown reason");
QString explanation;
if (config->hushDaemon) {
@ -96,18 +131,18 @@ void ConnectionLoader::doAutoConnect(bool tryEhushdStart) {
"Please remove the following line from your HUSH3.conf and restart SilentDragon\n"
"daemon=1");
} else {
explanation = QString() % QObject::tr("Couldn't start the embedded hushd.\n\n"
"Please try restarting.\n\nIf you previously started hushd with custom arguments, you might need to reset HUSH3.conf.\n\n"
"If all else fails, please run hushd manually.") %
explanation = QString() % QObject::tr("Couldn't start the embedded hushd.\n\n"
"Please try restarting.\n\nIf you previously started hushd with custom arguments, you might need to reset HUSH3.conf.\n\n"
"If all else fails, please run hushd manually.") %
(ehushd ? QObject::tr("The process returned") + ":\n\n" % ehushd->errorString() : QString(""));
}
this->showError(explanation);
}
}
} else {
// HUSH3.conf exists, there's no connection, and the user asked us not to start hushd. Error!
main->logger->write("Not using embedded and couldn't connect to hushd");
QString explanation = QString() % QObject::tr("Couldn't connect to hushd configured in HUSH3.conf.\n\n"
QString explanation = QString() % QObject::tr("Couldn't connect to hushd configured in HUSH3.conf.\n\n"
"Not starting embedded hushd because --no-embedded was passed");
this->showError(explanation);
}
@ -120,7 +155,7 @@ void ConnectionLoader::doAutoConnect(bool tryEhushdStart) {
// Fall back to manual connect
doManualConnect();
}
}
}
}
QString randomPassword() {
@ -142,7 +177,7 @@ QString randomPassword() {
/**
* This will create a new HUSH3.conf and download params if they cannot be found
*/
*/
void ConnectionLoader::createHushConf() {
main->logger->write(__func__);
@ -266,7 +301,7 @@ void ConnectionLoader::doNextDownload(std::function<void(void)> cb) {
}
// The downloaded file is written to a new name, and then renamed when the operation completes.
currentOutput = new QFile(QDir(paramsDir).filePath(filename + ".part"));
currentOutput = new QFile(QDir(paramsDir).filePath(filename + ".part"));
if (!currentOutput->open(QIODevice::WriteOnly)) {
main->logger->write("Couldn't open " + currentOutput->fileName() + " for writing");
@ -274,12 +309,12 @@ void ConnectionLoader::doNextDownload(std::function<void(void)> cb) {
}
main->logger->write("Downloading to " + filename);
qDebug() << "Downloading " << url << " to " << filename;
QNetworkRequest request(url);
request.setAttribute(QNetworkRequest::FollowRedirectsAttribute, true);
currentDownload = client->get(request);
downloadTime.start();
// Download Progress
QObject::connect(currentDownload, &QNetworkReply::downloadProgress, [=] (auto done, auto total) {
// calculate the download speed
@ -299,7 +334,7 @@ void ConnectionLoader::doNextDownload(std::function<void(void)> cb) {
QObject::tr("Downloading ") % filename % (filesRemaining > 1 ? " ( +" % QString::number(filesRemaining) % QObject::tr(" more remaining )") : QString("")),
QString::number(done/1024/1024, 'f', 0) % QObject::tr("MB of ") % QString::number(total/1024/1024, 'f', 0) + QObject::tr("MB at ") % QString::number(speed, 'f', 2) % unit);
});
// Download Finished
QObject::connect(currentDownload, &QNetworkReply::finished, [=] () {
// Rename file
@ -312,22 +347,22 @@ void ConnectionLoader::doNextDownload(std::function<void(void)> cb) {
if (currentDownload->error()) {
main->logger->write("Downloading " + filename + " failed");
this->showError(QObject::tr("Downloading ") + filename + QObject::tr(" failed. Please check the help site for more info"));
this->showError(QObject::tr("Downloading ") + filename + QObject::tr(" failed. Please check the help site for more info"));
} else {
doNextDownload(cb);
}
});
// Download new data available.
// Download new data available.
QObject::connect(currentDownload, &QNetworkReply::readyRead, [=] () {
currentOutput->write(currentDownload->readAll());
});
});
}
bool ConnectionLoader::startEmbeddedHushd() {
if (!Settings::getInstance()->useEmbedded())
if (!Settings::getInstance()->useEmbedded())
return false;
main->logger->write("Trying to start embedded hushd");
// Static because it needs to survive even after this method returns.
@ -336,13 +371,13 @@ bool ConnectionLoader::startEmbeddedHushd() {
if (ehushd != nullptr) {
if (ehushd->state() == QProcess::NotRunning) {
if (!processStdErrOutput.isEmpty()) {
QMessageBox::critical(main, QObject::tr("hushd error"), "hushd said: " + processStdErrOutput,
QMessageBox::critical(main, QObject::tr("hushd error"), "hushd said: " + processStdErrOutput,
QMessageBox::Ok);
}
return false;
} else {
return true;
}
}
}
QDir appPath(QCoreApplication::applicationDirPath());
@ -352,7 +387,7 @@ bool ConnectionLoader::startEmbeddedHushd() {
#else
auto hushdProgram = appPath.absoluteFilePath("hushd");
#endif
//if (!QFile(hushdProgram).exists()) {
if (!QFile::exists(hushdProgram)) {
qDebug() << "Can't find hushd at " << hushdProgram;
@ -441,7 +476,7 @@ void ConnectionLoader::doManualConnect() {
auto connection = makeConnection(config);
refreshHushdState(connection, [=] () {
QString explanation = QString()
% QObject::tr("Could not connect to hushd configured in settings.\n\n"
% QObject::tr("Could not connect to hushd configured in settings.\n\n"
"Please set the host/port and user/password in the Edit->Settings menu.");
showError(explanation);
@ -454,7 +489,7 @@ void ConnectionLoader::doManualConnect() {
void ConnectionLoader::doRPCSetConnection(Connection* conn) {
rpc->setEHushd(ehushd);
rpc->setConnection(conn);
d->accept();
delete this;
@ -462,7 +497,7 @@ void ConnectionLoader::doRPCSetConnection(Connection* conn) {
Connection* ConnectionLoader::makeConnection(std::shared_ptr<ConnectionConfig> config) {
QNetworkAccessManager* client = new QNetworkAccessManager(main);
QUrl myurl;
myurl.setScheme("http");
myurl.setHost(config.get()->host);
@ -471,10 +506,10 @@ Connection* ConnectionLoader::makeConnection(std::shared_ptr<ConnectionConfig> c
QNetworkRequest* request = new QNetworkRequest();
request->setUrl(myurl);
request->setHeader(QNetworkRequest::ContentTypeHeader, "text/plain");
QString userpass = config.get()->rpcuser % ":" % config.get()->rpcpassword;
QString headerData = "Basic " + userpass.toLocal8Bit().toBase64();
request->setRawHeader("Authorization", headerData.toLocal8Bit());
request->setRawHeader("Authorization", headerData.toLocal8Bit());
return new Connection(main, client, request, config);
}
@ -484,7 +519,7 @@ void ConnectionLoader::refreshHushdState(Connection* connection, std::function<v
QJsonObject payload = {
{"jsonrpc", "1.0"},
{"id", "someid"},
{"id", "42"},
{"method", "getinfo"}
};
connection->doRPC(payload,
@ -495,20 +530,20 @@ void ConnectionLoader::refreshHushdState(Connection* connection, std::function<v
QTimer::singleShot(1000, [=]() { this->doRPCSetConnection(connection); });
},
[=] (QNetworkReply* reply, const QJsonValue &res) {
// Failed, see what it is.
// Failed, see what it is.
auto err = reply->error();
//qDebug() << err << res;
if (err == QNetworkReply::NetworkError::ConnectionRefusedError) {
if (err == QNetworkReply::NetworkError::ConnectionRefusedError) {
refused();
} else if (err == QNetworkReply::NetworkError::AuthenticationRequiredError) {
main->logger->write("Authentication failed");
QString explanation = QString() %
QString explanation = QString() %
QObject::tr("Authentication failed. The username / password you specified was "
"not accepted by hushd. Try changing it in the Edit->Settings menu");
this->showError(explanation);
} else if (err == QNetworkReply::NetworkError::InternalServerError &&
} else if (err == QNetworkReply::NetworkError::InternalServerError &&
!res.isNull()) {
// The server is loading, so just poll until it succeeds
QString status = res["error"].toObject()["message"].toString();
@ -532,9 +567,10 @@ void ConnectionLoader::refreshHushdState(Connection* connection, std::function<v
void ConnectionLoader::showInformation(QString info, QString detail) {
static int rescanCount = 0;
if (detail.toLower().startsWith("rescan")) {
qDebug() << "showInformation detail = " +detail.toLower();
rescanCount++;
}
if (rescanCount > 10) {
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->noConnection();
@ -665,8 +701,8 @@ bool ConnectionLoader::verifyParams() {
/**
* Try to automatically detect a HUSH3/HUSH3.conf file in the correct location and load parameters
*/
std::shared_ptr<ConnectionConfig> ConnectionLoader::autoDetectHushConf() {
*/
std::shared_ptr<ConnectionConfig> ConnectionLoader::autoDetectHushConf() {
auto confLocation = locateHushConfFile();
if (confLocation.isNull()) {
@ -688,7 +724,7 @@ std::shared_ptr<ConnectionConfig> ConnectionLoader::autoDetectHushConf() {
hushconf->usingHushConf = true;
hushconf->hushDir = QFileInfo(confLocation).absoluteDir().absolutePath();
hushconf->hushDaemon = false;
Settings::getInstance()->setUsingHushConf(confLocation);
while (!in.atEnd()) {
@ -732,21 +768,28 @@ std::shared_ptr<ConnectionConfig> ConnectionLoader::autoDetectHushConf() {
if (hushconf->port.isEmpty()) hushconf->port = "18031";
file.close();
// In addition to the HUSH3/HUSH3.conf file, also double check the params.
// Save to Qsettings
Settings::getInstance()->saveSettings(
hushconf->host,
hushconf->port,
hushconf->rpcuser,
hushconf->rpcpassword);
// In addition to the HUSH3/HUSH3.conf file, also double check the params.
return std::shared_ptr<ConnectionConfig>(hushconf);
}
// Load connection settings from the UI, which indicates an unknown, external hushd
std::shared_ptr<ConnectionConfig> ConnectionLoader::loadFromSettings() {
// Load from the QT Settings.
// Load from the QT Settings.
QSettings s;
auto host = s.value("connection/host").toString();
auto port = s.value("connection/port").toString();
auto username = s.value("connection/rpcuser").toString();
auto password = s.value("connection/rpcpassword").toString();
if (username.isEmpty() || password.isEmpty())
return nullptr;
@ -761,8 +804,8 @@ std::shared_ptr<ConnectionConfig> ConnectionLoader::loadFromSettings() {
/***********************************************************************************
* Connection Class
************************************************************************************/
Connection::Connection(MainWindow* m, QNetworkAccessManager* c, QNetworkRequest* r,
************************************************************************************/
Connection::Connection(MainWindow* m, QNetworkAccessManager* c, QNetworkRequest* r,
std::shared_ptr<ConnectionConfig> conf) {
this->restclient = c;
this->request = r;
@ -795,7 +838,7 @@ void Connection::doRPC(const QJsonValue& payload, const std::function<void(QJson
// Ignoring callback because shutdown in progress
return;
}
QJsonDocument jd_reply = QJsonDocument::fromJson(reply->readAll());
QJsonValue parsed;
@ -807,13 +850,13 @@ void Connection::doRPC(const QJsonValue& payload, const std::function<void(QJson
if (reply->error() != QNetworkReply::NoError) {
ne(reply, parsed);
return;
}
}
if (parsed.isNull()) {
ne(reply, "Unknown error");
}
cb(parsed["result"]);
cb(parsed["result"]);
});
}
@ -847,9 +890,97 @@ void Connection::showTxError(const QString& error) {
shown = false;
}
/*
QString ConnectionLoader::rescanProgress() {
#ifdef Q_OS_LINUX
auto debugLocation = QStandardPaths::locate(QStandardPaths::HomeLocation, ".hush/HUSH3/debug.log");
if(!QFile(debugLocation).exists()) {
// legacy location
debugLocation = QStandardPaths::locate(QStandardPaths::HomeLocation, ".komodo/HUSH3/debug.log");
}
#elif defined(Q_OS_DARWIN)
auto debugLocation = QStandardPaths::locate(QStandardPaths::HomeLocation, "Library/Application Support/Hush/HUSH3/debug.log");
if(!QFile(debugLocation).exists()) {
// legacy location
debugLocation = QStandardPaths::locate(QStandardPaths::HomeLocation, "Library/Application Support/Komodo/HUSH3/debug.log");
}
#else
auto debugLocation = QStandardPaths::locate(QStandardPaths::AppDataLocation, "../../Hush/HUSH3/debug.log");
if(!QFile(debugLocation).exists()) {
// legacy location
debugLocation = QStandardPaths::locate(QStandardPaths::AppDataLocation, "../../Komodo/HUSH3/debug.log");
}
#endif
if(QFile(debugLocation).exists()){
qDebug() << "Found debug.log at "+ debugLocation;
QFile file(debugLocation);
if (file.open(QIODevice::ReadOnly)){
// TODO: Handle this more efficiently
int lineNumber = -1;
QString line;
qint64 chars = 2056; // Change this - If too small, may only read junk data with no rescan info. Too large and progress will update with older rescan info until out of range and new match found.
qint64 fileSize = file.size();
qDebug() << "debug.log size = " << fileSize;
file.seek(fileSize - chars);
QTextStream textStream( &file );
QRegularExpression rxStart("Rescanning from height=(\\d*[.]?\\d+)");
QRegularExpression rxBlock("block ([0-9]+)");
QRegularExpression rxProgress("Progress=(\\d*[.]?\\d+)");
QRegularExpression rxDone("Done rescanning");
QJsonObject rescanProgress;
while (!textStream.atEnd()){
line = textStream.readLine();
lineNumber++;
QRegularExpressionMatch matchStart = rxStart.match(line);
QRegularExpressionMatch matchBlock = rxBlock.match(line);
QRegularExpressionMatch matchProgress = rxProgress.match(line);
QRegularExpressionMatch matchDone = rxDone.match(line);
QString start;
QString block;
QString progress;
QString done;
if (matchStart.hasMatch()){
start = matchStart.captured(0);
qDebug() << "Start Rescan = " +start;
return "Start";
}
if (matchBlock.hasMatch() && matchProgress.hasMatch()){
block = matchBlock.captured(0).replace("block ","");
progress = matchProgress.captured(0).replace("Progress=","");
double percent = progress.toDouble()*100;
QString percentStr = QString::number(percent);
qDebug() << "debug.log still rescanning - " << "Block = " + block + " " + progress;
return progress+" "+block;
}
// This is difficult to catch unless reading lots of log
if (matchDone.hasMatch()){
done = matchDone.captured(0);
qDebug() << "debug.log rescan done? " + done;
return "Stop";
}
}
file.close();
}
}
return "Stop";
}
*/
/**
* Prevent all future calls from going through
*/
*/
void Connection::shutdown() {
shutdownInProgress = true;
}

3
src/connection.h

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

32
src/connection.ui

@ -9,10 +9,28 @@
<rect>
<x>0</x>
<y>0</y>
<width>513</width>
<height>513</height>
<width>512</width>
<height>512</height>
</rect>
</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">
<string>SilentDragon</string>
</property>
@ -20,6 +38,9 @@
<bool>true</bool>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<property name="sizeConstraint">
<enum>QLayout::SetFixedSize</enum>
</property>
<property name="leftMargin">
<number>0</number>
</property>
@ -84,13 +105,6 @@
</item>
</layout>
</widget>
<customwidgets>
<customwidget>
<class>FilledIconLabel</class>
<extends>QLabel</extends>
<header>fillediconlabel.h</header>
</customwidget>
</customwidgets>
<resources/>
<connections/>
</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
#include "mainwindow.h"
#include "addressbook.h"
@ -8,11 +8,13 @@
#include "ui_mobileappconnector.h"
#include "ui_addressbook.h"
#include "ui_privkey.h"
#include "ui_qrcode.h"
#include "ui_viewkey.h"
#include "ui_about.h"
#include "ui_settings.h"
#include "ui_viewalladdresses.h"
#include "ui_validateaddress.h"
#include "ui_rescandialog.h"
#include "rpc.h"
#include "balancestablemodel.h"
#include "settings.h"
@ -33,7 +35,8 @@ MainWindow::MainWindow(QWidget *parent) :
theme_name = Settings::getInstance()->get_theme_name();
} catch (...)
{
theme_name = "default";
qDebug() << __func__ << ": exception!";
theme_name = "dark";
}
this->slot_change_theme(theme_name);
@ -112,6 +115,7 @@ MainWindow::MainWindow(QWidget *parent) :
// Initialize to the balances tab
ui->tabWidget->setCurrentIndex(0);
setupSendTab();
setupTransactionsTab();
setupReceiveTab();
@ -185,6 +189,119 @@ void MainWindow::doClose() {
closeEvent(nullptr);
}
// Called every time, when a menu entry of the language menu is called
void MainWindow::slotLanguageChanged(QString lang)
{
qDebug() << __func__ << ": lang=" << lang;
if(lang != "") {
// load the language
loadLanguage(lang);
QDialog settingsDialog(this);
qDebug() << __func__ << ": retranslating settingsDialog";
settings.retranslateUi(&settingsDialog);
}
}
void switchTranslator(QTranslator& translator, const QString& filename) {
qDebug() << __func__ << ": filename=" << filename;
// remove the old translator
qApp->removeTranslator(&translator);
// load the new translator
QString path = QApplication::applicationDirPath();
path.append("/res/");
qDebug() << __func__ << ": attempting to load " << path + filename;
if(translator.load(path + filename)) {
qApp->installTranslator(&translator);
} else {
qDebug() << __func__ << ": translation path does not exist! " << path + filename;
}
}
void MainWindow::loadLanguage(QString& rLanguage) {
qDebug() << __func__ << ": currLang=" << m_currLang << " rLanguage=" << rLanguage;
QString lang = rLanguage;
// this allows us to call this function with just a locale such as "zh"
if(lang.right(1) == ")") {
lang.chop(1); // remove trailing )
}
// remove everything up to and including the first (
lang = lang.remove(0, lang.indexOf("(") + 1);
// NOTE: language codes can be 2 or 3 letters
// https://www.loc.gov/standards/iso639-2/php/code_list.php
QString languageName;
if(m_currLang != lang) {
qDebug() << __func__ << ": changing language to lang=" << lang;
m_currLang = lang;
QLocale locale = QLocale(m_currLang);
qDebug() << __func__ << ": locale nativeLanguage=" << locale.nativeLanguageName();
// an invalid locale such as "zz" will give the C locale which has no native language name
if (locale.nativeLanguageName() == "") {
qDebug() << __func__ << ": detected invalid language in config file, defaulting to en";
locale = QLocale("en");
Settings::getInstance()->set_language("en");
m_currLang = "en";
lang = "en";
}
qDebug() << __func__ << ": locale=" << locale;
QLocale::setDefault(locale);
qDebug() << __func__ << ": setDefault locale=" << locale;
languageName = locale.nativeLanguageName(); //locale.language());
qDebug() << __func__ << ": languageName=" << languageName;
switchTranslator(m_translator, QString("silentdragon_%1.qm").arg(lang));
switchTranslator(m_translatorQt, QString("qt_%1.qm").arg(lang));
// TODO: this likely wont work for RTL languages like Arabic
auto first = QString(languageName.at(0)).toUpper();
languageName = first + languageName.right(languageName.size()-1);
if( lang == "en" ) {
languageName.replace("American ","");
}
ui->statusBar->showMessage(tr("Language changed to") + " " + languageName + " (" + lang + ")");
}
// write this language (the locale shortcode) out to config file
if (lang != "") {
// only write valid languages to config file
Settings::getInstance()->set_language(lang);
}
}
void MainWindow::changeEvent(QEvent* event) {
if(0 != event) {
switch(event->type()) {
// this event is sent if a translator is loaded
case QEvent::LanguageChange:
qDebug() << __func__ << ": QEvent::LanguageChange changeEvent";
ui->retranslateUi(this);
break;
// this event is sent, if the system, language changes
case QEvent::LocaleChange:
{
QString locale = QLocale::system().name();
locale.truncate(locale.lastIndexOf('_'));
qDebug() << __func__ << ": QEvent::LocaleChange changeEvent locale=" << locale;
loadLanguage(locale);
}
break;
default:
qDebug() << __func__ << ": " << event->type();
}
}
QMainWindow::changeEvent(event);
}
void MainWindow::closeEvent(QCloseEvent* event) {
QSettings s;
@ -265,7 +382,7 @@ void MainWindow::setupSettingsModal() {
// Set up File -> Settings action
QObject::connect(ui->actionSettings, &QAction::triggered, [=]() {
QDialog settingsDialog(this);
Ui_Settings settings;
//Ui_Settings settings;
settings.setupUi(&settingsDialog);
Settings::saveRestore(&settingsDialog);
@ -295,14 +412,22 @@ void MainWindow::setupSettingsModal() {
}
});
// Setup rescan button
QObject::connect(settings.rescanButton, &QPushButton::clicked, [=] () {
this->rescanButtonClicked(1);
});
int theme_index = settings.comboBoxTheme->findText(Settings::getInstance()->get_theme_name(), Qt::MatchExactly);
settings.comboBoxTheme->setCurrentIndex(theme_index);
QObject::connect(settings.comboBoxTheme, &QComboBox::currentTextChanged, [=] (QString theme_name) {
this->slot_change_theme(theme_name);
QMessageBox::information(this, tr("Theme Change"), tr("This change can take a few seconds."), QMessageBox::Ok);
// QMessageBox::information(this, tr("Theme Change"), tr("This change can take a few seconds."), QMessageBox::Ok);
// For some reason, changing language also triggers this
//ui->statusBar->showMessage(tr("Theme changed to ") + theme_name);
});
// Set local currency
QString ticker = Settings::getInstance()->get_currency_name();
int currency_index = settings.comboBoxCurrency->findText(ticker, Qt::MatchExactly);
@ -310,7 +435,8 @@ void MainWindow::setupSettingsModal() {
QObject::connect(settings.comboBoxCurrency, &QComboBox::currentTextChanged, [=] (QString ticker) {
this->slot_change_currency(ticker);
rpc->refresh(true);
QMessageBox::information(this, tr("Currency Change"), tr("This change can take a few seconds."), QMessageBox::Ok);
ui->statusBar->showMessage(tr("Currency changed to") + " " + ticker);
// QMessageBox::information(this, tr("Currency Change"), tr("This change can take a few seconds."), QMessageBox::Ok);
});
// Save sent transactions
@ -342,18 +468,24 @@ void MainWindow::setupSettingsModal() {
settings.lblTor->setToolTip(tooltip);
}
//Use Consolidation
// Wallet Size
if (rpc->getConnection() != nullptr) {
int size = 0;
qDebug() << __func__ << ": settings hushDir=" << rpc->getConnection()->config->hushDir;
QDir hushdir(rpc->getConnection()->config->hushDir);
QFile WalletSize(hushdir.filePath("wallet.dat"));
if (WalletSize.open(QIODevice::ReadOnly)){
size = WalletSize.size() / 1000000; //when file does open.
//QString size1 = QString::number(size) ;
settings.WalletSize->setText(QString::number(size));
WalletSize.close();
}
}
// Use Consolidation
bool isUsingConsolidation = false;
int size = 0;
QDir hushdir(rpc->getConnection()->config->hushDir);
QFile WalletSize(hushdir.filePath("wallet.dat"));
if (WalletSize.open(QIODevice::ReadOnly)){
size = WalletSize.size() / 1000000; //when file does open.
//QString size1 = QString::number(size) ;
settings.WalletSize->setText(QString::number(size));
WalletSize.close();
}
if (rpc->getConnection() != nullptr) {
isUsingConsolidation = !rpc->getConnection()->config->consolidation.isEmpty() == true;
}
@ -362,8 +494,7 @@ void MainWindow::setupSettingsModal() {
settings.chkConso->setEnabled(false);
}
//Use Deletetx
// Use Deletetx
bool isUsingDeletetx = false;
if (rpc->getConnection() != nullptr) {
isUsingDeletetx = !rpc->getConnection()->config->deletetx.isEmpty() == true;
@ -373,8 +504,7 @@ void MainWindow::setupSettingsModal() {
settings.chkDeletetx->setEnabled(false);
}
//Use Zindex
// Use Zindex
bool isUsingZindex = false;
if (rpc->getConnection() != nullptr) {
isUsingZindex = !rpc->getConnection()->config->zindex.isEmpty() == true;
@ -392,10 +522,10 @@ void MainWindow::setupSettingsModal() {
auto hushConfLocation = Settings::getInstance()->getHushdConfLocation();
if (!hushConfLocation.isEmpty()) {
settings.confMsg->setText("Settings are being read from \n" + hushConfLocation);
settings.hostname->setEnabled(false);
settings.port->setEnabled(false);
settings.rpcuser->setEnabled(false);
settings.rpcpassword->setEnabled(false);
settings.hostname->setReadOnly(true);
settings.port->setReadOnly(true);
settings.rpcuser->setReadOnly(true);
settings.rpcpassword->setReadOnly(true);
} else {
settings.confMsg->setText("No local HUSH3.conf found. Please configure connection manually.");
settings.hostname->setEnabled(true);
@ -418,13 +548,94 @@ void MainWindow::setupSettingsModal() {
settings.testnetTxExplorerUrl->setText(explorer.testnetTxExplorerUrl);
settings.testnetAddressExplorerUrl->setText(explorer.testnetAddressExplorerUrl);
// Connection tab by default
settings.tabWidget->setCurrentIndex(0);
// format systems language
QString defaultLocale = QLocale::system().name(); // e.g. "de_DE"
defaultLocale.truncate(defaultLocale.lastIndexOf('_')); // e.g. "de"
// Set the current language to the default system language
// TODO: this will need to change when we read/write selected language to config on disk
//m_currLang = defaultLocale;
//qDebug() << __func__ << ": changed m_currLang to " << defaultLocale;
m_currLang = Settings::getInstance()->get_language();
qDebug() << __func__ << ": got a currLang=" << m_currLang << " from config file";
//QString defaultLang = QLocale::languageToString(QLocale("en").language());
settings.comboBoxLanguage->addItem("English (en)");
m_langPath = QApplication::applicationDirPath();
m_langPath.append("/res");
qDebug() << __func__ <<": defaultLocale=" << defaultLocale << " m_langPath=" << m_langPath;;
QDir dir(m_langPath);
QStringList fileNames = dir.entryList(QStringList("silentdragon_*.qm"));
qDebug() << __func__ <<": found " << fileNames.size() << " translations";
// create language drop down dynamically
for (int i = 0; i < fileNames.size(); ++i) {
// get locale extracted by filename
QString locale;
locale = fileNames[i]; // "silentdragon_de.qm"
locale.truncate(locale.lastIndexOf('.')); // "silentdragon_de"
locale.remove(0, locale.lastIndexOf('_') + 1); // "de"
QString lang = QLocale(locale).nativeLanguageName(); //locale.language());
// TODO: this likely wont work for RTL languages like Arabic
// uppercase the first letter of all languages
auto first = QString(lang.at(0)).toUpper();
lang = first + lang.right(lang.size()-1);
//settings.comboBoxLanguage->addItem(action);
settings.comboBoxLanguage->addItem(lang + " (" + locale + ")");
qDebug() << __func__ << ": added lang=" << lang << " locale=" << locale << " defaultLocale=" << defaultLocale << " m_currLang=" << m_currLang;
qDebug() << __func__ << ": m_currLang=" << m_currLang << " ?= locale=" << locale;
//if (defaultLocale == locale) {
if (m_currLang == locale) {
settings.comboBoxLanguage->setCurrentIndex(i+1);
qDebug() << " set defaultLocale=" << locale << " to checked!!!";
}
}
settings.comboBoxLanguage->model()->sort(0,Qt::AscendingOrder);
qDebug() << __func__ <<": sorted translations";
//QString lang = QLocale::languageToString(QLocale(m_currLang).language());
QString lang = QLocale(m_currLang).nativeLanguageName(); //locale.language());
auto first = QString(lang.at(0)).toUpper();
lang = first + lang.right(lang.size()-1);
if (m_currLang == "en") {
// we have just 1 English translation
// en_US will render as "American English", so fix that
lang.replace("American ","");
}
qDebug() << __func__ << ": looking for " << lang + " (" + m_currLang + ")";
//qDebug() << __func__ << ": looking for " << m_currLang;
int lang_index = settings.comboBoxLanguage->findText(lang + " (" + m_currLang + ")", Qt::MatchExactly);
qDebug() << __func__ << ": setting comboBoxLanguage index to " << lang_index;
settings.comboBoxLanguage->setCurrentIndex(lang_index);
QObject::connect(settings.comboBoxLanguage, &QComboBox::currentTextChanged, [=] (QString lang) {
qDebug() << "comboBoxLanguage.currentTextChanged lang=" << lang;
this->slotLanguageChanged(lang);
//QMessageBox::information(this, tr("Language Changed"), tr("This change can take a few seconds."), QMessageBox::Ok);
});
// Options tab by default
settings.tabWidget->setCurrentIndex(1);
// Enable the troubleshooting options only if using embedded hushd
if (!rpc->isEmbedded()) {
settings.chkRescan->setEnabled(false);
settings.chkRescan->setToolTip(tr("You're using an external hushd. Please restart hushd with -rescan"));
//settings.chkRescan->setEnabled(false);
//settings.chkRescan->setToolTip(tr("You're using an external hushd. Please restart hushd with -rescan"));
settings.chkReindex->setEnabled(false);
settings.chkReindex->setToolTip(tr("You're using an external hushd. Please restart hushd with -reindex"));
@ -490,10 +701,12 @@ void MainWindow::setupSettingsModal() {
// Check to see if rescan or reindex have been enabled
bool showRestartInfo = false;
bool showReindexInfo = false;
/*
if (settings.chkRescan->isChecked()) {
Settings::addToHushConf(hushConfLocation, "rescan=1");
showRestartInfo = true;
}
}*/
if (settings.chkReindex->isChecked()) {
Settings::addToHushConf(hushConfLocation, "reindex=1");
@ -580,7 +793,7 @@ void MainWindow::telegram() {
}
void MainWindow::reportbug() {
QString url = "https://git.hush.is/hush/SilentDragon/issues/new";
QString url = "https://hush.is/tg_support";
QDesktopServices::openUrl(QUrl(url));
}
@ -928,6 +1141,20 @@ void MainWindow::getViewKey(QString addr) {
*isDialogAlive = false;
}
void MainWindow::getQRCode(QString addr) {
QDialog d(this);
Ui_QRCode qrui;
qrui.setupUi(&d);
// Display QR Code for address
qrui.qrcodeDisplayAddr->setQrcodeString(addr);
auto isDialogAlive = std::make_shared<bool>(true);
d.exec();
*isDialogAlive = false;
}
void MainWindow::exportKeys(QString addr) {
bool allKeys = addr.isEmpty() ? true : false;
@ -1132,6 +1359,11 @@ void MainWindow::setupBalancesTab() {
menu.addAction(tr("Get viewing key"), [=] () {
this->getViewKey(addr);
});
// QR Code for zaddrs only
menu.addAction(tr("Get QR code"), [=] () {
this->getQRCode(addr);
});
}
menu.addAction("Send from " % addr.left(40) % (addr.size() > 40 ? "..." : ""), [=]() {
@ -1206,21 +1438,23 @@ QString peer2ip(QString peer) {
void MainWindow::setupPeersTab() {
qDebug() << __FUNCTION__;
// Set up context menu on transactions tab
// Set up context menu on peers tab
ui->peersTable->setContextMenuPolicy(Qt::CustomContextMenu);
ui->bannedPeersTable->setContextMenuPolicy(Qt::CustomContextMenu);
// Table right click
QObject::connect(ui->bannedPeersTable, &QTableView::customContextMenuRequested, [=] (QPoint pos) {
QModelIndex index = ui->peersTable->indexAt(pos);
QModelIndex index = ui->bannedPeersTable->indexAt(pos);
if (index.row() < 0) return;
QMenu menu(this);
auto bannedPeerModel = dynamic_cast<BannedPeersTableModel *>(ui->bannedPeersTable->model());
QString addr = bannedPeerModel->getAddress(index.row());
qint64 asn = bannedPeerModel->getASN(index.row());
QString ip = peer2ip(addr);
QString subnet = bannedPeerModel->getSubnet(index.row());
QString as = QString::number(asn);
//qint64 banned_until = bannedPeerModel->getBannedUntil(index.row());
if(!ip.isEmpty()) {
@ -1240,6 +1474,46 @@ void MainWindow::setupPeersTab() {
});
}
if(!as.isEmpty()) {
menu.addAction(tr("View ASN on bgpview.io (3rd party service)"), [=] () {
QString url = "https://bgpview.io/asn/" + as;
qDebug() << "opening " << url;
QDesktopServices::openUrl(QUrl(url));
});
}
if(!ip.isEmpty()) {
menu.addAction(tr("Unban this peer"), [=] () {
ui->statusBar->showMessage(tr("Unbanning peer..."));
// Hide single banned peer
ui->bannedPeersTable->hideRow(index.row());
// Call setban
rpc->setban(ip, "remove", [=] (QJsonValue response){
qDebug() << "setban remove " << response;
ui->statusBar->showMessage(tr("Peer unbanned"), 3 * 1000);
rpc->refreshPeers();
});
});
menu.addAction(tr("Unban all peers"), [=] () {
ui->statusBar->showMessage(tr("Unbanning all peers..."));
// Hide all banned peers
for (int i=0; i < bannedPeerModel->rowCount(index); i++){
ui->bannedPeersTable->hideRow(i);
}
// Call clearBanned
rpc->clearBanned([=] (QJsonValue response){
qDebug() << "clearBanned " << response;
ui->statusBar->showMessage(tr("All peers unbanned"), 3 * 1000);
rpc->refreshPeers();
});
});
}
menu.exec(ui->bannedPeersTable->viewport()->mapToGlobal(pos));
});
@ -1295,6 +1569,20 @@ void MainWindow::setupPeersTab() {
});
}
menu.addAction(tr("Ban this peer"), [=] () {
ui->statusBar->showMessage(tr("Banning peer..."));
// Hide single peer
ui->peersTable->hideRow(index.row());
// Call setban
rpc->setban(ip, "add", [=] (QJsonValue response){
qDebug() << "setban add " << response;
ui->statusBar->showMessage(tr("Peer banned"), 3 * 1000);
rpc->refreshPeers();
});
});
menu.exec(ui->peersTable->viewport()->mapToGlobal(pos));
});
@ -1383,10 +1671,52 @@ void MainWindow::setupTransactionsTab() {
QString memo = txModel->getMemo(index.row());
if (!memo.isEmpty()) {
QMessageBox mb(QMessageBox::Information, tr("Memo"), memo, QMessageBox::Ok, this);
QMessageBox mb;
mb.setText(memo);
mb.setWindowTitle(tr("Memo"));
mb.setIcon(QMessageBox::Information);
QAbstractButton* buttonMemoReply = mb.addButton(tr("Reply"), QMessageBox::YesRole); mb.addButton(tr("OK"), QMessageBox::NoRole);
mb.setTextFormat(Qt::PlainText);
mb.setTextInteractionFlags(Qt::TextSelectableByMouse | Qt::TextSelectableByKeyboard);
mb.exec();
if (mb.clickedButton()==buttonMemoReply) {
qDebug() << "Reply clicked";
int lastPost = memo.trimmed().lastIndexOf(QRegExp("[\r\n]+"));
QString lastWord = memo.right(memo.length() - lastPost - 1);
if (Settings::getInstance()->isSaplingAddress(lastWord)) {
// First, cancel any pending stuff in the send tab by pretending to click
// the cancel button
cancelButton();
// Then set up the fields in the send tab
ui->Address1->setText(lastWord);
ui->Address1->setCursorPosition(0);
ui->Amount1->setText("0.0001");
// And switch to the send tab.
ui->tabWidget->setCurrentIndex(1);
qApp->processEvents();
// Click the memo button
this->memoButtonClicked(1, true);
}else{
// TODO: This memo has no reply to address. Show alert or don't show button to begin with.
QMessageBox mb;
mb.setText(tr("Sorry! This memo has no reply to address."));
mb.setWindowTitle(tr("Error"));
mb.setTextFormat(Qt::PlainText);
mb.setTextInteractionFlags(Qt::TextSelectableByMouse | Qt::TextSelectableByKeyboard);
mb.exec();
}
}
}
});
@ -1440,6 +1770,14 @@ void MainWindow::setupTransactionsTab() {
QGuiApplication::clipboard()->setText(url);
});
/* TODO: Decide whether to use this or not.
menu.addAction(tr("Look for new transactions"), [=] () {
QGuiApplication::clipboard()->setText(addr);
ui->statusBar->showMessage(tr("Looking for new transactions"), 3 * 1000);
rpc->watchTxStatus();
});
*/
// Payment Request
if (!memo.isEmpty() && memo.startsWith("hush:")) {
menu.addAction(tr("View Payment Request"), [=] () {
@ -1450,10 +1788,57 @@ void MainWindow::setupTransactionsTab() {
// View Memo
if (!memo.isEmpty()) {
menu.addAction(tr("View Memo"), [=] () {
/*
QMessageBox mb(QMessageBox::Information, tr("Memo"), memo, QMessageBox::Ok, this);
mb.setTextFormat(Qt::PlainText);
mb.setTextInteractionFlags(Qt::TextSelectableByMouse | Qt::TextSelectableByKeyboard);
mb.exec();*/
QMessageBox mb;
mb.setText(memo);
mb.setWindowTitle(tr("Memo"));
mb.setIcon(QMessageBox::Information);
QAbstractButton* buttonMemoReply = mb.addButton(tr("Reply"), QMessageBox::YesRole); mb.addButton(tr("OK"), QMessageBox::NoRole);
mb.setTextFormat(Qt::PlainText);
mb.setTextInteractionFlags(Qt::TextSelectableByMouse | Qt::TextSelectableByKeyboard);
mb.exec();
if (mb.clickedButton()==buttonMemoReply) {
qDebug() << "Reply clicked";
int lastPost = memo.trimmed().lastIndexOf(QRegExp("[\r\n]+"));
QString lastWord = memo.right(memo.length() - lastPost - 1);
if (Settings::getInstance()->isSaplingAddress(lastWord)) {
// First, cancel any pending stuff in the send tab by pretending to click
// the cancel button
cancelButton();
// Then set up the fields in the send tab
ui->Address1->setText(lastWord);
ui->Address1->setCursorPosition(0);
ui->Amount1->setText("0.0001");
// And switch to the send tab.
ui->tabWidget->setCurrentIndex(1);
qApp->processEvents();
// Click the memo button
this->memoButtonClicked(1, true);
}else{
// TODO: This memo has no reply to address. Show alert or don't show button to begin with.
QMessageBox mb;
mb.setText(tr("Sorry! This memo has no reply to address."));
mb.setWindowTitle(tr("Error"));
mb.setTextFormat(Qt::PlainText);
mb.setTextInteractionFlags(Qt::TextSelectableByMouse | Qt::TextSelectableByKeyboard);
mb.exec();
}
}
});
}
@ -1658,6 +2043,7 @@ void MainWindow::setupReceiveTab() {
ui->rcvBal->setText(Settings::getHUSHUSDDisplayFormat(rpc->getAllBalances()->value(addr)));
ui->txtReceive->setPlainText(addr);
ui->qrcodeDisplay->setQrcodeString(addr);
if (rpc->getUsedAddresses()->value(addr, false)) {
ui->rcvBal->setToolTip(tr("Address has been previously used"));
} else {
@ -1747,7 +2133,7 @@ void MainWindow::updateLabels() {
void MainWindow::slot_change_currency(const QString& currency_name)
{
qDebug() << "slot_change_currency"; //<< ": " << currency_name;
qDebug() << __func__ << ": " << currency_name;
Settings::getInstance()->set_currency_name(currency_name);
qDebug() << "Refreshing price stats after currency change";
rpc->refreshPrice();
@ -1762,9 +2148,17 @@ void MainWindow::slot_change_currency(const QString& currency_name)
}
}
void MainWindow::slot_change_theme(const QString& theme_name)
void MainWindow::slot_change_theme(QString& theme_name)
{
Settings::getInstance()->set_theme_name(theme_name);
qDebug() << __func__ << ": theme_name=" << theme_name;
if (theme_name == "dark" || theme_name == "default" || theme_name == "light" ||
theme_name == "midnight" || theme_name == "blue") {
Settings::getInstance()->set_theme_name(theme_name);
} else {
qDebug() << __func__ << ": ignoring invalid theme_name=" << theme_name;
Settings::getInstance()->set_theme_name("dark");
}
// Include css
QString saved_theme_name;
@ -1772,10 +2166,12 @@ void MainWindow::slot_change_theme(const QString& theme_name)
saved_theme_name = Settings::getInstance()->get_theme_name();
} catch (const std::exception& e) {
qDebug() << QString("Ignoring theme change Exception! : ");
saved_theme_name = "default";
saved_theme_name = "dark";
}
QFile qFile(":/css/res/css/" + saved_theme_name +".css");
QString filename = ":/css/res/css/" + saved_theme_name +".css";
QFile qFile(filename);
qDebug() << __func__ << ": attempting to open filename=" << filename;
if (qFile.open(QFile::ReadOnly))
{
QString styleSheet = QLatin1String(qFile.readAll());
@ -1785,6 +2181,46 @@ void MainWindow::slot_change_theme(const QString& theme_name)
}
void MainWindow::rescanButtonClicked(int number) {
qDebug() << "rescanButtonClicked" << number;
// Setup rescan dialog
Ui_RescanDialog rescanDialog;
QDialog dialog(this);
rescanDialog.setupUi(&dialog);
// TODO: Maybe set to current blockheight by default
rescanDialog.rescanBlockheight->setFocus();
// Add validator for block height
QRegExpValidator* heightValidator = new QRegExpValidator(QRegExp("\\d*"), this);
rescanDialog.rescanBlockheight->setValidator(heightValidator);
// Check if OK clicked
if (dialog.exec() == QDialog::Accepted) {
// Show message in status bar
ui->statusBar->showMessage(tr("Rescanning..."));
// Close settings
QWidget *modalWidget = QApplication::activeModalWidget();
if (modalWidget)
modalWidget->close();
// Get submitted rescan height
int rescanHeight = rescanDialog.rescanBlockheight->text().toInt();
qDebug() << "rescan height = " << rescanHeight;
// Call rescan RPC
rpc->rescan(rescanHeight, [=] (QJsonValue response){
qDebug() << "rescanning finished" << response;
ui->statusBar->showMessage(tr("Rescanning finished"));
});
}
}
MainWindow::~MainWindow()
{
delete ui;

26
src/mainwindow.h

@ -6,6 +6,7 @@
#include "precompiled.h"
#include "logger.h"
#include <memory>
#include "ui_settings.h"
// Forward declare to break circular dependency.
class RPC;
@ -64,6 +65,7 @@ public:
void updateFromCombo();
Ui::MainWindow* ui;
Ui_Settings settings;
QLabel* statusLabel;
QLabel* statusIcon;
@ -72,10 +74,21 @@ public:
Logger* logger;
void doClose();
// loads a language by the given language shortcode (e.g. de, en)
void loadLanguage(QString& rLanguage);
protected:
// this event is called, when a new translator is loaded or the system language is changed
void changeEvent(QEvent* event);
protected slots:
// this slot is called by the language menu actions
void slotLanguageChanged(QString lang);
private:
void closeEvent(QCloseEvent* event);
void checkRescan();
void setupSendTab();
void setupPeersTab();
void setupTransactionsTab();
@ -85,8 +98,9 @@ private:
void setupChatTab();
void setupMarketTab();
void slot_change_theme(const QString& themeName);
void slot_change_theme(QString& themeName);
void slot_change_currency(const QString& currencyName);
void setupTurnstileDialog();
void setupSettingsModal();
void setupStatusBar();
@ -113,6 +127,8 @@ private:
void memoButtonClicked(int number, bool includeReplyTo = false);
void fileUploadButtonClicked(int number);
void setMemoEnabled(int number, bool enabled);
void rescanButtonClicked(int number);
void donate();
void website();
@ -124,6 +140,7 @@ private:
void exportAllKeys();
void exportKeys(QString addr = "");
void getViewKey(QString addr = "");
void getQRCode(QString addr = "");
void backupWalletDat();
void exportTransactions();
@ -144,6 +161,13 @@ private:
QRegExpValidator* feesValidator = nullptr;
QMovie* loadingMovie;
// creates the language menu dynamically from the content of m_langPath
void createLanguageMenu(void);
QTranslator m_translator; // contains the translations for this application
QTranslator m_translatorQt; // contains the translations for qt
QString m_currLang; // contains the currently loaded language
QString m_langPath; // Path of language files
};
#endif // MAINWINDOW_H

248
src/mainwindow.ui

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

10
src/mobileappconnector.ui

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

40
src/peerstablemodel.cpp

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

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);
QPainter painter(&pm);
qrcodegen::QrCode qr = qrcodegen::QrCode::encodeText(str.toUtf8().constData(), qrcodegen::QrCode::Ecc::LOW);
qrcodegen::QrCode qr = qrcodegen::QrCode::encodeText(str.toUtf8().constData(), qrcodegen::QrCode::Ecc::HIGH);
const int s = qr.getSize()>0?qr.getSize():1;
const double w = pm.width();
const double h = pm.height();
@ -49,7 +49,11 @@ QPixmap QRCodeLabel::scaledPixmap() const {
}
}
}
// TODO: Maybe add logo if it doesn't break QR code - requires setting Ecc to HIGH
painter.drawPixmap((w/2)-50, (h/2)-50, 100, 100, QPixmap(":/img/res/logobig.gif"));
painter.end();
return pm;
}

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

16
src/rpc.h

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

84
src/scripts/make-deb.sh

@ -6,7 +6,7 @@
DEBLOG=deb.log.$$
if [ -z $QT_STATIC ]; then
echo "QT_STATIC is not set. Please set it to the base directory of a statically compiled Qt";
echo "QT_STATIC is not set; to set it use -static for QT configuration. Please set it to the base directory of a statically compiled Qt";
exit 1;
fi
@ -15,7 +15,7 @@ if [ -z $APP_VERSION ]; then echo "APP_VERSION is not set"; exit 1; fi
#if [ -z $PREV_VERSION ]; then echo "PREV_VERSION is not set"; exit 1; fi
if [ -z $HUSH_DIR ]; then
echo "HUSH_DIR is not set. Please set it to the base directory of hush3.git"
echo "HUSH_DIR is not set. Please set it to the src directory of hush3.git"
exit 1;
fi
@ -34,7 +34,12 @@ if [ ! -f $HUSH_DIR/hush-tx ]; then
exit 1;
fi
echo -n "Cleaning..............."
if [ ! -f $HUSH_DIR/hush-smart-chain ]; then
echo "Couldn't find hush-smart-chain in $HUSH_DIR . Please build hush-smart-chain."
exit 1;
fi
echo "Cleaning..............."
rm -rf bin/*
rm -rf artifacts/*
make distclean >/dev/null 2>&1
@ -43,7 +48,7 @@ echo "[OK]"
echo ""
echo "[Building $APP_VERSION on" `lsb_release -r`" logging to $DEBLOG ]"
echo -n "Translations............"
echo "Translations............"
QT_STATIC=$QT_STATIC bash src/scripts/dotranslations.sh >/dev/null
echo -n "Configuring............"
$QT_STATIC/bin/qmake silentdragon.pro -spec linux-clang CONFIG+=release > /dev/null
@ -58,7 +63,7 @@ echo "[OK]"
# Test for Qt
echo -n "Static link............"
echo "Static link............"
if [[ $(ldd silentdragon | grep -i "Qt") ]]; then
echo "FOUND QT; ABORT";
exit 1
@ -66,34 +71,39 @@ fi
echo "[OK]"
echo -n "Packaging.............."
echo "Packaging.............."
APP=SilentDragon-v$APP_VERSION
DIR=bin/$APP
mkdir $DIR > /dev/null
strip silentdragon
cp silentdragon $DIR > /dev/null
cp $HUSH_DIR/artifacts/hushd $DIR > /dev/null
cp $HUSH_DIR/artifacts/hush-cli $DIR > /dev/null
cp $HUSH_DIR/artifacts/hush-tx $DIR > /dev/null
cp README.md $DIR > /dev/null
cp LICENSE $DIR > /dev/null
#Organizing all bins & essentials to centralized folder for tar.gz
echo "Organizing binaries & essentials.............."
cp silentdragon $DIR > /dev/null
cp README.md $DIR > /dev/null
cp LICENSE $DIR > /dev/null
cd bin && tar czf $APP.tar.gz $DIR/ > /dev/null
cd ..
echo "Stripping silentdragon.............."
cd $DIR
strip silentdragon
cd ../..
mkdir artifacts >/dev/null 2>&1
cp $DIR.tar.gz ./artifacts/$APP-linux.tar.gz
echo "[OK]"
echo "Compressing files.............."
cd bin/
tar -czf $APP.tar.gz ./$APP > /dev/null
echo "Copy compressed file.............."
mkdir artifacts >/dev/null 2>&1
cp $APP.tar.gz ./artifacts/$APP-linux.tar.gz
echo -n "[OK]"
echo "Verify Compressed File.............."
if [ -f artifacts/$APP-linux.tar.gz ] ; then
echo -n "Package contents......."
# Test if the package is built OK
if tar tf "artifacts/$APP-linux.tar.gz" | wc -l | grep -q "9"; then
if tar -tf "artifacts/$APP-linux.tar.gz" | wc -l | grep -q "4"; then
echo "[OK]"
else
echo "[ERROR] Wrong number of files does not match 9"
echo "[ERROR] Wrong number of files does not match 11"
exit 1
fi
else
@ -101,25 +111,37 @@ else
exit 1
fi
echo -n "Building deb..........."
debdir=bin/deb/silentdragon-v$APP_VERSION
echo "Building package..........."
debdir=deb/silentdragon-v$APP_VERSION
mkdir -p $debdir > /dev/null
mkdir $debdir/DEBIAN
mkdir -p $debdir/usr/local/bin
cat src/scripts/control | sed "s/RELEASE_VERSION/$APP_VERSION/g" > $debdir/DEBIAN/control
mkdir -p $debdir/usr/lib
mkdir -p $debdir/usr/share/pixmaps/
cat ../src/scripts/control | sed "s/RELEASE_VERSION/$APP_VERSION/g" > $debdir/DEBIAN/control
cp silentdragon $debdir/usr/local/bin/
cp $HUSH_DIR/artifacts/hushd $debdir/usr/local/bin/hushd
echo "Copying silentdragon bin..........."
cp ../silentdragon $debdir/usr/local/bin/
mkdir -p $debdir/usr/share/pixmaps/
cp res/silentdragon.xpm $debdir/usr/share/pixmaps/
echo "Copying core libraries from silentdragon binary..........."
# copy the required shared libs to the target folder
# create directories if required
for lib in `ldd $debdir/usr/local/bin/silentdragon | cut -d'>' -f2 | awk '{print $1}'` ; do
if [ -f "$lib" ] ; then
cp -v "$lib" $debdir/usr/lib/
fi
done
echo "Copying SilentDragon icon..........."
cp ../res/silentdragon.xpm $debdir/usr/share/pixmaps/
mkdir -p $debdir/usr/share/applications
cp src/scripts/desktopentry $debdir/usr/share/applications/silentdragon.desktop
cp ../src/scripts/desktopentry $debdir/usr/share/applications/silentdragon.desktop
dpkg-deb --build $debdir >/dev/null
cp $debdir.deb artifacts/$DIR.deb
echo "[OK]"
dpkg-deb --build --root-owner-group $debdir >/dev/null
echo "[Success! $APP .deb has been created in $APP/bin/deb]"
exit 0

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
cp $HUSH_DIR/src/hushd silentdragon.app/Contents/MacOS/
cp $HUSH_DIR/src/hush-cli silentdragon.app/Contents/MacOS/
cp $HUSH_DIR/src/komodod silentdragon.app/Contents/MacOS/
cp $HUSH_DIR/src/komodo-cli silentdragon.app/Contents/MacOS/
cp $HUSH_DIR/sapling-output.params silentdragon.app/Contents/MacOS/
cp $HUSH_DIR/sapling-spend.params silentdragon.app/Contents/MacOS/
$QT_PATH/bin/macdeployqt silentdragon.app

46
src/scripts/mkrelease.sh

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

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() {
// Create the validator for send to/amount fields
amtValidator = new QRegExpValidator(QRegExp("[0-9]{0,8}\\.?[0-9]{0,8}"));
amtValidator = new QRegExpValidator(QRegExp("[0-9]{0,9}\\.?[0-9]{0,8}"));
ui->Amount1->setValidator(amtValidator);
@ -251,51 +251,69 @@ void MainWindow::addAddressSection() {
Amount1->setPlaceholderText(tr("Amount"));
Amount1->setObjectName(QString("Amount") % QString::number(itemNumber));
Amount1->setBaseSize(QSize(200, 0));
Amount1->setAlignment(Qt::AlignRight);
Amount1->setMinimumWidth(200);
Amount1->setSizePolicy(QSizePolicy ::Preferred , QSizePolicy ::Preferred );
Amount1->setAlignment(Qt::AlignRight);
// Create the validator for send to/amount fields
Amount1->setValidator(amtValidator);
QObject::connect(Amount1, &QLineEdit::textChanged, [=] (auto text) {
this->amountChanged(itemNumber, text);
});
horizontalLayout_13->addWidget(Amount1);
auto AmtUSD1 = new QLabel(verticalGroupBox);
AmtUSD1->setObjectName(QString("AmtUSD") % QString::number(itemNumber));
AmtUSD1->setObjectName(QString("AmtUSD") % QString::number(itemNumber));
horizontalLayout_13->addWidget(AmtUSD1);
/* TODO: Fix so it updates amount on correct recipient row...or just remove. Added for UI consistency.
auto Max1 = new QCheckBox(verticalGroupBox);
Max1->setText(tr("Max Available"));
// Connect Max Available checkbox
QObject::connect(Max1, &QCheckBox::stateChanged, [=] () {
this->maxAmountChecked(Max1->checkState());
});
horizontalLayout_13->addWidget(Max1);
*/
auto horizontalSpacer_4 = new QSpacerItem(40, 20, QSizePolicy::Expanding, QSizePolicy::Minimum);
horizontalLayout_13->addItem(horizontalSpacer_4);
auto MemoBtn1 = new QPushButton(verticalGroupBox);
MemoBtn1->setObjectName(QString("MemoBtn") % QString::number(itemNumber));
MemoBtn1->setText(tr("Memo"));
// Connect Memo Clicked button
QObject::connect(MemoBtn1, &QPushButton::clicked, [=] () {
this->memoButtonClicked(itemNumber);
});
horizontalLayout_13->addWidget(MemoBtn1);
setMemoEnabled(itemNumber, false);
auto FileBtn = new QPushButton(verticalGroupBox);
FileBtn->setObjectName(QString("FileBtn") % QString::number(itemNumber));
FileBtn->setText(tr("File Upload"));
FileBtn->setText(tr("Upload File"));
// Connect File Upload button
QObject::connect(FileBtn, &QPushButton::clicked, [=] () {
this->fileUploadButtonClicked(itemNumber);
});
horizontalLayout_13->addWidget(FileBtn);
sendAddressLayout->addLayout(horizontalLayout_13);
auto horizontalLayout_20 = new QHBoxLayout();
horizontalLayout_20->setSpacing(6);
horizontalLayout_20->setAlignment(Qt::AlignTop);
auto MemoTxt1 = new QLabel(verticalGroupBox);
MemoTxt1->setObjectName(QString("MemoTxt") % QString::number(itemNumber));
MemoTxt1->setSizePolicy(QSizePolicy ::MinimumExpanding , QSizePolicy ::MinimumExpanding );
QFont font1 = Address1->font();
font1.setPointSize(font1.pointSize()-1);
MemoTxt1->setFont(font1);
MemoTxt1->setWordWrap(true);
sendAddressLayout->addWidget(MemoTxt1);
horizontalLayout_20->addWidget(MemoTxt1, 0, Qt::AlignTop);
auto MemoBtn1 = new QPushButton(verticalGroupBox);
MemoBtn1->setObjectName(QString("MemoBtn") % QString::number(itemNumber));
MemoBtn1->setText(tr("Memo"));
// Connect Memo Clicked button
QObject::connect(MemoBtn1, &QPushButton::clicked, [=] () {
this->memoButtonClicked(itemNumber);
});
horizontalLayout_20->addWidget(MemoBtn1, 0, Qt::AlignTop);
setMemoEnabled(itemNumber, false);
sendAddressLayout->addLayout(horizontalLayout_20);
ui->sendToLayout->insertWidget(itemNumber-1, verticalGroupBox);
@ -702,18 +720,11 @@ void MainWindow::sendButton() {
auto connD = new Ui_ConnectionDialog();
connD->setupUi(d);
QMovie *movie1 = new QMovie(":/img/res/silentdragon-animated.gif");;
QMovie *movie2 = new QMovie(":/img/res/silentdragon-animated-dark.gif");;
QMovie *movie1 = new QMovie(":/img/res/silentdragon-animated-dark.gif");;
auto theme = Settings::getInstance()->get_theme_name();
if (theme == "dark" || theme == "midnight") {
movie2->setScaledSize(QSize(512,512));
connD->topIcon->setMovie(movie2);
movie2->start();
} else {
movie1->setScaledSize(QSize(512,512));
connD->topIcon->setMovie(movie1);
movie1->start();
}
movie1->setScaledSize(QSize(512,512));
connD->topIcon->setMovie(movie1);
movie1->start();
//connD->topIcon->setBasePixmap(logo.scaled(256, 256, Qt::KeepAspectRatio, Qt::SmoothTransformation));
@ -729,7 +740,7 @@ void MainWindow::sendButton() {
qDebug() << "Computing opid: " << opid;
},
[=] (QString, QString txid) {
[=] (QString, QString txid) {
ui->statusBar->showMessage(Settings::txidStatusMessage + " " + txid);
connD->status->setText(tr("Done!"));
@ -744,7 +755,8 @@ void MainWindow::sendButton() {
// And switch to the balances tab
ui->tabWidget->setCurrentIndex(0);
});
// Force a UI update so we get the unconfirmed Tx
// Force a UI update so we get the unconfirmed Tx
rpc->refresh(true);
},
[=] (QString opid, QString errStr) {

3
src/senttxstore.cpp

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

31
src/settings.cpp

@ -40,9 +40,20 @@ Explorer Settings::getExplorer() {
auto txExplorerUrl = s.value("explorer/txExplorerUrl", explorer + "/tx/").toString();
auto addressExplorerUrl = s.value("explorer/addressExplorerUrl", explorer + "/address/").toString();
auto testnetTxExplorerUrl = s.value("explorer/testnetTxExplorerUrl").toString();
auto testnetAddressExplorerUrl = s.value("explorer/testnetAddressExplorerUrl").toString();
// Some users have the old malicious explorer URL saved in their config file, help them out
if (txExplorerUrl == "https://explorer.myhush.org/tx/") {
txExplorerUrl = explorer + "/tx/";
saveExplorer(txExplorerUrl, addressExplorerUrl, testnetTxExplorerUrl, testnetAddressExplorerUrl);
}
if (addressExplorerUrl == "https://explorer.myhush.org/address/") {
addressExplorerUrl = explorer + "/address/";
saveExplorer(txExplorerUrl, addressExplorerUrl, testnetTxExplorerUrl, testnetAddressExplorerUrl);
}
return Explorer{txExplorerUrl, addressExplorerUrl, testnetTxExplorerUrl, testnetAddressExplorerUrl};
}
@ -246,10 +257,13 @@ void Settings::setAllowCustomFees(bool allow) {
QString Settings::get_theme_name() {
// Load from the QT Settings.
return QSettings().value("options/theme_name", false).toString();
QString theme_name = QSettings().value("options/theme_name", false).toString();
//qDebug() << __func__ << ": theme_name=" << theme_name;
return theme_name;
}
void Settings::set_theme_name(QString theme_name) {
qDebug() << __func__ << ": settings theme_name=" << theme_name;
QSettings().setValue("options/theme_name", theme_name);
}
@ -351,6 +365,21 @@ void Settings::set_currency_name(QString currency_name) {
QSettings().setValue("options/currency_name", currency_name);
}
QString Settings::get_language() {
// use the default system language if none is set
QString locale = QLocale::system().name();
// remove country data, i.e. en_US => en
locale.truncate( locale.lastIndexOf("_"));
auto lang = QSettings().value("options/language", locale).toString();
qDebug() << __func__ << ": found lang=" << lang << " in config file";
return lang;
}
void Settings::set_language(QString lang) {
qDebug() << __func__ << ": setting lang=" << lang << " in config file";
QSettings().setValue("options/language", lang);
}
bool Settings::removeFromHushConf(QString confLocation, QString option) {
if (confLocation.isEmpty())

13
src/settings.h

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

89
src/settings.ui

@ -22,8 +22,8 @@
<property name="modal">
<bool>true</bool>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<layout class="QGridLayout" name="gridLayout_2">
<item row="0" column="0">
<widget class="QTabWidget" name="tabWidget">
<property name="currentIndex">
<number>3</number>
@ -177,6 +177,32 @@
</property>
</widget>
</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">
<widget class="QComboBox" name="comboBoxCurrency">
<property name="sizePolicy">
@ -412,27 +438,27 @@
</property>
<item>
<property name="text">
<string>default</string>
<string notr="true">default</string>
</property>
</item>
<item>
<property name="text">
<string>blue</string>
<string notr="true">blue</string>
</property>
</item>
<item>
<property name="text">
<string>light</string>
<string notr="true">light</string>
</property>
</item>
<item>
<property name="text">
<string>dark</string>
<string notr="true">dark</string>
</property>
</item>
<item>
<property name="text">
<string>midnight</string>
<string notr="true">midnight</string>
</property>
</item>
</widget>
@ -698,6 +724,9 @@
</widget>
</widget>
<widget class="QWidget" name="tab_3">
<property name="focusPolicy">
<enum>Qt::NoFocus</enum>
</property>
<attribute name="title">
<string>Troubleshooting</string>
</attribute>
@ -717,32 +746,19 @@
<widget class="QLabel" name="label_9">
<property name="geometry">
<rect>
<x>9</x>
<y>38</y>
<x>10</x>
<y>10</y>
<width>583</width>
<height>51</height>
</rect>
</property>
<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 name="wordWrap">
<bool>true</bool>
</property>
</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">
<property name="geometry">
<rect>
@ -963,10 +979,35 @@
<string>MB</string>
</property>
</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>
</item>
<item>
<item row="1" column="0">
<widget class="QDialogButtonBox" name="buttonBox">
<property name="orientation">
<enum>Qt::Horizontal</enum>

106
src/txtablemodel.cpp

@ -3,6 +3,7 @@
#include "txtablemodel.h"
#include "settings.h"
#include "rpc.h"
#include "guiconstants.h"
TxTableModel::TxTableModel(QObject *parent)
: QAbstractTableModel(parent) {
@ -56,7 +57,7 @@ bool TxTableModel::exportToCsv(QString fileName) const {
out << "\"" << headers[i] << "\",";
}
out << "\"Memo\"";
out << endl;
out << Qt::endl;
// Write out each row
for (int row = 0; row < modeldata->length(); row++) {
@ -65,14 +66,14 @@ bool TxTableModel::exportToCsv(QString fileName) const {
}
// Memo
out << "\"" << modeldata->at(row).memo << "\"";
out << endl;
out << Qt::endl;
}
file.close();
return true;
}
void TxTableModel::updateAllData() {
void TxTableModel::updateAllData() {
auto newmodeldata = new QList<TransactionItem>();
if (tTrans != nullptr) std::copy( tTrans->begin(), tTrans->end(), std::back_inserter(*newmodeldata));
@ -92,33 +93,61 @@ void TxTableModel::updateAllData() {
layoutChanged();
}
int TxTableModel::rowCount(const QModelIndex&) const
{
QImage TxTableModel::colorizeIcon(QIcon icon, QColor color) const{
QImage img(icon.pixmap(16, 16).toImage());
img = img.convertToFormat(QImage::Format_ARGB32);
for (int x = img.width(); x--; )
{
for (int y = img.height(); y--; )
{
const QRgb rgb = img.pixel(x, y);
img.setPixel(x, y, qRgba(color.red(), color.green(), color.blue(), qAlpha(rgb)));
}
}
return img;
}
int TxTableModel::rowCount(const QModelIndex&) const
{
if (modeldata == nullptr) return 0;
return modeldata->size();
}
}
int TxTableModel::columnCount(const QModelIndex&) const
{
int TxTableModel::columnCount(const QModelIndex&) const
{
return headers.size();
}
}
QVariant TxTableModel::data(const QModelIndex &index, int role) const
{
// Align column 4 (amount) right
// Get current theme name
QString theme_name = Settings::getInstance()->get_theme_name();
QBrush b;
QColor color;
if (theme_name == "dark" || theme_name == "midnight") {
color = COLOR_WHITE;
}else{
color = COLOR_BLACK;
}
// Align column 4 (amount) right
if (role == Qt::TextAlignmentRole && index.column() == 3) return QVariant(Qt::AlignRight | Qt::AlignVCenter);
if (role == Qt::ForegroundRole) {
if (modeldata->at(index.row()).confirmations == 0) {
QBrush b;
b.setColor(Qt::red);
b.setColor(COLOR_UNCONFIRMED_TX);
return b;
}
if (theme_name == "dark" || theme_name == "midnight") {
b.setColor(color);
return b;
}else{
b.setColor(color);
return b;
}
// Else, just return the default brush
QBrush b;
b.setColor(Qt::black);
return b;
}
@ -161,6 +190,9 @@ void TxTableModel::updateAllData() {
}
if (role == Qt::DecorationRole && index.column() == 0) {
//qDebug() << "TX Type = " + dat.type;
if (!dat.memo.isEmpty()) {
// If the memo is a Payment URI, then show a payment request icon
if (dat.memo.startsWith("hush:")) {
@ -172,7 +204,43 @@ void TxTableModel::updateAllData() {
return QVariant(icon.pixmap(16, 16));
}
} else {
// Empty pixmap to make it align
// TODO: Add appropriate icons for types of txs instead of empty pixmap
//qDebug() << "Type = " +getType(index.row()) + "Address = " +getAddr(index.row()) + "From Address = " +getFromAddr(index.row());
// Send
if(this->getType(index.row()) == "send"){
QImage image = colorizeIcon(QIcon(":/icons/res/tx_output.png"), color);
QIcon icon;
icon.addPixmap(QPixmap::fromImage(image));
return QVariant(icon.pixmap(16, 16));
}
// Send T->Z - Untested
if(this->getType(index.row()) == "send" && !this->getFromAddr(index.row()).startsWith("zs1")){
QImage image = colorizeIcon(QIcon(":/icons/res/lock_closed.png"), color);
QIcon icon;
icon.addPixmap(QPixmap::fromImage(image));
return QVariant(icon.pixmap(16, 16));
}
// Receive
if(this->getType(index.row()) == "receive"){
QImage image = colorizeIcon(QIcon(":/icons/res/tx_input.png"), color);
QIcon icon;
icon.addPixmap(QPixmap::fromImage(image));
return QVariant(icon.pixmap(16, 16));
}
// Mined
if(this->getType(index.row()) == "generate"){
QImage image = colorizeIcon(QIcon(":/icons/res/tx_mined.png"), color);
QIcon icon;
icon.addPixmap(QPixmap::fromImage(image));
return QVariant(icon.pixmap(16, 16));
}
// Empty pixmap to make it align (old behavior)
QPixmap p(16, 16);
p.fill(Qt::white);
return QVariant(p);
@ -216,6 +284,10 @@ QString TxTableModel::getAddr(int row) const {
return modeldata->at(row).address.trimmed();
}
QString TxTableModel::getFromAddr(int row) const {
return modeldata->at(row).fromAddr.trimmed();
}
qint64 TxTableModel::getDate(int row) const {
return modeldata->at(row).datetime;
}

6
src/txtablemodel.h

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

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

Loading…
Cancel
Save