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 }