1 // Copyright (c) 2011-2017 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.
5 #include <qt/addresstablemodel.h>
7 #include <qt/guiutil.h>
8 #include <qt/walletmodel.h>
11 #include <wallet/wallet.h>
17 const QString
AddressTableModel::Send
= "S";
18 const QString
AddressTableModel::Receive
= "R";
20 struct AddressTableEntry
25 Hidden
/* QSortFilterProxyModel will filter these out */
32 AddressTableEntry() {}
33 AddressTableEntry(Type _type
, const QString
&_label
, const QString
&_address
):
34 type(_type
), label(_label
), address(_address
) {}
37 struct AddressTableEntryLessThan
39 bool operator()(const AddressTableEntry
&a
, const AddressTableEntry
&b
) const
41 return a
.address
< b
.address
;
43 bool operator()(const AddressTableEntry
&a
, const QString
&b
) const
47 bool operator()(const QString
&a
, const AddressTableEntry
&b
) const
53 /* Determine address type from address purpose */
54 static AddressTableEntry::Type
translateTransactionType(const QString
&strPurpose
, bool isMine
)
56 AddressTableEntry::Type addressType
= AddressTableEntry::Hidden
;
57 // "refund" addresses aren't shown, and change addresses aren't in mapAddressBook at all.
58 if (strPurpose
== "send")
59 addressType
= AddressTableEntry::Sending
;
60 else if (strPurpose
== "receive")
61 addressType
= AddressTableEntry::Receiving
;
62 else if (strPurpose
== "unknown" || strPurpose
== "") // if purpose not set, guess
63 addressType
= (isMine
? AddressTableEntry::Receiving
: AddressTableEntry::Sending
);
67 // Private implementation
68 class AddressTablePriv
72 QList
<AddressTableEntry
> cachedAddressTable
;
73 AddressTableModel
*parent
;
75 AddressTablePriv(CWallet
*_wallet
, AddressTableModel
*_parent
):
76 wallet(_wallet
), parent(_parent
) {}
78 void refreshAddressTable()
80 cachedAddressTable
.clear();
82 LOCK(wallet
->cs_wallet
);
83 for (const std::pair
<CTxDestination
, CAddressBookData
>& item
: wallet
->mapAddressBook
)
85 const CTxDestination
& address
= item
.first
;
86 bool fMine
= IsMine(*wallet
, address
);
87 AddressTableEntry::Type addressType
= translateTransactionType(
88 QString::fromStdString(item
.second
.purpose
), fMine
);
89 const std::string
& strName
= item
.second
.name
;
90 cachedAddressTable
.append(AddressTableEntry(addressType
,
91 QString::fromStdString(strName
),
92 QString::fromStdString(EncodeDestination(address
))));
95 // qLowerBound() and qUpperBound() require our cachedAddressTable list to be sorted in asc order
96 // Even though the map is already sorted this re-sorting step is needed because the originating map
97 // is sorted by binary address, not by base58() address.
98 qSort(cachedAddressTable
.begin(), cachedAddressTable
.end(), AddressTableEntryLessThan());
101 void updateEntry(const QString
&address
, const QString
&label
, bool isMine
, const QString
&purpose
, int status
)
103 // Find address / label in model
104 QList
<AddressTableEntry
>::iterator lower
= qLowerBound(
105 cachedAddressTable
.begin(), cachedAddressTable
.end(), address
, AddressTableEntryLessThan());
106 QList
<AddressTableEntry
>::iterator upper
= qUpperBound(
107 cachedAddressTable
.begin(), cachedAddressTable
.end(), address
, AddressTableEntryLessThan());
108 int lowerIndex
= (lower
- cachedAddressTable
.begin());
109 int upperIndex
= (upper
- cachedAddressTable
.begin());
110 bool inModel
= (lower
!= upper
);
111 AddressTableEntry::Type newEntryType
= translateTransactionType(purpose
, isMine
);
118 qWarning() << "AddressTablePriv::updateEntry: Warning: Got CT_NEW, but entry is already in model";
121 parent
->beginInsertRows(QModelIndex(), lowerIndex
, lowerIndex
);
122 cachedAddressTable
.insert(lowerIndex
, AddressTableEntry(newEntryType
, label
, address
));
123 parent
->endInsertRows();
128 qWarning() << "AddressTablePriv::updateEntry: Warning: Got CT_UPDATED, but entry is not in model";
131 lower
->type
= newEntryType
;
132 lower
->label
= label
;
133 parent
->emitDataChanged(lowerIndex
);
138 qWarning() << "AddressTablePriv::updateEntry: Warning: Got CT_DELETED, but entry is not in model";
141 parent
->beginRemoveRows(QModelIndex(), lowerIndex
, upperIndex
-1);
142 cachedAddressTable
.erase(lower
, upper
);
143 parent
->endRemoveRows();
150 return cachedAddressTable
.size();
153 AddressTableEntry
*index(int idx
)
155 if(idx
>= 0 && idx
< cachedAddressTable
.size())
157 return &cachedAddressTable
[idx
];
166 AddressTableModel::AddressTableModel(CWallet
*_wallet
, WalletModel
*parent
) :
167 QAbstractTableModel(parent
),walletModel(parent
),wallet(_wallet
),priv(0)
169 columns
<< tr("Label") << tr("Address");
170 priv
= new AddressTablePriv(wallet
, this);
171 priv
->refreshAddressTable();
174 AddressTableModel::~AddressTableModel()
179 int AddressTableModel::rowCount(const QModelIndex
&parent
) const
185 int AddressTableModel::columnCount(const QModelIndex
&parent
) const
188 return columns
.length();
191 QVariant
AddressTableModel::data(const QModelIndex
&index
, int role
) const
196 AddressTableEntry
*rec
= static_cast<AddressTableEntry
*>(index
.internalPointer());
198 if(role
== Qt::DisplayRole
|| role
== Qt::EditRole
)
200 switch(index
.column())
203 if(rec
->label
.isEmpty() && role
== Qt::DisplayRole
)
205 return tr("(no label)");
215 else if (role
== Qt::FontRole
)
218 if(index
.column() == Address
)
220 font
= GUIUtil::fixedPitchFont();
224 else if (role
== TypeRole
)
228 case AddressTableEntry::Sending
:
230 case AddressTableEntry::Receiving
:
238 bool AddressTableModel::setData(const QModelIndex
&index
, const QVariant
&value
, int role
)
242 AddressTableEntry
*rec
= static_cast<AddressTableEntry
*>(index
.internalPointer());
243 std::string strPurpose
= (rec
->type
== AddressTableEntry::Sending
? "send" : "receive");
246 if(role
== Qt::EditRole
)
248 LOCK(wallet
->cs_wallet
); /* For SetAddressBook / DelAddressBook */
249 CTxDestination curAddress
= DecodeDestination(rec
->address
.toStdString());
250 if(index
.column() == Label
)
252 // Do nothing, if old label == new label
253 if(rec
->label
== value
.toString())
255 editStatus
= NO_CHANGES
;
258 wallet
->SetAddressBook(curAddress
, value
.toString().toStdString(), strPurpose
);
259 } else if(index
.column() == Address
) {
260 CTxDestination newAddress
= DecodeDestination(value
.toString().toStdString());
261 // Refuse to set invalid address, set error status and return false
262 if(boost::get
<CNoDestination
>(&newAddress
))
264 editStatus
= INVALID_ADDRESS
;
267 // Do nothing, if old address == new address
268 else if(newAddress
== curAddress
)
270 editStatus
= NO_CHANGES
;
273 // Check for duplicate addresses to prevent accidental deletion of addresses, if you try
274 // to paste an existing address over another address (with a different label)
275 else if(wallet
->mapAddressBook
.count(newAddress
))
277 editStatus
= DUPLICATE_ADDRESS
;
280 // Double-check that we're not overwriting a receiving address
281 else if(rec
->type
== AddressTableEntry::Sending
)
284 wallet
->DelAddressBook(curAddress
);
285 // Add new entry with new address
286 wallet
->SetAddressBook(newAddress
, rec
->label
.toStdString(), strPurpose
);
294 QVariant
AddressTableModel::headerData(int section
, Qt::Orientation orientation
, int role
) const
296 if(orientation
== Qt::Horizontal
)
298 if(role
== Qt::DisplayRole
&& section
< columns
.size())
300 return columns
[section
];
306 Qt::ItemFlags
AddressTableModel::flags(const QModelIndex
&index
) const
310 AddressTableEntry
*rec
= static_cast<AddressTableEntry
*>(index
.internalPointer());
312 Qt::ItemFlags retval
= Qt::ItemIsSelectable
| Qt::ItemIsEnabled
;
313 // Can edit address and label for sending addresses,
314 // and only label for receiving addresses.
315 if(rec
->type
== AddressTableEntry::Sending
||
316 (rec
->type
== AddressTableEntry::Receiving
&& index
.column()==Label
))
318 retval
|= Qt::ItemIsEditable
;
323 QModelIndex
AddressTableModel::index(int row
, int column
, const QModelIndex
&parent
) const
326 AddressTableEntry
*data
= priv
->index(row
);
329 return createIndex(row
, column
, priv
->index(row
));
333 return QModelIndex();
337 void AddressTableModel::updateEntry(const QString
&address
,
338 const QString
&label
, bool isMine
, const QString
&purpose
, int status
)
340 // Update address book model from Bitcoin core
341 priv
->updateEntry(address
, label
, isMine
, purpose
, status
);
344 QString
AddressTableModel::addRow(const QString
&type
, const QString
&label
, const QString
&address
)
346 std::string strLabel
= label
.toStdString();
347 std::string strAddress
= address
.toStdString();
353 if(!walletModel
->validateAddress(address
))
355 editStatus
= INVALID_ADDRESS
;
358 // Check for duplicate addresses
360 LOCK(wallet
->cs_wallet
);
361 if(wallet
->mapAddressBook
.count(DecodeDestination(strAddress
)))
363 editStatus
= DUPLICATE_ADDRESS
;
368 else if(type
== Receive
)
370 // Generate a new address to associate with given label
372 if(!wallet
->GetKeyFromPool(newKey
))
374 WalletModel::UnlockContext
ctx(walletModel
->requestUnlock());
377 // Unlock wallet failed or was cancelled
378 editStatus
= WALLET_UNLOCK_FAILURE
;
381 if(!wallet
->GetKeyFromPool(newKey
))
383 editStatus
= KEY_GENERATION_FAILURE
;
387 strAddress
= EncodeDestination(newKey
.GetID());
396 LOCK(wallet
->cs_wallet
);
397 wallet
->SetAddressBook(DecodeDestination(strAddress
), strLabel
,
398 (type
== Send
? "send" : "receive"));
400 return QString::fromStdString(strAddress
);
403 bool AddressTableModel::removeRows(int row
, int count
, const QModelIndex
&parent
)
406 AddressTableEntry
*rec
= priv
->index(row
);
407 if(count
!= 1 || !rec
|| rec
->type
== AddressTableEntry::Receiving
)
409 // Can only remove one row at a time, and cannot remove rows not in model.
410 // Also refuse to remove receiving addresses.
414 LOCK(wallet
->cs_wallet
);
415 wallet
->DelAddressBook(DecodeDestination(rec
->address
.toStdString()));
420 /* Look up label for address in address book, if not found return empty string.
422 QString
AddressTableModel::labelForAddress(const QString
&address
) const
425 LOCK(wallet
->cs_wallet
);
426 CTxDestination destination
= DecodeDestination(address
.toStdString());
427 std::map
<CTxDestination
, CAddressBookData
>::iterator mi
= wallet
->mapAddressBook
.find(destination
);
428 if (mi
!= wallet
->mapAddressBook
.end())
430 return QString::fromStdString(mi
->second
.name
);
436 int AddressTableModel::lookupAddress(const QString
&address
) const
438 QModelIndexList lst
= match(index(0, Address
, QModelIndex()),
439 Qt::EditRole
, address
, 1, Qt::MatchExactly
);
446 return lst
.at(0).row();
450 void AddressTableModel::emitDataChanged(int idx
)
452 Q_EMIT
dataChanged(index(idx
, 0, QModelIndex()), index(idx
, columns
.length()-1, QModelIndex()));