diff --git a/CMakeLists.txt b/CMakeLists.txt index 21049a2..65210e7 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,123 +1,125 @@ cmake_minimum_required(VERSION 3.5) set(KF5_VERSION "5.64.0") # handled by release scripts set(KF5_DEP_VERSION "5.63.0") # handled by release scripts project(KXmlGui VERSION ${KF5_VERSION}) # ECM setup include(FeatureSummary) find_package(ECM 5.63.0 NO_MODULE) set_package_properties(ECM PROPERTIES TYPE REQUIRED DESCRIPTION "Extra CMake Modules." URL "https://projects.kde.org/projects/kdesupport/extra-cmake-modules") feature_summary(WHAT REQUIRED_PACKAGES_NOT_FOUND FATAL_ON_MISSING_REQUIRED_PACKAGES) set(CMAKE_MODULE_PATH ${ECM_MODULE_PATH}) include(ECMMarkNonGuiExecutable) include(ECMSetupVersion) include(ECMGenerateHeaders) include(ECMAddQch) +include(ECMGenerateExportHeader) -include(GenerateExportHeader) include(KDEInstallDirs) include(KDEFrameworkCompilerSettings NO_POLICY_SCOPE) include(KDECMakeSettings) include(ECMQtDeclareLoggingCategory) ecm_setup_version(PROJECT VARIABLE_PREFIX KXMLGUI VERSION_HEADER "${CMAKE_CURRENT_BINARY_DIR}/kxmlgui_version.h" PACKAGE_VERSION_FILE "${CMAKE_CURRENT_BINARY_DIR}/KF5XmlGuiConfigVersion.cmake" SOVERSION 5) +set(EXCLUDE_DEPRECATED_BEFORE_AND_AT 0 CACHE STRING "Control the range of deprecated API excluded from the build [default=0].") + option(FORCE_DISABLE_KGLOBALACCEL "Force building KXmlGui without KGlobalAccel. Doing this will break global shortcut support. [default=OFF]" OFF) option(BUILD_QCH "Build API documentation in QCH format (for e.g. Qt Assistant, Qt Creator & KDevelop)" OFF) add_feature_info(QCH ${BUILD_QCH} "API documentation in QCH format (for e.g. Qt Assistant, Qt Creator & KDevelop)") option(BUILD_DESIGNERPLUGIN "Build plugin for Qt Designer" ON) add_feature_info(DESIGNERPLUGIN ${BUILD_DESIGNERPLUGIN} "Build plugin for Qt Designer") # Dependencies set(REQUIRED_QT_VERSION 5.11.0) find_package(Qt5 ${REQUIRED_QT_VERSION} CONFIG REQUIRED Widgets Xml Network PrintSupport) if (NOT ANDROID) find_package(Qt5 ${REQUIRED_QT_VERSION} CONFIG REQUIRED DBus) endif() find_package(KF5CoreAddons ${KF5_DEP_VERSION} REQUIRED) find_package(KF5ItemViews ${KF5_DEP_VERSION} REQUIRED) find_package(KF5Config ${KF5_DEP_VERSION} REQUIRED) find_package(KF5ConfigWidgets ${KF5_DEP_VERSION} REQUIRED) find_package(KF5I18n ${KF5_DEP_VERSION} REQUIRED) find_package(KF5IconThemes ${KF5_DEP_VERSION} REQUIRED) find_package(KF5TextWidgets ${KF5_DEP_VERSION} REQUIRED) find_package(KF5WidgetsAddons ${KF5_DEP_VERSION} REQUIRED) find_package(KF5WindowSystem ${KF5_DEP_VERSION} REQUIRED) find_package(KF5Attica ${KF5_DEP_VERSION}) set_package_properties(KF5Attica PROPERTIES DESCRIPTION "A Qt library that implements the Open Collaboration Services API" PURPOSE "Support for Get Hot New Stuff in KXMLGUI" URL "https://projects.kde.org/attica" TYPE OPTIONAL ) set (HAVE_ATTICA ${KF5Attica_FOUND}) if (NOT FORCE_DISABLE_KGLOBALACCEL) find_package(KF5GlobalAccel ${KF5_DEP_VERSION} REQUIRED) endif() set (HAVE_GLOBALACCEL ${KF5GlobalAccel_FOUND}) add_definitions(-DQT_DISABLE_DEPRECATED_BEFORE=0x050d00) # Subdirectories add_definitions(-DTRANSLATION_DOMAIN=\"kxmlgui5\") if (IS_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/po") ki18n_install(po) endif() add_subdirectory(src) if (BUILD_TESTING) add_subdirectory(tests) add_subdirectory(autotests) endif() # create a Config.cmake and a ConfigVersion.cmake file and install them set(CMAKECONFIG_INSTALL_DIR "${KDE_INSTALL_CMAKEPACKAGEDIR}/KF5XmlGui") if (BUILD_QCH) ecm_install_qch_export( TARGETS KF5XmlGui_QCH FILE KF5XmlGuiQchTargets.cmake DESTINATION "${CMAKECONFIG_INSTALL_DIR}" COMPONENT Devel ) set(PACKAGE_INCLUDE_QCHTARGETS "include(\"\${CMAKE_CURRENT_LIST_DIR}/KF5XmlGuiQchTargets.cmake\")") endif() include(CMakePackageConfigHelpers) set(HAVE_DBUS FALSE) if (TARGET Qt5::DBus) set(HAVE_DBUS TRUE) endif() configure_package_config_file( "${CMAKE_CURRENT_SOURCE_DIR}/KF5XmlGuiConfig.cmake.in" "${CMAKE_CURRENT_BINARY_DIR}/KF5XmlGuiConfig.cmake" PATH_VARS KDE_INSTALL_DBUSINTERFACEDIR INSTALL_DESTINATION ${CMAKECONFIG_INSTALL_DIR} ) install(FILES "${CMAKE_CURRENT_BINARY_DIR}/KF5XmlGuiConfig.cmake" "${CMAKE_CURRENT_BINARY_DIR}/KF5XmlGuiConfigVersion.cmake" DESTINATION "${CMAKECONFIG_INSTALL_DIR}" COMPONENT Devel ) install(EXPORT KF5XmlGuiTargets DESTINATION "${CMAKECONFIG_INSTALL_DIR}" FILE KF5XmlGuiTargets.cmake NAMESPACE KF5:: ) install(FILES ${CMAKE_CURRENT_BINARY_DIR}/kxmlgui_version.h DESTINATION ${KDE_INSTALL_INCLUDEDIR_KF5} COMPONENT Devel ) install(FILES kxmlgui.categories DESTINATION ${KDE_INSTALL_LOGGINGCATEGORIESDIR}) feature_summary(WHAT ALL FATAL_ON_MISSING_REQUIRED_PACKAGES) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index fc8cf5e..bcf8a62 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -1,181 +1,189 @@ add_subdirectory(ksendbugmail) if (HAVE_ATTICA) set (XMLGUI_EXTRA_LIBS ${XMLGUI_EXTRA_LIBS} KF5::Attica) endif () if (HAVE_GLOBALACCEL) set (XMLGUI_EXTRA_LIBS ${XMLGUI_EXTRA_LIBS} KF5::GlobalAccel) endif () if(WIN32) set (XMLGUI_EXTRA_LIBS ${XMLGUI_EXTRA_LIBS} secur32) # GetUserNameEx() endif() configure_file(config-xmlgui.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/config-xmlgui.h ) configure_file( ${CMAKE_CURRENT_SOURCE_DIR}/kaboutapplicationconfigattica_p.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/kaboutapplicationconfigattica_p.h ) set(kxmlgui_SRCS kaboutapplicationdialog.cpp kaboutapplicationpersonlistdelegate_p.cpp kaboutapplicationpersonlistview_p.cpp kaboutapplicationpersonmodel_p.cpp kaboutkdedialog_p.cpp kactioncategory.cpp kactioncollection.cpp kactionconflictdetector.cpp kbugreport.cpp kedittoolbar.cpp kgesture.cpp kgesturemap.cpp khelpmenu.cpp kkeysequencewidget.cpp kmainwindow.cpp kmenumenuhandler_p.cpp kshortcuteditwidget.cpp kshortcutschemeseditor.cpp kshortcutschemeshelper.cpp kshortcutsdialog.cpp kshortcutseditor.cpp kshortcutseditordelegate.cpp kshortcutseditoritem.cpp kshortcutwidget.cpp kswitchlanguagedialog_p.cpp ktoggletoolbaraction.cpp ktoolbar.cpp ktoolbarhandler.cpp ktoolbarhelper.cpp kxmlguibuilder.cpp kxmlguiclient.cpp kxmlguifactory.cpp kxmlguifactory_p.cpp kxmlguiversionhandler.cpp kxmlguiwindow.cpp kundoactions.cpp kcheckaccelerators.cpp ) if (TARGET Qt5::DBus) list(APPEND kxmlgui_SRCS kmainwindowiface.cpp) endif() # add the resource file qt5_add_resources(kxmlgui_SRCS kxmlgui.qrc) ecm_qt_declare_logging_category(kxmlgui_SRCS HEADER debug.h IDENTIFIER DEBUG_KXMLGUI CATEGORY_NAME kf5.kxmlgui) set(kxmlgui_UI kshortcutsdialog.ui kshortcutwidget.ui ) ki18n_wrap_ui(kxmlgui_SRCS ${kxmlgui_UI} ) add_library(KF5XmlGui ${kxmlgui_SRCS}) -generate_export_header(KF5XmlGui BASE_NAME KXmlGui) +ecm_generate_export_header(KF5XmlGui + BASE_NAME KXmlGui + # GROUP_BASE_NAME KF <- enable once all of KF modules use ecm_generate_export_header + VERSION ${KF5_VERSION} + DEPRECATED_BASE_VERSION 0 + DEPRECATION_VERSIONS 4.1 5.0 + EXCLUDE_DEPRECATED_BEFORE_AND_AT ${EXCLUDE_DEPRECATED_BEFORE_AND_AT} +) + add_library(KF5::XmlGui ALIAS KF5XmlGui) target_include_directories(KF5XmlGui INTERFACE "$") target_link_libraries(KF5XmlGui PUBLIC Qt5::Xml #To parse the configuration (QDomDocument etc) Qt5::Widgets #QWidget is used everywhere KF5::ConfigCore #Reading config for ToolbarIcons, Shortcut Schemes... KF5::ConfigWidgets #KStandardAction, KToggleAction... PRIVATE Qt5::Network #QNetworkAccessManager in kaboutapplicationpersonmodel_p Qt5::PrintSupport #QPrinter in kshortcutseditor Qt5::CorePrivate #QSystemLocale in initializeLanguages KF5::CoreAddons #KAboutData KF5::WidgetsAddons KF5::ItemViews #KWidgetItemDelegate in KAboutApplicationPersonListDelegate KF5::I18n #i18n and i18nc in many places KF5::IconThemes #KIconLoader and KIconThemes in KToolBar KF5::TextWidgets #KTextEdit in kbugreport KF5::WindowSystem #KKeyServer in kkeysequencewidget ${XMLGUI_EXTRA_LIBS} ) if (TARGET Qt5::DBus) target_link_libraries(KF5XmlGui PUBLIC Qt5::DBus) #QDBus connect to signal in KToolBar endif() set_target_properties(KF5XmlGui PROPERTIES VERSION ${KXMLGUI_VERSION_STRING} SOVERSION ${KXMLGUI_SOVERSION} EXPORT_NAME XmlGui ) ecm_generate_headers(KXmlGui_HEADERS HEADER_NAMES KAboutApplicationDialog KActionCategory KActionCollection KBugReport KEditToolBar KHelpMenu KKeySequenceWidget KMainWindow KShortcutsDialog KShortcutsEditor KShortcutWidget KToggleToolBarAction KToolBar KXMLGUIBuilder KXMLGUIClient KXMLGUIFactory KXmlGuiWindow KUndoActions REQUIRED_HEADERS KXmlGui_HEADERS ) install(TARGETS KF5XmlGui EXPORT KF5XmlGuiTargets ${KF5_INSTALL_TARGETS_DEFAULT_ARGS}) install(FILES kgesture_p.h kgesturemap_p.h ${CMAKE_CURRENT_BINARY_DIR}/kxmlgui_export.h ${KXmlGui_HEADERS} DESTINATION ${KDE_INSTALL_INCLUDEDIR_KF5}/KXmlGui COMPONENT Devel ) # install this file to be compatible, it is bundled in the resource file, too install( FILES ui_standards.rc DESTINATION ${KDE_INSTALL_CONFDIR}/ui ) if(BUILD_DESIGNERPLUGIN) add_subdirectory(designer) endif() if (BUILD_QCH) ecm_add_qch( KF5XmlGui_QCH NAME KXmlGui BASE_NAME KF5XmlGui VERSION ${KF5_VERSION} ORG_DOMAIN org.kde SOURCES # using only public headers, to cover only public API ${KXmlGui_HEADERS} MD_MAINPAGE "${CMAKE_SOURCE_DIR}/README.md" IMAGE_DIRS "${CMAKE_SOURCE_DIR}/docs/pics" LINK_QCHS Qt5Xml_QCH Qt5DBus_QCH Qt5Widgets_QCH KF5Config_QCH KF5ConfigWidgets_QCH INCLUDE_DIRS ${CMAKE_CURRENT_BINARY_DIR} BLANK_MACROS KXMLGUI_EXPORT KXMLGUI_DEPRECATED_EXPORT KXMLGUI_DEPRECATED TAGFILE_INSTALL_DESTINATION ${KDE_INSTALL_QTQCHDIR} QCH_INSTALL_DESTINATION ${KDE_INSTALL_QTQCHDIR} COMPONENT Devel ) endif() include(ECMGeneratePriFile) ecm_generate_pri_file(BASE_NAME KXmlGui LIB_NAME KF5XmlGui DEPS "dbus xml widgets KConfigCore KConfigWidgets" FILENAME_VAR PRI_FILENAME INCLUDE_INSTALL_DIR ${KDE_INSTALL_INCLUDEDIR_KF5}/KXmlGui) install(FILES ${PRI_FILENAME} DESTINATION ${ECM_MKSPECS_INSTALL_DIR}) diff --git a/src/kactioncollection.cpp b/src/kactioncollection.cpp index 03053e6..afc3f9a 100644 --- a/src/kactioncollection.cpp +++ b/src/kactioncollection.cpp @@ -1,878 +1,889 @@ /* This file is part of the KDE libraries Copyright (C) 1999 Reginald Stadlbauer (C) 1999 Simon Hausmann (C) 2000 Nicolas Hadacek (C) 2000 Kurt Granroth (C) 2000 Michael Koch (C) 2001 Holger Freyther (C) 2002 Ellis Whitehead (C) 2002 Joseph Wenninger (C) 2005-2007 Hamish Rodda This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License version 2 as published by the Free Software Foundation. This library 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 Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "config-xmlgui.h" #include "kactioncollection.h" #include "kactioncategory.h" #include "kxmlguiclient.h" #include "kxmlguifactory.h" #include "debug.h" #include #include #if HAVE_GLOBALACCEL # include #endif #include #include #include #include #include #include #include #include #include class KActionCollectionPrivate { public: KActionCollectionPrivate() : m_parentGUIClient(nullptr), configGroup(QStringLiteral("Shortcuts")), configIsGlobal(false), connectTriggered(false), connectHovered(false), q(nullptr) { } void setComponentForAction(QAction *action) { #if HAVE_GLOBALACCEL bool hasGlobalShortcut = KGlobalAccel::self()->hasShortcut(action); #else bool hasGlobalShortcut = false; #endif if (!hasGlobalShortcut) { action->setProperty("componentName", m_componentName); action->setProperty("componentDisplayName", m_componentDisplayName); } } static QList s_allCollections; void _k_associatedWidgetDestroyed(QObject *obj); void _k_actionDestroyed(QObject *obj); bool writeKXMLGUIConfigFile(); QString m_componentName; QString m_componentDisplayName; //! Remove a action from our internal bookkeeping. Returns a nullptr if the //! action doesn't belong to us. QAction *unlistAction(QAction *); QMap actionByName; QList actions; const KXMLGUIClient *m_parentGUIClient; QString configGroup; bool configIsGlobal : 1; bool connectTriggered : 1; bool connectHovered : 1; KActionCollection *q; QList associatedWidgets; }; QList KActionCollectionPrivate::s_allCollections; KActionCollection::KActionCollection(QObject *parent, const QString &cName) : QObject(parent) , d(new KActionCollectionPrivate) { d->q = this; KActionCollectionPrivate::s_allCollections.append(this); setComponentName(cName); } KActionCollection::KActionCollection(const KXMLGUIClient *parent) : QObject(nullptr) , d(new KActionCollectionPrivate) { d->q = this; KActionCollectionPrivate::s_allCollections.append(this); d->m_parentGUIClient = parent; d->m_componentName = parent->componentName(); } KActionCollection::~KActionCollection() { KActionCollectionPrivate::s_allCollections.removeAll(this); delete d; } void KActionCollection::clear() { d->actionByName.clear(); qDeleteAll(d->actions); d->actions.clear(); } QAction *KActionCollection::action(const QString &name) const { QAction *action = nullptr; if (!name.isEmpty()) { action = d->actionByName.value(name); } return action; } QAction *KActionCollection::action(int index) const { // ### investigate if any apps use this at all return actions().value(index); } int KActionCollection::count() const { return d->actions.count(); } bool KActionCollection::isEmpty() const { return count() == 0; } void KActionCollection::setComponentName(const QString &cName) { if (count() > 0) { // Its component name is part of an action's signature in the context of // global shortcuts and the semantics of changing an existing action's // signature are, as it seems, impossible to get right. // As of now this only matters for global shortcuts. We could // thus relax the requirement and only refuse to change the component data // if we have actions with global shortcuts in this collection. qCWarning(DEBUG_KXMLGUI) << "this does not work on a KActionCollection containing actions!"; } if (!cName.isEmpty()) { d->m_componentName = cName; } else { d->m_componentName = QCoreApplication::applicationName(); } } QString KActionCollection::componentName() const { return d->m_componentName; } void KActionCollection::setComponentDisplayName(const QString &displayName) { d->m_componentDisplayName = displayName; } QString KActionCollection::componentDisplayName() const { if (!d->m_componentDisplayName.isEmpty()) { return d->m_componentDisplayName; } if (!QGuiApplication::applicationDisplayName().isEmpty()) { return QGuiApplication::applicationDisplayName(); } return QCoreApplication::applicationName(); } const KXMLGUIClient *KActionCollection::parentGUIClient() const { return d->m_parentGUIClient; } QList KActionCollection::actions() const { return d->actions; } const QList< QAction * > KActionCollection::actionsWithoutGroup() const { QList ret; for (QAction *action : qAsConst(d->actions)) { if (!action->actionGroup()) { ret.append(action); } } return ret; } const QList< QActionGroup * > KActionCollection::actionGroups() const { QSet set; for (QAction *action : qAsConst(d->actions)) { if (action->actionGroup()) { set.insert(action->actionGroup()); } } return set.toList(); } QAction *KActionCollection::addAction(const QString &name, QAction *action) { if (!action) { return action; } const QString objectName = action->objectName(); QString indexName = name; if (indexName.isEmpty()) { // No name provided. Use the objectName. indexName = objectName; } else { // A name was provided. Check against objectName. if ((!objectName.isEmpty()) && (objectName != indexName)) { // The user specified a new name and the action already has a // different one. The objectName is used for saving shortcut // settings to disk. Both for local and global shortcuts. qCDebug(DEBUG_KXMLGUI) << "Registering action " << objectName << " under new name " << indexName; // If there is a global shortcuts it's a very bad idea. #if HAVE_GLOBALACCEL if (KGlobalAccel::self()->hasShortcut(action)) { // In debug mode assert Q_ASSERT(!KGlobalAccel::self()->hasShortcut(action)); // In release mode keep the old name qCritical() << "Changing action name from " << objectName << " to " << indexName << "\nignored because of active global shortcut."; indexName = objectName; } #endif } // Set the new name action->setObjectName(indexName); } // No name provided and the action had no name. Make one up. This will not // work when trying to save shortcuts. Both local and global shortcuts. if (indexName.isEmpty()) { indexName = QString::asprintf("unnamed-%p", (void *)action); action->setObjectName(indexName); } // From now on the objectName has to have a value. Else we cannot safely // remove actions. Q_ASSERT(!action->objectName().isEmpty()); // look if we already have THIS action under THIS name ;) if (d->actionByName.value(indexName, nullptr) == action) { // This is not a multi map! Q_ASSERT(d->actionByName.count(indexName) == 1); return action; } if (!KAuthorized::authorizeAction(indexName)) { // Disable this action action->setEnabled(false); action->setVisible(false); action->blockSignals(true); } // Check if we have another action under this name if (QAction *oldAction = d->actionByName.value(indexName)) { takeAction(oldAction); } // Check if we have this action under a different name. // Not using takeAction because we don't want to remove it from categories, // and because it has the new name already. const int oldIndex = d->actions.indexOf(action); if (oldIndex != -1) { d->actionByName.remove(d->actionByName.key(action)); d->actions.removeAt(oldIndex); } // Add action to our lists. d->actionByName.insert(indexName, action); d->actions.append(action); for (QWidget *widget : qAsConst(d->associatedWidgets)) { widget->addAction(action); } connect(action, SIGNAL(destroyed(QObject*)), SLOT(_k_actionDestroyed(QObject*))); d->setComponentForAction(action); if (d->connectHovered) { connect(action, &QAction::hovered, this, &KActionCollection::slotActionHovered); } if (d->connectTriggered) { connect(action, &QAction::triggered, this, &KActionCollection::slotActionTriggered); } emit inserted(action); return action; } void KActionCollection::addActions(const QList &actions) { for (QAction *action : actions) { addAction(action->objectName(), action); } } void KActionCollection::removeAction(QAction *action) { delete takeAction(action); } QAction *KActionCollection::takeAction(QAction *action) { if (!d->unlistAction(action)) { return nullptr; } // Remove the action from all widgets for (QWidget *widget : qAsConst(d->associatedWidgets)) { widget->removeAction(action); } action->disconnect(this); +#if KXMLGUI_BUILD_DEPRECATED_SINCE(5, 0) emit removed(action); //deprecated +#endif return action; } QAction *KActionCollection::addAction(KStandardAction::StandardAction actionType, const QObject *receiver, const char *member) { QAction *action = KStandardAction::create(actionType, receiver, member, this); return action; } QAction *KActionCollection::addAction(KStandardAction::StandardAction actionType, const QString &name, const QObject *receiver, const char *member) { // pass 0 as parent, because if the parent is a KActionCollection KStandardAction::create automatically // adds the action to it under the default name. We would trigger the // warning about renaming the action then. QAction *action = KStandardAction::create(actionType, receiver, member, nullptr); // Give it a parent for gc. action->setParent(this); // Remove the name to get rid of the "rename action" warning above action->setObjectName(name); // And now add it with the desired name. return addAction(name, action); } QAction *KActionCollection::addAction(const QString &name, const QObject *receiver, const char *member) { QAction *a = new QAction(this); if (receiver && member) { connect(a, SIGNAL(triggered(bool)), receiver, member); } return addAction(name, a); } QKeySequence KActionCollection::defaultShortcut(QAction *action) const { const QList shortcuts = defaultShortcuts(action); return shortcuts.isEmpty() ? QKeySequence() : shortcuts.first(); } QList KActionCollection::defaultShortcuts(QAction *action) const { return action->property("defaultShortcuts").value >(); } void KActionCollection::setDefaultShortcut(QAction *action, const QKeySequence &shortcut) { setDefaultShortcuts(action, QList() << shortcut); } void KActionCollection::setDefaultShortcuts(QAction *action, const QList &shortcuts) { action->setShortcuts(shortcuts); action->setProperty("defaultShortcuts", QVariant::fromValue(shortcuts)); } bool KActionCollection::isShortcutsConfigurable(QAction *action) const { // Considered as true by default const QVariant value = action->property("isShortcutConfigurable"); return value.isValid() ? value.toBool() : true; } void KActionCollection::setShortcutsConfigurable(QAction *action, bool configurable) { action->setProperty("isShortcutConfigurable", configurable); } QString KActionCollection::configGroup() const { return d->configGroup; } void KActionCollection::setConfigGroup(const QString &group) { d->configGroup = group; } bool KActionCollection::configIsGlobal() const { return d->configIsGlobal; } void KActionCollection::setConfigGlobal(bool global) { d->configIsGlobal = global; } void KActionCollection::importGlobalShortcuts(KConfigGroup *config) { #if HAVE_GLOBALACCEL Q_ASSERT(config); if (!config || !config->exists()) { return; } for (QMap::ConstIterator it = d->actionByName.constBegin(); it != d->actionByName.constEnd(); ++it) { QAction *action = it.value(); if (!action) { continue; } QString actionName = it.key(); if (isShortcutsConfigurable(action)) { QString entry = config->readEntry(actionName, QString()); if (!entry.isEmpty()) { KGlobalAccel::self()->setShortcut(action, QKeySequence::listFromString(entry), KGlobalAccel::NoAutoloading); } else { QList defaultShortcut = KGlobalAccel::self()->defaultShortcut(action); KGlobalAccel::self()->setShortcut(action, defaultShortcut, KGlobalAccel::NoAutoloading); } } } #else Q_UNUSED(config); #endif } void KActionCollection::readSettings(KConfigGroup *config) { KConfigGroup cg(KSharedConfig::openConfig(), configGroup()); if (!config) { config = &cg; } if (!config->exists()) { return; } for (QMap::ConstIterator it = d->actionByName.constBegin(); it != d->actionByName.constEnd(); ++it) { QAction *action = it.value(); if (!action) { continue; } if (isShortcutsConfigurable(action)) { QString actionName = it.key(); QString entry = config->readEntry(actionName, QString()); if (!entry.isEmpty()) { action->setShortcuts(QKeySequence::listFromString(entry)); } else { action->setShortcuts(defaultShortcuts(action)); } } } //qCDebug(DEBUG_KXMLGUI) << " done"; } void KActionCollection::exportGlobalShortcuts(KConfigGroup *config, bool writeAll) const { #if HAVE_GLOBALACCEL Q_ASSERT(config); if (!config) { return; } for (QMap::ConstIterator it = d->actionByName.constBegin(); it != d->actionByName.constEnd(); ++it) { QAction *action = it.value(); if (!action) { continue; } QString actionName = it.key(); // If the action name starts with unnamed- spit out a warning. That name // will change at will and will break loading writing if (actionName.startsWith(QLatin1String("unnamed-"))) { qCritical() << "Skipped exporting Shortcut for action without name " << action->text() << "!"; continue; } if (isShortcutsConfigurable(action) && KGlobalAccel::self()->hasShortcut(action)) { bool bConfigHasAction = !config->readEntry(actionName, QString()).isEmpty(); bool bSameAsDefault = (KGlobalAccel::self()->shortcut(action) == KGlobalAccel::self()->defaultShortcut(action)); // If we're using a global config or this setting // differs from the default, then we want to write. KConfigGroup::WriteConfigFlags flags = KConfigGroup::Persistent; if (configIsGlobal()) { flags |= KConfigGroup::Global; } if (writeAll || !bSameAsDefault) { QString s = QKeySequence::listToString(KGlobalAccel::self()->shortcut(action)); if (s.isEmpty()) { s = QStringLiteral("none"); } qCDebug(DEBUG_KXMLGUI) << "\twriting " << actionName << " = " << s; config->writeEntry(actionName, s, flags); } // Otherwise, this key is the same as default // but exists in config file. Remove it. else if (bConfigHasAction) { qCDebug(DEBUG_KXMLGUI) << "\tremoving " << actionName << " because == default"; config->deleteEntry(actionName, flags); } } } config->sync(); #else Q_UNUSED(config); Q_UNUSED(writeAll); #endif } bool KActionCollectionPrivate::writeKXMLGUIConfigFile() { const KXMLGUIClient *kxmlguiClient = q->parentGUIClient(); // return false if there is no KXMLGUIClient if (!kxmlguiClient || kxmlguiClient->xmlFile().isEmpty()) { return false; } qCDebug(DEBUG_KXMLGUI) << "xmlFile=" << kxmlguiClient->xmlFile(); QString attrShortcut = QStringLiteral("shortcut"); // Read XML file QString sXml(KXMLGUIFactory::readConfigFile(kxmlguiClient->xmlFile(), q->componentName())); QDomDocument doc; doc.setContent(sXml); // Process XML data // Get hold of ActionProperties tag QDomElement elem = KXMLGUIFactory::actionPropertiesElement(doc); // now, iterate through our actions for (QMap::ConstIterator it = actionByName.constBegin(); it != actionByName.constEnd(); ++it) { QAction *action = it.value(); if (!action) { continue; } QString actionName = it.key(); // If the action name starts with unnamed- spit out a warning and ignore // it. That name will change at will and will break loading writing if (actionName.startsWith(QLatin1String("unnamed-"))) { qCritical() << "Skipped writing shortcut for action " << actionName << "(" << action->text() << ")!"; continue; } bool bSameAsDefault = (action->shortcuts() == q->defaultShortcuts(action)); qCDebug(DEBUG_KXMLGUI) << "name = " << actionName << " shortcut = " << QKeySequence::listToString(action->shortcuts()) #if HAVE_GLOBALACCEL << " globalshortcut = " << QKeySequence::listToString(KGlobalAccel::self()->shortcut(action)) #endif << " def = " << QKeySequence::listToString(q->defaultShortcuts(action)); // now see if this element already exists // and create it if necessary (unless bSameAsDefault) QDomElement act_elem = KXMLGUIFactory::findActionByName(elem, actionName, !bSameAsDefault); if (act_elem.isNull()) { continue; } if (bSameAsDefault) { act_elem.removeAttribute(attrShortcut); //qCDebug(DEBUG_KXMLGUI) << "act_elem.attributes().count() = " << act_elem.attributes().count(); if (act_elem.attributes().count() == 1) { elem.removeChild(act_elem); } } else { act_elem.setAttribute(attrShortcut, QKeySequence::listToString(action->shortcuts())); } } // Write back to XML file KXMLGUIFactory::saveConfigFile(doc, kxmlguiClient->localXMLFile(), q->componentName()); return true; } void KActionCollection::writeSettings(KConfigGroup *config, bool writeAll, QAction *oneAction) const { // If the caller didn't provide a config group we try to save the KXMLGUI // Configuration file. If that succeeds we are finished. if (config == nullptr && d->writeKXMLGUIConfigFile()) { return; } KConfigGroup cg(KSharedConfig::openConfig(), configGroup()); if (!config) { config = &cg; } QList writeActions; if (oneAction) { writeActions.append(oneAction); } else { writeActions = actions(); } for (QMap::ConstIterator it = d->actionByName.constBegin(); it != d->actionByName.constEnd(); ++it) { QAction *action = it.value(); if (!action) { continue; } QString actionName = it.key(); // If the action name starts with unnamed- spit out a warning and ignore // it. That name will change at will and will break loading writing if (actionName.startsWith(QLatin1String("unnamed-"))) { qCritical() << "Skipped saving Shortcut for action without name " << action->text() << "!"; continue; } // Write the shortcut if (isShortcutsConfigurable(action)) { bool bConfigHasAction = !config->readEntry(actionName, QString()).isEmpty(); bool bSameAsDefault = (action->shortcuts() == defaultShortcuts(action)); // If we're using a global config or this setting // differs from the default, then we want to write. KConfigGroup::WriteConfigFlags flags = KConfigGroup::Persistent; // Honor the configIsGlobal() setting if (configIsGlobal()) { flags |= KConfigGroup::Global; } if (writeAll || !bSameAsDefault) { // We are instructed to write all shortcuts or the shortcut is // not set to its default value. Write it QString s = QKeySequence::listToString(action->shortcuts()); if (s.isEmpty()) { s = QStringLiteral("none"); } qCDebug(DEBUG_KXMLGUI) << "\twriting " << actionName << " = " << s; config->writeEntry(actionName, s, flags); } else if (bConfigHasAction) { // Otherwise, this key is the same as default but exists in // config file. Remove it. qCDebug(DEBUG_KXMLGUI) << "\tremoving " << actionName << " because == default"; config->deleteEntry(actionName, flags); } } } config->sync(); } void KActionCollection::slotActionTriggered() { QAction *action = qobject_cast(sender()); if (action) { emit actionTriggered(action); } } +#if KXMLGUI_BUILD_DEPRECATED_SINCE(5, 0) void KActionCollection::slotActionHighlighted() { slotActionHovered(); } +#endif void KActionCollection::slotActionHovered() { QAction *action = qobject_cast(sender()); if (action) { +#if KXMLGUI_BUILD_DEPRECATED_SINCE(5, 0) emit actionHighlighted(action); +#endif emit actionHovered(action); } } void KActionCollectionPrivate::_k_actionDestroyed(QObject *obj) { // obj isn't really a QAction anymore. So make sure we don't do fancy stuff // with it. QAction *action = static_cast(obj); if (!unlistAction(action)) { return; } //HACK the object we emit is partly destroyed +#if KXMLGUI_BUILD_DEPRECATED_SINCE(5, 0) emit q->removed(action); //deprecated. remove in KDE5 +#endif } void KActionCollection::connectNotify(const QMetaMethod &signal) { if (d->connectHovered && d->connectTriggered) { return; } - if (signal.methodSignature() == "actionHighlighted(QAction*)" || + if ( +#if KXMLGUI_BUILD_DEPRECATED_SINCE(5, 0) + signal.methodSignature() == "actionHighlighted(QAction*)" || +#endif signal.methodSignature() == "actionHovered(QAction*)") { if (!d->connectHovered) { d->connectHovered = true; for (QAction *action : qAsConst(d->actions)) { connect(action, &QAction::hovered, this, &KActionCollection::slotActionHovered); } } } else if (signal.methodSignature() == "actionTriggered(QAction*)") { if (!d->connectTriggered) { d->connectTriggered = true; for (QAction *action: qAsConst(d->actions)) { connect(action, &QAction::triggered, this, &KActionCollection::slotActionTriggered); } } } QObject::connectNotify(signal); } const QList< KActionCollection * > &KActionCollection::allCollections() { return KActionCollectionPrivate::s_allCollections; } void KActionCollection::associateWidget(QWidget *widget) const { for (QAction *action : qAsConst(d->actions)) { if (!widget->actions().contains(action)) { widget->addAction(action); } } } void KActionCollection::addAssociatedWidget(QWidget *widget) { if (!d->associatedWidgets.contains(widget)) { widget->addActions(actions()); d->associatedWidgets.append(widget); connect(widget, SIGNAL(destroyed(QObject*)), this, SLOT(_k_associatedWidgetDestroyed(QObject*))); } } void KActionCollection::removeAssociatedWidget(QWidget *widget) { for (QAction *action : qAsConst(d->actions)) { widget->removeAction(action); } d->associatedWidgets.removeAll(widget); disconnect(widget, SIGNAL(destroyed(QObject*)), this, SLOT(_k_associatedWidgetDestroyed(QObject*))); } QAction *KActionCollectionPrivate::unlistAction(QAction *action) { // ATTENTION: // This method is called with an QObject formerly known as a QAction // during _k_actionDestroyed(). So don't do fancy stuff here that needs a // real QAction! // Get the index for the action int index = actions.indexOf(action); // Action not found. if (index == -1) { return nullptr; } // An action collection can't have the same action twice. Q_ASSERT(actions.indexOf(action, index + 1) == -1); // Get the actions name const QString name = action->objectName(); // Remove the action actionByName.remove(name); actions.removeAt(index); // Remove the action from the categories. Should be only one const QList categories = q->findChildren(); for (KActionCategory *category : categories) { category->unlistAction(action); } return action; } QList< QWidget * > KActionCollection::associatedWidgets() const { return d->associatedWidgets; } void KActionCollection::clearAssociatedWidgets() { for (QWidget *widget : qAsConst(d->associatedWidgets)) { for (QAction *action : qAsConst(d->actions)) { widget->removeAction(action); } } d->associatedWidgets.clear(); } void KActionCollectionPrivate::_k_associatedWidgetDestroyed(QObject *obj) { associatedWidgets.removeAll(static_cast(obj)); } #include "moc_kactioncollection.cpp" diff --git a/src/kactioncollection.h b/src/kactioncollection.h index c991c75..210438b 100644 --- a/src/kactioncollection.h +++ b/src/kactioncollection.h @@ -1,588 +1,597 @@ /* This file is part of the KDE libraries Copyright (C) 1999 Reginald Stadlbauer (C) 1999 Simon Hausmann (C) 2000 Nicolas Hadacek (C) 2000 Kurt Granroth (C) 2000 Michael Koch (C) 2001 Holger Freyther (C) 2002 Ellis Whitehead (C) 2005-2006 Hamish Rodda This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License version 2 as published by the Free Software Foundation. This library 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 Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef KACTIONCOLLECTION_H #define KACTIONCOLLECTION_H #include #include #include #include class QAction; class KXMLGUIClient; class KConfigGroup; class QActionGroup; class QString; /** * @class KActionCollection kactioncollection.h KActionCollection * * \short A container for a set of QAction objects. * * KActionCollection manages a set of QAction objects. It * allows them to be grouped for organized presentation of configuration to the user, * saving + loading of configuration, and optionally for automatic plugging into * specified widget(s). * * Additionally, KActionCollection provides several convenience functions for locating * named actions, and actions grouped by QActionGroup. * * \note If you create your own action collection and need to assign shortcuts * to the actions within, you have to call associateWidget() or * addAssociatedWidget() to have them working. */ class KXMLGUI_EXPORT KActionCollection : public QObject { friend class KXMLGUIClient; Q_OBJECT Q_PROPERTY(QString configGroup READ configGroup WRITE setConfigGroup) Q_PROPERTY(bool configIsGlobal READ configIsGlobal WRITE setConfigGlobal) public: /** * Constructor. Allows specification of a component name other than the default * application name, where needed (remember to call setComponentDisplayName() too). */ explicit KActionCollection(QObject *parent, const QString &cName = QString()); /** * Destructor. */ ~KActionCollection() override; /** * Access the list of all action collections in existence for this app */ static const QList &allCollections(); /** * Clears the entire action collection, deleting all actions. */ void clear(); /** * Associate all actions in this collection to the given @p widget. * Unlike addAssociatedWidget(), this method only adds all current actions * in the collection to the given widget. Any action added after this call * will not be added to the given widget automatically. * So this is just a shortcut for a foreach loop and a widget->addAction call. */ void associateWidget(QWidget *widget) const; /** * Associate all actions in this collection to the given @p widget, including any actions * added after this association is made. * * This does not change the action's shortcut context, so if you need to have the actions only * trigger when the widget has focus, you'll need to set the shortcut context on each action * to Qt::WidgetShortcut (or better still, Qt::WidgetWithChildrenShortcut with Qt 4.4+) */ void addAssociatedWidget(QWidget *widget); /** * Remove an association between all actions in this collection and the given @p widget, i.e. * remove those actions from the widget, and stop associating newly added actions as well. */ void removeAssociatedWidget(QWidget *widget); /** * Return a list of all associated widgets. */ QList associatedWidgets() const; /** * Clear all associated widgets and remove the actions from those widgets. */ void clearAssociatedWidgets(); /** * Returns the KConfig group with which settings will be loaded and saved. */ QString configGroup() const; /** * Returns whether this action collection's configuration should be global to KDE ( @c true ), * or specific to the application ( @c false ). */ bool configIsGlobal() const; /** * Sets @p group as the KConfig group with which settings will be loaded and saved. */ void setConfigGroup(const QString &group); /** * Set whether this action collection's configuration should be global to KDE ( @c true ), * or specific to the application ( @c false ). */ void setConfigGlobal(bool global); /** * Read all key associations from @p config. * * If @p config is zero, read all key associations from the * application's configuration file KSharedConfig::openConfig(), * in the group set by setConfigGroup(). */ void readSettings(KConfigGroup *config = nullptr); /** * Import from @p config all configurable global key associations. * * \since 4.1 * * \param config Config object to read from */ void importGlobalShortcuts(KConfigGroup *config); /** * Export the current configurable global key associations to @p config. * * \since 4.1 * * \param config Config object to save to * \param writeDefaults set to true to write settings which are already at defaults. */ void exportGlobalShortcuts(KConfigGroup *config, bool writeDefaults = false) const; /** * Write the current configurable key associations to @p config. What the * function does if @p config is zero depends. If this action collection * belongs to a KXMLGUIClient the setting are saved to the kxmlgui * definition file. If not the settings are written to the applications * config file. * * \note @p oneAction and @p writeDefaults have no meaning for the kxmlgui * configuration file. * * \param config Config object to save to, or null (see above) * \param writeDefaults set to true to write settings which are already at defaults. * \param oneAction pass an action here if you just want to save the values for one action, eg. * if you know that action is the only one which has changed. */ void writeSettings(KConfigGroup *config = nullptr, bool writeDefaults = false, QAction *oneAction = nullptr) const; /** * Returns the number of actions in the collection. * * This is equivalent to actions().count(). */ int count() const; /** * Returns whether the action collection is empty or not. */ bool isEmpty() const; /** * Return the QAction* at position @p index in the action collection. * * This is equivalent to actions().value(index); */ QAction *action(int index) const; /** * Get the action with the given \p name from the action collection. * * @param name Name of the QAction * @return A pointer to the QAction in the collection which matches the parameters or * null if nothing matches. */ QAction *action(const QString &name) const; /** * Returns the list of QActions which belong to this action collection. * * The list is guaranteed to be in the same order the action were put into * the collection. */ QList actions() const; /** * Returns the list of QActions without an QAction::actionGroup() which belong to this action collection. */ const QList actionsWithoutGroup() const; /** * Returns the list of all QActionGroups associated with actions in this action collection. */ const QList actionGroups() const; /** * Set the @p componentName associated with this action collection. * * \warning Don't call this method on a KActionCollection that contains * actions. This is not supported. * * \param componentData the name which is to be associated with this action collection, * or QString() to indicate the app name. This is used to load/save settings into XML files. * KXMLGUIClient::setComponentName takes care of calling this. */ void setComponentName(const QString &componentName); /** The component name with which this class is associated. */ QString componentName() const; /** * Set the component display name associated with this action collection. * (e.g. for the toolbar editor) * KXMLGUIClient::setComponentName takes care of calling this. */ void setComponentDisplayName(const QString &displayName); /** The display name for the associated component. */ QString componentDisplayName() const; /** * The parent KXMLGUIClient, or null if not available. */ const KXMLGUIClient *parentGUIClient() const; Q_SIGNALS: /** * Indicates that @p action was inserted into this action collection. */ void inserted(QAction *action); +#if KXMLGUI_ENABLE_DEPRECATED_SINCE(5, 0) /** * Indicates that @p action was removed from this action collection. - * @deprecated + * @deprecated Since 5.0 */ + KXMLGUI_DEPRECATED_VERSION(5, 0, "Do not rely on") QT_MOC_COMPAT void removed(QAction *action); +#endif +#if KXMLGUI_ENABLE_DEPRECATED_SINCE(5, 0) /** * Indicates that @p action was highlighted (hovered over). - * @deprecated Replaced by actionHovered(QAction* action); + * @deprecated Since 5.0. Replaced by actionHovered(QAction* action); */ + KXMLGUI_DEPRECATED_VERSION(5, 0, "Use KActionCollection::actionHovered(QAction* action)") QT_MOC_COMPAT void actionHighlighted(QAction *action); +#endif /** * Indicates that @p action was hovered. */ void actionHovered(QAction *action); /** * Indicates that @p action was triggered */ void actionTriggered(QAction *action); protected: /// Overridden to perform connections when someone wants to know whether an action was highlighted or triggered void connectNotify(const QMetaMethod &signal) override; protected Q_SLOTS: virtual void slotActionTriggered(); +#if KXMLGUI_ENABLE_DEPRECATED_SINCE(5, 0) /** * @internal - * @deprecated Replaced by slotActionHovered(); + * @deprecated Since 5.0. Replaced by slotActionHovered(); */ + KXMLGUI_DEPRECATED_VERSION(5, 0, "Use KActionCollection::slotActionHovered()") QT_MOC_COMPAT virtual void slotActionHighlighted(); +#endif private Q_SLOTS: void slotActionHovered(); public: /** * Add an action under the given name to the collection. * * Inserting an action that was previously inserted under a different name will replace the * old entry, i.e. the action will not be available under the old name anymore but only under * the new one. * * Inserting an action under a name that is already used for another action will replace * the other action in the collection (but will not delete it). * * If KAuthorized::authorizeAction() reports that the action is not * authorized, it will be disabled and hidden. * * The ownership of the action object is not transferred. * If the action is destroyed it will be removed automatically from the KActionCollection. * * @param name The name by which the action be retrieved again from the collection. * @param action The action to add. * @return the same as the action given as parameter. This is just for convenience * (chaining calls) and consistency with the other addAction methods, you can also * simply ignore the return value. */ Q_INVOKABLE QAction *addAction(const QString &name, QAction *action); /** * Adds a list of actions to the collection. * * The objectName of the actions is used as their internal name in the collection. * * The ownership of the action objects is not transferred. * If the action is destroyed it will be removed automatically from the KActionCollection. * * Uses addAction(const QString&, QAction*). * * @param actions the list of the actions to add. * * @see addAction() * @since 5.0 */ void addActions(const QList &actions); /** * Removes an action from the collection and deletes it. * @param action The action to remove. */ void removeAction(QAction *action); /** * Removes an action from the collection. * * The ownership of the action object is not changed. * * @param action the action to remove. */ QAction *takeAction(QAction *action); /** * Creates a new standard action, adds it to the collection and connects the * action's triggered(bool) signal to the specified receiver/member. The * newly created action is also returned. * * @note Using KStandardAction::OpenRecent will cause a different signal than * triggered(bool) to be used, see KStandardAction for more information. * * The action can be retrieved later from the collection by its standard name as per * KStandardAction::stdName. * * The KActionCollection takes ownership of the action object. * * @param actionType The standard action type of the action to create. * @param receiver The QObject to connect the triggered(bool) signal to. Leave nullptr if no * connection is desired. * @param member The SLOT to connect the triggered(bool) signal to. Leave nullptr if no * connection is desired. * @return new action of the given type ActionType. */ QAction *addAction(KStandardAction::StandardAction actionType, const QObject *receiver = nullptr, const char *member = nullptr); /** * Creates a new standard action, adds to the collection under the given name * and connects the action's triggered(bool) signal to the specified * receiver/member. The newly created action is also returned. * * @note Using KStandardAction::OpenRecent will cause a different signal than * triggered(bool) to be used, see KStandardAction for more information. * * The action can be retrieved later from the collection by the specified name. * * The KActionCollection takes ownership of the action object. * * @param actionType The standard action type of the action to create. * @param name The name by which the action be retrieved again from the collection. * @param receiver The QObject to connect the triggered(bool) signal to. Leave nullptr if no * connection is desired. * @param member The SLOT to connect the triggered(bool) signal to. Leave nullptr if no * connection is desired. * @return new action of the given type ActionType. */ QAction *addAction(KStandardAction::StandardAction actionType, const QString &name, const QObject *receiver = nullptr, const char *member = nullptr); /** * Creates a new action under the given name to the collection and connects * the action's triggered(bool) signal to the specified receiver/member. The * newly created action is returned. * * NOTE: KDE prior to 4.2 used the triggered() signal instead of the triggered(bool) * signal. * * Inserting an action that was previously inserted under a different name will replace the * old entry, i.e. the action will not be available under the old name anymore but only under * the new one. * * Inserting an action under a name that is already used for another action will replace * the other action in the collection. * * The KActionCollection takes ownership of the action object. * * @param name The name by which the action be retrieved again from the collection. * @param receiver The QObject to connect the triggered(bool) signal to. Leave nullptr if no * connection is desired. * @param member The SLOT to connect the triggered(bool) signal to. Leave nullptr if no * connection is desired. * @return new action of the given type ActionType. */ QAction *addAction(const QString &name, const QObject *receiver = nullptr, const char *member = nullptr); /** * Creates a new action under the given name, adds it to the collection and connects the action's triggered(bool) * signal to the specified receiver/member. The receiver slot may accept either a bool or no * parameters at all (i.e. slotTriggered(bool) or slotTriggered() ). * The type of the action is specified by the template parameter ActionType. * * NOTE: KDE prior to 4.2 connected the triggered() signal instead of the triggered(bool) * signal. * * The KActionCollection takes ownership of the action object. * * @param name The internal name of the action (e.g. "file-open"). * @param receiver The QObject to connect the triggered(bool) signal to. Leave nullptr if no * connection is desired. * @param member The SLOT to connect the triggered(bool) signal to. Leave nullptr if no * connection is desired. * @return new action of the given type ActionType. * * @see addAction() */ template ActionType *add(const QString &name, const QObject *receiver = nullptr, const char *member = nullptr) { ActionType *a = new ActionType(this); if (receiver && member) { connect(a, SIGNAL(triggered(bool)), receiver, member); } addAction(name, a); return a; } /** * This is the same as add(const QString &name, const QObject *receiver, const char *member) using * new style connect syntax. * * @param name The internal name of the action (e.g. "file-open"). * @param receiver The QObject to connect the triggered(bool) signal to. * @param slot The slot or lambda to connect the triggered(bool) signal to. * @return new action of the given type ActionType. * * @see add(const QString &, const QObject *, const char *) * @since 5.28 */ #ifdef DOXYGEN_SHOULD_SKIP_THIS template inline ActionType *add(const QString &name, const Receiver *receiver, Func slot) #else template inline typename std::enable_if::value, ActionType>::type *add( const QString &name, const Receiver *receiver, Func slot) #endif { ActionType *a = new ActionType(this); connect(a, &QAction::triggered, receiver, slot); addAction(name, a); return a; } /** * This is the same as addAction(const QString &name, const QObject *receiver, const char *member) using * new style connect syntax. * * @param name The internal name of the action (e.g. "file-open"). * @param receiver The QObject to connect the triggered(bool) signal to. * @param slot The slot or lambda to connect the triggered(bool) signal to. * @return new action of the given type ActionType. * * @see addAction(const QString &, const QObject *, const char *) * @since 5.28 */ #ifdef DOXYGEN_SHOULD_SKIP_THIS inline QAction *addAction(const QString &name, const Receiver *receiver, Func slot) #else template inline typename std::enable_if::value, QAction>::type *addAction( const QString &name, const Receiver *receiver, Func slot) #endif { return add(name, receiver, slot); } /** * Get the default primary shortcut for the given action. * * @param action the action for which the default primary shortcut should be returned. * @return the default primary shortcut of the given action * @since 5.0 */ QKeySequence defaultShortcut(QAction *action) const; /** * Get the default shortcuts for the given action. * * @param action the action for which the default shortcuts should be returned. * @return the default shortcuts of the given action * @since 5.0 */ QList defaultShortcuts(QAction *action) const; //TODO KF6: Make setDefaultShortcut static /** * Set the default shortcut for the given action. * Since 5.2, this also calls action->setShortcut(shortcut), i.e. the default shortcut is * made active initially. * * @param action the action for which the default shortcut should be set. * @param shortcut the shortcut to use for the given action in its specified shortcutContext() * @since 5.0 */ void setDefaultShortcut(QAction *action, const QKeySequence &shortcut); /** * Set the default shortcuts for the given action. * Since 5.2, this also calls action->setShortcuts(shortcuts), i.e. the default shortcut is * made active initially. * * @param action the action for which the default shortcut should be set. * @param shortcuts the shortcuts to use for the given action in its specified shortcutContext() * @since 5.0 */ Q_INVOKABLE void setDefaultShortcuts(QAction *action, const QList &shortcuts); /** * Returns true if the given action's shortcuts may be configured by the user. * * @param action the action for the hint should be verified. * @since 5.0 */ bool isShortcutsConfigurable(QAction *action) const; /** * Indicate whether the user may configure the action's shortcuts. * * @param action the action for the hint should be verified. * @param configurable set to true if the shortcuts of the given action may be configured by the user, otherwise false. * @since 5.0 */ void setShortcutsConfigurable(QAction *action, bool configurable); private: Q_PRIVATE_SLOT(d, void _k_actionDestroyed(QObject *)) Q_PRIVATE_SLOT(d, void _k_associatedWidgetDestroyed(QObject *)) KActionCollection(const KXMLGUIClient *parent); // used by KXMLGUIClient friend class KActionCollectionPrivate; class KActionCollectionPrivate *const d; }; #endif diff --git a/src/kkeysequencewidget.cpp b/src/kkeysequencewidget.cpp index f38e503..7f3d0c1 100644 --- a/src/kkeysequencewidget.cpp +++ b/src/kkeysequencewidget.cpp @@ -1,861 +1,861 @@ /* This file is part of the KDE libraries Copyright (C) 1998 Mark Donohoe Copyright (C) 2001 Ellis Whitehead Copyright (C) 2007 Andreas Hartmetz This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library 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 Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "config-xmlgui.h" #include "kkeysequencewidget.h" #include "kkeysequencewidget_p.h" #include "debug.h" #include #include #include #include #include #include #include #include #include #include #if HAVE_GLOBALACCEL # include #endif #include "kactioncollection.h" class KKeySequenceWidgetPrivate { public: KKeySequenceWidgetPrivate(KKeySequenceWidget *q); void init(); static QKeySequence appendToSequence(const QKeySequence &seq, int keyQt); static bool isOkWhenModifierless(int keyQt); void updateShortcutDisplay(); void startRecording(); /** * Conflicts the key sequence @p seq with a current standard * shortcut? */ bool conflictWithStandardShortcuts(const QKeySequence &seq); /** * Conflicts the key sequence @p seq with a current local * shortcut? */ bool conflictWithLocalShortcuts(const QKeySequence &seq); /** * Conflicts the key sequence @p seq with a current global * shortcut? */ bool conflictWithGlobalShortcuts(const QKeySequence &seq); /** * Get permission to steal the shortcut @seq from the standard shortcut @p std. */ bool stealStandardShortcut(KStandardShortcut::StandardShortcut std, const QKeySequence &seq); bool checkAgainstStandardShortcuts() const { return checkAgainstShortcutTypes & KKeySequenceWidget::StandardShortcuts; } bool checkAgainstGlobalShortcuts() const { return checkAgainstShortcutTypes & KKeySequenceWidget::GlobalShortcuts; } bool checkAgainstLocalShortcuts() const { return checkAgainstShortcutTypes & KKeySequenceWidget::LocalShortcuts; } void controlModifierlessTimout() { if (nKey != 0 && !modifierKeys) { // No modifier key pressed currently. Start the timout modifierlessTimeout.start(600); } else { // A modifier is pressed. Stop the timeout modifierlessTimeout.stop(); } } void cancelRecording() { keySequence = oldKeySequence; doneRecording(); } #if HAVE_GLOBALACCEL bool promptStealShortcutSystemwide( QWidget *parent, const QHash > &shortcuts, const QKeySequence &sequence) { if (shortcuts.isEmpty()) { // Usage error. Just say no return false; } QString clashingKeys; for (auto it = shortcuts.begin(), end = shortcuts.end(); it != end; ++it) { const auto seqAsString = it.key().toString(); for (const KGlobalShortcutInfo &info : it.value()) { clashingKeys += i18n("Shortcut '%1' in Application %2 for action %3\n", seqAsString, info.componentFriendlyName(), info.friendlyName()); } } const int hashSize = shortcuts.size(); QString message = i18ncp("%1 is the number of conflicts (hidden), %2 is the key sequence of the shortcut that is problematic", "The shortcut '%2' conflicts with the following key combination:\n", "The shortcut '%2' conflicts with the following key combinations:\n", hashSize, sequence.toString()); message += clashingKeys; QString title = i18ncp("%1 is the number of shortcuts with which there is a conflict", "Conflict with Registered Global Shortcut", "Conflict with Registered Global Shortcuts", hashSize); return KMessageBox::warningContinueCancel(parent, message, title, KGuiItem(i18n("Reassign"))) == KMessageBox::Continue; } #endif //private slot void doneRecording(bool validate = true); //members KKeySequenceWidget *const q; QHBoxLayout *layout; KKeySequenceButton *keyButton; QToolButton *clearButton; QKeySequence keySequence; QKeySequence oldKeySequence; QTimer modifierlessTimeout; bool allowModifierless; uint nKey; uint modifierKeys; bool isRecording; bool multiKeyShortcutsAllowed; QString componentName; //! Check the key sequence against KStandardShortcut::find() KKeySequenceWidget::ShortcutTypes checkAgainstShortcutTypes; /** * The list of action to check against for conflict shortcut */ QList checkList; // deprecated /** * The list of action collections to check against for conflict shortcut */ QList checkActionCollections; /** * The action to steal the shortcut from. */ QList stealActions; bool stealShortcuts(const QList &actions, const QKeySequence &seq); void wontStealShortcut(QAction *item, const QKeySequence &seq); }; KKeySequenceWidgetPrivate::KKeySequenceWidgetPrivate(KKeySequenceWidget *q) : q(q) , layout(nullptr) , keyButton(nullptr) , clearButton(nullptr) , allowModifierless(false) , nKey(0) , modifierKeys(0) , isRecording(false) , multiKeyShortcutsAllowed(true) , componentName() , checkAgainstShortcutTypes(KKeySequenceWidget::LocalShortcuts | KKeySequenceWidget::GlobalShortcuts) , stealActions() {} bool KKeySequenceWidgetPrivate::stealShortcuts( const QList &actions, const QKeySequence &seq) { const int listSize = actions.size(); QString title = i18ncp("%1 is the number of conflicts", "Shortcut Conflict", "Shortcut Conflicts", listSize); QString conflictingShortcuts; for (const QAction *action : actions) { conflictingShortcuts += i18n("Shortcut '%1' for action '%2'\n", action->shortcut().toString(QKeySequence::NativeText), KLocalizedString::removeAcceleratorMarker(action->text())); } QString message = i18ncp("%1 is the number of ambigious shortcut clashes (hidden)", "The \"%2\" shortcut is ambiguous with the following shortcut.\n" "Do you want to assign an empty shortcut to this action?\n" "%3", "The \"%2\" shortcut is ambiguous with the following shortcuts.\n" "Do you want to assign an empty shortcut to these actions?\n" "%3", listSize, seq.toString(QKeySequence::NativeText), conflictingShortcuts); if (KMessageBox::warningContinueCancel(q, message, title, KGuiItem(i18n("Reassign"))) != KMessageBox::Continue) { return false; } return true; } void KKeySequenceWidgetPrivate::wontStealShortcut(QAction *item, const QKeySequence &seq) { QString title(i18n("Shortcut conflict")); QString msg(i18n("The '%1' key combination is already used by the %2 action.
" "Please select a different one.
", seq.toString(QKeySequence::NativeText), KLocalizedString::removeAcceleratorMarker(item->text()))); KMessageBox::sorry(q, msg, title); } KKeySequenceWidget::KKeySequenceWidget(QWidget *parent) : QWidget(parent), d(new KKeySequenceWidgetPrivate(this)) { d->init(); setFocusProxy(d->keyButton); connect(d->keyButton, &KKeySequenceButton::clicked, this, &KKeySequenceWidget::captureKeySequence); connect(d->clearButton, &QToolButton::clicked, this, &KKeySequenceWidget::clearKeySequence); connect(&d->modifierlessTimeout, SIGNAL(timeout()), this, SLOT(doneRecording())); //TODO: how to adopt style changes at runtime? /*QFont modFont = d->clearButton->font(); modFont.setStyleHint(QFont::TypeWriter); d->clearButton->setFont(modFont);*/ d->updateShortcutDisplay(); } void KKeySequenceWidgetPrivate::init() { layout = new QHBoxLayout(q); layout->setContentsMargins(0, 0, 0, 0); keyButton = new KKeySequenceButton(this, q); keyButton->setFocusPolicy(Qt::StrongFocus); keyButton->setIcon(QIcon::fromTheme(QStringLiteral("configure"))); keyButton->setToolTip(i18n("Click on the button, then enter the shortcut like you would in the program.\nExample for Ctrl+A: hold the Ctrl key and press A.")); layout->addWidget(keyButton); clearButton = new QToolButton(q); layout->addWidget(clearButton); if (qApp->isLeftToRight()) { clearButton->setIcon(QIcon::fromTheme(QStringLiteral("edit-clear-locationbar-rtl"))); } else { clearButton->setIcon(QIcon::fromTheme(QStringLiteral("edit-clear-locationbar-ltr"))); } } KKeySequenceWidget::~KKeySequenceWidget() { delete d; } KKeySequenceWidget::ShortcutTypes KKeySequenceWidget::checkForConflictsAgainst() const { return d->checkAgainstShortcutTypes; } void KKeySequenceWidget::setComponentName(const QString &componentName) { d->componentName = componentName; } bool KKeySequenceWidget::multiKeyShortcutsAllowed() const { return d->multiKeyShortcutsAllowed; } void KKeySequenceWidget::setMultiKeyShortcutsAllowed(bool allowed) { d->multiKeyShortcutsAllowed = allowed; } void KKeySequenceWidget::setCheckForConflictsAgainst(ShortcutTypes types) { d->checkAgainstShortcutTypes = types; } void KKeySequenceWidget::setModifierlessAllowed(bool allow) { d->allowModifierless = allow; } bool KKeySequenceWidget::isKeySequenceAvailable(const QKeySequence &keySequence) const { if (keySequence.isEmpty()) { return true; } return !(d->conflictWithLocalShortcuts(keySequence) || d->conflictWithGlobalShortcuts(keySequence) || d->conflictWithStandardShortcuts(keySequence)); } bool KKeySequenceWidget::isModifierlessAllowed() { return d->allowModifierless; } void KKeySequenceWidget::setClearButtonShown(bool show) { d->clearButton->setVisible(show); } -#ifndef KXMLGUI_NO_DEPRECATED +#if KXMLGUI_BUILD_DEPRECATED_SINCE(4, 1) void KKeySequenceWidget::setCheckActionList(const QList &checkList) // deprecated { d->checkList = checkList; Q_ASSERT(d->checkActionCollections.isEmpty()); // don't call this method if you call setCheckActionCollections! } #endif void KKeySequenceWidget::setCheckActionCollections(const QList &actionCollections) { d->checkActionCollections = actionCollections; } //slot void KKeySequenceWidget::captureKeySequence() { d->startRecording(); } QKeySequence KKeySequenceWidget::keySequence() const { return d->keySequence; } //slot void KKeySequenceWidget::setKeySequence(const QKeySequence &seq, Validation validate) { // oldKeySequence holds the key sequence before recording started, if setKeySequence() // is called while not recording then set oldKeySequence to the existing sequence so // that the keySequenceChanged() signal is emitted if the new and previous key // sequences are different if (!d->isRecording) { d->oldKeySequence = d->keySequence; } d->keySequence = seq; d->doneRecording(validate == Validate); } //slot void KKeySequenceWidget::clearKeySequence() { setKeySequence(QKeySequence()); } //slot void KKeySequenceWidget::applyStealShortcut() { QSet changedCollections; for (QAction *stealAction : qAsConst(d->stealActions)) { // Stealing a shortcut means setting it to an empty one. stealAction->setShortcuts(QList()); // The following code will find the action we are about to // steal from and save it's actioncollection. KActionCollection *parentCollection = nullptr; for (KActionCollection *collection : qAsConst(d->checkActionCollections)) { if (collection->actions().contains(stealAction)) { parentCollection = collection; break; } } // Remember the changed collection if (parentCollection) { changedCollections.insert(parentCollection); } } for (KActionCollection *col : qAsConst(changedCollections)) { col->writeSettings(); } d->stealActions.clear(); } void KKeySequenceWidgetPrivate::startRecording() { nKey = 0; modifierKeys = 0; oldKeySequence = keySequence; keySequence = QKeySequence(); isRecording = true; keyButton->grabKeyboard(); if (!QWidget::keyboardGrabber()) { qCWarning(DEBUG_KXMLGUI) << "Failed to grab the keyboard! Most likely qt's nograb option is active"; } keyButton->setDown(true); updateShortcutDisplay(); } void KKeySequenceWidgetPrivate::doneRecording(bool validate) { modifierlessTimeout.stop(); isRecording = false; keyButton->releaseKeyboard(); keyButton->setDown(false); stealActions.clear(); if (keySequence == oldKeySequence) { // The sequence hasn't changed updateShortcutDisplay(); return; } if (validate && !q->isKeySequenceAvailable(keySequence)) { // The sequence had conflicts and the user said no to stealing it keySequence = oldKeySequence; } else { emit q->keySequenceChanged(keySequence); } updateShortcutDisplay(); } bool KKeySequenceWidgetPrivate::conflictWithGlobalShortcuts(const QKeySequence &keySequence) { #ifdef Q_OS_WIN //on windows F12 is reserved by the debugger at all times, so we can't use it for a global shortcut if (KKeySequenceWidget::GlobalShortcuts && keySequence.toString().contains(QLatin1String("F12"))) { QString title = i18n("Reserved Shortcut"); QString message = i18n("The F12 key is reserved on Windows, so cannot be used for a global shortcut.\n" "Please choose another one."); KMessageBox::sorry(q, message, title); return false; } #endif #if HAVE_GLOBALACCEL if (!(checkAgainstShortcutTypes & KKeySequenceWidget::GlobalShortcuts)) { return false; } // Global shortcuts are on key+modifier shortcuts. They can clash with // each of the keys of a multi key shortcut. QHash > others; for (int i = 0; i < keySequence.count(); ++i) { QKeySequence tmp(keySequence[i]); if (!KGlobalAccel::isGlobalShortcutAvailable(tmp, componentName)) { others.insert(tmp, KGlobalAccel::getGlobalShortcutsByKey(tmp)); } } if (!others.isEmpty() && !promptStealShortcutSystemwide(q, others, keySequence)) { return true; } // The user approved stealing the shortcut. We have to steal // it immediately because KAction::setGlobalShortcut() refuses // to set a global shortcut that is already used. There is no // error it just silently fails. So be nice because this is // most likely the first action that is done in the slot // listening to keySequenceChanged(). for (int i = 0; i < keySequence.count(); ++i) { KGlobalAccel::stealShortcutSystemwide(keySequence[i]); } return false; #else Q_UNUSED(keySequence); return false; #endif } static bool shortcutsConflictWith(const QList &shortcuts, const QKeySequence &needle) { if (needle.isEmpty()) { return false; } for (const QKeySequence &sequence : shortcuts) { if (sequence.isEmpty()) { continue; } if (sequence.matches(needle) != QKeySequence::NoMatch || needle.matches(sequence) != QKeySequence::NoMatch) { return true; } } return false; } bool KKeySequenceWidgetPrivate::conflictWithLocalShortcuts(const QKeySequence &keySequence) { if (!(checkAgainstShortcutTypes & KKeySequenceWidget::LocalShortcuts)) { return false; } // We have actions both in the deprecated checkList and the // checkActionCollections list. Add all the actions to a single list to // be able to process them in a single loop below. // Note that this can't be done in setCheckActionCollections(), because we // keep pointers to the action collections, and between the call to // setCheckActionCollections() and this function some actions might already be // removed from the collection again. QList allActions; allActions += checkList; for (KActionCollection *collection : qAsConst(checkActionCollections)) { allActions += collection->actions(); } // Because of multikey shortcuts we can have clashes with many shortcuts. // // Example 1: // // Application currently uses 'CTRL-X,a', 'CTRL-X,f' and 'CTRL-X,CTRL-F' // and the user wants to use 'CTRL-X'. 'CTRL-X' will only trigger as // 'activatedAmbiguously()' for obvious reasons. // // Example 2: // // Application currently uses 'CTRL-X'. User wants to use 'CTRL-X,CTRL-F'. // This will shadow 'CTRL-X' for the same reason as above. // // Example 3: // // Some weird combination of Example 1 and 2 with three shortcuts using // 1/2/3 key shortcuts. I think you can imagine. QList conflictingActions; //find conflicting shortcuts with existing actions for (QAction *qaction : qAsConst(allActions)) { if (shortcutsConflictWith(qaction->shortcuts(), keySequence)) { // A conflict with a KAction. If that action is configurable // ask the user what to do. If not reject this keySequence. if (checkActionCollections.first()->isShortcutsConfigurable(qaction)) { conflictingActions.append(qaction); } else { wontStealShortcut(qaction, keySequence); return true; } } } if (conflictingActions.isEmpty()) { // No conflicting shortcuts found. return false; } if (stealShortcuts(conflictingActions, keySequence)) { stealActions = conflictingActions; // Announce that the user // agreed Q_FOREACH (QAction *stealAction, stealActions) { emit q->stealShortcut( keySequence, stealAction); } return false; } else { return true; } } bool KKeySequenceWidgetPrivate::conflictWithStandardShortcuts(const QKeySequence &keySequence) { if (!(checkAgainstShortcutTypes & KKeySequenceWidget::StandardShortcuts)) { return false; } KStandardShortcut::StandardShortcut ssc = KStandardShortcut::find(keySequence); if (ssc != KStandardShortcut::AccelNone && !stealStandardShortcut(ssc, keySequence)) { return true; } return false; } bool KKeySequenceWidgetPrivate::stealStandardShortcut(KStandardShortcut::StandardShortcut std, const QKeySequence &seq) { QString title = i18n("Conflict with Standard Application Shortcut"); QString message = i18n("The '%1' key combination is also used for the standard action " "\"%2\" that some applications use.\n" "Do you really want to use it as a global shortcut as well?", seq.toString(QKeySequence::NativeText), KStandardShortcut::label(std)); if (KMessageBox::warningContinueCancel(q, message, title, KGuiItem(i18n("Reassign"))) != KMessageBox::Continue) { return false; } return true; } void KKeySequenceWidgetPrivate::updateShortcutDisplay() { //empty string if no non-modifier was pressed QString s = keySequence.toString(QKeySequence::NativeText); s.replace(QLatin1Char('&'), QLatin1String("&&")); if (isRecording) { if (modifierKeys) { if (!s.isEmpty()) { s.append(QLatin1Char(',')); } if (modifierKeys & Qt::MetaModifier) { s += QKeySequence(Qt::MetaModifier).toString(QKeySequence::NativeText); } #if defined(Q_OS_MAC) if (modifierKeys & Qt::AltModifier) { s += QKeySequence(Qt::AltModifier).toString(QKeySequence::NativeText); } if (modifierKeys & Qt::ControlModifier) { s += QKeySequence(Qt::ControlModifier).toString(QKeySequence::NativeText); } #else if (modifierKeys & Qt::ControlModifier) { s += QKeySequence(Qt::ControlModifier).toString(QKeySequence::NativeText); } if (modifierKeys & Qt::AltModifier) { s += QKeySequence(Qt::AltModifier).toString(QKeySequence::NativeText); } #endif if (modifierKeys & Qt::ShiftModifier) { s += QKeySequence(Qt::ShiftModifier).toString(QKeySequence::NativeText); } if (modifierKeys & Qt::KeypadModifier) { s += QKeySequence(Qt::KeypadModifier).toString(QKeySequence::NativeText); } } else if (nKey == 0) { s = i18nc("What the user inputs now will be taken as the new shortcut", "Input"); } //make it clear that input is still going on s.append(QLatin1String(" ...")); } if (s.isEmpty()) { s = i18nc("No shortcut defined", "None"); } s = QLatin1Char(' ') + s + QLatin1Char(' '); keyButton->setText(s); } KKeySequenceButton::~KKeySequenceButton() { } //prevent Qt from special casing Tab and Backtab bool KKeySequenceButton::event(QEvent *e) { if (d->isRecording && e->type() == QEvent::KeyPress) { keyPressEvent(static_cast(e)); return true; } // The shortcut 'alt+c' ( or any other dialog local action shortcut ) // ended the recording and triggered the action associated with the // action. In case of 'alt+c' ending the dialog. It seems that those // ShortcutOverride events get sent even if grabKeyboard() is active. if (d->isRecording && e->type() == QEvent::ShortcutOverride) { e->accept(); return true; } if (d->isRecording && e->type() == QEvent::ContextMenu) { // is caused by Qt::Key_Menu e->accept(); return true; } return QPushButton::event(e); } void KKeySequenceButton::keyPressEvent(QKeyEvent *e) { int keyQt = e->key(); if (keyQt == -1) { // Qt sometimes returns garbage keycodes, I observed -1, if it doesn't know a key. // We cannot do anything useful with those (several keys have -1, indistinguishable) // and QKeySequence.toString() will also yield a garbage string. KMessageBox::sorry(this, i18n("The key you just pressed is not supported by Qt."), i18n("Unsupported Key")); d->cancelRecording(); return; } uint newModifiers = e->modifiers() & (Qt::ShiftModifier | Qt::ControlModifier | Qt::AltModifier | Qt::MetaModifier | Qt::KeypadModifier); //don't have the return or space key appear as first key of the sequence when they //were pressed to start editing - catch and them and imitate their effect if (!d->isRecording && ((keyQt == Qt::Key_Return || keyQt == Qt::Key_Space))) { d->startRecording(); d->modifierKeys = newModifiers; d->updateShortcutDisplay(); return; } // We get events even if recording isn't active. if (!d->isRecording) { QPushButton::keyPressEvent(e); return; } e->accept(); d->modifierKeys = newModifiers; switch (keyQt) { case Qt::Key_AltGr: //or else we get unicode salad return; case Qt::Key_Shift: case Qt::Key_Control: case Qt::Key_Alt: case Qt::Key_Meta: case Qt::Key_Super_L: case Qt::Key_Super_R: d->controlModifierlessTimout(); d->updateShortcutDisplay(); break; default: if (d->nKey == 0 && !(d->modifierKeys & ~Qt::SHIFT)) { // It's the first key and no modifier pressed. Check if this is // allowed if (!(KKeySequenceWidgetPrivate::isOkWhenModifierless(keyQt) || d->allowModifierless)) { // No it's not return; } } // We now have a valid key press. if (keyQt) { if ((keyQt == Qt::Key_Backtab) && (d->modifierKeys & Qt::SHIFT)) { keyQt = Qt::Key_Tab | d->modifierKeys; } else if (KKeyServer::isShiftAsModifierAllowed(keyQt)) { keyQt |= d->modifierKeys; } else { keyQt |= (d->modifierKeys & ~Qt::SHIFT); } if (d->nKey == 0) { d->keySequence = QKeySequence(keyQt); } else { d->keySequence = KKeySequenceWidgetPrivate::appendToSequence(d->keySequence, keyQt); } d->nKey++; if ((!d->multiKeyShortcutsAllowed) || (d->nKey >= 4)) { d->doneRecording(); return; } d->controlModifierlessTimout(); d->updateShortcutDisplay(); } } } void KKeySequenceButton::keyReleaseEvent(QKeyEvent *e) { if (e->key() == -1) { // ignore garbage, see keyPressEvent() return; } if (!d->isRecording) { QPushButton::keyReleaseEvent(e); return; } e->accept(); uint newModifiers = e->modifiers() & (Qt::ShiftModifier | Qt::ControlModifier | Qt::AltModifier | Qt::MetaModifier | Qt::KeypadModifier); //if a modifier that belongs to the shortcut was released... if ((newModifiers & d->modifierKeys) < d->modifierKeys) { d->modifierKeys = newModifiers; d->controlModifierlessTimout(); d->updateShortcutDisplay(); } } //static QKeySequence KKeySequenceWidgetPrivate::appendToSequence(const QKeySequence &seq, int keyQt) { switch (seq.count()) { case 0: return QKeySequence(keyQt); case 1: return QKeySequence(seq[0], keyQt); case 2: return QKeySequence(seq[0], seq[1], keyQt); case 3: return QKeySequence(seq[0], seq[1], seq[2], keyQt); default: return seq; } } //static bool KKeySequenceWidgetPrivate::isOkWhenModifierless(int keyQt) { //this whole function is a hack, but especially the first line of code if (QKeySequence(keyQt).toString().length() == 1) { return false; } switch (keyQt) { case Qt::Key_Return: case Qt::Key_Space: case Qt::Key_Tab: case Qt::Key_Backtab: //does this ever happen? case Qt::Key_Backspace: case Qt::Key_Delete: return false; default: return true; } } #include "moc_kkeysequencewidget.cpp" diff --git a/src/kkeysequencewidget.h b/src/kkeysequencewidget.h index 237b310..7955cb8 100644 --- a/src/kkeysequencewidget.h +++ b/src/kkeysequencewidget.h @@ -1,310 +1,311 @@ /* This file is part of the KDE libraries Copyright (C) 2001, 2002 Ellis Whitehead Copyright (C) 2007 Andreas Hartmetz This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library 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 Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef KKEYSEQUENCEWIDGET_H #define KKEYSEQUENCEWIDGET_H #include #include #include class KKeySequenceWidgetPrivate; class QAction; class KActionCollection; /** * @short A widget to input a QKeySequence. * * This widget lets the user choose a QKeySequence, which is usually used as a * shortcut key. The recording is initiated by calling captureKeySequence() or * the user clicking into the widget. * * The widgets provides support for conflict handling. See * setCheckForConflictsAgainst() for more information. * * \image html kkeysequencewidget.png "KKeySequenceWidget" * * @author Mark Donohoe * @internal */ class KXMLGUI_EXPORT KKeySequenceWidget: public QWidget { Q_OBJECT Q_PROPERTY( bool multiKeyShortcutsAllowed READ multiKeyShortcutsAllowed WRITE setMultiKeyShortcutsAllowed) Q_PROPERTY( ShortcutTypes checkForConflictsAgainst READ checkForConflictsAgainst WRITE setCheckForConflictsAgainst) Q_PROPERTY( bool modifierlessAllowed READ isModifierlessAllowed WRITE setModifierlessAllowed) public: ///An enum about validation when setting a key sequence. ///@see setKeySequence() enum Validation { ///Validate key sequence Validate = 0, ///Use key sequence without validation NoValidate = 1 }; /** * Constructor. */ explicit KKeySequenceWidget(QWidget *parent = nullptr); /** * Destructs the widget. */ virtual ~KKeySequenceWidget(); /** * \name Configuration * * Configuration options for the widget. */ //@{ enum ShortcutType { None = 0x00, //!< No checking for conflicts LocalShortcuts = 0x01, //!< Check with local shortcuts. @see setCheckActionCollections() StandardShortcuts = 0x02, //!< Check against standard shortcuts. @see KStandardShortcut GlobalShortcuts = 0x04 //!< Check against global shortcuts. @see KGlobalAccel }; Q_DECLARE_FLAGS(ShortcutTypes, ShortcutType) Q_FLAG(ShortcutTypes) /** * Configure if the widget should check for conflicts with existing * shortcuts. * * When capturing a key sequence for local shortcuts you should check * against GlobalShortcuts and your other local shortcuts. This is the * default. * * You have to provide the local actions to check against with * setCheckActionCollections(). * * When capturing a key sequence for a global shortcut you should * check against StandardShortcuts, GlobalShortcuts and your local * shortcuts. * * There are two ways to react to a user agreeing to steal a shortcut: * * 1. Listen to the stealShortcut() signal and steal the shortcuts * manually. It's your responsibility to save that change later when * you think it is appropriate. * * 2. Call applyStealShortcut() and KKeySequenceWidget will steal the * shortcut. This will save the actionCollections the shortcut is part * of so make sure it doesn't inadvertly save some unwanted changes * too. Read its documentation for some limitation when handling * global shortcuts. * * If you want to do the conflict checking yourself here are some code * snippets for global ... * * \code * QStringList conflicting = KGlobalAccel::findActionNameSystemwide(keySequence); * if (!conflicting.isEmpty()) { * // Inform and ask the user about the conflict and reassigning * // the keys sequence * if (!KGlobalAccel::promptStealShortcutSystemwide(q, conflicting, keySequence)) { * return true; * } * KGlobalAccel::stealShortcutSystemwide(keySequence); * } * \endcode * * ... and standard shortcuts * * \code * KStandardShortcut::StandardShortcut ssc = KStandardShortcut::find(keySequence); * if (ssc != KStandardShortcut::AccelNone) { * // We have a conflict * } * \endcode * * * @since 4.2 */ void setCheckForConflictsAgainst(ShortcutTypes types); /** * The shortcut types we check for conflicts. * * @see setCheckForConflictsAgainst() * @since 4.2 */ ShortcutTypes checkForConflictsAgainst() const; /** * Allow multikey shortcuts? */ void setMultiKeyShortcutsAllowed(bool); bool multiKeyShortcutsAllowed() const; /** * This only applies to user input, not to setShortcut(). * Set whether to accept "plain" keys without modifiers (like Ctrl, Alt, Meta). * Plain keys by our definition include letter and symbol keys and * text editing keys (Return, Space, Tab, Backspace, Delete). * "Special" keys like F1, Cursor keys, Insert, PageDown will always work. */ void setModifierlessAllowed(bool allow); /** * @see setModifierlessAllowed() */ bool isModifierlessAllowed(); /** * Set whether a small button to set an empty key sequence should be displayed next to the * main input widget. The default is to show the clear button. */ void setClearButtonShown(bool show); //@} /** * Checks whether the key sequence @p seq is available to grab. * * The sequence is checked under the same rules as if it has been typed by * the user. This method is useful if you get key sequences from another * input source and want to check if it is save to set them. * * @since 4.2 */ bool isKeySequenceAvailable(const QKeySequence &seq) const; /** * Return the currently selected key sequence. */ QKeySequence keySequence() const; /** * Set a list of action collections to check against for conflictuous shortcut. * * @see setCheckForConflictsAgainst() * * If a QAction with a conflicting shortcut is found inside this list and * its shortcut can be configured (KActionCollection::isShortcutConfigurable() * returns true) the user will be prompted whether to steal the shortcut * from this action. * * @since 4.1 */ void setCheckActionCollections(const QList &actionCollections); +#if KXMLGUI_ENABLE_DEPRECATED_SINCE(4, 1) /** * @deprecated since 4.1 * use setCheckActionCollections so that KKeySequenceWidget knows * in which action collection to call the writeSettings method after stealing * a shortcut from an action. */ -#ifndef KXMLGUI_NO_DEPRECATED - KXMLGUI_DEPRECATED void setCheckActionList(const QList &checkList); + KXMLGUI_DEPRECATED_VERSION(4, 1, "Use KKeySequenceWidget::setCheckActionCollections(const QList &)") + void setCheckActionList(const QList &checkList); #endif /** * If the component using this widget supports shortcuts contexts, it has * to set its component name so we can check conflicts correctly. */ void setComponentName(const QString &componentName); Q_SIGNALS: /** * This signal is emitted when the current key sequence has changed, be it by user * input or programmatically. */ void keySequenceChanged(const QKeySequence &seq); /** * This signal is emitted after the user agreed to steal a shortcut from * an action. This is only done for local shortcuts. So you can be sure \a * action is one of the actions you provided with setCheckActionList() or * setCheckActionCollections(). * * If you listen to that signal and don't call applyStealShortcut() you * are supposed to steal the shortcut and save this change. */ void stealShortcut(const QKeySequence &seq, QAction *action); public Q_SLOTS: /** * Capture a shortcut from the keyboard. This call will only return once a key sequence * has been captured or input was aborted. * If a key sequence was input, keySequenceChanged() will be emitted. * * @see setModifierlessAllowed() */ void captureKeySequence(); /** * Set the key sequence. * * If @p val == Validate, and the call is actually changing the key sequence, * conflictuous shortcut will be checked. */ void setKeySequence(const QKeySequence &seq, Validation val = NoValidate); /** * Clear the key sequence. */ void clearKeySequence(); /** * Actually remove the shortcut that the user wanted to steal, from the * action that was using it. This only applies to actions provided to us * by setCheckActionCollections() and setCheckActionList(). * * Global and Standard Shortcuts have to be stolen immediately when the * user gives his consent (technical reasons). That means those changes * will be active even if you never call applyStealShortcut(). * * To be called before you apply your changes. No local shortcuts are * stolen until this function is called. */ void applyStealShortcut(); private: Q_PRIVATE_SLOT(d, void doneRecording()) private: friend class KKeySequenceWidgetPrivate; KKeySequenceWidgetPrivate *const d; Q_DISABLE_COPY(KKeySequenceWidget) }; Q_DECLARE_OPERATORS_FOR_FLAGS(KKeySequenceWidget::ShortcutTypes) #endif //KKEYSEQUENCEWIDGET_H diff --git a/src/kmainwindow.cpp b/src/kmainwindow.cpp index 5f1c839..34d60d4 100644 --- a/src/kmainwindow.cpp +++ b/src/kmainwindow.cpp @@ -1,942 +1,942 @@ /* This file is part of the KDE libraries Copyright (C) 2000 Reginald Stadlbauer (reggie@kde.org) (C) 1997 Stephan Kulow (coolo@kde.org) (C) 1997-2000 Sven Radej (radej@kde.org) (C) 1997-2000 Matthias Ettrich (ettrich@kde.org) (C) 1999 Chris Schlaeger (cs@kde.org) (C) 2002 Joseph Wenninger (jowenn@kde.org) (C) 2005-2006 Hamish Rodda (rodda@kde.org) (C) 2000-2008 David Faure (faure@kde.org) This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License version 2 as published by the Free Software Foundation. This library 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 Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kmainwindow.h" #include "kmainwindow_p.h" #ifdef QT_DBUS_LIB #include "kmainwindowiface_p.h" #endif #include "ktoolbarhandler_p.h" #include "khelpmenu.h" #include "ktoolbar.h" #include #include #include #include #include #include #include #include #include #include #include #include #ifdef QT_DBUS_LIB #include #endif #include #include #include #include #include #include #include #include #include //#include static const char WINDOW_PROPERTIES[]="WindowProperties"; static QMenuBar *internalMenuBar(KMainWindow *mw) { return mw->findChild(QString(), Qt::FindDirectChildrenOnly); } static QStatusBar *internalStatusBar(KMainWindow *mw) { return mw->findChild(QString(), Qt::FindDirectChildrenOnly); } /** * Listens to resize events from QDockWidgets. The KMainWindow * settings are set as dirty, as soon as at least one resize * event occurred. The listener is attached to the dock widgets * by dock->installEventFilter(dockResizeListener) inside * KMainWindow::event(). */ class DockResizeListener : public QObject { Q_OBJECT public: DockResizeListener(KMainWindow *win); ~DockResizeListener() override; bool eventFilter(QObject *watched, QEvent *event) override; private: KMainWindow *m_win; }; DockResizeListener::DockResizeListener(KMainWindow *win) : QObject(win), m_win(win) { } DockResizeListener::~DockResizeListener() { } bool DockResizeListener::eventFilter(QObject *watched, QEvent *event) { switch (event->type()) { case QEvent::Resize: case QEvent::Move: case QEvent::Hide: m_win->k_ptr->setSettingsDirty(KMainWindowPrivate::CompressCalls); break; default: break; } return QObject::eventFilter(watched, event); } KMWSessionManager::KMWSessionManager() { connect(qApp, &QGuiApplication::saveStateRequest, this, &KMWSessionManager::saveState); connect(qApp, &QGuiApplication::commitDataRequest, this, &KMWSessionManager::commitData); } KMWSessionManager::~KMWSessionManager() { } void KMWSessionManager::saveState(QSessionManager &sm) { KConfigGui::setSessionConfig(sm.sessionId(), sm.sessionKey()); KConfig *config = KConfigGui::sessionConfig(); const auto windows = KMainWindow::memberList(); if (!windows.isEmpty()) { // According to Jochen Wilhelmy , this // hook is useful for better document orientation windows.at(0)->saveGlobalProperties(config); } int n = 0; for (KMainWindow *mw : windows) { n++; mw->savePropertiesInternal(config, n); } KConfigGroup group(config, "Number"); group.writeEntry("NumberOfWindows", n); // store new status to disk config->sync(); // generate discard command for new file QString localFilePath = QStandardPaths::writableLocation(QStandardPaths::GenericConfigLocation) + QLatin1Char('/') + config->name(); if (QFile::exists(localFilePath)) { QStringList discard; discard << QStringLiteral("rm"); discard << localFilePath; sm.setDiscardCommand(discard); } } void KMWSessionManager::commitData(QSessionManager &sm) { if (!sm.allowsInteraction()) { return; } /* Purpose of this exercise: invoke queryClose() without actually closing the windows, because - queryClose() may contain session management code, so it must be invoked - actually closing windows may quit the application - cf. QGuiApplication::quitOnLastWindowClosed() - quitting the application and thus closing the session manager connection violates the X11 XSMP protocol. The exact requirement of XSMP that would be broken is, in the description of the client's state machine: save-yourself-done: (changing state is forbidden) Closing the session manager connection causes a state change. Worst of all, that is a real problem with ksmserver - it will not save applications that quit on their own in state save-yourself-done. */ const auto windows = KMainWindow::memberList(); for (KMainWindow *window : windows) { if (window->testAttribute(Qt::WA_WState_Hidden)) { continue; } QCloseEvent e; QApplication::sendEvent(window, &e); if (!e.isAccepted()) { sm.cancel(); return; } } } Q_GLOBAL_STATIC(KMWSessionManager, ksm) Q_GLOBAL_STATIC(QList, sMemberList) KMainWindow::KMainWindow(QWidget *parent, Qt::WindowFlags f) : QMainWindow(parent, f), k_ptr(new KMainWindowPrivate) { k_ptr->init(this); } KMainWindow::KMainWindow(KMainWindowPrivate &dd, QWidget *parent, Qt::WindowFlags f) : QMainWindow(parent, f), k_ptr(&dd) { k_ptr->init(this); } void KMainWindowPrivate::init(KMainWindow *_q) { q = _q; QGuiApplication::setFallbackSessionManagementEnabled(false); q->setAnimated(q->style()->styleHint(QStyle::SH_Widget_Animate, nullptr, q)); q->setAttribute(Qt::WA_DeleteOnClose); helpMenu = nullptr; //actionCollection()->setWidget( this ); #if 0 QObject::connect(KGlobalSettings::self(), SIGNAL(settingsChanged(int)), q, SLOT(_k_slotSettingsChanged(int))); #endif // force KMWSessionManager creation ksm(); sMemberList()->append(q); // If application is translated, load translator information for use in // KAboutApplicationDialog or other getters. The context and messages below // both must be exactly as listed, and are forced to be loaded from the // application's own message catalog instead of kxmlgui's. KAboutData aboutData(KAboutData::applicationData()); if (aboutData.translators().isEmpty()) { aboutData.setTranslator( i18ndc(nullptr, "NAME OF TRANSLATORS", "Your names"), i18ndc(nullptr, "EMAIL OF TRANSLATORS", "Your emails")); KAboutData::setApplicationData(aboutData); } settingsDirty = false; autoSaveSettings = false; autoSaveWindowSize = true; // for compatibility //d->kaccel = actionCollection()->kaccel(); settingsTimer = nullptr; sizeTimer = nullptr; dockResizeListener = new DockResizeListener(_q); letDirtySettings = true; sizeApplied = false; } static bool endsWithHashNumber(const QString &s) { for (int i = s.length() - 1; i > 0; --i) { if (s[ i ] == QLatin1Char('#') && i != s.length() - 1) { return true; // ok } if (!s[ i ].isDigit()) { break; } } return false; } static inline bool isValidDBusObjectPathCharacter(const QChar &c) { ushort u = c.unicode(); return (u >= QLatin1Char('a') && u <= QLatin1Char('z')) || (u >= QLatin1Char('A') && u <= QLatin1Char('Z')) || (u >= QLatin1Char('0') && u <= QLatin1Char('9')) || (u == QLatin1Char('_')) || (u == QLatin1Char('/')); } void KMainWindowPrivate::polish(KMainWindow *q) { // Set a unique object name. Required by session management, window management, and for the dbus interface. QString objname; QString s; int unusedNumber = 1; const QString name = q->objectName(); bool startNumberingImmediately = true; bool tryReuse = false; if (name.isEmpty()) { // no name given objname = QStringLiteral("MainWindow#"); } else if (name.endsWith(QLatin1Char('#'))) { // trailing # - always add a number - KWin uses this for better grouping objname = name; } else if (endsWithHashNumber(name)) { // trailing # with a number - like above, try to use the given number first objname = name; tryReuse = true; startNumberingImmediately = false; } else { objname = name; startNumberingImmediately = false; } s = objname; if (startNumberingImmediately) { s += QLatin1Char('1'); } for (;;) { const QList list = qApp->topLevelWidgets(); bool found = false; for (QWidget *w : list) { if (w != q && w->objectName() == s) { found = true; break; } } if (!found) { break; } if (tryReuse) { objname = name.left(name.length() - 1); // lose the hash unusedNumber = 0; // start from 1 below tryReuse = false; } s.setNum(++unusedNumber); s = objname + s; } q->setObjectName(s); q->winId(); // workaround for setWindowRole() crashing, and set also window role, just in case TT q->setWindowRole(s); // will keep insisting that object name suddenly should not be used for window role dbusName = QLatin1Char('/') + QCoreApplication::applicationName() + QLatin1Char('/'); dbusName += q->objectName().replace(QLatin1Char('/'), QLatin1Char('_')); // Clean up for dbus usage: any non-alphanumeric char should be turned into '_' const int len = dbusName.length(); for (int i = 0; i < len; ++i) { if (!isValidDBusObjectPathCharacter(dbusName[i])) { dbusName[i] = QLatin1Char('_'); } } #ifdef QT_DBUS_LIB QDBusConnection::sessionBus().registerObject(dbusName, q, QDBusConnection::ExportScriptableSlots | QDBusConnection::ExportScriptableProperties | QDBusConnection::ExportNonScriptableSlots | QDBusConnection::ExportNonScriptableProperties | QDBusConnection::ExportAdaptors); #endif } void KMainWindowPrivate::setSettingsDirty(CallCompression callCompression) { if (!letDirtySettings) { return; } settingsDirty = true; if (autoSaveSettings) { if (callCompression == CompressCalls) { if (!settingsTimer) { settingsTimer = new QTimer(q); settingsTimer->setInterval(500); settingsTimer->setSingleShot(true); QObject::connect(settingsTimer, &QTimer::timeout, q, &KMainWindow::saveAutoSaveSettings); } settingsTimer->start(); } else { q->saveAutoSaveSettings(); } } } void KMainWindowPrivate::setSizeDirty() { if (autoSaveWindowSize) { if (!sizeTimer) { sizeTimer = new QTimer(q); sizeTimer->setInterval(500); sizeTimer->setSingleShot(true); QObject::connect(sizeTimer, SIGNAL(timeout()), q, SLOT(_k_slotSaveAutoSaveSize())); } sizeTimer->start(); } } KMainWindow::~KMainWindow() { sMemberList()->removeAll(this); delete static_cast(k_ptr->dockResizeListener); //so we don't get anymore events after k_ptr is destroyed delete k_ptr; } -#ifndef KXMLGUI_NO_DEPRECATED +#if KXMLGUI_BUILD_DEPRECATED_SINCE(5, 0) QMenu *KMainWindow::helpMenu(const QString &aboutAppText, bool showWhatsThis) { K_D(KMainWindow); if (!d->helpMenu) { if (aboutAppText.isEmpty()) { d->helpMenu = new KHelpMenu(this, KAboutData::applicationData(), showWhatsThis); } else { d->helpMenu = new KHelpMenu(this, aboutAppText, showWhatsThis); } if (!d->helpMenu) { return nullptr; } } return d->helpMenu->menu(); } QMenu *KMainWindow::customHelpMenu(bool showWhatsThis) { K_D(KMainWindow); if (!d->helpMenu) { d->helpMenu = new KHelpMenu(this, QString(), showWhatsThis); connect(d->helpMenu, &KHelpMenu::showAboutApplication, this, &KMainWindow::showAboutApplication); } return d->helpMenu->menu(); } #endif bool KMainWindow::canBeRestored(int number) { if (!qApp->isSessionRestored()) { return false; } KConfig *config = KConfigGui::sessionConfig(); if (!config) { return false; } KConfigGroup group(config, "Number"); const int n = group.readEntry("NumberOfWindows", 1); return number >= 1 && number <= n; } const QString KMainWindow::classNameOfToplevel(int number) { if (!qApp->isSessionRestored()) { return QString(); } KConfig *config = KConfigGui::sessionConfig(); if (!config) { return QString(); } KConfigGroup group(config, QByteArray(WINDOW_PROPERTIES).append(QByteArray::number(number)).constData()); if (!group.hasKey("ClassName")) { return QString(); } else { return group.readEntry("ClassName"); } } bool KMainWindow::restore(int number, bool show) { if (!canBeRestored(number)) { return false; } KConfig *config = KConfigGui::sessionConfig(); if (readPropertiesInternal(config, number)) { if (show) { KMainWindow::show(); } return false; } return false; } void KMainWindow::setCaption(const QString &caption) { setPlainCaption(caption); } void KMainWindow::setCaption(const QString &caption, bool modified) { QString title = caption; if (!title.contains(QLatin1String("[*]")) && !title.isEmpty()) { // append the placeholder so that the modified mechanism works title.append(QLatin1String(" [*]")); } setPlainCaption(title); setWindowModified(modified); } void KMainWindow::setPlainCaption(const QString &caption) { setWindowTitle(caption); } void KMainWindow::appHelpActivated() { K_D(KMainWindow); if (!d->helpMenu) { d->helpMenu = new KHelpMenu(this); if (!d->helpMenu) { return; } } d->helpMenu->appHelpActivated(); } void KMainWindow::closeEvent(QCloseEvent *e) { K_D(KMainWindow); // Save settings if auto-save is enabled, and settings have changed if (d->settingsTimer && d->settingsTimer->isActive()) { d->settingsTimer->stop(); saveAutoSaveSettings(); } if (d->sizeTimer && d->sizeTimer->isActive()) { d->sizeTimer->stop(); d->_k_slotSaveAutoSaveSize(); } if (queryClose()) { // widgets will start destroying themselves at this point and we don't // want to save state anymore after this as it might be incorrect d->autoSaveSettings = false; d->letDirtySettings = false; e->accept(); } else { e->ignore(); //if the window should not be closed, don't close it } } bool KMainWindow::queryClose() { return true; } void KMainWindow::saveGlobalProperties(KConfig *) { } void KMainWindow::readGlobalProperties(KConfig *) { } void KMainWindow::savePropertiesInternal(KConfig *config, int number) { K_D(KMainWindow); const bool oldASWS = d->autoSaveWindowSize; d->autoSaveWindowSize = true; // make saveMainWindowSettings save the window size KConfigGroup cg(config, QByteArray(WINDOW_PROPERTIES).append(QByteArray::number(number)).constData()); // store objectName, className, Width and Height for later restoring // (Only useful for session management) cg.writeEntry("ObjectName", objectName()); cg.writeEntry("ClassName", metaObject()->className()); saveMainWindowSettings(cg); // Menubar, statusbar and Toolbar settings. cg = KConfigGroup(config, QByteArray::number(number).constData()); saveProperties(cg); d->autoSaveWindowSize = oldASWS; } void KMainWindow::saveMainWindowSettings(KConfigGroup &cg) { K_D(KMainWindow); //qDebug(200) << "KMainWindow::saveMainWindowSettings " << cg.name(); // Called by session management - or if we want to save the window size anyway if (d->autoSaveWindowSize) { KWindowConfig::saveWindowSize(windowHandle(), cg); } // One day will need to save the version number, but for now, assume 0 // Utilise the QMainWindow::saveState() functionality. const QByteArray state = saveState(); cg.writeEntry("State", state.toBase64()); QStatusBar *sb = internalStatusBar(this); if (sb) { if (!cg.hasDefault("StatusBar") && !sb->isHidden()) { cg.revertToDefault("StatusBar"); } else { cg.writeEntry("StatusBar", sb->isHidden() ? "Disabled" : "Enabled"); } } QMenuBar *mb = internalMenuBar(this); if (mb) { if (!cg.hasDefault("MenuBar") && !mb->isHidden()) { cg.revertToDefault("MenuBar"); } else { cg.writeEntry("MenuBar", mb->isHidden() ? "Disabled" : "Enabled"); } } if (!autoSaveSettings() || cg.name() == autoSaveGroup()) { // TODO should be cg == d->autoSaveGroup, to compare both kconfig and group name if (!cg.hasDefault("ToolBarsMovable") && !KToolBar::toolBarsLocked()) { cg.revertToDefault("ToolBarsMovable"); } else { cg.writeEntry("ToolBarsMovable", KToolBar::toolBarsLocked() ? "Disabled" : "Enabled"); } } int n = 1; // Toolbar counter. toolbars are counted from 1, const auto toolBars = this->toolBars(); for (KToolBar *toolbar : toolBars) { QByteArray groupName("Toolbar"); // Give a number to the toolbar, but prefer a name if there is one, // because there's no real guarantee on the ordering of toolbars groupName += (toolbar->objectName().isEmpty() ? QByteArray::number(n) : QByteArray(" ").append(toolbar->objectName().toUtf8())); KConfigGroup toolbarGroup(&cg, groupName.constData()); toolbar->saveSettings(toolbarGroup); n++; } } bool KMainWindow::readPropertiesInternal(KConfig *config, int number) { K_D(KMainWindow); const bool oldLetDirtySettings = d->letDirtySettings; d->letDirtySettings = false; if (number == 1) { readGlobalProperties(config); } // in order they are in toolbar list KConfigGroup cg(config, QByteArray(WINDOW_PROPERTIES).append(QByteArray::number(number)).constData()); // restore the object name (window role) if (cg.hasKey("ObjectName")) { setObjectName(cg.readEntry("ObjectName")); } d->sizeApplied = false; // since we are changing config file, reload the size of the window // if necessary. Do it before the call to applyMainWindowSettings. applyMainWindowSettings(cg); // Menubar, statusbar and toolbar settings. KConfigGroup grp(config, QByteArray::number(number).constData()); readProperties(grp); d->letDirtySettings = oldLetDirtySettings; return true; } void KMainWindow::applyMainWindowSettings(const KConfigGroup &cg) { K_D(KMainWindow); //qDebug(200) << "KMainWindow::applyMainWindowSettings " << cg.name(); QWidget *focusedWidget = QApplication::focusWidget(); const bool oldLetDirtySettings = d->letDirtySettings; d->letDirtySettings = false; if (!d->sizeApplied) { winId(); // ensure there's a window created KWindowConfig::restoreWindowSize(windowHandle(), cg); // NOTICE: QWindow::setGeometry() does NOT impact the backing QWidget geometry even if the platform // window was created -> QTBUG-40584. We therefore copy the size here. // TODO: remove once this was resolved in QWidget QPA resize(windowHandle()->size()); d->sizeApplied = true; } QStatusBar *sb = internalStatusBar(this); if (sb) { QString entry = cg.readEntry("StatusBar", "Enabled"); sb->setVisible( entry != QLatin1String("Disabled") ); } QMenuBar *mb = internalMenuBar(this); if (mb) { QString entry = cg.readEntry("MenuBar", "Enabled"); mb->setVisible( entry != QLatin1String("Disabled") ); } if (!autoSaveSettings() || cg.name() == autoSaveGroup()) { // TODO should be cg == d->autoSaveGroup, to compare both kconfig and group name QString entry = cg.readEntry("ToolBarsMovable", "Disabled"); KToolBar::setToolBarsLocked(entry == QLatin1String("Disabled")); } int n = 1; // Toolbar counter. toolbars are counted from 1, const auto toolBars = this->toolBars(); for (KToolBar *toolbar : toolBars) { QByteArray groupName("Toolbar"); // Give a number to the toolbar, but prefer a name if there is one, // because there's no real guarantee on the ordering of toolbars groupName += (toolbar->objectName().isEmpty() ? QByteArray::number(n) : QByteArray(" ").append(toolbar->objectName().toUtf8())); KConfigGroup toolbarGroup(&cg, groupName.constData()); toolbar->applySettings(toolbarGroup); n++; } QByteArray state; if (cg.hasKey("State")) { state = cg.readEntry("State", state); state = QByteArray::fromBase64(state); // One day will need to load the version number, but for now, assume 0 restoreState(state); } if (focusedWidget) { focusedWidget->setFocus(); } d->settingsDirty = false; d->letDirtySettings = oldLetDirtySettings; } -#ifndef KXMLGUI_NO_DEPRECATED +#if KXMLGUI_BUILD_DEPRECATED_SINCE(5, 0) void KMainWindow::restoreWindowSize(const KConfigGroup &cg) { KWindowConfig::restoreWindowSize(windowHandle(), cg); } #endif -#ifndef KXMLGUI_NO_DEPRECATED +#if KXMLGUI_BUILD_DEPRECATED_SINCE(5, 0) void KMainWindow::saveWindowSize(KConfigGroup &cg) const { KWindowConfig::saveWindowSize(windowHandle(), cg); } #endif void KMainWindow::setSettingsDirty() { K_D(KMainWindow); d->setSettingsDirty(); } bool KMainWindow::settingsDirty() const { K_D(const KMainWindow); return d->settingsDirty; } void KMainWindow::setAutoSaveSettings(const QString &groupName, bool saveWindowSize) { setAutoSaveSettings(KConfigGroup(KSharedConfig::openConfig(), groupName), saveWindowSize); } void KMainWindow::setAutoSaveSettings(const KConfigGroup &group, bool saveWindowSize) { K_D(KMainWindow); d->autoSaveSettings = true; d->autoSaveGroup = group; d->autoSaveWindowSize = saveWindowSize; if (!saveWindowSize && d->sizeTimer) { d->sizeTimer->stop(); } // Now read the previously saved settings applyMainWindowSettings(d->autoSaveGroup); } void KMainWindow::resetAutoSaveSettings() { K_D(KMainWindow); d->autoSaveSettings = false; if (d->settingsTimer) { d->settingsTimer->stop(); } } bool KMainWindow::autoSaveSettings() const { K_D(const KMainWindow); return d->autoSaveSettings; } QString KMainWindow::autoSaveGroup() const { K_D(const KMainWindow); return d->autoSaveSettings ? d->autoSaveGroup.name() : QString(); } KConfigGroup KMainWindow::autoSaveConfigGroup() const { K_D(const KMainWindow); return d->autoSaveSettings ? d->autoSaveGroup : KConfigGroup(); } void KMainWindow::saveAutoSaveSettings() { K_D(KMainWindow); Q_ASSERT(d->autoSaveSettings); //qDebug(200) << "KMainWindow::saveAutoSaveSettings -> saving settings"; saveMainWindowSettings(d->autoSaveGroup); d->autoSaveGroup.sync(); d->settingsDirty = false; } bool KMainWindow::event(QEvent *ev) { K_D(KMainWindow); switch (ev->type()) { #if defined(Q_OS_WIN) || defined(Q_OS_OSX) case QEvent::Move: #endif case QEvent::Resize: d->setSizeDirty(); break; case QEvent::Polish: d->polish(this); break; case QEvent::ChildPolished: { QChildEvent *event = static_cast(ev); QDockWidget *dock = qobject_cast(event->child()); KToolBar *toolbar = qobject_cast(event->child()); QMenuBar *menubar = qobject_cast(event->child()); if (dock) { connect(dock, &QDockWidget::dockLocationChanged, this, &KMainWindow::setSettingsDirty); connect(dock, &QDockWidget::topLevelChanged, this, &KMainWindow::setSettingsDirty); // there is no signal emitted if the size of the dock changes, // hence install an event filter instead dock->installEventFilter(k_ptr->dockResizeListener); } else if (toolbar) { // there is no signal emitted if the size of the toolbar changes, // hence install an event filter instead toolbar->installEventFilter(k_ptr->dockResizeListener); } else if (menubar) { // there is no signal emitted if the size of the menubar changes, // hence install an event filter instead menubar->installEventFilter(k_ptr->dockResizeListener); } } break; case QEvent::ChildRemoved: { QChildEvent *event = static_cast(ev); QDockWidget *dock = qobject_cast(event->child()); KToolBar *toolbar = qobject_cast(event->child()); QMenuBar *menubar = qobject_cast(event->child()); if (dock) { disconnect(dock, &QDockWidget::dockLocationChanged, this, &KMainWindow::setSettingsDirty); disconnect(dock, &QDockWidget::topLevelChanged, this, &KMainWindow::setSettingsDirty); dock->removeEventFilter(k_ptr->dockResizeListener); } else if (toolbar) { toolbar->removeEventFilter(k_ptr->dockResizeListener); } else if (menubar) { menubar->removeEventFilter(k_ptr->dockResizeListener); } } break; default: break; } return QMainWindow::event(ev); } bool KMainWindow::hasMenuBar() { return internalMenuBar(this); } void KMainWindowPrivate::_k_slotSettingsChanged(int category) { Q_UNUSED(category); // This slot will be called when the style KCM changes settings that need // to be set on the already running applications. // At this level (KMainWindow) the only thing we need to restore is the // animations setting (whether the user wants builtin animations or not). q->setAnimated(q->style()->styleHint(QStyle::SH_Widget_Animate, nullptr, q)); } void KMainWindowPrivate::_k_slotSaveAutoSaveSize() { if (autoSaveGroup.isValid()) { KWindowConfig::saveWindowSize(q->windowHandle(), autoSaveGroup); } } KToolBar *KMainWindow::toolBar(const QString &name) { QString childName = name; if (childName.isEmpty()) { childName = QStringLiteral("mainToolBar"); } KToolBar *tb = findChild(childName); if (tb) { return tb; } KToolBar *toolbar = new KToolBar(childName, this); // non-XMLGUI toolbar return toolbar; } QList KMainWindow::toolBars() const { QList ret; foreach (QObject *child, children()) if (KToolBar *toolBar = qobject_cast(child)) { ret.append(toolBar); } return ret; } QList KMainWindow::memberList() { return *sMemberList(); } QString KMainWindow::dbusName() const { return k_func()->dbusName; } #include "moc_kmainwindow.cpp" #include "kmainwindow.moc" diff --git a/src/kmainwindow.h b/src/kmainwindow.h index 7353f4a..57227e2 100644 --- a/src/kmainwindow.h +++ b/src/kmainwindow.h @@ -1,739 +1,745 @@ /* This file is part of the KDE libraries Copyright (C) 2000 Reginald Stadlbauer (reggie@kde.org) (C) 1997 Stephan Kulow (coolo@kde.org) (C) 1997-2000 Sven Radej (radej@kde.org) (C) 1997-2000 Matthias Ettrich (ettrich@kde.org) (C) 1999 Chris Schlaeger (cs@kde.org) (C) 2002 Joseph Wenninger (jowenn@kde.org) (C) 2005-2006 Hamish Rodda (rodda@kde.org) (C) 2000-2008 David Faure (faure@kde.org) This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License version 2 as published by the Free Software Foundation. This library 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 Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef KMAINWINDOW_H #define KMAINWINDOW_H #include #include class QMenu; class KConfig; class KConfigGroup; class KMWSessionManager; class KMainWindowPrivate; class KToolBar; // internal, not public API, may change any time #define XMLGUI_DECLARE_PRIVATE(classname) \ inline classname ## Private *k_func() { return reinterpret_cast(k_ptr); } \ inline const classname ## Private *k_func() const { return reinterpret_cast(k_ptr); } \ friend class classname ## Private; // TODO KF6: remove #define KDE_DEFAULT_WINDOWFLAGS 0 /** * @class KMainWindow kmainwindow.h KMainWindow * * @short Top level main window * * Top level widget that provides toolbars, a status line and a frame. * * It should be used as a top level (parent-less) widget. * It manages the geometry for all its children, including your * main widget. * * Normally, you will inherit from KMainWindow, * then construct (or use some existing) widget as * your main view. You can set only one main view. * * You can add as many toolbars as you like. There can be only one menubar * and only one statusbar. * * The toolbars, menubar, and statusbar can be created by the * KMainWindow and - unlike the old KMainWindow - may, but do not * have to, be deleted by you. KMainWindow will handle that internally. * * Height and width can be operated independently from each other. Simply * define the minimum/maximum height/width of your main widget and * KMainWindow will take this into account. For fixed size windows set * your main widget to a fixed size. * * Fixed aspect ratios (heightForWidth()) and fixed width widgets are * not supported. * * KMainWindow will set icon, mini icon and caption, which it gets * from KApplication. It provides full session management, and * will save its position, geometry and positions of toolbars and * menubar on logout. If you want to save additional data, reimplement * saveProperties() and (to read them again on next login) * readProperties(). To save special data about your data, reimplement * saveGlobalProperties(). To warn user that application or * windows have unsaved data on close or logout, reimplement * queryClose(). * * You have to implement session restoring also in your main() function. * There are also kRestoreMainWindows convenience functions which * can do this for you and restore all your windows on next login. * * Note that KMainWindow uses KGlobal::ref() and KGlobal::deref() so that closing * the last mainwindow will quit the application unless there is still something * that holds a ref in KGlobal - like a KIO job, or a systray icon. * * @author Reginald Stadlbauer (reggie@kde.org) Stephan Kulow (coolo@kde.org), Matthias Ettrich (ettrich@kde.org), Chris Schlaeger (cs@kde.org), Sven Radej (radej@kde.org). Maintained by David Faure (faure@kde.org) */ class KXMLGUI_EXPORT KMainWindow : public QMainWindow { friend class KMWSessionManager; friend class DockResizeListener; XMLGUI_DECLARE_PRIVATE(KMainWindow) Q_OBJECT Q_PROPERTY(bool hasMenuBar READ hasMenuBar) Q_PROPERTY(bool autoSaveSettings READ autoSaveSettings) Q_PROPERTY(QString autoSaveGroup READ autoSaveGroup) public: /** * Construct a main window. * * @param parent The widget parent. This is usually 0 but it may also be the window * group leader. In that case, the KMainWindow becomes sort of a * secondary window. * * @param f Specify the window flags. The default is none. * * Note that a KMainWindow per-default is created with the * Qt::WA_DeleteOnClose attribute set, i.e. it is automatically destroyed * when the window is closed. If you do not want this behavior, call * \code * window->setAttribute(Qt::WA_DeleteOnClose, false); * \endcode * * KMainWindows must be created on the heap with 'new', like: * \code * KMainWindow *kmw = new KMainWindow(...); * kmw->setObjectName(...); * \endcode * * Since the KDE Frameworks 5.16 release, KMainWindow will also enter information regarding * the application's translators by default, using KAboutData::setTranslator(). This only occurs * if no translators are already assigned in KAboutData (see KAboutData::setTranslator() for * details -- the auto-assignment here uses the same translated strings as specified for that * function). * * IMPORTANT: For session management and window management to work * properly, all main windows in the application should have a * different name. If you don't do it, KMainWindow will create * a unique name, but it's recommended to explicitly pass a window name that will * also describe the type of the window. If there can be several windows of the same * type, append '#' (hash) to the name, and KMainWindow will replace it with numbers to make * the names unique. For example, for a mail client which has one main window showing * the mails and folders, and which can also have one or more windows for composing * mails, the name for the folders window should be e.g. "mainwindow" and * for the composer windows "composer#". * */ explicit KMainWindow(QWidget *parent = nullptr, Qt::WindowFlags f = Qt::WindowFlags()); /** * \brief Destructor. * * Will also destroy the toolbars, and menubar if * needed. */ ~KMainWindow() override; +#if KXMLGUI_ENABLE_DEPRECATED_SINCE(5, 0) /** * Retrieve the standard help menu. * * It contains entries for the * help system (activated by F1), an optional "What's This?" entry * (activated by Shift F1), an application specific dialog box, * and an "About KDE" dialog box. * * Example (adding a standard help menu to your application): * \code * QMenu *help = helpMenu( ); * menuBar()->addMenu( help ); * \endcode * * @param aboutAppText The string that is used in the application * specific dialog box. If you leave this string empty the * information in the global KAboutData of the * application will be used to make a standard dialog box. * * @param showWhatsThis Set this to false if you do not want to include * the "What's This" menu entry. * * @return A standard help menu. - * @deprecated use KHelpMenu directly + * @deprecated Since 5.0, use KHelpMenu directly */ -#ifndef KXMLGUI_NO_DEPRECATED - KXMLGUI_DEPRECATED QMenu *helpMenu(const QString &aboutAppText = QString(), - bool showWhatsThis = true); + KXMLGUI_DEPRECATED_VERSION(5, 0, "Use KHelpMenu directly") + QMenu *helpMenu(const QString &aboutAppText = QString(), + bool showWhatsThis = true); #endif +#if KXMLGUI_ENABLE_DEPRECATED_SINCE(5, 0) /** * Returns the help menu. Creates a standard help menu if none exists yet. * * It contains entries for the * help system (activated by F1), an optional "What's This?" entry * (activated by Shift F1), an application specific dialog box, * and an "About KDE" dialog box. You must create the application * specific dialog box yourself. When the "About application" * menu entry is activated, a signal will trigger the * showAboutApplication slot. See showAboutApplication for more * information. * * Example (adding a help menu to your application): * \code * menuBar()->addMenu( customHelpMenu() ); * \endcode * * @param showWhatsThis Set this to @c false if you do not want to include * the "What's This" menu entry. * * @return A standard help menu. - * @deprecated use XMLGUI instead, or KHelpMenu directly + * @deprecated Since 5.0, use XMLGUI instead, or KHelpMenu directly */ -#ifndef KXMLGUI_NO_DEPRECATED - KXMLGUI_DEPRECATED QMenu *customHelpMenu(bool showWhatsThis = true); + KXMLGUI_DEPRECATED_VERSION(5, 0, "Use XMLGUI or KHelpMenu") + QMenu *customHelpMenu(bool showWhatsThis = true); #endif /** * If the session did contain so high a number, @c true is returned, * else @c false. * @see restore() **/ static bool canBeRestored(int number); /** * Returns the className() of the @p number of the toplevel window which * should be restored. * * This is only useful if your application uses * different kinds of toplevel windows. */ static const QString classNameOfToplevel(int number); /** * Try to restore the toplevel widget as defined by @p number (1..X). * * You should call canBeRestored() first. * * If the session did not contain so high a number, the configuration * is not changed and @c false returned. * * That means clients could simply do the following: * \code * if (qApp->isSessionRestored()){ * int n = 1; * while (KMainWindow::canBeRestored(n)){ * (new childMW)->restore(n); * n++; * } * } else { * // create default application as usual * } * \endcode * Note that if @p show is @c true (default), QWidget::show() is called * implicitly in restore. * * With this you can easily restore all toplevel windows of your * application. * * If your application uses different kinds of toplevel * windows, then you can use KMainWindow::classNameOfToplevel(n) * to determine the exact type before calling the childMW * constructor in the example from above. * * @note You don't need to deal with this function. Use the * kRestoreMainWindows() convenience template function instead! * @see kRestoreMainWindows() * @see readProperties() * @see canBeRestored() */ bool restore(int number, bool show = true); /** * Returns true, if there is a menubar */ bool hasMenuBar(); /** * List of members of KMainWindow class. */ static QList memberList(); /** * Returns a pointer to the toolbar with the specified name. * This refers to toolbars created dynamically from the XML UI * framework. If the toolbar does not exist one will be created. * * @param name The internal name of the toolbar. If no name is * specified "mainToolBar" is assumed. * * @return A pointer to the toolbar **/ KToolBar *toolBar(const QString &name = QString()); /** * @return A list of all toolbars for this window */ QList toolBars() const; /** * Call this to enable "auto-save" of toolbar/menubar/statusbar settings * (and optionally window size). * If the *bars were moved around/shown/hidden when the window is closed, * saveMainWindowSettings( KConfigGroup(KSharedConfig::openConfig(), groupName) ) will be called. * * @param groupName a name that identifies this "type of window". * You can have several types of window in the same application. * * @param saveWindowSize set it to true to include the window size * when saving. * * Typically, you will call setAutoSaveSettings() in your * KMainWindow-inherited class constructor, and it will take care * of restoring and saving automatically. Make sure you call this * _after all_ your *bars have been created. * * To make sure that KMainWindow properly obtains the default * size of the window you should do the following: * - Remove hard coded resize() calls in the constructor or main, they * should be removed in favor of letting the automatic resizing * determine the default window size. Hard coded window sizes will * be wrong for users that have big fonts, use different styles, * long/small translations, large toolbars, and other factors. * - Put the setAutoSaveSettings() ( or setupGUI() ) call after all widgets * have been created and placed inside the main window (i.e. for 99% of * apps setCentralWidget()) * - Widgets that inherit from QWidget (like game boards) should overload * "virtual QSize sizeHint() const;" to specify a default size rather * than letting QWidget::adjust use the default size of 0x0. */ void setAutoSaveSettings(const QString &groupName = QStringLiteral("MainWindow"), bool saveWindowSize = true); /** * Overload that lets you specify a KConfigGroup. * This allows the settings to be saved into another file than KSharedConfig::openConfig(). * @since 4.1 */ void setAutoSaveSettings(const KConfigGroup &group, bool saveWindowSize = true); /** * Disable the auto-save-settings feature. * You don't normally need to call this, ever. */ void resetAutoSaveSettings(); /** * @return the current autosave setting, i.e. true if setAutoSaveSettings() was called, * false by default or if resetAutoSaveSettings() was called. */ bool autoSaveSettings() const; /** * @return the group used for setting-autosaving. * Only meaningful if setAutoSaveSettings(const QString&, bool) was called. * This can be useful for forcing a save or an apply, e.g. before and after * using KEditToolBar. * * @note You should rather use saveAutoSaveSettings() for saving or autoSaveConfigGroup() for loading. * This method doesn't make sense if setAutoSaveSettings(const KConfigGroup&, bool) was called. */ QString autoSaveGroup() const; /** * @return the group used for setting-autosaving. * Only meaningful if setAutoSaveSettings() was called. * This can be useful for forcing an apply, e.g. after using KEditToolBar. * @since 4.1 */ KConfigGroup autoSaveConfigGroup() const; /** * Read settings for statusbar, menubar and toolbar from their respective * groups in the config file and apply them. * * @param config Config group to read the settings from. * * KF5 porting note: the unused bool argument was removed, make sure to remove it from your * reimplementations too! And add a override for good measure. */ virtual void applyMainWindowSettings(const KConfigGroup &config); /** * Save settings for statusbar, menubar and toolbar to their respective * groups in the config group @p config. * * @param config Config group to save the settings to. */ void saveMainWindowSettings(KConfigGroup &config); /** * Returns the path under which this window's D-Bus object is exported. * @since 4.0.1 */ QString dbusName() const; +#if KXMLGUI_ENABLE_DEPRECATED_SINCE(5, 0) /** * @returns Always @c false * @deprecated since 5.0, the functionality got removed **/ -#ifndef KXMLGUI_NO_DEPRECATED - KXMLGUI_DEPRECATED bool initialGeometrySet() const { return false; } + KXMLGUI_DEPRECATED_VERSION(5, 0, "Remove usage, is a no-op now") + bool initialGeometrySet() const { return false; } #endif public Q_SLOTS: /** * Makes a KDE compliant caption (window title). * * @param caption Your caption. @em Do @em not include the application name * in this string. It will be added automatically according to the KDE * standard. */ virtual void setCaption(const QString &caption); /** * Makes a KDE compliant caption. * * @param caption Your caption. @em Do @em not include the application name * in this string. It will be added automatically according to the KDE * standard. * @param modified Specify whether the document is modified. This displays * an additional sign in the title bar, usually "**". */ virtual void setCaption(const QString &caption, bool modified); /** * Make a plain caption without any modifications. * * @param caption Your caption. This is the string that will be * displayed in the window title. */ virtual void setPlainCaption(const QString &caption); /** * Open the help page for the application. * * The application name is * used as a key to determine what to display and the system will attempt * to open \/index.html. * * This method is intended for use by a help button in the toolbar or * components outside the regular help menu. Use helpMenu() when you * want to provide access to the help system from the help menu. * * Example (adding a help button to the first toolbar): * * \code * toolBar(0)->addAction(QIcon::fromTheme("help-contents"), i18n("Help"), * this, SLOT(appHelpActivated())); * \endcode * */ void appHelpActivated(); /** * Tell the main window that it should save its settings when being closed. * This is part of the auto-save-settings feature. * For everything related to toolbars this happens automatically, * but you have to call setSettingsDirty() in the slot that toggles * the visibility of the statusbar. */ void setSettingsDirty(); protected: /** * Reimplemented to catch QEvent::Polish in order to adjust the object name * if needed, once all constructor code for the main window has run. * Also reimplemented to catch when a QDockWidget is added or removed. */ bool event(QEvent *event) override; /** * Reimplemented to autosave settings and call queryClose(). * * We recommend that you reimplement queryClose() rather than closeEvent(). * If you do it anyway, ensure to call the base implementation to keep * the feature of auto-saving window settings working. */ void closeEvent(QCloseEvent *) override; /** Called before the window is closed, either by the user or indirectly by the session manager. The purpose of this function is to prepare the window in a way that it is safe to close it, i.e. without the user losing some data. Default implementation returns true. Returning @c false will cancel the closing, and, if KApplication::sessionSaving() is true, it will also cancel KDE logout. Reimplement this function to prevent the user from losing data. Example: \code switch ( KMessageBox::warningYesNoCancel( this, i18n("Save changes to document foo?")) ) { case KMessageBox::Yes : // save document here. If saving fails, return false; return true; case KMessageBox::No : return true; default: // cancel return false; \endcode Note that you should probably @em not actually close the document from within this method, as it may be called by the session manager before the session is saved. If the document is closed before the session save occurs, its location might not be properly saved. In addition, the session shutdown may be canceled, in which case the document should remain open. @see KApplication::sessionSaving() */ virtual bool queryClose(); /** * Save your instance-specific properties. The function is * invoked when the session manager requests your application * to save its state. * * Please reimplement these function in childclasses. * * Note: No user interaction is allowed * in this function! * */ virtual void saveProperties(KConfigGroup &) {} /** * Read your instance-specific properties. * * Is called indirectly by restore(). */ virtual void readProperties(const KConfigGroup &) {} /** * Save your application-wide properties. The function is * invoked when the session manager requests your application * to save its state. * * This function is similar to saveProperties() but is only called for * the very first main window, regardless how many main window are open. * Override it if you need to save other data about your documents on * session end. sessionConfig is a config to which that data should be * saved. Normally, you don't need this function. But if you want to save * data about your documents that are not in opened windows you might need * it. * * Default implementation does nothing. */ virtual void saveGlobalProperties(KConfig *sessionConfig); /** * The counterpart of saveGlobalProperties(). * * Read the application-specific properties in again. */ virtual void readGlobalProperties(KConfig *sessionConfig); void savePropertiesInternal(KConfig *, int); bool readPropertiesInternal(KConfig *, int); /** * For inherited classes */ bool settingsDirty() const; +#if KXMLGUI_ENABLE_DEPRECATED_SINCE(5, 0) /** * For inherited classes - * @deprecated use KWindowConfig::saveWindowSize + * @deprecated Since 5.0, use KWindowConfig::saveWindowSize */ -#ifndef KXMLGUI_NO_DEPRECATED - KXMLGUI_DEPRECATED void saveWindowSize(KConfigGroup &config) const; + KXMLGUI_DEPRECATED_VERSION(5, 0, "Use KWindowConfig::saveWindowSize(...)") + void saveWindowSize(KConfigGroup &config) const; #endif +#if KXMLGUI_ENABLE_DEPRECATED_SINCE(5, 0) /** * For inherited classes - * @deprecated use KWindowConfig::restoreWindowSize + * @deprecated Since 5.0, use KWindowConfig::restoreWindowSize */ -#ifndef KXMLGUI_NO_DEPRECATED - KXMLGUI_DEPRECATED void restoreWindowSize(const KConfigGroup &config); + KXMLGUI_DEPRECATED_VERSION(5, 0, "Use KWindowConfig::restoreWindowSize(...)") + void restoreWindowSize(const KConfigGroup &config); #endif protected Q_SLOTS: +#if KXMLGUI_BUILD_DEPRECATED_SINCE(5, 0) /** * This slot does nothing. * * It must be reimplemented if you want * to use a custom About Application dialog box. This slot is * connected to the About Application entry in the menu returned * by customHelpMenu. * * Example: * \code * * void MyMainLevel::setupInterface() * { * .. * menuBar()->addMenu( customHelpMenu() ); * .. * } * * void MyMainLevel::showAboutApplication() * { * * } * \endcode - * @deprecated use KHelpMenu + * @deprecated Since 5.0, use KHelpMenu */ -#ifndef KXMLGUI_NO_DEPRECATED - virtual KXMLGUI_DEPRECATED void showAboutApplication() {} + KXMLGUI_DEPRECATED_VERSION(5, 0, "Use KHelpMenu") + virtual void showAboutApplication() {} #endif /** * This slot should only be called in case you reimplement closeEvent() and * if you are using the "auto-save" feature. In all other cases, * setSettingsDirty() should be called instead to benefit from the delayed * saving. * * @see setAutoSaveSettings * @see setSettingsDirty * * Example: * \code * * void MyMainWindow::closeEvent( QCloseEvent *e ) * { * // Save settings if auto-save is enabled, and settings have changed * if ( settingsDirty() && autoSaveSettings() ) * saveAutoSaveSettings(); * .. * } * \endcode */ void saveAutoSaveSettings(); protected: KMainWindow(KMainWindowPrivate &dd, QWidget *parent, Qt::WindowFlags f); KMainWindowPrivate *const k_ptr; private: Q_PRIVATE_SLOT(k_func(), void _k_slotSettingsChanged(int)) Q_PRIVATE_SLOT(k_func(), void _k_slotSaveAutoSaveSize()) }; /** * @defgroup KXMLGUI_Session KXMLGUI Session Macros and Functions * * @{ */ -#ifndef KXMLGUI_NO_DEPRECATED +#if KXMLGUI_ENABLE_DEPRECATED_SINCE(5, 0) /** * @def RESTORE * Restores the last session. * * @deprecated since 5.0, use kRestoreMainWindows() instead **/ #define RESTORE(type) { int n = 1;\ while (KMainWindow::canBeRestored(n)){\ (new type)->restore(n);\ n++;}} #endif /** * @def KDE_RESTORE_MAIN_WINDOWS_NUM_TEMPLATE_ARGS * Returns the maximal number of arguments that are actually * supported by kRestoreMainWindows(). **/ #define KDE_RESTORE_MAIN_WINDOWS_NUM_TEMPLATE_ARGS 3 /** * Restores the last session. (To be used in your main function). * * These functions work also if you have more than one kind of toplevel * widget (each derived from KMainWindow, of course). * * Imagine you have three kinds of toplevel widgets: the classes @c childMW1, * @c childMW2 and @c childMW3. Then you can just do: * * \code * int main(int argc, char *argv[]) * { * // [...] * if (qApp->isSessionRestored()) * kRestoreMainWindows(); * else { * // create default application as usual * } * // [...] * } * \endcode * * kRestoreMainWindows<>() will create (on the heap) as many instances * of your main windows as have existed in the last session and * call KMainWindow::restore() with the correct arguments. Note that * also QWidget::show() is called implicitly. * * Currently, these functions are provided for up to three * template arguments. If you need more, tell us. To help you in * deciding whether or not you can use kRestoreMainWindows, a * define #KDE_RESTORE_MAIN_WINDOWS_NUM_TEMPLATE_ARGS is provided. * * @tparam T toplevel widget class * * @see KMainWindow::restore() * @see KMainWindow::classNameOfToplevel() **/ template inline void kRestoreMainWindows() { for (int n = 1; KMainWindow::canBeRestored(n); ++n) { const QString className = KMainWindow::classNameOfToplevel(n); if (className == QLatin1String(T::staticMetaObject.className())) { (new T)->restore(n); } } } /** * Restores the last session. * Overloaded method for usage with multiple different toplevel widget classes. * * @tparam T0 one toplevel widget class * @tparam T1 explicit other toplevel widget class for disambiguation from base template * @tparam Tn Parameter pack to take 0..n further KMainWindows */ template inline void kRestoreMainWindows() { kRestoreMainWindows(); kRestoreMainWindows(); } /** @} */ #endif diff --git a/src/kshortcutseditor.cpp b/src/kshortcutseditor.cpp index a1a7915..813c726 100644 --- a/src/kshortcutseditor.cpp +++ b/src/kshortcutseditor.cpp @@ -1,798 +1,798 @@ /* This file is part of the KDE libraries Copyright (C) 1998 Mark Donohoe Copyright (C) 1997 Nicolas Hadacek Copyright (C) 1998 Matthias Ettrich Copyright (C) 2001 Ellis Whitehead Copyright (C) 2006 Hamish Rodda Copyright (C) 2007 Roberto Raggi Copyright (C) 2007 Andreas Hartmetz Copyright (C) 2008 Michael Jansen This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library 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 Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "config-xmlgui.h" #include "kshortcutseditor.h" // The following is needed for KShortcutsEditorPrivate and QTreeWidgetHack #include "kshortcutsdialog_p.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #if HAVE_GLOBALACCEL # include #endif #include #include "kactioncollection.h" #include "kactioncategory.h" #include //--------------------------------------------------------------------- // KShortcutsEditor //--------------------------------------------------------------------- KShortcutsEditor::KShortcutsEditor(KActionCollection *collection, QWidget *parent, ActionTypes actionType, LetterShortcuts allowLetterShortcuts) : QWidget(parent) , d(new KShortcutsEditorPrivate(this)) { d->initGUI(actionType, allowLetterShortcuts); addCollection(collection); } KShortcutsEditor::KShortcutsEditor(QWidget *parent, ActionTypes actionType, LetterShortcuts allowLetterShortcuts) : QWidget(parent) , d(new KShortcutsEditorPrivate(this)) { d->initGUI(actionType, allowLetterShortcuts); } KShortcutsEditor::~KShortcutsEditor() { delete d; } bool KShortcutsEditor::isModified() const { // Iterate over all items QTreeWidgetItemIterator it(d->ui.list, QTreeWidgetItemIterator::NoChildren); for (; (*it); ++it) { KShortcutsEditorItem *item = dynamic_cast(*it); if (item && item->isModified()) { return true; } } return false; } void KShortcutsEditor::clearCollections() { d->delegate->contractAll(); d->ui.list->clear(); d->actionCollections.clear(); QTimer::singleShot(0, this, &KShortcutsEditor::resizeColumns); } void KShortcutsEditor::addCollection(KActionCollection *collection, const QString &title) { // KXmlGui add action collections unconditionally. If some plugin doesn't // provide actions we don't want to create empty subgroups. if (collection->isEmpty()) { return; } // We add a bunch of items. Prevent the treewidget from permanently // updating. setUpdatesEnabled(false); d->actionCollections.append(collection); // Forward our actionCollections to the delegate which does the conflict // checking. d->delegate->setCheckActionCollections(d->actionCollections); QString displayTitle = title; if (displayTitle.isEmpty()) { // Use the programName (Translated). displayTitle = collection->componentDisplayName(); } QTreeWidgetItem *hier[3]; hier[KShortcutsEditorPrivate::Root] = d->ui.list->invisibleRootItem(); hier[KShortcutsEditorPrivate::Program] = d->findOrMakeItem(hier[KShortcutsEditorPrivate::Root], displayTitle); hier[KShortcutsEditorPrivate::Action] = nullptr; // Set to remember which actions we have seen. QSet actionsSeen; // Add all categories in their own subtree below the collections root node const QList categories = collection->findChildren(); for (KActionCategory *category : categories) { hier[KShortcutsEditorPrivate::Action] = d->findOrMakeItem(hier[KShortcutsEditorPrivate::Program], category->text()); const auto categoryActions = category->actions(); for (QAction *action : categoryActions) { // Set a marker that we have seen this action actionsSeen.insert(action); d->addAction(action, hier, KShortcutsEditorPrivate::Action); } } // The rest of the shortcuts is added as a direct shild of the action // collections root node const auto collectionActions = collection->actions(); for (QAction *action : collectionActions) { if (actionsSeen.contains(action)) { continue; } d->addAction(action, hier, KShortcutsEditorPrivate::Program); } // sort the list d->ui.list->sortItems(Name, Qt::AscendingOrder); // reenable updating setUpdatesEnabled(true); QTimer::singleShot(0, this, &KShortcutsEditor::resizeColumns); } void KShortcutsEditor::clearConfiguration() { d->clearConfiguration(); } -#ifndef KXMLGUI_NO_DEPRECATED +#if KXMLGUI_BUILD_DEPRECATED_SINCE(5, 0) void KShortcutsEditor::importConfiguration(KConfig *config) { d->importConfiguration(config); } #endif void KShortcutsEditor::importConfiguration(KConfigBase *config) { d->importConfiguration(config); } -#ifndef KXMLGUI_NO_DEPRECATED +#if KXMLGUI_BUILD_DEPRECATED_SINCE(5, 0) void KShortcutsEditor::exportConfiguration(KConfig *config) const { exportConfiguration(static_cast(config)); } #endif void KShortcutsEditor::exportConfiguration(KConfigBase *config) const { Q_ASSERT(config); if (!config) { return; } if (d->actionTypes & KShortcutsEditor::GlobalAction) { QString groupName(QStringLiteral("Global Shortcuts")); KConfigGroup group(config, groupName); for (KActionCollection *collection : qAsConst(d->actionCollections)) { collection->exportGlobalShortcuts(&group, true); } } if (d->actionTypes & ~KShortcutsEditor::GlobalAction) { QString groupName(QStringLiteral("Shortcuts")); KConfigGroup group(config, groupName); for (KActionCollection *collection : qAsConst(d->actionCollections)) { collection->writeSettings(&group, true); } } } void KShortcutsEditor::writeConfiguration(KConfigGroup *config) const { for (KActionCollection *collection : qAsConst(d->actionCollections)) { collection->writeSettings(config); } } //slot void KShortcutsEditor::resizeColumns() { for (int i = 0; i < d->ui.list->columnCount(); i++) { d->ui.list->resizeColumnToContents(i); } } void KShortcutsEditor::commit() { for (QTreeWidgetItemIterator it(d->ui.list); (*it); ++it) { if (KShortcutsEditorItem *item = dynamic_cast(*it)) { item->commit(); } } } void KShortcutsEditor::save() { writeConfiguration(); // we have to call commit. If we wouldn't do that the changes would be // undone on deletion! That would lead to weird problems. Changes to // Global Shortcuts would vanish completely. Changes to local shortcuts // would vanish for this session. commit(); } // KDE5 : rename to undo() void KShortcutsEditor::undoChanges() { //This function used to crash sometimes when invoked by clicking on "cancel" //with Qt 4.2.something. Apparently items were deleted too early by Qt. //It seems to work with 4.3-ish Qt versions. Keep an eye on this. for (QTreeWidgetItemIterator it(d->ui.list); (*it); ++it) { if (KShortcutsEditorItem *item = dynamic_cast(*it)) { item->undo(); } } } //We ask the user here if there are any conflicts, as opposed to undoChanges(). //They don't do the same thing anyway, this just not to confuse any readers. //slot void KShortcutsEditor::allDefault() { d->allDefault(); } void KShortcutsEditor::printShortcuts() const { d->printShortcuts(); } KShortcutsEditor::ActionTypes KShortcutsEditor::actionTypes() const { return d->actionTypes; } void KShortcutsEditor::setActionTypes(ActionTypes actionTypes) { d->setActionTypes(actionTypes); } //--------------------------------------------------------------------- // KShortcutsEditorPrivate //--------------------------------------------------------------------- KShortcutsEditorPrivate::KShortcutsEditorPrivate(KShortcutsEditor *q) : q(q), delegate(nullptr) {} void KShortcutsEditorPrivate::initGUI(KShortcutsEditor::ActionTypes types, KShortcutsEditor::LetterShortcuts allowLetterShortcuts) { actionTypes = types; ui.setupUi(q); q->layout()->setContentsMargins(0, 0, 0, 0); ui.searchFilter->searchLine()->setTreeWidget(ui.list); // Plug into search line ui.list->header()->setSectionResizeMode(QHeaderView::ResizeToContents); ui.list->header()->hideSection(ShapeGesture); //mouse gestures didn't make it in time... ui.list->header()->hideSection(RockerGesture); #if HAVE_GLOBALACCEL bool hideGlobals = !(actionTypes & KShortcutsEditor::GlobalAction); #else bool hideGlobals = true; #endif if (hideGlobals) { ui.list->header()->hideSection(GlobalPrimary); ui.list->header()->hideSection(GlobalAlternate); } else if (!(actionTypes & ~KShortcutsEditor::GlobalAction)) { ui.list->header()->hideSection(LocalPrimary); ui.list->header()->hideSection(LocalAlternate); } // Create the Delegate. It is responsible for the KKeySeqeunceWidgets that // really change the shortcuts. delegate = new KShortcutsEditorDelegate( ui.list, allowLetterShortcuts == KShortcutsEditor::LetterShortcutsAllowed); ui.list->setItemDelegate(delegate); ui.list->setSelectionBehavior(QAbstractItemView::SelectItems); ui.list->setSelectionMode(QAbstractItemView::SingleSelection); //we have our own editing mechanism ui.list->setEditTriggers(QAbstractItemView::NoEditTriggers); ui.list->setAlternatingRowColors(true); //TODO listen to changes to global shortcuts QObject::connect(delegate, SIGNAL(shortcutChanged(QVariant,QModelIndex)), q, SLOT(capturedShortcut(QVariant,QModelIndex))); //hide the editor widget chen its item becomes hidden QObject::connect(ui.searchFilter->searchLine(), &KTreeWidgetSearchLine::hiddenChanged, delegate, &KShortcutsEditorDelegate::hiddenBySearchLine); ui.searchFilter->setFocus(); } void KShortcutsEditorPrivate::setActionTypes(KShortcutsEditor::ActionTypes types) { if (actionTypes == types) { return; } actionTypes = types; // show/hide the sections based on new selection QHeaderView *header = ui.list->header(); if (actionTypes & KShortcutsEditor::GlobalAction) { header->showSection(GlobalPrimary); header->showSection(GlobalAlternate); } else { header->hideSection(GlobalPrimary); header->hideSection(GlobalAlternate); } if (actionTypes & ~KShortcutsEditor::GlobalAction) { header->showSection(LocalPrimary); header->showSection(LocalAlternate); } else { header->hideSection(LocalPrimary); header->hideSection(LocalAlternate); } } bool KShortcutsEditorPrivate::addAction(QAction *action, QTreeWidgetItem *hier[], hierarchyLevel level) { // If the action name starts with unnamed- spit out a warning and ignore // it. That name will change at will and will break loading and writing QString actionName = action->objectName(); if (actionName.isEmpty() || actionName.startsWith(QLatin1String("unnamed-"))) { qCritical() << "Skipping action without name " << action->text() << "," << actionName << "!"; return false; } // This code doesn't allow editing of QAction. It can not distinguish // between default and active shortcuts. This breaks many assumptions the // editor makes. const QVariant value = action->property("isShortcutConfigurable"); if (!value.isValid() || value.toBool()) { new KShortcutsEditorItem((hier[level]), action); return true; } return false; } void KShortcutsEditorPrivate::allDefault() { for (QTreeWidgetItemIterator it(ui.list); (*it); ++it) { if (!(*it)->parent() || (*it)->type() != ActionItem) { continue; } KShortcutsEditorItem *item = static_cast(*it); QAction *act = item->m_action; QList defaultShortcuts = act->property("defaultShortcuts").value >(); if (act->shortcuts() != defaultShortcuts) { QKeySequence primary = defaultShortcuts.isEmpty() ? QKeySequence() : defaultShortcuts.at(0); QKeySequence alternate = defaultShortcuts.size() <= 1 ? QKeySequence() : defaultShortcuts.at(1); changeKeyShortcut(item, LocalPrimary, primary); changeKeyShortcut(item, LocalAlternate, alternate); } #if HAVE_GLOBALACCEL if (KGlobalAccel::self()->shortcut(act) != KGlobalAccel::self()->defaultShortcut(act)) { QList defaultShortcut = KGlobalAccel::self()->defaultShortcut(act); changeKeyShortcut(item, GlobalPrimary, primarySequence(defaultShortcut)); changeKeyShortcut(item, GlobalAlternate, alternateSequence(defaultShortcut)); } #endif #if 0 KShapeGesture actShapeGesture = KGestureMap::self()->shapeGesture(act); KShapeGesture actDefaultShapeGesture = KGestureMap::self()->defaultShapeGesture(act); if (actShapeGesture != actDefaultShapeGesture) { changeShapeGesture(item, actDefaultShapeGesture); } KRockerGesture actRockerGesture = KGestureMap::self()->rockerGesture(act); KRockerGesture actDefaultRockerGesture = KGestureMap::self()->defaultRockerGesture(act); if (actRockerGesture != actDefaultRockerGesture) { changeRockerGesture(item, actDefaultRockerGesture); } #endif } } //static KShortcutsEditorItem *KShortcutsEditorPrivate::itemFromIndex(QTreeWidget *const w, const QModelIndex &index) { QTreeWidgetItem *item = static_cast(w)->itemFromIndex(index); if (item && item->type() == ActionItem) { return static_cast(item); } return nullptr; } QTreeWidgetItem *KShortcutsEditorPrivate::findOrMakeItem(QTreeWidgetItem *parent, const QString &name) { for (int i = 0; i < parent->childCount(); i++) { QTreeWidgetItem *child = parent->child(i); if (child->text(0) == name) { return child; } } QTreeWidgetItem *ret = new QTreeWidgetItem(parent, NonActionItem); ret->setText(0, name); ui.list->expandItem(ret); ret->setFlags(ret->flags() & ~Qt::ItemIsSelectable); return ret; } //private slot void KShortcutsEditorPrivate::capturedShortcut(const QVariant &newShortcut, const QModelIndex &index) { //dispatch to the right handler if (!index.isValid()) { return; } int column = index.column(); KShortcutsEditorItem *item = itemFromIndex(ui.list, index); Q_ASSERT(item); if (column >= LocalPrimary && column <= GlobalAlternate) { changeKeyShortcut(item, column, newShortcut.value()); } #if 0 else if (column == ShapeGesture) { changeShapeGesture(item, newShortcut.value()); } else if (column == RockerGesture) { changeRockerGesture(item, newShortcut.value()); } #endif } void KShortcutsEditorPrivate::changeKeyShortcut(KShortcutsEditorItem *item, uint column, const QKeySequence &capture) { // The keySequence we get is cleared by KKeySequenceWidget. No conflicts. if (capture == item->keySequence(column)) { return; } item->setKeySequence(column, capture); emit q->keyChange(); //force view update item->setText(column, capture.toString(QKeySequence::NativeText)); } #if 0 void KShortcutsEditorPrivate::changeShapeGesture(KShortcutsEditorItem *item, const KShapeGesture &capture) { if (capture == KGestureMap::self()->shapeGesture(item->m_action)) { return; } if (capture.isValid()) { bool conflict = false; KShortcutsEditorItem *otherItem; //search for conflicts for (QTreeWidgetItemIterator it(ui.list); (*it); ++it) { if (!(*it)->parent() || (*it == item)) { continue; } otherItem = static_cast(*it); //comparisons are possibly expensive KShapeGesture otherGesture = KGestureMap::self()->shapeGesture(otherItem->m_action); if (!otherGesture.isValid()) { continue; } if (capture == otherGesture) { conflict = true; break; } } if (conflict && !stealShapeGesture(otherItem, capture)) { return; } } item->setShapeGesture(capture); } #endif #if 0 void KShortcutsEditorPrivate::changeRockerGesture(KShortcutsEditorItem *item, const KRockerGesture &capture) { if (capture == KGestureMap::self()->rockerGesture(item->m_action)) { return; } if (capture.isValid()) { bool conflict = false; KShortcutsEditorItem *otherItem; for (QTreeWidgetItemIterator it(ui.list); (*it); ++it) { if (!(*it)->parent() || (*it == item)) { continue; } otherItem = static_cast(*it); KRockerGesture otherGesture = KGestureMap::self()->rockerGesture(otherItem->m_action); if (capture == otherGesture) { conflict = true; break; } } if (conflict && !stealRockerGesture(otherItem, capture)) { return; } } item->setRockerGesture(capture); } #endif void KShortcutsEditorPrivate::clearConfiguration() { for (QTreeWidgetItemIterator it(ui.list); (*it); ++it) { if (!(*it)->parent()) { continue; } KShortcutsEditorItem *item = static_cast(*it); changeKeyShortcut(item, LocalPrimary, QKeySequence()); changeKeyShortcut(item, LocalAlternate, QKeySequence()); changeKeyShortcut(item, GlobalPrimary, QKeySequence()); changeKeyShortcut(item, GlobalAlternate, QKeySequence()); #if 0 changeShapeGesture(item, KShapeGesture()); #endif } } void KShortcutsEditorPrivate::importConfiguration(KConfigBase *config) { Q_ASSERT(config); if (!config) { return; } KConfigGroup globalShortcutsGroup(config, QStringLiteral("Global Shortcuts")); if ((actionTypes & KShortcutsEditor::GlobalAction) && globalShortcutsGroup.exists()) { for (QTreeWidgetItemIterator it(ui.list); (*it); ++it) { if (!(*it)->parent()) { continue; } KShortcutsEditorItem *item = static_cast(*it); const QString actionId = item->data(Id).toString(); if (!globalShortcutsGroup.hasKey(actionId)) continue; QList sc = QKeySequence::listFromString(globalShortcutsGroup.readEntry(actionId, QString())); changeKeyShortcut(item, GlobalPrimary, primarySequence(sc)); changeKeyShortcut(item, GlobalAlternate, alternateSequence(sc)); } } if (actionTypes & ~KShortcutsEditor::GlobalAction) { const KConfigGroup localShortcutsGroup(config, QStringLiteral("Shortcuts")); for (QTreeWidgetItemIterator it(ui.list); (*it); ++it) { if (!(*it)->parent()) { continue; } KShortcutsEditorItem *item = static_cast(*it); const QString actionId = item->data(Id).toString(); if (!localShortcutsGroup.hasKey(actionId)) continue; QList sc = QKeySequence::listFromString(localShortcutsGroup.readEntry(actionId, QString())); changeKeyShortcut(item, LocalPrimary, primarySequence(sc)); changeKeyShortcut(item, LocalAlternate, alternateSequence(sc)); } } } #if 0 bool KShortcutsEditorPrivate::stealShapeGesture(KShortcutsEditorItem *item, const KShapeGesture &gst) { QString title = i18n("Key Conflict"); QString message = i18n("The '%1' shape gesture has already been allocated to the \"%2\" action.\n" "Do you want to reassign it from that action to the current one?", gst.shapeName(), item->m_action->text()); if (KMessageBox::warningContinueCancel(q, message, title, KGuiItem(i18n("Reassign"))) != KMessageBox::Continue) { return false; } item->setShapeGesture(KShapeGesture()); return true; } #endif #if 0 bool KShortcutsEditorPrivate::stealRockerGesture(KShortcutsEditorItem *item, const KRockerGesture &gst) { QString title = i18n("Key Conflict"); QString message = i18n("The '%1' rocker gesture has already been allocated to the \"%2\" action.\n" "Do you want to reassign it from that action to the current one?", gst.rockerName(), item->m_action->text()); if (KMessageBox::warningContinueCancel(q, message, title, KGuiItem(i18n("Reassign"))) != KMessageBox::Continue) { return false; } item->setRockerGesture(KRockerGesture()); return true; } #endif /*TODO for the printShortcuts function Nice to have features (which I'm not sure I can do before may due to more important things): - adjust the general page borders, IMHO they're too wide - add a custom printer options page that allows to filter out all actions that don't have a shortcut set to reduce this list. IMHO this should be optional as people might want to simply print all and when they find a new action that they assign a shortcut they can simply use a pen to fill out the empty space - find a way to align the Main/Alternate/Global entries in the shortcuts column without adding borders. I first did this without a nested table but instead simply added 3 rows and merged the 3 cells in the Action name and description column, but unfortunately I didn't find a way to remove the borders between the 6 shortcut cells. */ void KShortcutsEditorPrivate::printShortcuts() const { // One cant print on wince #ifndef _WIN32_WCE QTreeWidgetItem *root = ui.list->invisibleRootItem(); QTextDocument doc; doc.setDefaultFont(QFontDatabase::systemFont(QFontDatabase::GeneralFont)); QTextCursor cursor(&doc); cursor.beginEditBlock(); QTextCharFormat headerFormat; headerFormat.setProperty(QTextFormat::FontSizeAdjustment, 3); headerFormat.setFontWeight(QFont::Bold); cursor.insertText(i18nc("header for an applications shortcut list", "Shortcuts for %1", QGuiApplication::applicationDisplayName()), headerFormat); QTextCharFormat componentFormat; componentFormat.setProperty(QTextFormat::FontSizeAdjustment, 2); componentFormat.setFontWeight(QFont::Bold); QTextBlockFormat componentBlockFormat = cursor.blockFormat(); componentBlockFormat.setTopMargin(16); componentBlockFormat.setBottomMargin(16); QTextTableFormat tableformat; tableformat.setHeaderRowCount(1); tableformat.setCellPadding(4.0); tableformat.setCellSpacing(0); tableformat.setBorderStyle(QTextFrameFormat::BorderStyle_Solid); tableformat.setBorder(0.5); QList > shortcutTitleToColumn; shortcutTitleToColumn << qMakePair(i18n("Main:"), LocalPrimary); shortcutTitleToColumn << qMakePair(i18n("Alternate:"), LocalAlternate); shortcutTitleToColumn << qMakePair(i18n("Global:"), GlobalPrimary); shortcutTitleToColumn << qMakePair(i18n("Global alternate:"), GlobalAlternate); for (int i = 0; i < root->childCount(); i++) { QTreeWidgetItem *item = root->child(i); cursor.insertBlock(componentBlockFormat, componentFormat); cursor.insertText(item->text(0)); QTextTable *table = cursor.insertTable(1, 3); table->setFormat(tableformat); int currow = 0; QTextTableCell cell = table->cellAt(currow, 0); QTextCharFormat format = cell.format(); format.setFontWeight(QFont::Bold); cell.setFormat(format); cell.firstCursorPosition().insertText(i18n("Action Name")); cell = table->cellAt(currow, 1); cell.setFormat(format); cell.firstCursorPosition().insertText(i18n("Shortcuts")); cell = table->cellAt(currow, 2); cell.setFormat(format); cell.firstCursorPosition().insertText(i18n("Description")); currow++; for (QTreeWidgetItemIterator it(item); *it; ++it) { if ((*it)->type() != ActionItem) { continue; } KShortcutsEditorItem *editoritem = static_cast(*it); table->insertRows(table->rows(), 1); QVariant data = editoritem->data(Name, Qt::DisplayRole); table->cellAt(currow, 0).firstCursorPosition().insertText(data.toString()); QTextTable *shortcutTable = nullptr; for (int k = 0; k < shortcutTitleToColumn.count(); k++) { data = editoritem->data(shortcutTitleToColumn.at(k).second, Qt::DisplayRole); QString key = data.value().toString(); if (!key.isEmpty()) { if (!shortcutTable) { shortcutTable = table->cellAt(currow, 1).firstCursorPosition().insertTable(1, 2); QTextTableFormat shortcutTableFormat = tableformat; shortcutTableFormat.setCellSpacing(0.0); shortcutTableFormat.setHeaderRowCount(0); shortcutTableFormat.setBorder(0.0); shortcutTable->setFormat(shortcutTableFormat); } else { shortcutTable->insertRows(shortcutTable->rows(), 1); } shortcutTable->cellAt(shortcutTable->rows() - 1, 0).firstCursorPosition().insertText(shortcutTitleToColumn.at(k).first); shortcutTable->cellAt(shortcutTable->rows() - 1, 1).firstCursorPosition().insertText(key); } } QAction *action = editoritem->m_action; cell = table->cellAt(currow, 2); format = cell.format(); format.setProperty(QTextFormat::FontSizeAdjustment, -1); cell.setFormat(format); cell.firstCursorPosition().insertHtml(action->whatsThis()); currow++; } cursor.movePosition(QTextCursor::End); } cursor.endEditBlock(); QPrinter printer; QPrintDialog *dlg = new QPrintDialog(&printer, q); if (dlg->exec() == QDialog::Accepted) { doc.print(&printer); } delete dlg; #endif } #include "moc_kshortcutseditor.cpp" diff --git a/src/kshortcutseditor.h b/src/kshortcutseditor.h index a5da6a5..5a86b0d 100644 --- a/src/kshortcutseditor.h +++ b/src/kshortcutseditor.h @@ -1,257 +1,259 @@ /* This file is part of the KDE libraries Copyright (C) 1997 Nicolas Hadacek Copyright (C) 2001,2001 Ellis Whitehead Copyright (C) 2006 Hamish Rodda Copyright (C) 2007 Roberto Raggi Copyright (C) 2007 Andreas Hartmetz Copyright (C) 2008 Michael Jansen This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library 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 Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef KSHORTCUTSEDITOR_H #define KSHORTCUTSEDITOR_H #include #include #if 0 #include #endif class KActionCollection; class KConfig; class KConfigBase; class KConfigGroup; class KGlobalAccel; class KShortcutsEditorPrivate; // KShortcutsEditor expects that the list of existing shortcuts is already // free of conflicts. If it is not, nothing will crash, but your users // won't like the resulting behavior. /** * @class KShortcutsEditor kshortcutseditor.h KShortcutsEditor * * @short Widget for configuration of KAccel and KGlobalAccel. * * Configure dictionaries of key/action associations for QActions, * including global shortcuts. * * The class takes care of all aspects of configuration, including * handling key conflicts internally. Connect to the allDefault() * slot if you want to set all configurable shortcuts to their * default values. * * @see KShortcutsDialog * @author Nicolas Hadacek * @author Hamish Rodda (KDE 4 porting) * @author Michael Jansen */ class KXMLGUI_EXPORT KShortcutsEditor : public QWidget { Q_OBJECT Q_PROPERTY(ActionTypes actionTypes READ actionTypes WRITE setActionTypes) public: enum ActionType { /// Actions which are triggered by any keypress in a widget which has the action added to it WidgetAction = Qt::WidgetShortcut /*0*/, /// Actions which are triggered by any keypress in a window which has the action added to it or its child widget(s) WindowAction = Qt::WindowShortcut /*1*/, /// Actions which are triggered by any keypress in the application ApplicationAction = Qt::ApplicationShortcut /*2*/, /// Actions which are triggered by any keypress in the windowing system GlobalAction = 4, /// All actions AllActions = 0xffffffff }; Q_DECLARE_FLAGS(ActionTypes, ActionType) enum LetterShortcuts { /// Shortcuts without a modifier are not allowed, /// so 'A' would not be valid, whereas 'Ctrl+A' would be. /// This only applies to printable characters, however. /// 'F1', 'Insert' etc. could still be used. LetterShortcutsDisallowed = 0, /// Letter shortcuts are allowed LetterShortcutsAllowed }; /** * Constructor. * * @param collection the KActionCollection to configure * @param parent parent widget * @param actionTypes types of actions to display in this widget. * @param allowLetterShortcuts set to LetterShortcutsDisallowed if unmodified alphanumeric * keys ('A', '1', etc.) are not permissible shortcuts. */ KShortcutsEditor(KActionCollection *collection, QWidget *parent, ActionTypes actionTypes = AllActions, LetterShortcuts allowLetterShortcuts = LetterShortcutsAllowed); /** * \overload * * Creates a key chooser without a starting action collection. * * @param parent parent widget * @param actionTypes types of actions to display in this widget. * @param allowLetterShortcuts set to LetterShortcutsDisallowed if unmodified alphanumeric * keys ('A', '1', etc.) are not permissible shortcuts. */ explicit KShortcutsEditor(QWidget *parent, ActionTypes actionTypes = AllActions, LetterShortcuts allowLetterShortcuts = LetterShortcutsAllowed); /// Destructor virtual ~KShortcutsEditor(); /** * Are the unsaved changes? */ bool isModified() const; /** * Removes all action collections from the editor */ void clearCollections(); /** * Insert an action collection, i.e. add all its actions to the ones * already associated with the KShortcutsEditor object. * @param title subtree title of this collection of shortcut. */ void addCollection(KActionCollection *, const QString &title = QString()); /** * Undo all change made since the last commit(). */ void undoChanges(); /** * Save the changes. * * Before saving the changes are committed. This saves the actions to disk. * Any KActionCollection objects with the xmlFile() value set will be * written to an XML file. All other will be written to the application's * rc file. */ void save(); /** * Commit the changes without saving. * * This commits the changes without saving. * * @since 4.2 */ void commit(); /** * Removes all configured shortcuts. */ void clearConfiguration(); /** * Write the current settings to the \p config object. * * This does not initialize the \p config object. It adds the * configuration. * * @note This will not save the global configuration! globalaccel holds * that part of the configuration. * @see writeGlobalConfig() * * @param config Config object to save to or, or null to use the * applications config object * */ void writeConfiguration(KConfigGroup *config = nullptr) const; /** * Export the current setting to configuration @p config. * * This initializes the configuration object. This will export the global * configuration too. * * @param config Config object */ -#ifndef KXMLGUI_NO_DEPRECATED - KXMLGUI_DEPRECATED void exportConfiguration(KConfig *config) const; -#endif void exportConfiguration(KConfigBase *config) const; +#if KXMLGUI_ENABLE_DEPRECATED_SINCE(5, 0) + KXMLGUI_DEPRECATED_VERSION(5, 0, "Use KShortcutsEditor::exportConfiguration(KConfigBase *config)") + void exportConfiguration(KConfig *config) const; +#endif /** * Import the settings from configuration @p config. * * This will remove all current setting before importing. All shortcuts * are set to QList() prior to importing from @p config! * * @param config Config object */ -#ifndef KXMLGUI_NO_DEPRECATED - KXMLGUI_DEPRECATED void importConfiguration(KConfig *config); -#endif void importConfiguration(KConfigBase *config); +#if KXMLGUI_ENABLE_DEPRECATED_SINCE(5, 0) + KXMLGUI_DEPRECATED_VERSION(5, 0, "Use KShortcutsEditor::importConfiguration(KConfigBase *config)") + void importConfiguration(KConfig *config); +#endif /** * Sets the types of actions to display in this widget. * * @param actionTypes New types of actions * @since 5.0 */ void setActionTypes(ActionTypes actionTypes); /** * * @return The types of actions currently displayed in this widget. * @since 5.0 */ ActionTypes actionTypes() const; Q_SIGNALS: /** * Emitted when an action's shortcut has been changed. **/ void keyChange(); public Q_SLOTS: /** * Resize columns to width required */ void resizeColumns(); /** * Set all shortcuts to their default values (bindings). **/ void allDefault(); /** * Opens a printing dialog to print all the shortcuts */ void printShortcuts() const; private: Q_PRIVATE_SLOT(d, void capturedShortcut(QVariant, const QModelIndex &)) private: friend class KShortcutsDialog; friend class KShortcutsEditorPrivate; KShortcutsEditorPrivate *const d; Q_DISABLE_COPY(KShortcutsEditor) }; Q_DECLARE_OPERATORS_FOR_FLAGS(KShortcutsEditor::ActionTypes) #endif // KSHORTCUTSEDITOR_H diff --git a/src/kshortcutwidget.cpp b/src/kshortcutwidget.cpp index 710d85d..d591fc0 100644 --- a/src/kshortcutwidget.cpp +++ b/src/kshortcutwidget.cpp @@ -1,158 +1,158 @@ /* This file is part of the KDE libraries Copyright (C) 2007 Andreas Hartmetz This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library 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 Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kshortcutwidget.h" #include "ui_kshortcutwidget.h" class KShortcutWidgetPrivate { public: KShortcutWidgetPrivate(KShortcutWidget *q) : q(q) {} //private slots void priKeySequenceChanged(const QKeySequence &); void altKeySequenceChanged(const QKeySequence &); //members KShortcutWidget *const q; Ui::KShortcutWidget ui; QList cut; bool holdChangedSignal; }; KShortcutWidget::KShortcutWidget(QWidget *parent) : QWidget(parent), d(new KShortcutWidgetPrivate(this)) { d->holdChangedSignal = false; d->ui.setupUi(this); connect(d->ui.priEditor, SIGNAL(keySequenceChanged(QKeySequence)), this, SLOT(priKeySequenceChanged(QKeySequence))); connect(d->ui.altEditor, SIGNAL(keySequenceChanged(QKeySequence)), this, SLOT(altKeySequenceChanged(QKeySequence))); } KShortcutWidget::~KShortcutWidget() { delete d; } void KShortcutWidget::setModifierlessAllowed(bool allow) { d->ui.priEditor->setModifierlessAllowed(allow); d->ui.altEditor->setModifierlessAllowed(allow); } bool KShortcutWidget::isModifierlessAllowed() { return d->ui.priEditor->isModifierlessAllowed(); } void KShortcutWidget::setClearButtonsShown(bool show) { d->ui.priEditor->setClearButtonShown(show); d->ui.altEditor->setClearButtonShown(show); } QList KShortcutWidget::shortcut() const { QList ret; ret << d->ui.priEditor->keySequence() << d->ui.altEditor->keySequence(); return ret; } -#ifndef KXMLGUI_NO_DEPRECATED +#if KXMLGUI_BUILD_DEPRECATED_SINCE(4, 1) void KShortcutWidget::setCheckActionList(const QList &checkList) { d->ui.priEditor->setCheckActionList(checkList); d->ui.altEditor->setCheckActionList(checkList); } #endif void KShortcutWidget::setCheckActionCollections(const QList &actionCollections) { d->ui.priEditor->setCheckActionCollections(actionCollections); d->ui.altEditor->setCheckActionCollections(actionCollections); } //slot void KShortcutWidget::applyStealShortcut() { d->ui.priEditor->applyStealShortcut(); d->ui.altEditor->applyStealShortcut(); } //slot void KShortcutWidget::setShortcut(const QList &newSc) { if (newSc == d->cut) { return; } d->holdChangedSignal = true; if (!newSc.isEmpty()) { d->ui.priEditor->setKeySequence(newSc.first()); } if (newSc.size() > 1) { d->ui.altEditor->setKeySequence(newSc.at(1)); } d->holdChangedSignal = false; emit shortcutChanged(d->cut); } //slot void KShortcutWidget::clearShortcut() { setShortcut(QList()); } //private slot void KShortcutWidgetPrivate::priKeySequenceChanged(const QKeySequence &seq) { if (cut.isEmpty()) { cut << seq; } else { cut[0] = seq; } if (!holdChangedSignal) { emit q->shortcutChanged(cut); } } //private slot void KShortcutWidgetPrivate::altKeySequenceChanged(const QKeySequence &seq) { if (cut.size() <= 1) { cut << seq; } else { cut[1] = seq; } if (!holdChangedSignal) { emit q->shortcutChanged(cut); } } #include "moc_kshortcutwidget.cpp" diff --git a/src/kshortcutwidget.h b/src/kshortcutwidget.h index 46fcc61..a63b172 100644 --- a/src/kshortcutwidget.h +++ b/src/kshortcutwidget.h @@ -1,101 +1,102 @@ /* This file is part of the KDE libraries Copyright (C) 2007 Andreas Hartmetz This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library 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 Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef KSHORTCUTWIDGET_H #define KSHORTCUTWIDGET_H #include #include #include #include class KActionCollection; class KShortcutWidgetPrivate; /** * @class KShortcutWidget kshortcutwidget.h KShortcutWidget * * \image html kshortcutwidget.png "KShortcutWidget" */ class KXMLGUI_EXPORT KShortcutWidget : public QWidget { Q_OBJECT Q_PROPERTY(bool modifierlessAllowed READ isModifierlessAllowed WRITE setModifierlessAllowed) public: KShortcutWidget(QWidget *parent = nullptr); ~KShortcutWidget(); void setModifierlessAllowed(bool allow); bool isModifierlessAllowed(); void setClearButtonsShown(bool show); QList shortcut() const; /** * Set a list of action collections to check against for conflictuous shortcut. * * If there is a conflictuous shortcut with a QAction, and that his shortcut can be configured * (KActionCollection::isShortcutConfigurable() returns true) the user will be prompted for eventually steal * the shortcut from this action * * Global shortcuts are automatically checked for conflicts * * Don't forget to call applyStealShortcut to actually steal the shortcut. * * @since 4.1 */ void setCheckActionCollections(const QList &actionCollections); +#if KXMLGUI_ENABLE_DEPRECATED_SINCE(4, 1) /** * @deprecated since 4.1 * Use setCheckActionCollections so that KShortcutWidget knows * in which action collection to call the writeSettings method after stealing * a shortcut from an action. */ -#ifndef KXMLGUI_NO_DEPRECATED - KXMLGUI_DEPRECATED void setCheckActionList(const QList &checkList); + KXMLGUI_DEPRECATED_VERSION(4, 1, "Use KShortcutWidget::setCheckActionCollections(const QList &)") + void setCheckActionList(const QList &checkList); #endif Q_SIGNALS: void shortcutChanged(const QList &cut); public Q_SLOTS: void setShortcut(const QList &cut); void clearShortcut(); /** * Actually remove the shortcut that the user wanted to steal, from the * action that was using it. * * To be called before you apply your changes. * No shortcuts are stolen until this function is called. */ void applyStealShortcut(); private: Q_PRIVATE_SLOT(d, void priKeySequenceChanged(const QKeySequence &)) Q_PRIVATE_SLOT(d, void altKeySequenceChanged(const QKeySequence &)) private: friend class KShortcutWidgetPrivate; KShortcutWidgetPrivate *const d; }; #endif //KSHORTCUTWIDGET_H diff --git a/src/ktoolbar.cpp b/src/ktoolbar.cpp index 83aa092..5dbd639 100644 --- a/src/ktoolbar.cpp +++ b/src/ktoolbar.cpp @@ -1,1420 +1,1420 @@ /* This file is part of the KDE libraries Copyright (C) 2000 Reginald Stadlbauer (reggie@kde.org) (C) 1997, 1998 Stephan Kulow (coolo@kde.org) (C) 1997, 1998 Mark Donohoe (donohoe@kde.org) (C) 1997, 1998 Sven Radej (radej@kde.org) (C) 1997, 1998 Matthias Ettrich (ettrich@kde.org) (C) 1999 Chris Schlaeger (cs@kde.org) (C) 1999 Kurt Granroth (granroth@kde.org) (C) 2005-2006 Hamish Rodda (rodda@kde.org) This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License version 2 as published by the Free Software Foundation. This library 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 Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "ktoolbar.h" #include #include #include #include #include #include #include #include #include #include #include #ifdef QT_DBUS_LIB #include #include #endif #include #include #include #include #include #include #include #include #include "kactioncollection.h" #include "kedittoolbar.h" #include "kxmlguifactory.h" #include "kxmlguiwindow.h" #include "ktoolbarhelper_p.h" /* Toolbar settings (e.g. icon size or toolButtonStyle) ===================================================== We have the following stack of settings (in order of priority) : - user-specified settings (loaded/saved in KConfig) - developer-specified settings in the XMLGUI file (if using xmlgui) (cannot change at runtime) - KDE-global default (user-configurable; can change at runtime) and when switching between kparts, they are saved as xml in memory, which, in the unlikely case of no-kmainwindow-autosaving, could be different from the user-specified settings saved in KConfig and would have priority over it. So, in summary, without XML: Global config / User settings (loaded/saved in kconfig) and with XML: Global config / App-XML attributes / User settings (loaded/saved in kconfig) And all those settings (except the KDE-global defaults) have to be stored in memory since we cannot retrieve them at random points in time, not knowing the xml document nor config file that holds these settings. Hence the iconSizeSettings and toolButtonStyleSettings arrays. For instance, if you change the KDE-global default, whether this makes a change on a given toolbar depends on whether there are settings at Level_AppXML or Level_UserSettings. Only if there are no settings at those levels, should the change of KDEDefault make a difference. */ enum SettingLevel { Level_KDEDefault, Level_AppXML, Level_UserSettings, NSettingLevels }; enum { Unset = -1 }; class Q_DECL_HIDDEN KToolBar::Private { public: Private(KToolBar *qq) : q(qq), isMainToolBar(false), -#ifndef KXMLGUI_NO_DEPRECATED +#if KXMLGUI_BUILD_DEPRECATED_SINCE(5, 0) enableContext(true), #endif unlockedMovable(true), contextOrient(nullptr), contextMode(nullptr), contextSize(nullptr), contextButtonTitle(nullptr), contextShowText(nullptr), contextButtonAction(nullptr), contextTop(nullptr), contextLeft(nullptr), contextRight(nullptr), contextBottom(nullptr), contextIcons(nullptr), contextTextRight(nullptr), contextText(nullptr), contextTextUnder(nullptr), contextLockAction(nullptr), dropIndicatorAction(nullptr), context(nullptr), dragAction(nullptr) { } void slotAppearanceChanged(); void slotContextAboutToShow(); void slotContextAboutToHide(); void slotContextLeft(); void slotContextRight(); void slotContextShowText(); void slotContextTop(); void slotContextBottom(); void slotContextIcons(); void slotContextText(); void slotContextTextRight(); void slotContextTextUnder(); void slotContextIconSize(); void slotLockToolBars(bool lock); void init(bool readConfig = true, bool isMainToolBar = false); QString getPositionAsString() const; QMenu *contextMenu(const QPoint &globalPos); void setLocked(bool locked); void adjustSeparatorVisibility(); void loadKDESettings(); void applyCurrentSettings(); QAction *findAction(const QString &actionName, KXMLGUIClient **client = nullptr) const; static Qt::ToolButtonStyle toolButtonStyleFromString(const QString &style); static QString toolButtonStyleToString(Qt::ToolButtonStyle); static Qt::ToolBarArea positionFromString(const QString &position); static Qt::ToolButtonStyle toolButtonStyleSetting(); KToolBar *q; bool isMainToolBar : 1; -#ifndef KXMLGUI_NO_DEPRECATED +#if KXMLGUI_BUILD_DEPRECATED_SINCE(5, 0) bool enableContext : 1; #endif bool unlockedMovable : 1; static bool s_editable; static bool s_locked; QSet xmlguiClients; QMenu *contextOrient; QMenu *contextMode; QMenu *contextSize; QAction *contextButtonTitle; QAction *contextShowText; QAction *contextButtonAction; QAction *contextTop; QAction *contextLeft; QAction *contextRight; QAction *contextBottom; QAction *contextIcons; QAction *contextTextRight; QAction *contextText; QAction *contextTextUnder; KToggleAction *contextLockAction; QMap contextIconSizes; class IntSetting { public: IntSetting() { for (int level = 0; level < NSettingLevels; ++level) { values[level] = Unset; } } int currentValue() const { int val = Unset; for (int level = 0; level < NSettingLevels; ++level) { if (values[level] != Unset) { val = values[level]; } } return val; } // Default value as far as the user is concerned is kde-global + app-xml. // If currentValue()==defaultValue() then nothing to write into kconfig. int defaultValue() const { int val = Unset; for (int level = 0; level < Level_UserSettings; ++level) { if (values[level] != Unset) { val = values[level]; } } return val; } QString toString() const { QString str; for (int level = 0; level < NSettingLevels; ++level) { str += QString::number(values[level]) + QLatin1Char(' '); } return str; } int &operator[](int index) { return values[index]; } private: int values[NSettingLevels]; }; IntSetting iconSizeSettings; IntSetting toolButtonStyleSettings; // either Qt::ToolButtonStyle or -1, hence "int". QList actionsBeingDragged; QAction *dropIndicatorAction; QMenu *context; QAction *dragAction; QPoint dragStartPosition; }; bool KToolBar::Private::s_editable = false; bool KToolBar::Private::s_locked = true; void KToolBar::Private::init(bool readConfig, bool _isMainToolBar) { isMainToolBar = _isMainToolBar; loadKDESettings(); // also read in our configurable settings (for non-xmlgui toolbars) if (readConfig) { KConfigGroup cg(KSharedConfig::openConfig(), QString()); q->applySettings(cg); } if (q->mainWindow()) { // Get notified when settings change connect(q, &QToolBar::allowedAreasChanged, q->mainWindow(), &KMainWindow::setSettingsDirty); connect(q, &QToolBar::iconSizeChanged, q->mainWindow(), &KMainWindow::setSettingsDirty); connect(q, &QToolBar::toolButtonStyleChanged, q->mainWindow(), &KMainWindow::setSettingsDirty); connect(q, &QToolBar::movableChanged, q->mainWindow(), &KMainWindow::setSettingsDirty); connect(q, &QToolBar::orientationChanged, q->mainWindow(), &KMainWindow::setSettingsDirty); } if (!KAuthorized::authorize(QStringLiteral("movable_toolbars"))) { q->setMovable(false); } else { q->setMovable(!KToolBar::toolBarsLocked()); } q->toggleViewAction()->setEnabled(KAuthorized::authorizeAction(QStringLiteral("options_show_toolbar"))); connect(q, &QToolBar::movableChanged, q, &KToolBar::slotMovableChanged); q->setAcceptDrops(true); #ifdef QT_DBUS_LIB QDBusConnection::sessionBus().connect(QString(), QStringLiteral("/KToolBar"), QStringLiteral("org.kde.KToolBar"), QStringLiteral("styleChanged"), q, SLOT(slotAppearanceChanged())); #endif connect(KIconLoader::global(), SIGNAL(iconLoaderSettingsChanged()), q, SLOT(slotAppearanceChanged())); } QString KToolBar::Private::getPositionAsString() const { // get all of the stuff to save switch (q->mainWindow()->toolBarArea(const_cast(q))) { case Qt::BottomToolBarArea: return QStringLiteral("Bottom"); case Qt::LeftToolBarArea: return QStringLiteral("Left"); case Qt::RightToolBarArea: return QStringLiteral("Right"); case Qt::TopToolBarArea: default: return QStringLiteral("Top"); } } QMenu *KToolBar::Private::contextMenu(const QPoint &globalPos) { if (!context) { context = new QMenu(q); contextButtonTitle = context->addSection(i18nc("@title:menu", "Show Text")); contextShowText = context->addAction(QString(), q, SLOT(slotContextShowText())); context->addSection(i18nc("@title:menu", "Toolbar Settings")); contextOrient = new QMenu(i18nc("Toolbar orientation", "Orientation"), context); contextTop = contextOrient->addAction(i18nc("toolbar position string", "Top"), q, SLOT(slotContextTop())); contextTop->setChecked(true); contextLeft = contextOrient->addAction(i18nc("toolbar position string", "Left"), q, SLOT(slotContextLeft())); contextRight = contextOrient->addAction(i18nc("toolbar position string", "Right"), q, SLOT(slotContextRight())); contextBottom = contextOrient->addAction(i18nc("toolbar position string", "Bottom"), q, SLOT(slotContextBottom())); QActionGroup *positionGroup = new QActionGroup(contextOrient); const auto orientActions = contextOrient->actions(); for (QAction *action : orientActions) { action->setActionGroup(positionGroup); action->setCheckable(true); } contextMode = new QMenu(i18n("Text Position"), context); contextIcons = contextMode->addAction(i18n("Icons Only"), q, SLOT(slotContextIcons())); contextText = contextMode->addAction(i18n("Text Only"), q, SLOT(slotContextText())); contextTextRight = contextMode->addAction(i18n("Text Alongside Icons"), q, SLOT(slotContextTextRight())); contextTextUnder = contextMode->addAction(i18n("Text Under Icons"), q, SLOT(slotContextTextUnder())); QActionGroup *textGroup = new QActionGroup(contextMode); const auto modeActions = contextMode->actions(); for (QAction *action : modeActions) { action->setActionGroup(textGroup); action->setCheckable(true); } contextSize = new QMenu(i18n("Icon Size"), context); contextIconSizes.insert(contextSize->addAction(i18nc("@item:inmenu Icon size", "Default"), q, SLOT(slotContextIconSize())), iconSizeSettings.defaultValue()); // Query the current theme for available sizes KIconTheme *theme = KIconLoader::global()->theme(); QList avSizes; if (theme) { avSizes = theme->querySizes(isMainToolBar ? KIconLoader::MainToolbar : KIconLoader::Toolbar); } std::sort(avSizes.begin(), avSizes.end()); if (avSizes.count() < 10) { // Fixed or threshold type icons for (int it : qAsConst(avSizes)) { QString text; if (it < 19) { text = i18n("Small (%1x%2)", it, it); } else if (it < 25) { text = i18n("Medium (%1x%2)", it, it); } else if (it < 35) { text = i18n("Large (%1x%2)", it, it); } else { text = i18n("Huge (%1x%2)", it, it); } // save the size in the contextIconSizes map contextIconSizes.insert(contextSize->addAction(text, q, SLOT(slotContextIconSize())), it); } } else { // Scalable icons. const int progression[] = { 16, 22, 32, 48, 64, 96, 128, 192, 256 }; for (uint i = 0; i < 9; i++) { for (int it : qAsConst(avSizes)) { if (it >= progression[ i ]) { QString text; if (it < 19) { text = i18n("Small (%1x%2)", it, it); } else if (it < 25) { text = i18n("Medium (%1x%2)", it, it); } else if (it < 35) { text = i18n("Large (%1x%2)", it, it); } else { text = i18n("Huge (%1x%2)", it, it); } // save the size in the contextIconSizes map contextIconSizes.insert(contextSize->addAction(text, q, SLOT(slotContextIconSize())), it); break; } } } } QActionGroup *sizeGroup = new QActionGroup(contextSize); const auto sizeActions = contextSize->actions(); for (QAction *action : sizeActions) { action->setActionGroup(sizeGroup); action->setCheckable(true); } if (!q->toolBarsLocked() && !q->isMovable()) { unlockedMovable = false; } delete contextLockAction; contextLockAction = new KToggleAction(QIcon::fromTheme(QStringLiteral("system-lock-screen")), i18n("Lock Toolbar Positions"), q); contextLockAction->setChecked(q->toolBarsLocked()); connect(contextLockAction, SIGNAL(toggled(bool)), q, SLOT(slotLockToolBars(bool))); // Now add the actions to the menu context->addMenu(contextMode); context->addMenu(contextSize); context->addMenu(contextOrient); context->addSeparator(); connect(context, SIGNAL(aboutToShow()), q, SLOT(slotContextAboutToShow())); } contextButtonAction = q->actionAt(q->mapFromGlobal(globalPos)); if (contextButtonAction) { contextShowText->setText(contextButtonAction->text()); contextShowText->setIcon(contextButtonAction->icon()); contextShowText->setCheckable(true); } contextOrient->menuAction()->setVisible(!q->toolBarsLocked()); // Unplugging a submenu from abouttohide leads to the popupmenu floating around // So better simply call that code from after exec() returns (DF) //connect(context, SIGNAL(aboutToHide()), this, SLOT(slotContextAboutToHide())); return context; } void KToolBar::Private::setLocked(bool locked) { if (unlockedMovable) { q->setMovable(!locked); } } void KToolBar::Private::adjustSeparatorVisibility() { bool visibleNonSeparator = false; int separatorToShow = -1; for (int index = 0; index < q->actions().count(); ++index) { QAction *action = q->actions().at(index); if (action->isSeparator()) { if (visibleNonSeparator) { separatorToShow = index; visibleNonSeparator = false; } else { action->setVisible(false); } } else if (!visibleNonSeparator) { if (action->isVisible()) { visibleNonSeparator = true; if (separatorToShow != -1) { q->actions().at(separatorToShow)->setVisible(true); separatorToShow = -1; } } } } if (separatorToShow != -1) { q->actions().at(separatorToShow)->setVisible(false); } } Qt::ToolButtonStyle KToolBar::Private::toolButtonStyleFromString(const QString &_style) { QString style = _style.toLower(); if (style == QLatin1String("textbesideicon") || style == QLatin1String("icontextright")) { return Qt::ToolButtonTextBesideIcon; } else if (style == QLatin1String("textundericon") || style == QLatin1String("icontextbottom")) { return Qt::ToolButtonTextUnderIcon; } else if (style == QLatin1String("textonly")) { return Qt::ToolButtonTextOnly; } else { return Qt::ToolButtonIconOnly; } } QString KToolBar::Private::toolButtonStyleToString(Qt::ToolButtonStyle style) { switch (style) { case Qt::ToolButtonIconOnly: default: return QStringLiteral("IconOnly"); case Qt::ToolButtonTextBesideIcon: return QStringLiteral("TextBesideIcon"); case Qt::ToolButtonTextOnly: return QStringLiteral("TextOnly"); case Qt::ToolButtonTextUnderIcon: return QStringLiteral("TextUnderIcon"); } } Qt::ToolBarArea KToolBar::Private::positionFromString(const QString &position) { Qt::ToolBarArea newposition = Qt::TopToolBarArea; if (position == QLatin1String("left")) { newposition = Qt::LeftToolBarArea; } else if (position == QLatin1String("bottom")) { newposition = Qt::BottomToolBarArea; } else if (position == QLatin1String("right")) { newposition = Qt::RightToolBarArea; } return newposition; } // Global setting was changed void KToolBar::Private::slotAppearanceChanged() { loadKDESettings(); applyCurrentSettings(); } Qt::ToolButtonStyle KToolBar::Private::toolButtonStyleSetting() { KConfigGroup group(KSharedConfig::openConfig(), "Toolbar style"); const QString fallback = KToolBar::Private::toolButtonStyleToString(Qt::ToolButtonTextBesideIcon); return KToolBar::Private::toolButtonStyleFromString(group.readEntry("ToolButtonStyle", fallback)); } void KToolBar::Private::loadKDESettings() { iconSizeSettings[Level_KDEDefault] = q->iconSizeDefault(); if (isMainToolBar) { toolButtonStyleSettings[Level_KDEDefault] = toolButtonStyleSetting(); } else { const QString fallBack = toolButtonStyleToString(Qt::ToolButtonTextBesideIcon); /** TODO: if we get complaints about text beside icons on small screens, try the following code out on such systems - aseigo. // if we are on a small screen with a non-landscape ratio, then // we revert to text under icons since width is probably not our // friend in such cases QDesktopWidget *desktop = QApplication::desktop(); QRect screenGeom = desktop->screenGeometry(desktop->primaryScreen()); qreal ratio = screenGeom.width() / qreal(screenGeom.height()); if (screenGeom.width() < 1024 && ratio <= 1.4) { fallBack = "TextUnderIcon"; } **/ KConfigGroup group(KSharedConfig::openConfig(), "Toolbar style"); const QString value = group.readEntry("ToolButtonStyleOtherToolbars", fallBack); toolButtonStyleSettings[Level_KDEDefault] = KToolBar::Private::toolButtonStyleFromString(value); } } // Call this after changing something in d->iconSizeSettings or d->toolButtonStyleSettings void KToolBar::Private::applyCurrentSettings() { //qCDebug(DEBUG_KXMLGUI) << q->objectName() << "iconSizeSettings:" << iconSizeSettings.toString() << "->" << iconSizeSettings.currentValue(); const int currentIconSize = iconSizeSettings.currentValue(); q->setIconSize(QSize(currentIconSize, currentIconSize)); //qCDebug(DEBUG_KXMLGUI) << q->objectName() << "toolButtonStyleSettings:" << toolButtonStyleSettings.toString() << "->" << toolButtonStyleSettings.currentValue(); q->setToolButtonStyle(static_cast(toolButtonStyleSettings.currentValue())); // And remember to save the new look later KMainWindow *kmw = q->mainWindow(); if (kmw) { kmw->setSettingsDirty(); } } QAction *KToolBar::Private::findAction(const QString &actionName, KXMLGUIClient **clientOut) const { for (KXMLGUIClient *client : xmlguiClients) { QAction *action = client->actionCollection()->action(actionName); if (action) { if (clientOut) { *clientOut = client; } return action; } } return nullptr; } void KToolBar::Private::slotContextAboutToShow() { /** * The idea here is to reuse the "static" part of the menu to save time. * But the "Toolbars" action is dynamic (can be a single action or a submenu) * and ToolBarHandler::setupActions() deletes it, so better not keep it around. * So we currently plug/unplug the last two actions of the menu. * Another way would be to keep around the actions and plug them all into a (new each time) popupmenu. */ KXmlGuiWindow *kmw = qobject_cast(q->mainWindow()); // try to find "configure toolbars" action QAction *configureAction = nullptr; const char *actionName = KStandardAction::name(KStandardAction::ConfigureToolbars); configureAction = findAction(QLatin1String(actionName)); if (!configureAction && kmw) { configureAction = kmw->actionCollection()->action(QLatin1String(actionName)); } if (configureAction) { context->addAction(configureAction); } context->addAction(contextLockAction); if (kmw) { kmw->setupToolbarMenuActions(); // Only allow hiding a toolbar if the action is also plugged somewhere else (e.g. menubar) QAction *tbAction = kmw->toolBarMenuAction(); if (!q->toolBarsLocked() && tbAction && !tbAction->associatedWidgets().isEmpty()) { context->addAction(tbAction); } } KEditToolBar::setGlobalDefaultToolBar(q->QObject::objectName().toLatin1().constData()); // Check the actions that should be checked switch (q->toolButtonStyle()) { case Qt::ToolButtonIconOnly: default: contextIcons->setChecked(true); break; case Qt::ToolButtonTextBesideIcon: contextTextRight->setChecked(true); break; case Qt::ToolButtonTextOnly: contextText->setChecked(true); break; case Qt::ToolButtonTextUnderIcon: contextTextUnder->setChecked(true); break; } QMapIterator< QAction *, int > it = contextIconSizes; while (it.hasNext()) { it.next(); if (it.value() == q->iconSize().width()) { it.key()->setChecked(true); break; } } switch (q->mainWindow()->toolBarArea(q)) { case Qt::BottomToolBarArea: contextBottom->setChecked(true); break; case Qt::LeftToolBarArea: contextLeft->setChecked(true); break; case Qt::RightToolBarArea: contextRight->setChecked(true); break; default: case Qt::TopToolBarArea: contextTop->setChecked(true); break; } const bool showButtonSettings = contextButtonAction && !contextShowText->text().isEmpty() && contextTextRight->isChecked(); contextButtonTitle->setVisible(showButtonSettings); contextShowText->setVisible(showButtonSettings); if (showButtonSettings) { contextShowText->setChecked(contextButtonAction->priority() >= QAction::NormalPriority); } } void KToolBar::Private::slotContextAboutToHide() { // We have to unplug whatever slotContextAboutToShow plugged into the menu. // Unplug the toolbar menu action KXmlGuiWindow *kmw = qobject_cast(q->mainWindow()); if (kmw && kmw->toolBarMenuAction()) { if (kmw->toolBarMenuAction()->associatedWidgets().count() > 1) { context->removeAction(kmw->toolBarMenuAction()); } } // Unplug the configure toolbars action too, since it's afterwards anyway QAction *configureAction = nullptr; const char *actionName = KStandardAction::name(KStandardAction::ConfigureToolbars); configureAction = findAction(QLatin1String(actionName)); if (!configureAction && kmw) { configureAction = kmw->actionCollection()->action(QLatin1String(actionName)); } if (configureAction) { context->removeAction(configureAction); } context->removeAction(contextLockAction); } void KToolBar::Private::slotContextLeft() { q->mainWindow()->addToolBar(Qt::LeftToolBarArea, q); } void KToolBar::Private::slotContextRight() { q->mainWindow()->addToolBar(Qt::RightToolBarArea, q); } void KToolBar::Private::slotContextShowText() { Q_ASSERT(contextButtonAction); const QAction::Priority priority = contextShowText->isChecked() ? QAction::NormalPriority : QAction::LowPriority; contextButtonAction->setPriority(priority); // Find to which xml file and componentData the action belongs to QString componentName; QString filename; KXMLGUIClient *client; if (findAction(contextButtonAction->objectName(), &client)) { componentName = client->componentName(); filename = client->xmlFile(); } if (filename.isEmpty()) { componentName = QCoreApplication::applicationName(); filename = componentName + QLatin1String("ui.rc"); } // Save the priority state of the action const QString configFile = KXMLGUIFactory::readConfigFile(filename, componentName); QDomDocument document; document.setContent(configFile); QDomElement elem = KXMLGUIFactory::actionPropertiesElement(document); QDomElement actionElem = KXMLGUIFactory::findActionByName(elem, contextButtonAction->objectName(), true); actionElem.setAttribute(QStringLiteral("priority"), priority); KXMLGUIFactory::saveConfigFile(document, filename, componentName); } void KToolBar::Private::slotContextTop() { q->mainWindow()->addToolBar(Qt::TopToolBarArea, q); } void KToolBar::Private::slotContextBottom() { q->mainWindow()->addToolBar(Qt::BottomToolBarArea, q); } void KToolBar::Private::slotContextIcons() { q->setToolButtonStyle(Qt::ToolButtonIconOnly); toolButtonStyleSettings[Level_UserSettings] = q->toolButtonStyle(); } void KToolBar::Private::slotContextText() { q->setToolButtonStyle(Qt::ToolButtonTextOnly); toolButtonStyleSettings[Level_UserSettings] = q->toolButtonStyle(); } void KToolBar::Private::slotContextTextUnder() { q->setToolButtonStyle(Qt::ToolButtonTextUnderIcon); toolButtonStyleSettings[Level_UserSettings] = q->toolButtonStyle(); } void KToolBar::Private::slotContextTextRight() { q->setToolButtonStyle(Qt::ToolButtonTextBesideIcon); toolButtonStyleSettings[Level_UserSettings] = q->toolButtonStyle(); } void KToolBar::Private::slotContextIconSize() { QAction *action = qobject_cast(q->sender()); if (action && contextIconSizes.contains(action)) { const int iconSize = contextIconSizes.value(action); q->setIconDimensions(iconSize); } } void KToolBar::Private::slotLockToolBars(bool lock) { q->setToolBarsLocked(lock); } KToolBar::KToolBar(QWidget *parent, bool isMainToolBar, bool readConfig) : QToolBar(parent), d(new Private(this)) { d->init(readConfig, isMainToolBar); // KToolBar is auto-added to the top area of the main window if parent is a QMainWindow if (QMainWindow *mw = qobject_cast(parent)) { mw->addToolBar(this); } } KToolBar::KToolBar(const QString &objectName, QWidget *parent, bool readConfig) : QToolBar(parent), d(new Private(this)) { setObjectName(objectName); // mainToolBar -> isMainToolBar = true -> buttonStyle is configurable // others -> isMainToolBar = false -> ### hardcoded default for buttonStyle !!! should be configurable? -> hidden key added d->init(readConfig, (objectName == QLatin1String("mainToolBar"))); // KToolBar is auto-added to the top area of the main window if parent is a QMainWindow if (QMainWindow *mw = qobject_cast(parent)) { mw->addToolBar(this); } } KToolBar::KToolBar(const QString &objectName, QMainWindow *parent, Qt::ToolBarArea area, bool newLine, bool isMainToolBar, bool readConfig) : QToolBar(parent), d(new Private(this)) { setObjectName(objectName); d->init(readConfig, isMainToolBar); if (newLine) { mainWindow()->addToolBarBreak(area); } mainWindow()->addToolBar(area, this); if (newLine) { mainWindow()->addToolBarBreak(area); } } KToolBar::~KToolBar() { delete d->contextLockAction; delete d; } -#ifndef KXMLGUI_NO_DEPRECATED +#if KXMLGUI_BUILD_DEPRECATED_SINCE(5, 0) void KToolBar::setContextMenuEnabled(bool enable) { d->enableContext = enable; } #endif -#ifndef KXMLGUI_NO_DEPRECATED +#if KXMLGUI_BUILD_DEPRECATED_SINCE(5, 0) bool KToolBar::contextMenuEnabled() const { return d->enableContext; } #endif void KToolBar::saveSettings(KConfigGroup &cg) { Q_ASSERT(!cg.name().isEmpty()); const int currentIconSize = iconSize().width(); //qCDebug(DEBUG_KXMLGUI) << objectName() << currentIconSize << d->iconSizeSettings.toString() << "defaultValue=" << d->iconSizeSettings.defaultValue(); if (!cg.hasDefault("IconSize") && currentIconSize == d->iconSizeSettings.defaultValue()) { cg.revertToDefault("IconSize"); d->iconSizeSettings[Level_UserSettings] = Unset; } else { cg.writeEntry("IconSize", currentIconSize); d->iconSizeSettings[Level_UserSettings] = currentIconSize; } const Qt::ToolButtonStyle currentToolButtonStyle = toolButtonStyle(); if (!cg.hasDefault("ToolButtonStyle") && currentToolButtonStyle == d->toolButtonStyleSettings.defaultValue()) { cg.revertToDefault("ToolButtonStyle"); d->toolButtonStyleSettings[Level_UserSettings] = Unset; } else { cg.writeEntry("ToolButtonStyle", d->toolButtonStyleToString(currentToolButtonStyle)); d->toolButtonStyleSettings[Level_UserSettings] = currentToolButtonStyle; } } -#ifndef KXMLGUI_NO_DEPRECATED +#if KXMLGUI_BUILD_DEPRECATED_SINCE(5, 0) void KToolBar::setXMLGUIClient(KXMLGUIClient *client) { d->xmlguiClients.clear(); d->xmlguiClients << client; } #endif void KToolBar::addXMLGUIClient(KXMLGUIClient *client) { d->xmlguiClients << client; } void KToolBar::removeXMLGUIClient(KXMLGUIClient *client) { d->xmlguiClients.remove(client); } void KToolBar::contextMenuEvent(QContextMenuEvent *event) { -#ifndef KXMLGUI_NO_DEPRECATED +#if KXMLGUI_BUILD_DEPRECATED_SINCE(5, 0) if (mainWindow() && d->enableContext) { QPointer guard(this); const QPoint globalPos = event->globalPos(); d->contextMenu(globalPos)->exec(globalPos); // "Configure Toolbars" recreates toolbars, so we might not exist anymore. if (guard) { d->slotContextAboutToHide(); } return; } #endif QToolBar::contextMenuEvent(event); } void KToolBar::loadState(const QDomElement &element) { QMainWindow *mw = mainWindow(); if (!mw) { return; } { const QString& i18nText = KToolbarHelper::i18nToolBarName(element); if (!i18nText.isEmpty()) { setWindowTitle(i18nText); } } /* This method is called in order to load toolbar settings from XML. However this can be used in two rather different cases: - for the initial loading of the app's XML. In that case the settings are only the defaults (Level_AppXML), the user's KConfig settings will override them - for later re-loading when switching between parts in KXMLGUIFactory. In that case the XML contains the final settings, not the defaults. We do need the defaults, and the toolbar might have been completely deleted and recreated meanwhile. So we store the app-default settings into the XML. */ bool loadingAppDefaults = true; if (element.hasAttribute(QStringLiteral("tempXml"))) { // this isn't the first time, so the app-xml defaults have been saved into the (in-memory) XML loadingAppDefaults = false; const QString iconSizeDefault = element.attribute(QStringLiteral("iconSizeDefault")); if (!iconSizeDefault.isEmpty()) { d->iconSizeSettings[Level_AppXML] = iconSizeDefault.toInt(); } const QString toolButtonStyleDefault = element.attribute(QStringLiteral("toolButtonStyleDefault")); if (!toolButtonStyleDefault.isEmpty()) { d->toolButtonStyleSettings[Level_AppXML] = d->toolButtonStyleFromString(toolButtonStyleDefault); } } else { // loading app defaults bool newLine = false; QString attrNewLine = element.attribute(QStringLiteral("newline")).toLower(); if (!attrNewLine.isEmpty()) { newLine = (attrNewLine == QLatin1String("true")); } if (newLine && mw) { mw->insertToolBarBreak(this); } } int newIconSize = -1; if (element.hasAttribute(QStringLiteral("iconSize"))) { bool ok; newIconSize = element.attribute(QStringLiteral("iconSize")).trimmed().toInt(&ok); if (!ok) { newIconSize = -1; } } if (newIconSize != -1) { d->iconSizeSettings[loadingAppDefaults ? Level_AppXML : Level_UserSettings] = newIconSize; } const QString newToolButtonStyle = element.attribute(QStringLiteral("iconText")); if (!newToolButtonStyle.isEmpty()) { d->toolButtonStyleSettings[loadingAppDefaults ? Level_AppXML : Level_UserSettings] = d->toolButtonStyleFromString(newToolButtonStyle); } bool hidden = false; { QString attrHidden = element.attribute(QStringLiteral("hidden")).toLower(); if (!attrHidden.isEmpty()) { hidden = (attrHidden == QLatin1String("true")); } } Qt::ToolBarArea pos = Qt::NoToolBarArea; { QString attrPosition = element.attribute(QStringLiteral("position")).toLower(); if (!attrPosition.isEmpty()) { pos = KToolBar::Private::positionFromString(attrPosition); } } if (pos != Qt::NoToolBarArea) { mw->addToolBar(pos, this); } setVisible(!hidden); d->applyCurrentSettings(); } // Called when switching between xmlgui clients, in order to find any unsaved settings // again when switching back to the current xmlgui client. void KToolBar::saveState(QDomElement ¤t) const { Q_ASSERT(!current.isNull()); current.setAttribute(QStringLiteral("tempXml"), QStringLiteral("true")); current.setAttribute(QStringLiteral("noMerge"), QStringLiteral("1")); current.setAttribute(QStringLiteral("position"), d->getPositionAsString().toLower()); current.setAttribute(QStringLiteral("hidden"), isHidden() ? QStringLiteral("true") : QStringLiteral("false")); const int currentIconSize = iconSize().width(); if (currentIconSize == d->iconSizeSettings.defaultValue()) { current.removeAttribute(QStringLiteral("iconSize")); } else { current.setAttribute(QStringLiteral("iconSize"), iconSize().width()); } if (toolButtonStyle() == d->toolButtonStyleSettings.defaultValue()) { current.removeAttribute(QStringLiteral("iconText")); } else { current.setAttribute(QStringLiteral("iconText"), d->toolButtonStyleToString(toolButtonStyle())); } // Note: if this method is used by more than KXMLGUIBuilder, e.g. to save XML settings to *disk*, // then the stuff below shouldn't always be done. This is not the case currently though. if (d->iconSizeSettings[Level_AppXML] != Unset) { current.setAttribute(QStringLiteral("iconSizeDefault"), d->iconSizeSettings[Level_AppXML]); } if (d->toolButtonStyleSettings[Level_AppXML] != Unset) { const Qt::ToolButtonStyle bs = static_cast(d->toolButtonStyleSettings[Level_AppXML]); current.setAttribute(QStringLiteral("toolButtonStyleDefault"), d->toolButtonStyleToString(bs)); } } // called by KMainWindow::applyMainWindowSettings to read from the user settings void KToolBar::applySettings(const KConfigGroup &cg) { Q_ASSERT(!cg.name().isEmpty()); if (cg.hasKey("IconSize")) { d->iconSizeSettings[Level_UserSettings] = cg.readEntry("IconSize", 0); } if (cg.hasKey("ToolButtonStyle")) { d->toolButtonStyleSettings[Level_UserSettings] = d->toolButtonStyleFromString(cg.readEntry("ToolButtonStyle", QString())); } d->applyCurrentSettings(); } KMainWindow *KToolBar::mainWindow() const { return qobject_cast(const_cast(parent())); } void KToolBar::setIconDimensions(int size) { QToolBar::setIconSize(QSize(size, size)); d->iconSizeSettings[Level_UserSettings] = size; } int KToolBar::iconSizeDefault() const { return KIconLoader::global()->currentSize(d->isMainToolBar ? KIconLoader::MainToolbar : KIconLoader::Toolbar); } void KToolBar::slotMovableChanged(bool movable) { if (movable && !KAuthorized::authorize(QStringLiteral("movable_toolbars"))) { setMovable(false); } } void KToolBar::dragEnterEvent(QDragEnterEvent *event) { if (toolBarsEditable() && event->proposedAction() & (Qt::CopyAction | Qt::MoveAction) && event->mimeData()->hasFormat(QStringLiteral("application/x-kde-action-list"))) { QByteArray data = event->mimeData()->data(QStringLiteral("application/x-kde-action-list")); QDataStream stream(data); QStringList actionNames; stream >> actionNames; const auto allCollections = KActionCollection::allCollections(); for (const QString &actionName : qAsConst(actionNames)) { for (KActionCollection *ac : allCollections) { QAction *newAction = ac->action(actionName); if (newAction) { d->actionsBeingDragged.append(newAction); break; } } } if (!d->actionsBeingDragged.isEmpty()) { QAction *overAction = actionAt(event->pos()); QFrame *dropIndicatorWidget = new QFrame(this); dropIndicatorWidget->resize(8, height() - 4); dropIndicatorWidget->setFrameShape(QFrame::VLine); dropIndicatorWidget->setLineWidth(3); d->dropIndicatorAction = insertWidget(overAction, dropIndicatorWidget); insertAction(overAction, d->dropIndicatorAction); event->acceptProposedAction(); return; } } QToolBar::dragEnterEvent(event); } void KToolBar::dragMoveEvent(QDragMoveEvent *event) { if (toolBarsEditable()) Q_FOREVER { if (d->dropIndicatorAction) { QAction *overAction = nullptr; const auto actions = this->actions(); for (QAction *action : actions) { // want to make it feel that half way across an action you're dropping on the other side of it QWidget *widget = widgetForAction(action); if (event->pos().x() < widget->pos().x() + (widget->width() / 2)) { overAction = action; break; } } if (overAction != d->dropIndicatorAction) { // Check to see if the indicator is already in the right spot int dropIndicatorIndex = actions.indexOf(d->dropIndicatorAction); if (dropIndicatorIndex + 1 < actions.count()) { if (actions.at(dropIndicatorIndex + 1) == overAction) { break; } } else if (!overAction) { break; } insertAction(overAction, d->dropIndicatorAction); } event->accept(); return; } break; } QToolBar::dragMoveEvent(event); } void KToolBar::dragLeaveEvent(QDragLeaveEvent *event) { // Want to clear this even if toolBarsEditable was changed mid-drag (unlikey) delete d->dropIndicatorAction; d->dropIndicatorAction = nullptr; d->actionsBeingDragged.clear(); if (toolBarsEditable()) { event->accept(); return; } QToolBar::dragLeaveEvent(event); } void KToolBar::dropEvent(QDropEvent *event) { if (toolBarsEditable()) { Q_FOREACH (QAction *action, d->actionsBeingDragged) { if (actions().contains(action)) { removeAction(action); } insertAction(d->dropIndicatorAction, action); } } // Want to clear this even if toolBarsEditable was changed mid-drag (unlikey) delete d->dropIndicatorAction; d->dropIndicatorAction = nullptr; d->actionsBeingDragged.clear(); if (toolBarsEditable()) { event->accept(); return; } QToolBar::dropEvent(event); } void KToolBar::mousePressEvent(QMouseEvent *event) { if (toolBarsEditable() && event->button() == Qt::LeftButton) { if (QAction *action = actionAt(event->pos())) { d->dragAction = action; d->dragStartPosition = event->pos(); event->accept(); return; } } QToolBar::mousePressEvent(event); } void KToolBar::mouseMoveEvent(QMouseEvent *event) { if (!toolBarsEditable() || !d->dragAction) { QToolBar::mouseMoveEvent(event); return; } if ((event->pos() - d->dragStartPosition).manhattanLength() < QApplication::startDragDistance()) { event->accept(); return; } QDrag *drag = new QDrag(this); QMimeData *mimeData = new QMimeData; QByteArray data; { QDataStream stream(&data, QIODevice::WriteOnly); QStringList actionNames; actionNames << d->dragAction->objectName(); stream << actionNames; } mimeData->setData(QStringLiteral("application/x-kde-action-list"), data); drag->setMimeData(mimeData); Qt::DropAction dropAction = drag->exec(Qt::MoveAction); if (dropAction == Qt::MoveAction) // Only remove from this toolbar if it was moved to another toolbar // Otherwise the receiver moves it. if (drag->target() != this) { removeAction(d->dragAction); } d->dragAction = nullptr; event->accept(); } void KToolBar::mouseReleaseEvent(QMouseEvent *event) { // Want to clear this even if toolBarsEditable was changed mid-drag (unlikey) if (d->dragAction) { d->dragAction = nullptr; event->accept(); return; } QToolBar::mouseReleaseEvent(event); } bool KToolBar::eventFilter(QObject *watched, QEvent *event) { // Generate context menu events for disabled buttons too... if (event->type() == QEvent::MouseButtonPress) { QMouseEvent *me = static_cast(event); if (me->buttons() & Qt::RightButton) if (QWidget *ww = qobject_cast(watched)) if (ww->parent() == this && !ww->isEnabled()) { QCoreApplication::postEvent(this, new QContextMenuEvent(QContextMenuEvent::Mouse, me->pos(), me->globalPos())); } } else if (event->type() == QEvent::ParentChange) { // Make sure we're not leaving stale event filters around, // when a child is reparented somewhere else if (QWidget *ww = qobject_cast(watched)) { if (!this->isAncestorOf(ww)) { // New parent is not a subwidget - remove event filter ww->removeEventFilter(this); const auto children = ww->findChildren(); for (QWidget *child : children) { child->removeEventFilter(this); } } } } // Redirect mouse events to the toolbar when drag + drop editing is enabled if (toolBarsEditable()) { if (QWidget *ww = qobject_cast(watched)) { switch (event->type()) { case QEvent::MouseButtonPress: { QMouseEvent *me = static_cast(event); QMouseEvent newEvent(me->type(), mapFromGlobal(ww->mapToGlobal(me->pos())), me->globalPos(), me->button(), me->buttons(), me->modifiers()); mousePressEvent(&newEvent); return true; } case QEvent::MouseMove: { QMouseEvent *me = static_cast(event); QMouseEvent newEvent(me->type(), mapFromGlobal(ww->mapToGlobal(me->pos())), me->globalPos(), me->button(), me->buttons(), me->modifiers()); mouseMoveEvent(&newEvent); return true; } case QEvent::MouseButtonRelease: { QMouseEvent *me = static_cast(event); QMouseEvent newEvent(me->type(), mapFromGlobal(ww->mapToGlobal(me->pos())), me->globalPos(), me->button(), me->buttons(), me->modifiers()); mouseReleaseEvent(&newEvent); return true; } default: break; } } } return QToolBar::eventFilter(watched, event); } void KToolBar::actionEvent(QActionEvent *event) { if (event->type() == QEvent::ActionRemoved) { QWidget *widget = widgetForAction(event->action()); if (widget) { widget->removeEventFilter(this); const auto children = widget->findChildren(); for (QWidget *child : children) { child->removeEventFilter(this); } } } QToolBar::actionEvent(event); if (event->type() == QEvent::ActionAdded) { QWidget *widget = widgetForAction(event->action()); if (widget) { widget->installEventFilter(this); const auto children = widget->findChildren(); for (QWidget *child : children) { child->installEventFilter(this); } // Center widgets that do not have any use for more space. See bug 165274 if (!(widget->sizePolicy().horizontalPolicy() & QSizePolicy::GrowFlag) // ... but do not center when using text besides icon in vertical toolbar. See bug 243196 && !(orientation() == Qt::Vertical && toolButtonStyle() == Qt::ToolButtonTextBesideIcon)) { const int index = layout()->indexOf(widget); if (index != -1) { layout()->itemAt(index)->setAlignment(Qt::AlignJustify); } } } } d->adjustSeparatorVisibility(); } bool KToolBar::toolBarsEditable() { return KToolBar::Private::s_editable; } void KToolBar::setToolBarsEditable(bool editable) { if (KToolBar::Private::s_editable != editable) { KToolBar::Private::s_editable = editable; } } void KToolBar::setToolBarsLocked(bool locked) { if (KToolBar::Private::s_locked != locked) { KToolBar::Private::s_locked = locked; const auto windows = KMainWindow::memberList(); for (KMainWindow *mw : windows) { const auto toolbars = mw->findChildren(); for (KToolBar *toolbar : toolbars) { toolbar->d->setLocked(locked); } } } } bool KToolBar::toolBarsLocked() { return KToolBar::Private::s_locked; } void KToolBar::emitToolbarStyleChanged() { #ifdef QT_DBUS_LIB QDBusMessage message = QDBusMessage::createSignal(QStringLiteral("/KToolBar"), QStringLiteral("org.kde.KToolBar"), QStringLiteral("styleChanged")); QDBusConnection::sessionBus().send(message); #endif } #include "moc_ktoolbar.cpp" diff --git a/src/ktoolbar.h b/src/ktoolbar.h index f4a791f..bd2c283 100644 --- a/src/ktoolbar.h +++ b/src/ktoolbar.h @@ -1,277 +1,280 @@ /* This file is part of the KDE libraries Copyright (C) 2000 Reginald Stadlbauer (reggie@kde.org) (C) 1997, 1998 Stephan Kulow (coolo@kde.org) (C) 1997, 1998 Sven Radej (radej@kde.org) (C) 1997, 1998 Mark Donohoe (donohoe@kde.org) (C) 1997, 1998 Matthias Ettrich (ettrich@kde.org) (C) 1999, 2000 Kurt Granroth (granroth@kde.org) (C) 2005-2006 Hamish Rodda (rodda@kde.org) This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License version 2 as published by the Free Software Foundation. This library 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 Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef KTOOLBAR_H #define KTOOLBAR_H #include #include class QDomElement; class KConfigGroup; class KConfig; class KMainWindow; class KXMLGUIClient; /** * @class KToolBar ktoolbar.h KToolBar * * @short Floatable toolbar with auto resize. * * A KDE-style toolbar. * * KToolBar can be used as a standalone widget, but KMainWindow * provides easy factories and management of one or more toolbars. * * KToolBar uses a global config group to load toolbar settings on * construction. It will reread this config group on a * KApplication::appearanceChanged() signal. * * KToolBar respects Kiosk settings (see the KAuthorized namespace in the * KConfig framework). In particular, system administrators can prevent users * from moving toolbars with the "movable_toolbars" action, and from showing or * hiding toolbars with the "options_show_toolbar" action. For example, to * disable both, add the following the application or global configuration: * @verbatim [KDE Action Restrictions][$i] movable_toolbars=false options_show_toolbar=false @endverbatim * * @note If you can't depend on KXmlGui but you want to integrate with KDE, you can use QToolBar with: * Set ToolButtonStyle to Qt::ToolButtonFollowStyle, this will make QToolBar use the settings for "Main Toolbar" * Additionally set QToolBar::setProperty("otherToolbar", true) to use settings for "Other toolbars" * Settings from "Other toolbars" will only work on widget styles derived from KStyle * @author Reginald Stadlbauer , Stephan Kulow , Sven Radej , Hamish Rodda . */ class KXMLGUI_EXPORT KToolBar : public QToolBar { Q_OBJECT public: /** * Constructor. * * This constructor takes care of adding the toolbar to the mainwindow, * if @p parent is a QMainWindow. * * Normally KDE applications do not call this directly, they either * call KMainWindow::toolBar(), or they use XML-GUI and specify * toolbars using XML. * * @param parent The standard toolbar parent (usually a KMainWindow) * @param isMainToolBar True for the "main toolbar", false for other toolbars. Different settings apply. * @param readConfig whether to apply the configuration (global and application-specific) */ explicit KToolBar(QWidget *parent, bool isMainToolBar = false, bool readConfig = true); // KDE5: remove. The one below is preferred so that all debug output from init() shows the right objectName already, // and so that isMainToolBar() and iconSizeDefault() return correct values during loading too. /** * Constructor. * * This constructor takes care of adding the toolbar to the mainwindow, * if @p parent is a QMainWindow. * * Normally KDE applications do not call this directly, they either * call KMainWindow::toolBar(), or they use XML-GUI and specify * toolbars using XML. * * @param objectName The QObject name of this toolbar, required so that QMainWindow can save and load the toolbar position, * and so that KToolBar can find out if it's the main toolbar. * @param parent The standard toolbar parent (usually a KMainWindow) * @param readConfig whether to apply the configuration (global and application-specific) */ explicit KToolBar(const QString &objectName, QWidget *parent, bool readConfig = true); /** * Alternate constructor with additional arguments, e.g. to choose in which area * the toolbar should be auto-added. This is rarely used in KDE. When using XMLGUI * you can specify this as an xml attribute instead. * * @param objectName The QObject name of this toolbar, required so that QMainWindow can save and load the toolbar position * @param parentWindow The window that should be the parent of this toolbar * @param area The position of the toolbar. Usually Qt::TopToolBarArea. * @param newLine If true, start a new line in the dock for this toolbar. * @param isMainToolBar True for the "main toolbar", false for other toolbars. Different settings apply. * @param readConfig whether to apply the configuration (global and application-specific) */ KToolBar(const QString &objectName, QMainWindow *parentWindow, Qt::ToolBarArea area, bool newLine = false, bool isMainToolBar = false, bool readConfig = true); // KDE5: remove, I don't think anyone is using this. /** * Destroys the toolbar. */ virtual ~KToolBar(); /** * Returns the main window that this toolbar is docked with. */ KMainWindow *mainWindow() const; /** * Convenience function to set icon size */ void setIconDimensions(int size); /** * Returns the default size for this type of toolbar. * * @return the default size for this type of toolbar. */ int iconSizeDefault() const; // KDE5: hide from public API. Doesn't make sense to export this, and it isn't used. +#if KXMLGUI_ENABLE_DEPRECATED_SINCE(5, 0) /** * This allows you to enable or disable the context menu. * * @param enable If false, then the context menu will be disabled - * @deprecated use setContextMenuPolicy() + * @deprecated Since 5.0, use setContextMenuPolicy() */ -#ifndef KXMLGUI_NO_DEPRECATED - KXMLGUI_DEPRECATED void setContextMenuEnabled(bool enable = true); + KXMLGUI_DEPRECATED_VERSION(5, 0, "Use QWidget::setContextMenuPolicy(Qt::ContextMenuPolicy)") + void setContextMenuEnabled(bool enable = true); #endif +#if KXMLGUI_ENABLE_DEPRECATED_SINCE(5, 0) /** * Returns the context menu enabled flag * @return true if the context menu is disabled - * @deprecated use contextMenuPolicy() + * @deprecated Since 5.0, use contextMenuPolicy() */ -#ifndef KXMLGUI_NO_DEPRECATED - KXMLGUI_DEPRECATED bool contextMenuEnabled() const; + KXMLGUI_DEPRECATED_VERSION(5, 0, "Use QWidget::contextMenuPolicy()") + bool contextMenuEnabled() const; #endif /** * Save the toolbar settings to group @p cg. */ void saveSettings(KConfigGroup &cg); /** * Read the toolbar settings from group @p cg * and apply them. */ void applySettings(const KConfigGroup &cg); +#if KXMLGUI_ENABLE_DEPRECATED_SINCE(5, 0) /** * Sets the XML gui client. - * @deprecated use addXMLGUIClient() + * @deprecated Since 5.0, use addXMLGUIClient() */ -#ifndef KXMLGUI_NO_DEPRECATED - KXMLGUI_DEPRECATED void setXMLGUIClient(KXMLGUIClient *client); + KXMLGUI_DEPRECATED_VERSION(5, 0, "Use KToolBar::addXMLGUIClient(KXMLGUIClient *)") + void setXMLGUIClient(KXMLGUIClient *client); #endif /** * Adds an XML gui client that uses this toolbar * @since 4.8.1 */ void addXMLGUIClient(KXMLGUIClient *client); /** * Removes an XML gui client that uses this toolbar * @since 4.8.5 */ void removeXMLGUIClient(KXMLGUIClient *client); /** * Load state from an XML @param element, called by KXMLGUIBuilder. */ void loadState(const QDomElement &element); /** * Save state into an XML @param element, called by KXMLGUIBuilder. */ void saveState(QDomElement &element) const; /** * Reimplemented to support context menu activation on disabled tool buttons. */ bool eventFilter(QObject *watched, QEvent *event) override; /** * Returns whether the toolbars are currently editable (drag & drop of actions). */ static bool toolBarsEditable(); /** * Enable or disable toolbar editing via drag & drop of actions. This is * called by KEditToolBar and should generally be set to disabled whenever * KEditToolBar is not active. */ static void setToolBarsEditable(bool editable); /** * Returns whether the toolbars are locked (i.e., moving of the toobars disallowed). */ static bool toolBarsLocked(); /** * Allows you to lock and unlock all toolbars (i.e., disallow/allow moving of the toobars). */ static void setToolBarsLocked(bool locked); /** * Emits a D-Bus signal to tell all toolbars in all applications, that the user settings have * changed. * @since 5.0 */ static void emitToolbarStyleChanged(); protected Q_SLOTS: virtual void slotMovableChanged(bool movable); protected: void contextMenuEvent(QContextMenuEvent *) override; void actionEvent(QActionEvent *) override; // Draggable toolbar configuration void dragEnterEvent(QDragEnterEvent *) override; void dragMoveEvent(QDragMoveEvent *) override; void dragLeaveEvent(QDragLeaveEvent *) override; void dropEvent(QDropEvent *) override; void mousePressEvent(QMouseEvent *) override; void mouseMoveEvent(QMouseEvent *) override; void mouseReleaseEvent(QMouseEvent *) override; private: class Private; Private *const d; Q_PRIVATE_SLOT(d, void slotAppearanceChanged()) Q_PRIVATE_SLOT(d, void slotContextAboutToShow()) Q_PRIVATE_SLOT(d, void slotContextAboutToHide()) Q_PRIVATE_SLOT(d, void slotContextLeft()) Q_PRIVATE_SLOT(d, void slotContextRight()) Q_PRIVATE_SLOT(d, void slotContextShowText()) Q_PRIVATE_SLOT(d, void slotContextTop()) Q_PRIVATE_SLOT(d, void slotContextBottom()) Q_PRIVATE_SLOT(d, void slotContextIcons()) Q_PRIVATE_SLOT(d, void slotContextText()) Q_PRIVATE_SLOT(d, void slotContextTextRight()) Q_PRIVATE_SLOT(d, void slotContextTextUnder()) Q_PRIVATE_SLOT(d, void slotContextIconSize()) Q_PRIVATE_SLOT(d, void slotLockToolBars(bool)) }; #endif diff --git a/src/kxmlguibuilder.cpp b/src/kxmlguibuilder.cpp index 4403a1d..e909007 100644 --- a/src/kxmlguibuilder.cpp +++ b/src/kxmlguibuilder.cpp @@ -1,423 +1,425 @@ /* This file is part of the KDE project Copyright (C) 2000 Simon Hausmann David Faure This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library 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 Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kxmlguibuilder.h" #include "kxmlguiclient.h" #include "ktoolbar.h" #include "kmainwindow.h" #include "kxmlguiwindow.h" #include "kmenumenuhandler_p.h" #include "debug.h" #include #include #include #include #include #include #include #include #include using namespace KDEPrivate; class KXMLGUIBuilderPrivate { public: KXMLGUIBuilderPrivate() : m_client(nullptr) {} ~KXMLGUIBuilderPrivate() { } QWidget *m_widget; QString tagMainWindow; QString tagMenuBar; QString tagMenu; QString tagToolBar; QString tagStatusBar; QString tagSeparator; QString tagSpacer; QString tagTearOffHandle; QString tagMenuTitle; QString attrName; QString attrLineSeparator; QString attrDomain; QString attrText1; QString attrText2; QString attrContext; QString attrIcon; KXMLGUIClient *m_client; KMenuMenuHandler *m_menumenuhandler; }; KXMLGUIBuilder::KXMLGUIBuilder(QWidget *widget) : d(new KXMLGUIBuilderPrivate) { d->m_widget = widget; d->tagMainWindow = QStringLiteral("mainwindow"); d->tagMenuBar = QStringLiteral("menubar"); d->tagMenu = QStringLiteral("menu"); d->tagToolBar = QStringLiteral("toolbar"); d->tagStatusBar = QStringLiteral("statusbar"); d->tagSeparator = QStringLiteral("separator"); d->tagSpacer = QStringLiteral("spacer"); d->tagTearOffHandle = QStringLiteral("tearoffhandle"); d->tagMenuTitle = QStringLiteral("title"); d->attrName = QStringLiteral("name"); d->attrLineSeparator = QStringLiteral("lineseparator"); d->attrDomain = QStringLiteral("translationDomain"); d->attrText1 = QStringLiteral("text"); d->attrText2 = QStringLiteral("Text"); d->attrContext = QStringLiteral("context"); d->attrIcon = QStringLiteral("icon"); d->m_menumenuhandler = new KMenuMenuHandler(this); } KXMLGUIBuilder::~KXMLGUIBuilder() { delete d->m_menumenuhandler; delete d; } QWidget *KXMLGUIBuilder::widget() { return d->m_widget; } QStringList KXMLGUIBuilder::containerTags() const { QStringList res; res << d->tagMenu << d->tagToolBar << d->tagMainWindow << d->tagMenuBar << d->tagStatusBar; return res; } QWidget *KXMLGUIBuilder::createContainer(QWidget *parent, int index, const QDomElement &element, QAction *&containerAction) { containerAction = nullptr; if (element.attribute(QStringLiteral("deleted")).toLower() == QLatin1String("true")) { return nullptr; } const QString tagName = element.tagName().toLower(); if (tagName == d->tagMainWindow) { KMainWindow *mainwindow = qobject_cast(d->m_widget); // could be 0 return mainwindow; } if (tagName == d->tagMenuBar) { KMainWindow *mainWin = qobject_cast(d->m_widget); QMenuBar *bar = nullptr; if (mainWin) { bar = mainWin->menuBar(); } if (!bar) { bar = new QMenuBar(d->m_widget); } bar->show(); return bar; } if (tagName == d->tagMenu) { // Look up to see if we are inside a mainwindow. If yes, then // use it as parent widget (to get kaction to plug itself into the // mainwindow). Don't use a popupmenu as parent widget, otherwise // the popup won't be hidden if it is used as a standalone menu as well. // Note: menus with a parent of 0, coming from child clients, can be // leaked if the child client is deleted without a proper removeClient call, though. QWidget *p = parent; if (!p && qobject_cast(d->m_widget)) { p = d->m_widget; } while (p && !qobject_cast(p)) { p = p->parentWidget(); } QString name = element.attribute(d->attrName); if (!KAuthorized::authorizeAction(name)) { return nullptr; } QMenu *popup = new QMenu(p); popup->setObjectName(name); d->m_menumenuhandler->insertMenu(popup); QString i18nText; QDomElement textElem = element.namedItem(d->attrText1).toElement(); if (textElem.isNull()) { // try with capital T textElem = element.namedItem(d->attrText2).toElement(); } const QString text = textElem.text(); const QString context = textElem.attribute(d->attrContext); //qCDebug(DEBUG_KXMLGUI) << "DOMAIN" << KLocalizedString::applicationDomain(); //qCDebug(DEBUG_KXMLGUI) << "ELEMENT TEXT:" << text; if (text.isEmpty()) { // still no luck i18nText = i18n("No text"); } else { QByteArray domain = textElem.attribute(d->attrDomain).toUtf8(); if (domain.isEmpty()) { domain = element.ownerDocument().documentElement().attribute(d->attrDomain).toUtf8(); if (domain.isEmpty()) { domain = KLocalizedString::applicationDomain(); } } if (context.isEmpty()) { i18nText = i18nd(domain.constData(), text.toUtf8().constData()); } else { i18nText = i18ndc(domain.constData(), context.toUtf8().constData(), text.toUtf8().constData()); } } //qCDebug(DEBUG_KXMLGUI) << "ELEMENT i18n TEXT:" << i18nText; const QString icon = element.attribute(d->attrIcon); QIcon pix; if (!icon.isEmpty()) { pix = QIcon::fromTheme(icon); } if (parent) { QAction *act = popup->menuAction(); if (!icon.isEmpty()) { act->setIcon(pix); } act->setText(i18nText); if (index == -1 || index >= parent->actions().count()) { parent->addAction(act); } else { parent->insertAction(parent->actions().value(index), act); } containerAction = act; containerAction->setObjectName(name); } return popup; } if (tagName == d->tagToolBar) { QString name = element.attribute(d->attrName); KToolBar *bar = static_cast(d->m_widget->findChild(name)); if (!bar) { bar = new KToolBar(name, d->m_widget, false); } if (qobject_cast(d->m_widget)) { if (d->m_client && !d->m_client->xmlFile().isEmpty()) { bar->addXMLGUIClient(d->m_client); } } bar->loadState(element); return bar; } if (tagName == d->tagStatusBar) { KMainWindow *mainWin = qobject_cast(d->m_widget); if (mainWin) { mainWin->statusBar()->show(); return mainWin->statusBar(); } QStatusBar *bar = new QStatusBar(d->m_widget); return bar; } return nullptr; } void KXMLGUIBuilder::removeContainer(QWidget *container, QWidget *parent, QDomElement &element, QAction *containerAction) { // Warning parent can be 0L if (qobject_cast(container)) { if (parent) { parent->removeAction(containerAction); } delete container; } else if (qobject_cast(container)) { KToolBar *tb = static_cast(container); tb->saveState(element); delete tb; } else if (qobject_cast(container)) { QMenuBar *mb = static_cast(container); mb->hide(); // Don't delete menubar - it can be reused by createContainer. // If you decide that you do need to delete the menubar, make // sure that QMainWindow::d->mb does not point to a deleted // menubar object. } else if (qobject_cast(container)) { if (qobject_cast(d->m_widget)) { container->hide(); } else { delete static_cast(container); } } else { qCWarning(DEBUG_KXMLGUI) << "Unhandled container to remove : " << container->metaObject()->className(); } } QStringList KXMLGUIBuilder::customTags() const { QStringList res; res << d->tagSeparator << d->tagSpacer << d->tagTearOffHandle << d->tagMenuTitle; return res; } QAction *KXMLGUIBuilder::createCustomElement(QWidget *parent, int index, const QDomElement &element) { QAction *before = nullptr; if (index > 0 && index < parent->actions().count()) { before = parent->actions().at(index); } const QString tagName = element.tagName().toLower(); if (tagName == d->tagSeparator) { if (QMenu *menu = qobject_cast(parent)) { // QMenu already cares for leading/trailing/repeated separators // no need to check anything return menu->insertSeparator(before); } else if (QMenuBar *bar = qobject_cast(parent)) { QAction *separatorAction = new QAction(bar); separatorAction->setSeparator(true); bar->insertAction(before, separatorAction); return separatorAction; } else if (KToolBar *bar = qobject_cast(parent)) { /* FIXME KAction port - any need to provide a replacement for lineSeparator/normal separator? bool isLineSep = true; QDomNamedNodeMap attributes = element.attributes(); unsigned int i = 0; for (; i < attributes.length(); i++ ) { QDomAttr attr = attributes.item( i ).toAttr(); if ( attr.name().toLower() == d->attrLineSeparator && attr.value().toLower() == QLatin1String("false") ) { isLineSep = false; break; } } if ( isLineSep ) return bar->insertSeparator( index ? bar->actions()[index - 1] : 0L ); else*/ return bar->insertSeparator(before); } } else if (tagName == d->tagSpacer) { if (QToolBar *bar = qobject_cast(parent)) { // Create the simple spacer widget QWidget *spacer = new QWidget(parent); spacer->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::MinimumExpanding); return bar->insertWidget(before, spacer); } } else if (tagName == d->tagTearOffHandle) { static_cast(parent)->setTearOffEnabled(true); } else if (tagName == d->tagMenuTitle) { if (QMenu *m = qobject_cast(parent)) { QString i18nText; const QString text = element.text(); if (text.isEmpty()) { i18nText = i18n("No text"); } else { QByteArray domain = element.attribute(d->attrDomain).toUtf8(); if (domain.isEmpty()) { domain = element.ownerDocument().documentElement().attribute(d->attrDomain).toUtf8(); if (domain.isEmpty()) { domain = KLocalizedString::applicationDomain(); } } i18nText = i18nd(domain.constData(), qPrintable(text)); } QString icon = element.attribute(d->attrIcon); QIcon pix; if (!icon.isEmpty()) { pix = QIcon::fromTheme(icon); } if (!icon.isEmpty()) { return m->insertSection(before, pix, i18nText); } else { return m->insertSection(before, i18nText); } } } QAction *blank = new QAction(parent); blank->setVisible(false); parent->insertAction(before, blank); return blank; } +#if KXMLGUI_BUILD_DEPRECATED_SINCE(5, 0) void KXMLGUIBuilder::removeCustomElement(QWidget *parent, QAction *action) { parent->removeAction(action); } +#endif KXMLGUIClient *KXMLGUIBuilder::builderClient() const { return d->m_client; } void KXMLGUIBuilder::setBuilderClient(KXMLGUIClient *client) { d->m_client = client; } void KXMLGUIBuilder::finalizeGUI(KXMLGUIClient *) { KXmlGuiWindow *window = qobject_cast(d->m_widget); if (window) { window->finalizeGUI(false); } } void KXMLGUIBuilder::virtual_hook(int, void *) { /*BASE::virtual_hook( id, data );*/ } diff --git a/src/kxmlguibuilder.h b/src/kxmlguibuilder.h index 19f6b6c..b5328b5 100644 --- a/src/kxmlguibuilder.h +++ b/src/kxmlguibuilder.h @@ -1,98 +1,102 @@ /* This file is part of the KDE project Copyright (C) 2000 Simon Hausmann David Faure This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library 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 Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef kxmlguibuilder_h #define kxmlguibuilder_h #include class KXMLGUIBuilderPrivate; class KXMLGUIClient; class QAction; class QDomElement; class QStringList; class QWidget; /** * @class KXMLGUIBuilder kxmlguibuilder.h KXMLGUIBuilder * * Implements the creation of the GUI (menubar, menus and toolbars) * as requested by the GUI factory. * * The virtual methods are mostly for historical reasons, there isn't really * a need to derive from KXMLGUIBuilder anymore. */ class KXMLGUI_EXPORT KXMLGUIBuilder { public: explicit KXMLGUIBuilder(QWidget *widget); virtual ~KXMLGUIBuilder(); /* @internal */ KXMLGUIClient *builderClient() const; /* @internal */ void setBuilderClient(KXMLGUIClient *client); /* @internal */ QWidget *widget(); virtual QStringList containerTags() const; /** * Creates a container (menubar/menu/toolbar/statusbar/separator/...) * from an element in the XML file * * @param parent The parent for the container * @param index The index where the container should be inserted * into the parent container/widget * @param element The element from the DOM tree describing the * container (use it to access container specified * attributes or child elements) * @param action The action created for this container; used for e.g. passing to removeContainer. */ virtual QWidget *createContainer(QWidget *parent, int index, const QDomElement &element, QAction *&containerAction); /** * Removes the given (and previously via createContainer ) * created container. * */ virtual void removeContainer(QWidget *container, QWidget *parent, QDomElement &element, QAction *containerAction); virtual QStringList customTags() const; virtual QAction *createCustomElement(QWidget *parent, int index, const QDomElement &element); +#if KXMLGUI_BUILD_DEPRECATED_SINCE(5, 0) // KF6 TODO: REMOVE - /// @internal, @deprecated, do not use - virtual KXMLGUI_DEPRECATED void removeCustomElement(QWidget *parent, QAction *action); + /// @internal + /// @deprecated Since 5.0, do not use + KXMLGUI_DEPRECATED_VERSION(5, 0, "Do not use") + virtual void removeCustomElement(QWidget *parent, QAction *action); +#endif virtual void finalizeGUI(KXMLGUIClient *client); protected: virtual void virtual_hook(int id, void *data); private: KXMLGUIBuilderPrivate *const d; }; #endif diff --git a/tests/kmainwindowrestoretest.cpp b/tests/kmainwindowrestoretest.cpp index 4e93ccc..b813cdb 100644 --- a/tests/kmainwindowrestoretest.cpp +++ b/tests/kmainwindowrestoretest.cpp @@ -1,41 +1,44 @@ #include "kmainwindowrestoretest.h" #include #include #define MAKE_WINDOW( kind, title ) do { \ MainWin##kind * m = new MainWin##kind; \ m->setCaption( title ); \ m->setCentralWidget( new QLabel( title, m ) ); \ m->show(); \ } while ( false ) int main(int argc, char *argv[]) { QApplication::setApplicationName(QStringLiteral("kmainwindowrestoretest")); QApplication app(argc, argv); if (qApp->isSessionRestored()) { kRestoreMainWindows< MainWin1, MainWin2, MainWin3 >(); kRestoreMainWindows< MainWin4, MainWin5 >(); +#if KXMLGUI_BUILD_DEPRECATED_SINCE(5, 0) RESTORE(MainWin6); - //kRestoreMainWindows< MainWin6 >(); // should be equivalent to RESTORE() +#else + kRestoreMainWindows< MainWin6 >(); // should be equivalent to RESTORE() +#endif } else { MAKE_WINDOW(1, QStringLiteral("First 1")); MAKE_WINDOW(1, QStringLiteral("Second 1")); MAKE_WINDOW(2, QStringLiteral("Only 2")); MAKE_WINDOW(3, QStringLiteral("First 3")); MAKE_WINDOW(4, QStringLiteral("First 4")); MAKE_WINDOW(4, QStringLiteral("Second 4")); MAKE_WINDOW(3, QStringLiteral("Second 3")); MAKE_WINDOW(4, QStringLiteral("Third 4")); MAKE_WINDOW(5, QStringLiteral("First 5")); MAKE_WINDOW(5, QStringLiteral("Second 5")); MAKE_WINDOW(1, QStringLiteral("Only 6")); } return app.exec(); }