Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve 'Requested Payments History' Multiselect #684

Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
206 changes: 156 additions & 50 deletions src/qt/receivecoinsdialog.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
// Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.

#include <reverse_iterator.h>
#include <wallet/wallet.h>

#include <qt/receivecoinsdialog.h>
Expand All @@ -22,13 +23,28 @@
#include <QSettings>
#include <QTextDocument>

static const char* const COPY_URI_SINGLE_TEXT = "Copy &URI";
static const char* const COPY_ADDRESS_SINGLE_TEXT = "&Copy address";
static const char* const COPY_LABEL_SINGLE_TEXT = "Copy &label";
static const char* const COPY_MESSAGE_SINGLE_TEXT = "Copy &message";
static const char* const COPY_AMOUNT_SINGLE_TEXT = "Copy &amount";

static const char* const COPY_URI_MULTIPLE_TEXT = "Copy &URIs";
static const char* const COPY_ADDRESS_MULTIPLE_TEXT = "&Copy addresses";
static const char* const COPY_LABEL_MULTIPLE_TEXT = "Copy &labels";
static const char* const COPY_MESSAGE_MULTIPLE_TEXT = "Copy &messages";
static const char* const COPY_AMOUNT_MULTIPLE_TEXT = "Copy &amounts";

ReceiveCoinsDialog::ReceiveCoinsDialog(const PlatformStyle *_platformStyle, QWidget *parent) :
QDialog(parent, GUIUtil::dialog_flags),
ui(new Ui::ReceiveCoinsDialog),
platformStyle(_platformStyle)
{
ui->setupUi(this);

m_sort_proxy = new QSortFilterProxyModel(this);
m_sort_proxy->setSortRole(Qt::UserRole);

if (!_platformStyle->getImagesOnButtons()) {
ui->clearButton->setIcon(QIcon());
ui->receiveButton->setIcon(QIcon());
Expand All @@ -43,11 +59,11 @@ ReceiveCoinsDialog::ReceiveCoinsDialog(const PlatformStyle *_platformStyle, QWid

// context menu
contextMenu = new QMenu(this);
contextMenu->addAction(tr("Copy &URI"), this, &ReceiveCoinsDialog::copyURI);
contextMenu->addAction(tr("&Copy address"), this, &ReceiveCoinsDialog::copyAddress);
copyLabelAction = contextMenu->addAction(tr("Copy &label"), this, &ReceiveCoinsDialog::copyLabel);
copyMessageAction = contextMenu->addAction(tr("Copy &message"), this, &ReceiveCoinsDialog::copyMessage);
copyAmountAction = contextMenu->addAction(tr("Copy &amount"), this, &ReceiveCoinsDialog::copyAmount);
copyURIAction = contextMenu->addAction(tr(COPY_URI_SINGLE_TEXT), this, &ReceiveCoinsDialog::copyURI);
copyAddressAction = contextMenu->addAction(tr(COPY_ADDRESS_SINGLE_TEXT), this, &ReceiveCoinsDialog::copyAddress);
copyLabelAction = contextMenu->addAction(tr(COPY_LABEL_SINGLE_TEXT), this, &ReceiveCoinsDialog::copyLabel);
copyMessageAction = contextMenu->addAction(tr(COPY_MESSAGE_SINGLE_TEXT), this, &ReceiveCoinsDialog::copyMessage);
copyAmountAction = contextMenu->addAction(tr(COPY_AMOUNT_SINGLE_TEXT), this, &ReceiveCoinsDialog::copyAmount);
connect(ui->recentRequestsView, &QWidget::customContextMenuRequested, this, &ReceiveCoinsDialog::showMenu);

connect(ui->clearButton, &QPushButton::clicked, this, &ReceiveCoinsDialog::clear);
Expand All @@ -56,7 +72,7 @@ ReceiveCoinsDialog::ReceiveCoinsDialog(const PlatformStyle *_platformStyle, QWid
tableView->verticalHeader()->hide();
tableView->setAlternatingRowColors(true);
tableView->setSelectionBehavior(QAbstractItemView::SelectRows);
tableView->setSelectionMode(QAbstractItemView::ContiguousSelection);
tableView->setSelectionMode(QAbstractItemView::ExtendedSelection);

QSettings settings;
if (!tableView->horizontalHeader()->restoreState(settings.value("RecentRequestsViewHeaderState").toByteArray())) {
Expand All @@ -74,12 +90,12 @@ void ReceiveCoinsDialog::setModel(WalletModel *_model)

if(_model && _model->getOptionsModel())
{
_model->getRecentRequestsTableModel()->sort(RecentRequestsTableModel::Date, Qt::DescendingOrder);
connect(_model->getOptionsModel(), &OptionsModel::displayUnitChanged, this, &ReceiveCoinsDialog::updateDisplayUnit);
updateDisplayUnit();

QTableView* tableView = ui->recentRequestsView;
tableView->setModel(_model->getRecentRequestsTableModel());
tableView->setModel(m_sort_proxy);
m_sort_proxy->setSourceModel(_model->getRecentRequestsTableModel());
tableView->sortByColumn(RecentRequestsTableModel::Date, Qt::DescendingOrder);

connect(tableView->selectionModel(),
Expand Down Expand Up @@ -191,12 +207,10 @@ void ReceiveCoinsDialog::on_receiveButton_clicked()

void ReceiveCoinsDialog::on_recentRequestsView_doubleClicked(const QModelIndex &index)
{
const RecentRequestsTableModel *submodel = model->getRecentRequestsTableModel();
ReceiveRequestDialog *dialog = new ReceiveRequestDialog(this);
dialog->setModel(model);
dialog->setInfo(submodel->entry(index.row()).recipient);
dialog->setAttribute(Qt::WA_DeleteOnClose);
dialog->show();
QModelIndexList selection = SelectedRows();
if (!selection.isEmpty() && selection.at(0).isValid()) {
ShowReceiveRequestDialogForItem(selection.at(0));
}
}

void ReceiveCoinsDialog::recentRequestsView_selectionChanged(const QItemSelection &selected, const QItemSelection &deselected)
Expand All @@ -209,90 +223,182 @@ void ReceiveCoinsDialog::recentRequestsView_selectionChanged(const QItemSelectio

void ReceiveCoinsDialog::on_showRequestButton_clicked()
{
if(!model || !model->getRecentRequestsTableModel() || !ui->recentRequestsView->selectionModel())
return;
QModelIndexList selection = ui->recentRequestsView->selectionModel()->selectedRows();
QModelIndexList selection = SelectedRows();

for (const QModelIndex& index : selection) {
on_recentRequestsView_doubleClicked(index);
ShowReceiveRequestDialogForItem(index);
}
}

void ReceiveCoinsDialog::on_removeRequestButton_clicked()
{
if(!model || !model->getRecentRequestsTableModel() || !ui->recentRequestsView->selectionModel())
return;
QModelIndexList selection = ui->recentRequestsView->selectionModel()->selectedRows();
QModelIndexList selection = SelectedRows();
if(selection.empty())
return;
// correct for selection mode ContiguousSelection
QModelIndex firstIndex = selection.at(0);
model->getRecentRequestsTableModel()->removeRows(firstIndex.row(), selection.length(), firstIndex.parent());

// Collect row indices in a set (sorted) and pass in reverse order to removeRows
// to avoid having to keep track of changed source indices after each removal
std::set<int> row_indices;
for (const QModelIndex& ind : selection) {
row_indices.insert(ind.row());
}

for (auto row_ind : reverse_iterate(row_indices)) {
model->getRecentRequestsTableModel()->removeRows(row_ind, 1);
}
}

QModelIndex ReceiveCoinsDialog::selectedRow()
QModelIndexList ReceiveCoinsDialog::SelectedRows()
{
if(!model || !model->getRecentRequestsTableModel() || !ui->recentRequestsView->selectionModel())
return QModelIndex();
return QModelIndexList();
QModelIndexList selection = ui->recentRequestsView->selectionModel()->selectedRows();
if(selection.empty())
return QModelIndex();
// correct for selection mode ContiguousSelection
QModelIndex firstIndex = selection.at(0);
return firstIndex;
QModelIndexList source_mapped;
for (auto row : selection) {
source_mapped.append(m_sort_proxy->mapToSource(row));
}

return source_mapped;
}

// copy column of selected row to clipboard
void ReceiveCoinsDialog::copyColumnToClipboard(int column)
{
QModelIndex firstIndex = selectedRow();
if (!firstIndex.isValid()) {
const QModelIndexList sel = SelectedRows();
if (sel.isEmpty()) {
return;
}

const RecentRequestsTableModel* const submodel = model->getRecentRequestsTableModel();
QString column_value;
for (int sel_ind = 0; sel_ind < sel.size(); ++sel_ind) {
if (!sel.at(sel_ind).isValid()) {
continue;
}
column_value += submodel->index(sel.at(sel_ind).row(), column).data(Qt::EditRole).toString();
if (sel_ind < sel.size() - 1) {
column_value += QString("\n");
}
}
GUIUtil::setClipboard(column_value);
}

void ReceiveCoinsDialog::ShowReceiveRequestDialogForItem(const QModelIndex& index)
{
if (!index.isValid()) {
return;
}
GUIUtil::setClipboard(model->getRecentRequestsTableModel()->index(firstIndex.row(), column).data(Qt::EditRole).toString());
const RecentRequestsTableModel* submodel = model->getRecentRequestsTableModel();
ReceiveRequestDialog* dialog = new ReceiveRequestDialog(this);
dialog->setModel(model);
dialog->setInfo(submodel->entry(index.row()).recipient);
dialog->setAttribute(Qt::WA_DeleteOnClose);
dialog->show();
}

// context menu
void ReceiveCoinsDialog::showMenu(const QPoint &point)
{
const QModelIndex sel = selectedRow();
if (!sel.isValid()) {
const QModelIndexList sel = SelectedRows();
if (sel.isEmpty()) {
return;
}

// disable context menu actions when appropriate
const RecentRequestsTableModel* const submodel = model->getRecentRequestsTableModel();
const RecentRequestEntry& req = submodel->entry(sel.row());
copyLabelAction->setDisabled(req.recipient.label.isEmpty());
copyMessageAction->setDisabled(req.recipient.message.isEmpty());
copyAmountAction->setDisabled(req.recipient.amount == 0);

contextMenu->exec(QCursor::pos());
if (sel.size() == 1 && sel.at(0).isValid()) {
// Make sure we have single labels
copyAddressAction->setText(tr(COPY_ADDRESS_SINGLE_TEXT));
copyURIAction->setText(tr(COPY_URI_SINGLE_TEXT));
copyLabelAction->setText(tr(COPY_LABEL_SINGLE_TEXT));
copyMessageAction->setText(tr(COPY_MESSAGE_SINGLE_TEXT));
copyAmountAction->setText(tr(COPY_AMOUNT_SINGLE_TEXT));

// disable context menu actions when appropriate
const RecentRequestsTableModel* const submodel = model->getRecentRequestsTableModel();
const RecentRequestEntry& req = submodel->entry(sel.at(0).row());
copyLabelAction->setDisabled(req.recipient.label.isEmpty());
copyMessageAction->setDisabled(req.recipient.message.isEmpty());
copyAmountAction->setDisabled(req.recipient.amount == 0);

contextMenu->exec(QCursor::pos());
} else if (sel.size() > 1) {
// multiple selection

// Make sure we have multiple labels
copyAddressAction->setText(tr(COPY_ADDRESS_MULTIPLE_TEXT));
copyURIAction->setText(tr(COPY_URI_MULTIPLE_TEXT));
copyLabelAction->setText(tr(COPY_LABEL_MULTIPLE_TEXT));
copyMessageAction->setText(tr(COPY_MESSAGE_MULTIPLE_TEXT));
copyAmountAction->setText(tr(COPY_AMOUNT_MULTIPLE_TEXT));

copyLabelAction->setDisabled(false);
copyMessageAction->setDisabled(false);
copyAmountAction->setDisabled(false);

// disable context menu actions when appropriate
const RecentRequestsTableModel* const submodel = model->getRecentRequestsTableModel();

for (auto selection : sel) {
if (!selection.isValid()) {
continue;
}
const RecentRequestEntry& req = submodel->entry(selection.row());
if (req.recipient.label.isEmpty()) {
copyLabelAction->setDisabled(true);
}
if (req.recipient.message.isEmpty()) {
copyMessageAction->setDisabled(true);
}
if (req.recipient.amount == 0) {
copyAmountAction->setDisabled(true);
}
}
contextMenu->exec(QCursor::pos());
}
}

// context menu action: copy URI
void ReceiveCoinsDialog::copyURI()
{
QModelIndex sel = selectedRow();
if (!sel.isValid()) {
const QModelIndexList sel = SelectedRows();
if (sel.isEmpty()) {
return;
}

const RecentRequestsTableModel * const submodel = model->getRecentRequestsTableModel();
const QString uri = GUIUtil::formatBitcoinURI(submodel->entry(sel.row()).recipient);
QString uri;
for (int sel_ind = 0; sel_ind < sel.size(); ++sel_ind) {
if (!sel.at(sel_ind).isValid()) {
continue;
}
const RecentRequestEntry& req = submodel->entry(sel.at(sel_ind).row());
uri += GUIUtil::formatBitcoinURI(req.recipient);
if (sel_ind < sel.size() - 1) {
uri += QString("\n");
}
}
GUIUtil::setClipboard(uri);
}

// context menu action: copy address
void ReceiveCoinsDialog::copyAddress()
{
const QModelIndex sel = selectedRow();
if (!sel.isValid()) {
const QModelIndexList sel = SelectedRows();
if (sel.isEmpty()) {
return;
}

const RecentRequestsTableModel* const submodel = model->getRecentRequestsTableModel();
const QString address = submodel->entry(sel.row()).recipient.address;
QString address;
for (int sel_ind = 0; sel_ind < sel.size(); ++sel_ind) {
if (!sel.at(sel_ind).isValid()) {
continue;
}
const RecentRequestEntry& req = submodel->entry(sel.at(sel_ind).row());
address += req.recipient.address;
if (sel_ind < sel.size() - 1) {
address += QString("\n");
}
}
GUIUtil::setClipboard(address);
}

Expand Down
8 changes: 7 additions & 1 deletion src/qt/receivecoinsdialog.h
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
#include <QKeyEvent>
#include <QMenu>
#include <QPoint>
#include <QSortFilterProxyModel>
#include <QVariant>

class PlatformStyle;
Expand Down Expand Up @@ -51,15 +52,20 @@ public Q_SLOTS:

private:
Ui::ReceiveCoinsDialog *ui;
QSortFilterProxyModel* m_sort_proxy;
WalletModel* model{nullptr};
QMenu *contextMenu;
QAction* copyURIAction;
QAction* copyAddressAction;
QAction* copyLabelAction;
QAction* copyMessageAction;
QAction* copyAmountAction;
const PlatformStyle *platformStyle;

QModelIndex selectedRow();
// Returns QModelIndex in source model
QModelIndexList SelectedRows();
void copyColumnToClipboard(int column);
void ShowReceiveRequestDialogForItem(const QModelIndex& index);

private Q_SLOTS:
void on_receiveButton_clicked();
Expand Down
Loading