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

0001 /*
0002     ui/treewidget.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 "treewidget.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 TreeWidget::Private
0026 {
0027     TreeWidget *q;
0028 
0029 public:
0030     QMenu *mHeaderPopup = nullptr;
0031     QList<QAction *> mColumnActions;
0032     QString mStateGroupName;
0033 
0034     Private(TreeWidget *qq)
0035         : q(qq)
0036     {
0037     }
0038 
0039     ~Private()
0040     {
0041         saveColumnLayout();
0042     }
0043     void saveColumnLayout();
0044 };
0045 
0046 TreeWidget::TreeWidget(QWidget *parent)
0047     : QTreeWidget::QTreeWidget(parent)
0048     , d{new Private(this)}
0049 {
0050     header()->installEventFilter(this);
0051 }
0052 
0053 TreeWidget::~TreeWidget() = default;
0054 
0055 void TreeWidget::Private::saveColumnLayout()
0056 {
0057     if (mStateGroupName.isEmpty()) {
0058         return;
0059     }
0060     auto config = KConfigGroup(KSharedConfig::openStateConfig(), mStateGroupName);
0061     auto header = q->header();
0062 
0063     QVariantList columnVisibility;
0064     QVariantList columnOrder;
0065     QVariantList columnWidths;
0066     const int headerCount = header->count();
0067     columnVisibility.reserve(headerCount);
0068     columnWidths.reserve(headerCount);
0069     columnOrder.reserve(headerCount);
0070     for (int i = 0; i < headerCount; ++i) {
0071         columnVisibility << QVariant(!q->isColumnHidden(i));
0072         columnWidths << QVariant(header->sectionSize(i));
0073         columnOrder << QVariant(header->visualIndex(i));
0074     }
0075 
0076     config.writeEntry("ColumnVisibility", columnVisibility);
0077     config.writeEntry("ColumnOrder", columnOrder);
0078     config.writeEntry("ColumnWidths", columnWidths);
0079 
0080     config.writeEntry("SortAscending", (int)header->sortIndicatorOrder());
0081     if (header->isSortIndicatorShown()) {
0082         config.writeEntry("SortColumn", header->sortIndicatorSection());
0083     } else {
0084         config.writeEntry("SortColumn", -1);
0085     }
0086 }
0087 
0088 bool TreeWidget::restoreColumnLayout(const QString &stateGroupName)
0089 {
0090     if (stateGroupName.isEmpty()) {
0091         return false;
0092     }
0093     d->mStateGroupName = stateGroupName;
0094     auto config = KConfigGroup(KSharedConfig::openStateConfig(), d->mStateGroupName);
0095     auto header = this->header();
0096 
0097     QVariantList columnVisibility = config.readEntry("ColumnVisibility", QVariantList());
0098     QVariantList columnOrder = config.readEntry("ColumnOrder", QVariantList());
0099     QVariantList columnWidths = config.readEntry("ColumnWidths", QVariantList());
0100 
0101     if (!columnVisibility.isEmpty() && !columnOrder.isEmpty() && !columnWidths.isEmpty()) {
0102         for (int i = 0; i < header->count(); ++i) {
0103             if (i >= columnOrder.size() || i >= columnWidths.size() || i >= columnVisibility.size()) {
0104                 // An additional column that was not around last time we saved.
0105                 // We default to hidden.
0106                 hideColumn(i);
0107                 continue;
0108             }
0109             bool visible = columnVisibility[i].toBool();
0110             int width = columnWidths[i].toInt();
0111             int order = columnOrder[i].toInt();
0112 
0113             header->resizeSection(i, width ? width : 100);
0114             header->moveSection(header->visualIndex(i), order);
0115 
0116             if (!visible) {
0117                 hideColumn(i);
0118             }
0119         }
0120     }
0121 
0122     int sortOrder = config.readEntry("SortAscending", (int)Qt::AscendingOrder);
0123     int sortColumn = config.readEntry("SortColumn", 0);
0124     if (sortColumn >= 0) {
0125         sortByColumn(sortColumn, (Qt::SortOrder)sortOrder);
0126     }
0127     return !columnVisibility.isEmpty() && !columnOrder.isEmpty() && !columnWidths.isEmpty();
0128 }
0129 
0130 bool TreeWidget::eventFilter(QObject *watched, QEvent *event)
0131 {
0132     Q_UNUSED(watched)
0133     if (event->type() == QEvent::ContextMenu) {
0134         auto e = static_cast<QContextMenuEvent *>(event);
0135 
0136         if (!d->mHeaderPopup) {
0137             d->mHeaderPopup = new QMenu(this);
0138             d->mHeaderPopup->setTitle(i18nc("@title:menu", "View Columns"));
0139             for (int i = 0; i < model()->columnCount(); ++i) {
0140                 QAction *tmp = d->mHeaderPopup->addAction(model()->headerData(i, Qt::Horizontal).toString());
0141                 tmp->setData(QVariant(i));
0142                 tmp->setCheckable(true);
0143                 d->mColumnActions << tmp;
0144             }
0145 
0146             connect(d->mHeaderPopup, &QMenu::triggered, this, [this](QAction *action) {
0147                 const int col = action->data().toInt();
0148                 if (action->isChecked()) {
0149                     showColumn(col);
0150                 } else {
0151                     hideColumn(col);
0152                 }
0153 
0154                 if (action->isChecked()) {
0155                     Q_EMIT columnEnabled(col);
0156                 } else {
0157                     Q_EMIT columnDisabled(col);
0158                 }
0159             });
0160         }
0161 
0162         for (QAction *action : std::as_const(d->mColumnActions)) {
0163             const int column = action->data().toInt();
0164             action->setChecked(!isColumnHidden(column));
0165         }
0166 
0167         d->mHeaderPopup->popup(mapToGlobal(e->pos()));
0168         return true;
0169     }
0170 
0171     return false;
0172 }
0173 
0174 QModelIndex TreeWidget::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 QTreeWidget::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 = QTreeWidget::moveCursor(cursorAction, modifiers);
0191 
0192     setItemsExpandable(savedItemsExpandable);
0193     setSelectionBehavior(savedSelectionBehavior);
0194 
0195     return result;
0196 }
0197 
0198 #include "moc_treewidget.cpp"