Browse Source

Misc changes

wizard_redesign
tobtoht 5 months ago
parent
commit
858861436b
Signed by: tobtoht GPG Key ID: 1CADD27F41F45C3C
  1. 2
      CMakeLists.txt
  2. 2
      Dockerfile
  3. 3
      Dockerfile.windows
  4. 16
      README.md
  5. 2
      monero
  6. 112
      src/coinswidget.cpp
  7. 1
      src/coinswidget.h
  8. 3
      src/dialog/aboutdialog.cpp
  9. 19
      src/dialog/outputsweepdialog.cpp
  10. 5
      src/dialog/outputsweepdialog.h
  11. 90
      src/dialog/outputsweepdialog.ui
  12. 84
      src/dialog/transactioninfodialog.cpp
  13. 6
      src/dialog/transactioninfodialog.h
  14. 131
      src/dialog/transactioninfodialog.ui
  15. 21
      src/dialog/txconfadvdialog.ui
  16. 2
      src/dialog/viewonlydialog.cpp
  17. 3
      src/historywidget.cpp
  18. 20
      src/libwalletqt/Coins.cpp
  19. 3
      src/libwalletqt/Coins.h
  20. 5
      src/libwalletqt/Subaddress.cpp
  21. 1
      src/libwalletqt/Subaddress.h
  22. 13
      src/libwalletqt/Wallet.cpp
  23. 3
      src/libwalletqt/Wallet.h
  24. 17
      src/mainwindow.cpp
  25. 5
      src/model/CoinsModel.cpp
  26. 2
      src/model/CoinsModel.h
  27. 12
      src/model/HistoryView.cpp
  28. 4
      src/model/HistoryView.h
  29. 5
      src/model/SubaddressModel.cpp
  30. 2
      src/model/SubaddressModel.h
  31. 4
      src/model/SubaddressProxyModel.cpp
  32. 14
      src/model/SubaddressProxyModel.h
  33. 5
      src/model/TransactionHistoryModel.cpp
  34. 82
      src/receivewidget.cpp
  35. 10
      src/receivewidget.h
  36. 21
      src/receivewidget.ui
  37. 9
      src/sendwidget.ui
  38. 27
      src/settings.cpp
  39. 9
      src/settings.h
  40. 24
      src/settings.ui
  41. 4
      src/utils/config.cpp
  42. 4
      src/utils/config.h
  43. 3
      src/wizard/PageMenu.ui
  44. 1
      src/wizard/PageNetwork.h
  45. 2
      src/wizard/PageWalletRestoreKeys.h
  46. 1
      src/wizard/PageWalletRestoreSeed.cpp

2
CMakeLists.txt

@ -29,7 +29,7 @@ if(DEBUG)
set(CMAKE_VERBOSE_MAKEFILE ON)
endif()
set(MONERO_HEAD "e175e02b9b8d289bccab3ff0f3fd70c4dbf8c71f")
set(MONERO_HEAD "52acbb68c1a4cc67ee539325a8febc2c144596c4")
set(BUILD_GUI_DEPS ON)
set(ARCH "x86-64")
set(BUILD_64 ON)

2
Dockerfile

@ -42,7 +42,7 @@ RUN git clone -b v1.2.11 --depth 1 https://github.com/madler/zlib && \
make -j$THREADS install && \
rm -rf $(pwd)
RUN git clone -b tor-0.4.5.5-rc --depth 1 https://git.torproject.org/tor.git && \
RUN git clone -b tor-0.4.5.6 --depth 1 https://git.torproject.org/tor.git && \
cd tor && \
git reset --hard b36a00e9a9d3eb4b2949951afaa72e45fb7e68cd && \
./autogen.sh && \

3
Dockerfile.windows

@ -146,8 +146,7 @@ RUN wget https://github.com/libevent/libevent/releases/download/release-2.1.11-s
make -j$THREADS install && \
rm -rf $(pwd)
ENV TOR_VERSION=0.4.5.5-rc
RUN git clone -b tor-0.4.5.5-rc --depth 1 https://git.torproject.org/tor.git && \
RUN git clone -b tor-0.4.5.6 --depth 1 https://git.torproject.org/tor.git && \
cd tor && \
git reset --hard b36a00e9a9d3eb4b2949951afaa72e45fb7e68cd && \
./autogen.sh && \

16
README.md

@ -1,27 +1,19 @@
# Feather - a free Monero desktop wallet
[![Build Status](https://build.featherwallet.org/api/badges/feather/feather/status.svg)](https://build.featherwallet.org/feather/feather)
Feather is a free, open-source Monero wallet for Linux, Tails, macOS and Windows. It is written in C++ with the Qt framework.
Feather is a free, open-source Monero client Linux with ports for Mac OS and Windows written in C++ with the Qt framework.
Copyright (c) 2020-2021, The Monero Project.
## Development resources
## Resources
* Web: [featherwallet.org](https://featherwallet.org)
* Git: [git.featherwallet.org/feather/feather](https://git.featherwallet.org/feather/feather)
* Mail: dev@featherwallet.org
* IRC: `#feather` on OFTC
* Development builds: [build.featherwallet.org/files](https://build.featherwallet.org/files/)
Copyright (c) 2020-2021 The Monero Project.
## Compiling Feather from source
Feather uses Monero, as such it requires the same dependencies as outlined in [Monero's README](https://github.com/monero-project/monero#compiling-monero-from-source). Additionally, Feather uses:
- Qt 5.15.0
- libqrencode
- openpgp
See [BUILDING.md](https://git.featherwallet.org/feather/feather/src/branch/master/BUILDING.md) for information on how to compile a build.
See [BUILDING.md](https://git.featherwallet.org/feather/feather/src/branch/master/BUILDING.md) for information on how to build from source.
## Supporting the project

2
monero

@ -1 +1 @@
Subproject commit e175e02b9b8d289bccab3ff0f3fd70c4dbf8c71f
Subproject commit 52acbb68c1a4cc67ee539325a8febc2c144596c4

112
src/coinswidget.cpp

@ -89,19 +89,12 @@ void CoinsWidget::showContextMenu(const QPoint &point) {
menu->addAction(m_thawAllSelectedAction);
}
else {
QModelIndex index = ui->coins->indexAt(point);
if (!index.isValid()) {
return;
}
int row = m_proxyModel->mapToSource(index).row();
CoinsInfo* c = this->currentEntry();
if (!c) return;
bool isSpent, isFrozen, isUnlocked;
m_coins->coin(row, [&isSpent, &isFrozen, &isUnlocked](CoinsInfo &c) {
isSpent = c.spent();
isFrozen = c.frozen();
isUnlocked = c.unlocked();
});
bool isSpent = c->spent();
bool isFrozen = c->frozen();
bool isUnlocked = c->unlocked();
menu->addMenu(m_copyMenu);
@ -166,44 +159,28 @@ void CoinsWidget::thawAllSelected() {
}
void CoinsWidget::viewOutput() {
QModelIndex index = ui->coins->currentIndex();
int row = m_proxyModel->mapToSource(index).row();
QPointer<CoinsInfo> c;
m_coins->coin(row, [&c](CoinsInfo &cInfo) {
c = &cInfo;
});
CoinsInfo* c = this->currentEntry();
if (!c) return;
if (c) {
auto * dialog = new OutputInfoDialog(c, this);
dialog->show();
}
auto * dialog = new OutputInfoDialog(c, this);
dialog->show();
}
void CoinsWidget::onSweepOutput() {
QModelIndex index = ui->coins->currentIndex();
int row = m_proxyModel->mapToSource(index).row();
CoinsInfo* c = this->currentEntry();
if (!c) return;
QString keyImage;
bool keyImageKnown;
m_coins->coin(row, [&keyImage, &keyImageKnown](CoinsInfo &c) {
keyImageKnown = c.keyImageKnown();
keyImage = c.keyImage();
});
QString keyImage = c->keyImage();
qCritical() << "key image: " << keyImage;
if (!keyImageKnown) {
if (!c->keyImageKnown()) {
QMessageBox::warning(this, "Unable to sweep output", "Unable to sweep output: key image unknown");
return;
}
auto * dialog = new OutputSweepDialog(this);
auto *dialog = new OutputSweepDialog(this, c);
int ret = dialog->exec();
if (!ret) return;
qCritical() << "key image: " << keyImage;
emit sweepOutput(keyImage, dialog->address(), dialog->churn(), dialog->outputs());
dialog->deleteLater();
}
@ -213,39 +190,46 @@ void CoinsWidget::resetModel() {
}
void CoinsWidget::copy(copyField field) {
QModelIndex index = ui->coins->currentIndex();
int row = m_proxyModel->mapToSource(index).row();
CoinsInfo* c = this->currentEntry();
if (!c) return;
QString data;
m_coins->coin(row, [field, &data](CoinsInfo &c) {
switch (field) {
case PubKey:
data = c.pubKey();
break;
case KeyImage:
data = c.keyImage();
break;
case TxID:
data = c.hash();
break;
case Address:
data = c.address();
break;
case Label:
data = c.addressLabel();
break;
case Height:
data = QString::number(c.blockHeight());
break;
case Amount:
data = c.displayAmount();
break;
}
});
switch (field) {
case PubKey:
data = c->pubKey();
break;
case KeyImage:
data = c->keyImage();
break;
case TxID:
data = c->hash();
break;
case Address:
data = c->address();
break;
case Label:
data = c->addressLabel();
break;
case Height:
data = QString::number(c->blockHeight());
break;
case Amount:
data = c->displayAmount();
break;
}
Utils::copyToClipboard(data);
}
CoinsInfo* CoinsWidget::currentEntry() {
QModelIndexList list = ui->coins->selectionModel()->selectedRows();
if (list.size() == 1) {
return m_model->entryFromIndex(m_proxyModel->mapToSource(list.first()));
} else {
return nullptr;
}
}
CoinsWidget::~CoinsWidget() {
delete ui;
}

1
src/coinswidget.h

@ -73,6 +73,7 @@ private:
void showContextMenu(const QPoint & point);
void copy(copyField field);
CoinsInfo* currentEntry();
};

3
src/dialog/aboutdialog.cpp

@ -9,6 +9,7 @@
AboutDialog::AboutDialog(QWidget *parent)
: QDialog(parent)
, ui(new Ui::AboutDialog)
, m_model(new QStringListModel(this))
{
ui->setupUi(this);
this->setWindowIcon(QIcon("://assets/images/appicons/64x64.png"));
@ -26,8 +27,6 @@ AboutDialog::AboutDialog(QWidget *parent)
auto ack_text = Utils::barrayToString(ack);
ui->ackText->setText(ack_text);
m_model = new QStringListModel(this);
QString contributors = Utils::barrayToString(Utils::fileOpenQRC(":assets/contributors.txt"));
QStringList contributor_list = contributors.split("\n");
m_model->setStringList(contributor_list);

19
src/dialog/outputsweepdialog.cpp

@ -3,15 +3,19 @@
#include "ui_outputsweepdialog.h"
#include "outputsweepdialog.h"
#include "libwalletqt/WalletManager.h"
OutputSweepDialog::OutputSweepDialog(QWidget *parent)
OutputSweepDialog::OutputSweepDialog(QWidget *parent, CoinsInfo* coin)
: QDialog(parent)
, ui(new Ui::OutputSweepDialog)
{
ui->setupUi(this);
m_amount = coin->amount();
connect(ui->checkBox_churn, &QCheckBox::toggled, [&](bool toggled){
ui->lineEdit_address->setEnabled(!toggled);
ui->lineEdit_address->setText(toggled ? "Primary address" : "");
});
connect(ui->buttonBox, &QDialogButtonBox::accepted, [&](){
@ -20,6 +24,19 @@ OutputSweepDialog::OutputSweepDialog(QWidget *parent)
m_outputs = ui->spinBox_numOutputs->value();
});
connect(ui->spinBox_numOutputs, QOverload<int>::of(&QSpinBox::valueChanged), [this](int value){
if (value == 1) {
ui->label_split->setText("");
return;
}
QString origAmount = WalletManager::displayAmount(m_amount);
QString splitAmount = WalletManager::displayAmount(m_amount / value);
ui->label_split->setText(QString("%1 XMR ≈ %2x %3 XMR").arg(origAmount, QString::number(value), splitAmount));
});
ui->label_split->setText("");
this->adjustSize();
}

5
src/dialog/outputsweepdialog.h

@ -5,6 +5,7 @@
#define FEATHER_OUTPUTSWEEPDIALOG_H
#include <QDialog>
#include "libwalletqt/CoinsInfo.h"
namespace Ui {
class OutputSweepDialog;
@ -15,7 +16,7 @@ class OutputSweepDialog : public QDialog
Q_OBJECT
public:
explicit OutputSweepDialog(QWidget *parent = nullptr);
explicit OutputSweepDialog(QWidget *parent, CoinsInfo* coin);
~OutputSweepDialog() override;
QString address();
@ -25,6 +26,8 @@ public:
private:
Ui::OutputSweepDialog *ui;
uint64_t m_amount;
QString m_address;
bool m_churn;
int m_outputs;

90
src/dialog/outputsweepdialog.ui

@ -6,16 +6,22 @@
<rect>
<x>0</x>
<y>0</y>
<width>623</width>
<height>231</height>
<width>720</width>
<height>193</height>
</rect>
</property>
<property name="windowTitle">
<string>Sweep output</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<property name="spacing">
<number>10</number>
</property>
<item>
<layout class="QFormLayout" name="formLayout">
<property name="verticalSpacing">
<number>0</number>
</property>
<item row="0" column="0">
<widget class="QLabel" name="label">
<property name="text">
@ -36,48 +42,60 @@
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QCheckBox" name="checkBox_churn">
<property name="text">
<string>Send to self (churn)</string>
</property>
</widget>
</item>
</layout>
</item>
<item>
<widget class="QCheckBox" name="checkBox_churn">
<property name="text">
<string>Send to self (churn)</string>
</property>
</widget>
<layout class="QHBoxLayout" name="horizontalLayout_4">
<item>
<widget class="QLabel" name="label_2">
<property name="text">
<string>Number of outputs:</string>
</property>
</widget>
</item>
<item>
<widget class="QSpinBox" name="spinBox_numOutputs">
<property name="minimum">
<number>1</number>
</property>
<property name="maximum">
<number>16</number>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer_2">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
</layout>
</item>
<item>
<widget class="QGroupBox" name="groupBox">
<property name="title">
<string>Advanced options</string>
<widget class="QLabel" name="label_split">
<property name="enabled">
<bool>false</bool>
</property>
<property name="checkable">
<bool>true</bool>
<property name="text">
<string>1.000000000000 XMR ≈ 5x 0.200000000000 XMR</string>
</property>
<property name="checked">
<bool>false</bool>
<property name="textInteractionFlags">
<set>Qt::LinksAccessibleByMouse|Qt::TextSelectableByMouse</set>
</property>
<layout class="QFormLayout" name="formLayout_2">
<property name="fieldGrowthPolicy">
<enum>QFormLayout::ExpandingFieldsGrow</enum>
</property>
<item row="0" column="0">
<widget class="QLabel" name="label_2">
<property name="text">
<string>Number of outputs:</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QSpinBox" name="spinBox_numOutputs">
<property name="minimum">
<number>1</number>
</property>
<property name="maximum">
<number>16</number>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>

84
src/dialog/transactioninfodialog.cpp

@ -7,10 +7,15 @@
#include "libwalletqt/CoinsInfo.h"
#include "libwalletqt/WalletManager.h"
#include "libwalletqt/Transfer.h"
#include "libwalletqt/TransactionHistory.h"
#include "utils.h"
#include "utils/ColorScheme.h"
#include "model/ModelUtils.h"
#include "config.h"
#include "appcontext.h"
#include <QMessageBox>
#include <QScrollBar>
TransactionInfoDialog::TransactionInfoDialog(Wallet *wallet, TransactionInfo *txInfo, QWidget *parent)
: QDialog(parent)
@ -23,32 +28,26 @@ TransactionInfoDialog::TransactionInfoDialog(Wallet *wallet, TransactionInfo *tx
m_txid = txInfo->hash();
ui->label_txid->setText(m_txid);
QString txKey = m_wallet->getTxKey(txInfo->hash());
if (txKey.isEmpty()) {
m_txKey = m_wallet->getTxKey(txInfo->hash());
if (m_txKey.isEmpty()) {
ui->btn_CopyTxKey->setEnabled(false);
ui->btn_CopyTxKey->setToolTip("Transaction key unknown");
}
m_txKey = txKey;
connect(ui->btn_CopyTxKey, &QPushButton::pressed, this, &TransactionInfoDialog::copyTxKey);
connect(ui->btn_createTxProof, &QPushButton::pressed, this, &TransactionInfoDialog::createTxProof);
QString blockHeight = QString::number(txInfo->blockHeight());
if (blockHeight == "0")
blockHeight = "Unconfirmed";
connect(m_wallet, &Wallet::newBlock, this, &TransactionInfoDialog::updateData);
ui->label_status->setText(QString("Status: %1 confirmations").arg(txInfo->confirmations()));
ui->label_date->setText(QString("Date: %1").arg(txInfo->timestamp().toString("yyyy-MM-dd HH:mm")));
ui->label_blockHeight->setText(QString("Block height: %1").arg(blockHeight));
this->setData(txInfo);
QString direction = txInfo->direction() == TransactionInfo::Direction_In ? "received" : "sent";
ui->label_amount->setText(QString("Amount %1: %2").arg(direction, txInfo->displayAmount()));
QString fee = txInfo->fee().isEmpty() ? "n/a" : txInfo->fee();
ui->label_fee->setText(QString("Fee: %1").arg(txInfo->isCoinbase() ? WalletManager::displayAmount(0) : fee));
ui->label_unlockTime->setText(QString("Unlock time: %1 (height)").arg(txInfo->unlockTime()));
qDebug() << m_wallet->coins()->coins_from_txid(txInfo->hash());
if (AppContext::txCache.contains(txInfo->hash()) && (txInfo->isFailed() || txInfo->isPending()) && txInfo->direction() != TransactionInfo::Direction_In) {
connect(ui->btn_rebroadcastTx, &QPushButton::pressed, [this]{
emit resendTranscation(m_txid);
});
} else {
ui->btn_rebroadcastTx->hide();
}
QTextCursor cursor = ui->destinations->textCursor();
for (const auto& transfer : txInfo->transfers()) {
@ -59,15 +58,66 @@ TransactionInfoDialog::TransactionInfoDialog(Wallet *wallet, TransactionInfo *tx
cursor.insertText(QString(" %1").arg(amount), QTextCharFormat());
cursor.insertBlock();
}
if (txInfo->transfers().size() == 0) {
ui->frameDestinations->hide();
}
m_txProofDialog = new TxProofDialog(this, m_wallet, txInfo);
QCoreApplication::processEvents();
qreal lineHeight = QFontMetrics(ui->destinations->document()->defaultFont()).height();
qreal docHeight = txInfo->transfers().size();
int h = int(docHeight * (lineHeight + 2) + 11);
h = qMin(qMax(h, 100), 600);
ui->destinations->setMinimumHeight(h);
ui->destinations->setMaximumHeight(h);
ui->destinations->verticalScrollBar()->hide();
this->adjustSize();
}
void TransactionInfoDialog::setData(TransactionInfo* tx) {
QString blockHeight = QString::number(tx->blockHeight());
if (tx->isFailed()) {
ui->label_status->setText("Status: Failed (node was unable to relay transaction)");
}
if (blockHeight == "0") {
ui->label_status->setText("Status: Unconfirmed (in mempool)");
}
else {
QString dateTimeFormat = QString("%1 %2").arg(config()->get(Config::dateFormat).toString(), config()->get(Config::timeFormat).toString());
QString date = tx->timestamp().toString(dateTimeFormat);
QString statusText = QString("Status: Included in block %1 (%2 confirmations) on %3").arg(blockHeight, QString::number(tx->confirmations()), date);
ui->label_status->setText(statusText);
}
if (tx->confirmationsRequired() > tx->confirmations()) {
bool mandatoryLock = tx->confirmationsRequired() == 10;
QString confsRequired = QString::number(tx->confirmationsRequired() - tx->confirmations());
ui->label_lock->setText(QString("Lock: Outputs become spendable in %1 blocks (%2)").arg(confsRequired, mandatoryLock ? "consensus rule" : "specified by sender"));
} else {
ui->label_lock->setText("Lock: Outputs are spendable");
}
QString direction = tx->direction() == TransactionInfo::Direction_In ? "received" : "sent";
ui->label_amount->setText(QString("Amount %1: %2").arg(direction, tx->displayAmount()));
QString fee = tx->fee().isEmpty() ? "n/a" : tx->fee();
ui->label_fee->setText(QString("Fee: %1 XMR").arg(tx->isCoinbase() ? WalletManager::displayAmount(0) : fee));
}
void TransactionInfoDialog::updateData() {
if (!m_wallet) return;
TransactionInfo* tx = m_wallet->history()->transaction(m_txid);
if (!tx) return;
this->setData(tx);
}
void TransactionInfoDialog::copyTxKey() {
Utils::copyToClipboard(m_txKey);
}

6
src/dialog/transactioninfodialog.h

@ -24,9 +24,14 @@ public:
explicit TransactionInfoDialog(Wallet *wallet, TransactionInfo *txInfo, QWidget *parent = nullptr);
~TransactionInfoDialog() override;
signals:
void resendTranscation(const QString &txid);
private:
void copyTxKey();
void createTxProof();
void setData(TransactionInfo* tx);
void updateData();
Ui::TransactionInfoDialog *ui;
@ -35,6 +40,7 @@ private:
Wallet *m_wallet;
QString m_txKey;
QString m_txid;
QTimer m_updateTimer;
};
#endif //FEATHER_TRANSACTIONINFODIALOG_H

131
src/dialog/transactioninfodialog.ui

@ -6,17 +6,14 @@
<rect>
<x>0</x>
<y>0</y>
<width>829</width>
<height>493</height>
<width>929</width>
<height>631</height>
</rect>
</property>
<property name="windowTitle">
<string>Transaction</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout_4">
<item>
<layout class="QHBoxLayout" name="horizontalLayout_2"/>
</item>
<item>
<widget class="QGroupBox" name="groupBox">
<property name="title">
@ -37,81 +34,46 @@
</widget>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout">
<layout class="QVBoxLayout" name="verticalLayout_3">
<item>
<layout class="QVBoxLayout" name="verticalLayout_2">
<item>
<widget class="QLabel" name="label_status">
<property name="text">
<string>Status:</string>
</property>
<property name="textInteractionFlags">
<set>Qt::LinksAccessibleByMouse|Qt::TextSelectableByMouse</set>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="label_date">
<property name="text">
<string>Date:</string>
</property>
<property name="textInteractionFlags">
<set>Qt::LinksAccessibleByMouse|Qt::TextSelectableByMouse</set>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="label_blockHeight">
<property name="text">
<string>Block height:</string>
</property>
<property name="textInteractionFlags">
<set>Qt::LinksAccessibleByMouse|Qt::TextSelectableByMouse</set>
</property>
</widget>
</item>
</layout>
<widget class="QLabel" name="label_status">
<property name="text">
<string>Status:</string>
</property>
<property name="textInteractionFlags">
<set>Qt::LinksAccessibleByMouse|Qt::TextSelectableByMouse</set>
</property>
</widget>
</item>
<item>
<widget class="Line" name="line">
<property name="orientation">
<enum>Qt::Vertical</enum>
<widget class="QLabel" name="label_lock">
<property name="text">
<string>Lock: </string>
</property>
<property name="textInteractionFlags">
<set>Qt::LinksAccessibleByMouse|Qt::TextSelectableByMouse</set>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="label_amount">
<property name="text">
<string>Amount received:</string>
</property>
<property name="textInteractionFlags">
<set>Qt::LinksAccessibleByMouse|Qt::TextSelectableByMouse</set>
</property>
</widget>
</item>
<item>
<layout class="QVBoxLayout" name="verticalLayout_3">
<item>
<widget class="QLabel" name="label_amount">
<property name="text">
<string>Amount received:</string>
</property>
<property name="textInteractionFlags">
<set>Qt::LinksAccessibleByMouse|Qt::TextSelectableByMouse</set>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="label_fee">
<property name="text">
<string>Fee: </string>
</property>
<property name="textInteractionFlags">
<set>Qt::LinksAccessibleByMouse|Qt::TextSelectableByMouse</set>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="label_unlockTime">
<property name="text">
<string>Unlock time:</string>
</property>
<property name="textInteractionFlags">
<set>Qt::LinksAccessibleByMouse|Qt::TextSelectableByMouse</set>
</property>
</widget>
</item>
</layout>
<widget class="QLabel" name="label_fee">
<property name="text">
<string>Fee: </string>
</property>
<property name="textInteractionFlags">
<set>Qt::LinksAccessibleByMouse|Qt::TextSelectableByMouse</set>
</property>
</widget>
</item>
</layout>
</item>
@ -154,6 +116,12 @@
</property>
<item>
<widget class="QLabel" name="label_destinations">
<property name="sizePolicy">
<sizepolicy hsizetype="Maximum" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>Destinations:</string>
</property>
@ -162,17 +130,11 @@
<item>
<widget class="QTextEdit" name="destinations">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Expanding">
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>0</width>
<height>100</height>
</size>
</property>
<property name="readOnly">
<bool>true</bool>
</property>
@ -187,7 +149,7 @@
<enum>Qt::Vertical</enum>
</property>
<property name="sizeType">
<enum>QSizePolicy::Minimum</enum>
<enum>QSizePolicy::Expanding</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
@ -213,6 +175,13 @@
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="btn_rebroadcastTx">
<property name="text">
<string>Rebroadcast Transaction</string>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer">
<property name="orientation">

21
src/dialog/txconfadvdialog.ui

@ -53,6 +53,9 @@
<property name="text">
<string>TextLabel</string>
</property>
<property name="textInteractionFlags">
<set>Qt::LinksAccessibleByMouse|Qt::TextSelectableByMouse</set>
</property>
</widget>
</item>
<item row="1" column="0">
@ -67,6 +70,9 @@
<property name="text">
<string>TextLabel</string>
</property>
<property name="textInteractionFlags">
<set>Qt::LinksAccessibleByMouse|Qt::TextSelectableByMouse</set>
</property>
</widget>
</item>
<item row="2" column="1">
@ -88,6 +94,9 @@
<property name="text">
<string>TextLabel</string>
</property>
<property name="textInteractionFlags">
<set>Qt::LinksAccessibleByMouse|Qt::TextSelectableByMouse</set>
</property>
</widget>
</item>
</layout>
@ -106,6 +115,9 @@
<property name="text">
<string>Description:</string>
</property>
<property name="textInteractionFlags">
<set>Qt::LinksAccessibleByMouse|Qt::TextSelectableByMouse</set>
</property>
</widget>
</item>
<item>
@ -113,6 +125,9 @@
<property name="text">
<string>Size: </string>
</property>
<property name="textInteractionFlags">
<set>Qt::LinksAccessibleByMouse|Qt::TextSelectableByMouse</set>
</property>
</widget>
</item>
<item>
@ -120,6 +135,9 @@
<property name="text">
<string>Unlock time: </string>
</property>
<property name="textInteractionFlags">
<set>Qt::LinksAccessibleByMouse|Qt::TextSelectableByMouse</set>
</property>
</widget>
</item>
<item>
@ -127,6 +145,9 @@
<property name="text">
<string>Ringsize:</string>
</property>
<property name="textInteractionFlags">
<set>Qt::LinksAccessibleByMouse|Qt::TextSelectableByMouse</set>
</property>
</widget>
</item>
</layout>

2
src/dialog/viewonlydialog.cpp

@ -26,7 +26,7 @@ ViewOnlyDialog::ViewOnlyDialog(AppContext *ctx, QWidget *parent)
}
void ViewOnlyDialog::onWriteViewOnlyWallet(){
QString fn = QFileDialog::getSaveFileName(this, "Save .keys wallet file", QDir::homePath(), "Monero wallet (*.keys)");
QString fn = QFileDialog::getSaveFileName(this, "Save .keys wallet file", Utils::defaultWalletDir(), "Monero wallet (*.keys)");
if(fn.isEmpty()) return;
if(!fn.endsWith(".keys")) fn += ".keys";

3
src/historywidget.cpp

@ -101,6 +101,9 @@ void HistoryWidget::showTxDetails() {
if (!tx) return;
auto *dialog = new TransactionInfoDialog(m_wallet, tx, this);
connect(dialog, &TransactionInfoDialog::resendTranscation, [this](const QString &txid){
emit resendTransaction(txid);
});
dialog->show();
}

20
src/libwalletqt/Coins.cpp

@ -24,6 +24,11 @@ bool Coins::coin(int index, std::function<void (CoinsInfo &)> callback)
return true;
}
CoinsInfo* Coins::coin(int index)
{
return m_tinfo.value(index);
}
void Coins::refresh(quint32 accountIndex)
{
emit refreshStarted();
@ -74,18 +79,17 @@ void Coins::thaw(int index) const
emit coinThawed();
}
QStringList Coins::coins_from_txid(const QString &txid)
QVector<CoinsInfo*> Coins::coins_from_txid(const QString &txid)
{
QStringList keyimages;
QVector<CoinsInfo*> coins;
for (int i = 0; i < this->count(); i++) {
this->coin(i, [&keyimages, &txid](const CoinsInfo &x) {
if (x.hash() == txid) {
keyimages += x.keyImage();
}
});
CoinsInfo* coin = this->coin(i);
if (coin->hash() == txid) {
coins.append(coin);
}
}
return keyimages;
return coins;
}
Coins::Coins(Monero::Coins *pimpl, QObject *parent)

3
src/libwalletqt/Coins.h

@ -25,11 +25,12 @@ Q_OBJECT
public:
Q_INVOKABLE bool coin(int index, std::function<void (CoinsInfo &)> callback);
Q_INVOKABLE CoinsInfo * coin(int index);
Q_INVOKABLE void refresh(quint32 accountIndex);
Q_INVOKABLE void refreshUnlocked();
Q_INVOKABLE void freeze(int index) const;
Q_INVOKABLE void thaw(int index) const;
Q_INVOKABLE QStringList coins_from_txid(const QString &txid); // Todo: return CoinsInfo vector
Q_INVOKABLE QVector<CoinsInfo*> coins_from_txid(const QString &txid);
quint64 count() const;

5
src/libwalletqt/Subaddress.cpp

@ -78,3 +78,8 @@ quint64 Subaddress::count() const
return m_rows.size();
}
Monero::SubaddressRow* Subaddress::row(int index) const
{
return m_rows.value(index);
}

1
src/libwalletqt/Subaddress.h

@ -23,6 +23,7 @@ public:
Q_INVOKABLE void refresh(quint32 accountIndex) const;
Q_INVOKABLE quint64 unusedLookahead() const;
quint64 count() const;
Monero::SubaddressRow* row(int index) const;
signals:
void refreshStarted() const;

13
src/libwalletqt/Wallet.cpp

@ -293,6 +293,11 @@ bool Wallet::viewOnly() const
return m_walletImpl->watchOnly();
}
bool Wallet::isDeterministic() const
{
return m_walletImpl->isDeterministic();
}
quint64 Wallet::balance() const
{
return balance(m_currentSubaddressAccount);
@ -400,9 +405,11 @@ void Wallet::refreshHeightAsync()
daemonHeightFuture.second.waitForFinished();
targetHeightFuture.second.waitForFinished();
setConnectionStatus(ConnectionStatus_Connected);
emit heightRefreshed(walletHeight, daemonHeight, targetHeight);
if (daemonHeight > 0 && targetHeight > 0) {
emit heightRefreshed(walletHeight, daemonHeight, targetHeight);
qDebug() << "Setting connection status from refreshHeightAsync";
setConnectionStatus(ConnectionStatus_Connected);
}
});
}

3
src/libwalletqt/Wallet.h

@ -224,6 +224,9 @@ public:
//! returns if view only wallet
Q_INVOKABLE bool viewOnly() const;
//! return true if deterministic keys
Q_INVOKABLE bool isDeterministic() const;
Q_INVOKABLE void refreshHeightAsync();
//! export/import key images

17
src/mainwindow.cpp

@ -597,7 +597,7 @@ void MainWindow::onWalletOpened() {
// receive page
m_ctx->currentWallet->subaddress()->refresh( m_ctx->currentWallet->currentSubaddressAccount());
ui->receiveWidget->setModel( m_ctx->currentWallet->subaddressModel(), m_ctx->currentWallet->subaddress());
ui->receiveWidget->setModel( m_ctx->currentWallet->subaddressModel(), m_ctx->currentWallet);
if (m_ctx->currentWallet->subaddress()->count() == 1) {
for (int i = 0; i < 10; i++) {
m_ctx->currentWallet->subaddress()->addRow(m_ctx->currentWallet->currentSubaddressAccount(), "");
@ -837,6 +837,16 @@ void MainWindow::showWalletInfoDialog() {
}
void MainWindow::showSeedDialog() {
if (m_ctx->currentWallet->viewOnly()) {
QMessageBox::information(this, "Information", "Wallet is view-only and has no seed.\n\nTo obtain wallet keys go to Wallet -> View-Only");
return;
}
if (!m_ctx->currentWallet->isDeterministic()) {
QMessageBox::information(this, "Information", "Wallet is non-deterministic and has no seed.\n\nTo obtain wallet keys go to Wallet -> Keys");
return;
}
auto *dialog = new SeedDialog(m_ctx->currentWallet, this);
dialog->exec();
dialog->deleteLater();
@ -1303,6 +1313,11 @@ void MainWindow::updateNetStats() {
return;
}
if (m_ctx->currentWallet->connectionStatus() == Wallet::ConnectionStatus_Disconnected) {
m_statusLabelNetStats->setText("");
return;
}
if (m_ctx->currentWallet->connectionStatus() == Wallet::ConnectionStatus_Connected && m_ctx->currentWallet->synchronized()) {
m_statusLabelNetStats->setText("");
return;

5
src/model/CoinsModel.cpp

@ -197,4 +197,9 @@ QVariant CoinsModel::parseTransactionInfo(const CoinsInfo &cInfo, int column, in
return QVariant();
}
}
}
CoinsInfo* CoinsModel::entryFromIndex(const QModelIndex &index) const {
Q_ASSERT(index.isValid() && index.row() < m_coins->count());
return m_coins->coin(index.row());
}

2
src/model/CoinsModel.h

@ -43,6 +43,8 @@ public:
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
QVariant headerData(int section, Qt::Orientation orientation, int role) const override;
CoinsInfo* entryFromIndex(const QModelIndex &index) const;
public slots:
void startReset();
void endReset();

12
src/model/HistoryView.cpp

@ -4,6 +4,7 @@
#include "HistoryView.h"
#include "TransactionHistoryProxyModel.h"
#include "libwalletqt/TransactionInfo.h"
#include <QHeaderView>
#include <QMenu>
@ -186,3 +187,14 @@ void HistoryView::resetViewToDefaults()
fitColumnsToWindow();
}
}
void HistoryView::keyPressEvent(QKeyEvent *event) {
TransactionInfo* tx = this->currentEntry();
if (event->matches(QKeySequence::Copy) && tx) {
Utils::copyToClipboard(tx->hash());
}
else {
QTreeView::keyPressEvent(event);
}
}

4
src/model/HistoryView.h

@ -4,6 +4,7 @@
#ifndef FEATHER_HISTORYVIEW_H
#define FEATHER_HISTORYVIEW_H
#include <QKeyEvent>
#include <QTreeView>
#include <QActionGroup>
@ -29,6 +30,9 @@ private slots:
void fitColumnsToContents();
void resetViewToDefaults();
protected:
void keyPressEvent(QKeyEvent *event);
private:
TransactionHistoryModel* sourceModel();

5
src/model/SubaddressModel.cpp

@ -180,3 +180,8 @@ bool SubaddressModel::isShowFullAddresses() const {
int SubaddressModel::unusedLookahead() const {
return m_subaddress->unusedLookahead();
}
Monero::SubaddressRow* SubaddressModel::entryFromIndex(const QModelIndex &index) const {
Q_ASSERT(index.isValid() && index.row() < m_subaddress->count());
return m_subaddress->row(index.row());
}

2
src/model/SubaddressModel.h

@ -38,6 +38,8 @@ public:
bool isShowFullAddresses() const;
void setShowFullAddresses(bool show);
Monero::SubaddressRow* entryFromIndex(const QModelIndex &index) const;
int unusedLookahead() const;
public slots:

4
src/model/SubaddressProxyModel.cpp

@ -29,6 +29,10 @@ bool SubaddressProxyModel::filterAcceptsRow(int sourceRow, const QModelIndex &so
if (sourceRow == 0 && m_hidePrimary)
return false;
if (!m_showHidden && m_hiddenAddresses.contains(address)) {
return false;
}
if (!m_searchRegExp.isEmpty()) {
return address.contains(m_searchCaseSensitiveRegExp) || label.contains(m_searchRegExp);
}

14
src/model/SubaddressProxyModel.h

@ -22,16 +22,30 @@ public slots:
m_searchCaseSensitiveRegExp.setPattern(searchString);
invalidateFilter();
}
void setShowUsed(const bool showUsed){
m_showUsed = showUsed;
invalidateFilter();
}
void setShowHidden(const bool showHidden){
m_showHidden = showHidden;
invalidateFilter();
}
void setHiddenAddresses(const QStringList& hiddenAddresses) {
m_hiddenAddresses = hiddenAddresses;
invalidateFilter();
}
private:
Subaddress *m_subaddress;
QStringList m_hiddenAddresses;
QRegExp m_searchRegExp;
QRegExp m_searchCaseSensitiveRegExp;
bool m_showUsed = false;
bool m_showHidden = false;
bool m_hidePrimary;
};

5
src/model/TransactionHistoryModel.cpp

@ -141,7 +141,10 @@ QVariant TransactionHistoryModel::parseTransactionInfo(const TransactionInfo &tI
switch (column)
{
case Column::Date:
return tInfo.timestamp().toString("yyyy-MM-dd HH:mm ");
{
return tInfo.timestamp().toString(QString("%1 %2 ").arg(config()->get(Config::dateFormat).toString(),
config()->get(Config::timeFormat).toString()));
}
case Column::Description:
return tInfo.description();
case Column::Amount:

82
src/receivewidget.cpp

@ -35,13 +35,19 @@ ReceiveWidget::ReceiveWidget(QWidget *parent) :
connect(ui->qrCode, &ClickableLabel::clicked, this, &ReceiveWidget::showQrCodeDialog);
connect(ui->label_addressSearch, &QLineEdit::textChanged, this, &ReceiveWidget::setSearchFilter);
connect(ui->check_showUsed, &QCheckBox::clicked, this, &ReceiveWidget::setShowUsedAddresses);
connect(ui->check_showHidden, &QCheckBox::clicked, this, &ReceiveWidget::setShowHiddenAddresses);
}
void ReceiveWidget::setModel(SubaddressModel * model, Subaddress * subaddress) {
m_subaddress = subaddress;
void ReceiveWidget::setModel(SubaddressModel * model, Wallet * wallet) {
m_wallet = wallet;
m_subaddress = wallet->subaddress();
m_model = model;
m_proxyModel = new SubaddressProxyModel(this, subaddress);
m_proxyModel = new SubaddressProxyModel(this, m_subaddress);
m_proxyModel->setSourceModel(m_model);
m_proxyModel->setHiddenAddresses(this->getHiddenAddresses());
ui->addresses->setModel(m_proxyModel);
ui->addresses->setColumnHidden(SubaddressModel::isUsed, true);
@ -74,13 +80,13 @@ void ReceiveWidget::editLabel() {
}
void ReceiveWidget::showContextMenu(const QPoint &point) {
QModelIndex index = ui->addresses->indexAt(point);
if (!index.isValid()) {
return;
}
Monero::SubaddressRow* row = this->currentEntry();
if (!row) return;
QString address = QString::fromStdString(row->getAddress());
bool isUsed = row->isUsed();
auto *menu = new QMenu(ui->addresses);
bool isUsed = index.model()->data(index.siblingAtColumn(SubaddressModel::isUsed), Qt::UserRole).toBool();
menu->addAction(QIcon(":/assets/images/copy.png"), "Copy address", this, &ReceiveWidget::copyAddress);
menu->addAction(QIcon(":/assets/images/copy.png"), "Copy label", this, &ReceiveWidget::copyLabel);
@ -90,6 +96,13 @@ void ReceiveWidget::showContextMenu(const QPoint &point) {
menu->addAction(m_showTransactionsAction);
}
QStringList hiddenAddresses = this->getHiddenAddresses();
if (hiddenAddresses.contains(address)) {
menu->addAction("Show address", this, &ReceiveWidget::showAddress);
} else {
menu->addAction("Hide address", this, &ReceiveWidget::hideAddress);
}
menu->popup(ui->addresses->viewport()->mapToGlobal(point));
}
@ -117,6 +130,11 @@ void ReceiveWidget::setShowUsedAddresses(bool show) {
m_proxyModel->setShowUsed(show);
}
void ReceiveWidget::setShowHiddenAddresses(bool show) {
if (!m_proxyModel) return;
m_proxyModel->setShowHidden(show);
}
void ReceiveWidget::setSearchFilter(const QString &filter) {
if (!m_proxyModel) return;
m_proxyModel->setSearchFilter(filter);
@ -128,6 +146,24 @@ void ReceiveWidget::showHeaderMenu(const QPoint& position)
m_headerMenu->exec(QCursor::pos());
}
void ReceiveWidget::hideAddress()
{
Monero::SubaddressRow* row = this->currentEntry();
if (!row) return;
QString address = QString::fromStdString(row->getAddress());
this->addHiddenAddress(address);
m_proxyModel->setHiddenAddresses(this->getHiddenAddresses());
}
void ReceiveWidget::showAddress()
{
Monero::SubaddressRow* row = this->currentEntry();
if (!row) return;
QString address = QString::fromStdString(row->getAddress());
this->removeHiddenAddress(address);
m_proxyModel->setHiddenAddresses(this->getHiddenAddresses());
}
void ReceiveWidget::updateQrCode(){
QModelIndex index = ui->addresses->currentIndex();
if (!index.isValid()) {
@ -156,6 +192,36 @@ void ReceiveWidget::showQrCodeDialog() {
dialog->deleteLater();
}
QStringList ReceiveWidget::getHiddenAddresses() {
QString data = m_wallet->getCacheAttribute("feather.hiddenaddresses");
return data.split(",");
}
void ReceiveWidget::addHiddenAddress(const QString& address) {
QStringList hiddenAddresses = this->getHiddenAddresses();
if (!hiddenAddresses.contains(address)) {
hiddenAddresses.append(address);
}
QString data = hiddenAddresses.join(",");
m_wallet->setCacheAttribute("feather.hiddenaddresses", data);
}
void ReceiveWidget::removeHiddenAddress(const QString &address) {
QStringList hiddenAddresses = this->getHiddenAddresses();
hiddenAddresses.removeAll(address);
QString data = hiddenAddresses.join(",");
m_wallet->setCacheAttribute("feather.hiddenaddresses", data);
}
Monero::SubaddressRow* ReceiveWidget::currentEntry() {
QModelIndexList list = ui->addresses->selectionModel()->selectedRows();
if (list.size() == 1) {
return m_model->entryFromIndex(m_proxyModel->mapToSource(list.first()));
} else {
return nullptr;
}
}
ReceiveWidget::~ReceiveWidget() {
delete ui;
}

10
src/receivewidget.h

@ -23,7 +23,7 @@ Q_OBJECT
public:
explicit ReceiveWidget(QWidget *parent = nullptr);
void setModel(SubaddressModel * model, Subaddress * subaddress);
void setModel(SubaddressModel * model, Wallet * wallet);
~ReceiveWidget() override;
@ -34,6 +34,7 @@ public slots:
void showContextMenu(const QPoint& point);
void setShowFullAddresses(bool show);
void setShowUsedAddresses(bool show);
void setShowHiddenAddresses(bool show);
void setSearchFilter(const QString &filter);
void onShowTransactions();
void resetModel();
@ -44,6 +45,8 @@ signals:
private slots:
void showHeaderMenu(const QPoint& position);
void hideAddress();
void showAddress();
private:
Ui::ReceiveWidget *ui;
@ -54,9 +57,14 @@ private:
Subaddress * m_subaddress;
SubaddressModel * m_model;
SubaddressProxyModel * m_proxyModel;
Wallet * m_wallet;
void updateQrCode();
void showQrCodeDialog();
QStringList getHiddenAddresses();
void addHiddenAddress(const QString& address);
void removeHiddenAddress(const QString& address);
Monero::SubaddressRow* currentEntry();
};
#endif //FEATHER_RECEIVEWIDGET_H

21
src/receivewidget.ui

@ -82,6 +82,27 @@
</property>
</spacer>
</item>
<item>
<layout class="QVBoxLayout" name="verticalLayout_3">
<property name="spacing">
<number>0</number>
</property>
<item>
<widget class="QCheckBox" name="check_showUsed">
<property name="text">
<string>Show used</string>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="check_showHidden">
<property name="text">
<string>Show hidden</string>
</property>
</widget>
</item>
</layout>
</item>
<item>
<widget class="QPushButton" name="btn_generateSubaddress">