addressbookpage.cpp raw

   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  }
 323