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