// SPDX-License-Identifier: BSD-3-Clause // SPDX-FileCopyrightText: 2020-2024 The Monero Project #include "HistoryView.h" #include "TransactionHistoryProxyModel.h" #include "utils/Utils.h" #include "utils/config.h" #include #include HistoryView::HistoryView(QWidget *parent) : QTreeView(parent) , m_model(nullptr) , m_headerMenu(new QMenu(this)) { setUniformRowHeights(true); setRootIsDecorated(false); setAlternatingRowColors(true); setDragEnabled(true); setSortingEnabled(true); header()->setStretchLastSection(false); header()->setContextMenuPolicy(Qt::CustomContextMenu); connect(header(), SIGNAL(customContextMenuRequested(QPoint)), SLOT(showHeaderMenu(QPoint))); } void HistoryView::setHistoryModel(TransactionHistoryProxyModel *model) { m_model = model; m_model->setSortCaseSensitivity(Qt::CaseInsensitive); m_model->setSortRole(Qt::UserRole); m_model->setDynamicSortFilter(true); m_model->setSortLocaleAware(true); QTreeView::setModel(m_model); resetViewToDefaults(); m_headerMenu->clear(); // Actions to toggle column visibility, each carrying the corresponding // column index as data m_columnActions = new QActionGroup(this); m_columnActions->setExclusive(false); for (int visualIndex = 1; visualIndex < header()->count(); ++visualIndex) { int logicalIndex = header()->logicalIndex(visualIndex); QString caption = m_model->headerData(logicalIndex, Qt::Horizontal, Qt::DisplayRole).toString(); if (caption.isEmpty()) { caption = m_model->headerData(logicalIndex, Qt::Horizontal, Qt::ToolTipRole).toString(); } auto action = m_headerMenu->addAction(caption); action->setCheckable(true); action->setData(logicalIndex); m_columnActions->addAction(action); } connect(m_columnActions, &QActionGroup::triggered, this, &HistoryView::toggleColumnVisibility); m_headerMenu->addSeparator(); auto action = m_headerMenu->addAction("Show full txid", this, &HistoryView::showFullTxid); action->setCheckable(true); action->setChecked(conf()->get(Config::historyShowFullTxid).toBool()); m_headerMenu->addSeparator(); m_headerMenu->addAction(tr("Fit to window"), this, &HistoryView::fitColumnsToWindow); m_headerMenu->addAction(tr("Fit to contents"), this, &HistoryView::fitColumnsToContents); m_headerMenu->addSeparator(); m_headerMenu->addAction(tr("Reset to defaults"), this, &HistoryView::resetViewToDefaults); fitColumnsToWindow(); } TransactionHistoryModel* HistoryView::sourceModel() { return dynamic_cast(m_model->sourceModel()); } TransactionRow* HistoryView::currentEntry() { QModelIndexList list = selectionModel()->selectedRows(); if (list.size() == 1) { return this->sourceModel()->entryFromIndex(m_model->mapToSource(list.first())); } else { return nullptr; } } void HistoryView::setSearchMode(bool mode) { if (!m_inSearchMode) { m_showTxidColumn = !header()->isSectionHidden(TransactionHistoryModel::TxID); } m_inSearchMode = mode; if (mode) { header()->showSection(TransactionHistoryModel::TxID); } else if (!m_showTxidColumn) { header()->hideSection(TransactionHistoryModel::TxID); } } QByteArray HistoryView::viewState() const { return header()->saveState(); } bool HistoryView::setViewState(const QByteArray& state) { // Reset to unsorted first (https://bugreports.qt.io/browse/QTBUG-86694) header()->setSortIndicator(-1, Qt::AscendingOrder); bool status = header()->restoreState(state); m_columnsNeedRelayout = state.isEmpty(); m_showTxidColumn = !header()->isSectionHidden(TransactionHistoryModel::TxID); return status; } void HistoryView::showHeaderMenu(const QPoint& position) { const QList actions = m_columnActions->actions(); for (auto& action : actions) { Q_ASSERT(static_cast(action->data().type()) == QMetaType::Int); if (static_cast(action->data().type()) != QMetaType::Int) { continue; } int columnIndex = action->data().toInt(); action->setChecked(!isColumnHidden(columnIndex)); } m_headerMenu->popup(mapToGlobal(position)); } void HistoryView::toggleColumnVisibility(QAction* action) { // Verify action carries a column index as data. Since QVariant.toInt() // below will accept anything that's interpretable as int, perform a type // check here to make sure data actually IS int Q_ASSERT(static_cast(action->data().type()) == QMetaType::Int); if (static_cast(action->data().type()) != QMetaType::Int) { return; } // Toggle column visibility. Visible columns will only be hidden if at // least one visible column remains, as the table header will disappear // entirely when all columns are hidden int columnIndex = action->data().toInt(); if (action->isChecked()) { header()->showSection(columnIndex); if (header()->sectionSize(columnIndex) == 0) { header()->resizeSection(columnIndex, header()->defaultSectionSize()); } return; } if ((header()->count() - header()->hiddenSectionCount()) > 1) { header()->hideSection(columnIndex); return; } action->setChecked(true); } void HistoryView::showFullTxid(bool enabled) { conf()->set(Config::historyShowFullTxid, enabled); this->reset(); if (!enabled) { this->resizeColumnToContents(TransactionHistoryModel::TxID); } } void HistoryView::fitColumnsToWindow() { header()->setSectionResizeMode(QHeaderView::ResizeToContents); header()->setSectionResizeMode(TransactionHistoryModel::Description, QHeaderView::Stretch); header()->setStretchLastSection(false); QCoreApplication::processEvents(); } void HistoryView::fitColumnsToContents() { header()->setSectionResizeMode(QHeaderView::ResizeToContents); QCoreApplication::processEvents(); header()->setSectionResizeMode(QHeaderView::Interactive); } void HistoryView::resetViewToDefaults() { if (m_inSearchMode) { header()->showSection(TransactionHistoryModel::TxID); } else { header()->hideSection(TransactionHistoryModel::TxID); } header()->showSection(TransactionHistoryModel::Date); header()->showSection(TransactionHistoryModel::Description); header()->showSection(TransactionHistoryModel::Amount); header()->showSection(TransactionHistoryModel::FiatAmount); // Reset column order to logical indices for (int i = 0; i < header()->count(); ++i) { header()->moveSection(header()->visualIndex(i), i); } m_model->sort(TransactionHistoryModel::Date, Qt::DescendingOrder); sortByColumn(TransactionHistoryModel::Date, Qt::DescendingOrder); // The following call only relayouts reliably if the widget has been shown // already, so only do it if the widget is visible and let showEvent() handle // the initial default layout. if (isVisible()) { fitColumnsToWindow(); } } void HistoryView::keyPressEvent(QKeyEvent *event) { TransactionRow* tx = this->currentEntry(); if (event->matches(QKeySequence::Copy) && tx) { Utils::copyToClipboard(tx->hash()); } else { QTreeView::keyPressEvent(event); } }