1 /* -*- mode: c++; c-basic-offset:4 -*-
2     view/tabwidget.cpp
3 
4     This file is part of Kleopatra, the KDE keymanager
5     SPDX-FileCopyrightText: 2007 Klarälvdalens Datakonsult AB
6 
7     SPDX-License-Identifier: GPL-2.0-or-later
8 */
9 
10 #include <config-kleopatra.h>
11 
12 #include "tabwidget.h"
13 #include "keytreeview.h"
14 #include "kleopatra_debug.h"
15 
16 #include <utils/action_data.h>
17 
18 #include <Libkleo/Stl_Util>
19 #include <Libkleo/KeyFilter>
20 #include <Libkleo/KeyFilterManager>
21 #include <Libkleo/KeyListModel>
22 #include <Libkleo/KeyListSortFilterProxyModel>
23 
24 #include <gpgme++/key.h>
25 
26 #include <KLocalizedString>
27 #include <QTabWidget>
28 #include <KConfigGroup>
29 #include <KSharedConfig>
30 #include <KConfig>
31 #include <QAction>
32 #include <KActionCollection>
33 #include <QInputDialog>
34 
35 #include <QTreeView>
36 #include <QToolButton>
37 #include <QMenu>
38 #include <QVBoxLayout>
39 #include <QRegularExpression>
40 #include <QAbstractProxyModel>
41 
42 #include <map>
43 
44 using namespace Kleo;
45 using namespace GpgME;
46 
47 namespace
48 {
49 
50 class Page : public Kleo::KeyTreeView
51 {
52     Q_OBJECT
53     Page(const Page &other);
54 public:
55     Page(const QString &title, const QString &id, const QString &text, AbstractKeyListSortFilterProxyModel *proxy = nullptr, const QString &toolTip = QString(), QWidget *parent = nullptr, const KConfigGroup &group = KConfigGroup());
56     Page(const KConfigGroup &group, QWidget *parent = nullptr);
57     ~Page() override;
58 
59     void setTemporary(bool temporary);
isTemporary() const60     bool isTemporary() const
61     {
62         return m_isTemporary;
63     }
64 
65     void setHierarchicalView(bool hierarchical) override;
66     void setStringFilter(const QString &filter) override;
67     void setKeyFilter(const std::shared_ptr<KeyFilter> &filter) override;
68 
title() const69     QString title() const
70     {
71         return m_title.isEmpty() && keyFilter() ? keyFilter()->name() : m_title;
72     }
73     void setTitle(const QString &title);
74 
toolTip() const75     QString toolTip() const
76     {
77         return m_toolTip.isEmpty() ? title() : m_toolTip;
78     }
79     // not used void setToolTip(const QString &tip);
80 
canBeClosed() const81     bool canBeClosed() const
82     {
83         return m_canBeClosed;
84     }
canBeRenamed() const85     bool canBeRenamed() const
86     {
87         return m_canBeRenamed;
88     }
canChangeStringFilter() const89     bool canChangeStringFilter() const
90     {
91         return m_canChangeStringFilter;
92     }
canChangeKeyFilter() const93     bool canChangeKeyFilter() const
94     {
95         return m_canChangeKeyFilter && !m_isTemporary;
96     }
canChangeHierarchical() const97     bool canChangeHierarchical() const
98     {
99         return m_canChangeHierarchical;
100     }
101 
102     void saveTo(KConfigGroup &group) const;
103 
clone() const104     Page *clone() const override
105     {
106         return new Page(*this);
107     }
108 
liftAllRestrictions()109     void liftAllRestrictions()
110     {
111         m_canBeClosed = m_canBeRenamed = m_canChangeStringFilter = m_canChangeKeyFilter = m_canChangeHierarchical = true;
112     }
113 
114 Q_SIGNALS:
115     void titleChanged(const QString &title);
116 
117 private:
118     void init();
119 
120 private:
121     QString m_title;
122     QString m_toolTip;
123     bool m_isTemporary : 1;
124     bool m_canBeClosed : 1;
125     bool m_canBeRenamed : 1;
126     bool m_canChangeStringFilter : 1;
127     bool m_canChangeKeyFilter : 1;
128     bool m_canChangeHierarchical : 1;
129 };
130 } // anon namespace
131 
Page(const Page & other)132 Page::Page(const Page &other)
133     : KeyTreeView(other),
134       m_title(other.m_title),
135       m_toolTip(other.m_toolTip),
136       m_isTemporary(other.m_isTemporary),
137       m_canBeClosed(other.m_canBeClosed),
138       m_canBeRenamed(other.m_canBeRenamed),
139       m_canChangeStringFilter(other.m_canChangeStringFilter),
140       m_canChangeKeyFilter(other.m_canChangeKeyFilter),
141       m_canChangeHierarchical(other.m_canChangeHierarchical)
142 {
143     init();
144 }
145 
Page(const QString & title,const QString & id,const QString & text,AbstractKeyListSortFilterProxyModel * proxy,const QString & toolTip,QWidget * parent,const KConfigGroup & group)146 Page::Page(const QString &title, const QString &id, const QString &text, AbstractKeyListSortFilterProxyModel *proxy, const QString &toolTip, QWidget *parent,
147            const KConfigGroup &group)
148     : KeyTreeView(text, KeyFilterManager::instance()->keyFilterByID(id), proxy, parent, group),
149       m_title(title),
150       m_toolTip(toolTip),
151       m_isTemporary(false),
152       m_canBeClosed(true),
153       m_canBeRenamed(true),
154       m_canChangeStringFilter(true),
155       m_canChangeKeyFilter(true),
156       m_canChangeHierarchical(true)
157 {
158     init();
159 }
160 
161 static const char TITLE_ENTRY[] = "title";
162 static const char STRING_FILTER_ENTRY[] = "string-filter";
163 static const char KEY_FILTER_ENTRY[] = "key-filter";
164 static const char HIERARCHICAL_VIEW_ENTRY[] = "hierarchical-view";
165 static const char COLUMN_SIZES[] = "column-sizes";
166 static const char SORT_COLUMN[] = "sort-column";
167 static const char SORT_DESCENDING[] = "sort-descending";
168 
Page(const KConfigGroup & group,QWidget * parent)169 Page::Page(const KConfigGroup &group, QWidget *parent)
170     : KeyTreeView(group.readEntry(STRING_FILTER_ENTRY),
171                   KeyFilterManager::instance()->keyFilterByID(group.readEntry(KEY_FILTER_ENTRY)),
172                   nullptr, parent, group),
173       m_title(group.readEntry(TITLE_ENTRY)),
174       m_toolTip(),
175       m_isTemporary(false),
176       m_canBeClosed(!group.isImmutable()),
177       m_canBeRenamed(!group.isEntryImmutable(TITLE_ENTRY)),
178       m_canChangeStringFilter(!group.isEntryImmutable(STRING_FILTER_ENTRY)),
179       m_canChangeKeyFilter(!group.isEntryImmutable(KEY_FILTER_ENTRY)),
180       m_canChangeHierarchical(!group.isEntryImmutable(HIERARCHICAL_VIEW_ENTRY))
181 {
182     init();
183     setHierarchicalView(group.readEntry(HIERARCHICAL_VIEW_ENTRY, true));
184     const QList<int> settings = group.readEntry(COLUMN_SIZES, QList<int>());
185     std::vector<int> sizes;
186     sizes.reserve(settings.size());
187     std::copy(settings.cbegin(), settings.cend(), std::back_inserter(sizes));
188     setColumnSizes(sizes);
189     setSortColumn(group.readEntry(SORT_COLUMN, 0),
190                   group.readEntry(SORT_DESCENDING, true) ? Qt::DescendingOrder : Qt::AscendingOrder);
191 }
192 
init()193 void Page::init()
194 {
195 
196 }
197 
~Page()198 Page::~Page() {}
199 
saveTo(KConfigGroup & group) const200 void Page::saveTo(KConfigGroup &group) const
201 {
202 
203     group.writeEntry(TITLE_ENTRY,         m_title);
204     group.writeEntry(STRING_FILTER_ENTRY, stringFilter());
205     group.writeEntry(KEY_FILTER_ENTRY,    keyFilter() ? keyFilter()->id() : QString());
206     group.writeEntry(HIERARCHICAL_VIEW_ENTRY, isHierarchicalView());
207     QList<int> settings;
208     const auto sizes = columnSizes();
209     settings.reserve(sizes.size());
210     std::copy(sizes.cbegin(), sizes.cend(), std::back_inserter(settings));
211     group.writeEntry(COLUMN_SIZES,        settings);
212     group.writeEntry(SORT_COLUMN,         sortColumn());
213     group.writeEntry(SORT_DESCENDING,     sortOrder() == Qt::DescendingOrder);
214 }
215 
setStringFilter(const QString & filter)216 void Page::setStringFilter(const QString &filter)
217 {
218     if (!m_canChangeStringFilter) {
219         return;
220     }
221     KeyTreeView::setStringFilter(filter);
222 }
223 
setKeyFilter(const std::shared_ptr<KeyFilter> & filter)224 void Page::setKeyFilter(const std::shared_ptr<KeyFilter> &filter)
225 {
226     if (!canChangeKeyFilter()) {
227         return;
228     }
229     const QString oldTitle = title();
230     KeyTreeView::setKeyFilter(filter);
231     const QString newTitle = title();
232     if (oldTitle != newTitle) {
233         Q_EMIT titleChanged(newTitle);
234     }
235 }
236 
setTitle(const QString & t)237 void Page::setTitle(const QString &t)
238 {
239     if (t == m_title) {
240         return;
241     }
242     if (!m_canBeRenamed) {
243         return;
244     }
245     const QString oldTitle = title();
246     m_title = t;
247     const QString newTitle = title();
248     if (oldTitle != newTitle) {
249         Q_EMIT titleChanged(newTitle);
250     }
251 }
252 
253 #if 0 // not used
254 void Page::setToolTip(const QString &tip)
255 {
256     if (tip == m_toolTip) {
257         return;
258     }
259     if (!m_canBeRenamed) {
260         return;
261     }
262     const QString oldTip = toolTip();
263     m_toolTip = tip;
264     const QString newTip = toolTip();
265     if (oldTip != newTip) {
266         Q_EMIT titleChanged(title());
267     }
268 }
269 #endif
270 
setHierarchicalView(bool on)271 void Page::setHierarchicalView(bool on)
272 {
273     if (!m_canChangeHierarchical) {
274         return;
275     }
276     KeyTreeView::setHierarchicalView(on);
277 }
278 
setTemporary(bool on)279 void Page::setTemporary(bool on)
280 {
281     if (on == m_isTemporary) {
282         return;
283     }
284     m_isTemporary = on;
285     if (on) {
286         setKeyFilter(std::shared_ptr<KeyFilter>());
287     }
288 }
289 
290 //
291 //
292 // TabWidget
293 //
294 //
295 
296 class TabWidget::Private
297 {
298     friend class ::Kleo::TabWidget;
299     TabWidget *const q;
300 public:
301     explicit Private(TabWidget *qq);
~Private()302     ~Private() {}
303 
304 private:
305     void slotContextMenu(const QPoint &p);
306     void currentIndexChanged(int index);
307     void slotPageTitleChanged(const QString &title);
308     void slotPageKeyFilterChanged(const std::shared_ptr<KeyFilter> &filter);
309     void slotPageStringFilterChanged(const QString &filter);
310     void slotPageHierarchyChanged(bool on);
311 
312 #ifndef QT_NO_INPUTDIALOG
slotRenameCurrentTab()313     void slotRenameCurrentTab()
314     {
315         renamePage(currentPage());
316     }
317 #endif // QT_NO_INPUTDIALOG
318     void slotNewTab();
slotDuplicateCurrentTab()319     void slotDuplicateCurrentTab()
320     {
321         duplicatePage(currentPage());
322     }
slotCloseCurrentTab()323     void slotCloseCurrentTab()
324     {
325         closePage(currentPage());
326     }
slotMoveCurrentTabLeft()327     void slotMoveCurrentTabLeft()
328     {
329         movePageLeft(currentPage());
330     }
slotMoveCurrentTabRight()331     void slotMoveCurrentTabRight()
332     {
333         movePageRight(currentPage());
334     }
slotToggleHierarchicalView(bool on)335     void slotToggleHierarchicalView(bool on)
336     {
337         toggleHierarchicalView(currentPage(), on);
338     }
slotExpandAll()339     void slotExpandAll()
340     {
341         expandAll(currentPage());
342     }
slotCollapseAll()343     void slotCollapseAll()
344     {
345         collapseAll(currentPage());
346     }
347 
348 #ifndef QT_NO_INPUTDIALOG
349     void renamePage(Page *page);
350 #endif
351     void duplicatePage(Page *page);
352     void closePage(Page *page);
353     void movePageLeft(Page *page);
354     void movePageRight(Page *page);
355     void toggleHierarchicalView(Page *page, bool on);
356     void expandAll(Page *page);
357     void collapseAll(Page *page);
358 
359     void enableDisableCurrentPageActions();
360     void enableDisablePageActions(const std::vector<QAction *> &actions, const Page *page);
361 
currentPage() const362     Page *currentPage() const
363     {
364         Q_ASSERT(!tabWidget.currentWidget() || qobject_cast<Page *>(tabWidget.currentWidget()));
365         return static_cast<Page *>(tabWidget.currentWidget());
366     }
page(unsigned int idx) const367     Page *page(unsigned int idx) const
368     {
369         Q_ASSERT(!tabWidget.widget(idx) || qobject_cast<Page *>(tabWidget.widget(idx)));
370         return static_cast<Page *>(tabWidget.widget(idx));
371     }
372 
senderPage() const373     Page *senderPage() const
374     {
375         QObject *const sender = q->sender();
376         Q_ASSERT(!sender || qobject_cast<Page *>(sender));
377         return static_cast<Page *>(sender);
378     }
379 
isSenderCurrentPage() const380     bool isSenderCurrentPage() const
381     {
382         Page *const sp = senderPage();
383         return sp && sp == currentPage();
384     }
385 
386     QTreeView *addView(Page *page, Page *columnReference);
387     void setCornerAction(QAction *action, Qt::Corner corner);
388 
389 private:
390     AbstractKeyListModel *flatModel;
391     AbstractKeyListModel *hierarchicalModel;
392     QTabWidget tabWidget;
393     QVBoxLayout layout;
394     enum {
395         Rename,
396         Duplicate,
397         Close,
398         MoveLeft,
399         MoveRight,
400         Hierarchical,
401         ExpandAll,
402         CollapseAll,
403 
404         NumPageActions
405     };
406     QAction *newAction = nullptr;
407     std::vector<QAction *> currentPageActions;
408     std::vector<QAction *> otherPageActions;
409     bool actionsCreated;
410 };
411 
Private(TabWidget * qq)412 TabWidget::Private::Private(TabWidget *qq)
413     : q(qq),
414       flatModel(nullptr),
415       hierarchicalModel(nullptr),
416       tabWidget(q),
417       layout(q),
418       actionsCreated(false)
419 {
420     KDAB_SET_OBJECT_NAME(tabWidget);
421     KDAB_SET_OBJECT_NAME(layout);
422 
423     layout.setContentsMargins(0, 0, 0, 0);
424     layout.addWidget(&tabWidget);
425 
426     tabWidget.tabBar()->hide();
427     tabWidget.setMovable(true);
428 
429     tabWidget.tabBar()->setContextMenuPolicy(Qt::CustomContextMenu);
430 
431     connect(&tabWidget, SIGNAL(currentChanged(int)), q, SLOT(currentIndexChanged(int)));
432     connect(tabWidget.tabBar(), &QWidget::customContextMenuRequested, q, [this](const QPoint & p) {
433         slotContextMenu(p);
434     });
435 
436 }
437 
slotContextMenu(const QPoint & p)438 void TabWidget::Private::slotContextMenu(const QPoint &p)
439 {
440     const int tabUnderPos = tabWidget.tabBar()->tabAt(p);
441     Page *const contextMenuPage = static_cast<Page *>(tabWidget.widget(tabUnderPos));
442     const Page *const current = currentPage();
443 
444     const std::vector<QAction *> actions = contextMenuPage == current ? currentPageActions : otherPageActions;
445 
446     enableDisablePageActions(actions, contextMenuPage);
447 
448     QMenu menu;
449     menu.addAction(actions[Rename]);
450     menu.addSeparator();
451     menu.addAction(newAction);
452     menu.addAction(actions[Duplicate]);
453     menu.addSeparator();
454     menu.addAction(actions[MoveLeft]);
455     menu.addAction(actions[MoveRight]);
456     menu.addSeparator();
457     menu.addAction(actions[Close]);
458 
459     const QAction *const action = menu.exec(tabWidget.tabBar()->mapToGlobal(p));
460 
461     if (contextMenuPage == current || action == newAction) {
462         return;    // performed through signal/slot connections...
463     }
464 
465 #ifndef QT_NO_INPUTDIALOG
466     if (action == otherPageActions[Rename]) {
467         renamePage(contextMenuPage);
468     }
469 #endif // QT_NO_INPUTDIALOG
470     else if (action == otherPageActions[Duplicate]) {
471         duplicatePage(contextMenuPage);
472     } else if (action == otherPageActions[Close]) {
473         closePage(contextMenuPage);
474     } else if (action == otherPageActions[MoveLeft]) {
475         movePageLeft(contextMenuPage);
476     } else if (action == otherPageActions[MoveRight]) {
477         movePageRight(contextMenuPage);
478     }
479 
480 }
481 
currentIndexChanged(int index)482 void TabWidget::Private::currentIndexChanged(int index)
483 {
484     const Page *const page = this->page(index);
485     Q_EMIT q->currentViewChanged(page ? page->view() : nullptr);
486     Q_EMIT q->keyFilterChanged(page ? page->keyFilter() : std::shared_ptr<KeyFilter>());
487     Q_EMIT q->stringFilterChanged(page ? page->stringFilter() : QString());
488     enableDisableCurrentPageActions();
489 }
490 
enableDisableCurrentPageActions()491 void TabWidget::Private::enableDisableCurrentPageActions()
492 {
493     const Page *const page = currentPage();
494 
495     Q_EMIT q->enableChangeStringFilter(page && page->canChangeStringFilter());
496     Q_EMIT q->enableChangeKeyFilter(page && page->canChangeKeyFilter());
497 
498     enableDisablePageActions(currentPageActions, page);
499 }
500 
enableDisablePageActions(const std::vector<QAction * > & actions,const Page * p)501 void TabWidget::Private::enableDisablePageActions(const std::vector<QAction *> &actions, const Page *p)
502 {
503     actions[Rename]      ->setEnabled(p && p->canBeRenamed());
504     actions[Duplicate]   ->setEnabled(p);
505     actions[Close]       ->setEnabled(p && p->canBeClosed() && tabWidget.count() > 1);
506     actions[MoveLeft]    ->setEnabled(p && tabWidget.indexOf(const_cast<Page *>(p)) != 0);
507     actions[MoveRight]   ->setEnabled(p && tabWidget.indexOf(const_cast<Page *>(p)) != tabWidget.count() - 1);
508     actions[Hierarchical]->setEnabled(p && p->canChangeHierarchical());
509     actions[Hierarchical]->setChecked(p && p->isHierarchicalView());
510     actions[ExpandAll]   ->setEnabled(p && p->isHierarchicalView());
511     actions[CollapseAll] ->setEnabled(p && p->isHierarchicalView());
512 
513     if (tabWidget.count() < 2) {
514         tabWidget.tabBar()->hide();
515     } else {
516         tabWidget.tabBar()->show();
517     }
518 }
519 
slotPageTitleChanged(const QString &)520 void TabWidget::Private::slotPageTitleChanged(const QString &)
521 {
522     if (Page *const page = senderPage()) {
523         const int idx = tabWidget.indexOf(page);
524         tabWidget.setTabText(idx, page->title());
525         tabWidget.setTabToolTip(idx, page->toolTip());
526     }
527 }
528 
slotPageKeyFilterChanged(const std::shared_ptr<KeyFilter> & kf)529 void TabWidget::Private::slotPageKeyFilterChanged(const std::shared_ptr<KeyFilter> &kf)
530 {
531     if (isSenderCurrentPage()) {
532         Q_EMIT q->keyFilterChanged(kf);
533     }
534 }
535 
slotPageStringFilterChanged(const QString & filter)536 void TabWidget::Private::slotPageStringFilterChanged(const QString &filter)
537 {
538     if (isSenderCurrentPage()) {
539         Q_EMIT q->stringFilterChanged(filter);
540     }
541 }
542 
slotPageHierarchyChanged(bool)543 void TabWidget::Private::slotPageHierarchyChanged(bool)
544 {
545     enableDisableCurrentPageActions();
546 }
547 
slotNewTab()548 void TabWidget::Private::slotNewTab()
549 {
550     const KConfigGroup group = KSharedConfig::openConfig()->group(QString::asprintf("View #%u", tabWidget.count()));
551     Page *page = new Page(QString(), QStringLiteral("all-certificates"), QString(), nullptr, QString(), nullptr, group);
552     addView(page, currentPage());
553     tabWidget.setCurrentIndex(tabWidget.count() - 1);
554 }
555 
renamePage(Page * page)556 void TabWidget::Private::renamePage(Page *page)
557 {
558     if (!page) {
559         return;
560     }
561     bool ok;
562     const QString text = QInputDialog::getText(q, i18n("Rename Tab"), i18n("New tab title:"), QLineEdit::Normal, page->title(), &ok);
563     if (!ok) {
564         return;
565     }
566     page->setTitle(text);
567 }
568 
duplicatePage(Page * page)569 void TabWidget::Private::duplicatePage(Page *page)
570 {
571     if (!page) {
572         return;
573     }
574     Page *const clone = page->clone();
575     Q_ASSERT(clone);
576     clone->liftAllRestrictions();
577     addView(clone, page);
578 }
579 
closePage(Page * page)580 void TabWidget::Private::closePage(Page *page)
581 {
582     if (!page || !page->canBeClosed() || tabWidget.count() <= 1) {
583         return;
584     }
585     Q_EMIT q->viewAboutToBeRemoved(page->view());
586     tabWidget.removeTab(tabWidget.indexOf(page));
587     enableDisableCurrentPageActions();
588 }
589 
movePageLeft(Page * page)590 void TabWidget::Private::movePageLeft(Page *page)
591 {
592     if (!page) {
593         return;
594     }
595     const int idx = tabWidget.indexOf(page);
596     if (idx <= 0) {
597         return;
598     }
599     tabWidget.tabBar()->moveTab(idx, idx - 1);
600     enableDisableCurrentPageActions();
601 }
602 
movePageRight(Page * page)603 void TabWidget::Private::movePageRight(Page *page)
604 {
605     if (!page) {
606         return;
607     }
608     const int idx = tabWidget.indexOf(page);
609     if (idx < 0 || idx >= tabWidget.count() - 1) {
610         return;
611     }
612     tabWidget.tabBar()->moveTab(idx, idx + 1);
613     enableDisableCurrentPageActions();
614 }
615 
toggleHierarchicalView(Page * page,bool on)616 void TabWidget::Private::toggleHierarchicalView(Page *page, bool on)
617 {
618     if (!page) {
619         return;
620     }
621     page->setHierarchicalView(on);
622 }
623 
expandAll(Page * page)624 void TabWidget::Private::expandAll(Page *page)
625 {
626     if (!page || !page->view()) {
627         return;
628     }
629     page->view()->expandAll();
630 }
631 
collapseAll(Page * page)632 void TabWidget::Private::collapseAll(Page *page)
633 {
634     if (!page || !page->view()) {
635         return;
636     }
637     page->view()->collapseAll();
638 }
639 
TabWidget(QWidget * p,Qt::WindowFlags f)640 TabWidget::TabWidget(QWidget *p, Qt::WindowFlags f)
641     : QWidget(p, f), d(new Private(this))
642 {
643 
644 }
645 
~TabWidget()646 TabWidget::~TabWidget() {
647     saveViews(KSharedConfig::openConfig().data());
648 }
649 
setFlatModel(AbstractKeyListModel * model)650 void TabWidget::setFlatModel(AbstractKeyListModel *model)
651 {
652     if (model == d->flatModel) {
653         return;
654     }
655     d->flatModel = model;
656     for (unsigned int i = 0, end = count(); i != end; ++i)
657         if (Page *const page = d->page(i)) {
658             page->setFlatModel(model);
659         }
660 }
661 
flatModel() const662 AbstractKeyListModel *TabWidget::flatModel() const
663 {
664     return d->flatModel;
665 }
666 
setHierarchicalModel(AbstractKeyListModel * model)667 void TabWidget::setHierarchicalModel(AbstractKeyListModel *model)
668 {
669     if (model == d->hierarchicalModel) {
670         return;
671     }
672     d->hierarchicalModel = model;
673     for (unsigned int i = 0, end = count(); i != end; ++i)
674         if (Page *const page = d->page(i)) {
675             page->setHierarchicalModel(model);
676         }
677 }
678 
hierarchicalModel() const679 AbstractKeyListModel *TabWidget::hierarchicalModel() const
680 {
681     return d->hierarchicalModel;
682 }
683 
setCornerAction(QAction * action,Qt::Corner corner)684 void TabWidget::Private::setCornerAction(QAction *action, Qt::Corner corner)
685 {
686     if (!action) {
687         return;
688     }
689     auto b = new QToolButton;
690     b->setDefaultAction(action);
691     tabWidget.setCornerWidget(b, corner);
692 }
693 
stringFilter() const694 QString TabWidget::stringFilter() const
695 {
696     return d->currentPage() ? d->currentPage()->stringFilter() : QString{};
697 }
698 
setStringFilter(const QString & filter)699 void TabWidget::setStringFilter(const QString &filter)
700 {
701     if (Page *const page = d->currentPage()) {
702         page->setStringFilter(filter);
703     }
704 }
705 
setKeyFilter(const std::shared_ptr<KeyFilter> & filter)706 void TabWidget::setKeyFilter(const std::shared_ptr<KeyFilter> &filter)
707 {
708     if (!filter) {
709         qCDebug(KLEOPATRA_LOG) << "TabWidget::setKeyFilter() trial to set filter=NULL";
710         return;
711     }
712 
713     if (Page *const page = d->currentPage()) {
714         page->setKeyFilter(filter);
715     }
716 }
717 
views() const718 std::vector<QAbstractItemView *> TabWidget::views() const
719 {
720     std::vector<QAbstractItemView *> result;
721     const unsigned int N = count();
722     result.reserve(N);
723     for (unsigned int i = 0; i != N; ++i)
724         if (const Page *const p = d->page(i)) {
725             result.push_back(p->view());
726         }
727     return result;
728 }
729 
currentView() const730 QAbstractItemView *TabWidget::currentView() const
731 {
732     if (Page *const page = d->currentPage()) {
733         return page->view();
734     } else {
735         return nullptr;
736     }
737 }
738 
currentModel() const739 KeyListModelInterface *TabWidget::currentModel() const
740 {
741     const QAbstractItemView *const view = currentView();
742     if (!view) {
743         return nullptr;
744     }
745     auto const proxy = qobject_cast<QAbstractProxyModel *>(view->model());
746     if (!proxy) {
747         return nullptr;
748     }
749     return dynamic_cast<KeyListModelInterface *>(proxy);
750 }
751 
count() const752 unsigned int TabWidget::count() const
753 {
754     return d->tabWidget.count();
755 }
756 
setMultiSelection(bool on)757 void TabWidget::setMultiSelection(bool on)
758 {
759     for (unsigned int i = 0, end = count(); i != end; ++i)
760         if (const Page *const p = d->page(i))
761             if (QTreeView *const view = p->view()) {
762                 view->setSelectionMode(on ? QAbstractItemView::ExtendedSelection : QAbstractItemView::SingleSelection);
763             }
764 }
765 
createActions(KActionCollection * coll)766 void TabWidget::createActions(KActionCollection *coll)
767 {
768     if (!coll) {
769         return;
770     }
771     const action_data actionDataNew = {
772         "window_new_tab", i18n("New Tab"), i18n("Open a new tab"),
773         "tab-new-background", this, SLOT(slotNewTab()), QStringLiteral("CTRL+SHIFT+N"), false, true
774     };
775 
776     d->newAction = make_action_from_data(actionDataNew, coll);
777 
778     struct action_data actionData[] = {
779         {
780             "window_rename_tab", i18n("Rename Tab..."), i18n("Rename this tab"),
781             "edit-rename", this, SLOT(slotRenameCurrentTab()), QStringLiteral("CTRL+SHIFT+R"), false, false
782         },
783         {
784             "window_duplicate_tab", i18n("Duplicate Tab"), i18n("Duplicate this tab"),
785             "tab-duplicate", this, SLOT(slotDuplicateCurrentTab()), QStringLiteral("CTRL+SHIFT+D"), false, true
786         },
787         {
788             "window_close_tab", i18n("Close Tab"), i18n("Close this tab"),
789             "tab-close", this, SLOT(slotCloseCurrentTab()), QStringLiteral("CTRL+SHIFT+W"), false, false
790         }, // ### CTRL-W when available
791         {
792             "window_move_tab_left", i18n("Move Tab Left"), i18n("Move this tab left"),
793             nullptr, this, SLOT(slotMoveCurrentTabLeft()), QStringLiteral("CTRL+SHIFT+LEFT"), false, false
794         },
795         {
796             "window_move_tab_right", i18n("Move Tab Right"), i18n("Move this tab right"),
797             nullptr, this, SLOT(slotMoveCurrentTabRight()), QStringLiteral("CTRL+SHIFT+RIGHT"), false, false
798         },
799         {
800             "window_view_hierarchical", i18n("Hierarchical Certificate List"), QString(),
801             nullptr, this, SLOT(slotToggleHierarchicalView(bool)), QString(), true, false
802         },
803         {
804             "window_expand_all", i18n("Expand All"), QString(),
805             nullptr, this, SLOT(slotExpandAll()), QStringLiteral("CTRL+."), false, false
806         },
807         {
808             "window_collapse_all", i18n("Collapse All"), QString(),
809             nullptr, this, SLOT(slotCollapseAll()), QStringLiteral("CTRL+,"), false, false
810         },
811     };
812 
813     d->currentPageActions.reserve(d->NumPageActions);
814     for (int i = 0; i < d->NumPageActions; ++i) {
815         d->currentPageActions.push_back(make_action_from_data(actionData[i], coll));
816     }
817 
818     d->otherPageActions.reserve(d->NumPageActions);
819     for (int i = 0; i < d->NumPageActions; ++i) {
820         // create actions for the context menu of the currently not active tabs,
821         // but do not add those actions to the action collection
822         const action_data ad = actionData[i];
823         auto action = new QAction(ad.text, coll);
824         if (ad.icon) {
825             action->setIcon(QIcon::fromTheme(QLatin1String(ad.icon)));
826         }
827         action->setEnabled(ad.enabled);
828         d->otherPageActions.push_back(action);
829     }
830 
831     d->setCornerAction(d->newAction,                 Qt::TopLeftCorner);
832     d->setCornerAction(d->currentPageActions[d->Close], Qt::TopRightCorner);
833     d->actionsCreated = true;
834 }
835 
addView(const QString & title,const QString & id,const QString & text)836 QAbstractItemView *TabWidget::addView(const QString &title, const QString &id, const QString &text)
837 {
838     const KConfigGroup group = KSharedConfig::openConfig()->group(QString::asprintf("View #%u", d->tabWidget.count()));
839     Page *page = new Page(title, id, text, nullptr, QString(), nullptr, group);
840     return d->addView(page, d->currentPage());
841 }
842 
addView(const KConfigGroup & group)843 QAbstractItemView *TabWidget::addView(const KConfigGroup &group)
844 {
845     return d->addView(new Page(group), nullptr);
846 }
847 
addTemporaryView(const QString & title,AbstractKeyListSortFilterProxyModel * proxy,const QString & tabToolTip)848 QAbstractItemView *TabWidget::addTemporaryView(const QString &title, AbstractKeyListSortFilterProxyModel *proxy, const QString &tabToolTip)
849 {
850     const KConfigGroup group = KSharedConfig::openConfig()->group("KeyTreeView_default");
851     Page *const page = new Page(title, QString(), QString(), proxy, tabToolTip, nullptr, group);
852     page->setTemporary(true);
853     QAbstractItemView *v = d->addView(page, d->currentPage());
854     d->tabWidget.setCurrentIndex(d->tabWidget.count() - 1);
855     return v;
856 }
857 
addView(Page * page,Page * columnReference)858 QTreeView *TabWidget::Private::addView(Page *page, Page *columnReference)
859 {
860     if (!page) {
861         return nullptr;
862     }
863 
864     if (!actionsCreated) {
865         auto coll = new KActionCollection(q);
866         q->createActions(coll);
867     }
868 
869     page->setFlatModel(flatModel);
870     page->setHierarchicalModel(hierarchicalModel);
871 
872     connect(page, SIGNAL(titleChanged(QString)), q, SLOT(slotPageTitleChanged(QString)));
873     connect(page, SIGNAL(keyFilterChanged(std::shared_ptr<Kleo::KeyFilter>)), q, SLOT(slotPageKeyFilterChanged(std::shared_ptr<Kleo::KeyFilter>)));
874     connect(page, SIGNAL(stringFilterChanged(QString)), q, SLOT(slotPageStringFilterChanged(QString)));
875     connect(page, SIGNAL(hierarchicalChanged(bool)), q, SLOT(slotPageHierarchyChanged(bool)));
876 
877     if (columnReference) {
878         page->setColumnSizes(columnReference->columnSizes());
879         page->setSortColumn(columnReference->sortColumn(), columnReference->sortOrder());
880     }
881 
882     QAbstractItemView *const previous = q->currentView();
883     const int tabIndex = tabWidget.addTab(page, page->title());
884     tabWidget.setTabToolTip(tabIndex, page->toolTip());
885     // work around a bug in QTabWidget (tested with 4.3.2) not emitting currentChanged() when the first widget is inserted
886     QAbstractItemView *const current = q->currentView();
887     if (previous != current) {
888         currentIndexChanged(tabWidget.currentIndex());
889     }
890     enableDisableCurrentPageActions();
891     QTreeView *view = page->view();
892     Q_EMIT q->viewAdded(view);
893     return view;
894 }
895 
extractViewGroups(const KConfig * config)896 static QStringList extractViewGroups(const KConfig *config)
897 {
898     return config ? config->groupList().filter(QRegularExpression(QStringLiteral("^View #\\d+$"))) : QStringList();
899 }
900 
901 // work around deleteGroup() not deleting groups out of groupList():
902 static const bool KCONFIG_DELETEGROUP_BROKEN = true;
903 
loadViews(const KConfig * config)904 void TabWidget::loadViews(const KConfig *config)
905 {
906     if (config) {
907         QStringList groupList = extractViewGroups(config);
908         groupList.sort();
909         for (const QString &group : std::as_const(groupList)) {
910             const KConfigGroup kcg(config, group);
911             if (!KCONFIG_DELETEGROUP_BROKEN || kcg.readEntry("magic", 0U) == 0xFA1AFE1U) {
912                 addView(kcg);
913             }
914         }
915     }
916     if (!count()) {
917         // add default view:
918         addView(QString(), QStringLiteral("all-certificates"));
919     }
920 }
921 
saveViews(KConfig * config) const922 void TabWidget::saveViews(KConfig *config) const
923 {
924     if (!config) {
925         return;
926     }
927     const auto extraView{extractViewGroups(config)};
928     for (const QString &group : extraView) {
929         config->deleteGroup(group);
930     }
931     unsigned int vg = 0;
932     for (unsigned int i = 0, end = count(); i != end; ++i) {
933         if (const Page *const p = d->page(i)) {
934             if (p->isTemporary()) {
935                 continue;
936             }
937             KConfigGroup group(config, QString::asprintf("View #%u", vg++));
938             p->saveTo(group);
939             if (KCONFIG_DELETEGROUP_BROKEN) {
940                 group.writeEntry("magic", 0xFA1AFE1U);
941             }
942         }
943     }
944 }
945 
xconnect(const QObject * o1,const char * signal,const QObject * o2,const char * slot)946 static void xconnect(const QObject *o1, const char *signal, const QObject *o2, const char *slot)
947 {
948     QObject::connect(o1, signal, o2, slot);
949     QObject::connect(o2, signal, o1, slot);
950 }
951 
connectSearchBar(QObject * sb)952 void TabWidget::connectSearchBar(QObject *sb)
953 {
954     xconnect(sb, SIGNAL(stringFilterChanged(QString)),
955              this, SLOT(setStringFilter(QString)));
956     xconnect(sb, SIGNAL(keyFilterChanged(std::shared_ptr<Kleo::KeyFilter>)),
957              this, SLOT(setKeyFilter(std::shared_ptr<Kleo::KeyFilter>)));
958     connect(this, SIGNAL(enableChangeStringFilter(bool)),
959             sb, SLOT(setChangeStringFilterEnabled(bool)));
960     connect(this, SIGNAL(enableChangeKeyFilter(bool)),
961             sb, SLOT(setChangeKeyFilterEnabled(bool)));
962 }
963 
964 #include "moc_tabwidget.cpp"
965 #include "tabwidget.moc"
966