1 // Copyright (c) 2011-2019 The Bitcoin Core developers
2 // Distributed under the MIT software license, see the accompanying
3 // file COPYING or http://www.opensource.org/licenses/mit-license.php.
4 
5 #include <wallet/wallet.h>
6 
7 #include <qt/receivecoinsdialog.h>
8 #include <qt/forms/ui_receivecoinsdialog.h>
9 
10 #include <interfaces/node.h>
11 #include <qt/addresstablemodel.h>
12 #include <qt/optionsmodel.h>
13 #include <qt/platformstyle.h>
14 #include <qt/recentrequeststablemodel.h>
15 #include <qt/styleSheet.h>
16 
17 #include <QAction>
18 #include <QCursor>
19 #include <QMessageBox>
20 #include <QScrollBar>
21 #include <QTextDocument>
22 
ReceiveCoinsDialog(const PlatformStyle * _platformStyle,QWidget * parent)23 ReceiveCoinsDialog::ReceiveCoinsDialog(const PlatformStyle *_platformStyle, QWidget *parent) :
24     QDialog(parent),
25     ui(new Ui::ReceiveCoinsDialog),
26     columnResizingFixer(nullptr),
27     model(nullptr),
28     platformStyle(_platformStyle)
29 {
30     ui->setupUi(this);
31 
32     // Set stylesheet
33     SetObjectStyleSheet(ui->showRequestButton, StyleSheetNames::ButtonTransparentBordered);
34     SetObjectStyleSheet(ui->removeRequestButton, StyleSheetNames::ButtonTransparentBordered);
35 
36     if (!_platformStyle->getImagesOnButtons()) {
37         ui->showRequestButton->setIcon(QIcon());
38         ui->removeRequestButton->setIcon(QIcon());
39     } else {
40         ui->showRequestButton->setIcon(_platformStyle->MultiStatesIcon(":/icons/show", PlatformStyle::PushButton));
41         ui->removeRequestButton->setIcon(_platformStyle->MultiStatesIcon(":/icons/remove", PlatformStyle::PushButton));
42     }
43 
44     // context menu actions
45     QAction *copyURIAction = new QAction(tr("Copy URI"), this);
46     QAction *copyLabelAction = new QAction(tr("Copy label"), this);
47     QAction *copyMessageAction = new QAction(tr("Copy message"), this);
48     QAction *copyAmountAction = new QAction(tr("Copy amount"), this);
49 
50     // context menu
51     contextMenu = new QMenu(this);
52     contextMenu->addAction(copyURIAction);
53     contextMenu->addAction(copyLabelAction);
54     contextMenu->addAction(copyMessageAction);
55     contextMenu->addAction(copyAmountAction);
56 
57     // context menu signals
58     connect(ui->recentRequestsView, &QWidget::customContextMenuRequested, this, &ReceiveCoinsDialog::showMenu);
59     connect(copyURIAction, &QAction::triggered, this, &ReceiveCoinsDialog::copyURI);
60     connect(copyLabelAction, &QAction::triggered, this, &ReceiveCoinsDialog::copyLabel);
61     connect(copyMessageAction, &QAction::triggered, this, &ReceiveCoinsDialog::copyMessage);
62     connect(copyAmountAction, &QAction::triggered, this, &ReceiveCoinsDialog::copyAmount);
63 
64 }
65 
setModel(WalletModel * _model)66 void ReceiveCoinsDialog::setModel(WalletModel *_model)
67 {
68     this->model = _model;
69 
70     if(_model && _model->getOptionsModel())
71     {
72         _model->getRecentRequestsTableModel()->sort(RecentRequestsTableModel::Date, Qt::DescendingOrder);
73         connect(_model->getOptionsModel(), &OptionsModel::displayUnitChanged, this, &ReceiveCoinsDialog::updateDisplayUnit);
74         updateDisplayUnit();
75 
76         QTableView* tableView = ui->recentRequestsView;
77 
78         tableView->verticalHeader()->hide();
79         tableView->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
80         tableView->setModel(_model->getRecentRequestsTableModel());
81         tableView->setAlternatingRowColors(true);
82         tableView->setSelectionBehavior(QAbstractItemView::SelectRows);
83         tableView->setSelectionMode(QAbstractItemView::ContiguousSelection);
84         tableView->setColumnWidth(RecentRequestsTableModel::Date, DATE_COLUMN_WIDTH);
85         tableView->setColumnWidth(RecentRequestsTableModel::Label, LABEL_COLUMN_WIDTH);
86         tableView->setColumnWidth(RecentRequestsTableModel::Amount, AMOUNT_MINIMUM_COLUMN_WIDTH);
87 
88         connect(tableView->selectionModel(),
89             &QItemSelectionModel::selectionChanged, this,
90             &ReceiveCoinsDialog::recentRequestsView_selectionChanged);
91         // Last 2 columns are set by the columnResizingFixer, when the table geometry is ready.
92         columnResizingFixer = new GUIUtil::TableViewLastColumnResizingFixer(tableView, AMOUNT_MINIMUM_COLUMN_WIDTH, DATE_COLUMN_WIDTH, this);
93 
94         if (model->node().isAddressTypeSet()) {
95             // user explicitly set the type, use it
96             if (model->wallet().getDefaultAddressType() == OutputType::BECH32) {
97                 ui->useBech32->setCheckState(Qt::Checked);
98             } else {
99                 ui->useBech32->setCheckState(Qt::Unchecked);
100             }
101         } else {
102             // Always fall back to default in the gui
103             ui->useBech32->setVisible(model->wallet().getDefaultAddressType() != OutputType::LEGACY);
104         }
105 
106         // Set the button to be enabled or disabled based on whether the wallet can give out new addresses.
107         ui->receiveButton->setEnabled(model->wallet().canGetAddresses());
108 
109         // Enable/disable the receive button if the wallet is now able/unable to give out new addresses.
110         connect(model, &WalletModel::canGetAddressesChanged, [this] {
111             ui->receiveButton->setEnabled(model->wallet().canGetAddresses());
112         });
113     }
114 }
115 
~ReceiveCoinsDialog()116 ReceiveCoinsDialog::~ReceiveCoinsDialog()
117 {
118     delete ui;
119 }
120 
clear()121 void ReceiveCoinsDialog::clear()
122 {
123     ui->reqAmount->clear();
124     ui->reqLabel->setText("");
125     ui->reqMessage->setText("");
126     updateDisplayUnit();
127 }
128 
reject()129 void ReceiveCoinsDialog::reject()
130 {
131     clear();
132     QDialog::reject();
133 }
134 
accept()135 void ReceiveCoinsDialog::accept()
136 {
137     clear();
138     QDialog::accept();
139 }
140 
updateDisplayUnit()141 void ReceiveCoinsDialog::updateDisplayUnit()
142 {
143     if(model && model->getOptionsModel())
144     {
145         ui->reqAmount->setDisplayUnit(model->getOptionsModel()->getDisplayUnit());
146     }
147 }
148 
on_receiveButton_clicked()149 void ReceiveCoinsDialog::on_receiveButton_clicked()
150 {
151     if(!model || !model->getOptionsModel() || !model->getAddressTableModel() || !model->getRecentRequestsTableModel())
152         return;
153 
154     QString address;
155     QString label = ui->reqLabel->text();
156     /* Generate new receiving address */
157     OutputType address_type;
158     if (ui->useBech32->isChecked()) {
159         address_type = OutputType::BECH32;
160     } else {
161         address_type = model->wallet().getDefaultAddressType();
162         if (address_type == OutputType::BECH32) {
163             address_type = OutputType::P2SH_SEGWIT;
164         }
165     }
166     address = model->getAddressTableModel()->addRow(AddressTableModel::Receive, label, "", address_type);
167     SendCoinsRecipient _info(address, label,
168         ui->reqAmount->value(), ui->reqMessage->text());
169 
170     /* Store request for later reference */
171     model->getRecentRequestsTableModel()->addNewRequest(_info);
172 
173     info = _info;
174 
175     accept();
176 }
177 
on_recentRequestsView_doubleClicked(const QModelIndex & index)178 void ReceiveCoinsDialog::on_recentRequestsView_doubleClicked(const QModelIndex &index)
179 {
180     const RecentRequestsTableModel *submodel = model->getRecentRequestsTableModel();
181     info = submodel->entry(index.row()).recipient;
182 
183     accept();
184 }
185 
on_recentRequestsView_clicked(const QModelIndex & index)186 void ReceiveCoinsDialog::on_recentRequestsView_clicked(const QModelIndex &index)
187 {
188     const RecentRequestsTableModel *submodel = model->getRecentRequestsTableModel();
189     SendCoinsRecipient info = submodel->entry(index.row()).recipient;
190 
191     ui->reqLabel->setText(info.label);
192     ui->reqMessage->setText(info.message);
193     ui->reqAmount->setValue(info.amount);
194 }
195 
recentRequestsView_selectionChanged(const QItemSelection & selected,const QItemSelection & deselected)196 void ReceiveCoinsDialog::recentRequestsView_selectionChanged(const QItemSelection &selected, const QItemSelection &deselected)
197 {
198     // Enable Show/Remove buttons only if anything is selected.
199     bool enable = !ui->recentRequestsView->selectionModel()->selectedRows().isEmpty();
200     ui->showRequestButton->setEnabled(enable);
201     ui->removeRequestButton->setEnabled(enable);
202 }
203 
on_showRequestButton_clicked()204 void ReceiveCoinsDialog::on_showRequestButton_clicked()
205 {
206     if(!model || !model->getRecentRequestsTableModel() || !ui->recentRequestsView->selectionModel())
207         return;
208     QModelIndexList selection = ui->recentRequestsView->selectionModel()->selectedRows();
209 
210     for (const QModelIndex& index : selection) {
211         on_recentRequestsView_doubleClicked(index);
212     }
213 }
214 
on_removeRequestButton_clicked()215 void ReceiveCoinsDialog::on_removeRequestButton_clicked()
216 {
217     if(!model || !model->getRecentRequestsTableModel() || !ui->recentRequestsView->selectionModel())
218         return;
219     QModelIndexList selection = ui->recentRequestsView->selectionModel()->selectedRows();
220     if(selection.empty())
221         return;
222     // correct for selection mode ContiguousSelection
223     QModelIndex firstIndex = selection.at(0);
224     model->getRecentRequestsTableModel()->removeRows(firstIndex.row(), selection.length(), firstIndex.parent());
225 }
226 
227 // We override the virtual resizeEvent of the QWidget to adjust tables column
228 // sizes as the tables width is proportional to the dialogs width.
resizeEvent(QResizeEvent * event)229 void ReceiveCoinsDialog::resizeEvent(QResizeEvent *event)
230 {
231     QWidget::resizeEvent(event);
232     columnResizingFixer->stretchColumnWidth(RecentRequestsTableModel::Message);
233 }
234 
keyPressEvent(QKeyEvent * event)235 void ReceiveCoinsDialog::keyPressEvent(QKeyEvent *event)
236 {
237     if (event->key() == Qt::Key_Return)
238     {
239         // press return -> submit form
240         if (ui->reqLabel->hasFocus() || ui->reqAmount->hasFocus() || ui->reqMessage->hasFocus())
241         {
242             event->ignore();
243             on_receiveButton_clicked();
244             return;
245         }
246     }
247 
248     this->QDialog::keyPressEvent(event);
249 }
250 
selectedRow()251 QModelIndex ReceiveCoinsDialog::selectedRow()
252 {
253     if(!model || !model->getRecentRequestsTableModel() || !ui->recentRequestsView->selectionModel())
254         return QModelIndex();
255     QModelIndexList selection = ui->recentRequestsView->selectionModel()->selectedRows();
256     if(selection.empty())
257         return QModelIndex();
258     // correct for selection mode ContiguousSelection
259     QModelIndex firstIndex = selection.at(0);
260     return firstIndex;
261 }
262 
263 // copy column of selected row to clipboard
copyColumnToClipboard(int column)264 void ReceiveCoinsDialog::copyColumnToClipboard(int column)
265 {
266     QModelIndex firstIndex = selectedRow();
267     if (!firstIndex.isValid()) {
268         return;
269     }
270     GUIUtil::setClipboard(model->getRecentRequestsTableModel()->index(firstIndex.row(), column).data(Qt::EditRole).toString());
271 }
272 
273 // context menu
showMenu(const QPoint & point)274 void ReceiveCoinsDialog::showMenu(const QPoint &point)
275 {
276     if (!selectedRow().isValid()) {
277         return;
278     }
279     contextMenu->exec(QCursor::pos());
280 }
281 
282 // context menu action: copy URI
copyURI()283 void ReceiveCoinsDialog::copyURI()
284 {
285     QModelIndex sel = selectedRow();
286     if (!sel.isValid()) {
287         return;
288     }
289 
290     const RecentRequestsTableModel * const submodel = model->getRecentRequestsTableModel();
291     const QString uri = GUIUtil::formatBitcoinURI(submodel->entry(sel.row()).recipient);
292     GUIUtil::setClipboard(uri);
293 }
294 
295 // context menu action: copy label
copyLabel()296 void ReceiveCoinsDialog::copyLabel()
297 {
298     copyColumnToClipboard(RecentRequestsTableModel::Label);
299 }
300 
301 // context menu action: copy message
copyMessage()302 void ReceiveCoinsDialog::copyMessage()
303 {
304     copyColumnToClipboard(RecentRequestsTableModel::Message);
305 }
306 
307 // context menu action: copy amount
copyAmount()308 void ReceiveCoinsDialog::copyAmount()
309 {
310     copyColumnToClipboard(RecentRequestsTableModel::Amount);
311 }
312 
getInfo() const313 SendCoinsRecipient ReceiveCoinsDialog::getInfo() const
314 {
315     return info;
316 }
317 
on_cancelButton_clicked()318 void ReceiveCoinsDialog::on_cancelButton_clicked()
319 {
320     reject();
321 }
322