File indexing completed on 2024-06-16 04:56:17

0001 /* -*- mode: c++; c-basic-offset:4 -*-
0002     view/tabwidget.cpp
0003 
0004     This file is part of Kleopatra, the KDE keymanager
0005     SPDX-FileCopyrightText: 2007 Klarälvdalens Datakonsult AB
0006 
0007     SPDX-License-Identifier: GPL-2.0-or-later
0008 */
0009 
0010 #include <config-kleopatra.h>
0011 
0012 #include "tabwidget.h"
0013 
0014 #include "keytreeview.h"
0015 #include "kleopatra_debug.h"
0016 #include "searchbar.h"
0017 
0018 #include <settings.h>
0019 
0020 #include <utils/action_data.h>
0021 
0022 #include <Libkleo/KeyFilter>
0023 #include <Libkleo/KeyFilterManager>
0024 #include <Libkleo/KeyListModel>
0025 #include <Libkleo/KeyListSortFilterProxyModel>
0026 #include <Libkleo/Stl_Util>
0027 
0028 #include <gpgme++/key.h>
0029 // needed for GPGME_VERSION_NUMBER
0030 #include <gpgme.h>
0031 
0032 #include <KActionCollection>
0033 #include <KConfig>
0034 #include <KConfigGroup>
0035 #include <KLocalizedString>
0036 #include <KSharedConfig>
0037 #include <QAction>
0038 #include <QInputDialog>
0039 #include <QTabWidget>
0040 
0041 #include <QAbstractProxyModel>
0042 #include <QMenu>
0043 #include <QRegularExpression>
0044 #include <QToolButton>
0045 #include <QTreeView>
0046 #include <QVBoxLayout>
0047 
0048 #include <map>
0049 
0050 using namespace Kleo;
0051 using namespace GpgME;
0052 
0053 namespace
0054 {
0055 
0056 class Page : public Kleo::KeyTreeView
0057 {
0058     Q_OBJECT
0059     Page(const Page &other);
0060 
0061 public:
0062     Page(const QString &title,
0063          const QString &id,
0064          const QString &text,
0065          AbstractKeyListSortFilterProxyModel *proxy = nullptr,
0066          const QString &toolTip = QString(),
0067          QWidget *parent = nullptr,
0068          const KConfigGroup &group = KConfigGroup());
0069     Page(const KConfigGroup &group, QWidget *parent = nullptr);
0070     ~Page() override;
0071 
0072     void setTemporary(bool temporary);
0073     bool isTemporary() const
0074     {
0075         return m_isTemporary;
0076     }
0077 
0078     void setHierarchicalView(bool hierarchical) override;
0079     void setStringFilter(const QString &filter) override;
0080     void setKeyFilter(const std::shared_ptr<KeyFilter> &filter) override;
0081 
0082     QString title() const
0083     {
0084         return m_title.isEmpty() && keyFilter() ? keyFilter()->name() : m_title;
0085     }
0086     void setTitle(const QString &title);
0087 
0088     QString toolTip() const
0089     {
0090         return m_toolTip.isEmpty() ? title() : m_toolTip;
0091     }
0092     // not used void setToolTip(const QString &tip);
0093 
0094     bool canBeClosed() const
0095     {
0096         return m_canBeClosed;
0097     }
0098     bool canBeRenamed() const
0099     {
0100         return m_canBeRenamed;
0101     }
0102     bool canChangeStringFilter() const
0103     {
0104         return m_canChangeStringFilter;
0105     }
0106     bool canChangeKeyFilter() const
0107     {
0108         return m_canChangeKeyFilter && !m_isTemporary;
0109     }
0110     bool canChangeHierarchical() const
0111     {
0112         return m_canChangeHierarchical;
0113     }
0114 
0115     void saveTo(KConfigGroup &group) const;
0116 
0117     Page *clone() const override
0118     {
0119         return new Page(*this);
0120     }
0121 
0122     void liftAllRestrictions()
0123     {
0124         m_canBeClosed = m_canBeRenamed = m_canChangeStringFilter = m_canChangeKeyFilter = m_canChangeHierarchical = true;
0125     }
0126 
0127 Q_SIGNALS:
0128     void titleChanged(const QString &title);
0129 
0130 private:
0131     void init();
0132 
0133 private:
0134     QString m_title;
0135     QString m_toolTip;
0136     bool m_isTemporary : 1;
0137     bool m_canBeClosed : 1;
0138     bool m_canBeRenamed : 1;
0139     bool m_canChangeStringFilter : 1;
0140     bool m_canChangeKeyFilter : 1;
0141     bool m_canChangeHierarchical : 1;
0142 };
0143 } // anon namespace
0144 
0145 Page::Page(const Page &other)
0146     : KeyTreeView(other)
0147     , m_title(other.m_title)
0148     , m_toolTip(other.m_toolTip)
0149     , m_isTemporary(other.m_isTemporary)
0150     , m_canBeClosed(other.m_canBeClosed)
0151     , m_canBeRenamed(other.m_canBeRenamed)
0152     , m_canChangeStringFilter(other.m_canChangeStringFilter)
0153     , m_canChangeKeyFilter(other.m_canChangeKeyFilter)
0154     , m_canChangeHierarchical(other.m_canChangeHierarchical)
0155 {
0156     init();
0157 }
0158 
0159 Page::Page(const QString &title,
0160            const QString &id,
0161            const QString &text,
0162            AbstractKeyListSortFilterProxyModel *proxy,
0163            const QString &toolTip,
0164            QWidget *parent,
0165            const KConfigGroup &group)
0166     : KeyTreeView(text, KeyFilterManager::instance()->keyFilterByID(id), proxy, parent, group)
0167     , m_title(title)
0168     , m_toolTip(toolTip)
0169     , m_isTemporary(false)
0170     , m_canBeClosed(true)
0171     , m_canBeRenamed(true)
0172     , m_canChangeStringFilter(true)
0173     , m_canChangeKeyFilter(true)
0174     , m_canChangeHierarchical(true)
0175 {
0176     init();
0177 }
0178 
0179 static const char TITLE_ENTRY[] = "title";
0180 static const char STRING_FILTER_ENTRY[] = "string-filter";
0181 static const char KEY_FILTER_ENTRY[] = "key-filter";
0182 static const char HIERARCHICAL_VIEW_ENTRY[] = "hierarchical-view";
0183 static const char COLUMN_SIZES[] = "column-sizes";
0184 static const char SORT_COLUMN[] = "sort-column";
0185 static const char SORT_DESCENDING[] = "sort-descending";
0186 
0187 Page::Page(const KConfigGroup &group, QWidget *parent)
0188     : KeyTreeView(group.readEntry(STRING_FILTER_ENTRY), KeyFilterManager::instance()->keyFilterByID(group.readEntry(KEY_FILTER_ENTRY)), nullptr, parent, group)
0189     , m_title(group.readEntry(TITLE_ENTRY))
0190     , m_toolTip()
0191     , m_isTemporary(false)
0192     , m_canBeClosed(!group.isImmutable())
0193     , m_canBeRenamed(!group.isEntryImmutable(TITLE_ENTRY))
0194     , m_canChangeStringFilter(!group.isEntryImmutable(STRING_FILTER_ENTRY))
0195     , m_canChangeKeyFilter(!group.isEntryImmutable(KEY_FILTER_ENTRY))
0196     , m_canChangeHierarchical(!group.isEntryImmutable(HIERARCHICAL_VIEW_ENTRY))
0197 {
0198     init();
0199     setHierarchicalView(group.readEntry(HIERARCHICAL_VIEW_ENTRY, true));
0200     const QList<int> settings = group.readEntry(COLUMN_SIZES, QList<int>());
0201     std::vector<int> sizes;
0202     sizes.reserve(settings.size());
0203     std::copy(settings.cbegin(), settings.cend(), std::back_inserter(sizes));
0204     setColumnSizes(sizes);
0205     setSortColumn(group.readEntry(SORT_COLUMN, 0), group.readEntry(SORT_DESCENDING, true) ? Qt::DescendingOrder : Qt::AscendingOrder);
0206 }
0207 
0208 void Page::init()
0209 {
0210 #if GPGME_VERSION_NUMBER >= 0x011800 // 1.24.0
0211     view()->setDragDropMode(QAbstractItemView::DragOnly);
0212     view()->setDragEnabled(true);
0213 #endif
0214 }
0215 
0216 Page::~Page()
0217 {
0218 }
0219 
0220 void Page::saveTo(KConfigGroup &group) const
0221 {
0222     group.writeEntry(TITLE_ENTRY, m_title);
0223     group.writeEntry(STRING_FILTER_ENTRY, stringFilter());
0224     group.writeEntry(KEY_FILTER_ENTRY, keyFilter() ? keyFilter()->id() : QString());
0225     group.writeEntry(HIERARCHICAL_VIEW_ENTRY, isHierarchicalView());
0226     QList<int> settings;
0227     const auto sizes = columnSizes();
0228     settings.reserve(sizes.size());
0229     std::copy(sizes.cbegin(), sizes.cend(), std::back_inserter(settings));
0230     group.writeEntry(COLUMN_SIZES, settings);
0231     group.writeEntry(SORT_COLUMN, sortColumn());
0232     group.writeEntry(SORT_DESCENDING, sortOrder() == Qt::DescendingOrder);
0233 }
0234 
0235 void Page::setStringFilter(const QString &filter)
0236 {
0237     if (!m_canChangeStringFilter) {
0238         return;
0239     }
0240     KeyTreeView::setStringFilter(filter);
0241 }
0242 
0243 void Page::setKeyFilter(const std::shared_ptr<KeyFilter> &filter)
0244 {
0245     if (!canChangeKeyFilter()) {
0246         return;
0247     }
0248     const QString oldTitle = title();
0249     KeyTreeView::setKeyFilter(filter);
0250     const QString newTitle = title();
0251     if (oldTitle != newTitle) {
0252         Q_EMIT titleChanged(newTitle);
0253     }
0254 }
0255 
0256 void Page::setTitle(const QString &t)
0257 {
0258     if (t == m_title) {
0259         return;
0260     }
0261     if (!m_canBeRenamed) {
0262         return;
0263     }
0264     const QString oldTitle = title();
0265     m_title = t;
0266     const QString newTitle = title();
0267     if (oldTitle != newTitle) {
0268         Q_EMIT titleChanged(newTitle);
0269     }
0270 }
0271 
0272 #if 0 // not used
0273 void Page::setToolTip(const QString &tip)
0274 {
0275     if (tip == m_toolTip) {
0276         return;
0277     }
0278     if (!m_canBeRenamed) {
0279         return;
0280     }
0281     const QString oldTip = toolTip();
0282     m_toolTip = tip;
0283     const QString newTip = toolTip();
0284     if (oldTip != newTip) {
0285         Q_EMIT titleChanged(title());
0286     }
0287 }
0288 #endif
0289 
0290 void Page::setHierarchicalView(bool on)
0291 {
0292     if (!m_canChangeHierarchical) {
0293         return;
0294     }
0295     KeyTreeView::setHierarchicalView(on);
0296 }
0297 
0298 void Page::setTemporary(bool on)
0299 {
0300     if (on == m_isTemporary) {
0301         return;
0302     }
0303     m_isTemporary = on;
0304     if (on) {
0305         setKeyFilter(std::shared_ptr<KeyFilter>());
0306     }
0307 }
0308 
0309 namespace
0310 {
0311 class Actions
0312 {
0313 public:
0314     constexpr static const char *Rename = "window_rename_tab";
0315     constexpr static const char *Duplicate = "window_duplicate_tab";
0316     constexpr static const char *Close = "window_close_tab";
0317     constexpr static const char *MoveLeft = "window_move_tab_left";
0318     constexpr static const char *MoveRight = "window_move_tab_right";
0319     constexpr static const char *Hierarchical = "window_view_hierarchical";
0320     constexpr static const char *ExpandAll = "window_expand_all";
0321     constexpr static const char *CollapseAll = "window_collapse_all";
0322 
0323     explicit Actions()
0324     {
0325     }
0326 
0327     void insert(const std::string &name, QAction *action)
0328     {
0329         actions.insert({name, action});
0330     }
0331 
0332     auto get(const std::string &name) const
0333     {
0334         const auto it = actions.find(name);
0335         return (it != actions.end()) ? it->second : nullptr;
0336     }
0337 
0338     void setChecked(const std::string &name, bool checked) const
0339     {
0340         if (auto action = get(name)) {
0341             action->setChecked(checked);
0342         }
0343     }
0344 
0345     void setEnabled(const std::string &name, bool enabled) const
0346     {
0347         if (auto action = get(name)) {
0348             action->setEnabled(enabled);
0349         }
0350     }
0351 
0352     void setVisible(const std::string &name, bool visible) const
0353     {
0354         if (auto action = get(name)) {
0355             action->setVisible(visible);
0356         }
0357     }
0358 
0359 private:
0360     std::map<std::string, QAction *> actions;
0361 };
0362 }
0363 
0364 //
0365 //
0366 // TabWidget
0367 //
0368 //
0369 
0370 class TabWidget::Private
0371 {
0372     friend class ::Kleo::TabWidget;
0373     TabWidget *const q;
0374 
0375 public:
0376     explicit Private(TabWidget *qq);
0377     ~Private()
0378     {
0379     }
0380 
0381 private:
0382     void slotContextMenu(const QPoint &p);
0383     void currentIndexChanged(int index);
0384     void slotPageTitleChanged(const QString &title);
0385     void slotPageKeyFilterChanged(const std::shared_ptr<KeyFilter> &filter);
0386     void slotPageStringFilterChanged(const QString &filter);
0387     void slotPageHierarchyChanged(bool on);
0388 
0389 #ifndef QT_NO_INPUTDIALOG
0390     void slotRenameCurrentTab()
0391     {
0392         renamePage(currentPage());
0393     }
0394 #endif // QT_NO_INPUTDIALOG
0395     void slotNewTab();
0396     void slotDuplicateCurrentTab()
0397     {
0398         duplicatePage(currentPage());
0399     }
0400     void slotCloseCurrentTab()
0401     {
0402         closePage(currentPage());
0403     }
0404     void slotMoveCurrentTabLeft()
0405     {
0406         movePageLeft(currentPage());
0407     }
0408     void slotMoveCurrentTabRight()
0409     {
0410         movePageRight(currentPage());
0411     }
0412     void slotToggleHierarchicalView(bool on)
0413     {
0414         toggleHierarchicalView(currentPage(), on);
0415     }
0416     void slotExpandAll()
0417     {
0418         expandAll(currentPage());
0419     }
0420     void slotCollapseAll()
0421     {
0422         collapseAll(currentPage());
0423     }
0424 
0425 #ifndef QT_NO_INPUTDIALOG
0426     void renamePage(Page *page);
0427 #endif
0428     void duplicatePage(Page *page);
0429     void closePage(Page *page);
0430     void movePageLeft(Page *page);
0431     void movePageRight(Page *page);
0432     void toggleHierarchicalView(Page *page, bool on);
0433     void expandAll(Page *page);
0434     void collapseAll(Page *page);
0435 
0436     void enableDisableCurrentPageActions();
0437     void enableDisablePageActions(const Actions &actions, const Page *page);
0438 
0439     Page *currentPage() const
0440     {
0441         Q_ASSERT(!tabWidget->currentWidget() || qobject_cast<Page *>(tabWidget->currentWidget()));
0442         return static_cast<Page *>(tabWidget->currentWidget());
0443     }
0444     Page *page(unsigned int idx) const
0445     {
0446         Q_ASSERT(!tabWidget->widget(idx) || qobject_cast<Page *>(tabWidget->widget(idx)));
0447         return static_cast<Page *>(tabWidget->widget(idx));
0448     }
0449 
0450     Page *senderPage() const
0451     {
0452         QObject *const sender = q->sender();
0453         Q_ASSERT(!sender || qobject_cast<Page *>(sender));
0454         return static_cast<Page *>(sender);
0455     }
0456 
0457     bool isSenderCurrentPage() const
0458     {
0459         Page *const sp = senderPage();
0460         return sp && sp == currentPage();
0461     }
0462 
0463     QTreeView *addView(Page *page, Page *columnReference);
0464 
0465 private:
0466     AbstractKeyListModel *flatModel = nullptr;
0467     AbstractKeyListModel *hierarchicalModel = nullptr;
0468     QToolButton *newTabButton = nullptr;
0469     QToolButton *closeTabButton = nullptr;
0470     QTabWidget *tabWidget = nullptr;
0471     QAction *newAction = nullptr;
0472     Actions currentPageActions;
0473     Actions otherPageActions;
0474     bool actionsCreated = false;
0475 };
0476 
0477 TabWidget::Private::Private(TabWidget *qq)
0478     : q{qq}
0479 {
0480     auto layout = new QVBoxLayout{q};
0481     layout->setContentsMargins(0, 0, 0, 0);
0482 
0483     // create "New Tab" button before tab widget to ensure correct tab order
0484     newTabButton = new QToolButton{q};
0485 
0486     tabWidget = new QTabWidget{q};
0487     KDAB_SET_OBJECT_NAME(tabWidget);
0488 
0489     layout->addWidget(tabWidget);
0490 
0491     tabWidget->setMovable(true);
0492 
0493     tabWidget->tabBar()->setContextMenuPolicy(Qt::CustomContextMenu);
0494 
0495     // create "Close Tab" button after tab widget to ensure correct tab order
0496     closeTabButton = new QToolButton{q};
0497 
0498     connect(tabWidget, &QTabWidget::currentChanged, q, [this](int index) {
0499         currentIndexChanged(index);
0500     });
0501     connect(tabWidget->tabBar(), &QWidget::customContextMenuRequested, q, [this](const QPoint &p) {
0502         slotContextMenu(p);
0503     });
0504 }
0505 
0506 void TabWidget::Private::slotContextMenu(const QPoint &p)
0507 {
0508     const int tabUnderPos = tabWidget->tabBar()->tabAt(p);
0509     Page *const contextMenuPage = static_cast<Page *>(tabWidget->widget(tabUnderPos));
0510     const Page *const current = currentPage();
0511 
0512     const auto actions = contextMenuPage == current ? currentPageActions : otherPageActions;
0513 
0514     enableDisablePageActions(actions, contextMenuPage);
0515 
0516     QMenu menu;
0517     if (auto action = actions.get(Actions::Rename)) {
0518         menu.addAction(action);
0519     }
0520     menu.addSeparator();
0521     menu.addAction(newAction);
0522     if (auto action = actions.get(Actions::Duplicate)) {
0523         menu.addAction(action);
0524     }
0525     menu.addSeparator();
0526     if (auto action = actions.get(Actions::MoveLeft)) {
0527         menu.addAction(action);
0528     }
0529     if (auto action = actions.get(Actions::MoveRight)) {
0530         menu.addAction(action);
0531     }
0532     menu.addSeparator();
0533     if (auto action = actions.get(Actions::Close)) {
0534         menu.addAction(action);
0535     }
0536 
0537     const QAction *const action = menu.exec(tabWidget->tabBar()->mapToGlobal(p));
0538     if (!action) {
0539         return;
0540     }
0541 
0542     if (contextMenuPage == current || action == newAction) {
0543         return; // performed through signal/slot connections...
0544     }
0545 
0546 #ifndef QT_NO_INPUTDIALOG
0547     if (action == otherPageActions.get(Actions::Rename)) {
0548         renamePage(contextMenuPage);
0549     }
0550 #endif // QT_NO_INPUTDIALOG
0551     else if (action == otherPageActions.get(Actions::Duplicate)) {
0552         duplicatePage(contextMenuPage);
0553     } else if (action == otherPageActions.get(Actions::Close)) {
0554         closePage(contextMenuPage);
0555     } else if (action == otherPageActions.get(Actions::MoveLeft)) {
0556         movePageLeft(contextMenuPage);
0557     } else if (action == otherPageActions.get(Actions::MoveRight)) {
0558         movePageRight(contextMenuPage);
0559     }
0560 }
0561 
0562 void TabWidget::Private::currentIndexChanged(int index)
0563 {
0564     const Page *const page = this->page(index);
0565     Q_EMIT q->currentViewChanged(page ? page->view() : nullptr);
0566     Q_EMIT q->keyFilterChanged(page ? page->keyFilter() : std::shared_ptr<KeyFilter>());
0567     Q_EMIT q->stringFilterChanged(page ? page->stringFilter() : QString());
0568     enableDisableCurrentPageActions();
0569 }
0570 
0571 void TabWidget::Private::enableDisableCurrentPageActions()
0572 {
0573     const Page *const page = currentPage();
0574 
0575     Q_EMIT q->enableChangeStringFilter(page && page->canChangeStringFilter());
0576     Q_EMIT q->enableChangeKeyFilter(page && page->canChangeKeyFilter());
0577 
0578     enableDisablePageActions(currentPageActions, page);
0579 }
0580 
0581 void TabWidget::Private::enableDisablePageActions(const Actions &actions, const Page *p)
0582 {
0583     actions.setEnabled(Actions::Rename, p && p->canBeRenamed());
0584     actions.setEnabled(Actions::Duplicate, p);
0585     actions.setEnabled(Actions::Close, p && p->canBeClosed() && tabWidget->count() > 1);
0586     actions.setEnabled(Actions::MoveLeft, p && tabWidget->indexOf(const_cast<Page *>(p)) != 0);
0587     actions.setEnabled(Actions::MoveRight, p && tabWidget->indexOf(const_cast<Page *>(p)) != tabWidget->count() - 1);
0588     actions.setEnabled(Actions::Hierarchical, p && p->canChangeHierarchical());
0589     actions.setChecked(Actions::Hierarchical, p && p->isHierarchicalView());
0590     actions.setVisible(Actions::Hierarchical, Kleo::Settings{}.cmsEnabled());
0591     actions.setEnabled(Actions::ExpandAll, p && p->isHierarchicalView());
0592     actions.setEnabled(Actions::CollapseAll, p && p->isHierarchicalView());
0593 }
0594 
0595 void TabWidget::Private::slotPageTitleChanged(const QString &)
0596 {
0597     if (Page *const page = senderPage()) {
0598         const int idx = tabWidget->indexOf(page);
0599         tabWidget->setTabText(idx, page->title());
0600         tabWidget->setTabToolTip(idx, page->toolTip());
0601     }
0602 }
0603 
0604 void TabWidget::Private::slotPageKeyFilterChanged(const std::shared_ptr<KeyFilter> &kf)
0605 {
0606     if (isSenderCurrentPage()) {
0607         Q_EMIT q->keyFilterChanged(kf);
0608     }
0609 }
0610 
0611 void TabWidget::Private::slotPageStringFilterChanged(const QString &filter)
0612 {
0613     if (isSenderCurrentPage()) {
0614         Q_EMIT q->stringFilterChanged(filter);
0615     }
0616 }
0617 
0618 void TabWidget::Private::slotPageHierarchyChanged(bool)
0619 {
0620     enableDisableCurrentPageActions();
0621 }
0622 
0623 void TabWidget::Private::slotNewTab()
0624 {
0625     const KConfigGroup group = KSharedConfig::openConfig()->group(QString::asprintf("View #%u", tabWidget->count()));
0626     Page *page = new Page(QString(), QStringLiteral("all-certificates"), QString(), nullptr, QString(), nullptr, group);
0627     addView(page, currentPage());
0628     tabWidget->setCurrentIndex(tabWidget->count() - 1);
0629 }
0630 
0631 void TabWidget::Private::renamePage(Page *page)
0632 {
0633     if (!page) {
0634         return;
0635     }
0636     bool ok;
0637     const QString text = QInputDialog::getText(q, i18n("Rename Tab"), i18n("New tab title:"), QLineEdit::Normal, page->title(), &ok);
0638     if (!ok) {
0639         return;
0640     }
0641     page->setTitle(text);
0642 }
0643 
0644 void TabWidget::Private::duplicatePage(Page *page)
0645 {
0646     if (!page) {
0647         return;
0648     }
0649     Page *const clone = page->clone();
0650     Q_ASSERT(clone);
0651     clone->liftAllRestrictions();
0652     addView(clone, page);
0653 }
0654 
0655 void TabWidget::Private::closePage(Page *page)
0656 {
0657     if (!page || !page->canBeClosed() || tabWidget->count() <= 1) {
0658         return;
0659     }
0660     Q_EMIT q->viewAboutToBeRemoved(page->view());
0661     tabWidget->removeTab(tabWidget->indexOf(page));
0662     enableDisableCurrentPageActions();
0663 }
0664 
0665 void TabWidget::Private::movePageLeft(Page *page)
0666 {
0667     if (!page) {
0668         return;
0669     }
0670     const int idx = tabWidget->indexOf(page);
0671     if (idx <= 0) {
0672         return;
0673     }
0674     tabWidget->tabBar()->moveTab(idx, idx - 1);
0675     enableDisableCurrentPageActions();
0676 }
0677 
0678 void TabWidget::Private::movePageRight(Page *page)
0679 {
0680     if (!page) {
0681         return;
0682     }
0683     const int idx = tabWidget->indexOf(page);
0684     if (idx < 0 || idx >= tabWidget->count() - 1) {
0685         return;
0686     }
0687     tabWidget->tabBar()->moveTab(idx, idx + 1);
0688     enableDisableCurrentPageActions();
0689 }
0690 
0691 void TabWidget::Private::toggleHierarchicalView(Page *page, bool on)
0692 {
0693     if (!page) {
0694         return;
0695     }
0696     page->setHierarchicalView(on);
0697 }
0698 
0699 void TabWidget::Private::expandAll(Page *page)
0700 {
0701     if (!page || !page->view()) {
0702         return;
0703     }
0704     page->view()->expandAll();
0705 }
0706 
0707 void TabWidget::Private::collapseAll(Page *page)
0708 {
0709     if (!page || !page->view()) {
0710         return;
0711     }
0712     page->view()->collapseAll();
0713 }
0714 
0715 TabWidget::TabWidget(QWidget *p, Qt::WindowFlags f)
0716     : QWidget(p, f)
0717     , d(new Private(this))
0718 {
0719 }
0720 
0721 TabWidget::~TabWidget()
0722 {
0723     saveViews(KSharedConfig::openConfig().data());
0724 }
0725 
0726 void TabWidget::setFlatModel(AbstractKeyListModel *model)
0727 {
0728     if (model == d->flatModel) {
0729         return;
0730     }
0731     d->flatModel = model;
0732     for (unsigned int i = 0, end = count(); i != end; ++i)
0733         if (Page *const page = d->page(i)) {
0734             page->setFlatModel(model);
0735         }
0736 }
0737 
0738 AbstractKeyListModel *TabWidget::flatModel() const
0739 {
0740     return d->flatModel;
0741 }
0742 
0743 void TabWidget::setHierarchicalModel(AbstractKeyListModel *model)
0744 {
0745     if (model == d->hierarchicalModel) {
0746         return;
0747     }
0748     d->hierarchicalModel = model;
0749     for (unsigned int i = 0, end = count(); i != end; ++i)
0750         if (Page *const page = d->page(i)) {
0751             page->setHierarchicalModel(model);
0752         }
0753 }
0754 
0755 AbstractKeyListModel *TabWidget::hierarchicalModel() const
0756 {
0757     return d->hierarchicalModel;
0758 }
0759 
0760 QString TabWidget::stringFilter() const
0761 {
0762     return d->currentPage() ? d->currentPage()->stringFilter() : QString{};
0763 }
0764 
0765 void TabWidget::setStringFilter(const QString &filter)
0766 {
0767     if (Page *const page = d->currentPage()) {
0768         page->setStringFilter(filter);
0769     }
0770 }
0771 
0772 void TabWidget::setKeyFilter(const std::shared_ptr<KeyFilter> &filter)
0773 {
0774     if (!filter) {
0775         qCDebug(KLEOPATRA_LOG) << "TabWidget::setKeyFilter() trial to set filter=NULL";
0776         return;
0777     }
0778 
0779     if (Page *const page = d->currentPage()) {
0780         page->setKeyFilter(filter);
0781     }
0782 }
0783 
0784 std::vector<QAbstractItemView *> TabWidget::views() const
0785 {
0786     std::vector<QAbstractItemView *> result;
0787     const unsigned int N = count();
0788     result.reserve(N);
0789     for (unsigned int i = 0; i != N; ++i)
0790         if (const Page *const p = d->page(i)) {
0791             result.push_back(p->view());
0792         }
0793     return result;
0794 }
0795 
0796 QAbstractItemView *TabWidget::currentView() const
0797 {
0798     if (Page *const page = d->currentPage()) {
0799         return page->view();
0800     } else {
0801         return nullptr;
0802     }
0803 }
0804 
0805 KeyListModelInterface *TabWidget::currentModel() const
0806 {
0807     const QAbstractItemView *const view = currentView();
0808     if (!view) {
0809         return nullptr;
0810     }
0811     auto const proxy = qobject_cast<QAbstractProxyModel *>(view->model());
0812     if (!proxy) {
0813         return nullptr;
0814     }
0815     return dynamic_cast<KeyListModelInterface *>(proxy);
0816 }
0817 
0818 unsigned int TabWidget::count() const
0819 {
0820     return d->tabWidget->count();
0821 }
0822 
0823 void TabWidget::setMultiSelection(bool on)
0824 {
0825     for (unsigned int i = 0, end = count(); i != end; ++i)
0826         if (const Page *const p = d->page(i))
0827             if (QTreeView *const view = p->view()) {
0828                 view->setSelectionMode(on ? QAbstractItemView::ExtendedSelection : QAbstractItemView::SingleSelection);
0829             }
0830 }
0831 
0832 void TabWidget::createActions(KActionCollection *coll)
0833 {
0834     if (!coll) {
0835         return;
0836     }
0837     const action_data actionDataNew = {
0838         "window_new_tab",
0839         i18n("New Tab"),
0840         i18n("Open a new tab"),
0841         "tab-new-background",
0842         this,
0843         [this](bool) {
0844             d->slotNewTab();
0845         },
0846         QStringLiteral("CTRL+SHIFT+N"),
0847     };
0848 
0849     d->newAction = make_action_from_data(actionDataNew, coll);
0850 
0851     const std::vector<action_data> actionData = {
0852         {
0853             Actions::Rename,
0854             i18n("Rename Tab..."),
0855             i18n("Rename this tab"),
0856             "edit-rename",
0857             this,
0858             [this](bool) {
0859                 d->slotRenameCurrentTab();
0860             },
0861             QStringLiteral("CTRL+SHIFT+R"),
0862             RegularQAction,
0863             Disabled,
0864         },
0865         {
0866             Actions::Duplicate,
0867             i18n("Duplicate Tab"),
0868             i18n("Duplicate this tab"),
0869             "tab-duplicate",
0870             this,
0871             [this](bool) {
0872                 d->slotDuplicateCurrentTab();
0873             },
0874             QStringLiteral("CTRL+SHIFT+D"),
0875         },
0876         {
0877             Actions::Close,
0878             i18n("Close Tab"),
0879             i18n("Close this tab"),
0880             "tab-close",
0881             this,
0882             [this](bool) {
0883                 d->slotCloseCurrentTab();
0884             },
0885             QStringLiteral("CTRL+SHIFT+W"),
0886             RegularQAction,
0887             Disabled,
0888         }, // ### CTRL-W when available
0889         {
0890             Actions::MoveLeft,
0891             i18n("Move Tab Left"),
0892             i18n("Move this tab left"),
0893             nullptr,
0894             this,
0895             [this](bool) {
0896                 d->slotMoveCurrentTabLeft();
0897             },
0898             QStringLiteral("CTRL+SHIFT+LEFT"),
0899             RegularQAction,
0900             Disabled,
0901         },
0902         {
0903             Actions::MoveRight,
0904             i18n("Move Tab Right"),
0905             i18n("Move this tab right"),
0906             nullptr,
0907             this,
0908             [this](bool) {
0909                 d->slotMoveCurrentTabRight();
0910             },
0911             QStringLiteral("CTRL+SHIFT+RIGHT"),
0912             RegularQAction,
0913             Disabled,
0914         },
0915         {
0916             Actions::Hierarchical,
0917             i18n("Hierarchical Certificate List"),
0918             QString(),
0919             nullptr,
0920             this,
0921             [this](bool on) {
0922                 d->slotToggleHierarchicalView(on);
0923             },
0924             QString(),
0925             KFToggleAction,
0926             Disabled,
0927         },
0928         {
0929             Actions::ExpandAll,
0930             i18n("Expand All"),
0931             QString(),
0932             nullptr,
0933             this,
0934             [this](bool) {
0935                 d->slotExpandAll();
0936             },
0937             QStringLiteral("CTRL+."),
0938             RegularQAction,
0939             Disabled,
0940         },
0941         {
0942             Actions::CollapseAll,
0943             i18n("Collapse All"),
0944             QString(),
0945             nullptr,
0946             this,
0947             [this](bool) {
0948                 d->slotCollapseAll();
0949             },
0950             QStringLiteral("CTRL+,"),
0951             RegularQAction,
0952             Disabled,
0953         },
0954     };
0955 
0956     for (const auto &ad : actionData) {
0957         d->currentPageActions.insert(ad.name, make_action_from_data(ad, coll));
0958     }
0959 
0960     for (const auto &ad : actionData) {
0961         // create actions for the context menu of the currently not active tabs,
0962         // but do not add those actions to the action collection
0963         auto action = new QAction(ad.text, coll);
0964         if (ad.icon) {
0965             action->setIcon(QIcon::fromTheme(QLatin1StringView(ad.icon)));
0966         }
0967         action->setEnabled(ad.actionState == Enabled);
0968         d->otherPageActions.insert(ad.name, action);
0969     }
0970 
0971     d->newTabButton->setDefaultAction(d->newAction);
0972     d->tabWidget->setCornerWidget(d->newTabButton, Qt::TopLeftCorner);
0973     if (auto action = d->currentPageActions.get(Actions::Close)) {
0974         d->closeTabButton->setDefaultAction(action);
0975         d->tabWidget->setCornerWidget(d->closeTabButton, Qt::TopRightCorner);
0976     } else {
0977         d->closeTabButton->setVisible(false);
0978     }
0979     d->actionsCreated = true;
0980 }
0981 
0982 QAbstractItemView *TabWidget::addView(const QString &title, const QString &id, const QString &text)
0983 {
0984     const KConfigGroup group = KSharedConfig::openConfig()->group(QString::asprintf("View #%u", d->tabWidget->count()));
0985     Page *page = new Page(title, id, text, nullptr, QString(), nullptr, group);
0986     return d->addView(page, d->currentPage());
0987 }
0988 
0989 QAbstractItemView *TabWidget::addView(const KConfigGroup &group)
0990 {
0991     return d->addView(new Page(group), nullptr);
0992 }
0993 
0994 QAbstractItemView *TabWidget::addTemporaryView(const QString &title, AbstractKeyListSortFilterProxyModel *proxy, const QString &tabToolTip)
0995 {
0996     const KConfigGroup group = KSharedConfig::openConfig()->group(QStringLiteral("KeyTreeView_default"));
0997     Page *const page = new Page(title, QString(), QString(), proxy, tabToolTip, nullptr, group);
0998     page->setTemporary(true);
0999     QAbstractItemView *v = d->addView(page, d->currentPage());
1000     d->tabWidget->setCurrentIndex(d->tabWidget->count() - 1);
1001     return v;
1002 }
1003 
1004 QTreeView *TabWidget::Private::addView(Page *page, Page *columnReference)
1005 {
1006     if (!page) {
1007         return nullptr;
1008     }
1009 
1010     if (!actionsCreated) {
1011         auto coll = new KActionCollection(q);
1012         q->createActions(coll);
1013     }
1014 
1015     page->setFlatModel(flatModel);
1016     page->setHierarchicalModel(hierarchicalModel);
1017 
1018     connect(page, &Page::titleChanged, q, [this](const QString &text) {
1019         slotPageTitleChanged(text);
1020     });
1021     connect(page, &Page::keyFilterChanged, q, [this](const std::shared_ptr<Kleo::KeyFilter> &filter) {
1022         slotPageKeyFilterChanged(filter);
1023     });
1024     connect(page, &Page::stringFilterChanged, q, [this](const QString &text) {
1025         slotPageStringFilterChanged(text);
1026     });
1027     connect(page, &Page::hierarchicalChanged, q, [this](bool on) {
1028         slotPageHierarchyChanged(on);
1029     });
1030 
1031     if (columnReference) {
1032         page->setColumnSizes(columnReference->columnSizes());
1033         page->setSortColumn(columnReference->sortColumn(), columnReference->sortOrder());
1034     }
1035 
1036     QAbstractItemView *const previous = q->currentView();
1037     const int tabIndex = tabWidget->addTab(page, page->title());
1038     setTabOrder(closeTabButton, page->view());
1039     tabWidget->setTabToolTip(tabIndex, page->toolTip());
1040     // work around a bug in QTabWidget (tested with 4.3.2) not emitting currentChanged() when the first widget is inserted
1041     QAbstractItemView *const current = q->currentView();
1042     if (previous != current) {
1043         currentIndexChanged(tabWidget->currentIndex());
1044     }
1045     enableDisableCurrentPageActions();
1046     QTreeView *view = page->view();
1047     Q_EMIT q->viewAdded(view);
1048     return view;
1049 }
1050 
1051 static QStringList extractViewGroups(const KConfig *config)
1052 {
1053     return config ? config->groupList().filter(QRegularExpression(QStringLiteral("^View #\\d+$"))) : QStringList();
1054 }
1055 
1056 // work around deleteGroup() not deleting groups out of groupList():
1057 static const bool KCONFIG_DELETEGROUP_BROKEN = true;
1058 
1059 void TabWidget::loadViews(const KConfig *config)
1060 {
1061     if (config) {
1062         QStringList groupList = extractViewGroups(config);
1063         groupList.sort();
1064         for (const QString &group : std::as_const(groupList)) {
1065             const KConfigGroup kcg(config, group);
1066             if (!KCONFIG_DELETEGROUP_BROKEN || kcg.readEntry("magic", 0U) == 0xFA1AFE1U) {
1067                 addView(kcg);
1068             }
1069         }
1070     }
1071     if (!count()) {
1072         // add default view:
1073         addView(i18n("All Certificates"), QStringLiteral("all-certificates"));
1074     }
1075 }
1076 
1077 void TabWidget::saveViews(KConfig *config) const
1078 {
1079     if (!config) {
1080         return;
1081     }
1082     const auto extraView{extractViewGroups(config)};
1083     for (const QString &group : extraView) {
1084         config->deleteGroup(group);
1085     }
1086     unsigned int vg = 0;
1087     for (unsigned int i = 0, end = count(); i != end; ++i) {
1088         if (const Page *const p = d->page(i)) {
1089             if (p->isTemporary()) {
1090                 continue;
1091             }
1092             KConfigGroup group(config, QString::asprintf("View #%u", vg++));
1093             p->saveTo(group);
1094             if (KCONFIG_DELETEGROUP_BROKEN) {
1095                 group.writeEntry("magic", 0xFA1AFE1U);
1096             }
1097         }
1098     }
1099 }
1100 
1101 void TabWidget::connectSearchBar(SearchBar *sb)
1102 {
1103     connect(sb, &SearchBar::stringFilterChanged, this, &TabWidget::setStringFilter);
1104     connect(this, &TabWidget::stringFilterChanged, sb, &SearchBar::setStringFilter);
1105 
1106     connect(sb, &SearchBar::keyFilterChanged, this, &TabWidget::setKeyFilter);
1107     connect(this, &TabWidget::keyFilterChanged, sb, &SearchBar::setKeyFilter);
1108 
1109     connect(this, &TabWidget::enableChangeStringFilter, sb, &SearchBar::setChangeStringFilterEnabled);
1110     connect(this, &TabWidget::enableChangeKeyFilter, sb, &SearchBar::setChangeKeyFilterEnabled);
1111 }
1112 
1113 #include "moc_tabwidget.cpp"
1114 #include "tabwidget.moc"