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"