Line data Source code
1 : // Copyright (c) 2011-2019 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/overviewpage.h>
6 : #include <qt/forms/ui_overviewpage.h>
7 :
8 : #include <qt/bitcoinunits.h>
9 : #include <qt/clientmodel.h>
10 : #include <qt/guiconstants.h>
11 : #include <qt/guiutil.h>
12 : #include <qt/optionsmodel.h>
13 : #include <qt/platformstyle.h>
14 : #include <qt/transactionfilterproxy.h>
15 : #include <qt/transactiontablemodel.h>
16 : #include <qt/walletmodel.h>
17 :
18 : #include <QAbstractItemDelegate>
19 : #include <QApplication>
20 : #include <QPainter>
21 : #include <QStatusTipEvent>
22 :
23 : #define DECORATION_SIZE 54
24 : #define NUM_ITEMS 5
25 :
26 0 : Q_DECLARE_METATYPE(interfaces::WalletBalances)
27 :
28 0 : class TxViewDelegate : public QAbstractItemDelegate
29 : {
30 : Q_OBJECT
31 : public:
32 0 : explicit TxViewDelegate(const PlatformStyle *_platformStyle, QObject *parent=nullptr):
33 0 : QAbstractItemDelegate(parent), unit(BitcoinUnits::BTC),
34 0 : platformStyle(_platformStyle)
35 0 : {
36 :
37 0 : }
38 :
39 0 : inline void paint(QPainter *painter, const QStyleOptionViewItem &option,
40 : const QModelIndex &index ) const override
41 : {
42 0 : painter->save();
43 :
44 0 : QIcon icon = qvariant_cast<QIcon>(index.data(TransactionTableModel::RawDecorationRole));
45 0 : QRect mainRect = option.rect;
46 0 : QRect decorationRect(mainRect.topLeft(), QSize(DECORATION_SIZE, DECORATION_SIZE));
47 : int xspace = DECORATION_SIZE + 8;
48 : int ypad = 6;
49 0 : int halfheight = (mainRect.height() - 2*ypad)/2;
50 0 : QRect amountRect(mainRect.left() + xspace, mainRect.top()+ypad, mainRect.width() - xspace, halfheight);
51 0 : QRect addressRect(mainRect.left() + xspace, mainRect.top()+ypad+halfheight, mainRect.width() - xspace, halfheight);
52 0 : icon = platformStyle->SingleColorIcon(icon);
53 0 : icon.paint(painter, decorationRect);
54 :
55 0 : QDateTime date = index.data(TransactionTableModel::DateRole).toDateTime();
56 0 : QString address = index.data(Qt::DisplayRole).toString();
57 0 : qint64 amount = index.data(TransactionTableModel::AmountRole).toLongLong();
58 0 : bool confirmed = index.data(TransactionTableModel::ConfirmedRole).toBool();
59 0 : QVariant value = index.data(Qt::ForegroundRole);
60 0 : QColor foreground = option.palette.color(QPalette::Text);
61 0 : if(value.canConvert<QBrush>())
62 : {
63 0 : QBrush brush = qvariant_cast<QBrush>(value);
64 0 : foreground = brush.color();
65 0 : }
66 :
67 0 : painter->setPen(foreground);
68 0 : QRect boundingRect;
69 0 : painter->drawText(addressRect, Qt::AlignLeft|Qt::AlignVCenter, address, &boundingRect);
70 :
71 0 : if (index.data(TransactionTableModel::WatchonlyRole).toBool())
72 : {
73 0 : QIcon iconWatchonly = qvariant_cast<QIcon>(index.data(TransactionTableModel::WatchonlyDecorationRole));
74 0 : QRect watchonlyRect(boundingRect.right() + 5, mainRect.top()+ypad+halfheight, 16, halfheight);
75 0 : iconWatchonly.paint(painter, watchonlyRect);
76 0 : }
77 :
78 0 : if(amount < 0)
79 : {
80 0 : foreground = COLOR_NEGATIVE;
81 0 : }
82 0 : else if(!confirmed)
83 : {
84 0 : foreground = COLOR_UNCONFIRMED;
85 0 : }
86 : else
87 : {
88 0 : foreground = option.palette.color(QPalette::Text);
89 : }
90 0 : painter->setPen(foreground);
91 0 : QString amountText = BitcoinUnits::formatWithUnit(unit, amount, true, BitcoinUnits::SeparatorStyle::ALWAYS);
92 0 : if(!confirmed)
93 : {
94 0 : amountText = QString("[") + amountText + QString("]");
95 0 : }
96 0 : painter->drawText(amountRect, Qt::AlignRight|Qt::AlignVCenter, amountText);
97 :
98 0 : painter->setPen(option.palette.color(QPalette::Text));
99 0 : painter->drawText(amountRect, Qt::AlignLeft|Qt::AlignVCenter, GUIUtil::dateTimeStr(date));
100 :
101 0 : painter->restore();
102 0 : }
103 :
104 0 : inline QSize sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const override
105 : {
106 0 : return QSize(DECORATION_SIZE, DECORATION_SIZE);
107 : }
108 :
109 : int unit;
110 : const PlatformStyle *platformStyle;
111 :
112 : };
113 : #include <qt/overviewpage.moc>
114 :
115 0 : OverviewPage::OverviewPage(const PlatformStyle *platformStyle, QWidget *parent) :
116 0 : QWidget(parent),
117 0 : ui(new Ui::OverviewPage),
118 0 : clientModel(nullptr),
119 0 : walletModel(nullptr),
120 0 : txdelegate(new TxViewDelegate(platformStyle, this))
121 0 : {
122 0 : ui->setupUi(this);
123 :
124 0 : m_balances.balance = -1;
125 :
126 : // use a SingleColorIcon for the "out of sync warning" icon
127 0 : QIcon icon = platformStyle->SingleColorIcon(":/icons/warning");
128 0 : icon.addPixmap(icon.pixmap(QSize(64,64), QIcon::Normal), QIcon::Disabled); // also set the disabled icon because we are using a disabled QPushButton to work around missing HiDPI support of QLabel (https://bugreports.qt.io/browse/QTBUG-42503)
129 0 : ui->labelTransactionsStatus->setIcon(icon);
130 0 : ui->labelWalletStatus->setIcon(icon);
131 :
132 : // Recent transactions
133 0 : ui->listTransactions->setItemDelegate(txdelegate);
134 0 : ui->listTransactions->setIconSize(QSize(DECORATION_SIZE, DECORATION_SIZE));
135 0 : ui->listTransactions->setMinimumHeight(NUM_ITEMS * (DECORATION_SIZE + 2));
136 0 : ui->listTransactions->setAttribute(Qt::WA_MacShowFocusRect, false);
137 :
138 0 : connect(ui->listTransactions, &QListView::clicked, this, &OverviewPage::handleTransactionClicked);
139 :
140 : // start with displaying the "out of sync" warnings
141 0 : showOutOfSyncWarning(true);
142 0 : connect(ui->labelWalletStatus, &QPushButton::clicked, this, &OverviewPage::handleOutOfSyncWarningClicks);
143 0 : connect(ui->labelTransactionsStatus, &QPushButton::clicked, this, &OverviewPage::handleOutOfSyncWarningClicks);
144 0 : }
145 :
146 0 : void OverviewPage::handleTransactionClicked(const QModelIndex &index)
147 : {
148 0 : if(filter)
149 0 : Q_EMIT transactionClicked(filter->mapToSource(index));
150 0 : }
151 :
152 0 : void OverviewPage::handleOutOfSyncWarningClicks()
153 : {
154 0 : Q_EMIT outOfSyncWarningClicked();
155 0 : }
156 :
157 0 : void OverviewPage::setPrivacy(bool privacy)
158 : {
159 0 : m_privacy = privacy;
160 0 : if (m_balances.balance != -1) {
161 0 : setBalance(m_balances);
162 0 : }
163 :
164 0 : ui->listTransactions->setVisible(!m_privacy);
165 :
166 0 : const QString status_tip = m_privacy ? tr("Privacy mode activated for the Overview tab. To unmask the values, uncheck Settings->Mask values.") : "";
167 0 : setStatusTip(status_tip);
168 0 : QStatusTipEvent event(status_tip);
169 0 : QApplication::sendEvent(this, &event);
170 0 : }
171 :
172 0 : OverviewPage::~OverviewPage()
173 0 : {
174 0 : delete ui;
175 0 : }
176 :
177 0 : void OverviewPage::setBalance(const interfaces::WalletBalances& balances)
178 : {
179 0 : int unit = walletModel->getOptionsModel()->getDisplayUnit();
180 0 : m_balances = balances;
181 0 : if (walletModel->wallet().isLegacy()) {
182 0 : if (walletModel->wallet().privateKeysDisabled()) {
183 0 : ui->labelBalance->setText(BitcoinUnits::formatWithPrivacy(unit, balances.watch_only_balance, BitcoinUnits::SeparatorStyle::ALWAYS, m_privacy));
184 0 : ui->labelUnconfirmed->setText(BitcoinUnits::formatWithPrivacy(unit, balances.unconfirmed_watch_only_balance, BitcoinUnits::SeparatorStyle::ALWAYS, m_privacy));
185 0 : ui->labelImmature->setText(BitcoinUnits::formatWithPrivacy(unit, balances.immature_watch_only_balance, BitcoinUnits::SeparatorStyle::ALWAYS, m_privacy));
186 0 : ui->labelTotal->setText(BitcoinUnits::formatWithPrivacy(unit, balances.watch_only_balance + balances.unconfirmed_watch_only_balance + balances.immature_watch_only_balance, BitcoinUnits::SeparatorStyle::ALWAYS, m_privacy));
187 0 : } else {
188 0 : ui->labelBalance->setText(BitcoinUnits::formatWithPrivacy(unit, balances.balance, BitcoinUnits::SeparatorStyle::ALWAYS, m_privacy));
189 0 : ui->labelUnconfirmed->setText(BitcoinUnits::formatWithPrivacy(unit, balances.unconfirmed_balance, BitcoinUnits::SeparatorStyle::ALWAYS, m_privacy));
190 0 : ui->labelImmature->setText(BitcoinUnits::formatWithPrivacy(unit, balances.immature_balance, BitcoinUnits::SeparatorStyle::ALWAYS, m_privacy));
191 0 : ui->labelTotal->setText(BitcoinUnits::formatWithPrivacy(unit, balances.balance + balances.unconfirmed_balance + balances.immature_balance, BitcoinUnits::SeparatorStyle::ALWAYS, m_privacy));
192 0 : ui->labelWatchAvailable->setText(BitcoinUnits::formatWithPrivacy(unit, balances.watch_only_balance, BitcoinUnits::SeparatorStyle::ALWAYS, m_privacy));
193 0 : ui->labelWatchPending->setText(BitcoinUnits::formatWithPrivacy(unit, balances.unconfirmed_watch_only_balance, BitcoinUnits::SeparatorStyle::ALWAYS, m_privacy));
194 0 : ui->labelWatchImmature->setText(BitcoinUnits::formatWithPrivacy(unit, balances.immature_watch_only_balance, BitcoinUnits::SeparatorStyle::ALWAYS, m_privacy));
195 0 : ui->labelWatchTotal->setText(BitcoinUnits::formatWithPrivacy(unit, balances.watch_only_balance + balances.unconfirmed_watch_only_balance + balances.immature_watch_only_balance, BitcoinUnits::SeparatorStyle::ALWAYS, m_privacy));
196 : }
197 : } else {
198 0 : ui->labelBalance->setText(BitcoinUnits::formatWithPrivacy(unit, balances.balance, BitcoinUnits::SeparatorStyle::ALWAYS, m_privacy));
199 0 : ui->labelUnconfirmed->setText(BitcoinUnits::formatWithPrivacy(unit, balances.unconfirmed_balance, BitcoinUnits::SeparatorStyle::ALWAYS, m_privacy));
200 0 : ui->labelImmature->setText(BitcoinUnits::formatWithPrivacy(unit, balances.immature_balance, BitcoinUnits::SeparatorStyle::ALWAYS, m_privacy));
201 0 : ui->labelTotal->setText(BitcoinUnits::formatWithPrivacy(unit, balances.balance + balances.unconfirmed_balance + balances.immature_balance, BitcoinUnits::SeparatorStyle::ALWAYS, m_privacy));
202 : }
203 : // only show immature (newly mined) balance if it's non-zero, so as not to complicate things
204 : // for the non-mining users
205 0 : bool showImmature = balances.immature_balance != 0;
206 0 : bool showWatchOnlyImmature = balances.immature_watch_only_balance != 0;
207 :
208 : // for symmetry reasons also show immature label when the watch-only one is shown
209 0 : ui->labelImmature->setVisible(showImmature || showWatchOnlyImmature);
210 0 : ui->labelImmatureText->setVisible(showImmature || showWatchOnlyImmature);
211 0 : ui->labelWatchImmature->setVisible(!walletModel->wallet().privateKeysDisabled() && showWatchOnlyImmature); // show watch-only immature balance
212 0 : }
213 :
214 : // show/hide watch-only labels
215 0 : void OverviewPage::updateWatchOnlyLabels(bool showWatchOnly)
216 : {
217 0 : ui->labelSpendable->setVisible(showWatchOnly); // show spendable label (only when watch-only is active)
218 0 : ui->labelWatchonly->setVisible(showWatchOnly); // show watch-only label
219 0 : ui->lineWatchBalance->setVisible(showWatchOnly); // show watch-only balance separator line
220 0 : ui->labelWatchAvailable->setVisible(showWatchOnly); // show watch-only available balance
221 0 : ui->labelWatchPending->setVisible(showWatchOnly); // show watch-only pending balance
222 0 : ui->labelWatchTotal->setVisible(showWatchOnly); // show watch-only total balance
223 :
224 0 : if (!showWatchOnly)
225 0 : ui->labelWatchImmature->hide();
226 0 : }
227 :
228 0 : void OverviewPage::setClientModel(ClientModel *model)
229 : {
230 0 : this->clientModel = model;
231 0 : if (model) {
232 : // Show warning, for example if this is a prerelease version
233 0 : connect(model, &ClientModel::alertsChanged, this, &OverviewPage::updateAlerts);
234 0 : updateAlerts(model->getStatusBarWarnings());
235 0 : }
236 0 : }
237 :
238 0 : void OverviewPage::setWalletModel(WalletModel *model)
239 : {
240 0 : this->walletModel = model;
241 0 : if(model && model->getOptionsModel())
242 : {
243 : // Set up transaction list
244 0 : filter.reset(new TransactionFilterProxy());
245 0 : filter->setSourceModel(model->getTransactionTableModel());
246 0 : filter->setLimit(NUM_ITEMS);
247 0 : filter->setDynamicSortFilter(true);
248 0 : filter->setSortRole(Qt::EditRole);
249 0 : filter->setShowInactive(false);
250 0 : filter->sort(TransactionTableModel::Date, Qt::DescendingOrder);
251 :
252 0 : ui->listTransactions->setModel(filter.get());
253 0 : ui->listTransactions->setModelColumn(TransactionTableModel::ToAddress);
254 :
255 : // Keep up to date with wallet
256 0 : interfaces::Wallet& wallet = model->wallet();
257 0 : interfaces::WalletBalances balances = wallet.getBalances();
258 0 : setBalance(balances);
259 0 : connect(model, &WalletModel::balanceChanged, this, &OverviewPage::setBalance);
260 :
261 0 : connect(model->getOptionsModel(), &OptionsModel::displayUnitChanged, this, &OverviewPage::updateDisplayUnit);
262 :
263 0 : updateWatchOnlyLabels(wallet.haveWatchOnly() && !model->wallet().privateKeysDisabled());
264 0 : connect(model, &WalletModel::notifyWatchonlyChanged, [this](bool showWatchOnly) {
265 0 : updateWatchOnlyLabels(showWatchOnly && !walletModel->wallet().privateKeysDisabled());
266 0 : });
267 0 : }
268 :
269 : // update the display unit, to not use the default ("BTC")
270 0 : updateDisplayUnit();
271 0 : }
272 :
273 0 : void OverviewPage::updateDisplayUnit()
274 : {
275 0 : if(walletModel && walletModel->getOptionsModel())
276 : {
277 0 : if (m_balances.balance != -1) {
278 0 : setBalance(m_balances);
279 0 : }
280 :
281 : // Update txdelegate->unit with the current unit
282 0 : txdelegate->unit = walletModel->getOptionsModel()->getDisplayUnit();
283 :
284 0 : ui->listTransactions->update();
285 0 : }
286 0 : }
287 :
288 0 : void OverviewPage::updateAlerts(const QString &warnings)
289 : {
290 0 : this->ui->labelAlerts->setVisible(!warnings.isEmpty());
291 0 : this->ui->labelAlerts->setText(warnings);
292 0 : }
293 :
294 0 : void OverviewPage::showOutOfSyncWarning(bool fShow)
295 : {
296 0 : ui->labelWalletStatus->setVisible(fShow);
297 0 : ui->labelTransactionsStatus->setVisible(fShow);
298 0 : }
|