/ src / qt / addressbookpage.cpp
addressbookpage.cpp
  1  // Copyright (c) 2011-present 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 <qt/addressbookpage.h>
  6  #include <qt/forms/ui_addressbookpage.h>
  7  
  8  #include <qt/addresstablemodel.h>
  9  #include <qt/csvmodelwriter.h>
 10  #include <qt/editaddressdialog.h>
 11  #include <qt/guiutil.h>
 12  #include <qt/platformstyle.h>
 13  
 14  #include <QIcon>
 15  #include <QMenu>
 16  #include <QMessageBox>
 17  #include <QSortFilterProxyModel>
 18  #include <QRegularExpression>
 19  
 20  class AddressBookSortFilterProxyModel final : public QSortFilterProxyModel
 21  {
 22      const QString m_type;
 23  
 24  public:
 25      AddressBookSortFilterProxyModel(const QString& type, QObject* parent)
 26          : QSortFilterProxyModel(parent)
 27          , m_type(type)
 28      {
 29          setDynamicSortFilter(true);
 30          setFilterCaseSensitivity(Qt::CaseInsensitive);
 31          setSortCaseSensitivity(Qt::CaseInsensitive);
 32      }
 33  
 34  protected:
 35      bool filterAcceptsRow(int row, const QModelIndex& parent) const override
 36      {
 37          auto model = sourceModel();
 38          auto label = model->index(row, AddressTableModel::Label, parent);
 39  
 40          if (model->data(label, AddressTableModel::TypeRole).toString() != m_type) {
 41              return false;
 42          }
 43  
 44          auto address = model->index(row, AddressTableModel::Address, parent);
 45  
 46          const auto pattern = filterRegularExpression();
 47          return (model->data(address).toString().contains(pattern) ||
 48                  model->data(label).toString().contains(pattern));
 49      }
 50  };
 51  
 52  AddressBookPage::AddressBookPage(const PlatformStyle *platformStyle, Mode _mode, Tabs _tab, QWidget *parent) :
 53      QDialog(parent, GUIUtil::dialog_flags),
 54      ui(new Ui::AddressBookPage),
 55      mode(_mode),
 56      tab(_tab)
 57  {
 58      ui->setupUi(this);
 59  
 60      if (!platformStyle->getImagesOnButtons()) {
 61          ui->newAddress->setIcon(QIcon());
 62          ui->copyAddress->setIcon(QIcon());
 63          ui->deleteAddress->setIcon(QIcon());
 64          ui->exportButton->setIcon(QIcon());
 65      } else {
 66          ui->newAddress->setIcon(platformStyle->SingleColorIcon(":/icons/add"));
 67          ui->copyAddress->setIcon(platformStyle->SingleColorIcon(":/icons/editcopy"));
 68          ui->deleteAddress->setIcon(platformStyle->SingleColorIcon(":/icons/remove"));
 69          ui->exportButton->setIcon(platformStyle->SingleColorIcon(":/icons/export"));
 70      }
 71  
 72      if (mode == ForSelection) {
 73          switch(tab)
 74          {
 75          case SendingTab: setWindowTitle(tr("Choose the address to send coins to")); break;
 76          case ReceivingTab: setWindowTitle(tr("Choose the address to receive coins with")); break;
 77          }
 78          connect(ui->tableView, &QTableView::doubleClicked, this, &QDialog::accept);
 79          ui->tableView->setEditTriggers(QAbstractItemView::NoEditTriggers);
 80          ui->tableView->setFocus();
 81          ui->closeButton->setText(tr("C&hoose"));
 82          ui->exportButton->hide();
 83      }
 84      switch(tab)
 85      {
 86      case SendingTab:
 87          ui->labelExplanation->setText(tr("These are your Bitcoin addresses for sending payments. Always check the amount and the receiving address before sending coins."));
 88          ui->deleteAddress->setVisible(true);
 89          ui->newAddress->setVisible(true);
 90          break;
 91      case ReceivingTab:
 92          ui->labelExplanation->setText(tr("These are your Bitcoin addresses for receiving payments. Use the 'Create new receiving address' button in the receive tab to create new addresses.\nSigning is only possible with addresses of the type 'legacy'."));
 93          ui->deleteAddress->setVisible(false);
 94          ui->newAddress->setVisible(false);
 95          break;
 96      }
 97  
 98      // Build context menu
 99      contextMenu = new QMenu(this);
100      contextMenu->addAction(tr("&Copy Address"), this, &AddressBookPage::on_copyAddress_clicked);
101      contextMenu->addAction(tr("Copy &Label"), this, &AddressBookPage::onCopyLabelAction);
102      contextMenu->addAction(tr("&Edit"), this, &AddressBookPage::onEditAction);
103  
104      if (tab == SendingTab) {
105          contextMenu->addAction(tr("&Delete"), this, &AddressBookPage::on_deleteAddress_clicked);
106      }
107  
108      connect(ui->tableView, &QWidget::customContextMenuRequested, this, &AddressBookPage::contextualMenu);
109      connect(ui->closeButton, &QPushButton::clicked, this, &QDialog::accept);
110  
111      GUIUtil::handleCloseWindowShortcut(this);
112  }
113  
114  AddressBookPage::~AddressBookPage()
115  {
116      delete ui;
117  }
118  
119  void AddressBookPage::setModel(AddressTableModel *_model)
120  {
121      this->model = _model;
122      if(!_model)
123          return;
124  
125      auto type = tab == ReceivingTab ? AddressTableModel::Receive : AddressTableModel::Send;
126      proxyModel = new AddressBookSortFilterProxyModel(type, this);
127      proxyModel->setSourceModel(_model);
128  
129      connect(ui->searchLineEdit, &QLineEdit::textChanged, proxyModel, &QSortFilterProxyModel::setFilterWildcard);
130  
131      ui->tableView->setModel(proxyModel);
132      ui->tableView->sortByColumn(0, Qt::AscendingOrder);
133  
134      // Set column widths
135      ui->tableView->horizontalHeader()->setSectionResizeMode(AddressTableModel::Label, QHeaderView::Stretch);
136      ui->tableView->horizontalHeader()->setSectionResizeMode(AddressTableModel::Address, QHeaderView::ResizeToContents);
137  
138      connect(ui->tableView->selectionModel(), &QItemSelectionModel::selectionChanged,
139          this, &AddressBookPage::selectionChanged);
140  
141      // Select row for newly created address
142      connect(_model, &AddressTableModel::rowsInserted, this, &AddressBookPage::selectNewAddress);
143  
144      selectionChanged();
145      this->updateWindowsTitleWithWalletName();
146  }
147  
148  void AddressBookPage::on_copyAddress_clicked()
149  {
150      GUIUtil::copyEntryData(ui->tableView, AddressTableModel::Address);
151  }
152  
153  void AddressBookPage::onCopyLabelAction()
154  {
155      GUIUtil::copyEntryData(ui->tableView, AddressTableModel::Label);
156  }
157  
158  void AddressBookPage::onEditAction()
159  {
160      if(!model)
161          return;
162  
163      if(!ui->tableView->selectionModel())
164          return;
165      QModelIndexList indexes = ui->tableView->selectionModel()->selectedRows();
166      if(indexes.isEmpty())
167          return;
168  
169      auto dlg = new EditAddressDialog(
170          tab == SendingTab ?
171          EditAddressDialog::EditSendingAddress :
172          EditAddressDialog::EditReceivingAddress, this);
173      dlg->setModel(model);
174      QModelIndex origIndex = proxyModel->mapToSource(indexes.at(0));
175      dlg->loadRow(origIndex.row());
176      GUIUtil::ShowModalDialogAsynchronously(dlg);
177  }
178  
179  void AddressBookPage::on_newAddress_clicked()
180  {
181      if(!model)
182          return;
183  
184      if (tab == ReceivingTab) {
185          return;
186      }
187  
188      EditAddressDialog dlg(EditAddressDialog::NewSendingAddress, this);
189      dlg.setModel(model);
190      if(dlg.exec())
191      {
192          newAddressToSelect = dlg.getAddress();
193      }
194  }
195  
196  void AddressBookPage::on_deleteAddress_clicked()
197  {
198      QTableView *table = ui->tableView;
199      if(!table->selectionModel())
200          return;
201  
202      QModelIndexList indexes = table->selectionModel()->selectedRows();
203      if(!indexes.isEmpty())
204      {
205          table->model()->removeRow(indexes.at(0).row());
206      }
207  }
208  
209  void AddressBookPage::selectionChanged()
210  {
211      // Set button states based on selected tab and selection
212      QTableView *table = ui->tableView;
213      if(!table->selectionModel())
214          return;
215  
216      if(table->selectionModel()->hasSelection())
217      {
218          switch(tab)
219          {
220          case SendingTab:
221              // In sending tab, allow deletion of selection
222              ui->deleteAddress->setEnabled(true);
223              ui->deleteAddress->setVisible(true);
224              break;
225          case ReceivingTab:
226              // Deleting receiving addresses, however, is not allowed
227              ui->deleteAddress->setEnabled(false);
228              ui->deleteAddress->setVisible(false);
229              break;
230          }
231          ui->copyAddress->setEnabled(true);
232      }
233      else
234      {
235          ui->deleteAddress->setEnabled(false);
236          ui->copyAddress->setEnabled(false);
237      }
238  }
239  
240  void AddressBookPage::done(int retval)
241  {
242      QTableView *table = ui->tableView;
243      if(!table->selectionModel() || !table->model())
244          return;
245  
246      // Figure out which address was selected, and return it
247      QModelIndexList indexes = table->selectionModel()->selectedRows(AddressTableModel::Address);
248  
249      for (const QModelIndex& index : indexes) {
250          QVariant address = table->model()->data(index);
251          returnValue = address.toString();
252      }
253  
254      if(returnValue.isEmpty())
255      {
256          // If no address entry selected, return rejected
257          retval = Rejected;
258      }
259  
260      QDialog::done(retval);
261  }
262  
263  void AddressBookPage::on_exportButton_clicked()
264  {
265      // CSV is currently the only supported format
266      QString filename = GUIUtil::getSaveFileName(this,
267          tr("Export Address List"), QString(),
268          /*: Expanded name of the CSV file format.
269              See: https://en.wikipedia.org/wiki/Comma-separated_values. */
270          tr("Comma separated file") + QLatin1String(" (*.csv)"), nullptr);
271  
272      if (filename.isNull())
273          return;
274  
275      CSVModelWriter writer(filename);
276  
277      // name, column, role
278      writer.setModel(proxyModel);
279      writer.addColumn("Label", AddressTableModel::Label, Qt::EditRole);
280      writer.addColumn("Address", AddressTableModel::Address, Qt::EditRole);
281  
282      if(!writer.write()) {
283          QMessageBox::critical(this, tr("Exporting Failed"),
284              /*: An error message. %1 is a stand-in argument for the name
285                  of the file we attempted to save to. */
286              tr("There was an error trying to save the address list to %1. Please try again.").arg(filename));
287      }
288  }
289  
290  void AddressBookPage::contextualMenu(const QPoint &point)
291  {
292      QModelIndex index = ui->tableView->indexAt(point);
293      if(index.isValid())
294      {
295          contextMenu->exec(QCursor::pos());
296      }
297  }
298  
299  void AddressBookPage::selectNewAddress(const QModelIndex &parent, int begin, int /*end*/)
300  {
301      QModelIndex idx = proxyModel->mapFromSource(model->index(begin, AddressTableModel::Address, parent));
302      if(idx.isValid() && (idx.data(Qt::EditRole).toString() == newAddressToSelect))
303      {
304          // Select row of newly created address, once
305          ui->tableView->setFocus();
306          ui->tableView->selectRow(idx.row());
307          newAddressToSelect.clear();
308      }
309  }
310  
311  void AddressBookPage::updateWindowsTitleWithWalletName()
312  {
313      const QString walletName = this->model->GetWalletDisplayName();
314  
315      if (mode == ForEditing) {
316          switch(tab)
317          {
318          case SendingTab: setWindowTitle(tr("Sending addresses - %1").arg(walletName)); break;
319          case ReceivingTab: setWindowTitle(tr("Receiving addresses - %1").arg(walletName)); break;
320          }
321      }
322  }