addressbookpage.cpp raw

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