overviewpage.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/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 }