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