diff --git a/.gitignore b/.gitignore index efe68c9..27aca3f 100644 --- a/.gitignore +++ b/.gitignore @@ -12,7 +12,7 @@ build-bytecoin-gui* *~ CMakeLists.txt.user* -*.pro.user +*.pro.user* # Mac specific .DS_Store diff --git a/README.md b/README.md index 835b3ae..dffac98 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # bytecoin-gui -[![Build Status](https://dev.azure.com/bcndev/bytecoin/_apis/build/status/bytecoin-desktop?branchName=releases/3.4.5)](https://dev.azure.com/bcndev/bytecoin/_build/latest?definitionId=2&branchName=releases/3.4.5) +[![Build Status](https://dev.azure.com/bcndev/bytecoin/_apis/build/status/bytecoin-desktop?branchName=releases/3.5.0)](https://dev.azure.com/bcndev/bytecoin/_build/latest?definitionId=2&branchName=releases/3.5.0) ## How to build binaries from source code diff --git a/ReleaseNotes.md b/ReleaseNotes.md index ea05f1e..1382ec7 100644 --- a/ReleaseNotes.md +++ b/ReleaseNotes.md @@ -1,5 +1,13 @@ ## Release Notes +### v3.5.0 (Beryl) + +- Made many visual improvements to the graphical elements of the app. +- Added a full-value support for multiple addresses. +- Implemented an integration with the official Bytecoin explorer (it's opened when clicking either transaction/block hashes or block heights). +- Fixed bugs. +- Updated the Bytecoin daemons. + ### v3.4.5 (Amethyst) - Disabled asking passwords for wallets with empty passwords. diff --git a/src/addressbookframe.cpp b/src/addressbookframe.cpp index bce473e..4ad96fc 100644 --- a/src/addressbookframe.cpp +++ b/src/addressbookframe.cpp @@ -5,9 +5,9 @@ #include #include #include +#include #include "addressbookframe.h" -#include "addressbookdelegate.h" #include "addressbookmanager.h" #include "questiondialog.h" #include "addressbookmodel.h" @@ -25,17 +25,12 @@ AddressBookFrame::AddressBookFrame(QWidget* parent) , mainWindow_(nullptr) , m_sortedAddressBookModel(nullptr) , m_helperLabel(new QLabel(this)) - , m_addressBookDelegate(new AddressBookDelegate(this)) { m_ui->setupUi(this); QPixmap helperPixmap(":images/add_address_helper"); m_helperLabel->setGeometry(helperPixmap.rect()); m_helperLabel->setPixmap(helperPixmap); // m_ui->m_addressBookView->setHoverIsVisible(true); - m_ui->m_addressBookView->setItemDelegateForColumn(AddressBookModel::COLUMN_ACTION, m_addressBookDelegate); - connect(m_addressBookDelegate, &AddressBookDelegate::sendToSignal, this, &AddressBookFrame::sendToSignal); - connect(m_addressBookDelegate, &AddressBookDelegate::editSignal, this, &AddressBookFrame::editClicked); - connect(m_addressBookDelegate, &AddressBookDelegate::deleteSignal, this, &AddressBookFrame::deleteClicked); // connect(this, &AddressBookFrame::sendToSignal, this, &AddressBookFrame::sendToClicked); } @@ -59,20 +54,11 @@ void AddressBookFrame::setSortedAddressBookModel(QAbstractItemModel* _model) { // m_ui->m_addressBookView->setItemDelegateForColumn(AddressBookModel::COLUMN_ADDRESS, new RightAlignmentColumnDelegate(false, this)); m_ui->m_addressBookView->header()->setSectionResizeMode(AddressBookModel::COLUMN_LABEL, QHeaderView::Fixed); m_ui->m_addressBookView->header()->setSectionResizeMode(AddressBookModel::COLUMN_ADDRESS, QHeaderView::Stretch); -// m_ui->m_addressBookView->header()->setSectionResizeMode(AddressBookModel::COLUMN_DONATION, QHeaderView::Fixed); - m_ui->m_addressBookView->header()->setSectionResizeMode(AddressBookModel::COLUMN_ACTION, QHeaderView::Fixed); m_ui->m_addressBookView->header()->setResizeContentsPrecision(-1); m_ui->m_addressBookView->header()->resizeSection(AddressBookModel::COLUMN_LABEL, 250); -// m_ui->m_addressBookView->header()->resizeSection(AddressBookModel::COLUMN_DONATION, 90); - m_ui->m_addressBookView->header()->resizeSection(AddressBookModel::COLUMN_ACTION, 40); connect(m_sortedAddressBookModel, &QAbstractItemModel::rowsInserted, this, &AddressBookFrame::rowsInserted); connect(m_sortedAddressBookModel, &QAbstractItemModel::rowsRemoved, this, &AddressBookFrame::rowsRemoved); - for (int i = 0; i < m_sortedAddressBookModel->rowCount(); ++i) { - QPersistentModelIndex index = m_sortedAddressBookModel->index(i, AddressBookModel::COLUMN_ACTION); - m_ui->m_addressBookView->openPersistentEditor(index); - } - if (m_sortedAddressBookModel->rowCount() > 0) { m_helperLabel->hide(); } @@ -89,12 +75,7 @@ void AddressBookFrame::resizeEvent(QResizeEvent* /*_event*/) { m_helperLabel->raise(); } -void AddressBookFrame::rowsInserted(const QModelIndex& /*_parent*/, int _first, int _last) { - for (int i = _first; i <= _last; ++i) { - QPersistentModelIndex index = m_sortedAddressBookModel->index(i, AddressBookModel::COLUMN_ACTION); - m_ui->m_addressBookView->openPersistentEditor(index); - } - +void AddressBookFrame::rowsInserted(const QModelIndex& /*_parent*/, int /*_first*/, int /*_last*/) { m_helperLabel->hide(); } diff --git a/src/addressbookframe.h b/src/addressbookframe.h index 0341c2e..df19885 100644 --- a/src/addressbookframe.h +++ b/src/addressbookframe.h @@ -41,7 +41,6 @@ class AddressBookFrame : public QFrame MainWindow* mainWindow_; QAbstractItemModel* m_sortedAddressBookModel; QLabel* m_helperLabel; - AddressBookDelegate* m_addressBookDelegate; void rowsInserted(const QModelIndex& _parent, int _first, int _last); void rowsRemoved(const QModelIndex& _parent, int _first, int _last); diff --git a/src/addressbookframe.ui b/src/addressbookframe.ui index 23ffb3d..b778fcd 100644 --- a/src/addressbookframe.ui +++ b/src/addressbookframe.ui @@ -46,26 +46,6 @@ 6 - - - - Contacts - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - diff --git a/src/addressbookmanager.cpp b/src/addressbookmanager.cpp index 7c9009d..2649110 100644 --- a/src/addressbookmanager.cpp +++ b/src/addressbookmanager.cpp @@ -21,7 +21,7 @@ constexpr const char ADDRESS_ITEM_ADDRESS_TAG_NAME[] = "address"; } AddressBookManager::AddressBookManager(QObject* _parent) - : QObject(_parent) + : IAddressBookManager(_parent) { const QString jsonFile = Settings::instance().getDefaultWorkDir().absoluteFilePath("address_book.json"); @@ -157,4 +157,124 @@ void AddressBookManager::buildIndexes() { } } +MyAddressesManager::MyAddressesManager(QObject* parent) + : IAddressBookManager(parent) + , firstTime_(true) +{} + +MyAddressesManager::~MyAddressesManager() +{} + +void MyAddressesManager::connectedToWalletd() +{ + WalletLogger::debug(tr("[WalletAddressBook] Connected to walletd.")); + firstTime_ = true; + addressBook_.clear(); + requestAddresses(); +} + +void MyAddressesManager::disconnectedFromWalletd() +{ + emit addressBookClosedSignal(); +} + +void MyAddressesManager::requestAddresses(quint32 index, quint32 count) +{ + emit getWalletRecordsSignal(RpcApi::GetWalletRecords::Request{false, false, index, count}); +} + +void MyAddressesManager::createAddress(const QString& label) +{ + emit createAddressSignal(label); +} + +void MyAddressesManager::setAddressLabel(const QString& address, const QString& label) +{ + emit setAddressLabelSignal(RpcApi::SetAddressLabel::Request{address, label}); +} + +void MyAddressesManager::walletRecordsReceived(const RpcApi::WalletRecords& records) +{ + WalletLogger::debug(tr("[WalletAddressBook] Wallet records received.")); + for (const RpcApi::WalletRecord& rec: records.records) + { + addressBook_ << AddressItem{rec.label, rec.address}; + const AddressIndex index = addressBook_.size() - 1; + addressIndexes_[rec.address] = index; + WalletLogger::debug(tr("[WalletAddressBook] Wallet record indexed.")); + if (!firstTime_) + emit addressAddedSignal(index); + } + + if (firstTime_) + { + firstTime_ = false; + emit addressBookOpenedSignal(); + } +} + +void MyAddressesManager::addressLabelSetReceived(const QString& address, const QString& label) +{ + const AddressIndex index = findAddressByAddress(address); + if (index == INVALID_ADDRESS_INDEX) + { + WalletLogger::warning(tr("[WalletAddressBook] Failed to find address %1.").arg(address)); + return; + } + addressBook_[index].label = label; + WalletLogger::debug(tr("[WalletAddressBook] Label changed.")); + emit addressEditedSignal(index); +} + +AddressIndex MyAddressesManager::getAddressCount() const +{ + return addressBook_.size(); +} + +AddressItem MyAddressesManager::getAddress(AddressIndex addressIndex) const +{ + Q_ASSERT(addressIndex < getAddressCount()); + return addressBook_[addressIndex]; +} + +void MyAddressesManager::addAddress(const QString& label, const QString& address) +{ + Q_ASSERT(address.isEmpty()); + createAddress(label); +} + +void MyAddressesManager::addressCreatedReceived(const RpcApi::CreatedAddresses& addrs) +{ + Q_ASSERT(addrs.addresses.size() == 1); + WalletLogger::debug(tr("[WalletAddressBook] New address created.")); + requestAddresses(addressBook_.size(), 1); +} + +void MyAddressesManager::editAddress(AddressIndex addressIndex, const QString& label, const QString& address) +{ + Q_ASSERT(addressIndex < getAddressCount()); + Q_ASSERT(address.isEmpty()); + setAddressLabel(addressBook_[addressIndex].address, label); +} + +void MyAddressesManager::removeAddress(AddressIndex /*addressIndex*/) +{ + Q_ASSERT(false); // addresses cannot be removed from wallet +} + +//void MyAddressesManager::buildIndexes() +//{ +// for (int i = 0, size = addressBook_.size(); i < size; ++i) +// { +// const AddressItem& item = addressBook_[i]; +// addressIndexes_[item.address] = i; +// } +//} + +AddressIndex MyAddressesManager::findAddressByAddress(const QString& address) const +{ + return addressIndexes_.value(address, INVALID_ADDRESS_INDEX); +} + + } diff --git a/src/addressbookmanager.h b/src/addressbookmanager.h index e14fd5c..644a4cb 100644 --- a/src/addressbookmanager.h +++ b/src/addressbookmanager.h @@ -6,6 +6,7 @@ #include #include #include +#include "rpcapi.h" namespace WalletGUI { @@ -19,23 +20,51 @@ struct AddressItem QString address; }; -class AddressBookManager : public QObject +class IAddressBookManager : public QObject { - Q_OBJECT - Q_DISABLE_COPY(AddressBookManager) + Q_OBJECT + Q_DISABLE_COPY(IAddressBookManager) + +public: + IAddressBookManager(QObject* _parent) + : QObject(_parent) {} + virtual ~IAddressBookManager() {} + + virtual AddressIndex getAddressCount() const = 0; + virtual AddressItem getAddress(AddressIndex _addressIndex) const = 0; +// virtual AddressIndex findAddressByAddress(const QString& _address) const = 0; +// virtual AddressIndex findAddressByLabel(const QString& _label) const = 0; +// virtual AddressIndex findAddress(const QString& _label, const QString& _address) const = 0; + virtual void addAddress(const QString& _label, const QString& _address) = 0; + virtual void editAddress(AddressIndex _addressIndex, const QString& _label, const QString& _address) = 0; + virtual void removeAddress(AddressIndex _addressIndex) = 0; + +signals: + void addressBookOpenedSignal(); + void addressBookClosedSignal(); + void addressAddedSignal(AddressIndex _addressIndex); + void addressEditedSignal(AddressIndex _addressIndex); +// void addressRemovedSignal(AddressIndex _addressIndex); + void beginRemoveAddressSignal(AddressIndex _addressIndex); + void endRemoveAddressSignal(); +}; + +class AddressBookManager : public IAddressBookManager +{ + Q_OBJECT public: AddressBookManager(QObject* _parent); - virtual ~AddressBookManager(); + virtual ~AddressBookManager() override; - AddressIndex getAddressCount() const; - AddressItem getAddress(AddressIndex _addressIndex) const; - AddressIndex findAddressByAddress(const QString& _address) const; - AddressIndex findAddressByLabel(const QString& _label) const; - AddressIndex findAddress(const QString& _label, const QString& _address) const; - void addAddress(const QString& _label, const QString& _address); - void editAddress(AddressIndex _addressIndex, const QString& _label, const QString& _address); - void removeAddress(AddressIndex _addressIndex); + virtual AddressIndex getAddressCount() const override; + virtual AddressItem getAddress(AddressIndex _addressIndex) const override; + /*virtual*/ AddressIndex findAddressByAddress(const QString& _address) const /*override*/; + /*virtual*/ AddressIndex findAddressByLabel(const QString& _label) const /*override*/; + /*virtual*/ AddressIndex findAddress(const QString& _label, const QString& _address) const /*override*/; + virtual void addAddress(const QString& _label, const QString& _address) override; + virtual void editAddress(AddressIndex _addressIndex, const QString& _label, const QString& _address) override; + virtual void removeAddress(AddressIndex _addressIndex) override; private: QScopedPointer addressBook_; @@ -44,14 +73,58 @@ class AddressBookManager : public QObject void buildIndexes(); +//signals: +// void addressBookOpenedSignal(); +// void addressBookClosedSignal(); +// void addressAddedSignal(AddressIndex _addressIndex); +// void addressEditedSignal(AddressIndex _addressIndex); +//// void addressRemovedSignal(AddressIndex _addressIndex); +// void beginRemoveAddressSignal(AddressIndex _addressIndex); +// void endRemoveAddressSignal(); +}; + +class MyAddressesManager : public IAddressBookManager +{ + Q_OBJECT + +public: + MyAddressesManager(QObject* _parent); + virtual ~MyAddressesManager() override; + + virtual AddressIndex getAddressCount() const override; + virtual AddressItem getAddress(AddressIndex _addressIndex) const override; +// virtual AddressIndex findAddressByAddress(const QString& _address) const override; +// virtual AddressIndex findAddressByLabel(const QString& _label) const override; +// virtual AddressIndex findAddress(const QString& _label, const QString& _address) const override; + virtual void addAddress(const QString& _label, const QString& _address) override; + virtual void editAddress(AddressIndex _addressIndex, const QString& _label, const QString& _address) override; + virtual void removeAddress(AddressIndex _addressIndex) override; + + /*virtual*/ AddressIndex findAddressByAddress(const QString& _address) const /*override*/; + + void requestAddresses(quint32 index = 0, quint32 count = std::numeric_limits::max()); + void createAddress(const QString& label); + void setAddressLabel(const QString& address, const QString& label); + + void connectedToWalletd(); + void disconnectedFromWalletd(); + + void walletRecordsReceived(const RpcApi::WalletRecords& records); + void addressLabelSetReceived(const QString& address, const QString& label); + void addressCreatedReceived(const RpcApi::CreatedAddresses& addrs); + signals: - void addressBookOpenedSignal(); - void addressBookClosedSignal(); - void addressAddedSignal(AddressIndex _addressIndex); - void addressEditedSignal(AddressIndex _addressIndex); -// void addressRemovedSignal(AddressIndex _addressIndex); - void beginRemoveAddressSignal(AddressIndex _addressIndex); - void endRemoveAddressSignal(); + void getWalletRecordsSignal(const RpcApi::GetWalletRecords::Request& req); + void createAddressSignal(const QString& label); + void setAddressLabelSignal(const RpcApi::SetAddressLabel::Request& req); + +private: + QList addressBook_; + QHash addressIndexes_; + bool firstTime_; + +// void buildIndexes(); + }; } diff --git a/src/addressbookmodel.cpp b/src/addressbookmodel.cpp index ee60698..b20d648 100644 --- a/src/addressbookmodel.cpp +++ b/src/addressbookmodel.cpp @@ -17,17 +17,19 @@ namespace WalletGUI { -AddressBookModel::AddressBookModel(AddressBookManager* _addressBookManager, QObject* _parent) +AddressBookModel::AddressBookModel(IAddressBookManager* _addressBookManager, QObject* _parent) : QAbstractItemModel(_parent) , m_addressBookManager(_addressBookManager) , m_columnCount(AddressBookModel::staticMetaObject.enumerator(AddressBookModel::staticMetaObject.indexOfEnumerator("Columns")).keyCount()) , m_rowCount(0) { - connect(m_addressBookManager, &AddressBookManager::addressAddedSignal, this, &AddressBookModel::addressAdded); - connect(m_addressBookManager, &AddressBookManager::addressEditedSignal, this, &AddressBookModel::addressEdited); -// connect(m_addressBookManager, &AddressBookManager::addressRemovedSignal, this, &AddressBookModel::addressRemoved); - connect(m_addressBookManager, &AddressBookManager::beginRemoveAddressSignal, this, &AddressBookModel::beginRemoveAddress); - connect(m_addressBookManager, &AddressBookManager::endRemoveAddressSignal, this, &AddressBookModel::endRemoveAddress); + connect(m_addressBookManager, &IAddressBookManager::addressBookOpenedSignal, this, &AddressBookModel::addressBookOpened); + connect(m_addressBookManager, &IAddressBookManager::addressBookClosedSignal, this, &AddressBookModel::addressBookClosed); + connect(m_addressBookManager, &IAddressBookManager::addressAddedSignal, this, &AddressBookModel::addressAdded); + connect(m_addressBookManager, &IAddressBookManager::addressEditedSignal, this, &AddressBookModel::addressEdited); +// connect(m_addressBookManager, &IAddressBookManager::addressRemovedSignal, this, &AddressBookModel::addressRemoved); + connect(m_addressBookManager, &IAddressBookManager::beginRemoveAddressSignal, this, &AddressBookModel::beginRemoveAddress); + connect(m_addressBookManager, &IAddressBookManager::endRemoveAddressSignal, this, &AddressBookModel::endRemoveAddress); addressBookOpened(); } @@ -87,7 +89,7 @@ QVariant AddressBookModel::headerData(int _section, Qt::Orientation _orientation case COLUMN_LABEL: return static_cast(Qt::AlignLeft | Qt::AlignVCenter); case COLUMN_ADDRESS: - return static_cast(Qt::AlignRight | Qt::AlignVCenter); + return static_cast(Qt::AlignLeft | Qt::AlignVCenter); } break; diff --git a/src/addressbookmodel.h b/src/addressbookmodel.h index c84f93e..97c1dbe 100644 --- a/src/addressbookmodel.h +++ b/src/addressbookmodel.h @@ -8,7 +8,7 @@ namespace WalletGUI { -class AddressBookManager; +class IAddressBookManager; class AddressBookModel : public QAbstractItemModel { @@ -19,14 +19,14 @@ class AddressBookModel : public QAbstractItemModel public: enum Columns { - COLUMN_LABEL = 0, COLUMN_ADDRESS, /*COLUMN_DONATION,*/ COLUMN_ACTION + COLUMN_LABEL = 0, COLUMN_ADDRESS, /*COLUMN_DONATION,*/ /*COLUMN_ACTION*/ }; enum Roles { ROLE_LABEL = Qt::UserRole, ROLE_ADDRESS, /*ROLE_IS_DONATION_ADDRESS,*/ ROLE_COLUMN, ROLE_ROW }; - explicit AddressBookModel(AddressBookManager* _addressBookManager, QObject* _parent); + explicit AddressBookModel(IAddressBookManager* _addressBookManager, QObject* _parent); ~AddressBookModel(); int columnCount(const QModelIndex& _parent = QModelIndex()) const override; @@ -47,7 +47,7 @@ class AddressBookModel : public QAbstractItemModel Q_SLOT void endRemoveAddress(); private: - AddressBookManager* m_addressBookManager; + IAddressBookManager* m_addressBookManager; const int m_columnCount; quintptr m_rowCount; diff --git a/src/application.cpp b/src/application.cpp index 83fd745..d55b94e 100644 --- a/src/application.cpp +++ b/src/application.cpp @@ -82,7 +82,7 @@ void WalletApplication::loadFonts() QFontDatabase::addApplicationFont(":font/Semibold"); QFontDatabase::addApplicationFont(":font/SemiboldItalic"); QFont font; - font.setFamily("Open Sans"); + font.setFamily("Work Sans"); font.setStyleStrategy(QFont::PreferAntialias); QApplication::setFont(font); } @@ -114,6 +114,7 @@ bool WalletApplication::init() WalletLogger::info(tr("[Application] First run detected")); addressBookManager_ = new AddressBookManager(this); + myAddressesManager_ = new MyAddressesManager(this); m_miningManager = new MiningManager(this); QFile styleSheetFile(":style/qss"); @@ -121,7 +122,7 @@ bool WalletApplication::init() const QByteArray styleSheet = styleSheetFile.readAll(); styleSheetFile.close(); WalletLogger::info(tr("[Application] Initialized successfully")); - m_mainWindow = new MainWindow(walletModel_, styleSheet, m_miningManager, addressBookManager_, nullptr); + m_mainWindow = new MainWindow(walletModel_, styleSheet, m_miningManager, addressBookManager_, myAddressesManager_, nullptr); m_mainWindow->show(); connect(m_mainWindow, &MainWindow::createTxSignal, this, &WalletApplication::createTx); connect(m_mainWindow, &MainWindow::sendTxSignal, this, &WalletApplication::sendTx); @@ -163,6 +164,18 @@ void WalletApplication::subscribeToWalletd() { connect(walletModel_, &WalletModel::getTransfersSignal, walletd_, &RemoteWalletd::getTransfers); connect(walletModel_, &WalletModel::netChangedSignal, m_mainWindow, &MainWindow::netChanged); + connect(walletModel_, &WalletModel::statusUpdatedSignal, m_mainWindow, &MainWindow::statusChanged); + + connect(myAddressesManager_, &MyAddressesManager::getWalletRecordsSignal, walletd_, &RemoteWalletd::getWalletRecords); + connect(myAddressesManager_, &MyAddressesManager::createAddressSignal, walletd_, &RemoteWalletd::createAddress); + connect(myAddressesManager_, &MyAddressesManager::setAddressLabelSignal, walletd_, &RemoteWalletd::setAddressLabel); + + connect(walletd_, &RemoteWalletd::walletRecordsReceivedSignal, myAddressesManager_, &MyAddressesManager::walletRecordsReceived); + connect(walletd_, &RemoteWalletd::addressLabelSetReceivedSignal, myAddressesManager_, &MyAddressesManager::addressLabelSetReceived); + connect(walletd_, &RemoteWalletd::addressesCreatedReceivedSignal, myAddressesManager_, &MyAddressesManager::addressCreatedReceived); + connect(walletd_, &RemoteWalletd::networkErrorSignal, myAddressesManager_, &MyAddressesManager::disconnectedFromWalletd); + connect(walletd_, &RemoteWalletd::connectedSignal, myAddressesManager_, &MyAddressesManager::connectedToWalletd); + connect(walletd_, &RemoteWalletd::statusReceivedSignal, walletModel_, &WalletModel::statusReceived); connect(walletd_, &RemoteWalletd::transfersReceivedSignal, walletModel_, &WalletModel::transfersReceived); connect(walletd_, &RemoteWalletd::walletInfoReceivedSignal, walletModel_, &WalletModel::walletInfoReceived); diff --git a/src/application.h b/src/application.h index c7fc2c7..d55d2de 100644 --- a/src/application.h +++ b/src/application.h @@ -22,6 +22,7 @@ class SignalHandler; class MiningManager; class WalletModel; class AddressBookManager; +class MyAddressesManager; class CrashDialog; @@ -45,6 +46,7 @@ class WalletApplication: public QApplication MainWindow* m_mainWindow; MiningManager* m_miningManager; AddressBookManager* addressBookManager_; + MyAddressesManager* myAddressesManager_; RemoteWalletd* walletd_; WalletModel* walletModel_; FileDownloader* downloader_; diff --git a/src/balanceoverviewframe.cpp b/src/balanceoverviewframe.cpp index ec8f66c..0993d80 100644 --- a/src/balanceoverviewframe.cpp +++ b/src/balanceoverviewframe.cpp @@ -10,9 +10,22 @@ #include "ui_balanceoverviewframe.h" #include "walletmodel.h" #include "popup.h" +#include "common.h" namespace WalletGUI { +const char BALANCE_OVERVIEW_STYLE_SHEET_TEMPLATE[] = + "WalletGUI--BalanceOverviewFrame {" + "border: 1px solid #c4c4c4" + "}"; + +const char TEXT_LABEL_STYLE_SHEET_TEMPLATE[] = + "QLabel {" + "color: rgba(0,0,0,0.5);" + "}"; + +constexpr int UI_SCALE = 90; + BalanceOverviewFrame::BalanceOverviewFrame(QWidget *parent) : QFrame(parent) , ui(new Ui::BalanceFrame) @@ -25,6 +38,8 @@ BalanceOverviewFrame::BalanceOverviewFrame(QWidget *parent) { ui->setupUi(this); + scaleWidgetText(ui->m_titleLabel, UI_SCALE); + ui->m_overviewSpendableBalanceLabel->installEventFilter(this); ui->m_overviewLockedOrUnconfirmedBalanceLabel->installEventFilter(this); // ui->m_overviewSpendableDustBalanceLabel->installEventFilter(this); @@ -36,6 +51,11 @@ BalanceOverviewFrame::BalanceOverviewFrame(QWidget *parent) ui->m_overviewLockedOrUnconfirmedBalanceLabel->setFont(font); // ui->m_overviewSpendableDustBalanceLabel->setFont(font); ui->m_overviewTotalBalanceLabel->setFont(font); + + ui->m_overviewSpendableBalanceTextLabel->setStyleSheet(TEXT_LABEL_STYLE_SHEET_TEMPLATE); + ui->m_overviewLockedOrUnconfirmedBalanceTextLabel->setStyleSheet(TEXT_LABEL_STYLE_SHEET_TEMPLATE); + ui->m_overviewTotalBalanceTextLabel->setStyleSheet(TEXT_LABEL_STYLE_SHEET_TEMPLATE); + setStyleSheet(BALANCE_OVERVIEW_STYLE_SHEET_TEMPLATE); } BalanceOverviewFrame::~BalanceOverviewFrame() diff --git a/src/balanceoverviewframe.ui b/src/balanceoverviewframe.ui index e19c764..21a8149 100644 --- a/src/balanceoverviewframe.ui +++ b/src/balanceoverviewframe.ui @@ -7,7 +7,7 @@ 0 0 358 - 123 + 194 @@ -26,29 +26,32 @@ QFrame::Raised - - - - Qt::Vertical - - - QSizePolicy::Expanding + + 24 + + + 20 + + + 12 + + + + + Total balance - - - 20 - 0 - + + 0 - + - - + + Qt::Vertical - QSizePolicy::Fixed + QSizePolicy::Expanding @@ -58,18 +61,8 @@ - - - - Spendable - - - 0 - - - - - + + 0 @@ -90,18 +83,8 @@ - - - - BCN - - - 0 - - - - - + + Qt::Horizontal @@ -113,18 +96,8 @@ - - - - Locked or unconfirmed - - - 0 - - - - - + + 0 @@ -145,27 +118,41 @@ - - - - BCN - - - 0 - - - - - - - Total balance - - - 0 - - - - + + + + 16 + + + + + + + + :/icons/balance + + + false + + + + + + + + 75 + true + false + + + + BALANCE DETAILS + + + + + + @@ -187,68 +174,42 @@ - - + + - BCN + Spendable + + + 0 - - - - Qt::Vertical - - - QSizePolicy::Expanding + + + + Locked or unconfirmed - - - 20 - 0 - + + 0 - + - - + + - Qt::Horizontal + Qt::Vertical QSizePolicy::Fixed - - - 10 - 20 - - - - - - - - Qt::Horizontal - 20 - 20 + 0 - - - - - - - :/icons/total_balance - - - diff --git a/src/bytecoin-gui.pro b/src/bytecoin-gui.pro index c26b14a..6e94c15 100644 --- a/src/bytecoin-gui.pro +++ b/src/bytecoin-gui.pro @@ -15,12 +15,13 @@ TEMPLATE = app macx: QMAKE_MACOSX_DEPLOYMENT_TARGET = 10.11 macx: ICON = images/bytecoin.icns win32: RC_ICONS = images/bytecoin.ico -win32: VERSION = 3.19.6.11 +win32: VERSION = 3.19.7.4 #QMAKE_CXXFLAGS += -fno-omit-frame-pointer -fsanitize=address,undefined #LIBS += -lasan -lubsan CONFIG += c++14 strict_c++ no-opengl +DEFINES += QT_FORCE_ASSERTS DESTDIR = $$PWD/../bin @@ -93,7 +94,6 @@ SOURCES += main.cpp\ addressbookmodel.cpp \ addressbooksortedmodel.cpp \ newaddressdialog.cpp \ - addressbookdelegate.cpp \ addressbookmanager.cpp \ balanceoverviewframe.cpp \ miningoverviewframe.cpp \ @@ -113,7 +113,10 @@ SOURCES += main.cpp\ exportkeydialog.cpp \ filedownloader.cpp \ version.cpp \ - mnemonicdialog.cpp + mnemonicdialog.cpp \ + elidedlabel.cpp \ + myaddressesframe.cpp \ + newmyaddressdialog.cpp HEADERS += mainwindow.h \ signalhandler.h \ @@ -156,7 +159,6 @@ HEADERS += mainwindow.h \ addressbookmodel.h \ addressbooksortedmodel.h \ newaddressdialog.h \ - addressbookdelegate.h \ addressbookmanager.h \ balanceoverviewframe.h \ miningoverviewframe.h \ @@ -176,7 +178,10 @@ HEADERS += mainwindow.h \ exportkeydialog.h \ version.h \ filedownloader.h \ - mnemonicdialog.h + mnemonicdialog.h \ + elidedlabel.h \ + myaddressesframe.h \ + newmyaddressdialog.h FORMS += mainwindow.ui \ overviewframe.ui \ @@ -202,7 +207,9 @@ FORMS += mainwindow.ui \ checkproofdialog.ui \ walletdparamsdialog.ui \ exportkeydialog.ui \ - mnemonicdialog.ui + mnemonicdialog.ui \ + myaddressesframe.ui \ + newmyaddressdialog.ui RESOURCES += \ resources.qrc \ diff --git a/src/checkproofdialog.cpp b/src/checkproofdialog.cpp index e197588..c520ac0 100644 --- a/src/checkproofdialog.cpp +++ b/src/checkproofdialog.cpp @@ -82,7 +82,7 @@ void CheckProofDialog::showCheckResult(const RpcApi::ProofCheck& result) { ui->resultLabel->setText(QString("%1").arg(tr("The proof is correct!"))); ui->messageLabel->setText(result.message); - ui->amountLabel->setText(formatAmount(result.amount) + " BCN"); + ui->amountLabel->setText(formatAmount(result.amount)); ui->addressLabel->setText(result.address); ui->txHashLabel->setText(result.transaction_hash); } diff --git a/src/checkproofdialog.ui b/src/checkproofdialog.ui index a9d4bd3..ed7bdf6 100644 --- a/src/checkproofdialog.ui +++ b/src/checkproofdialog.ui @@ -20,6 +20,9 @@ Check proof + + QLayout::SetMaximumSize + @@ -116,7 +119,13 @@ - + + + + 0 + 0 + + TextLabel @@ -130,7 +139,7 @@ - + TextLabel @@ -187,6 +196,13 @@ + + + WalletGUI::ElidedLabel + QLabel +
elidedlabel.h
+
+
diff --git a/src/common.cpp b/src/common.cpp index e5df728..926b56f 100644 --- a/src/common.cpp +++ b/src/common.cpp @@ -2,7 +2,10 @@ // Licensed under the GNU Lesser General Public License. See LICENSE for details. #include +#include #include +#include +#include #include "common.h" @@ -45,7 +48,7 @@ QString formatUnsignedAmount(quint64 amount, bool trim /*= true*/) result.insert(pos, ','); } - return result; + return result + ' ' + CURRENCY_TICKER; } QString formatAmount(qint64 amount) @@ -131,4 +134,59 @@ bool parseAmount(const QString& str, qint64& amount) return true; } +QString formatTimeDiff(quint64 timeDiff) +{ + static const QDateTime EPOCH_DATE_TIME = QDateTime::fromTime_t(0).toUTC(); + QDateTime dateTime = QDateTime::fromTime_t(timeDiff).toUTC(); + QString firstPart; + QString secondPart; + quint64 year = dateTime.date().year() - EPOCH_DATE_TIME.date().year(); + quint64 month = dateTime.date().month() - EPOCH_DATE_TIME.date().month(); + quint64 day = dateTime.date().day() - EPOCH_DATE_TIME.date().day(); + if (year > 0) + { + firstPart = QStringLiteral("%1 %2").arg(year).arg(year == 1 ? QObject::tr("year") : QObject::tr("years")); + secondPart = QStringLiteral("%1 %2").arg(month).arg(month == 1 ? QObject::tr("month") : QObject::tr("months")); + } + else if (month > 0) + { + firstPart = QStringLiteral("%1 %2").arg(month).arg(month == 1 ? QObject::tr("month") : QObject::tr("months")); + secondPart = QStringLiteral("%1 %2").arg(day).arg(day == 1 ? QObject::tr("day") : QObject::tr("days")); + } + else if (day > 0) + { + quint64 hour = dateTime.time().hour(); + firstPart = QStringLiteral("%1 %2").arg(day).arg(day == 1 ? QObject::tr("day") : QObject::tr("days")); + secondPart = QStringLiteral("%1 %2").arg(hour).arg(hour == 1 ? QObject::tr("hour") : QObject::tr("hours")); + } + else if (dateTime.time().hour() > 0) + { + quint64 hour = dateTime.time().hour(); + quint64 minute = dateTime.time().minute(); + firstPart = QStringLiteral("%1 %2").arg(hour).arg(hour == 1 ? QObject::tr("hour") : QObject::tr("hours")); + secondPart = QStringLiteral("%1 %2").arg(minute).arg(minute == 1 ? QObject::tr("minute") : QObject::tr("minutes")); + } + else if (dateTime.time().minute() > 0) + { + quint64 minute = dateTime.time().minute(); + firstPart = QStringLiteral("%1 %2").arg(minute).arg(minute == 1 ? QObject::tr("minute") : QObject::tr("minutes")); + } + else + { + firstPart = QStringLiteral("Less than 1 minute"); + } + + if (secondPart.isNull()) + return firstPart; + + return QStringLiteral("%1 %2").arg(firstPart).arg(secondPart); +} + +void scaleWidgetText(QWidget* w, int scale) +{ + QFont f = w->font(); + f.setPointSize(f.pointSize() * scale / 100); + w->setFont(f); +} + } diff --git a/src/common.h b/src/common.h index d008398..9beb1ef 100644 --- a/src/common.h +++ b/src/common.h @@ -8,6 +8,7 @@ #include class QUrl; +class QWidget; constexpr const char CURRENCY_TICKER[] = "BCN"; //constexpr const uint64_t MAXIMUM_UNSYNCED_BLOCKS_WHEN_SEND_AVAILABLE = 5; @@ -43,6 +44,10 @@ bool isIpOrHostName(const QString& string); QString rpcUrlToString(const QUrl& url); +QString formatTimeDiff(quint64 timeDiff); + +void scaleWidgetText(QWidget* w, int scale); + } #endif // COMMON_H diff --git a/src/elidedlabel.cpp b/src/elidedlabel.cpp new file mode 100644 index 0000000..f2ac1a8 --- /dev/null +++ b/src/elidedlabel.cpp @@ -0,0 +1,48 @@ +#include +#include + +#include "elidedlabel.h" + +namespace WalletGUI { + +void ElidedLabel::setElideMode(Qt::TextElideMode elideMode) +{ + m_elideMode = elideMode; + m_cachedText.clear(); + update(); +} + +void ElidedLabel::resizeEvent(QResizeEvent *e) +{ + QLabel::resizeEvent(e); + m_cachedText.clear(); +} + +void ElidedLabel::paintEvent(QPaintEvent *e) +{ + if (m_elideMode == Qt::ElideNone) + return QLabel::paintEvent(e); + + updateCachedTexts(); + QLabel::setText(m_cachedElidedText); + QLabel::paintEvent(e); + QLabel::setText(m_cachedText); +} + +void ElidedLabel::updateCachedTexts() +{ + const auto txt = text(); + if (m_cachedText == txt) + return; + m_cachedText = txt; + const QFontMetrics fm(fontMetrics()); + m_cachedElidedText = fm.elidedText(text(), m_elideMode, width(), Qt::TextShowMnemonic); + // make sure to show at least the first character + if (!m_cachedText.isEmpty()) + { + const QString showFirstCharacter = m_cachedText.at(0) + QStringLiteral("..."); + setMinimumWidth(fm.width(showFirstCharacter) + 1); + } +} + +} diff --git a/src/elidedlabel.h b/src/elidedlabel.h new file mode 100644 index 0000000..e33a5a8 --- /dev/null +++ b/src/elidedlabel.h @@ -0,0 +1,31 @@ +#ifndef ELLIDEDLABEL_H +#define ELLIDEDLABEL_H + +#include + +namespace WalletGUI { + +class ElidedLabel : public QLabel +{ + Q_OBJECT +public: + using QLabel::QLabel; + void setElideMode(Qt::TextElideMode elideMode); + Qt::TextElideMode elideMode() const { return m_elideMode; } + +protected: + void paintEvent(QPaintEvent *e) override; + void resizeEvent(QResizeEvent *e) override; + +private: + void updateCachedTexts(); + +private: + Qt::TextElideMode m_elideMode = Qt::ElideMiddle; + QString m_cachedElidedText; + QString m_cachedText; +}; + +} + +#endif // ELLIDEDLABEL_H diff --git a/src/font/WorkSans-Black.ttf b/src/font/WorkSans-Black.ttf new file mode 100644 index 0000000..9e4f86f Binary files /dev/null and b/src/font/WorkSans-Black.ttf differ diff --git a/src/font/WorkSans-BlackItalic.ttf b/src/font/WorkSans-BlackItalic.ttf new file mode 100644 index 0000000..02a769f Binary files /dev/null and b/src/font/WorkSans-BlackItalic.ttf differ diff --git a/src/font/WorkSans-Bold.ttf b/src/font/WorkSans-Bold.ttf new file mode 100644 index 0000000..d85eea8 Binary files /dev/null and b/src/font/WorkSans-Bold.ttf differ diff --git a/src/font/WorkSans-BoldItalic.ttf b/src/font/WorkSans-BoldItalic.ttf new file mode 100644 index 0000000..be08e1a Binary files /dev/null and b/src/font/WorkSans-BoldItalic.ttf differ diff --git a/src/font/WorkSans-ExtraBold.ttf b/src/font/WorkSans-ExtraBold.ttf new file mode 100644 index 0000000..234f0a0 Binary files /dev/null and b/src/font/WorkSans-ExtraBold.ttf differ diff --git a/src/font/WorkSans-ExtraBoldItalic.ttf b/src/font/WorkSans-ExtraBoldItalic.ttf new file mode 100644 index 0000000..84c9ab9 Binary files /dev/null and b/src/font/WorkSans-ExtraBoldItalic.ttf differ diff --git a/src/font/WorkSans-ExtraLight.ttf b/src/font/WorkSans-ExtraLight.ttf new file mode 100644 index 0000000..2ebe0bb Binary files /dev/null and b/src/font/WorkSans-ExtraLight.ttf differ diff --git a/src/font/WorkSans-ExtraLightItalic.ttf b/src/font/WorkSans-ExtraLightItalic.ttf new file mode 100644 index 0000000..39fe55d Binary files /dev/null and b/src/font/WorkSans-ExtraLightItalic.ttf differ diff --git a/src/font/WorkSans-Italic.ttf b/src/font/WorkSans-Italic.ttf new file mode 100644 index 0000000..496861e Binary files /dev/null and b/src/font/WorkSans-Italic.ttf differ diff --git a/src/font/WorkSans-Light.ttf b/src/font/WorkSans-Light.ttf new file mode 100644 index 0000000..a1e35fc Binary files /dev/null and b/src/font/WorkSans-Light.ttf differ diff --git a/src/font/WorkSans-LightItalic.ttf b/src/font/WorkSans-LightItalic.ttf new file mode 100644 index 0000000..e218c53 Binary files /dev/null and b/src/font/WorkSans-LightItalic.ttf differ diff --git a/src/font/WorkSans-Medium.ttf b/src/font/WorkSans-Medium.ttf new file mode 100644 index 0000000..69a596c Binary files /dev/null and b/src/font/WorkSans-Medium.ttf differ diff --git a/src/font/WorkSans-MediumItalic.ttf b/src/font/WorkSans-MediumItalic.ttf new file mode 100644 index 0000000..60c211f Binary files /dev/null and b/src/font/WorkSans-MediumItalic.ttf differ diff --git a/src/font/WorkSans-Regular.ttf b/src/font/WorkSans-Regular.ttf new file mode 100644 index 0000000..e99090c Binary files /dev/null and b/src/font/WorkSans-Regular.ttf differ diff --git a/src/font/WorkSans-SemiBold.ttf b/src/font/WorkSans-SemiBold.ttf new file mode 100644 index 0000000..20062ec Binary files /dev/null and b/src/font/WorkSans-SemiBold.ttf differ diff --git a/src/font/WorkSans-SemiBoldItalic.ttf b/src/font/WorkSans-SemiBoldItalic.ttf new file mode 100644 index 0000000..806dc8e Binary files /dev/null and b/src/font/WorkSans-SemiBoldItalic.ttf differ diff --git a/src/font/WorkSans-Thin.ttf b/src/font/WorkSans-Thin.ttf new file mode 100644 index 0000000..59ea722 Binary files /dev/null and b/src/font/WorkSans-Thin.ttf differ diff --git a/src/font/WorkSans-ThinItalic.ttf b/src/font/WorkSans-ThinItalic.ttf new file mode 100644 index 0000000..f69732b Binary files /dev/null and b/src/font/WorkSans-ThinItalic.ttf differ diff --git a/src/icons/balance_new.png b/src/icons/balance_new.png new file mode 100644 index 0000000..b5750a7 Binary files /dev/null and b/src/icons/balance_new.png differ diff --git a/src/icons/copy.png b/src/icons/copy.png new file mode 100644 index 0000000..43534ac Binary files /dev/null and b/src/icons/copy.png differ diff --git a/src/icons/logo_bl.png b/src/icons/logo_bl.png index e43ef07..89fb54a 100644 Binary files a/src/icons/logo_bl.png and b/src/icons/logo_bl.png differ diff --git a/src/icons/logo_stage.png b/src/icons/logo_stage.png index a951624..e838e98 100644 Binary files a/src/icons/logo_stage.png and b/src/icons/logo_stage.png differ diff --git a/src/icons/logo_test.png b/src/icons/logo_test.png index 9c073a6..0b6cb08 100644 Binary files a/src/icons/logo_test.png and b/src/icons/logo_test.png differ diff --git a/src/icons/mining.png b/src/icons/mining.png new file mode 100644 index 0000000..b471ca8 Binary files /dev/null and b/src/icons/mining.png differ diff --git a/src/icons/not_synced.png b/src/icons/not_synced.png new file mode 100644 index 0000000..9cc5d43 Binary files /dev/null and b/src/icons/not_synced.png differ diff --git a/src/icons/sync_lag.png b/src/icons/sync_lag.png new file mode 100644 index 0000000..bf7950f Binary files /dev/null and b/src/icons/sync_lag.png differ diff --git a/src/icons/synced.png b/src/icons/synced.png index 6a4c603..74acf1a 100644 Binary files a/src/icons/synced.png and b/src/icons/synced.png differ diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index ae44f55..bf60cec 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -48,8 +48,32 @@ const char REPORT_ISSUE_URL[] = "https://bytecoin.org/contact"; const char DOWNLOAD_URL[] = "https://github.com/bcndev/bytecoin-gui/releases"; const char BUTTON_STYLE_SHEET[] = - "QPushButton {border: none;}" - "QPushButton:checked {background-color: %1; color: #FFFFFF}"; + "QPushButton {" + "border: none;" + "border-left: 3px solid transparent ;" + "text-align: left;" + "padding-left: 15px;" + "padding-right: 15px;" + "padding-top: 12px;" + "padding-bottom:12px;" + "}" + "QPushButton:checked {" + "border-left: 3px solid %1;" + "border-top: 0px;" + "border-bottom: 0px;" + "border-right: 0px;" + "color: %1;" + "}"; + +const char TEXT_LABEL_STYLE_SHEET_TEMPLATE[] = + "QLabel {" + "color: rgba(0,0,0,0.5);" + "}"; + +constexpr int UI_SCALE = 110; // pct + +// "QPushButton {border: none;}" +// "QPushButton:checked {background-color: %1; color: #FFFFFF}"; const char WINDOW_MAIN_ICON_PATH[] = ":images/bytecoin"; const char WINDOW_STAGE_ICON_PATH[] = ":images/bytecoin_stage"; @@ -66,6 +90,7 @@ MainWindow::MainWindow( const QString& styleSheetTemplate, MiningManager* miningManager, AddressBookManager* addressBookManager, + IAddressBookManager* myAddressesManager, QWidget* parent) : QMainWindow(parent) , m_ui(new Ui::MainWindow) @@ -75,15 +100,32 @@ MainWindow::MainWindow( , m_syncMovie(new QMovie(QString(":icons/light/wallet-sync"), QByteArray(), this)) , m_miningManager(miningManager) , addressBookManager_(addressBookManager) + , myAddressesManager_(myAddressesManager) , copiedToolTip_(new CopiedToolTip(this)) , walletModel_(walletModel) , netColor_(MAIN_NET_COLOR) { m_ui->setupUi(this); + scaleWidgetText(m_ui->m_balanceLabelText , UI_SCALE); + scaleWidgetText(m_ui->m_balanceLabel , UI_SCALE); + scaleWidgetText(m_ui->m_addressLabelText , UI_SCALE); + scaleWidgetText(m_ui->m_addressLabel , UI_SCALE); + scaleWidgetText(m_ui->m_walletStatusLabel, UI_SCALE); + scaleWidgetText(m_ui->m_topBlockLabel , UI_SCALE); + scaleWidgetText(m_ui->m_overviewButton , UI_SCALE); + scaleWidgetText(m_ui->m_myAddressesButton, UI_SCALE); + scaleWidgetText(m_ui->m_sendButton , UI_SCALE); + scaleWidgetText(m_ui->m_addressBookButton, UI_SCALE); + scaleWidgetText(m_ui->m_miningButton , UI_SCALE); + scaleWidgetText(m_ui->m_logButton , UI_SCALE); + m_ui->m_updateLabel->setText(""); m_ui->m_updateLabel->hide(); - m_ui->m_viewOnlyLabel->setText(""); + m_ui->m_topBlockLabel->setText(""); + m_ui->m_topBlockLabel->hide(); + +// m_ui->m_viewOnlyLabel->setText(""); setGeometry(QStyle::alignedRect(Qt::LeftToRight, Qt::AlignCenter, this->size(), qApp->desktop()->availableGeometry())); // setWindowIcon(QIcon(WINDOW_MAIN_ICON_PATH)); clearTitle(); @@ -97,6 +139,7 @@ MainWindow::MainWindow( m_addressBookModel = new AddressBookModel(addressBookManager_, this); m_sortedAddressBookModel = new SortedAddressBookModel(m_addressBookModel, this); + m_myAddressesModel = new AddressBookModel(myAddressesManager_, this); m_minerModel = new MinerModel(m_miningManager, this); m_ui->m_overviewFrame->setMainWindow(this); @@ -104,21 +147,21 @@ MainWindow::MainWindow( m_ui->m_overviewFrame->hide(); m_ui->m_walletFrame->show(); - m_ui->m_addressLabel->installEventFilter(this); - m_ui->m_balanceLabel->installEventFilter(this); +// m_ui->m_addressLabel->installEventFilter(this); + m_ui->m_copyWalletAddressLabel->installEventFilter(this); - m_ui->m_balanceIconLabel->setPixmap(QPixmap(QString(":icons/light/balance"))); +// m_ui->m_balanceIconLabel->setPixmap(QPixmap(QString(":icons/light/balance"))); // m_ui->m_logoLabel->setPixmap(QPixmap(QString(LOGO_LABEL_MAIN_ICON_PATH))); m_ui->statusBar->setWalletModel(walletModel_); m_ui->m_syncProgress->setWalletModel(walletModel_); - m_ui->m_addressesCountLabel->hide(); - m_ui->m_creationTimestampLabel->hide(); +// m_ui->m_addressesCountLabel->hide(); +// m_ui->m_creationTimestampLabel->hide(); m_addressesMapper->setModel(walletModel_); m_addressesMapper->addMapping(m_ui->m_addressLabel, WalletModel::COLUMN_FIRST_ADDRESS, "text"); // m_addressesMapper->addMapping(m_ui->m_addressesCountLabel, WalletModel::COLUMN_TOTAL_ADDRESS_COUNT, "text"); // m_addressesMapper->addMapping(m_ui->m_creationTimestampLabel, WalletModel::COLUMN_WALLET_CREATION_TIMESTAMP, "text"); - m_addressesMapper->addMapping(m_ui->m_viewOnlyLabel, WalletModel::COLUMN_VIEW_ONLY, "text"); +// m_addressesMapper->addMapping(m_ui->m_viewOnlyLabel, WalletModel::COLUMN_VIEW_ONLY, "text"); m_addressesMapper->toFirst(); connect(walletModel_, &QAbstractItemModel::modelReset, m_addressesMapper, &QDataWidgetMapper::toFirst); @@ -127,9 +170,9 @@ MainWindow::MainWindow( m_balanceMapper->toFirst(); connect(walletModel_, &QAbstractItemModel::modelReset, m_balanceMapper, &QDataWidgetMapper::toFirst); - QFont font = m_ui->m_balanceLabel->font(); - font.setBold(true); - m_ui->m_balanceLabel->setFont(font); +// QFont font = m_ui->m_balanceLabel->font(); +// font.setBold(true); +// m_ui->m_balanceLabel->setFont(font); connect(m_ui->m_exitAction, &QAction::triggered, qApp, &QApplication::quit); @@ -152,6 +195,10 @@ MainWindow::MainWindow( m_ui->m_addressBookFrame->setAddressBookManager(addressBookManager_); m_ui->m_addressBookFrame->setSortedAddressBookModel(m_sortedAddressBookModel); m_ui->m_addressBookFrame->hide(); + m_ui->m_myAddressesFrame->setMainWindow(this); + m_ui->m_myAddressesFrame->setAddressBookManager(myAddressesManager_); + m_ui->m_myAddressesFrame->setSortedAddressBookModel(m_myAddressesModel); + m_ui->m_myAddressesFrame->hide(); m_ui->m_sendButton->setEnabled(false); m_ui->m_miningButton->setEnabled(false); @@ -163,6 +210,10 @@ MainWindow::MainWindow( m_ui->m_logButton->setChecked(true); m_ui->m_logFrame->show(); + + m_ui->m_balanceLabelText->setStyleSheet(TEXT_LABEL_STYLE_SHEET_TEMPLATE); + m_ui->m_addressLabelText->setStyleSheet(TEXT_LABEL_STYLE_SHEET_TEMPLATE); + m_ui->m_topBlockLabel->setStyleSheet(TEXT_LABEL_STYLE_SHEET_TEMPLATE); } MainWindow::~MainWindow() @@ -197,11 +248,50 @@ void MainWindow::netChanged(const QString& net) m_ui->m_overviewButton->setStyleSheet(QString{BUTTON_STYLE_SHEET}.arg(netColor_.name())); m_ui->m_sendButton->setStyleSheet(QString{BUTTON_STYLE_SHEET}.arg(netColor_.name())); m_ui->m_addressBookButton->setStyleSheet(QString{BUTTON_STYLE_SHEET}.arg(netColor_.name())); + m_ui->m_myAddressesButton->setStyleSheet(QString{BUTTON_STYLE_SHEET}.arg(netColor_.name())); m_ui->m_miningButton->setStyleSheet(QString{BUTTON_STYLE_SHEET}.arg(netColor_.name())); m_ui->m_logButton->setStyleSheet(QString{BUTTON_STYLE_SHEET}.arg(netColor_.name())); m_ui->m_syncProgress->setColor(netColor_); } +void MainWindow::statusChanged() +{ + const bool isThereAnyBlock = walletModel_->isThereAnyBlock(); + const QString formattedTimeDiff = isThereAnyBlock ? walletModel_->getFormattedTimeSinceLastBlock() : tr("unknown"); + const QString blockchainAge = isThereAnyBlock ? tr("%1 ago").arg(formattedTimeDiff) : tr("%1").arg(formattedTimeDiff); + m_ui->m_topBlockLabel->setText(tr("Top block: %1").arg(blockchainAge)); + + if (!walletModel_->isConnected()) + return; + + const WalletModel::SyncStatus status = walletModel_->getSyncStatus(); + switch(status) + { + case WalletModel::SyncStatus::NOT_SYNCED: + m_ui->m_walletStatusPicLabel->setPixmap(QPixmap{QString{":/icons/not_synced"}}); + m_ui->m_walletStatusLabel->setText(tr("Wallet out of sync")); + break; + case WalletModel::SyncStatus::LAGGED: + m_ui->m_walletStatusPicLabel->setPixmap(QPixmap{QString{":/icons/sync_lag"}}); + m_ui->m_walletStatusLabel->setText(tr("Wallet lagging")); + break; + case WalletModel::SyncStatus::SYNCED: + m_ui->m_walletStatusPicLabel->setPixmap(QPixmap{QString{":/icons/synced"}}); + m_ui->m_walletStatusLabel->setText(tr("Wallet synchronized")); + break; + } + + + m_ui->m_balanceLabelText->setVisible(true); + m_ui->m_balanceLabel->setVisible(true); + m_ui->m_addressLabelText->setVisible(true); + m_ui->m_addressLabel->setVisible(true); + m_ui->m_copyWalletAddressLabel->setVisible(true); + m_ui->m_walletStatusPicLabel->setVisible(true); + m_ui->m_walletStatusLabel->setVisible(true); + m_ui->m_topBlockLabel->setVisible(true); +} + QString MainWindow::getAddress() const { Q_ASSERT(walletModel_ != nullptr); @@ -248,10 +338,10 @@ void MainWindow::showSendConfirmation(const RpcApi::CreatedTx& tx) } const QString& amountStr = formatAmount(tf.amount); const QString& addressStr = tf.address; - msg.append(tr("%1 BCN to %2\n").arg(amountStr).arg(addressStr)); + msg.append(tr("%1 to %2\n").arg(amountStr).arg(addressStr)); } - msg.append(tr("Fee: %1 BCN\n").arg(formatAmount(tx.transaction.fee))); - msg.append(tr("Total send: %1 BCN").arg(formatAmount(-ourAmount))); + msg.append(tr("Fee: %1\n").arg(formatAmount(tx.transaction.fee))); + msg.append(tr("Total send: %1").arg(formatAmount(-ourAmount))); SendConfirmationDialog dlg( tr("Confirm send coins"), @@ -273,10 +363,10 @@ void MainWindow::showSendConfirmation(const RpcApi::CreatedTx& tx) bool MainWindow::eventFilter(QObject* object, QEvent* event) { - if (object == m_ui->m_addressLabel && event->type() == QEvent::MouseButtonRelease) + if (object == m_ui->m_copyWalletAddressLabel && event->type() == QEvent::MouseButtonRelease) copyAddress(); - else if (object == m_ui->m_balanceLabel && event->type() == QEvent::MouseButtonRelease) - copyBalance(); +// else if (object == m_ui->m_balanceLabel && event->type() == QEvent::MouseButtonRelease) +// copyBalance(); return false; } @@ -316,6 +406,8 @@ void MainWindow::about() { void MainWindow::copyAddress() { + if (!walletModel_->isConnected()) + return; QApplication::clipboard()->setText(walletModel_->index(0, WalletModel::COLUMN_FIRST_ADDRESS).data(WalletModel::ROLE_FIRST_ADDRESS).toString()); copiedToClipboard(); } @@ -381,9 +473,11 @@ void MainWindow::reportIssueTriggered() void MainWindow::splashMsg(const QString& msg) { m_ui->m_logFrame->addGuiMessage(QString("\n\n\n\n\n") + msg + '\n'); - m_ui->m_addressLabel->setText(msg); - m_ui->m_addressesCountLabel->setText(""); - m_ui->m_creationTimestampLabel->setText(""); + m_ui->m_walletStatusLabel->setText(msg); + m_ui->m_walletStatusPicLabel->setVisible(false); +// m_ui->m_addressLabel->setText(msg); +// m_ui->m_addressesCountLabel->setText(""); +// m_ui->m_creationTimestampLabel->setText(""); showLog(); } @@ -438,6 +532,7 @@ void MainWindow::setConnectedState() m_ui->m_miningButton->setEnabled(true); m_ui->m_overviewButton->setEnabled(true); m_ui->m_addressBookButton->setEnabled(true); + m_ui->m_myAddressesButton->setEnabled(true); m_ui->m_checkProofAction->setEnabled(true); if (m_ui->m_logFrame->isVisible()) m_ui->m_overviewButton->click(); @@ -458,6 +553,7 @@ void MainWindow::setDisconnectedState() m_ui->m_miningButton->setEnabled(false); m_ui->m_overviewButton->setEnabled(false); m_ui->m_addressBookButton->setEnabled(false); + m_ui->m_myAddressesButton->setEnabled(false); walletModel_->reset(); @@ -467,6 +563,14 @@ void MainWindow::setDisconnectedState() m_ui->m_exportKeysAction->setEnabled(false); m_ui->m_exportViewOnlyKeysAction->setEnabled(false); + m_ui->m_balanceLabelText->setVisible(false); + m_ui->m_balanceLabel->setVisible(false); + m_ui->m_addressLabelText->setVisible(false); + m_ui->m_addressLabel->setVisible(false); + m_ui->m_copyWalletAddressLabel->setVisible(false); + m_ui->m_walletStatusPicLabel->setVisible(false); + m_ui->m_topBlockLabel->setVisible(false); + clearTitle(); } diff --git a/src/mainwindow.h b/src/mainwindow.h index fdbd5b1..e734733 100644 --- a/src/mainwindow.h +++ b/src/mainwindow.h @@ -29,6 +29,7 @@ namespace WalletGUI { class WalletModel; class MiningManager; +class IAddressBookManager; class AddressBookManager; class CopiedToolTip; @@ -43,6 +44,7 @@ class MainWindow : public QMainWindow const QString& styleSheetTemplate, MiningManager* miningManager, AddressBookManager* addressBookManager, + IAddressBookManager* myAddressesManager, QWidget* parent); virtual ~MainWindow(); @@ -78,6 +80,7 @@ class MainWindow : public QMainWindow Q_SLOT void exportKeys(); Q_SLOT void updateIsReady(const QString& newVersion); Q_SLOT void netChanged(const QString& net); + Q_SLOT void statusChanged(); protected: void changeEvent(QEvent* event) override; @@ -87,6 +90,7 @@ class MainWindow : public QMainWindow QScopedPointer m_ui; QAbstractItemModel* m_addressBookModel; QAbstractItemModel* m_sortedAddressBookModel; + QAbstractItemModel* m_myAddressesModel; QAbstractItemModel* m_minerModel; QString m_styleSheetTemplate; QDataWidgetMapper* m_addressesMapper; @@ -94,6 +98,7 @@ class MainWindow : public QMainWindow QMovie* m_syncMovie; MiningManager* m_miningManager; AddressBookManager* addressBookManager_; + IAddressBookManager* myAddressesManager_; CopiedToolTip* copiedToolTip_; WalletModel* walletModel_; diff --git a/src/mainwindow.ui b/src/mainwindow.ui index c81fc6a..962d4b2 100644 --- a/src/mainwindow.ui +++ b/src/mainwindow.ui @@ -80,7 +80,7 @@ 0 - 25 + 0 0 @@ -95,7 +95,7 @@ - 20 + 24 20 @@ -103,6 +103,12 @@
+ + + 64 + 64 + + @@ -121,7 +127,7 @@ - 20 + 60 20 @@ -137,7 +143,7 @@ - 5 + 0 0 @@ -146,15 +152,21 @@ 0 - 0 + 24 0 + + 0 + + + 0 + @@ -163,7 +175,7 @@ 40 - 20 + 5 @@ -186,7 +198,7 @@ 40 - 20 + 5 @@ -199,83 +211,228 @@ Qt::Vertical - QSizePolicy::Maximum + QSizePolicy::Expanding 20 - 40 + 20 - - - - - 0 - 0 - - - - PointingHandCursor - - - address - - - 0 - - - - - - - 0 - 0 - + + + 4 - - Wallet created: 01/01/1970 - - + + + + Available balance + + + + + + + + 75 + true + + + + 1 BCN + + + + - - - Total addresses in the wallet: 5 + + + Qt::Horizontal - + + QSizePolicy::Fixed + + + + 40 + 20 + + + - - - - 0 - 0 - + + + 4 - - (view-only) + + QLayout::SetDefaultConstraint - + + + + My wallet address + + + + + + + 5 + + + + + + 0 + 0 + + + + + 75 + true + + + + address + + + + + + + + 0 + 0 + + + + PointingHandCursor + + + + + + :/icons/copy + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter + + + + + + + Qt::Horizontal + + + + 40 + 0 + + + + + + + - + Qt::Horizontal + + QSizePolicy::MinimumExpanding + - 40 + 200 20 + + + + 0 + + + + + 0 + + + + + Qt::Horizontal + + + QSizePolicy::Expanding + + + + 0 + 20 + + + + + + + + + + + :/icons/synced + + + + + + + Qt::Horizontal + + + QSizePolicy::Fixed + + + + 12 + 20 + + + + + + + + Wallet syncronized + + + + + + + + + Top block: %1 + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + @@ -283,13 +440,10 @@ Qt::Vertical - - QSizePolicy::Maximum - 20 - 65 + 20 @@ -299,122 +453,17 @@ - - - - - - - Qt::Horizontal - - - QSizePolicy::Fixed - - - - 34 - 20 - - - - - - - - - - - :/icons/total_balance - - - - - - - Qt::Horizontal - - - QSizePolicy::Fixed - - - - 5 - 20 - - - - - - - - 5 - - - - - Qt::Vertical - - - QSizePolicy::Maximum - - - - 20 - 40 - - - - - - - - - - Total balance - - - - - - - PointingHandCursor - - - Click to copy - - - 0.00 - - - - - - - - - Qt::Vertical - - - QSizePolicy::Maximum - - - - 20 - 40 - - - - - - - + 0 + + 24 + @@ -427,35 +476,60 @@ 0 - - 0 - 0 - - 0 - - - 0 - - - 0 - - + 0 0 - 90 + 0 + 32 + + + + + 16777215 50 + + Qt::NoFocus + + + Overview + + + true + + + true + + + m_toolButtonGroup + + + + + + + + 0 + 0 + + + + + 0 + 32 + + 16777215 @@ -466,7 +540,7 @@ Qt::NoFocus - OVERVIEW + My addresses true @@ -482,15 +556,15 @@ - + 0 0 - 90 - 50 + 0 + 32 @@ -503,7 +577,7 @@ Qt::NoFocus - SEND + Send coins true @@ -522,15 +596,15 @@ true - + 0 0 - 90 - 50 + 0 + 32 @@ -543,7 +617,7 @@ Qt::NoFocus - CONTACTS + Contacts true @@ -562,15 +636,15 @@ true - + 0 0 - 90 - 50 + 0 + 32 @@ -583,7 +657,7 @@ Qt::NoFocus - MINING + Mining true @@ -602,15 +676,15 @@ true - + 0 0 - 90 - 50 + 0 + 32 @@ -623,7 +697,7 @@ Qt::NoFocus - CONSOLE + Console true @@ -705,6 +779,16 @@ + + + + QFrame::NoFrame + + + QFrame::Raised + + + @@ -728,7 +812,7 @@ 0 0 1273 - 23 + 30 @@ -837,7 +921,7 @@ - C&hange password + Cha&nge password @@ -931,6 +1015,17 @@
logframe.h
1 + + WalletGUI::ElidedLabel + QLabel +
elidedlabel.h
+
+ + WalletGUI::MyAddressesFrame + QFrame +
myaddressesframe.h
+ 1 +
@@ -1304,6 +1399,22 @@ + + m_myAddressesButton + toggled(bool) + m_myAddressesFrame + setVisible(bool) + + + 89 + 215 + + + 1159 + 460 + + + aboutQt() @@ -1328,10 +1439,6 @@ createHWWallet() - - - true - - + diff --git a/src/miningoverviewframe.cpp b/src/miningoverviewframe.cpp index b4dc972..011953f 100644 --- a/src/miningoverviewframe.cpp +++ b/src/miningoverviewframe.cpp @@ -8,9 +8,22 @@ #include "MiningManager.h" #include "MinerModel.h" #include "walletmodel.h" +#include "common.h" namespace WalletGUI { +const char MINING_OVERVIEW_STYLE_SHEET_TEMPLATE[] = + "WalletGUI--MiningOverviewFrame {" + "border: 1px solid #c4c4c4" + "}"; + +const char TEXT_LABEL_STYLE_SHEET_TEMPLATE[] = + "QLabel {" + "color: rgba(0,0,0,0.5);" + "}"; + +constexpr int UI_SCALE = 90; + MiningOverviewFrame::MiningOverviewFrame(QWidget *parent) : QFrame(parent) , ui(new Ui::MiningOverviewFrame) @@ -20,12 +33,21 @@ MiningOverviewFrame::MiningOverviewFrame(QWidget *parent) { ui->setupUi(this); - QFont font = ui->m_overviewHashRateLabel->font(); + scaleWidgetText(ui->m_titleLabel, UI_SCALE); + + QFont font = ui->m_overviewHashrateLabel->font(); font.setBold(true); - ui->m_overviewHashRateLabel->setFont(font); + ui->m_overviewHashrateLabel->setFont(font); ui->m_miningStateLabel->setFont(font); ui->m_overviewNetworkHashrateLabel->setFont(font); ui->m_overviewNetworkDifficultyLabel->setFont(font); + + ui->m_miningStatusTextLabel->setStyleSheet(TEXT_LABEL_STYLE_SHEET_TEMPLATE); + ui->m_overviewHashrateTextLabel->setStyleSheet(TEXT_LABEL_STYLE_SHEET_TEMPLATE); + ui->m_overviewNetworkHashrateTextLabel->setStyleSheet(TEXT_LABEL_STYLE_SHEET_TEMPLATE); + ui->m_overviewNetworkDifficultyTextLabel->setStyleSheet(TEXT_LABEL_STYLE_SHEET_TEMPLATE); + + setStyleSheet(MINING_OVERVIEW_STYLE_SHEET_TEMPLATE); } MiningOverviewFrame::~MiningOverviewFrame() @@ -45,11 +67,11 @@ void MiningOverviewFrame::setMiningManager(MiningManager* miningManager) void MiningOverviewFrame::setMinerModel(QAbstractItemModel* model) { miningMapper_->setModel(model); - miningMapper_->addMapping(ui->m_overviewHashRateLabel, MinerModel::COLUMN_HASHRATE, "text"); + miningMapper_->addMapping(ui->m_overviewHashrateLabel, MinerModel::COLUMN_HASHRATE, "text"); if (model->rowCount() > 0) miningMapper_->setCurrentIndex(0); else - ui->m_overviewHashRateLabel->setText("0 H/s"); + ui->m_overviewHashrateLabel->setText("0 H/s"); } void MiningOverviewFrame::setWalletModel(QAbstractItemModel* model) diff --git a/src/miningoverviewframe.ui b/src/miningoverviewframe.ui index e441e15..3a89644 100644 --- a/src/miningoverviewframe.ui +++ b/src/miningoverviewframe.ui @@ -6,8 +6,8 @@ 0 0 - 288 - 163 + 354 + 236 @@ -20,18 +20,27 @@ QFrame::Raised - - + + 24 + + + 20 + + + 24 + + + 12 + + + - Your hashrate - - - 0 + TextLabel - - + + Qt::Vertical @@ -43,57 +52,75 @@ - - + + - TextLabel + OFF + + + 0 - - - - Qt::Vertical + + + + Mining status - - - 20 - 0 - + + 0 - + - - - - Qt::Horizontal - - - - 20 - 20 - - - + + + + 16 + + + + + + + + :/icons/mining + + + + + + + + 75 + true + false + + + + MINING DETAILS + + + + - - + + TextLabel - - + + - Network hashrate + Your hashrate 0 - + Qt::Horizontal @@ -106,18 +133,8 @@ - - - - OFF - - - 0 - - - - - + + Difficulty @@ -126,49 +143,23 @@ - - - - Qt::Horizontal - - - QSizePolicy::Fixed - - - - 10 - 20 - - - - - - + + TextLabel - - + + - Mining status + Network hashrate 0 - - - - - - - :/icons/miner-big - - -
diff --git a/src/myaddressesframe.cpp b/src/myaddressesframe.cpp new file mode 100644 index 0000000..2c54531 --- /dev/null +++ b/src/myaddressesframe.cpp @@ -0,0 +1,87 @@ +// Copyright (c) 2015-2018, The Bytecoin developers. +// Licensed under the GNU Lesser General Public License. See LICENSE for details. + +#include +#include +#include +#include + +#include "myaddressesframe.h" +#include "addressbookmanager.h" +#include "questiondialog.h" +#include "addressbookmodel.h" +#include "newmyaddressdialog.h" +#include "ui_myaddressesframe.h" +#include "mainwindow.h" + +namespace WalletGUI +{ + +MyAddressesFrame::MyAddressesFrame(QWidget* parent) + : QFrame(parent) + , m_ui(new Ui::MyAddressesFrame) +// , m_addressBookManager(nullptr) + , mainWindow_(nullptr) + , m_sortedAddressBookModel(nullptr) +{ + m_ui->setupUi(this); +} + +MyAddressesFrame::~MyAddressesFrame() { +} + +void MyAddressesFrame::setAddressBookManager(IAddressBookManager* manager) +{ + m_addressBookManager = manager; +} + +void MyAddressesFrame::setMainWindow(MainWindow* _mainWindow) +{ + mainWindow_ = _mainWindow; +} + +void MyAddressesFrame::setSortedAddressBookModel(QAbstractItemModel* _model) { + m_sortedAddressBookModel = _model; + m_ui->m_addressBookView->setModel(m_sortedAddressBookModel); + m_ui->m_addressBookView->header()->setSectionResizeMode(AddressBookModel::COLUMN_LABEL, QHeaderView::Fixed); + m_ui->m_addressBookView->header()->setSectionResizeMode(AddressBookModel::COLUMN_ADDRESS, QHeaderView::Stretch); + m_ui->m_addressBookView->header()->setResizeContentsPrecision(-1); + m_ui->m_addressBookView->header()->resizeSection(AddressBookModel::COLUMN_LABEL, 250); +} + +void MyAddressesFrame::addClicked() { + m_addressBookManager->addAddress(QString{}, QString{}); +} + +void MyAddressesFrame::editClicked(const QPersistentModelIndex& _index) { + NewMyAddressDialog dlg{m_addressBookManager, _index, mainWindow_}; + if (dlg.exec() == QDialog::Accepted) { + QString label = dlg.getLabel(); + m_addressBookManager->editAddress(_index.data(AddressBookModel::ROLE_ROW).toInt(), label, QString{}); + } +} + +void MyAddressesFrame::contextMenu(const QPoint& _pos) { + QPersistentModelIndex index = m_ui->m_addressBookView->indexAt(_pos); + if (!index.isValid()) { + return; + } + + QMenu menu; + menu.setObjectName("m_addressBookMenu"); + QAction* editAction = new QAction(tr("Edit"), &menu); + QAction* copyAction = new QAction(tr("Copy to clipboard"), &menu); + menu.addAction(editAction); + menu.addAction(copyAction); + + connect(editAction, &QAction::triggered, [this, index]() { + Q_EMIT this->editClicked(index); + }); + connect(copyAction, &QAction::triggered, [index]() { + QApplication::clipboard()->setText(index.data(AddressBookModel::ROLE_ADDRESS).toString()); + }); + + menu.exec(m_ui->m_addressBookView->mapToGlobal(_pos) + QPoint(-10, 20)); +} + +} diff --git a/src/myaddressesframe.h b/src/myaddressesframe.h new file mode 100644 index 0000000..9883542 --- /dev/null +++ b/src/myaddressesframe.h @@ -0,0 +1,45 @@ +// Copyright (c) 2015-2018, The Bytecoin developers. +// Licensed under the GNU Lesser General Public License. See LICENSE for details. + +#pragma once + +#include + +class QLabel; +class QPersistentModelIndex; +class QAbstractItemModel; + +namespace Ui { +class MyAddressesFrame; +} + +namespace WalletGUI { + +class MainWindow; +class IAddressBookManager; + +class MyAddressesFrame : public QFrame +{ + Q_OBJECT + Q_DISABLE_COPY(MyAddressesFrame) + +public: + explicit MyAddressesFrame(QWidget* _parent); + ~MyAddressesFrame(); + + void setMainWindow(MainWindow* mainWindow); + void setAddressBookManager(IAddressBookManager* manager); + void setSortedAddressBookModel(QAbstractItemModel* _model); + +private: + QScopedPointer m_ui; + IAddressBookManager* m_addressBookManager; + MainWindow* mainWindow_; + QAbstractItemModel* m_sortedAddressBookModel; + + Q_SLOT void addClicked(); + Q_SLOT void editClicked(const QPersistentModelIndex& _index); + Q_SLOT void contextMenu(const QPoint& _pos); +}; + +} diff --git a/src/myaddressesframe.ui b/src/myaddressesframe.ui new file mode 100644 index 0000000..b012882 --- /dev/null +++ b/src/myaddressesframe.ui @@ -0,0 +1,203 @@ + + + MyAddressesFrame + + + + 0 + 0 + 1070 + 500 + + + + Frame + + + QFrame::NoFrame + + + QFrame::Plain + + + + 0 + + + 0 + + + 0 + + + 0 + + + 10 + + + + + 0 + + + 6 + + + 6 + + + + + + + Qt::Vertical + + + QSizePolicy::Fixed + + + + 20 + 20 + + + + + + + + Qt::NoFocus + + + Qt::CustomContextMenu + + + QFrame::NoFrame + + + true + + + QAbstractItemView::NoSelection + + + Qt::ElideMiddle + + + QAbstractItemView::ScrollPerPixel + + + false + + + false + + + false + + + false + + + + + + + Qt::Vertical + + + QSizePolicy::Fixed + + + + 20 + 10 + + + + + + + + 0 + + + 6 + + + 6 + + + + + Qt::Horizontal + + + QSizePolicy::MinimumExpanding + + + + 800 + 20 + + + + + + + + + 0 + 0 + + + + Generate new address + + + + + + + + + + + m_addAddressButton + clicked() + MyAddressesFrame + addClicked() + + + 53 + 556 + + + 436 + 292 + + + + + m_addressBookView + customContextMenuRequested(QPoint) + MyAddressesFrame + contextMenu(QPoint) + + + 436 + 297 + + + 436 + 292 + + + + + + addClicked() + contextMenu(QPoint) + + diff --git a/src/newaddressdialog.ui b/src/newaddressdialog.ui index a531fbf..c1669eb 100644 --- a/src/newaddressdialog.ui +++ b/src/newaddressdialog.ui @@ -47,23 +47,14 @@ 5 - - - - Qt::Vertical - - - QSizePolicy::Fixed - - - - 20 - 10 - + + + + Cancel - + - + Qt::Vertical @@ -79,15 +70,8 @@ - - - - Cancel - - - - - + + Qt::Vertical @@ -102,7 +86,7 @@ - + Qt::Horizontal @@ -115,8 +99,14 @@ - - + + + + + 0 + 0 + + [errorState="true"] { border-color: #ef3131; @@ -127,7 +117,7 @@ - + @@ -140,7 +130,7 @@ - + false @@ -153,14 +143,21 @@ - - + + - + 0 0 + + ADDRESS + + + + + [errorState="true"] { border-color: #ef3131; @@ -172,23 +169,25 @@ - - - - 0 - 0 - + + + Qt::Vertical - - ADDRESS + + QSizePolicy::Fixed - + + + 20 + 10 + + +
m_labelEdit - m_addressEdit m_okButton diff --git a/src/newmyaddressdialog.cpp b/src/newmyaddressdialog.cpp new file mode 100644 index 0000000..0cbd53b --- /dev/null +++ b/src/newmyaddressdialog.cpp @@ -0,0 +1,113 @@ +// Copyright (c) 2015-2018, The Bytecoin developers. +// Licensed under the GNU Lesser General Public License. See LICENSE for details. + +#include + +#include "newmyaddressdialog.h" +#include "addressbookmodel.h" +#include "addressbookmanager.h" +#include "ui_newmyaddressdialog.h" + +namespace WalletGUI +{ + +NewMyAddressDialog::NewMyAddressDialog(IAddressBookManager* addressBookManager, QWidget* _parent) + : QDialog(_parent, static_cast(Qt::WindowCloseButtonHint)) + , m_ui(new Ui::NewMyAddressDialog) + , m_addressBookManager(addressBookManager) +{ + m_ui->setupUi(this); +} + +NewMyAddressDialog::NewMyAddressDialog(IAddressBookManager* addressBookManager, const QPersistentModelIndex& _index, QWidget* _parent) + : NewMyAddressDialog(addressBookManager, _parent) +{ + setWindowTitle(tr("Edit address")); + m_ui->m_labelEdit->setText(_index.data(AddressBookModel::ROLE_LABEL).toString()); + m_ui->m_addressLabel->setText(_index.data(AddressBookModel::ROLE_ADDRESS).toString()); +} + +NewMyAddressDialog::~NewMyAddressDialog() { +} + +QString NewMyAddressDialog::getAddress() const { + return m_ui->m_addressLabel->text(); +} + +QString NewMyAddressDialog::getLabel() const { + return m_ui->m_labelEdit->text(); +} + +//void NewMyAddressDialog::setAddressError(bool _error) { +// m_ui->m_addressEdit->setProperty("errorState", _error); +// m_ui->m_addressTextLabel->setProperty("errorState", _error); +// m_ui->m_addressTextLabel->setText(_error ? tr("INVALID ADDRESS") : tr("ADDRESS")); + +// m_ui->m_addressEdit->style()->unpolish(m_ui->m_addressEdit); +// m_ui->m_addressEdit->style()->polish(m_ui->m_addressEdit); +// m_ui->m_addressEdit->update(); + +// m_ui->m_addressTextLabel->style()->unpolish(m_ui->m_addressTextLabel); +// m_ui->m_addressTextLabel->style()->polish(m_ui->m_addressTextLabel); +// m_ui->m_addressTextLabel->update(); +// m_ui->m_okButton->setEnabled(!checkForErrors() && !m_ui->m_labelEdit->text().trimmed().isEmpty() && +// !m_ui->m_addressEdit->text().trimmed().isEmpty()); +//} + +//void NewMyAddressDialog::setAddressDuplicationError(bool _error) { +// m_ui->m_addressEdit->setProperty("errorState", _error); +// m_ui->m_addressTextLabel->setProperty("errorState", _error); +// m_ui->m_addressTextLabel->setText(_error ? tr("ALREADY IN THE ADDRESS BOOK") : tr("ADDRESS")); + +// m_ui->m_addressEdit->style()->unpolish(m_ui->m_addressEdit); +// m_ui->m_addressEdit->style()->polish(m_ui->m_addressEdit); +// m_ui->m_addressEdit->update(); + +// m_ui->m_addressTextLabel->style()->unpolish(m_ui->m_addressTextLabel); +// m_ui->m_addressTextLabel->style()->polish(m_ui->m_addressTextLabel); +// m_ui->m_addressTextLabel->update(); + +// m_ui->m_okButton->setEnabled(!checkForErrors() && !m_ui->m_labelEdit->text().trimmed().isEmpty() && +// !m_ui->m_addressEdit->text().trimmed().isEmpty()); +//} + +//void NewMyAddressDialog::setLabelDuplicationError(bool _error) { +// m_ui->m_labelEdit->setProperty("errorState", _error); +// m_ui->m_labelTextLabel->setProperty("errorState", _error); +// m_ui->m_labelTextLabel->setText(_error ? tr("ALREADY IN THE ADDRESS BOOK") : tr("LABEL")); + +// m_ui->m_labelEdit->style()->unpolish(m_ui->m_labelEdit); +// m_ui->m_labelEdit->style()->polish(m_ui->m_labelEdit); +// m_ui->m_labelEdit->update(); + +// m_ui->m_labelTextLabel->style()->unpolish(m_ui->m_labelTextLabel); +// m_ui->m_labelTextLabel->style()->polish(m_ui->m_labelTextLabel); +// m_ui->m_labelTextLabel->update(); + +// m_ui->m_okButton->setEnabled(!checkForErrors() && !m_ui->m_labelEdit->text().trimmed().isEmpty() && +// !m_ui->m_addressEdit->text().trimmed().isEmpty()); +//} + +//bool NewMyAddressDialog::checkForErrors() const { +// return m_ui->m_addressEdit->property("errorState").toBool() || m_ui->m_labelEdit->property("errorState").toBool(); +//} + +//void NewMyAddressDialog::validateAddress(const QString& _address) { +// bool isInvalidAddress = (/*!*/_address.isEmpty()/* && !m_cryptoNoteAdapter->isValidAddress(_address)*/); +// if (isInvalidAddress) { +// setAddressError(true); +// return; +// } + +// QString address = getAddress().trimmed(); +// quintptr addressIndex = m_addressBookManager->findAddressByAddress(address); +// setAddressDuplicationError(addressIndex != INVALID_ADDRESS_INDEX); +//} + +//void NewMyAddressDialog::validateLabel(const QString& /*_label*/) { +// QString label = getLabel().trimmed(); +// quintptr addressIndex = m_addressBookManager->findAddressByLabel(label); +// setLabelDuplicationError(addressIndex != INVALID_ADDRESS_INDEX); +//} + +} diff --git a/src/newmyaddressdialog.h b/src/newmyaddressdialog.h new file mode 100644 index 0000000..a4b185c --- /dev/null +++ b/src/newmyaddressdialog.h @@ -0,0 +1,46 @@ +// Copyright (c) 2015-2018, The Bytecoin developers. +// Licensed under the GNU Lesser General Public License. See LICENSE for details. + +#pragma once + +#include + +class QPersistentModelIndex; + +namespace Ui { +class NewMyAddressDialog; +} + +namespace WalletGUI { + +class IAddressBookManager; + +class NewMyAddressDialog : public QDialog +{ + Q_OBJECT + Q_DISABLE_COPY(NewMyAddressDialog) + +public: + NewMyAddressDialog(IAddressBookManager* addressBookManager, QWidget* _parent); + NewMyAddressDialog(IAddressBookManager* addressBookManager, const QPersistentModelIndex& _index, QWidget* _parent); + ~NewMyAddressDialog(); + + QString getAddress() const; + QString getLabel() const; +// bool hasDonationFlag() const; + +// void setAddressError(bool _error); +// void setAddressDuplicationError(bool _error); +// void setLabelDuplicationError(bool _error); + +private: + QScopedPointer m_ui; + IAddressBookManager* m_addressBookManager; + +// bool checkForErrors() const; + +// Q_SLOT void validateAddress(const QString& _address); +// Q_SLOT void validateLabel(const QString& _label); +}; + +} diff --git a/src/newmyaddressdialog.ui b/src/newmyaddressdialog.ui new file mode 100644 index 0000000..bdef67f --- /dev/null +++ b/src/newmyaddressdialog.ui @@ -0,0 +1,241 @@ + + + NewMyAddressDialog + + + + 0 + 0 + 584 + 229 + + + + + 0 + 0 + + + + + 584 + 229 + + + + + 584 + 229 + + + + New contact + + + + 30 + + + 30 + + + 30 + + + 30 + + + 5 + + + + + Qt::Horizontal + + + + 338 + 20 + + + + + + + + Qt::Vertical + + + QSizePolicy::Expanding + + + + 20 + 10 + + + + + + + + Qt::Vertical + + + QSizePolicy::Fixed + + + + 20 + 10 + + + + + + + + OK + + + true + + + + + + + Qt::Vertical + + + QSizePolicy::Fixed + + + + 20 + 10 + + + + + + + + Cancel + + + + + + + + 0 + 0 + + + + [errorState="true"] { + border-color: #ef3131; +} + + + + + + QLineEdit::Normal + + + + + + + + 0 + 0 + + + + LABEL + + + + + + + + 0 + 0 + + + + ADDRESS + + + + + + + + 11 + 75 + true + + + + address + + + + + + + + WalletGUI::ElidedLabel + QLabel +
elidedlabel.h
+
+
+ + m_okButton + + + + + m_okButton + clicked() + NewMyAddressDialog + accept() + + + 410 + 102 + + + 294 + 63 + + + + + m_cancelButton + clicked() + NewMyAddressDialog + reject() + + + 526 + 102 + + + 294 + 63 + + + + + + validateAddress(QString) + validateLabel(QString) + donationCheckStateChanged(int) + +
diff --git a/src/overviewframe.cpp b/src/overviewframe.cpp index 274ec55..fd106fc 100644 --- a/src/overviewframe.cpp +++ b/src/overviewframe.cpp @@ -9,6 +9,8 @@ #include #include #include +#include + #include "overviewframe.h" #include "walletmodel.h" @@ -20,6 +22,11 @@ namespace WalletGUI { namespace { +const char TX_HASH_URL[] = "https://explorer.bytecoin.org/tx?hash=%1"; +const char BLOCK_HASH_URL[] = "https://explorer.bytecoin.org/block?hash=%1"; +const char BLOCK_HEIGHT_URL[] = "https://explorer.bytecoin.org/block?height=%1"; + + //const char OVERVIEW_STYLE_SHEET_TEMPLATE[] = // "* {" // "font-family: %fontFamily%;" @@ -163,25 +170,35 @@ bool OverviewFrame::eventFilter(QObject* object, QEvent* event) if (!modelIndex.isValid()) return false; - const bool copyableColumns = - modelIndex.column() == WalletModel::COLUMN_HASH || - (modelIndex.column() == WalletModel::COLUMN_BLOCK_HASH && !m_transactionsModel->data(modelIndex, WalletModel::ROLE_BLOCK_HASH).toString().isEmpty()); + const bool explorableColumns = + (modelIndex.column() == WalletModel::COLUMN_HASH && !m_transactionsModel->data(modelIndex, WalletModel::ROLE_HASH).toString().isEmpty()) || + (modelIndex.column() == WalletModel::COLUMN_BLOCK_HASH && !m_transactionsModel->data(modelIndex, WalletModel::ROLE_BLOCK_HASH).toString().isEmpty()) || + (modelIndex.column() == WalletModel::COLUMN_BLOCK_HEIGHT && !m_transactionsModel->data(modelIndex, WalletModel::ROLE_BLOCK_HEIGHT).toString().isEmpty()); const bool proof = m_transactionsModel->data(modelIndex, WalletModel::ROLE_PROOF).toBool(); - const bool proofColumn = (modelIndex.column() == WalletModel::COLUMN_PROOF/* && proof*/); - const bool valid = copyableColumns || proofColumn; + const bool proofColumn = (modelIndex.column() == WalletModel::COLUMN_PROOF && proof); + const bool valid = explorableColumns || proofColumn; if(!valid) return false; - if (copyableColumns) + if (explorableColumns) { - QApplication::clipboard()->setText(m_transactionsModel->data(modelIndex).toString()); - emit copiedToClipboardSignal(); + switch(modelIndex.column()) + { + case WalletModel::COLUMN_HASH: + QDesktopServices::openUrl(QUrl::fromUserInput(QString{TX_HASH_URL}.arg(m_transactionsModel->data(modelIndex, WalletModel::ROLE_HASH).toString()))); + break; + case WalletModel::COLUMN_BLOCK_HASH: + QDesktopServices::openUrl(QUrl::fromUserInput(QString{BLOCK_HASH_URL}.arg(m_transactionsModel->data(modelIndex, WalletModel::ROLE_BLOCK_HASH).toString()))); + break; + case WalletModel::COLUMN_BLOCK_HEIGHT: + QDesktopServices::openUrl(QUrl::fromUserInput(QString{BLOCK_HEIGHT_URL}.arg(m_transactionsModel->data(modelIndex, WalletModel::ROLE_BLOCK_HEIGHT).toString()))); + break; + } } if (proofColumn) { const QString txHash = m_transactionsModel->data(modelIndex, WalletModel::ROLE_HASH).toString(); const QStringList addresses = m_transactionsModel->data(modelIndex, WalletModel::ROLE_RECIPIENTS).toStringList(); -// emit createProofSignal(txHash, !proof); emit createProofSignal(txHash, addresses, !proof); } } @@ -190,10 +207,14 @@ bool OverviewFrame::eventFilter(QObject* object, QEvent* event) QMouseEvent* e = (QMouseEvent*)event; QModelIndex modelIndex = view->indexAt(e->pos()); - const bool showHandCursor = - modelIndex.column() == WalletModel::COLUMN_HASH || + const bool explorableColumns = + (modelIndex.column() == WalletModel::COLUMN_HASH && !m_transactionsModel->data(modelIndex, WalletModel::ROLE_HASH).toString().isEmpty()) || (modelIndex.column() == WalletModel::COLUMN_BLOCK_HASH && !m_transactionsModel->data(modelIndex, WalletModel::ROLE_BLOCK_HASH).toString().isEmpty()) || - (modelIndex.column() == WalletModel::COLUMN_PROOF/* && m_transactionsModel->data(modelIndex, WalletModel::ROLE_PROOF).toBool()*/); + (modelIndex.column() == WalletModel::COLUMN_BLOCK_HEIGHT && !m_transactionsModel->data(modelIndex, WalletModel::ROLE_BLOCK_HEIGHT).toString().isEmpty()); + const bool proof = m_transactionsModel->data(modelIndex, WalletModel::ROLE_PROOF).toBool(); + const bool proofColumn = (modelIndex.column() == WalletModel::COLUMN_PROOF && proof); + + const bool showHandCursor = explorableColumns || proofColumn; setCursor(showHandCursor ? Qt::PointingHandCursor : Qt::ArrowCursor); } diff --git a/src/overviewframe.ui b/src/overviewframe.ui index 04cf717..acf6f7f 100644 --- a/src/overviewframe.ui +++ b/src/overviewframe.ui @@ -37,6 +37,12 @@ + + 20 + + + 0 + @@ -51,12 +57,6 @@ 20 - - QFrame::NoFrame - - - QFrame::Raised - @@ -73,12 +73,6 @@ 20 - - QFrame::NoFrame - - - QFrame::Raised - diff --git a/src/resources.qrc b/src/resources.qrc index 8142287..cdc1c35 100644 --- a/src/resources.qrc +++ b/src/resources.qrc @@ -10,7 +10,7 @@ icons/disconnected_bl.png icons/sync_sprite.png icons/sync_sprite_bl.png - icons/synced.png + icons/synced.png icons/synced_bl.png icons/overview.png icons/send.png @@ -97,6 +97,11 @@ icons/stop.png icons/logo_test.png icons/logo_stage.png + icons/balance_new.png + icons/mining.png + icons/sync_lag.png + icons/not_synced.png + icons/copy.png images/splash.png @@ -111,16 +116,24 @@ bytecoinwallet.qss - font/OpenSans-Bold.ttf - font/OpenSans-BoldItalic.ttf - font/OpenSans-ExtraBold.ttf - font/OpenSans-ExtraBoldItalic.ttf - font/OpenSans-Italic.ttf - font/OpenSans-Light.ttf - font/OpenSans-LightItalic.ttf - font/OpenSans-Regular.ttf - font/OpenSans-Semibold.ttf - font/OpenSans-SemiboldItalic.ttf + font/WorkSans-Black.ttf + font/WorkSans-BlackItalic.ttf + font/WorkSans-Bold.ttf + font/WorkSans-BoldItalic.ttf + font/WorkSans-ExtraBold.ttf + font/WorkSans-ExtraBoldItalic.ttf + font/WorkSans-ExtraLight.ttf + font/WorkSans-ExtraLightItalic.ttf + font/WorkSans-Italic.ttf + font/WorkSans-Light.ttf + font/WorkSans-LightItalic.ttf + font/WorkSans-Medium.ttf + font/WorkSans-MediumItalic.ttf + font/WorkSans-Regular.ttf + font/WorkSans-SemiBold.ttf + font/WorkSans-SemiBoldItalic.ttf + font/WorkSans-Thin.ttf + font/WorkSans-ThinItalic.ttf diff --git a/src/rpcapi.cpp b/src/rpcapi.cpp index ce0003a..ee593c4 100644 --- a/src/rpcapi.cpp +++ b/src/rpcapi.cpp @@ -19,6 +19,9 @@ constexpr char CreateTransaction::METHOD[]; constexpr char SendTransaction::METHOD[]; constexpr char CreateSendProof::METHOD[]; constexpr char CheckSendProof::METHOD[]; +constexpr char GetWalletRecords::METHOD[]; +constexpr char SetAddressLabel::METHOD[]; +constexpr char CreateAddresses::METHOD[]; #define RPCAPI_SERIALIZE_FIELD(obj, json, fieldName) \ do \ @@ -445,6 +448,21 @@ Block::fromJson(const QVariantMap& json) return value; } +/*static*/ +WalletRecord +WalletRecord::fromJson(const QVariantMap& json) +{ + WalletRecord value; + + RPCAPI_DESERIALIZE_FIELD(value, json, address); + RPCAPI_DESERIALIZE_FIELD(value, json, label); + RPCAPI_DESERIALIZE_FIELD(value, json, index); + RPCAPI_DESERIALIZE_FIELD(value, json, secret_spend_key); + RPCAPI_DESERIALIZE_FIELD(value, json, public_spend_key); + + return value; +} + /*static*/ Transaction Transaction::fromJson(const QVariantMap& json) @@ -566,6 +584,68 @@ CheckSendProof::Request::toJson() const return json; } +QVariantMap +SetAddressLabel::Request::toJson() const +{ + const SetAddressLabel::Request& value = *this; + QVariantMap json; + + RPCAPI_SERIALIZE_FIELD(value, json, address); + RPCAPI_SERIALIZE_FIELD(value, json, label); + + return json; +} + +QVariantMap +GetWalletRecords::Request::toJson() const +{ + const GetWalletRecords::Request& value = *this; + QVariantMap json; + + RPCAPI_SERIALIZE_FIELD(value, json, need_secrets); + RPCAPI_SERIALIZE_FIELD(value, json, create); + RPCAPI_SERIALIZE_FIELD(value, json, index); + RPCAPI_SERIALIZE_FIELD(value, json, count); + + return json; +} + +/*static*/ +GetWalletRecords::Response +GetWalletRecords::Response::fromJson(const QVariantMap& json) +{ + GetWalletRecords::Response value; + + RPCAPI_DESERIALIZE_LIST(value, json, records); + RPCAPI_DESERIALIZE_FIELD(value, json, total_count); + + return value; +} + +QVariantMap +CreateAddresses::Request::toJson() const +{ + const CreateAddresses::Request& value = *this; + QVariantMap json; + + RPCAPI_SERIALIZE_FIELD(value, json, secret_spend_keys); + RPCAPI_SERIALIZE_TIMESTAMP(value, json, creation_timestamp); + + return json; +} + +/*static*/ +CreateAddresses::Response +CreateAddresses::Response::fromJson(const QVariantMap& json) +{ + CreateAddresses::Response value; + + RPCAPI_DESERIALIZE_FIELD(value, json, addresses); + RPCAPI_DESERIALIZE_FIELD(value, json, secret_spend_keys); + + return value; +} + #undef RPCAPI_SERIALIZE_FIELD #undef RPCAPI_SERIALIZE_TIMESTAMP #undef RPCAPI_SERIALIZE_STRUCT diff --git a/src/rpcapi.h b/src/rpcapi.h index 8e01266..1d56f8a 100644 --- a/src/rpcapi.h +++ b/src/rpcapi.h @@ -60,7 +60,10 @@ enum class ErrorCodes struct EmptyStruct -{}; +{ + static EmptyStruct fromJson(const QVariantMap&) + { return EmptyStruct{}; } +}; struct Output { @@ -250,7 +253,7 @@ struct WalletRecord QString secret_spend_key; QString public_spend_key; - static Block fromJson(const QVariantMap& json); + static WalletRecord fromJson(const QVariantMap& json); auto tie() const { @@ -419,8 +422,8 @@ struct GetWalletRecords { bool need_secrets = false; bool create = false; - quint64 index = 0; - quint64 count = 1; + quint32 index = 0; + quint32 count = std::numeric_limits::max(); QVariantMap toJson() const; }; @@ -442,11 +445,34 @@ struct SetAddressLabel { QString address; QString label; + + QVariantMap toJson() const; }; using Response = EmptyStruct; }; +struct CreateAddresses +{ + static constexpr char METHOD[] = "create_addresses"; + + struct Request + { + QStringList secret_spend_keys; + QDateTime creation_timestamp; + + QVariantMap toJson() const; + }; + + struct Response + { + QStringList addresses; + QStringList secret_spend_keys; + + static Response fromJson(const QVariantMap& json); + }; +}; + struct GetBalance { static constexpr char METHOD[] = "get_balance"; @@ -654,6 +680,8 @@ using CreatedTx = CreateTransaction::Response; using SentTx = SendTransaction::Response; using Proofs = CreateSendProof::Response; using ProofCheck = CheckSendProof::Response; +using WalletRecords = GetWalletRecords::Response; +using CreatedAddresses = CreateAddresses::Response; inline bool operator == (const Status& lhs, const Status& rhs) { return lhs.tie() == rhs.tie(); } diff --git a/src/sendframe.cpp b/src/sendframe.cpp index e4ebdcc..8fcd965 100644 --- a/src/sendframe.cpp +++ b/src/sendframe.cpp @@ -362,10 +362,11 @@ void SendFrame::amountStringChanged(const QString& /*amountString*/) for (TransferFrame* transfer : m_transfers) amount += transfer->getAmount(); - m_ui->m_totalAmountLabel->setText(QString("%1 %2") - .arg(formatUnsignedAmount(amount, false /*trim*/)) - .arg(QString(CURRENCY_TICKER).toUpper())); +// m_ui->m_totalAmountLabel->setText(QString("%1 %2") +// .arg(formatUnsignedAmount(amount, false /*trim*/)) +// .arg(QString(CURRENCY_TICKER).toUpper())); + m_ui->m_totalAmountLabel->setText(formatUnsignedAmount(amount, false /*trim*/)); m_ui->m_sendButton->setEnabled(readyToSend()); } diff --git a/src/statusbar.cpp b/src/statusbar.cpp index a46e916..18ad406 100644 --- a/src/statusbar.cpp +++ b/src/statusbar.cpp @@ -17,10 +17,6 @@ namespace WalletGUI { namespace { -const QDateTime EPOCH_DATE_TIME = QDateTime::fromTime_t(0).toUTC(); -const int MSECS_IN_MINUTE = 60 * 1000; -const int MSECS_IN_HOUR = 60 * MSECS_IN_MINUTE; - //const char STATUS_BAR_STYLE_SHEET_TEMPLATE[] = // "WalletGui--WalletStatusBar, " // "WalletGui--WalletStatusBar QLabel {" @@ -30,57 +26,12 @@ const int MSECS_IN_HOUR = 60 * MSECS_IN_MINUTE; // "}"; const char STATUS_BAR_STYLE_SHEET_TEMPLATE[] = + "WalletGUI--WalletStatusBar QLabel {" + "color: rgba(0,0,0,0.5);" + "}" "WalletGUI--WalletStatusBar::item {" "border: none;" "}"; - -QString formatTimeDiff(quint64 timeDiff) -{ - QDateTime dateTime = QDateTime::fromTime_t(timeDiff).toUTC(); - QString firstPart; - QString secondPart; - quint64 year = dateTime.date().year() - EPOCH_DATE_TIME.date().year(); - quint64 month = dateTime.date().month() - EPOCH_DATE_TIME.date().month(); - quint64 day = dateTime.date().day() - EPOCH_DATE_TIME.date().day(); - if (year > 0) - { - firstPart = QStringLiteral("%1 %2").arg(year).arg(year == 1 ? QObject::tr("year") : QObject::tr("years")); - secondPart = QStringLiteral("%1 %2").arg(month).arg(month == 1 ? QObject::tr("month") : QObject::tr("months")); - } - else if (month > 0) - { - firstPart = QStringLiteral("%1 %2").arg(month).arg(month == 1 ? QObject::tr("month") : QObject::tr("months")); - secondPart = QStringLiteral("%1 %2").arg(day).arg(day == 1 ? QObject::tr("day") : QObject::tr("days")); - } - else if (day > 0) - { - quint64 hour = dateTime.time().hour(); - firstPart = QStringLiteral("%1 %2").arg(day).arg(day == 1 ? QObject::tr("day") : QObject::tr("days")); - secondPart = QStringLiteral("%1 %2").arg(hour).arg(hour == 1 ? QObject::tr("hour") : QObject::tr("hours")); - } - else if (dateTime.time().hour() > 0) - { - quint64 hour = dateTime.time().hour(); - quint64 minute = dateTime.time().minute(); - firstPart = QStringLiteral("%1 %2").arg(hour).arg(hour == 1 ? QObject::tr("hour") : QObject::tr("hours")); - secondPart = QStringLiteral("%1 %2").arg(minute).arg(minute == 1 ? QObject::tr("minute") : QObject::tr("minutes")); - } - else if (dateTime.time().minute() > 0) - { - quint64 minute = dateTime.time().minute(); - firstPart = QStringLiteral("%1 %2").arg(minute).arg(minute == 1 ? QObject::tr("minute") : QObject::tr("minutes")); - } - else - { - firstPart = QStringLiteral("Less than 1 minute"); - } - - if (secondPart.isNull()) - return firstPart; - - return QStringLiteral("%1 %2").arg(firstPart).arg(secondPart); -} - } WalletStatusBar::WalletStatusBar(QWidget* parent) @@ -154,31 +105,33 @@ void WalletStatusBar::updateStatusDescription() { Q_ASSERT(walletModel_ != nullptr); const quint32 knownBlockHeight = walletModel_->getKnownBlockHeight(); - const quint32 lastBlockHeight = walletModel_->getLastBlockHeight(); - const QDateTime lastBlockTimestampReceived = walletModel_->getLastBlockTimestamp(); - const quint32 peerCount = walletModel_->getPeerCountSum(); - const QString lowerLevelError = walletModel_->getLowerLevelError(); +// const quint32 lastBlockHeight = walletModel_->getLastBlockHeight(); +// const QDateTime lastBlockTimestampReceived = walletModel_->getLastBlockTimestamp(); +// const quint32 peerCount = walletModel_->getPeerCountSum(); +// const QString lowerLevelError = walletModel_->getLowerLevelError(); - const QDateTime currentDateTime = QDateTime::currentDateTimeUtc(); - const QDateTime lastBlockTimestamp = qMin(lastBlockTimestampReceived, currentDateTime); - const quint64 msecsSinceLastBlock = lastBlockTimestamp.msecsTo(currentDateTime); - const quint64 secsSinceLastBlock = lastBlockTimestamp.secsTo(currentDateTime); +// const QDateTime currentDateTime = QDateTime::currentDateTimeUtc(); - const bool isThereAnyBlock = lastBlockTimestamp.toMSecsSinceEpoch() > 0; - const QString formattedTimeDiff = isThereAnyBlock ? formatTimeDiff(secsSinceLastBlock) : tr("unknown"); +// const QDateTime lastBlockTimestamp = qMin(lastBlockTimestampReceived, currentDateTime); +// const quint64 msecsSinceLastBlock = lastBlockTimestamp.msecsTo(currentDateTime); +// const quint64 secsSinceLastBlock = lastBlockTimestamp.secsTo(currentDateTime); + + const bool isThereAnyBlock = walletModel_->isThereAnyBlock(); + const quint32 peerCount = walletModel_->getPeerCountSum(); + const QString formattedTimeDiff = isThereAnyBlock ? walletModel_->getFormattedTimeSinceLastBlock() : tr("unknown"); const QString blockchainAge = isThereAnyBlock ? tr("%1 ago").arg(formattedTimeDiff) : tr("%1").arg(formattedTimeDiff); - isSynchronized_ = lowerLevelError.isEmpty() && isThereAnyBlock && lastBlockHeight == knownBlockHeight; + const quint32 blocksLeft = walletModel_->getBlocksLeftToSync(); + isSynchronized_ = walletModel_->isSyncronized(); updateSyncState(); QString warningString; - if (msecsSinceLastBlock > MSECS_IN_HOUR) + if (walletModel_->getSyncStatus() == WalletModel::SyncStatus::LAGGED) warningString.append(tr(" Warning: the wallet is lagged.")); if (peerCount == 0) warningString.append(tr(" No network connection.")); - const quint32 blocksLeft = knownBlockHeight >= lastBlockHeight ? knownBlockHeight - lastBlockHeight : 0; const QString statusText = isSynchronized_ ? tr("Wallet synchronized. Top block height: %1 / Received: %2 ago.%3") .arg(knownBlockHeight) @@ -196,12 +149,14 @@ void WalletStatusBar::updateSyncState() if (isSynchronized_) { m_syncMovie->stop(); - m_syncStatusIconLabel->setPixmap(QPixmap(QString(":icons/light/synced"))); +// m_syncStatusIconLabel->setPixmap(QPixmap(QString(":icons/light/synced"))); + m_syncStatusIconLabel->setVisible(false); } else { if (m_syncMovie->state() == QMovie::NotRunning) { + m_syncStatusIconLabel->setVisible(true); m_syncStatusIconLabel->setMovie(m_syncMovie); m_syncMovie->start(); } diff --git a/src/version.h b/src/version.h index c101dc2..9919084 100644 --- a/src/version.h +++ b/src/version.h @@ -5,10 +5,10 @@ namespace WalletGUI { -constexpr char VERSION[] = "3.4.5"; -constexpr char CODENAME[] = "Amethyst"; +constexpr char VERSION[] = "3.5.0"; +constexpr char CODENAME[] = "Beryl"; constexpr char VERSION_SUFFIX[] = "stable"; -constexpr char REVISION[] = "20190611"; +constexpr char REVISION[] = "20190704"; // returns <0, if newVersion is worse than currentVersion, returns >0, if newVersion is better, and returns 0, if versions are equal int compareVersion(const QString& newVersion, const QString& currentVersion); diff --git a/src/walletd.cpp b/src/walletd.cpp index bd3be16..807d4fd 100644 --- a/src/walletd.cpp +++ b/src/walletd.cpp @@ -116,6 +116,7 @@ namespace WalletGUI constexpr int RERUN_TIMER_MSEC = 3000; constexpr int STATUS_TIMER_MSEC = 15000; +constexpr int STATUS_DELAY_TIMER_MSEC = 500; constexpr int WAITING_TIMEOUT_MSEC = 10000; using namespace std::placeholders; @@ -149,10 +150,10 @@ RemoteWalletd::RemoteWalletd(const QString& endPoint, QObject* parent) rerunTimer_.setSingleShot(true); rerunTimer_.setInterval(RERUN_TIMER_MSEC); - statusTimer_.setSingleShot(false); - statusTimer_.setInterval(STATUS_TIMER_MSEC); + heartbeatTimer_.setSingleShot(false); + heartbeatTimer_.setInterval(STATUS_TIMER_MSEC); connect(&rerunTimer_, &QTimer::timeout, this, &RemoteWalletd::rerun); - connect(&statusTimer_, &QTimer::timeout, [this] { this->sendGetStatus(RpcApi::GetStatus::Request{}, /* sendAgain = */ false); }); + connect(&heartbeatTimer_, &QTimer::timeout, [this] { this->sendGetStatus(RpcApi::GetStatus::Request{}, /* sendAgain = */ false); }); } /*virtual*/ @@ -181,7 +182,7 @@ void RemoteWalletd::sendGetStatus(const RpcApi::GetStatus::Request& req, bool se std::bind(&RemoteWalletd::balanceReceived, this, _2), std::bind(&RemoteWalletd::jsonErrorResponse, this, _1, _2)); - statusTimer_.start(); + heartbeatTimer_.start(); } } @@ -198,7 +199,7 @@ void RemoteWalletd::run() // this, &RemoteWalletd::errorOccurred, // [this]() // { -//// statusTimer_.start(); +//// heartbeatTimer_.start(); // jsonClient_->sendGetStatus(RpcApi::GetStatus::Request{}); // }); @@ -219,7 +220,7 @@ void RemoteWalletd::run() void RemoteWalletd::stop() { rerunTimer_.stop(); -// statusTimer_.stop(); +// heartbeatTimer_.stop(); setState(State::STOPPED); } @@ -230,13 +231,16 @@ void RemoteWalletd::statusReceived(const RpcApi::Status& status, bool sendAgain) emit statusReceivedSignal(status); if (sendAgain) - sendGetStatus(RpcApi::GetStatus::Request{ - status.top_block_hash, - status.transaction_pool_version, - status.outgoing_peer_count, - status.incoming_peer_count, - status.lower_level_error}, - sendAgain); + QTimer::singleShot(STATUS_DELAY_TIMER_MSEC, + std::bind(&RemoteWalletd::sendGetStatus, + this, + RpcApi::GetStatus::Request{ + status.top_block_hash, + status.transaction_pool_version, + status.outgoing_peer_count, + status.incoming_peer_count, + status.lower_level_error}, + sendAgain)); } void RemoteWalletd::transfersReceived(const RpcApi::Transfers& history, RpcApi::Height topHeight, RpcApi::Height from_height, RpcApi::Height to_height) @@ -277,6 +281,32 @@ void RemoteWalletd::checkProofReceived(const RpcApi::ProofCheck& proofCheck) emit checkProofReceivedSignal(proofCheck); } +void RemoteWalletd::walletRecordsReceived(const RpcApi::WalletRecords& records) +{ + emit walletRecordsReceivedSignal(records); +} + +void RemoteWalletd::addressLabelSetReceived(const QString& address, const QString& label) +{ + emit addressLabelSetReceivedSignal(address, label); +} + +void RemoteWalletd::addressesCreatedReceived(const RpcApi::CreatedAddresses& addrs, const QString& label) +{ + if (label.isEmpty()) + { + emit addressesCreatedReceivedSignal(addrs); + return; + } + + Q_ASSERT(addrs.addresses.size() == 1); + + jsonClient_->sendRequest( + RpcApi::SetAddressLabel::Request{addrs.addresses.first(), label}, + [this, addrs](const QString&, const RpcApi::EmptyStruct&) { emit this->addressesCreatedReceivedSignal(addrs);}, + std::bind(&RemoteWalletd::jsonErrorResponse, this, _1, _2)); +} + void RemoteWalletd::networkError(const QString& errorString) { if (state_ == State::STOPPED) @@ -387,6 +417,38 @@ void RemoteWalletd::checkSendProof(const RpcApi::CheckSendProof::Request& proof) // std::bind(&RemoteWalletd::jsonErrorResponse, this, _1, _2)); } +void RemoteWalletd::getWalletRecords(const RpcApi::GetWalletRecords::Request& req) +{ + jsonClient_->sendRequest( + req, + std::bind(&RemoteWalletd::walletRecordsReceived, this, _2), + std::bind(&RemoteWalletd::jsonErrorResponse, this, _1, _2)); +} + +void RemoteWalletd::setAddressLabel(const RpcApi::SetAddressLabel::Request& req) +{ + jsonClient_->sendRequest( + req, + std::bind(&RemoteWalletd::addressLabelSetReceived, this, req.address, req.label), + std::bind(&RemoteWalletd::jsonErrorResponse, this, _1, _2)); +} + +void RemoteWalletd::createAddresses(const RpcApi::CreateAddresses::Request &req) +{ + jsonClient_->sendRequest( + req, + std::bind(&RemoteWalletd::addressesCreatedReceived, this, _2, QString{}), + std::bind(&RemoteWalletd::jsonErrorResponse, this, _1, _2)); +} + +void RemoteWalletd::createAddress(const QString &label) +{ + jsonClient_->sendRequest( + RpcApi::CreateAddresses::Request{QStringList{} << "", QDateTime{}}, + std::bind(&RemoteWalletd::addressesCreatedReceived, this, _2, label), + std::bind(&RemoteWalletd::jsonErrorResponse, this, _1, _2)); +} + void RemoteWalletd::authRequired(QAuthenticator* authenticator) { emit authRequiredSignal(authenticator); diff --git a/src/walletd.h b/src/walletd.h index abe7f99..3747c34 100644 --- a/src/walletd.h +++ b/src/walletd.h @@ -45,6 +45,10 @@ class RemoteWalletd : public QObject void getTransfers(const RpcApi::GetTransfers::Request& req, RpcApi::Height topHeight); void createProof(const RpcApi::CreateSendProof::Request& req); void checkSendProof(const RpcApi::CheckSendProof::Request& proof); + void getWalletRecords(const RpcApi::GetWalletRecords::Request& req); + void setAddressLabel(const RpcApi::SetAddressLabel::Request& req); + void createAddresses(const RpcApi::CreateAddresses::Request& req); + void createAddress(const QString& label); State getState() const; bool isConnected() const; @@ -58,6 +62,9 @@ class RemoteWalletd : public QObject void sendTxReceivedSignal(const RpcApi::SentTx& tx); void proofsReceivedSignal(const RpcApi::Proofs& proofs); void checkProofReceivedSignal(const RpcApi::ProofCheck& proofCheck); + void walletRecordsReceivedSignal(const RpcApi::WalletRecords& records); + void addressLabelSetReceivedSignal(const QString& address, const QString& label); + void addressesCreatedReceivedSignal(const RpcApi::CreatedAddresses& addrs); void networkErrorSignal(const QString& errorString); void jsonParsingErrorSignal(const QString& message); @@ -77,7 +84,7 @@ class RemoteWalletd : public QObject JsonRpc::WalletClient* jsonClient_; State state_; QTimer rerunTimer_; - QTimer statusTimer_; + QTimer heartbeatTimer_; void setState(State state); virtual void authRequired(QAuthenticator* authenticator); @@ -93,6 +100,9 @@ private slots: void sendTxReceived(const RpcApi::SentTx& tx); void proofsReceived(const RpcApi::Proofs& result); void checkProofReceived(const RpcApi::ProofCheck& result); + void walletRecordsReceived(const RpcApi::WalletRecords& records); + void addressLabelSetReceived(const QString& address, const QString& label); + void addressesCreatedReceived(const RpcApi::CreatedAddresses& addrs, const QString& label); void networkError(const QString& errorString); void jsonParsingError(const QString& message); diff --git a/src/walletmodel.cpp b/src/walletmodel.cpp index 8933db4..e61cf9c 100644 --- a/src/walletmodel.cpp +++ b/src/walletmodel.cpp @@ -16,6 +16,9 @@ namespace WalletGUI { +static const int SECS_IN_MINUTE = 60; +static const int SECS_IN_HOUR = 60 * SECS_IN_MINUTE; + class TxList { public: @@ -416,6 +419,7 @@ void WalletModel::statusReceived(const RpcApi::Status& status) pimpl_->status = status; changedRoles << Qt::EditRole << Qt::DisplayRole; + emit statusUpdatedSignal(); emit dataChanged(index(0, COLUMN_TOP_BLOCK_HEIGHT), index(0, COLUMN_PEER_COUNT_SUM), changedRoles); const bool firstRequest = pimpl_->prevTopHeight == 0; @@ -547,15 +551,15 @@ QVariant WalletModel::getDisplayRoleState(const QModelIndex& index) const switch(pimpl_->walletdState) { case RemoteWalletd::State::STOPPED: - return tr("Disconnected"); + return tr("Disconnected."); case RemoteWalletd::State::CONNECTING: - return tr("Connecting to %1").arg(Settings::instance().getUserFriendlyWalletdConnectionMethod()); + return tr("Connecting to %1.").arg(Settings::instance().getUserFriendlyWalletdConnectionMethod()); case RemoteWalletd::State::CONNECTED: - return tr("Connected to %1").arg(Settings::instance().getUserFriendlyWalletdConnectionMethod()); + return tr("Connected to %1.").arg(Settings::instance().getUserFriendlyWalletdConnectionMethod()); case RemoteWalletd::State::NETWORK_ERROR: - return tr("Network error"); + return tr("Network error."); case RemoteWalletd::State::JSON_ERROR: - return tr("RPC API error"); + return tr("RPC API error."); } } } @@ -581,7 +585,7 @@ QVariant WalletModel::getDisplayRoleAddresses(const QModelIndex& index) const switch(index.column()) { case COLUMN_FIRST_ADDRESS: - return "" + pimpl_->addresses[index.row()] + ""; + return pimpl_->addresses[index.row()]; case COLUMN_WALLET_TYPE: return (pimpl_->walletType == "amethyst") ? QVariant{""+tr("HD")+""} : (pimpl_->walletType == "hardware") ? QVariant{""+tr("HW")+""} : QVariant{}; case COLUMN_WALLET_CREATION_TIMESTAMP: @@ -814,7 +818,7 @@ QVariant WalletModel::getDisplayRoleStatus(const QModelIndex& index) const } case COLUMN_LOWER_LEVEL_ERROR: // return pimpl_->status.lower_level_error; - return pimpl_->status.lower_level_error.isEmpty() || (pimpl_->status.top_block_height < pimpl_->status.top_known_block_height && pimpl_->status.lower_level_error == "SEND_ERROR") ? tr("Connected to bytecoind") : tr("Bytecoind status: %1").arg(pimpl_->status.lower_level_error); + return pimpl_->status.lower_level_error.isEmpty() || (pimpl_->status.top_block_height < pimpl_->status.top_known_block_height && pimpl_->status.lower_level_error == "SEND_ERROR") ? tr("Connected to bytecoind.") : tr("Bytecoind status: %1.").arg(pimpl_->status.lower_level_error); case COLUMN_RECOMMENDED_MAX_TRANSACTION_SIZE: return pimpl_->status.recommended_max_transaction_size; case COLUMN_TXPOOL_VERSION: @@ -828,7 +832,7 @@ QVariant WalletModel::getDisplayRoleStatus(const QModelIndex& index) const case COLUMN_KNOWN_TOP_BLOCK_HEIGHT: return pimpl_->status.top_known_block_height; case COLUMN_PEER_COUNT_SUM: - return tr("%1 peer(s)").arg(pimpl_->status.outgoing_peer_count + pimpl_->status.incoming_peer_count); + return tr("%1 peer(s).").arg(pimpl_->status.outgoing_peer_count + pimpl_->status.incoming_peer_count); } return QVariant(); @@ -1016,6 +1020,41 @@ QString WalletModel::getLowerLevelError() const return pimpl_->status.lower_level_error; } +quint64 WalletModel::getSecsSinceLastBlock() const +{ + const QDateTime currentDateTime = QDateTime::currentDateTimeUtc(); + const QDateTime lastBlockTimestamp = qMin(getLastBlockTimestamp(), currentDateTime); + return lastBlockTimestamp.secsTo(currentDateTime); +} + +QString WalletModel::getFormattedTimeSinceLastBlock() const +{ + return isThereAnyBlock() ? formatTimeDiff(getSecsSinceLastBlock()) : tr("unknown"); +} + +bool WalletModel::isThereAnyBlock() const +{ + return getLastBlockTimestamp().toMSecsSinceEpoch() > 0; +} + +bool WalletModel::isSyncronized() const +{ + return getLowerLevelError().isEmpty() && isThereAnyBlock() && (getLastBlockHeight() == getKnownBlockHeight()); +} + +quint32 WalletModel::getBlocksLeftToSync() const +{ + return getKnownBlockHeight() >= getLastBlockHeight() ? (getKnownBlockHeight() - getLastBlockHeight()) : 0; +} + +WalletModel::SyncStatus WalletModel::getSyncStatus() const +{ + const WalletModel::SyncStatus result = !isSyncronized() ? WalletModel::SyncStatus::NOT_SYNCED + : getSecsSinceLastBlock() > SECS_IN_HOUR ? WalletModel::SyncStatus::LAGGED + : WalletModel::SyncStatus::SYNCED; + return result; +} + QString WalletModel::getAddress() const { if (pimpl_->addresses.isEmpty()) @@ -1023,6 +1062,11 @@ QString WalletModel::getAddress() const return pimpl_->addresses.first(); } +bool WalletModel::isConnected() const +{ + return pimpl_->walletdState == RemoteWalletd::State::CONNECTED; +} + bool WalletModel::isAmethyst() const { return pimpl_->walletType == "amethyst" || pimpl_->walletType == "hardware"; diff --git a/src/walletmodel.h b/src/walletmodel.h index 591c56c..a7a8717 100644 --- a/src/walletmodel.h +++ b/src/walletmodel.h @@ -137,6 +137,13 @@ class WalletModel : public QAbstractItemModel ROLE_TOTAL, }; + enum class SyncStatus + { + SYNCED, + NOT_SYNCED, + LAGGED, + }; + WalletModel(QObject* parent); virtual ~WalletModel(); @@ -153,6 +160,7 @@ class WalletModel : public QAbstractItemModel void reset(); QString getAddress() const; + bool isConnected() const; bool isAmethyst() const; quint32 getLastBlockHeight() const; @@ -165,6 +173,13 @@ class WalletModel : public QAbstractItemModel quint32 getPeerCountSum() const; QString getLowerLevelError() const; + quint64 getSecsSinceLastBlock() const; + QString getFormattedTimeSinceLastBlock() const; + bool isThereAnyBlock() const; + bool isSyncronized() const; + quint32 getBlocksLeftToSync() const; + SyncStatus getSyncStatus() const; + int getTopFetchedHeight() const; int getBottomFetchedHeight() const; @@ -173,6 +188,7 @@ class WalletModel : public QAbstractItemModel void fetchedSignal(); void nothingToFetchSignal(); void netChangedSignal(const QString& net); + void statusUpdatedSignal(); public slots: void statusReceived(const RpcApi::Status& status);