File indexing completed on 2025-01-05 04:55:50

0001 /*
0002     ui/treeview.cpp
0003 
0004     This file is part of libkleopatra
0005     SPDX-FileCopyrightText: 2022 g10 Code GmbH
0006     SPDX-FileContributor: Ingo Klöcker <dev@ingo-kloecker.de>
0007 
0008     SPDX-License-Identifier: GPL-2.0-or-later
0009 */
0010 
0011 #include <config-libkleo.h>
0012 
0013 #include "treeview.h"
0014 
0015 #include <KConfigGroup>
0016 #include <KLocalizedString>
0017 #include <KSharedConfig>
0018 
0019 #include <QContextMenuEvent>
0020 #include <QHeaderView>
0021 #include <QMenu>
0022 
0023 using namespace Kleo;
0024 
0025 class TreeView::Private
0026 {
0027     TreeView *q;
0028 
0029 public:
0030     QMenu *mHeaderPopup = nullptr;
0031     QList<QAction *> mColumnActions;
0032     QString mStateGroupName;
0033 
0034     Private(TreeView *qq)
0035         : q(qq)
0036     {
0037     }
0038 
0039     ~Private()
0040     {
0041         saveColumnLayout();
0042     }
0043     void saveColumnLayout();
0044 };
0045 
0046 TreeView::TreeView(QWidget *parent)
0047     : QTreeView::QTreeView(parent)
0048     , d{new Private(this)}
0049 {
0050     header()->installEventFilter(this);
0051 }
0052 
0053 TreeView::~TreeView() = default;
0054 
0055 bool TreeView::eventFilter(QObject *watched, QEvent *event)
0056 {
0057     Q_UNUSED(watched)
0058     if (event->type() == QEvent::ContextMenu) {
0059         auto e = static_cast<QContextMenuEvent *>(event);
0060 
0061         if (!d->mHeaderPopup) {
0062             d->mHeaderPopup = new QMenu(this);
0063             d->mHeaderPopup->setTitle(i18nc("@title:menu", "View Columns"));
0064             for (int i = 0; i < model()->columnCount(); ++i) {
0065                 QAction *tmp = d->mHeaderPopup->addAction(model()->headerData(i, Qt::Horizontal).toString());
0066                 tmp->setData(QVariant(i));
0067                 tmp->setCheckable(true);
0068                 d->mColumnActions << tmp;
0069             }
0070 
0071             connect(d->mHeaderPopup, &QMenu::triggered, this, [this](QAction *action) {
0072                 const int col = action->data().toInt();
0073                 if (action->isChecked()) {
0074                     showColumn(col);
0075                 } else {
0076                     hideColumn(col);
0077                 }
0078 
0079                 if (action->isChecked()) {
0080                     Q_EMIT columnEnabled(col);
0081                 } else {
0082                     Q_EMIT columnDisabled(col);
0083                 }
0084             });
0085         }
0086 
0087         for (QAction *action : std::as_const(d->mColumnActions)) {
0088             const int column = action->data().toInt();
0089             action->setChecked(!isColumnHidden(column));
0090         }
0091 
0092         d->mHeaderPopup->popup(mapToGlobal(e->pos()));
0093         return true;
0094     }
0095 
0096     return false;
0097 }
0098 
0099 void TreeView::Private::saveColumnLayout()
0100 {
0101     if (mStateGroupName.isEmpty()) {
0102         return;
0103     }
0104     auto config = KConfigGroup(KSharedConfig::openStateConfig(), mStateGroupName);
0105     auto header = q->header();
0106 
0107     QVariantList columnVisibility;
0108     QVariantList columnOrder;
0109     QVariantList columnWidths;
0110     const int headerCount = header->count();
0111     columnVisibility.reserve(headerCount);
0112     columnWidths.reserve(headerCount);
0113     columnOrder.reserve(headerCount);
0114     for (int i = 0; i < headerCount; ++i) {
0115         columnVisibility << QVariant(!q->isColumnHidden(i));
0116         columnWidths << QVariant(header->sectionSize(i));
0117         columnOrder << QVariant(header->visualIndex(i));
0118     }
0119 
0120     config.writeEntry("ColumnVisibility", columnVisibility);
0121     config.writeEntry("ColumnOrder", columnOrder);
0122     config.writeEntry("ColumnWidths", columnWidths);
0123 
0124     config.writeEntry("SortAscending", (int)header->sortIndicatorOrder());
0125     if (header->isSortIndicatorShown()) {
0126         config.writeEntry("SortColumn", header->sortIndicatorSection());
0127     } else {
0128         config.writeEntry("SortColumn", -1);
0129     }
0130 }
0131 
0132 bool TreeView::restoreColumnLayout(const QString &stateGroupName)
0133 {
0134     if (stateGroupName.isEmpty()) {
0135         return false;
0136     }
0137     d->mStateGroupName = stateGroupName;
0138     auto config = KConfigGroup(KSharedConfig::openStateConfig(), d->mStateGroupName);
0139     auto header = this->header();
0140 
0141     QVariantList columnVisibility = config.readEntry("ColumnVisibility", QVariantList());
0142     QVariantList columnOrder = config.readEntry("ColumnOrder", QVariantList());
0143     QVariantList columnWidths = config.readEntry("ColumnWidths", QVariantList());
0144 
0145     if (!columnVisibility.isEmpty() && !columnOrder.isEmpty() && !columnWidths.isEmpty()) {
0146         for (int i = 0; i < header->count(); ++i) {
0147             if (i >= columnOrder.size() || i >= columnWidths.size() || i >= columnVisibility.size()) {
0148                 // An additional column that was not around last time we saved.
0149                 // We default to hidden.
0150                 hideColumn(i);
0151                 continue;
0152             }
0153             bool visible = columnVisibility[i].toBool();
0154             int width = columnWidths[i].toInt();
0155             int order = columnOrder[i].toInt();
0156 
0157             header->resizeSection(i, width ? width : 100);
0158             header->moveSection(header->visualIndex(i), order);
0159 
0160             if (!visible) {
0161                 hideColumn(i);
0162             }
0163         }
0164     }
0165 
0166     int sortOrder = config.readEntry("SortAscending", (int)Qt::AscendingOrder);
0167     int sortColumn = config.readEntry("SortColumn", 0);
0168     if (sortColumn >= 0) {
0169         sortByColumn(sortColumn, (Qt::SortOrder)sortOrder);
0170     }
0171     return !columnVisibility.isEmpty() && !columnOrder.isEmpty() && !columnWidths.isEmpty();
0172 }
0173 
0174 QModelIndex TreeView::moveCursor(QAbstractItemView::CursorAction cursorAction, Qt::KeyboardModifiers modifiers)
0175 {
0176     // make column by column keyboard navigation with Left/Right possible by switching
0177     // the selection behavior to SelectItems before calling the parent class's moveCursor,
0178     // because it ignores MoveLeft/MoveRight if the selection behavior is SelectRows;
0179     // moreover, temporarily disable exanding of items to prevent expanding/collapsing
0180     // on MoveLeft/MoveRight
0181     if ((cursorAction != MoveLeft) && (cursorAction != MoveRight)) {
0182         return QTreeView::moveCursor(cursorAction, modifiers);
0183     }
0184 
0185     const auto savedSelectionBehavior = selectionBehavior();
0186     setSelectionBehavior(SelectItems);
0187     const auto savedItemsExpandable = itemsExpandable();
0188     setItemsExpandable(false);
0189 
0190     const auto result = QTreeView::moveCursor(cursorAction, modifiers);
0191 
0192     setItemsExpandable(savedItemsExpandable);
0193     setSelectionBehavior(savedSelectionBehavior);
0194 
0195     return result;
0196 }
0197 
0198 #include "moc_treeview.cpp"