]>
Commit | Line | Data |
---|---|---|
f914f1a7 | 1 | // Copyright (c) 2011-2014 The Bitcoin Core developers |
78253fcb | 2 | // Distributed under the MIT software license, see the accompanying |
e592d43f WL |
3 | // file COPYING or http://www.opensource.org/licenses/mit-license.php. |
4 | ||
0fd01780 | 5 | #include "transactiontablemodel.h" |
32af5266 | 6 | |
51ed9ec9 | 7 | #include "addresstablemodel.h" |
63760fa1 | 8 | #include "guiconstants.h" |
51ed9ec9 BD |
9 | #include "guiutil.h" |
10 | #include "optionsmodel.h" | |
9b7d3fb1 | 11 | #include "scicon.h" |
66d536ed | 12 | #include "transactiondesc.h" |
51ed9ec9 | 13 | #include "transactionrecord.h" |
ebff5c40 | 14 | #include "walletmodel.h" |
4d27c960 | 15 | |
51ed9ec9 BD |
16 | #include "main.h" |
17 | #include "sync.h" | |
18 | #include "uint256.h" | |
19 | #include "util.h" | |
50c72f23 | 20 | #include "wallet/wallet.h" |
e8ef3da7 | 21 | |
f488e735 | 22 | #include <QColor> |
ceb6d4e1 | 23 | #include <QDateTime> |
42018eff | 24 | #include <QDebug> |
51ed9ec9 BD |
25 | #include <QIcon> |
26 | #include <QList> | |
af943776 | 27 | |
b0849613 | 28 | // Amount column is right-aligned it contains numbers |
34fa1782 | 29 | static int column_alignments[] = { |
dcd0b077 | 30 | Qt::AlignLeft|Qt::AlignVCenter, /* status */ |
1c5f0af0 | 31 | Qt::AlignLeft|Qt::AlignVCenter, /* watchonly */ |
dcd0b077 WL |
32 | Qt::AlignLeft|Qt::AlignVCenter, /* date */ |
33 | Qt::AlignLeft|Qt::AlignVCenter, /* type */ | |
34 | Qt::AlignLeft|Qt::AlignVCenter, /* address */ | |
35 | Qt::AlignRight|Qt::AlignVCenter /* amount */ | |
34fa1782 WL |
36 | }; |
37 | ||
0f3981be | 38 | // Comparison operator for sort/binary search of model tx list |
9d9a4e87 WL |
39 | struct TxLessThan |
40 | { | |
41 | bool operator()(const TransactionRecord &a, const TransactionRecord &b) const | |
42 | { | |
43 | return a.hash < b.hash; | |
44 | } | |
45 | bool operator()(const TransactionRecord &a, const uint256 &b) const | |
46 | { | |
47 | return a.hash < b; | |
48 | } | |
49 | bool operator()(const uint256 &a, const TransactionRecord &b) const | |
50 | { | |
51 | return a < b.hash; | |
52 | } | |
53 | }; | |
54 | ||
0f3981be | 55 | // Private implementation |
87207a2e | 56 | class TransactionTablePriv |
213f7636 | 57 | { |
87207a2e | 58 | public: |
bdd0c59a PK |
59 | TransactionTablePriv(CWallet *wallet, TransactionTableModel *parent) : |
60 | wallet(wallet), | |
61 | parent(parent) | |
9d9a4e87 WL |
62 | { |
63 | } | |
bdd0c59a | 64 | |
e8ef3da7 | 65 | CWallet *wallet; |
9d9a4e87 WL |
66 | TransactionTableModel *parent; |
67 | ||
63760fa1 WL |
68 | /* Local cache of wallet. |
69 | * As it is in the same order as the CWallet, by definition | |
70 | * this is sorted by sha256. | |
71 | */ | |
213f7636 WL |
72 | QList<TransactionRecord> cachedWallet; |
73 | ||
0f3981be WL |
74 | /* Query entire wallet anew from core. |
75 | */ | |
63760fa1 | 76 | void refreshWallet() |
213f7636 | 77 | { |
42018eff | 78 | qDebug() << "TransactionTablePriv::refreshWallet"; |
8c937da5 | 79 | cachedWallet.clear(); |
213f7636 | 80 | { |
55a1db4f | 81 | LOCK2(cs_main, wallet->cs_wallet); |
e8ef3da7 | 82 | for(std::map<uint256, CWalletTx>::iterator it = wallet->mapWallet.begin(); it != wallet->mapWallet.end(); ++it) |
213f7636 | 83 | { |
fe4a6550 WL |
84 | if(TransactionRecord::showTransaction(it->second)) |
85 | cachedWallet.append(TransactionRecord::decomposeTransaction(wallet, it->second)); | |
213f7636 WL |
86 | } |
87 | } | |
213f7636 WL |
88 | } |
89 | ||
0f3981be WL |
90 | /* Update our model of the wallet incrementally, to synchronize our model of the wallet |
91 | with that of the core. | |
92 | ||
fe4a6550 | 93 | Call with transaction that was added, removed or changed. |
63760fa1 | 94 | */ |
023e63df | 95 | void updateWallet(const uint256 &hash, int status, bool showTransaction) |
63760fa1 | 96 | { |
5262fde0 | 97 | qDebug() << "TransactionTablePriv::updateWallet: " + QString::fromStdString(hash.ToString()) + " " + QString::number(status); |
fe4a6550 | 98 | |
023e63df WL |
99 | // Find bounds of this transaction in model |
100 | QList<TransactionRecord>::iterator lower = qLowerBound( | |
101 | cachedWallet.begin(), cachedWallet.end(), hash, TxLessThan()); | |
102 | QList<TransactionRecord>::iterator upper = qUpperBound( | |
103 | cachedWallet.begin(), cachedWallet.end(), hash, TxLessThan()); | |
104 | int lowerIndex = (lower - cachedWallet.begin()); | |
105 | int upperIndex = (upper - cachedWallet.begin()); | |
106 | bool inModel = (lower != upper); | |
fe4a6550 | 107 | |
023e63df WL |
108 | if(status == CT_UPDATED) |
109 | { | |
110 | if(showTransaction && !inModel) | |
111 | status = CT_NEW; /* Not in model, but want to show, treat as new */ | |
112 | if(!showTransaction && inModel) | |
113 | status = CT_DELETED; /* In model, but want to hide, treat as deleted */ | |
114 | } | |
fe4a6550 | 115 | |
023e63df WL |
116 | qDebug() << " inModel=" + QString::number(inModel) + |
117 | " Index=" + QString::number(lowerIndex) + "-" + QString::number(upperIndex) + | |
118 | " showTransaction=" + QString::number(showTransaction) + " derivedStatus=" + QString::number(status); | |
fe4a6550 | 119 | |
023e63df WL |
120 | switch(status) |
121 | { | |
122 | case CT_NEW: | |
123 | if(inModel) | |
9d9a4e87 | 124 | { |
5262fde0 | 125 | qWarning() << "TransactionTablePriv::updateWallet: Warning: Got CT_NEW, but transaction is already in model"; |
023e63df | 126 | break; |
fe4a6550 | 127 | } |
023e63df | 128 | if(showTransaction) |
fe4a6550 | 129 | { |
023e63df WL |
130 | LOCK2(cs_main, wallet->cs_wallet); |
131 | // Find transaction in wallet | |
132 | std::map<uint256, CWalletTx>::iterator mi = wallet->mapWallet.find(hash); | |
133 | if(mi == wallet->mapWallet.end()) | |
fe4a6550 | 134 | { |
5262fde0 | 135 | qWarning() << "TransactionTablePriv::updateWallet: Warning: Got CT_NEW, but transaction is not in wallet"; |
fe4a6550 WL |
136 | break; |
137 | } | |
023e63df WL |
138 | // Added -- insert at the right position |
139 | QList<TransactionRecord> toInsert = | |
140 | TransactionRecord::decomposeTransaction(wallet, mi->second); | |
141 | if(!toInsert.isEmpty()) /* only if something to insert */ | |
9d9a4e87 | 142 | { |
023e63df WL |
143 | parent->beginInsertRows(QModelIndex(), lowerIndex, lowerIndex+toInsert.size()-1); |
144 | int insert_idx = lowerIndex; | |
145 | foreach(const TransactionRecord &rec, toInsert) | |
9d9a4e87 | 146 | { |
023e63df WL |
147 | cachedWallet.insert(insert_idx, rec); |
148 | insert_idx += 1; | |
9d9a4e87 | 149 | } |
023e63df | 150 | parent->endInsertRows(); |
8e86dca2 | 151 | } |
023e63df WL |
152 | } |
153 | break; | |
154 | case CT_DELETED: | |
155 | if(!inModel) | |
156 | { | |
5262fde0 | 157 | qWarning() << "TransactionTablePriv::updateWallet: Warning: Got CT_DELETED, but transaction is not in model"; |
fe4a6550 | 158 | break; |
9d9a4e87 | 159 | } |
023e63df WL |
160 | // Removed -- remove entire transaction from table |
161 | parent->beginRemoveRows(QModelIndex(), lowerIndex, upperIndex-1); | |
162 | cachedWallet.erase(lower, upper); | |
163 | parent->endRemoveRows(); | |
164 | break; | |
165 | case CT_UPDATED: | |
166 | // Miscellaneous updates -- nothing to do, status update will take care of this, and is only computed for | |
167 | // visible transactions. | |
168 | break; | |
63760fa1 | 169 | } |
63760fa1 WL |
170 | } |
171 | ||
213f7636 WL |
172 | int size() |
173 | { | |
174 | return cachedWallet.size(); | |
175 | } | |
176 | ||
177 | TransactionRecord *index(int idx) | |
178 | { | |
179 | if(idx >= 0 && idx < cachedWallet.size()) | |
180 | { | |
64bca50d WL |
181 | TransactionRecord *rec = &cachedWallet[idx]; |
182 | ||
41106a50 WL |
183 | // Get required locks upfront. This avoids the GUI from getting |
184 | // stuck if the core is holding the locks for a longer time - for | |
185 | // example, during a wallet rescan. | |
186 | // | |
3015e0bc WL |
187 | // If a status update is needed (blocks came in since last check), |
188 | // update the status of this transaction from the wallet. Otherwise, | |
0f3981be | 189 | // simply re-use the cached status. |
41106a50 WL |
190 | TRY_LOCK(cs_main, lockMain); |
191 | if(lockMain) | |
64bca50d | 192 | { |
41106a50 | 193 | TRY_LOCK(wallet->cs_wallet, lockWallet); |
3015e0bc | 194 | if(lockWallet && rec->statusUpdateNeeded()) |
55a1db4f | 195 | { |
41106a50 WL |
196 | std::map<uint256, CWalletTx>::iterator mi = wallet->mapWallet.find(rec->hash); |
197 | ||
198 | if(mi != wallet->mapWallet.end()) | |
199 | { | |
200 | rec->updateStatus(mi->second); | |
201 | } | |
64bca50d WL |
202 | } |
203 | } | |
204 | return rec; | |
8e86dca2 | 205 | } |
bbad6832 | 206 | return 0; |
213f7636 | 207 | } |
63760fa1 | 208 | |
bdd0c59a | 209 | QString describe(TransactionRecord *rec, int unit) |
66d536ed | 210 | { |
66d536ed | 211 | { |
55a1db4f | 212 | LOCK2(cs_main, wallet->cs_wallet); |
e8ef3da7 WL |
213 | std::map<uint256, CWalletTx>::iterator mi = wallet->mapWallet.find(rec->hash); |
214 | if(mi != wallet->mapWallet.end()) | |
66d536ed | 215 | { |
b90711ca | 216 | return TransactionDesc::toHTML(wallet, mi->second, rec, unit); |
66d536ed WL |
217 | } |
218 | } | |
bbad6832 | 219 | return QString(); |
66d536ed | 220 | } |
213f7636 WL |
221 | }; |
222 | ||
ebff5c40 | 223 | TransactionTableModel::TransactionTableModel(CWallet* wallet, WalletModel *parent): |
213f7636 | 224 | QAbstractTableModel(parent), |
e8ef3da7 | 225 | wallet(wallet), |
ebff5c40 | 226 | walletModel(parent), |
023e63df WL |
227 | priv(new TransactionTablePriv(wallet, this)), |
228 | fProcessingQueuedTransactions(false) | |
4d27c960 | 229 | { |
e96028c7 | 230 | columns << QString() << QString() << tr("Date") << tr("Type") << tr("Label") << BitcoinUnits::getAmountColumnTitle(walletModel->getOptionsModel()->getDisplayUnit()); |
5d5990dc | 231 | priv->refreshWallet(); |
63760fa1 | 232 | |
229c34f8 | 233 | connect(walletModel->getOptionsModel(), SIGNAL(displayUnitChanged(int)), this, SLOT(updateDisplayUnit())); |
023e63df WL |
234 | |
235 | subscribeToCoreSignals(); | |
213f7636 WL |
236 | } |
237 | ||
238 | TransactionTableModel::~TransactionTableModel() | |
239 | { | |
023e63df | 240 | unsubscribeFromCoreSignals(); |
5d5990dc | 241 | delete priv; |
4d27c960 WL |
242 | } |
243 | ||
8969828d | 244 | /** Updates the column title to "Amount (DisplayUnit)" and emits headerDataChanged() signal for table headers to react. */ |
245 | void TransactionTableModel::updateAmountColumnTitle() | |
246 | { | |
247 | columns[Amount] = BitcoinUnits::getAmountColumnTitle(walletModel->getOptionsModel()->getDisplayUnit()); | |
248 | emit headerDataChanged(Qt::Horizontal,Amount,Amount); | |
249 | } | |
250 | ||
023e63df | 251 | void TransactionTableModel::updateTransaction(const QString &hash, int status, bool showTransaction) |
dd8e82f7 | 252 | { |
fe4a6550 WL |
253 | uint256 updated; |
254 | updated.SetHex(hash.toStdString()); | |
63760fa1 | 255 | |
023e63df | 256 | priv->updateWallet(updated, status, showTransaction); |
fe4a6550 | 257 | } |
63760fa1 | 258 | |
fe4a6550 WL |
259 | void TransactionTableModel::updateConfirmations() |
260 | { | |
55a1db4f WL |
261 | // Blocks came in since last poll. |
262 | // Invalidate status (number of confirmations) and (possibly) description | |
263 | // for all rows. Qt is smart enough to only actually request the data for the | |
264 | // visible rows. | |
265 | emit dataChanged(index(0, Status), index(priv->size()-1, Status)); | |
266 | emit dataChanged(index(0, ToAddress), index(priv->size()-1, ToAddress)); | |
dd8e82f7 | 267 | } |
213f7636 | 268 | |
4d27c960 WL |
269 | int TransactionTableModel::rowCount(const QModelIndex &parent) const |
270 | { | |
271 | Q_UNUSED(parent); | |
5d5990dc | 272 | return priv->size(); |
4d27c960 WL |
273 | } |
274 | ||
275 | int TransactionTableModel::columnCount(const QModelIndex &parent) const | |
276 | { | |
277 | Q_UNUSED(parent); | |
278 | return columns.length(); | |
279 | } | |
280 | ||
126185aa | 281 | QString TransactionTableModel::formatTxStatus(const TransactionRecord *wtx) const |
213f7636 | 282 | { |
0856c1a0 | 283 | QString status; |
f79efbab | 284 | |
f642fd9d | 285 | switch(wtx->status.status) |
e5992468 | 286 | { |
f642fd9d WL |
287 | case TransactionStatus::OpenUntilBlock: |
288 | status = tr("Open for %n more block(s)","",wtx->status.open_for); | |
289 | break; | |
290 | case TransactionStatus::OpenUntilDate: | |
291 | status = tr("Open until %1").arg(GUIUtil::dateTimeStr(wtx->status.open_for)); | |
292 | break; | |
293 | case TransactionStatus::Offline: | |
294 | status = tr("Offline"); | |
295 | break; | |
296 | case TransactionStatus::Unconfirmed: | |
297 | status = tr("Unconfirmed"); | |
298 | break; | |
299 | case TransactionStatus::Confirming: | |
300 | status = tr("Confirming (%1 of %2 recommended confirmations)").arg(wtx->status.depth).arg(TransactionRecord::RecommendedNumConfirmations); | |
301 | break; | |
302 | case TransactionStatus::Confirmed: | |
303 | status = tr("Confirmed (%1 confirmations)").arg(wtx->status.depth); | |
304 | break; | |
305 | case TransactionStatus::Conflicted: | |
306 | status = tr("Conflicted"); | |
307 | break; | |
308 | case TransactionStatus::Immature: | |
309 | status = tr("Immature (%1 confirmations, will be available after %2)").arg(wtx->status.depth).arg(wtx->status.depth + wtx->status.matures_in); | |
310 | break; | |
311 | case TransactionStatus::MaturesWarning: | |
312 | status = tr("This block was not received by any other nodes and will probably not be accepted!"); | |
313 | break; | |
314 | case TransactionStatus::NotAccepted: | |
315 | status = tr("Generated but not accepted"); | |
316 | break; | |
e5992468 | 317 | } |
0856c1a0 | 318 | |
126185aa | 319 | return status; |
213f7636 WL |
320 | } |
321 | ||
126185aa | 322 | QString TransactionTableModel::formatTxDate(const TransactionRecord *wtx) const |
213f7636 | 323 | { |
0856c1a0 WL |
324 | if(wtx->time) |
325 | { | |
b0849613 | 326 | return GUIUtil::dateTimeStr(wtx->time); |
8e86dca2 | 327 | } |
bbad6832 | 328 | return QString(); |
213f7636 WL |
329 | } |
330 | ||
2f5d3809 WL |
331 | /* Look up address in address book, if found return label (address) |
332 | otherwise just return (address) | |
ceb6d4e1 | 333 | */ |
2f5d3809 | 334 | QString TransactionTableModel::lookupAddress(const std::string &address, bool tooltip) const |
ceb6d4e1 | 335 | { |
51d7cc07 | 336 | QString label = walletModel->getAddressTableModel()->labelForAddress(QString::fromStdString(address)); |
ceb6d4e1 | 337 | QString description; |
2f5d3809 | 338 | if(!label.isEmpty()) |
ceb6d4e1 | 339 | { |
bbad6832 | 340 | description += label; |
ceb6d4e1 | 341 | } |
bdba2dd0 | 342 | if(label.isEmpty() || tooltip) |
ceb6d4e1 | 343 | { |
bbad6832 | 344 | description += QString(" (") + QString::fromStdString(address) + QString(")"); |
ceb6d4e1 | 345 | } |
f79efbab WL |
346 | return description; |
347 | } | |
348 | ||
05bcf708 | 349 | QString TransactionTableModel::formatTxType(const TransactionRecord *wtx) const |
213f7636 | 350 | { |
f488e735 WL |
351 | switch(wtx->type) |
352 | { | |
8c937da5 | 353 | case TransactionRecord::RecvWithAddress: |
05bcf708 | 354 | return tr("Received with"); |
56c6e369 WL |
355 | case TransactionRecord::RecvFromOther: |
356 | return tr("Received from"); | |
f488e735 | 357 | case TransactionRecord::SendToAddress: |
56c6e369 | 358 | case TransactionRecord::SendToOther: |
05bcf708 | 359 | return tr("Sent to"); |
f488e735 | 360 | case TransactionRecord::SendToSelf: |
05bcf708 | 361 | return tr("Payment to yourself"); |
34fa1782 | 362 | case TransactionRecord::Generated: |
05bcf708 WL |
363 | return tr("Mined"); |
364 | default: | |
365 | return QString(); | |
34fa1782 | 366 | } |
34fa1782 WL |
367 | } |
368 | ||
05bcf708 | 369 | QVariant TransactionTableModel::txAddressDecoration(const TransactionRecord *wtx) const |
34fa1782 | 370 | { |
05bcf708 WL |
371 | switch(wtx->type) |
372 | { | |
373 | case TransactionRecord::Generated: | |
374 | return QIcon(":/icons/tx_mined"); | |
375 | case TransactionRecord::RecvWithAddress: | |
56c6e369 | 376 | case TransactionRecord::RecvFromOther: |
05bcf708 WL |
377 | return QIcon(":/icons/tx_input"); |
378 | case TransactionRecord::SendToAddress: | |
56c6e369 | 379 | case TransactionRecord::SendToOther: |
05bcf708 WL |
380 | return QIcon(":/icons/tx_output"); |
381 | default: | |
382 | return QIcon(":/icons/tx_inout"); | |
383 | } | |
05bcf708 | 384 | } |
34fa1782 | 385 | |
05bcf708 WL |
386 | QString TransactionTableModel::formatTxToAddress(const TransactionRecord *wtx, bool tooltip) const |
387 | { | |
fbe0fcae PK |
388 | QString watchAddress; |
389 | if (tooltip) { | |
390 | // Mark transactions involving watch-only addresses by adding " (watch-only)" | |
391 | watchAddress = wtx->involvesWatchAddress ? QString(" (") + tr("watch-only") + QString(")") : ""; | |
392 | } | |
393 | ||
34fa1782 WL |
394 | switch(wtx->type) |
395 | { | |
56c6e369 | 396 | case TransactionRecord::RecvFromOther: |
fbe0fcae | 397 | return QString::fromStdString(wtx->address) + watchAddress; |
2f5d3809 | 398 | case TransactionRecord::RecvWithAddress: |
34fa1782 | 399 | case TransactionRecord::SendToAddress: |
e07c8e91 | 400 | case TransactionRecord::Generated: |
fbe0fcae | 401 | return lookupAddress(wtx->address, tooltip) + watchAddress; |
56c6e369 | 402 | case TransactionRecord::SendToOther: |
fbe0fcae | 403 | return QString::fromStdString(wtx->address) + watchAddress; |
34fa1782 | 404 | case TransactionRecord::SendToSelf: |
05bcf708 | 405 | default: |
fbe0fcae | 406 | return tr("(n/a)") + watchAddress; |
f488e735 | 407 | } |
213f7636 WL |
408 | } |
409 | ||
2f5d3809 WL |
410 | QVariant TransactionTableModel::addressColor(const TransactionRecord *wtx) const |
411 | { | |
412 | // Show addresses without label in a less visible color | |
413 | switch(wtx->type) | |
414 | { | |
415 | case TransactionRecord::RecvWithAddress: | |
416 | case TransactionRecord::SendToAddress: | |
e07c8e91 | 417 | case TransactionRecord::Generated: |
2f5d3809 WL |
418 | { |
419 | QString label = walletModel->getAddressTableModel()->labelForAddress(QString::fromStdString(wtx->address)); | |
420 | if(label.isEmpty()) | |
421 | return COLOR_BAREADDRESS; | |
422 | } break; | |
d8f5c59a | 423 | case TransactionRecord::SendToSelf: |
d8f5c59a | 424 | return COLOR_BAREADDRESS; |
2f5d3809 WL |
425 | default: |
426 | break; | |
427 | } | |
428 | return QVariant(); | |
429 | } | |
430 | ||
f7d70c60 | 431 | QString TransactionTableModel::formatTxAmount(const TransactionRecord *wtx, bool showUnconfirmed, BitcoinUnits::SeparatorStyle separators) const |
213f7636 | 432 | { |
f7d70c60 | 433 | QString str = BitcoinUnits::format(walletModel->getOptionsModel()->getDisplayUnit(), wtx->credit + wtx->debit, false, separators); |
fbaee7a8 | 434 | if(showUnconfirmed) |
0856c1a0 | 435 | { |
f642fd9d | 436 | if(!wtx->status.countsForBalance) |
fbaee7a8 WL |
437 | { |
438 | str = QString("[") + str + QString("]"); | |
439 | } | |
0856c1a0 | 440 | } |
126185aa | 441 | return QString(str); |
213f7636 WL |
442 | } |
443 | ||
126185aa | 444 | QVariant TransactionTableModel::txStatusDecoration(const TransactionRecord *wtx) const |
249300ae | 445 | { |
f642fd9d | 446 | switch(wtx->status.status) |
e5992468 | 447 | { |
f642fd9d WL |
448 | case TransactionStatus::OpenUntilBlock: |
449 | case TransactionStatus::OpenUntilDate: | |
21f15164 | 450 | return COLOR_TX_STATUS_OPENUNTILDATE; |
f642fd9d | 451 | case TransactionStatus::Offline: |
21f15164 | 452 | return COLOR_TX_STATUS_OFFLINE; |
f642fd9d WL |
453 | case TransactionStatus::Unconfirmed: |
454 | return QIcon(":/icons/transaction_0"); | |
455 | case TransactionStatus::Confirming: | |
456 | switch(wtx->status.depth) | |
e5992468 | 457 | { |
f642fd9d WL |
458 | case 1: return QIcon(":/icons/transaction_1"); |
459 | case 2: return QIcon(":/icons/transaction_2"); | |
460 | case 3: return QIcon(":/icons/transaction_3"); | |
461 | case 4: return QIcon(":/icons/transaction_4"); | |
462 | default: return QIcon(":/icons/transaction_5"); | |
463 | }; | |
464 | case TransactionStatus::Confirmed: | |
465 | return QIcon(":/icons/transaction_confirmed"); | |
466 | case TransactionStatus::Conflicted: | |
467 | return QIcon(":/icons/transaction_conflicted"); | |
468 | case TransactionStatus::Immature: { | |
469 | int total = wtx->status.depth + wtx->status.matures_in; | |
470 | int part = (wtx->status.depth * 4 / total) + 1; | |
471 | return QIcon(QString(":/icons/transaction_%1").arg(part)); | |
e5992468 | 472 | } |
f642fd9d WL |
473 | case TransactionStatus::MaturesWarning: |
474 | case TransactionStatus::NotAccepted: | |
475 | return QIcon(":/icons/transaction_0"); | |
21f15164 PK |
476 | default: |
477 | return COLOR_BLACK; | |
249300ae | 478 | } |
249300ae WL |
479 | } |
480 | ||
1c5f0af0 CL |
481 | QVariant TransactionTableModel::txWatchonlyDecoration(const TransactionRecord *wtx) const |
482 | { | |
483 | if (wtx->involvesWatchAddress) | |
484 | return QIcon(":/icons/eye"); | |
485 | else | |
486 | return QVariant(); | |
487 | } | |
488 | ||
126185aa WL |
489 | QString TransactionTableModel::formatTooltip(const TransactionRecord *rec) const |
490 | { | |
e74e8a18 | 491 | QString tooltip = formatTxStatus(rec) + QString("\n") + formatTxType(rec); |
56c6e369 | 492 | if(rec->type==TransactionRecord::RecvFromOther || rec->type==TransactionRecord::SendToOther || |
126185aa WL |
493 | rec->type==TransactionRecord::SendToAddress || rec->type==TransactionRecord::RecvWithAddress) |
494 | { | |
495 | tooltip += QString(" ") + formatTxToAddress(rec, true); | |
496 | } | |
126185aa WL |
497 | return tooltip; |
498 | } | |
499 | ||
4d27c960 WL |
500 | QVariant TransactionTableModel::data(const QModelIndex &index, int role) const |
501 | { | |
502 | if(!index.isValid()) | |
503 | return QVariant(); | |
213f7636 | 504 | TransactionRecord *rec = static_cast<TransactionRecord*>(index.internalPointer()); |
4d27c960 | 505 | |
e74e8a18 | 506 | switch(role) |
249300ae | 507 | { |
9b7d3fb1 | 508 | case RawDecorationRole: |
05bcf708 | 509 | switch(index.column()) |
249300ae | 510 | { |
05bcf708 | 511 | case Status: |
126185aa | 512 | return txStatusDecoration(rec); |
1c5f0af0 CL |
513 | case Watchonly: |
514 | return txWatchonlyDecoration(rec); | |
05bcf708 WL |
515 | case ToAddress: |
516 | return txAddressDecoration(rec); | |
249300ae | 517 | } |
e74e8a18 | 518 | break; |
9b7d3fb1 LD |
519 | case Qt::DecorationRole: |
520 | { | |
521 | QIcon icon = qvariant_cast<QIcon>(index.data(RawDecorationRole)); | |
522 | return TextColorIcon(icon); | |
523 | } | |
e74e8a18 | 524 | case Qt::DisplayRole: |
213f7636 WL |
525 | switch(index.column()) |
526 | { | |
213f7636 WL |
527 | case Date: |
528 | return formatTxDate(rec); | |
34fa1782 WL |
529 | case Type: |
530 | return formatTxType(rec); | |
531 | case ToAddress: | |
2f5d3809 | 532 | return formatTxToAddress(rec, false); |
34fa1782 | 533 | case Amount: |
f7d70c60 | 534 | return formatTxAmount(rec, true, BitcoinUnits::separatorAlways); |
213f7636 | 535 | } |
e74e8a18 WL |
536 | break; |
537 | case Qt::EditRole: | |
538 | // Edit role is used for sorting, so return the unformatted values | |
63760fa1 WL |
539 | switch(index.column()) |
540 | { | |
541 | case Status: | |
542 | return QString::fromStdString(rec->status.sortKey); | |
543 | case Date: | |
544 | return rec->time; | |
34fa1782 WL |
545 | case Type: |
546 | return formatTxType(rec); | |
1c5f0af0 CL |
547 | case Watchonly: |
548 | return (rec->involvesWatchAddress ? 1 : 0); | |
34fa1782 | 549 | case ToAddress: |
2f5d3809 | 550 | return formatTxToAddress(rec, true); |
34fa1782 | 551 | case Amount: |
a372168e | 552 | return qint64(rec->credit + rec->debit); |
63760fa1 | 553 | } |
e74e8a18 WL |
554 | break; |
555 | case Qt::ToolTipRole: | |
556 | return formatTooltip(rec); | |
557 | case Qt::TextAlignmentRole: | |
1355cfe1 | 558 | return column_alignments[index.column()]; |
e74e8a18 | 559 | case Qt::ForegroundRole: |
f642fd9d WL |
560 | // Non-confirmed (but not immature) as transactions are grey |
561 | if(!rec->status.countsForBalance && rec->status.status != TransactionStatus::Immature) | |
8e86dca2 | 562 | { |
bbae0fc9 | 563 | return COLOR_UNCONFIRMED; |
f488e735 | 564 | } |
34fa1782 WL |
565 | if(index.column() == Amount && (rec->credit+rec->debit) < 0) |
566 | { | |
bbae0fc9 | 567 | return COLOR_NEGATIVE; |
34fa1782 | 568 | } |
2f5d3809 WL |
569 | if(index.column() == ToAddress) |
570 | { | |
571 | return addressColor(rec); | |
572 | } | |
e74e8a18 WL |
573 | break; |
574 | case TypeRole: | |
ceb6d4e1 | 575 | return rec->type; |
e74e8a18 | 576 | case DateRole: |
ceb6d4e1 | 577 | return QDateTime::fromTime_t(static_cast<uint>(rec->time)); |
1c5f0af0 CL |
578 | case WatchonlyRole: |
579 | return rec->involvesWatchAddress; | |
580 | case WatchonlyDecorationRole: | |
581 | return txWatchonlyDecoration(rec); | |
e74e8a18 | 582 | case LongDescriptionRole: |
bdd0c59a | 583 | return priv->describe(rec, walletModel->getOptionsModel()->getDisplayUnit()); |
e74e8a18 | 584 | case AddressRole: |
ceb6d4e1 | 585 | return QString::fromStdString(rec->address); |
e74e8a18 | 586 | case LabelRole: |
51d7cc07 | 587 | return walletModel->getAddressTableModel()->labelForAddress(QString::fromStdString(rec->address)); |
e74e8a18 | 588 | case AmountRole: |
a372168e | 589 | return qint64(rec->credit + rec->debit); |
e74e8a18 | 590 | case TxIDRole: |
ed4c7fd4 | 591 | return rec->getTxID(); |
40c5b939 CL |
592 | case TxHashRole: |
593 | return QString::fromStdString(rec->hash.ToString()); | |
e74e8a18 | 594 | case ConfirmedRole: |
f642fd9d | 595 | return rec->status.countsForBalance; |
e74e8a18 | 596 | case FormattedAmountRole: |
f65352a7 | 597 | // Used for copy/export, so don't include separators |
70074029 | 598 | return formatTxAmount(rec, false, BitcoinUnits::separatorNever); |
9a3d936f WL |
599 | case StatusRole: |
600 | return rec->status.status; | |
fbaee7a8 | 601 | } |
4d27c960 WL |
602 | return QVariant(); |
603 | } | |
604 | ||
605 | QVariant TransactionTableModel::headerData(int section, Qt::Orientation orientation, int role) const | |
606 | { | |
f6c18bc9 | 607 | if(orientation == Qt::Horizontal) |
1355cfe1 | 608 | { |
f6c18bc9 | 609 | if(role == Qt::DisplayRole) |
1355cfe1 WL |
610 | { |
611 | return columns[section]; | |
8e86dca2 WL |
612 | } |
613 | else if (role == Qt::TextAlignmentRole) | |
f6c18bc9 WL |
614 | { |
615 | return column_alignments[section]; | |
a790ec58 WL |
616 | } else if (role == Qt::ToolTipRole) |
617 | { | |
618 | switch(section) | |
619 | { | |
620 | case Status: | |
40951d81 | 621 | return tr("Transaction status. Hover over this field to show number of confirmations."); |
a790ec58 WL |
622 | case Date: |
623 | return tr("Date and time that the transaction was received."); | |
34fa1782 WL |
624 | case Type: |
625 | return tr("Type of transaction."); | |
1c5f0af0 CL |
626 | case Watchonly: |
627 | return tr("Whether or not a watch-only address is involved in this transaction."); | |
34fa1782 | 628 | case ToAddress: |
e96028c7 | 629 | return tr("User-defined intent/purpose of the transaction."); |
34fa1782 WL |
630 | case Amount: |
631 | return tr("Amount removed from or added to balance."); | |
a790ec58 | 632 | } |
1355cfe1 | 633 | } |
4d27c960 WL |
634 | } |
635 | return QVariant(); | |
636 | } | |
637 | ||
5d5990dc | 638 | QModelIndex TransactionTableModel::index(int row, int column, const QModelIndex &parent) const |
213f7636 WL |
639 | { |
640 | Q_UNUSED(parent); | |
5d5990dc | 641 | TransactionRecord *data = priv->index(row); |
213f7636 WL |
642 | if(data) |
643 | { | |
5d5990dc | 644 | return createIndex(row, column, priv->index(row)); |
8e86dca2 | 645 | } |
bbad6832 | 646 | return QModelIndex(); |
213f7636 WL |
647 | } |
648 | ||
229c34f8 PK |
649 | void TransactionTableModel::updateDisplayUnit() |
650 | { | |
651 | // emit dataChanged to update Amount column with the current unit | |
8969828d | 652 | updateAmountColumnTitle(); |
229c34f8 PK |
653 | emit dataChanged(index(0, Amount), index(priv->size()-1, Amount)); |
654 | } | |
023e63df WL |
655 | |
656 | // queue notifications to show a non freezing progress dialog e.g. for rescan | |
657 | struct TransactionNotification | |
658 | { | |
659 | public: | |
660 | TransactionNotification() {} | |
661 | TransactionNotification(uint256 hash, ChangeType status, bool showTransaction): | |
662 | hash(hash), status(status), showTransaction(showTransaction) {} | |
663 | ||
664 | void invoke(QObject *ttm) | |
665 | { | |
666 | QString strHash = QString::fromStdString(hash.GetHex()); | |
5262fde0 | 667 | qDebug() << "NotifyTransactionChanged: " + strHash + " status= " + QString::number(status); |
023e63df WL |
668 | QMetaObject::invokeMethod(ttm, "updateTransaction", Qt::QueuedConnection, |
669 | Q_ARG(QString, strHash), | |
670 | Q_ARG(int, status), | |
671 | Q_ARG(bool, showTransaction)); | |
672 | } | |
673 | private: | |
674 | uint256 hash; | |
675 | ChangeType status; | |
676 | bool showTransaction; | |
677 | }; | |
678 | ||
679 | static bool fQueueNotifications = false; | |
680 | static std::vector< TransactionNotification > vQueueNotifications; | |
681 | ||
682 | static void NotifyTransactionChanged(TransactionTableModel *ttm, CWallet *wallet, const uint256 &hash, ChangeType status) | |
683 | { | |
684 | // Find transaction in wallet | |
685 | std::map<uint256, CWalletTx>::iterator mi = wallet->mapWallet.find(hash); | |
686 | // Determine whether to show transaction or not (determine this here so that no relocking is needed in GUI thread) | |
687 | bool inWallet = mi != wallet->mapWallet.end(); | |
688 | bool showTransaction = (inWallet && TransactionRecord::showTransaction(mi->second)); | |
689 | ||
690 | TransactionNotification notification(hash, status, showTransaction); | |
691 | ||
692 | if (fQueueNotifications) | |
693 | { | |
694 | vQueueNotifications.push_back(notification); | |
695 | return; | |
696 | } | |
697 | notification.invoke(ttm); | |
698 | } | |
699 | ||
700 | static void ShowProgress(TransactionTableModel *ttm, const std::string &title, int nProgress) | |
701 | { | |
702 | if (nProgress == 0) | |
703 | fQueueNotifications = true; | |
704 | ||
705 | if (nProgress == 100) | |
706 | { | |
707 | fQueueNotifications = false; | |
708 | if (vQueueNotifications.size() > 10) // prevent balloon spam, show maximum 10 balloons | |
709 | QMetaObject::invokeMethod(ttm, "setProcessingQueuedTransactions", Qt::QueuedConnection, Q_ARG(bool, true)); | |
710 | for (unsigned int i = 0; i < vQueueNotifications.size(); ++i) | |
711 | { | |
712 | if (vQueueNotifications.size() - i <= 10) | |
713 | QMetaObject::invokeMethod(ttm, "setProcessingQueuedTransactions", Qt::QueuedConnection, Q_ARG(bool, false)); | |
714 | ||
715 | vQueueNotifications[i].invoke(ttm); | |
716 | } | |
717 | std::vector<TransactionNotification >().swap(vQueueNotifications); // clear | |
718 | } | |
719 | } | |
720 | ||
721 | void TransactionTableModel::subscribeToCoreSignals() | |
722 | { | |
723 | // Connect signals to wallet | |
724 | wallet->NotifyTransactionChanged.connect(boost::bind(NotifyTransactionChanged, this, _1, _2, _3)); | |
725 | wallet->ShowProgress.connect(boost::bind(ShowProgress, this, _1, _2)); | |
726 | } | |
727 | ||
728 | void TransactionTableModel::unsubscribeFromCoreSignals() | |
729 | { | |
730 | // Disconnect signals from wallet | |
731 | wallet->NotifyTransactionChanged.disconnect(boost::bind(NotifyTransactionChanged, this, _1, _2, _3)); | |
732 | wallet->ShowProgress.disconnect(boost::bind(ShowProgress, this, _1, _2)); | |
733 | } |