overviewpage.cpp raw
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/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/transactionoverviewwidget.h>
16 #include <qt/transactiontablemodel.h>
17 #include <qt/walletmodel.h>
18
19 #include <QAbstractItemDelegate>
20 #include <QApplication>
21 #include <QDateTime>
22 #include <QPainter>
23 #include <QStatusTipEvent>
24
25 #include <algorithm>
26 #include <map>
27
28 #define DECORATION_SIZE 54
29 #define NUM_ITEMS 5
30
31 Q_DECLARE_METATYPE(interfaces::WalletBalances)
32
33 class TxViewDelegate : public QAbstractItemDelegate
34 {
35 Q_OBJECT
36 public:
37 explicit TxViewDelegate(const PlatformStyle* _platformStyle, QObject* parent = nullptr)
38 : QAbstractItemDelegate(parent), platformStyle(_platformStyle)
39 {
40 connect(this, &TxViewDelegate::width_changed, this, &TxViewDelegate::sizeHintChanged);
41 }
42
43 inline void paint(QPainter *painter, const QStyleOptionViewItem &option,
44 const QModelIndex &index ) const override
45 {
46 painter->save();
47
48 QIcon icon = qvariant_cast<QIcon>(index.data(TransactionTableModel::RawDecorationRole));
49 QRect mainRect = option.rect;
50 QRect decorationRect(mainRect.topLeft(), QSize(DECORATION_SIZE, DECORATION_SIZE));
51 int xspace = DECORATION_SIZE + 8;
52 int ypad = 6;
53 int halfheight = (mainRect.height() - 2*ypad)/2;
54 QRect amountRect(mainRect.left() + xspace, mainRect.top()+ypad, mainRect.width() - xspace, halfheight);
55 QRect addressRect(mainRect.left() + xspace, mainRect.top()+ypad+halfheight, mainRect.width() - xspace, halfheight);
56 icon = platformStyle->SingleColorIcon(icon);
57 icon.paint(painter, decorationRect);
58
59 QDateTime date = index.data(TransactionTableModel::DateRole).toDateTime();
60 QString address = index.data(Qt::DisplayRole).toString();
61 qint64 amount = index.data(TransactionTableModel::AmountRole).toLongLong();
62 bool confirmed = index.data(TransactionTableModel::ConfirmedRole).toBool();
63 QVariant value = index.data(Qt::ForegroundRole);
64 QColor foreground = option.palette.color(QPalette::Text);
65 if(value.canConvert<QBrush>())
66 {
67 QBrush brush = qvariant_cast<QBrush>(value);
68 foreground = brush.color();
69 }
70
71 painter->setPen(foreground);
72 QRect boundingRect;
73 painter->drawText(addressRect, Qt::AlignLeft | Qt::AlignVCenter, address, &boundingRect);
74
75 if(amount < 0)
76 {
77 foreground = COLOR_NEGATIVE;
78 }
79 else if(!confirmed)
80 {
81 foreground = COLOR_UNCONFIRMED;
82 }
83 else
84 {
85 foreground = option.palette.color(QPalette::Text);
86 }
87 painter->setPen(foreground);
88 QString amountText = BitcoinUnits::formatWithUnit(unit, amount, true, BitcoinUnits::SeparatorStyle::ALWAYS);
89 if(!confirmed)
90 {
91 amountText = QString("[") + amountText + QString("]");
92 }
93
94 QRect amount_bounding_rect;
95 painter->drawText(amountRect, Qt::AlignRight | Qt::AlignVCenter, amountText, &amount_bounding_rect);
96
97 painter->setPen(option.palette.color(QPalette::Text));
98 QRect date_bounding_rect;
99 painter->drawText(amountRect, Qt::AlignLeft | Qt::AlignVCenter, GUIUtil::dateTimeStr(date), &date_bounding_rect);
100
101 // 0.4*date_bounding_rect.width() is used to visually distinguish a date from an amount.
102 const int minimum_width = 1.4 * date_bounding_rect.width() + amount_bounding_rect.width();
103 const auto search = m_minimum_width.find(index.row());
104 if (search == m_minimum_width.end() || search->second != minimum_width) {
105 m_minimum_width[index.row()] = minimum_width;
106 Q_EMIT width_changed(index);
107 }
108
109 painter->restore();
110 }
111
112 inline QSize sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const override
113 {
114 const auto search = m_minimum_width.find(index.row());
115 const int minimum_text_width = search == m_minimum_width.end() ? 0 : search->second;
116 return {DECORATION_SIZE + 8 + minimum_text_width, DECORATION_SIZE};
117 }
118
119 BitcoinUnit unit{BitcoinUnit::BTC};
120
121 Q_SIGNALS:
122 //! An intermediate signal for emitting from the `paint() const` member function.
123 void width_changed(const QModelIndex& index) const;
124
125 private:
126 const PlatformStyle* platformStyle;
127 mutable std::map<int, int> m_minimum_width;
128 };
129
130 #include <qt/overviewpage.moc>
131
132 OverviewPage::OverviewPage(const PlatformStyle *platformStyle, QWidget *parent) :
133 QWidget(parent),
134 ui(new Ui::OverviewPage),
135 m_platform_style{platformStyle},
136 txdelegate(new TxViewDelegate(platformStyle, this))
137 {
138 ui->setupUi(this);
139
140 // use a SingleColorIcon for the "out of sync warning" icon
141 QIcon icon = m_platform_style->SingleColorIcon(QStringLiteral(":/icons/warning"));
142 ui->labelTransactionsStatus->setIcon(icon);
143 ui->labelWalletStatus->setIcon(icon);
144
145 // Recent transactions
146 ui->listTransactions->setItemDelegate(txdelegate);
147 ui->listTransactions->setIconSize(QSize(DECORATION_SIZE, DECORATION_SIZE));
148 ui->listTransactions->setMinimumHeight(NUM_ITEMS * (DECORATION_SIZE + 2));
149 ui->listTransactions->setAttribute(Qt::WA_MacShowFocusRect, false);
150
151 connect(ui->listTransactions, &TransactionOverviewWidget::clicked, this, &OverviewPage::handleTransactionClicked);
152
153 // start with displaying the "out of sync" warnings
154 showOutOfSyncWarning(true);
155 connect(ui->labelWalletStatus, &QPushButton::clicked, this, &OverviewPage::outOfSyncWarningClicked);
156 connect(ui->labelTransactionsStatus, &QPushButton::clicked, this, &OverviewPage::outOfSyncWarningClicked);
157 }
158
159 void OverviewPage::handleTransactionClicked(const QModelIndex &index)
160 {
161 if(filter)
162 Q_EMIT transactionClicked(filter->mapToSource(index));
163 }
164
165 void OverviewPage::setPrivacy(bool privacy)
166 {
167 m_privacy = privacy;
168 clientModel->getOptionsModel()->setOption(OptionsModel::OptionID::MaskValues, privacy);
169 const auto& balances = walletModel->getCachedBalance();
170 if (balances.balance != -1) {
171 setBalance(balances);
172 }
173
174 ui->listTransactions->setVisible(!m_privacy);
175
176 const QString status_tip = m_privacy ? tr("Privacy mode activated for the Overview tab. To unmask the values, uncheck Settings->Mask values.") : "";
177 setStatusTip(status_tip);
178 QStatusTipEvent event(status_tip);
179 QApplication::sendEvent(this, &event);
180 }
181
182 OverviewPage::~OverviewPage()
183 {
184 delete ui;
185 }
186
187 void OverviewPage::setBalance(const interfaces::WalletBalances& balances)
188 {
189 BitcoinUnit unit = walletModel->getOptionsModel()->getDisplayUnit();
190 ui->labelBalance->setText(BitcoinUnits::formatWithPrivacy(unit, balances.balance, BitcoinUnits::SeparatorStyle::ALWAYS, m_privacy));
191 ui->labelUnconfirmed->setText(BitcoinUnits::formatWithPrivacy(unit, balances.unconfirmed_balance, BitcoinUnits::SeparatorStyle::ALWAYS, m_privacy));
192 ui->labelImmature->setText(BitcoinUnits::formatWithPrivacy(unit, balances.immature_balance, BitcoinUnits::SeparatorStyle::ALWAYS, m_privacy));
193 ui->labelTotal->setText(BitcoinUnits::formatWithPrivacy(unit, balances.balance + balances.unconfirmed_balance + balances.immature_balance, BitcoinUnits::SeparatorStyle::ALWAYS, m_privacy));
194 // only show immature (newly mined) balance if it's non-zero, so as not to complicate things
195 // for the non-mining users
196 bool showImmature = balances.immature_balance != 0;
197
198 ui->labelImmature->setVisible(showImmature);
199 ui->labelImmatureText->setVisible(showImmature);
200 }
201
202 void OverviewPage::setClientModel(ClientModel *model)
203 {
204 this->clientModel = model;
205 if (model) {
206 // Show warning, for example if this is a prerelease version
207 connect(model, &ClientModel::alertsChanged, this, &OverviewPage::updateAlerts);
208 updateAlerts(model->getStatusBarWarnings());
209
210 connect(model->getOptionsModel(), &OptionsModel::fontForMoneyChanged, this, &OverviewPage::setMonospacedFont);
211 setMonospacedFont(clientModel->getOptionsModel()->getFontForMoney());
212 }
213 }
214
215 void OverviewPage::setWalletModel(WalletModel *model)
216 {
217 this->walletModel = model;
218 if(model && model->getOptionsModel())
219 {
220 // Set up transaction list
221 filter.reset(new TransactionFilterProxy());
222 filter->setSourceModel(model->getTransactionTableModel());
223 filter->setDynamicSortFilter(true);
224 filter->setSortRole(Qt::EditRole);
225 filter->setShowInactive(false);
226 filter->sort(TransactionTableModel::Date, Qt::DescendingOrder);
227
228 ui->listTransactions->setModel(filter.get());
229 ui->listTransactions->setModelColumn(TransactionTableModel::ToAddress);
230
231 connect(filter.get(), &TransactionFilterProxy::rowsInserted, this, &OverviewPage::LimitTransactionRows);
232 connect(filter.get(), &TransactionFilterProxy::rowsRemoved, this, &OverviewPage::LimitTransactionRows);
233 connect(filter.get(), &TransactionFilterProxy::rowsMoved, this, &OverviewPage::LimitTransactionRows);
234 LimitTransactionRows();
235 // Keep up to date with wallet
236 setBalance(model->getCachedBalance());
237 connect(model, &WalletModel::balanceChanged, this, &OverviewPage::setBalance);
238
239 connect(model->getOptionsModel(), &OptionsModel::displayUnitChanged, this, &OverviewPage::updateDisplayUnit);
240 }
241
242 // update the display unit, to not use the default ("BTC")
243 updateDisplayUnit();
244 }
245
246 void OverviewPage::changeEvent(QEvent* e)
247 {
248 if (e->type() == QEvent::PaletteChange) {
249 QIcon icon = m_platform_style->SingleColorIcon(QStringLiteral(":/icons/warning"));
250 ui->labelTransactionsStatus->setIcon(icon);
251 ui->labelWalletStatus->setIcon(icon);
252 }
253
254 QWidget::changeEvent(e);
255 }
256
257 // Only show most recent NUM_ITEMS rows
258 void OverviewPage::LimitTransactionRows()
259 {
260 if (filter && ui->listTransactions && ui->listTransactions->model() && filter.get() == ui->listTransactions->model()) {
261 for (int i = 0; i < filter->rowCount(); ++i) {
262 ui->listTransactions->setRowHidden(i, i >= NUM_ITEMS);
263 }
264 }
265 }
266
267 void OverviewPage::updateDisplayUnit()
268 {
269 if (walletModel && walletModel->getOptionsModel()) {
270 const auto& balances = walletModel->getCachedBalance();
271 if (balances.balance != -1) {
272 setBalance(balances);
273 }
274
275 // Update txdelegate->unit with the current unit
276 txdelegate->unit = walletModel->getOptionsModel()->getDisplayUnit();
277
278 ui->listTransactions->update();
279 }
280 }
281
282 void OverviewPage::updateAlerts(const QString &warnings)
283 {
284 this->ui->labelAlerts->setVisible(!warnings.isEmpty());
285 this->ui->labelAlerts->setText(warnings);
286 }
287
288 void OverviewPage::showOutOfSyncWarning(bool fShow)
289 {
290 ui->labelWalletStatus->setVisible(fShow);
291 ui->labelTransactionsStatus->setVisible(fShow);
292 }
293
294 void OverviewPage::setMonospacedFont(const QFont& f)
295 {
296 ui->labelBalance->setFont(f);
297 ui->labelUnconfirmed->setFont(f);
298 ui->labelImmature->setFont(f);
299 ui->labelTotal->setFont(f);
300 }
301