diff --git a/CMakeLists.txt b/CMakeLists.txt index 045dd1299..0ac8b74c9 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,149 +1,149 @@ set(kleopatra_version 3.1.7) # The following is for Windows. Keep in line with kleopatra_version. set(kleopatra_fileversion 3,1,7,0) cmake_minimum_required(VERSION 3.5) project(kleopatra VERSION ${kleopatra_version}) # Add version suffix for internal usage of the version string set(kleopatra_version ${kleopatra_version}${KLEOPATRA_VERSION_SUFFIX}) option(FORCE_DISABLE_KCMUTILS "Force building Kleopatra without KCMUtils. Doing this will disable configuration KCM Plugins. [default=OFF]" OFF) option(DISABLE_KWATCHGNUPG "Don't build the kwatchgnupg tool [default=OFF]" OFF) # Standalone build. Find / include everything necessary. set(KF5_MIN_VERSION "5.57.0") set(KMIME_VERSION "5.11.40") -set(LIBKLEO_VERSION "5.11.40") +set(LIBKLEO_VERSION "5.11.41") set(QT_REQUIRED_VERSION "5.10.0") set(GPGME_REQUIRED_VERSION "1.8.0") find_package(ECM ${KF5_MIN_VERSION} CONFIG REQUIRED) set(CMAKE_MODULE_PATH ${ECM_MODULE_PATH}) set(CMAKE_MODULE_PATH ${CMAKE_CURRENT_SOURCE_DIR}/cmake/modules ${CMAKE_MODULE_PATH}) include(ECMInstallIcons) include(ECMSetupVersion) include(ECMAddTests) include(GenerateExportHeader) include(ECMGenerateHeaders) include(FeatureSummary) include(CheckFunctionExists) include(ECMGeneratePriFile) include(KDEInstallDirs) include(KDECMakeSettings) include(KDEFrameworkCompilerSettings NO_POLICY_SCOPE) include(ECMAddAppIcon) include(ECMQtDeclareLoggingCategory) # Find KF5 packages if (NOT FORCE_DISABLE_KCMUTILS) find_package(KF5KCMUtils ${KF5_MIN_VERSION} CONFIG REQUIRED) endif() find_package(KF5WidgetsAddons ${KF5_MIN_VERSION} CONFIG REQUIRED) find_package(KF5ConfigWidgets ${KF5_MIN_VERSION} CONFIG REQUIRED) find_package(KF5CoreAddons ${KF5_MIN_VERSION} CONFIG REQUIRED) find_package(KF5Codecs ${KF5_MIN_VERSION} CONFIG REQUIRED) find_package(KF5Config ${KF5_MIN_VERSION} CONFIG REQUIRED) find_package(KF5I18n ${KF5_MIN_VERSION} CONFIG REQUIRED) find_package(KF5IconThemes ${KF5_MIN_VERSION} CONFIG REQUIRED) find_package(KF5ItemModels ${KF5_MIN_VERSION} CONFIG REQUIRED) find_package(KF5XmlGui ${KF5_MIN_VERSION} CONFIG REQUIRED) find_package(KF5WindowSystem ${KF5_MIN_VERSION} CONFIG REQUIRED) find_package(KF5DocTools ${KF5_MIN_VERSION} CONFIG) find_package(KF5Crash ${KF5_MIN_VERSION} REQUIRED) set_package_properties(KF5DocTools PROPERTIES DESCRIPTION "Documentation tools" TYPE OPTIONAL PURPOSE "Required to generate Kleopatra documentation.") # Optional packages if (WIN32) # Only a replacement available for Windows so this # is required on other platforms. find_package(KF5DBusAddons ${KF5_MIN_VERSION} CONFIG) set_package_properties(KF5DBusAddons PROPERTIES DESCRIPTION "Support library to work with DBus" PURPOSE "DBus session integration" URL "https://inqlude.org/libraries/kdbusaddons.html" TYPE OPTIONAL) else() find_package(KF5DBusAddons ${KF5_MIN_VERSION} CONFIG REQUIRED) set(_kleopatra_dbusaddons_libs KF5::DBusAddons) endif() set(HAVE_QDBUS ${Qt5DBus_FOUND}) find_package(Gpgmepp ${GPGME_REQUIRED_VERSION} CONFIG REQUIRED) find_package(QGpgme ${GPGME_REQUIRED_VERSION} CONFIG REQUIRED) # Kdepimlibs packages find_package(KF5Libkleo ${LIBKLEO_VERSION} CONFIG REQUIRED) find_package(KF5Mime ${KMIME_VERSION} CONFIG REQUIRED) find_package(Qt5 ${QT_REQUIRED_VERSION} CONFIG REQUIRED Widgets Test Network PrintSupport) find_package(Assuan2 REQUIRED) set(HAVE_KCMUTILS ${KF5KCMUtils_FOUND}) find_package(Boost 1.34.0 REQUIRED) find_path(Boost_TOPOLOGICAL_SORT_DIR NAMES boost/graph/topological_sort.hpp PATHS ${Boost_INCLUDE_DIRS}) if(NOT Boost_TOPOLOGICAL_SORT_DIR) message(FATAL_ERROR "The Boost Topological_sort header was NOT found. Should be part of Boost graph module.") endif() set(kleopatra_release FALSE) if(NOT kleopatra_release) if(GIT_FOUND) execute_process(COMMAND ${GIT_EXECUTABLE} log -1 --oneline --format=%h ${CMAKE_CURRENT_SOURCE_DIR} WORKING_DIRECTORY ${kdepim_SOURCE_DIR}/kleopatra OUTPUT_VARIABLE Kleopatra_WC_REVISION) string(REGEX REPLACE "\n" "" Kleopatra_WC_REVISION "${Kleopatra_WC_REVISION}") execute_process(COMMAND ${GIT_EXECUTABLE} log -1 --oneline --format=%ci ${CMAKE_CURRENT_SOURCE_DIR} WORKING_DIRECTORY ${kdepim_SOURCE_DIR}/kleopatra OUTPUT_VARIABLE Kleopatra_WC_LAST_CHANGED_DATE) string(REGEX REPLACE " [-0-9:+ ]*\n" "" Kleopatra_WC_LAST_CHANGED_DATE "${Kleopatra_WC_LAST_CHANGED_DATE}") set(kleopatra_version "${kleopatra_version}-git${Kleopatra_WC_REVISION} (${Kleopatra_WC_LAST_CHANGED_DATE})") endif() endif() configure_file(${CMAKE_CURRENT_SOURCE_DIR}/version-kleopatra.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/version-kleopatra.h) include (ConfigureChecks.cmake) configure_file(${CMAKE_CURRENT_SOURCE_DIR}/config-kleopatra.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/config-kleopatra.h) include_directories( ${CMAKE_CURRENT_BINARY_DIR} ${CMAKE_CURRENT_SOURCE_DIR} ${Boost_INCLUDE_DIR} ${ASSUAN2_INCLUDES} ) add_definitions(-D_ASSUAN_ONLY_GPG_ERRORS) if(CMAKE_COMPILER_IS_GNUCXX) set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-missing-braces -Wno-parentheses -Wno-ignored-qualifiers") endif() kde_enable_exceptions() add_subdirectory(pics) add_subdirectory(src) if(BUILD_TESTING) add_subdirectory(tests) add_subdirectory(autotests) endif() install( FILES kleopatra.renamecategories kleopatra.categories DESTINATION ${KDE_INSTALL_CONFDIR} ) if(KF5DocTools_FOUND) add_subdirectory(doc) endif() diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 4a422a766..040b5eff8 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -1,693 +1,693 @@ /* -*- mode: c++; c-basic-offset:4 -*- mainwindow.cpp This file is part of Kleopatra, the KDE keymanager Copyright (c) 2007 Klarälvdalens Datakonsult AB Kleopatra is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. Kleopatra is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA In addition, as a special exception, the copyright holders give permission to link the code of this program with any edition of the Qt library by Trolltech AS, Norway (or with modified versions of Qt that use the same license as Qt), and distribute linked combinations including the two. You must obey the GNU General Public License in all respects for all of the code used other than Qt. If you modify this file, you may extend this exception to your version of the file, but you are not obligated to do so. If you do not wish to do so, delete this exception statement from your version. */ #include #include "mainwindow.h" #include "aboutdata.h" #include "view/padwidget.h" #include "view/searchbar.h" #include "view/tabwidget.h" #include "view/keylistcontroller.h" #include "view/keycacheoverlay.h" #include "view/smartcardwidget.h" #include "view/welcomewidget.h" #include "commands/selftestcommand.h" #include "commands/importcrlcommand.h" #include "commands/importcertificatefromfilecommand.h" #include "commands/decryptverifyfilescommand.h" #include "commands/signencryptfilescommand.h" #include "utils/detail_p.h" #include "utils/gnupg-helper.h" #include "utils/action_data.h" #include "utils/filedialog.h" #include "utils/clipboardmenu.h" #include "dialogs/updatenotification.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include "kleopatra_debug.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include using namespace Kleo; using namespace Kleo::Commands; using namespace GpgME; static KGuiItem KStandardGuiItem_quit() { static const QString app = KAboutData::applicationData().componentName(); KGuiItem item = KStandardGuiItem::quit(); item.setText(i18nc("Quit [ApplicationName]", "&Quit %1", app)); return item; } static KGuiItem KStandardGuiItem_close() { KGuiItem item = KStandardGuiItem::close(); item.setText(i18n("Only &Close Window")); return item; } static bool isQuitting = false; class MainWindow::Private { friend class ::MainWindow; MainWindow *const q; public: explicit Private(MainWindow *qq); ~Private(); template void createAndStart() { (new T(this->currentView(), &this->controller))->start(); } template void createAndStart(QAbstractItemView *view) { (new T(view, &this->controller))->start(); } template void createAndStart(const QStringList &a) { (new T(a, this->currentView(), &this->controller))->start(); } template void createAndStart(const QStringList &a, QAbstractItemView *view) { (new T(a, view, &this->controller))->start(); } void closeAndQuit() { const QString app = KAboutData::applicationData().componentName(); const int rc = KMessageBox::questionYesNoCancel(q, i18n("%1 may be used by other applications as a service.\n" "You may instead want to close this window without exiting %1.", app), i18n("Really Quit?"), KStandardGuiItem_close(), KStandardGuiItem_quit(), KStandardGuiItem::cancel(), QLatin1String("really-quit-") + app.toLower()); if (rc == KMessageBox::Cancel) { return; } isQuitting = true; if (!q->close()) { return; } // WARNING: 'this' might be deleted at this point! if (rc == KMessageBox::No) { qApp->quit(); } } void configureToolbars() { KEditToolBar dlg(q->factory()); dlg.exec(); } void editKeybindings() { KShortcutsDialog::configure(q->actionCollection(), KShortcutsEditor::LetterShortcutsAllowed); updateSearchBarClickMessage(); } void updateSearchBarClickMessage() { const QString shortcutStr = focusToClickSearchAction->shortcut().toString(); ui.searchBar->updateClickMessage(shortcutStr); } void updateStatusBar() { const auto complianceMode = Formatting::complianceMode(); if (!complianceMode.isEmpty()) { auto statusBar = new QStatusBar; q->setStatusBar(statusBar); auto statusLbl = new QLabel(i18nc("Compliance means that GnuPG is running in a more restricted mode e.g. to handle restricted documents.", // The germans want some extra sausage "Compliance: %1", complianceMode == QStringLiteral("de-vs") ? QStringLiteral ("VS-NfD") : complianceMode)); statusBar->insertPermanentWidget(0, statusLbl); } else { q->setStatusBar(nullptr); } } void selfTest() { createAndStart(); } void configureBackend(); void showHandbook(); void gnupgLogViewer() { if (!QProcess::startDetached(QStringLiteral("kwatchgnupg"))) KMessageBox::error(q, i18n("Could not start the GnuPG Log Viewer (kwatchgnupg). " "Please check your installation."), i18n("Error Starting KWatchGnuPG")); } void forceUpdateCheck() { UpdateNotification::forceUpdateCheck(q); } void openCompendium() { QDir datadir(QCoreApplication::applicationDirPath() + QStringLiteral("/../share/gpg4win")); const auto path = datadir.filePath(i18nc("The Gpg4win compendium is only available" "at this point (24.7.2017) in german and english." "Please check with Gpg4win before translating this filename.", "gpg4win-compendium-en.pdf")); qCDebug(KLEOPATRA_LOG) << "Opening Compendium at:" << path; // The compendium is always installed. So this should work. Otherwise // we have debug output. QDesktopServices::openUrl(QUrl::fromLocalFile(path)); } void slotConfigCommitted(); void slotContextMenuRequested(QAbstractItemView *, const QPoint &p) { if (QMenu *const menu = qobject_cast(q->factory()->container(QStringLiteral("listview_popup"), q))) { menu->exec(p); } else { qCDebug(KLEOPATRA_LOG) << "no \"listview_popup\" in kleopatra's ui.rc file"; } } void slotFocusQuickSearch() { ui.searchBar->lineEdit()->setFocus(); } void toggleSmartcardView() { if (ui.stackWidget->currentWidget() == ui.scWidget) { ui.stackWidget->setCurrentWidget(ui.searchTab); checkWelcomePage(); return; } ui.stackWidget->setCurrentWidget(ui.scWidget); } void togglePadView() { if (ui.stackWidget->currentWidget() == ui.padWidget) { ui.stackWidget->setCurrentWidget(ui.searchTab); checkWelcomePage(); return; } if (!ui.padWidget) { ui.padWidget = new PadWidget; ui.stackWidget->addWidget(ui.padWidget); } ui.stackWidget->setCurrentWidget(ui.padWidget); ui.stackWidget->resize(ui.padWidget->sizeHint()); } private: void setupActions(); QAbstractItemView *currentView() const { return ui.tabWidget.currentView(); } void checkWelcomePage() { const auto curWidget = ui.stackWidget->currentWidget(); if (curWidget == ui.scWidget || curWidget == ui.padWidget) { return; } if (KeyCache::instance()->keys().empty()) { ui.stackWidget->setCurrentWidget(ui.welcomeWidget); } else { ui.stackWidget->setCurrentWidget(ui.searchTab); } } private: Kleo::KeyListController controller; bool firstShow : 1; struct UI { QWidget *searchTab; TabWidget tabWidget; SearchBar *searchBar; PadWidget *padWidget; SmartCardWidget *scWidget; WelcomeWidget *welcomeWidget; QStackedWidget *stackWidget; explicit UI(MainWindow *q); } ui; QAction *focusToClickSearchAction; ClipboardMenu *clipboadMenu; }; MainWindow::Private::UI::UI(MainWindow *q) : tabWidget(q), padWidget(nullptr) { KDAB_SET_OBJECT_NAME(tabWidget); searchTab = new QWidget; QVBoxLayout *vbox = new QVBoxLayout(searchTab); vbox->setSpacing(0); searchBar = new SearchBar; vbox->addWidget(searchBar); tabWidget.connectSearchBar(searchBar); vbox->addWidget(&tabWidget); QWidget *mainWidget = new QWidget; auto mainLayout = new QVBoxLayout(mainWidget); stackWidget = new QStackedWidget; mainLayout->addWidget(stackWidget); stackWidget->addWidget(searchTab); new KeyCacheOverlay(mainWidget, q); scWidget = new SmartCardWidget(); stackWidget->addWidget(scWidget); welcomeWidget = new WelcomeWidget(); stackWidget->addWidget(welcomeWidget); q->setCentralWidget(mainWidget); } MainWindow::Private::Private(MainWindow *qq) : q(qq), controller(q), firstShow(true), ui(q) { KDAB_SET_OBJECT_NAME(controller); AbstractKeyListModel *flatModel = AbstractKeyListModel::createFlatKeyListModel(q); AbstractKeyListModel *hierarchicalModel = AbstractKeyListModel::createHierarchicalKeyListModel(q); KDAB_SET_OBJECT_NAME(flatModel); KDAB_SET_OBJECT_NAME(hierarchicalModel); controller.setFlatModel(flatModel); controller.setHierarchicalModel(hierarchicalModel); controller.setTabWidget(&ui.tabWidget); ui.tabWidget.setFlatModel(flatModel); ui.tabWidget.setHierarchicalModel(hierarchicalModel); ui.stackWidget->setCurrentWidget(ui.searchTab); setupActions(); connect(&controller, SIGNAL(contextMenuRequested(QAbstractItemView*,QPoint)), q, SLOT(slotContextMenuRequested(QAbstractItemView*,QPoint))); connect(ui.scWidget, &SmartCardWidget::backRequested, q, [this]() { auto action = q->actionCollection()->action(QStringLiteral("manage_smartcard")); Q_ASSERT(action); action->setChecked(false); }); connect(KeyCache::instance().get(), &KeyCache::keyListingDone, q, [this] () {checkWelcomePage();}); q->createGUI(QStringLiteral("kleopatra.rc")); q->setAcceptDrops(true); q->setAutoSaveSettings(); updateSearchBarClickMessage(); updateStatusBar(); } MainWindow::Private::~Private() {} MainWindow::MainWindow(QWidget *parent, Qt::WindowFlags flags) : KXmlGuiWindow(parent, flags), d(new Private(this)) { - resize(1000, 500); + resize(1024, 500); } MainWindow::~MainWindow() {} void MainWindow::Private::setupActions() { KActionCollection *const coll = q->actionCollection(); const action_data action_data[] = { // most have been MOVED TO keylistcontroller.cpp // Tools menu #ifndef Q_OS_WIN { "tools_start_kwatchgnupg", i18n("GnuPG Log Viewer"), QString(), "kwatchgnupg", q, SLOT(gnupgLogViewer()), QString(), false, true }, #endif #ifdef Q_OS_WIN { "help_check_updates", i18n("Check for updates"), QString(), "gpg4win-compact", q, SLOT(forceUpdateCheck()), QString(), false, true }, { "help_show_compendium", i18n("Gpg4win Compendium"), QString(), "gpg4win-compact", q, SLOT(openCompendium()), QString(), false, true }, #endif { "pad_view", i18nc("Title for a generic data input / output area supporting text actions.", "Notepad"), i18n("Switch to Pad view."), "edittext", q, SLOT(togglePadView()), QString(), true, true }, // most have been MOVED TO keylistcontroller.cpp #if 0 { "configure_backend", i18n("Configure GnuPG Backend..."), QString(), 0, q, SLOT(configureBackend()), QString(), false, true }, #endif // Settings menu { "settings_self_test", i18n("Perform Self-Test"), QString(), nullptr, q, SLOT(selfTest()), QString(), false, true }, { "manage_smartcard", i18n("Manage Smartcards"), i18n("Edit or initialize a crypto hardware token."), "secure-card", q, SLOT(toggleSmartcardView()), QString(), true, true } // most have been MOVED TO keylistcontroller.cpp }; make_actions_from_data(action_data, /*sizeof action_data / sizeof *action_data,*/ coll); if (QAction *action = coll->action(QStringLiteral("configure_backend"))) { action->setMenuRole(QAction::NoRole); //prevent Qt OS X heuristics for config* actions } KStandardAction::close(q, SLOT(close()), coll); KStandardAction::quit(q, SLOT(closeAndQuit()), coll); KStandardAction::configureToolbars(q, SLOT(configureToolbars()), coll); KStandardAction::keyBindings(q, SLOT(editKeybindings()), coll); KStandardAction::preferences(qApp, SLOT(openOrRaiseConfigDialog()), coll); focusToClickSearchAction = new QAction(i18n("Set Focus to Quick Search"), q); coll->addAction(QStringLiteral("focus_to_quickseach"), focusToClickSearchAction); coll->setDefaultShortcut(focusToClickSearchAction, QKeySequence(Qt::ALT + Qt::Key_Q)); connect(focusToClickSearchAction, SIGNAL(triggered(bool)), q, SLOT(slotFocusQuickSearch())); clipboadMenu = new ClipboardMenu(q); clipboadMenu->setMainWindow(q); clipboadMenu->clipboardMenu()->setIcon(QIcon::fromTheme(QStringLiteral("edit-paste"))); clipboadMenu->clipboardMenu()->setDelayed(false); coll->addAction(QStringLiteral("clipboard_menu"), clipboadMenu->clipboardMenu()); q->setStandardToolBarMenuEnabled(true); controller.createActions(coll); ui.tabWidget.createActions(coll); } void MainWindow::Private::configureBackend() { QGpgME::CryptoConfig *const config = QGpgME::cryptoConfig(); if (!config) { KMessageBox::error(q, i18n("Could not configure the cryptography backend (gpgconf tool not found)"), i18n("Configuration Error")); return; } Kleo::CryptoConfigDialog dlg(config); const int result = dlg.exec(); // Forget all data parsed from gpgconf, so that we show updated information // when reopening the configuration dialog. config->clear(); if (result == QDialog::Accepted) { #if 0 // Tell other apps (e.g. kmail) that the gpgconf data might have changed QDBusMessage message = QDBusMessage::createSignal(QString(), "org.kde.kleo.CryptoConfig", "changed"); QDBusConnection::sessionBus().send(message); #endif } } void MainWindow::Private::slotConfigCommitted() { controller.updateConfig(); updateStatusBar(); } void MainWindow::closeEvent(QCloseEvent *e) { // KMainWindow::closeEvent() insists on quitting the application, // so do not let it touch the event... qCDebug(KLEOPATRA_LOG); if (d->controller.hasRunningCommands()) { if (d->controller.shutdownWarningRequired()) { const int ret = KMessageBox::warningContinueCancel(this, i18n("There are still some background operations ongoing. " "These will be terminated when closing the window. " "Proceed?"), i18n("Ongoing Background Tasks")); if (ret != KMessageBox::Continue) { e->ignore(); return; } } d->controller.cancelCommands(); if (d->controller.hasRunningCommands()) { // wait for them to be finished: setEnabled(false); QEventLoop ev; QTimer::singleShot(100, &ev, &QEventLoop::quit); connect(&d->controller, &KeyListController::commandsExecuting, &ev, &QEventLoop::quit); ev.exec(); if (d->controller.hasRunningCommands()) qCWarning(KLEOPATRA_LOG) << "controller still has commands running, this may crash now..."; setEnabled(true); } } if (isQuitting || qApp->isSavingSession()) { d->ui.tabWidget.saveViews(KSharedConfig::openConfig().data()); KConfigGroup grp(KConfigGroup(KSharedConfig::openConfig(), autoSaveGroup())); saveMainWindowSettings(grp); e->accept(); } else { e->ignore(); hide(); } } void MainWindow::showEvent(QShowEvent *e) { KXmlGuiWindow::showEvent(e); if (d->firstShow) { d->ui.tabWidget.loadViews(KSharedConfig::openConfig().data()); d->firstShow = false; } if (!savedGeometry.isEmpty()) { restoreGeometry(savedGeometry); } } void MainWindow::hideEvent(QHideEvent *e) { savedGeometry = saveGeometry(); KXmlGuiWindow::hideEvent(e); } void MainWindow::importCertificatesFromFile(const QStringList &files) { if (!files.empty()) { d->createAndStart(files); } } static QStringList extract_local_files(const QMimeData *data) { const QList urls = data->urls(); // begin workaround KDE/Qt misinterpretation of text/uri-list QList::const_iterator end = urls.end(); if (urls.size() > 1 && !urls.back().isValid()) { --end; } // end workaround QStringList result; std::transform(urls.begin(), end, std::back_inserter(result), std::mem_fn(&QUrl::toLocalFile)); result.erase(std::remove_if(result.begin(), result.end(), std::mem_fn(&QString::isEmpty)), result.end()); return result; } static bool can_decode_local_files(const QMimeData *data) { if (!data) { return false; } return !extract_local_files(data).empty(); } void MainWindow::dragEnterEvent(QDragEnterEvent *e) { qCDebug(KLEOPATRA_LOG); if (can_decode_local_files(e->mimeData())) { e->acceptProposedAction(); } } void MainWindow::dropEvent(QDropEvent *e) { qCDebug(KLEOPATRA_LOG); if (!can_decode_local_files(e->mimeData())) { return; } e->setDropAction(Qt::CopyAction); const QStringList files = extract_local_files(e->mimeData()); const unsigned int classification = classify(files); QMenu menu; QAction *const signEncrypt = menu.addAction(i18n("Sign/Encrypt...")); QAction *const decryptVerify = mayBeAnyMessageType(classification) ? menu.addAction(i18n("Decrypt/Verify...")) : nullptr; if (signEncrypt || decryptVerify) { menu.addSeparator(); } QAction *const importCerts = mayBeAnyCertStoreType(classification) ? menu.addAction(i18n("Import Certificates")) : nullptr; QAction *const importCRLs = mayBeCertificateRevocationList(classification) ? menu.addAction(i18n("Import CRLs")) : nullptr; if (importCerts || importCRLs) { menu.addSeparator(); } if (!signEncrypt && !decryptVerify && !importCerts && !importCRLs) { return; } menu.addAction(i18n("Cancel")); const QAction *const chosen = menu.exec(mapToGlobal(e->pos())); if (!chosen) { return; } if (chosen == signEncrypt) { d->createAndStart(files); } else if (chosen == decryptVerify) { d->createAndStart(files); } else if (chosen == importCerts) { d->createAndStart(files); } else if (chosen == importCRLs) { d->createAndStart(files); } e->accept(); } void MainWindow::readProperties(const KConfigGroup &cg) { qCDebug(KLEOPATRA_LOG); KXmlGuiWindow::readProperties(cg); savedGeometry = cg.readEntry("savedGeometry", QByteArray()); if (!savedGeometry.isEmpty()) { restoreGeometry(savedGeometry); } if (! cg.readEntry("hidden", false)) { show(); } } void MainWindow::saveProperties(KConfigGroup &cg) { qCDebug(KLEOPATRA_LOG); KXmlGuiWindow::saveProperties(cg); cg.writeEntry("hidden", isHidden()); if (isHidden()) { cg.writeEntry("savedGeometry", savedGeometry); } else { cg.writeEntry("savedGeometry", saveGeometry()); } } #include "moc_mainwindow.cpp" diff --git a/src/view/keytreeview.cpp b/src/view/keytreeview.cpp index 0c6f779ef..e592039d1 100644 --- a/src/view/keytreeview.cpp +++ b/src/view/keytreeview.cpp @@ -1,529 +1,687 @@ /* -*- mode: c++; c-basic-offset:4 -*- view/keytreeview.cpp This file is part of Kleopatra, the KDE keymanager Copyright (c) 2009 Klarälvdalens Datakonsult AB Kleopatra is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. Kleopatra is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA In addition, as a special exception, the copyright holders give permission to link the code of this program with any edition of the Qt library by Trolltech AS, Norway (or with modified versions of Qt that use the same license as Qt), and distribute linked combinations including the two. You must obey the GNU General Public License in all respects for all of the code used other than Qt. If you modify this file, you may extend this exception to your version of the file, but you are not obligated to do so. If you do not wish to do so, delete this exception statement from your version. */ #include #include "keytreeview.h" #include #include #include #include #include #include #include #include #include #include "kleopatra_debug.h" #include #include #include #include #include #include +#include +#include +#include +#include +#include #include #include +#include using namespace Kleo; using namespace GpgME; Q_DECLARE_METATYPE(GpgME::Key) namespace { class TreeView : public QTreeView { public: - explicit TreeView(QWidget *parent = nullptr) : QTreeView(parent) {} + explicit TreeView(QWidget *parent = nullptr) : QTreeView(parent) + { + header()->installEventFilter(this); + } QSize minimumSizeHint() const override { const QSize min = QTreeView::minimumSizeHint(); return QSize(min.width(), min.height() + 5 * fontMetrics().height()); } + +protected: + bool eventFilter(QObject *watched, QEvent *event) override + { + Q_UNUSED(watched); + if (event->type() == QEvent::ContextMenu) { + QContextMenuEvent *e = static_cast(event); + + if (!mHeaderPopup) { + mHeaderPopup = new QMenu(this); + mHeaderPopup->setTitle(i18n("View Columns")); + for (int i = 0; i < model()->columnCount(); ++i) { + QAction *tmp + = mHeaderPopup->addAction(model()->headerData(i, Qt::Horizontal).toString()); + tmp->setData(QVariant(i)); + tmp->setCheckable(true); + mColumnActions << tmp; + } + + connect(mHeaderPopup, &QMenu::triggered, this, [this] (QAction *action) { + if (action->isChecked()) { + showColumn(action->data().toInt()); + } else { + hideColumn(action->data().toInt()); + } + + KeyTreeView *tv = qobject_cast (parent()); + if (tv) { + tv->resizeColumns(); + } + }); + } + + foreach (QAction *action, mColumnActions) { + int column = action->data().toInt(); + action->setChecked(!isColumnHidden(column)); + } + + mHeaderPopup->popup(mapToGlobal(e->pos())); + return true; + } + + return false; + } + +private: + QMenu *mHeaderPopup = nullptr; + + QList mColumnActions; }; } // anon namespace KeyTreeView::KeyTreeView(QWidget *parent) : QWidget(parent), m_proxy(new KeyListSortFilterProxyModel(this)), m_additionalProxy(nullptr), m_view(new TreeView(this)), m_flatModel(nullptr), m_hierarchicalModel(nullptr), m_stringFilter(), m_keyFilter(), m_isHierarchical(true) { init(); } KeyTreeView::KeyTreeView(const KeyTreeView &other) : QWidget(nullptr), m_proxy(new KeyListSortFilterProxyModel(this)), m_additionalProxy(other.m_additionalProxy ? other.m_additionalProxy->clone() : nullptr), m_view(new TreeView(this)), m_flatModel(other.m_flatModel), m_hierarchicalModel(other.m_hierarchicalModel), m_stringFilter(other.m_stringFilter), m_keyFilter(other.m_keyFilter), + m_group(other.m_group), m_isHierarchical(other.m_isHierarchical) { init(); setColumnSizes(other.columnSizes()); setSortColumn(other.sortColumn(), other.sortOrder()); } -KeyTreeView::KeyTreeView(const QString &text, const std::shared_ptr &kf, AbstractKeyListSortFilterProxyModel *proxy, QWidget *parent) +KeyTreeView::KeyTreeView(const QString &text, const std::shared_ptr &kf, + AbstractKeyListSortFilterProxyModel *proxy, QWidget *parent, + const KConfigGroup &group) : QWidget(parent), m_proxy(new KeyListSortFilterProxyModel(this)), m_additionalProxy(proxy), m_view(new TreeView(this)), m_flatModel(nullptr), m_hierarchicalModel(nullptr), m_stringFilter(text), m_keyFilter(kf), - m_isHierarchical(true) + m_group(group), + m_isHierarchical(true), + m_onceResized(false) { init(); } void KeyTreeView::setColumnSizes(const std::vector &sizes) { if (sizes.empty()) { return; } Q_ASSERT(m_view); Q_ASSERT(m_view->header()); Q_ASSERT(qobject_cast(m_view->header()) == static_cast(m_view->header())); if (HeaderView *const hv = static_cast(m_view->header())) { hv->setSectionSizes(sizes); } } void KeyTreeView::setSortColumn(int sortColumn, Qt::SortOrder sortOrder) { Q_ASSERT(m_view); m_view->sortByColumn(sortColumn, sortOrder); } int KeyTreeView::sortColumn() const { Q_ASSERT(m_view); Q_ASSERT(m_view->header()); return m_view->header()->sortIndicatorSection(); } Qt::SortOrder KeyTreeView::sortOrder() const { Q_ASSERT(m_view); Q_ASSERT(m_view->header()); return m_view->header()->sortIndicatorOrder(); } std::vector KeyTreeView::columnSizes() const { Q_ASSERT(m_view); Q_ASSERT(m_view->header()); Q_ASSERT(qobject_cast(m_view->header()) == static_cast(m_view->header())); if (HeaderView *const hv = static_cast(m_view->header())) { return hv->sectionSizes(); } else { return std::vector(); } } void KeyTreeView::init() { KDAB_SET_OBJECT_NAME(m_proxy); KDAB_SET_OBJECT_NAME(m_view); + + if (!m_group.isValid()) { + m_group = KSharedConfig::openConfig()->group("KeyTreeView_default"); + } else { + // Reopen as non const + KConfig *conf = m_group.config(); + m_group = conf->group(m_group.name()); + } + if (m_additionalProxy && m_additionalProxy->objectName().isEmpty()) { KDAB_SET_OBJECT_NAME(m_additionalProxy); } - QLayout *layout = new QVBoxLayout(this); KDAB_SET_OBJECT_NAME(layout); layout->setContentsMargins(0, 0, 0, 0); layout->addWidget(m_view); HeaderView *headerView = new HeaderView(Qt::Horizontal); KDAB_SET_OBJECT_NAME(headerView); + headerView->installEventFilter(m_view); + headerView->setSectionsMovable(true); m_view->setHeader(headerView); m_view->setSelectionBehavior(QAbstractItemView::SelectRows); m_view->setSelectionMode(QAbstractItemView::ExtendedSelection); //m_view->setAlternatingRowColors( true ); m_view->setAllColumnsShowFocus(true); m_view->setSortingEnabled(true); if (model()) { if (m_additionalProxy) { m_additionalProxy->setSourceModel(model()); } else { m_proxy->setSourceModel(model()); } } if (m_additionalProxy) { m_proxy->setSourceModel(m_additionalProxy); if (!m_additionalProxy->parent()) { m_additionalProxy->setParent(this); } } m_proxy->setFilterFixedString(m_stringFilter); m_proxy->setKeyFilter(m_keyFilter); m_proxy->setSortCaseSensitivity(Qt::CaseInsensitive); KeyRearrangeColumnsProxyModel *rearangingModel = new KeyRearrangeColumnsProxyModel(this); rearangingModel->setSourceModel(m_proxy); - /* TODO: Make this configurable by the user. E.g. kdepim/src/todo/todoview.cpp */ rearangingModel->setSourceColumns(QVector() << KeyListModelInterface::PrettyName << KeyListModelInterface::PrettyEMail << KeyListModelInterface::Validity << KeyListModelInterface::ValidFrom << KeyListModelInterface::ValidUntil << KeyListModelInterface::TechnicalDetails - << KeyListModelInterface::KeyID); + << KeyListModelInterface::KeyID + << KeyListModelInterface::Fingerprint + << KeyListModelInterface::OwnerTrust + << KeyListModelInterface::Origin + << KeyListModelInterface::LastUpdate + << KeyListModelInterface::Issuer + << KeyListModelInterface::SerialNumber); m_view->setModel(rearangingModel); - std::vector defaultSizes; - defaultSizes.push_back(280); - defaultSizes.push_back(280); - defaultSizes.push_back(120); - defaultSizes.push_back(100); - defaultSizes.push_back(100); - defaultSizes.push_back(80); - defaultSizes.push_back(140); - setColumnSizes(defaultSizes); - /* Handle expansion state */ - const auto grp = KSharedConfig::openConfig()->group("KeyTreeView"); - m_expandedKeys = grp.readEntry("Expanded", QStringList()); + m_expandedKeys = m_group.readEntry("Expanded", QStringList()); connect(m_view, &QTreeView::expanded, this, [this] (const QModelIndex &index) { if (!index.isValid()) { return; } const auto &key = index.data(Kleo::KeyListModelInterface::KeyRole).value(); const auto fpr = QString::fromLatin1(key.primaryFingerprint()); if (m_expandedKeys.contains(fpr)) { return; } m_expandedKeys << fpr; - auto grp = KSharedConfig::openConfig()->group("KeyTreeView"); - grp.writeEntry("Expanded", m_expandedKeys); + m_group.writeEntry("Expanded", m_expandedKeys); }); connect(m_view, &QTreeView::collapsed, this, [this] (const QModelIndex &index) { if (!index.isValid()) { return; } const auto &key = index.data(Kleo::KeyListModelInterface::KeyRole).value(); m_expandedKeys.removeAll(QString::fromLatin1(key.primaryFingerprint())); - auto grp = KSharedConfig::openConfig()->group("KeyTreeView"); - grp.writeEntry("Expanded", m_expandedKeys); + m_group.writeEntry("Expanded", m_expandedKeys); }); connect(KeyCache::instance().get(), &KeyCache::keysMayHaveChanged, this, [this] () { /* We use a single shot timer here to ensure that the keysMayHaveChanged * handlers are all handled before we restore the expand state so that * the model is already populated. */ - QTimer::singleShot(0, [this] () { restoreExpandState(); }); + QTimer::singleShot(0, [this] () { + restoreExpandState(); + if (!m_onceResized) { + m_onceResized = true; + resizeColumns(); + } + }); }); + + resizeColumns(); + restoreLayout(); } void KeyTreeView::restoreExpandState() { if (!KeyCache::instance()->initialized()) { qCWarning(KLEOPATRA_LOG) << "Restore expand state before keycache available. Aborting."; return; } for (const auto &fpr: m_expandedKeys) { const KeyListModelInterface *km = dynamic_cast (m_view->model()); if (!km) { qCWarning(KLEOPATRA_LOG) << "invalid model"; return; } const auto key = KeyCache::instance()->findByFingerprint(fpr.toLatin1().constData()); if (key.isNull()) { qCDebug(KLEOPATRA_LOG) << "Cannot find:" << fpr << "anymore in cache"; m_expandedKeys.removeAll(fpr); return; } const auto idx = km->index(key); if (!idx.isValid()) { qCDebug(KLEOPATRA_LOG) << "Cannot find:" << fpr << "anymore in model"; m_expandedKeys.removeAll(fpr); return; } m_view->expand(idx); } } -KeyTreeView::~KeyTreeView() {} +void KeyTreeView::saveLayout() +{ + QHeaderView *header = m_view->header(); + + QVariantList columnVisibility; + QVariantList columnOrder; + QVariantList columnWidths; + const int headerCount = header->count(); + columnVisibility.reserve(headerCount); + columnWidths.reserve(headerCount); + columnOrder.reserve(headerCount); + for (int i = 0; i < headerCount; ++i) { + columnVisibility << QVariant(!m_view->isColumnHidden(i)); + columnWidths << QVariant(header->sectionSize(i)); + columnOrder << QVariant(header->visualIndex(i)); + } + + m_group.writeEntry("ColumnVisibility", columnVisibility); + m_group.writeEntry("ColumnOrder", columnOrder); + m_group.writeEntry("ColumnWidths", columnWidths); + + m_group.writeEntry("SortAscending", (int)header->sortIndicatorOrder()); + if (header->isSortIndicatorShown()) { + m_group.writeEntry("SortColumn", header->sortIndicatorSection()); + } else { + m_group.writeEntry("SortColumn", -1); + } +} + +void KeyTreeView::restoreLayout() +{ + QHeaderView *header = m_view->header(); + + QVariantList columnVisibility = m_group.readEntry("ColumnVisibility", QVariantList()); + QVariantList columnOrder = m_group.readEntry("ColumnOrder", QVariantList()); + QVariantList columnWidths = m_group.readEntry("ColumnWidths", QVariantList()); + + if (columnVisibility.isEmpty()) { + // if config is empty then use default settings + // The numbers have to be in line with the order in + // setsSourceColumns above + m_view->hideColumn(5); + + for (int i = 7; i < m_view->model()->columnCount(); ++i) { + m_view->hideColumn(i); + } + if (KeyCache::instance()->initialized()) { + QTimer::singleShot(0, this, &KeyTreeView::resizeColumns); + } + } else { + for (int i = 0; + i < header->count() + && i < columnOrder.size() + && i < columnWidths.size() + && i < columnVisibility.size(); + ++i) { + bool visible = columnVisibility[i].toBool(); + int width = columnWidths[i].toInt(); + int order = columnOrder[i].toInt(); + + header->resizeSection(i, width ? width : 100); + header->moveSection(header->visualIndex(i), order); + if (!visible) { + m_view->hideColumn(i); + } + } + m_onceResized = true; + } + + int sortOrder = m_group.readEntry("SortAscending", (int)Qt::AscendingOrder); + int sortColumn = m_group.readEntry("SortColumn", -1); + if (sortColumn >= 0) { + m_view->sortByColumn(sortColumn, (Qt::SortOrder)sortOrder); + } +} + +KeyTreeView::~KeyTreeView() +{ + saveLayout(); +} static QAbstractProxyModel *find_last_proxy(QAbstractProxyModel *pm) { Q_ASSERT(pm); while (QAbstractProxyModel *const sm = qobject_cast(pm->sourceModel())) { pm = sm; } return pm; } void KeyTreeView::setFlatModel(AbstractKeyListModel *model) { if (model == m_flatModel) { return; } m_flatModel = model; if (!m_isHierarchical) // TODO: this fails when called after setHierarchicalView( false )... { find_last_proxy(m_proxy)->setSourceModel(model); } } void KeyTreeView::setHierarchicalModel(AbstractKeyListModel *model) { if (model == m_hierarchicalModel) { return; } m_hierarchicalModel = model; if (m_isHierarchical) { find_last_proxy(m_proxy)->setSourceModel(model); m_view->expandAll(); for (int column = 0; column < m_view->header()->count(); ++column) { m_view->header()->resizeSection(column, qMax(m_view->header()->sectionSize(column), m_view->header()->sectionSizeHint(column))); } } } void KeyTreeView::setStringFilter(const QString &filter) { if (filter == m_stringFilter) { return; } m_stringFilter = filter; m_proxy->setFilterFixedString(filter); Q_EMIT stringFilterChanged(filter); } void KeyTreeView::setKeyFilter(const std::shared_ptr &filter) { if (filter == m_keyFilter || (filter && m_keyFilter && filter->id() == m_keyFilter->id())) { return; } m_keyFilter = filter; m_proxy->setKeyFilter(filter); Q_EMIT keyFilterChanged(filter); } static QItemSelection itemSelectionFromKeys(const std::vector &keys, const KeyListSortFilterProxyModel &proxy) { QItemSelection result; for (const Key &key : keys) { const QModelIndex mi = proxy.index(key); if (mi.isValid()) { result.merge(QItemSelection(mi, mi), QItemSelectionModel::Select); } } return result; } void KeyTreeView::selectKeys(const std::vector &keys) { m_view->selectionModel()->select(itemSelectionFromKeys(keys, *m_proxy), QItemSelectionModel::ClearAndSelect | QItemSelectionModel::Rows); } std::vector KeyTreeView::selectedKeys() const { return m_proxy->keys(m_view->selectionModel()->selectedRows()); } void KeyTreeView::setHierarchicalView(bool on) { if (on == m_isHierarchical) { return; } if (on && !hierarchicalModel()) { qCWarning(KLEOPATRA_LOG) << "hierarchical view requested, but no hierarchical model set"; return; } if (!on && !flatModel()) { qCWarning(KLEOPATRA_LOG) << "flat view requested, but no flat model set"; return; } const std::vector selectedKeys = m_proxy->keys(m_view->selectionModel()->selectedRows()); const Key currentKey = m_proxy->key(m_view->currentIndex()); m_isHierarchical = on; find_last_proxy(m_proxy)->setSourceModel(model()); if (on) { m_view->expandAll(); } selectKeys(selectedKeys); if (!currentKey.isNull()) { const QModelIndex currentIndex = m_proxy->index(currentKey); if (currentIndex.isValid()) { m_view->selectionModel()->setCurrentIndex(m_proxy->index(currentKey), QItemSelectionModel::NoUpdate); m_view->scrollTo(currentIndex); } } Q_EMIT hierarchicalChanged(on); } void KeyTreeView::setKeys(const std::vector &keys) { std::vector sorted = keys; _detail::sort_by_fpr(sorted); _detail::remove_duplicates_by_fpr(sorted); m_keys = sorted; if (m_flatModel) { m_flatModel->setKeys(sorted); } if (m_hierarchicalModel) { m_hierarchicalModel->setKeys(sorted); } - if (!sorted.empty()) - if (QHeaderView *const hv = m_view ? m_view->header() : nullptr) { - hv->resizeSections(QHeaderView::ResizeToContents); - } } void KeyTreeView::addKeysImpl(const std::vector &keys, bool select) { if (keys.empty()) { return; } if (m_keys.empty()) { setKeys(keys); return; } std::vector sorted = keys; _detail::sort_by_fpr(sorted); _detail::remove_duplicates_by_fpr(sorted); std::vector newKeys = _detail::union_by_fpr(sorted, m_keys); m_keys.swap(newKeys); if (m_flatModel) { m_flatModel->addKeys(sorted); } if (m_hierarchicalModel) { m_hierarchicalModel->addKeys(sorted); } if (select) { selectKeys(sorted); } } void KeyTreeView::addKeysSelected(const std::vector &keys) { addKeysImpl(keys, true); } void KeyTreeView::addKeysUnselected(const std::vector &keys) { addKeysImpl(keys, false); } void KeyTreeView::removeKeys(const std::vector &keys) { if (keys.empty()) { return; } std::vector sorted = keys; _detail::sort_by_fpr(sorted); _detail::remove_duplicates_by_fpr(sorted); std::vector newKeys; newKeys.reserve(m_keys.size()); std::set_difference(m_keys.begin(), m_keys.end(), sorted.begin(), sorted.end(), std::back_inserter(newKeys), _detail::ByFingerprint()); m_keys.swap(newKeys); if (m_flatModel) { std::for_each(sorted.cbegin(), sorted.cend(), [this](const Key &key) { m_flatModel->removeKey(key); }); } if (m_hierarchicalModel) { std::for_each(sorted.cbegin(), sorted.cend(), [this](const Key &key) { m_hierarchicalModel->removeKey(key); }); } } static const struct { const char *signal; const char *slot; } connections[] = { { SIGNAL(stringFilterChanged(QString)), SLOT(setStringFilter(QString)) }, { SIGNAL(keyFilterChanged(std::shared_ptr)), SLOT(setKeyFilter(std::shared_ptr)) }, }; static const unsigned int numConnections = sizeof connections / sizeof * connections; void KeyTreeView::disconnectSearchBar(const QObject *bar) { for (unsigned int i = 0; i < numConnections; ++i) { disconnect(this, connections[i].signal, bar, connections[i].slot); disconnect(bar, connections[i].signal, this, connections[i].slot); } } bool KeyTreeView::connectSearchBar(const QObject *bar) { for (unsigned int i = 0; i < numConnections; ++i) if (!connect(this, connections[i].signal, bar, connections[i].slot) || !connect(bar, connections[i].signal, this, connections[i].slot)) { return false; } return true; } +void KeyTreeView::resizeColumns() +{ + m_view->setColumnWidth(KeyListModelInterface::PrettyName, 260); + m_view->setColumnWidth(KeyListModelInterface::PrettyEMail, 260); + + for (int i = 2; i < m_view->model()->columnCount(); ++i) { + m_view->resizeColumnToContents(i); + } +} diff --git a/src/view/keytreeview.h b/src/view/keytreeview.h index 3e64a0a39..d5f88a793 100644 --- a/src/view/keytreeview.h +++ b/src/view/keytreeview.h @@ -1,172 +1,182 @@ /* -*- mode: c++; c-basic-offset:4 -*- view/keytreeview.h This file is part of Kleopatra, the KDE keymanager Copyright (c) 2009 Klarälvdalens Datakonsult AB Kleopatra is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. Kleopatra is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA In addition, as a special exception, the copyright holders give permission to link the code of this program with any edition of the Qt library by Trolltech AS, Norway (or with modified versions of Qt that use the same license as Qt), and distribute linked combinations including the two. You must obey the GNU General Public License in all respects for all of the code used other than Qt. If you modify this file, you may extend this exception to your version of the file, but you are not obligated to do so. If you do not wish to do so, delete this exception statement from your version. */ #ifndef __KLEOPATRA_VIEW_KEYTREEVIEW_H__ #define __KLEOPATRA_VIEW_KEYTREEVIEW_H__ #include #include #include +#include #include #include #include +#include + class QTreeView; namespace Kleo { class KeyFilter; class AbstractKeyListModel; class AbstractKeyListSortFilterProxyModel; class KeyListSortFilterProxyModel; class KeyTreeView : public QWidget { Q_OBJECT public: explicit KeyTreeView(QWidget *parent = nullptr); KeyTreeView(const QString &stringFilter, const std::shared_ptr &keyFilter, - AbstractKeyListSortFilterProxyModel *additionalProxy, QWidget *parent); + AbstractKeyListSortFilterProxyModel *additionalProxy, QWidget *parent, + const KConfigGroup &group = KConfigGroup()); ~KeyTreeView(); QTreeView *view() const { return m_view; } AbstractKeyListModel *model() const { return m_isHierarchical ? hierarchicalModel() : flatModel(); } AbstractKeyListModel *flatModel() const { return m_flatModel; } AbstractKeyListModel *hierarchicalModel() const { return m_hierarchicalModel; } void setFlatModel(AbstractKeyListModel *model); void setHierarchicalModel(AbstractKeyListModel *model); void setKeys(const std::vector &keys); const std::vector &keys() const { return m_keys; } void selectKeys(const std::vector &keys); std::vector selectedKeys() const; void addKeysUnselected(const std::vector &keys); void addKeysSelected(const std::vector &keys); void removeKeys(const std::vector &keys); #if 0 void setToolTipOptions(int options); int toolTipOptions() const; #endif QString stringFilter() const { return m_stringFilter; } const std::shared_ptr &keyFilter() const { return m_keyFilter; } bool isHierarchicalView() const { return m_isHierarchical; } void setColumnSizes(const std::vector &sizes); std::vector columnSizes() const; void setSortColumn(int sortColumn, Qt::SortOrder sortOrder); int sortColumn() const; Qt::SortOrder sortOrder() const; virtual KeyTreeView *clone() const { return new KeyTreeView(*this); } void disconnectSearchBar(const QObject *bar); bool connectSearchBar(const QObject *bar); + void resizeColumns(); public Q_SLOTS: virtual void setStringFilter(const QString &text); virtual void setKeyFilter(const std::shared_ptr &filter); virtual void setHierarchicalView(bool on); Q_SIGNALS: void stringFilterChanged(const QString &filter); void keyFilterChanged(const std::shared_ptr &filter); void hierarchicalChanged(bool on); protected: KeyTreeView(const KeyTreeView &); private: void init(); void addKeysImpl(const std::vector &, bool); void restoreExpandState(); + void saveLayout(); + void restoreLayout(); private: std::vector m_keys; KeyListSortFilterProxyModel *m_proxy; AbstractKeyListSortFilterProxyModel *m_additionalProxy; QTreeView *m_view; AbstractKeyListModel *m_flatModel; AbstractKeyListModel *m_hierarchicalModel; QString m_stringFilter; std::shared_ptr m_keyFilter; QStringList m_expandedKeys; + KConfigGroup m_group; + bool m_isHierarchical : 1; + bool m_onceResized : 1; }; } #endif // __KLEOPATRA_VIEW_KEYTREEVIEW_H__ diff --git a/src/view/tabwidget.cpp b/src/view/tabwidget.cpp index 86c1220fa..db4902810 100644 --- a/src/view/tabwidget.cpp +++ b/src/view/tabwidget.cpp @@ -1,966 +1,969 @@ /* -*- mode: c++; c-basic-offset:4 -*- view/tabwidget.cpp This file is part of Kleopatra, the KDE keymanager Copyright (c) 2007 Klarälvdalens Datakonsult AB Kleopatra is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. Kleopatra is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA In addition, as a special exception, the copyright holders give permission to link the code of this program with any edition of the Qt library by Trolltech AS, Norway (or with modified versions of Qt that use the same license as Qt), and distribute linked combinations including the two. You must obey the GNU General Public License in all respects for all of the code used other than Qt. If you modify this file, you may extend this exception to your version of the file, but you are not obligated to do so. If you do not wish to do so, delete this exception statement from your version. */ #include #include "tabwidget.h" #include "keytreeview.h" #include #include #include #include #include #include #include #include #include #include +#include #include #include #include #include #include #include #include #include #include #include #include #include using namespace Kleo; using namespace GpgME; namespace { class Page : public Kleo::KeyTreeView { Q_OBJECT Page(const Page &other); public: - Page(const QString &title, const QString &id, const QString &text, AbstractKeyListSortFilterProxyModel *proxy = nullptr, const QString &toolTip = QString(), QWidget *parent = nullptr); + Page(const QString &title, const QString &id, const QString &text, AbstractKeyListSortFilterProxyModel *proxy = nullptr, const QString &toolTip = QString(), QWidget *parent = nullptr, const KConfigGroup &group = KConfigGroup()); Page(const KConfigGroup &group, QWidget *parent = nullptr); ~Page(); void setTemporary(bool temporary); bool isTemporary() const { return m_isTemporary; } void setHierarchicalView(bool hierarchical) override; void setStringFilter(const QString &filter) override; void setKeyFilter(const std::shared_ptr &filter) override; QString title() const { return m_title.isEmpty() && keyFilter() ? keyFilter()->name() : m_title; } void setTitle(const QString &title); QString toolTip() const { return m_toolTip.isEmpty() ? title() : m_toolTip; } // not used void setToolTip(const QString &tip); bool canBeClosed() const { return m_canBeClosed; } bool canBeRenamed() const { return m_canBeRenamed; } bool canChangeStringFilter() const { return m_canChangeStringFilter; } bool canChangeKeyFilter() const { return m_canChangeKeyFilter && !m_isTemporary; } bool canChangeHierarchical() const { return m_canChangeHierarchical; } void saveTo(KConfigGroup &group) const; Page *clone() const override { return new Page(*this); } void liftAllRestrictions() { m_canBeClosed = m_canBeRenamed = m_canChangeStringFilter = m_canChangeKeyFilter = m_canChangeHierarchical = true; } Q_SIGNALS: void titleChanged(const QString &title); private: void init(); private: QString m_title; QString m_toolTip; bool m_isTemporary : 1; bool m_canBeClosed : 1; bool m_canBeRenamed : 1; bool m_canChangeStringFilter : 1; bool m_canChangeKeyFilter : 1; bool m_canChangeHierarchical : 1; }; } // anon namespace Page::Page(const Page &other) : KeyTreeView(other), m_title(other.m_title), m_toolTip(other.m_toolTip), m_isTemporary(other.m_isTemporary), m_canBeClosed(other.m_canBeClosed), m_canBeRenamed(other.m_canBeRenamed), m_canChangeStringFilter(other.m_canChangeStringFilter), m_canChangeKeyFilter(other.m_canChangeKeyFilter), m_canChangeHierarchical(other.m_canChangeHierarchical) { init(); } -Page::Page(const QString &title, const QString &id, const QString &text, AbstractKeyListSortFilterProxyModel *proxy, const QString &toolTip, QWidget *parent) - : KeyTreeView(text, KeyFilterManager::instance()->keyFilterByID(id), proxy, parent), +Page::Page(const QString &title, const QString &id, const QString &text, AbstractKeyListSortFilterProxyModel *proxy, const QString &toolTip, QWidget *parent, + const KConfigGroup &group) + : KeyTreeView(text, KeyFilterManager::instance()->keyFilterByID(id), proxy, parent, group), m_title(title), m_toolTip(toolTip), m_isTemporary(false), m_canBeClosed(true), m_canBeRenamed(true), m_canChangeStringFilter(true), m_canChangeKeyFilter(true), m_canChangeHierarchical(true) { init(); } static const char TITLE_ENTRY[] = "title"; static const char STRING_FILTER_ENTRY[] = "string-filter"; static const char KEY_FILTER_ENTRY[] = "key-filter"; static const char HIERARCHICAL_VIEW_ENTRY[] = "hierarchical-view"; static const char COLUMN_SIZES[] = "column-sizes"; static const char SORT_COLUMN[] = "sort-column"; static const char SORT_DESCENDING[] = "sort-descending"; Page::Page(const KConfigGroup &group, QWidget *parent) : KeyTreeView(group.readEntry(STRING_FILTER_ENTRY), KeyFilterManager::instance()->keyFilterByID(group.readEntry(KEY_FILTER_ENTRY)), - nullptr, parent), + nullptr, parent, group), m_title(group.readEntry(TITLE_ENTRY)), m_toolTip(), m_isTemporary(false), m_canBeClosed(!group.isImmutable()), m_canBeRenamed(!group.isEntryImmutable(TITLE_ENTRY)), m_canChangeStringFilter(!group.isEntryImmutable(STRING_FILTER_ENTRY)), m_canChangeKeyFilter(!group.isEntryImmutable(KEY_FILTER_ENTRY)), m_canChangeHierarchical(!group.isEntryImmutable(HIERARCHICAL_VIEW_ENTRY)) { init(); setHierarchicalView(group.readEntry(HIERARCHICAL_VIEW_ENTRY, true)); const QList settings = group.readEntry(COLUMN_SIZES, QList()); std::vector sizes; sizes.reserve(settings.size()); std::copy(settings.cbegin(), settings.cend(), std::back_inserter(sizes)); setColumnSizes(sizes); setSortColumn(group.readEntry(SORT_COLUMN, 0), group.readEntry(SORT_DESCENDING, true) ? Qt::DescendingOrder : Qt::AscendingOrder); } void Page::init() { } Page::~Page() {} void Page::saveTo(KConfigGroup &group) const { group.writeEntry(TITLE_ENTRY, m_title); group.writeEntry(STRING_FILTER_ENTRY, stringFilter()); group.writeEntry(KEY_FILTER_ENTRY, keyFilter() ? keyFilter()->id() : QString()); group.writeEntry(HIERARCHICAL_VIEW_ENTRY, isHierarchicalView()); QList settings; const auto sizes = columnSizes(); settings.reserve(sizes.size()); std::copy(sizes.cbegin(), sizes.cend(), std::back_inserter(settings)); group.writeEntry(COLUMN_SIZES, settings); group.writeEntry(SORT_COLUMN, sortColumn()); group.writeEntry(SORT_DESCENDING, sortOrder() == Qt::DescendingOrder); } void Page::setStringFilter(const QString &filter) { if (!m_canChangeStringFilter) { return; } KeyTreeView::setStringFilter(filter); } void Page::setKeyFilter(const std::shared_ptr &filter) { if (!canChangeKeyFilter()) { return; } const QString oldTitle = title(); KeyTreeView::setKeyFilter(filter); const QString newTitle = title(); if (oldTitle != newTitle) { Q_EMIT titleChanged(newTitle); } } void Page::setTitle(const QString &t) { if (t == m_title) { return; } if (!m_canBeRenamed) { return; } const QString oldTitle = title(); m_title = t; const QString newTitle = title(); if (oldTitle != newTitle) { Q_EMIT titleChanged(newTitle); } } #if 0 // not used void Page::setToolTip(const QString &tip) { if (tip == m_toolTip) { return; } if (!m_canBeRenamed) { return; } const QString oldTip = toolTip(); m_toolTip = tip; const QString newTip = toolTip(); if (oldTip != newTip) { Q_EMIT titleChanged(title()); } } #endif void Page::setHierarchicalView(bool on) { if (!m_canChangeHierarchical) { return; } KeyTreeView::setHierarchicalView(on); } void Page::setTemporary(bool on) { if (on == m_isTemporary) { return; } m_isTemporary = on; if (on) { setKeyFilter(std::shared_ptr()); } } // // // TabWidget // // class TabWidget::Private { friend class ::Kleo::TabWidget; TabWidget *const q; public: explicit Private(TabWidget *qq); ~Private() {} private: void slotContextMenu(const QPoint &p); void currentIndexChanged(int index); void slotPageTitleChanged(const QString &title); void slotPageKeyFilterChanged(const std::shared_ptr &filter); void slotPageStringFilterChanged(const QString &filter); void slotPageHierarchyChanged(bool on); #ifndef QT_NO_INPUTDIALOG void slotRenameCurrentTab() { renamePage(currentPage()); } #endif // QT_NO_INPUTDIALOG void slotNewTab(); void slotDuplicateCurrentTab() { duplicatePage(currentPage()); } void slotCloseCurrentTab() { closePage(currentPage()); } void slotMoveCurrentTabLeft() { movePageLeft(currentPage()); } void slotMoveCurrentTabRight() { movePageRight(currentPage()); } void slotToggleHierarchicalView(bool on) { toggleHierarchicalView(currentPage(), on); } void slotExpandAll() { expandAll(currentPage()); } void slotCollapseAll() { collapseAll(currentPage()); } #ifndef QT_NO_INPUTDIALOG void renamePage(Page *page); #endif void duplicatePage(Page *page); void closePage(Page *page); void movePageLeft(Page *page); void movePageRight(Page *page); void toggleHierarchicalView(Page *page, bool on); void expandAll(Page *page); void collapseAll(Page *page); void enableDisableCurrentPageActions(); void enableDisablePageActions(QAction *actions[], const Page *page); Page *currentPage() const { Q_ASSERT(!tabWidget.currentWidget() || qobject_cast(tabWidget.currentWidget())); return static_cast(tabWidget.currentWidget()); } Page *page(unsigned int idx) const { Q_ASSERT(!tabWidget.widget(idx) || qobject_cast(tabWidget.widget(idx))); return static_cast(tabWidget.widget(idx)); } Page *senderPage() const { QObject *const sender = q->sender(); Q_ASSERT(!sender || qobject_cast(sender)); return static_cast(sender); } bool isSenderCurrentPage() const { Page *const sp = senderPage(); return sp && sp == currentPage(); } QTreeView *addView(Page *page, Page *columnReference); void setCornerAction(QAction *action, Qt::Corner corner); private: AbstractKeyListModel *flatModel; AbstractKeyListModel *hierarchicalModel; QTabWidget tabWidget; QVBoxLayout layout; enum { Rename, Duplicate, Close, MoveLeft, MoveRight, Hierarchical, ExpandAll, CollapseAll, NumPageActions }; QAction *newAction; QAction *currentPageActions[NumPageActions]; QAction *otherPageActions[NumPageActions]; bool actionsCreated; }; TabWidget::Private::Private(TabWidget *qq) : q(qq), flatModel(nullptr), hierarchicalModel(nullptr), tabWidget(q), layout(q), actionsCreated(false) { KDAB_SET_OBJECT_NAME(tabWidget); KDAB_SET_OBJECT_NAME(layout); layout.setContentsMargins(0, 0, 0, 0); layout.addWidget(&tabWidget); tabWidget.tabBar()->hide(); tabWidget.setMovable(true); tabWidget.tabBar()->setContextMenuPolicy(Qt::CustomContextMenu); connect(&tabWidget, SIGNAL(currentChanged(int)), q, SLOT(currentIndexChanged(int))); connect(tabWidget.tabBar(), &QWidget::customContextMenuRequested, q, [this](const QPoint & p) { slotContextMenu(p); }); } void TabWidget::Private::slotContextMenu(const QPoint &p) { const int tabUnderPos = tabWidget.tabBar()->tabAt(p); Page *const contextMenuPage = static_cast(tabWidget.widget(tabUnderPos)); const Page *const current = currentPage(); QAction **const actions = contextMenuPage == current ? currentPageActions : otherPageActions; enableDisablePageActions(actions, contextMenuPage); QMenu menu; menu.addAction(actions[Rename]); menu.addSeparator(); menu.addAction(newAction); menu.addAction(actions[Duplicate]); menu.addSeparator(); menu.addAction(actions[MoveLeft]); menu.addAction(actions[MoveRight]); menu.addSeparator(); menu.addAction(actions[Close]); const QAction *const action = menu.exec(tabWidget.tabBar()->mapToGlobal(p)); if (contextMenuPage == current || action == newAction) { return; // performed through signal/slot connections... } #ifndef QT_NO_INPUTDIALOG if (action == otherPageActions[Rename]) { renamePage(contextMenuPage); } #endif // QT_NO_INPUTDIALOG else if (action == otherPageActions[Duplicate]) { duplicatePage(contextMenuPage); } else if (action == otherPageActions[Close]) { closePage(contextMenuPage); } else if (action == otherPageActions[MoveLeft]) { movePageLeft(contextMenuPage); } else if (action == otherPageActions[MoveRight]) { movePageRight(contextMenuPage); } } void TabWidget::Private::currentIndexChanged(int index) { const Page *const page = this->page(index); Q_EMIT q->currentViewChanged(page ? page->view() : nullptr); Q_EMIT q->keyFilterChanged(page ? page->keyFilter() : std::shared_ptr()); Q_EMIT q->stringFilterChanged(page ? page->stringFilter() : QString()); enableDisableCurrentPageActions(); } void TabWidget::Private::enableDisableCurrentPageActions() { const Page *const page = currentPage(); Q_EMIT q->enableChangeStringFilter(page && page->canChangeStringFilter()); Q_EMIT q->enableChangeKeyFilter(page && page->canChangeKeyFilter()); enableDisablePageActions(currentPageActions, page); } void TabWidget::Private::enableDisablePageActions(QAction *actions[], const Page *p) { actions[Rename] ->setEnabled(p && p->canBeRenamed()); actions[Duplicate] ->setEnabled(p); actions[Close] ->setEnabled(p && p->canBeClosed() && tabWidget.count() > 1); actions[MoveLeft] ->setEnabled(p && tabWidget.indexOf(const_cast(p)) != 0); actions[MoveRight] ->setEnabled(p && tabWidget.indexOf(const_cast(p)) != tabWidget.count() - 1); actions[Hierarchical]->setEnabled(p && p->canChangeHierarchical()); actions[Hierarchical]->setChecked(p && p->isHierarchicalView()); actions[ExpandAll] ->setEnabled(p && p->isHierarchicalView()); actions[CollapseAll] ->setEnabled(p && p->isHierarchicalView()); if (tabWidget.count() < 2) { tabWidget.tabBar()->hide(); } else { tabWidget.tabBar()->show(); } } void TabWidget::Private::slotPageTitleChanged(const QString &) { if (Page *const page = senderPage()) { const int idx = tabWidget.indexOf(page); tabWidget.setTabText(idx, page->title()); tabWidget.setTabToolTip(idx, page->toolTip()); } } void TabWidget::Private::slotPageKeyFilterChanged(const std::shared_ptr &kf) { if (isSenderCurrentPage()) { Q_EMIT q->keyFilterChanged(kf); } } void TabWidget::Private::slotPageStringFilterChanged(const QString &filter) { if (isSenderCurrentPage()) { Q_EMIT q->stringFilterChanged(filter); } } void TabWidget::Private::slotPageHierarchyChanged(bool) { enableDisableCurrentPageActions(); } void TabWidget::Private::slotNewTab() { - Page *page = new Page(QString(), QStringLiteral("all-certificates"), QString()); + const KConfigGroup group = KSharedConfig::openConfig()->group(QString().sprintf("View #%u", tabWidget.count())); + Page *page = new Page(QString(), QStringLiteral("all-certificates"), QString(), nullptr, QString(), nullptr, group); addView(page, currentPage()); tabWidget.setCurrentIndex(tabWidget.count() - 1); } void TabWidget::Private::renamePage(Page *page) { if (!page) { return; } bool ok; const QString text = QInputDialog::getText(q, i18n("Rename Tab"), i18n("New tab title:"), QLineEdit::Normal, page->title(), &ok); if (!ok) { return; } page->setTitle(text); } void TabWidget::Private::duplicatePage(Page *page) { if (!page) { return; } Page *const clone = page->clone(); Q_ASSERT(clone); clone->liftAllRestrictions(); addView(clone, page); } void TabWidget::Private::closePage(Page *page) { if (!page || !page->canBeClosed() || tabWidget.count() <= 1) { return; } Q_EMIT q->viewAboutToBeRemoved(page->view()); tabWidget.removeTab(tabWidget.indexOf(page)); enableDisableCurrentPageActions(); } void TabWidget::Private::movePageLeft(Page *page) { if (!page) { return; } const int idx = tabWidget.indexOf(page); if (idx <= 0) { return; } tabWidget.tabBar()->moveTab(idx, idx - 1); enableDisableCurrentPageActions(); } void TabWidget::Private::movePageRight(Page *page) { if (!page) { return; } const int idx = tabWidget.indexOf(page); if (idx < 0 || idx >= tabWidget.count() - 1) { return; } tabWidget.tabBar()->moveTab(idx, idx + 1); enableDisableCurrentPageActions(); } void TabWidget::Private::toggleHierarchicalView(Page *page, bool on) { if (!page) { return; } page->setHierarchicalView(on); } void TabWidget::Private::expandAll(Page *page) { if (!page || !page->view()) { return; } page->view()->expandAll(); } void TabWidget::Private::collapseAll(Page *page) { if (!page || !page->view()) { return; } page->view()->collapseAll(); } TabWidget::TabWidget(QWidget *p, Qt::WindowFlags f) : QWidget(p, f), d(new Private(this)) { } TabWidget::~TabWidget() {} void TabWidget::setFlatModel(AbstractKeyListModel *model) { if (model == d->flatModel) { return; } d->flatModel = model; for (unsigned int i = 0, end = count(); i != end; ++i) if (Page *const page = d->page(i)) { page->setFlatModel(model); } } AbstractKeyListModel *TabWidget::flatModel() const { return d->flatModel; } void TabWidget::setHierarchicalModel(AbstractKeyListModel *model) { if (model == d->hierarchicalModel) { return; } d->hierarchicalModel = model; for (unsigned int i = 0, end = count(); i != end; ++i) if (Page *const page = d->page(i)) { page->setHierarchicalModel(model); } } AbstractKeyListModel *TabWidget::hierarchicalModel() const { return d->hierarchicalModel; } void TabWidget::Private::setCornerAction(QAction *action, Qt::Corner corner) { if (!action) { return; } QToolButton *b = new QToolButton; b->setDefaultAction(action); tabWidget.setCornerWidget(b, corner); } void TabWidget::setStringFilter(const QString &filter) { if (Page *const page = d->currentPage()) { page->setStringFilter(filter); } } void TabWidget::setKeyFilter(const std::shared_ptr &filter) { if (Page *const page = d->currentPage()) { page->setKeyFilter(filter); } } std::vector TabWidget::views() const { std::vector result; const unsigned int N = count(); result.reserve(N); for (unsigned int i = 0; i != N; ++i) if (const Page *const p = d->page(i)) { result.push_back(p->view()); } return result; } QAbstractItemView *TabWidget::currentView() const { if (Page *const page = d->currentPage()) { return page->view(); } else { return nullptr; } } KeyListModelInterface *TabWidget::currentModel() const { const QAbstractItemView *const view = currentView(); if (!view) { return nullptr; } QAbstractProxyModel *const proxy = qobject_cast(view->model()); if (!proxy) { return nullptr; } return dynamic_cast(proxy); } unsigned int TabWidget::count() const { return d->tabWidget.count(); } void TabWidget::setMultiSelection(bool on) { for (unsigned int i = 0, end = count(); i != end; ++i) if (const Page *const p = d->page(i)) if (QTreeView *const view = p->view()) { view->setSelectionMode(on ? QAbstractItemView::ExtendedSelection : QAbstractItemView::SingleSelection); } } void TabWidget::createActions(KActionCollection *coll) { if (!coll) { return; } const action_data actionDataNew = { "window_new_tab", i18n("New Tab"), i18n("Open a new tab"), "tab-new-background", this, SLOT(slotNewTab()), QStringLiteral("CTRL+SHIFT+N"), false, true }; d->newAction = make_action_from_data(actionDataNew, coll); struct action_data actionData[] = { { "window_rename_tab", i18n("Rename Tab..."), i18n("Rename this tab"), "edit-rename", this, SLOT(slotRenameCurrentTab()), QStringLiteral("CTRL+SHIFT+R"), false, false }, { "window_duplicate_tab", i18n("Duplicate Tab"), i18n("Duplicate this tab"), "tab-duplicate", this, SLOT(slotDuplicateCurrentTab()), QStringLiteral("CTRL+SHIFT+D"), false, true }, { "window_close_tab", i18n("Close Tab"), i18n("Close this tab"), "tab-close", this, SLOT(slotCloseCurrentTab()), QStringLiteral("CTRL+SHIFT+W"), false, false }, // ### CTRL-W when available { "window_move_tab_left", i18n("Move Tab Left"), i18n("Move this tab left"), nullptr, this, SLOT(slotMoveCurrentTabLeft()), QStringLiteral("CTRL+SHIFT+LEFT"), false, false }, { "window_move_tab_right", i18n("Move Tab Right"), i18n("Move this tab right"), nullptr, this, SLOT(slotMoveCurrentTabRight()), QStringLiteral("CTRL+SHIFT+RIGHT"), false, false }, { "window_view_hierarchical", i18n("Hierarchical Certificate List"), QString(), nullptr, this, SLOT(slotToggleHierarchicalView(bool)), QString(), true, false }, { "window_expand_all", i18n("Expand All"), QString(), nullptr, this, SLOT(slotExpandAll()), QStringLiteral("CTRL+."), false, false }, { "window_collapse_all", i18n("Collapse All"), QString(), nullptr, this, SLOT(slotCollapseAll()), QStringLiteral("CTRL+,"), false, false }, }; for (int i = 0; i < d->NumPageActions; ++i) { d->currentPageActions[i] = make_action_from_data(actionData[i], coll); } for (int i = 0; i < d->NumPageActions; ++i) { action_data ad = actionData[i]; Q_ASSERT(QString::fromLatin1(ad.name).startsWith(QLatin1String("window_"))); ad.name = ad.name + strlen("window_"); ad.tooltip.clear(); ad.receiver = nullptr; ad.shortcut.clear(); d->otherPageActions[i] = make_action_from_data(ad, coll); } d->setCornerAction(d->newAction, Qt::TopLeftCorner); d->setCornerAction(d->currentPageActions[d->Close], Qt::TopRightCorner); d->actionsCreated = true; } QAbstractItemView *TabWidget::addView(const QString &title, const QString &id, const QString &text) { return d->addView(new Page(title, id, text), d->currentPage()); } QAbstractItemView *TabWidget::addView(const KConfigGroup &group) { return d->addView(new Page(group), nullptr); } QAbstractItemView *TabWidget::addTemporaryView(const QString &title, AbstractKeyListSortFilterProxyModel *proxy, const QString &tabToolTip) { Page *const page = new Page(title, QString(), QString(), proxy, tabToolTip); page->setTemporary(true); QAbstractItemView *v = d->addView(page, d->currentPage()); d->tabWidget.setCurrentIndex(d->tabWidget.count() - 1); return v; } QTreeView *TabWidget::Private::addView(Page *page, Page *columnReference) { if (!page) { return nullptr; } if (!actionsCreated) { KActionCollection *coll = new KActionCollection(q); q->createActions(coll); } page->setFlatModel(flatModel); page->setHierarchicalModel(hierarchicalModel); connect(page, SIGNAL(titleChanged(QString)), q, SLOT(slotPageTitleChanged(QString))); connect(page, SIGNAL(keyFilterChanged(std::shared_ptr)), q, SLOT(slotPageKeyFilterChanged(std::shared_ptr))); connect(page, SIGNAL(stringFilterChanged(QString)), q, SLOT(slotPageStringFilterChanged(QString))); connect(page, SIGNAL(hierarchicalChanged(bool)), q, SLOT(slotPageHierarchyChanged(bool))); if (columnReference) { page->setColumnSizes(columnReference->columnSizes()); page->setSortColumn(columnReference->sortColumn(), columnReference->sortOrder()); } QAbstractItemView *const previous = q->currentView(); const int tabIndex = tabWidget.addTab(page, page->title()); tabWidget.setTabToolTip(tabIndex, page->toolTip()); // work around a bug in QTabWidget (tested with 4.3.2) not emitting currentChanged() when the first widget is inserted QAbstractItemView *const current = q->currentView(); if (previous != current) { currentIndexChanged(tabWidget.currentIndex()); } enableDisableCurrentPageActions(); QTreeView *view = page->view(); Q_EMIT q->viewAdded(view); return view; } static QStringList extractViewGroups(const KConfig *config) { return config ? config->groupList().filter(QRegularExpression(QStringLiteral("^View #\\d+$"))) : QStringList(); } // work around deleteGroup() not deleting groups out of groupList(): static const bool KCONFIG_DELETEGROUP_BROKEN = true; void TabWidget::loadViews(const KConfig *config) { if (config) { QStringList groupList = extractViewGroups(config); groupList.sort(); for (const QString &group : qAsConst(groupList)) { const KConfigGroup kcg(config, group); if (!KCONFIG_DELETEGROUP_BROKEN || kcg.readEntry("magic", 0U) == 0xFA1AFE1U) { addView(kcg); } } } if (!count()) { // add default view: addView(QString(), QStringLiteral("all-certificates")); } } void TabWidget::saveViews(KConfig *config) const { if (!config) { return; } Q_FOREACH (const QString &group, extractViewGroups(config)) { config->deleteGroup(group); } unsigned int vg = 0; for (unsigned int i = 0, end = count(); i != end; ++i) { if (const Page *const p = d->page(i)) { if (p->isTemporary()) { continue; } KConfigGroup group(config, QString().sprintf("View #%u", vg++)); p->saveTo(group); if (KCONFIG_DELETEGROUP_BROKEN) { group.writeEntry("magic", 0xFA1AFE1U); } } } } static void xconnect(const QObject *o1, const char *signal, const QObject *o2, const char *slot) { QObject::connect(o1, signal, o2, slot); QObject::connect(o2, signal, o1, slot); } void TabWidget::connectSearchBar(QObject *sb) { xconnect(sb, SIGNAL(stringFilterChanged(QString)), this, SLOT(setStringFilter(QString))); xconnect(sb, SIGNAL(keyFilterChanged(std::shared_ptr)), this, SLOT(setKeyFilter(std::shared_ptr))); connect(this, SIGNAL(enableChangeStringFilter(bool)), sb, SLOT(setChangeStringFilterEnabled(bool))); connect(this, SIGNAL(enableChangeKeyFilter(bool)), sb, SLOT(setChangeKeyFilterEnabled(bool))); } #include "moc_tabwidget.cpp" #include "tabwidget.moc"