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