diff --git a/CMakeLists.txt b/CMakeLists.txt index 13366a5..d99fb47 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,125 +1,126 @@ cmake_minimum_required(VERSION 3.5) set(KF5_VERSION "5.65.0") # handled by release scripts set(KF5_DEP_VERSION "5.64.0") # handled by release scripts project(KXmlGui VERSION ${KF5_VERSION}) # ECM setup include(FeatureSummary) find_package(ECM 5.64.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(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(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) add_definitions(-DKF_DISABLE_DEPRECATED_BEFORE_AND_AT=0x053f00) # Subdirectories add_definitions(-DTRANSLATION_DOMAIN=\"kxmlgui5\") +add_definitions(-DQT_NO_FOREACH) 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/kaboutapplicationpersonlistdelegate_p.cpp b/src/kaboutapplicationpersonlistdelegate_p.cpp index 157af44..144b8dc 100644 --- a/src/kaboutapplicationpersonlistdelegate_p.cpp +++ b/src/kaboutapplicationpersonlistdelegate_p.cpp @@ -1,306 +1,307 @@ /* This file is part of the KDE libraries Copyright (C) 2010 Teo Mrnjavac This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 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 Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library. If not, see . */ #include "kaboutapplicationpersonlistdelegate_p.h" #include "kaboutapplicationpersonmodel_p.h" #include "kaboutapplicationpersonlistview_p.h" #include "ktoolbar.h" #include #include #include #include #include #include #include namespace KDEPrivate { enum { AVATAR_HEIGHT = 50, AVATAR_WIDTH = 50, MAIN_LINKS_HEIGHT = 32, SOCIAL_LINKS_HEIGHT = 26, MAX_SOCIAL_LINKS = 9 }; KAboutApplicationPersonListDelegate::KAboutApplicationPersonListDelegate( QAbstractItemView *itemView, QObject *parent) : KWidgetItemDelegate(itemView, parent) { } QList< QWidget *> KAboutApplicationPersonListDelegate::createItemWidgets(const QModelIndex &index) const { Q_UNUSED(index); QList< QWidget *> list; QLabel *textLabel = new QLabel(itemView()); list.append(textLabel); KToolBar *mainLinks = new KToolBar(itemView(), false, false); QAction *emailAction = new QAction(QIcon::fromTheme(QStringLiteral("mail-send")), i18nc("Action to send an email to a contributor", "Email contributor"), mainLinks); emailAction->setVisible(false); mainLinks->addAction(emailAction); QAction *homepageAction = new QAction(QIcon::fromTheme(QStringLiteral("internet-services")), i18n("Visit contributor's homepage"), mainLinks); homepageAction->setVisible(false); mainLinks->addAction(homepageAction); QAction *visitProfileAction = new QAction(QIcon::fromTheme(QStringLiteral("get-hot-new-stuff")), QString(), mainLinks); visitProfileAction->setVisible(false); mainLinks->addAction(visitProfileAction); list.append(mainLinks); KToolBar *socialLinks = new KToolBar(itemView(), false, false); for (int i = 0; i < MAX_SOCIAL_LINKS; ++i) { QAction *action = new QAction(QIcon::fromTheme(QStringLiteral("applications-internet")), QString(), socialLinks); action->setVisible(false); socialLinks->addAction(action); } list.append(socialLinks); connect(mainLinks, &QToolBar::actionTriggered, this, &KAboutApplicationPersonListDelegate::launchUrl); connect(socialLinks, &QToolBar::actionTriggered, this, &KAboutApplicationPersonListDelegate::launchUrl); return list; } void KAboutApplicationPersonListDelegate::updateItemWidgets(const QList widgets, const QStyleOptionViewItem &option, const QPersistentModelIndex &index) const { const int margin = option.fontMetrics.height() / 2; KAboutApplicationPersonProfile profile = index.data().value< KAboutApplicationPersonProfile >(); QRect wRect = widgetsRect(option, index); //Let's fill in the text first... QLabel *label = qobject_cast< QLabel * >(widgets.at(TextLabel)); label->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); QString text = buildTextForProfile(profile); label->move(wRect.left(), wRect.top()); label->resize(wRect.width(), heightForString(text, wRect.width() - margin, option) + margin); label->setWordWrap(true); label->setContentsMargins(0, 0, 0, 0); label->setAlignment(Qt::AlignBottom | Qt::AlignLeft); label->setForegroundRole(QPalette::WindowText); label->setText(text); //And now we fill in the main links (email + homepage + OCS profile)... KToolBar *mainLinks = qobject_cast< KToolBar * >(widgets.at(MainLinks)); mainLinks->setIconSize(QSize(22, 22)); mainLinks->setContentsMargins(0, 0, 0, 0); mainLinks->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); QAction *action; if (!profile.email().isEmpty()) { action = mainLinks->actions().at(EmailAction); action->setToolTip(i18nc("Action to send an email to a contributor", "Email contributor\n%1", profile.email())); action->setData(QString(QLatin1String("mailto:") + profile.email())); action->setVisible(true); } if (!profile.homepage().isEmpty()) { action = mainLinks->actions().at(HomepageAction); action->setToolTip(i18n("Visit contributor's homepage\n%1", profile.homepage().toString())); action->setData(profile.homepage().toString()); action->setVisible(true); } if (!profile.ocsProfileUrl().isEmpty()) { action = mainLinks->actions().at(VisitProfileAction); KAboutApplicationPersonModel *model = qobject_cast< KAboutApplicationPersonModel * >(itemView()->model()); action->setToolTip(i18n("Visit contributor's profile on %1\n%2", model->providerName(), profile.ocsProfileUrl())); action->setData(profile.ocsProfileUrl()); action->setVisible(true); } mainLinks->resize(QSize(mainLinks->sizeHint().width(), MAIN_LINKS_HEIGHT)); mainLinks->move(wRect.left(), wRect.top() + label->height()); //Finally, the social links... KToolBar *socialLinks = qobject_cast< KToolBar * >(widgets.at(SocialLinks)); socialLinks->setIconSize(QSize(16, 16)); socialLinks->setContentsMargins(0, 0, 0, 0); socialLinks->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); int currentSocialLinkAction = 0; - Q_FOREACH (KAboutApplicationPersonProfileOcsLink link, profile.ocsLinks()) { + const auto links = profile.ocsLinks(); + for (const KAboutApplicationPersonProfileOcsLink &link : links) { if (!profile.homepage().isEmpty() && profile.homepage() == link.url()) { continue; //We skip it if it's the same as the homepage from KAboutData } action = socialLinks->actions().at(currentSocialLinkAction); if (link.type() == KAboutApplicationPersonProfileOcsLink::Other) { action->setToolTip(i18n("Visit contributor's page\n%1", link.url().toString())); } else if (link.type() == KAboutApplicationPersonProfileOcsLink::Blog) { action->setToolTip(i18n("Visit contributor's blog\n%1", link.url().toString())); } else if (link.type() == KAboutApplicationPersonProfileOcsLink::Homepage) { action->setToolTip(i18n("Visit contributor's homepage\n%1", link.url().toString())); } else { action->setToolTip(i18n("Visit contributor's profile on %1\n%2", link.prettyType(), link.url().toString())); } action->setIcon(link.icon()); action->setData(link.url().toString()); action->setVisible(true); ++currentSocialLinkAction; if (currentSocialLinkAction > MAX_SOCIAL_LINKS - 1) { break; } } socialLinks->resize(QSize(socialLinks->sizeHint().width(), SOCIAL_LINKS_HEIGHT)); socialLinks->move(wRect.left() + mainLinks->width(), wRect.top() + label->height() + (MAIN_LINKS_HEIGHT - SOCIAL_LINKS_HEIGHT) / 2); itemView()->reset(); } QSize KAboutApplicationPersonListDelegate::sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const { KAboutApplicationPersonProfile profile = index.data().value< KAboutApplicationPersonProfile >(); bool hasAvatar = !profile.avatar().isNull(); int margin = option.fontMetrics.height() / 2; int height = hasAvatar ? qMax(widgetsRect(option, index).height(), AVATAR_HEIGHT + 2 * margin) : widgetsRect(option, index).height(); QSize metrics(option.fontMetrics.height() * 7, height); return metrics; } void KAboutApplicationPersonListDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const { int margin = option.fontMetrics.height() / 2; QStyle *style = QApplication::style(); style->drawPrimitive(QStyle::PE_Widget, &option, painter, nullptr); const KAboutApplicationPersonModel *model = qobject_cast< const KAboutApplicationPersonModel * >(index.model()); if (model->hasAvatarPixmaps()) { int height = qMax(widgetsRect(option, index).height(), AVATAR_HEIGHT + 2 * margin); QPoint point(option.rect.left() + 2 * margin, option.rect.top() + ((height - AVATAR_HEIGHT) / 2)); KAboutApplicationPersonProfile profile = index.data().value< KAboutApplicationPersonProfile >(); if (!profile.avatar().isNull()) { QPixmap pixmap = profile.avatar(); point.setX((AVATAR_WIDTH - pixmap.width()) / 2 + 5); point.setY(option.rect.top() + ((height - pixmap.height()) / 2)); painter->drawPixmap(point, pixmap); QPoint framePoint(point.x() - 5, point.y() - 5); QPixmap framePixmap(QStringLiteral(":/kxmlgui5/thumb_frame.png")); painter->drawPixmap(framePoint, framePixmap.scaled(pixmap.width() + 10, pixmap.height() + 10)); } } } void KAboutApplicationPersonListDelegate::launchUrl(QAction *action) const { QString url = action->data().toString(); if (!url.isEmpty()) { QDesktopServices::openUrl(QUrl(url)); } } int KAboutApplicationPersonListDelegate::heightForString(const QString &string, int lineWidth, const QStyleOptionViewItem &option) const { QFontMetrics fm = option.fontMetrics; QRect boundingRect = fm.boundingRect(0, 0, lineWidth, 9999, Qt::AlignLeft | Qt::AlignBottom | Qt::TextWordWrap, string); return boundingRect.height(); } QString KAboutApplicationPersonListDelegate::buildTextForProfile(const KAboutApplicationPersonProfile &profile) const { QString text = QLatin1String("") + i18nc("@item Contributor name in about dialog.", "%1", profile.name()) + QLatin1String(""); if (!profile.task().isEmpty()) { text += QLatin1String("\n
") + profile.task() + QLatin1String(""); } if (!profile.location().isEmpty()) { text += QLatin1String("\n
") + profile.location(); } return text; } QRect KAboutApplicationPersonListDelegate::widgetsRect(const QStyleOptionViewItem &option, const QPersistentModelIndex &index) const { KAboutApplicationPersonProfile profile = index.data().value< KAboutApplicationPersonProfile >(); int margin = option.fontMetrics.height() / 2; QRect widgetsRect; if (qobject_cast< const KAboutApplicationPersonModel * >(index.model())->hasAvatarPixmaps()) { widgetsRect = QRect(option.rect.left() + AVATAR_WIDTH + 3 * margin, margin / 2, option.rect.width() - AVATAR_WIDTH - 4 * margin, 0); } else { widgetsRect = QRect(option.rect.left() + margin, margin / 2, option.rect.width() - 2 * margin, 0); } int textHeight = heightForString(buildTextForProfile(profile), widgetsRect.width() - margin, option); widgetsRect.setHeight(textHeight + MAIN_LINKS_HEIGHT + 1.5 * margin); return widgetsRect; } } //namespace KDEPrivate diff --git a/src/kkeysequencewidget.cpp b/src/kkeysequencewidget.cpp index 7f3d0c1..7524f4d 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); } #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) { + for (QAction *stealAction : qAsConst(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/kmainwindow.cpp b/src/kmainwindow.cpp index 6b4943f..53f0a06 100644 --- a/src/kmainwindow.cpp +++ b/src/kmainwindow.cpp @@ -1,935 +1,936 @@ /* 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 '_' for (QChar &c : dbusName) { if (!isValidDBusObjectPathCharacter(c)) { c = 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; } #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) { 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) { 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; } #if KXMLGUI_BUILD_DEPRECATED_SINCE(5, 0) void KMainWindow::restoreWindowSize(const KConfigGroup &cg) { KWindowConfig::restoreWindowSize(windowHandle(), cg); } #endif #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()) + const auto theChildren = children(); + for (QObject *child : theChildren) 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/ktoolbar.cpp b/src/ktoolbar.cpp index 77986ce..8414699 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), #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; #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 &value : values) { value = Unset; } } int currentValue() const { int val = Unset; for (int value : values) { if (value != Unset) { val = value; } } 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 value : values) { str += QString::number(value) + 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 (int p : progression) { for (int it : qAsConst(avSizes)) { if (it >= p) { 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; } #if KXMLGUI_BUILD_DEPRECATED_SINCE(5, 0) void KToolBar::setContextMenuEnabled(bool enable) { d->enableContext = enable; } #endif #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; } } #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) { #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) { + for (QAction *action : qAsConst(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/ktoolbarhandler.cpp b/src/ktoolbarhandler.cpp index baade65..12cb7fa 100644 --- a/src/ktoolbarhandler.cpp +++ b/src/ktoolbarhandler.cpp @@ -1,275 +1,275 @@ /* This file is part of the KDE libraries Copyright (C) 2002 Simon Hausmann 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 "ktoolbarhandler_p.h" #include #include #include #include #include #include #include #include #include "kxmlguiwindow.h" #include "ktoggletoolbaraction.h" #include "ktoolbar.h" #include "kxmlguifactory.h" #include "kactioncollection.h" namespace { const char actionListName[] = "show_menu_and_toolbar_actionlist"; const char guiDescription[] = "" "" "" " " " " " " "" ""; class BarActionBuilder { public: BarActionBuilder(KActionCollection *actionCollection, KXmlGuiWindow *mainWindow, QLinkedList &oldToolBarList) : m_actionCollection(actionCollection), m_mainWindow(mainWindow), m_needsRebuild(false) { const QList toolBars = m_mainWindow->findChildren(); for (KToolBar *toolBar : toolBars) { if (toolBar->mainWindow() != m_mainWindow) { continue; } if (!oldToolBarList.contains(toolBar)) { m_needsRebuild = true; } m_toolBars.append(toolBar); } if (!m_needsRebuild) { m_needsRebuild = (oldToolBarList.count() != m_toolBars.count()); } } bool needsRebuild() const { return m_needsRebuild; } QList create() { QList actions; if (!m_needsRebuild) { return actions; } - Q_FOREACH (KToolBar *bar, m_toolBars) { + for (KToolBar *bar : qAsConst(m_toolBars)) { handleToolBar(bar); } if (m_toolBarActions.count() == 0) { return actions; } if (m_toolBarActions.count() == 1) { KToggleToolBarAction *action = static_cast(m_toolBarActions.first()); action->setText(KStandardShortcut::label(KStandardShortcut::ShowToolbar)); return m_toolBarActions; } KActionMenu *menuAction = new KActionMenu(i18n("Toolbars Shown"), m_actionCollection); m_actionCollection->addAction(QStringLiteral("toolbars_submenu_action"), menuAction); for (QAction *action : qAsConst(m_toolBarActions)) { menuAction->menu()->addAction(action); } actions.append(menuAction); return actions; } const QLinkedList &toolBars() const { return m_toolBars; } private: void handleToolBar(KToolBar *toolBar) { KToggleToolBarAction *action = new KToggleToolBarAction( toolBar, toolBar->windowTitle(), m_actionCollection); m_actionCollection->addAction(toolBar->objectName(), action); // ## tooltips, whatsthis? m_toolBarActions.append(action); } KActionCollection *m_actionCollection; KXmlGuiWindow *m_mainWindow; QLinkedList m_toolBars; QList m_toolBarActions; bool m_needsRebuild : 1; }; } using namespace KDEPrivate; class Q_DECL_HIDDEN ToolBarHandler::Private { public: Private(ToolBarHandler *_parent) : parent(_parent) { } void clientAdded(KXMLGUIClient *client) { Q_UNUSED(client) parent->setupActions(); } void init(KXmlGuiWindow *mainWindow); void connectToActionContainers(); void connectToActionContainer(QAction *action); void connectToActionContainer(QWidget *container); ToolBarHandler *parent; QPointer mainWindow; QList actions; QLinkedList toolBars; }; void ToolBarHandler::Private::init(KXmlGuiWindow *mw) { mainWindow = mw; QObject::connect(mainWindow->guiFactory(), &KXMLGUIFactory::clientAdded, parent, &ToolBarHandler::clientAdded); if (parent->domDocument().documentElement().isNull()) { QString completeDescription = QString::fromLatin1(guiDescription) .arg(QLatin1String(actionListName)); parent->setXML(completeDescription, false /*merge*/); } } void ToolBarHandler::Private::connectToActionContainers() { for (QAction *action : qAsConst(actions)) { connectToActionContainer(action); } } void ToolBarHandler::Private::connectToActionContainer(QAction *action) { const auto associatedWidgets = action->associatedWidgets(); for (auto *widget : associatedWidgets) { connectToActionContainer(widget); } } void ToolBarHandler::Private::connectToActionContainer(QWidget *container) { QMenu *popupMenu = qobject_cast(container); if (!popupMenu) { return; } connect(popupMenu, &QMenu::aboutToShow, parent, &ToolBarHandler::setupActions); } ToolBarHandler::ToolBarHandler(KXmlGuiWindow *mainWindow) : QObject(mainWindow), KXMLGUIClient(mainWindow), d(new Private(this)) { d->init(mainWindow); } ToolBarHandler::ToolBarHandler(KXmlGuiWindow *mainWindow, QObject *parent) : QObject(parent), KXMLGUIClient(mainWindow), d(new Private(this)) { d->init(mainWindow); } ToolBarHandler::~ToolBarHandler() { qDeleteAll(d->actions); d->actions.clear(); delete d; } QAction *ToolBarHandler::toolBarMenuAction() { Q_ASSERT(d->actions.count() == 1); return d->actions.first(); } void ToolBarHandler::setupActions() { if (!factory() || !d->mainWindow) { return; } BarActionBuilder builder(actionCollection(), d->mainWindow, d->toolBars); if (!builder.needsRebuild()) { return; } unplugActionList(QLatin1String(actionListName)); qDeleteAll(d->actions); d->actions.clear(); d->actions = builder.create(); d->toolBars = builder.toolBars(); // We have no XML file associated with our action collection, so load settings from KConfig actionCollection()->readSettings(); // #233712 if (KAuthorized::authorizeAction(QStringLiteral("options_show_toolbar"))) { plugActionList(QLatin1String(actionListName), d->actions); } d->connectToActionContainers(); } void ToolBarHandler::clientAdded(KXMLGUIClient* client) { d->clientAdded(client); } diff --git a/src/kxmlguifactory.cpp b/src/kxmlguifactory.cpp index 7d4229f..7fd88e3 100644 --- a/src/kxmlguifactory.cpp +++ b/src/kxmlguifactory.cpp @@ -1,771 +1,771 @@ /* This file is part of the KDE libraries Copyright (C) 1999,2000 Simon Hausmann Copyright (C) 2000 Kurt Granroth 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 "kxmlguifactory.h" #include "kxmlguifactory_p.h" #include "kshortcutschemeshelper_p.h" #include "kxmlguiclient.h" #include "kxmlguibuilder.h" #include "kshortcutsdialog.h" #include "kactioncollection.h" #include "debug.h" #include #include #include #include #include #include #include #include #include #include #include #include #if HAVE_GLOBALACCEL # include #endif Q_DECLARE_METATYPE(QList) using namespace KXMLGUI; class KXMLGUIFactoryPrivate : public BuildState { public: enum ShortcutOption { SetActiveShortcut = 1, SetDefaultShortcut = 2}; KXMLGUIFactoryPrivate() { m_rootNode = new ContainerNode(nullptr, QString(), QString()); attrName = QStringLiteral("name"); } ~KXMLGUIFactoryPrivate() { delete m_rootNode; } void pushState() { m_stateStack.push(*this); } void popState() { BuildState::operator=(m_stateStack.pop()); } bool emptyState() const { return m_stateStack.isEmpty(); } QWidget *findRecursive(KXMLGUI::ContainerNode *node, bool tag); QList findRecursive(KXMLGUI::ContainerNode *node, const QString &tagName); void applyActionProperties(const QDomElement &element, ShortcutOption shortcutOption = KXMLGUIFactoryPrivate::SetActiveShortcut); void configureAction(QAction *action, const QDomNamedNodeMap &attributes, ShortcutOption shortcutOption = KXMLGUIFactoryPrivate::SetActiveShortcut); void configureAction(QAction *action, const QDomAttr &attribute, ShortcutOption shortcutOption = KXMLGUIFactoryPrivate::SetActiveShortcut); void applyShortcutScheme(const QString &schemeName, KXMLGUIClient *client, const QList &actions); void refreshActionProperties(KXMLGUIClient *client, const QList &actions, const QDomDocument &doc); void saveDefaultActionProperties(const QList &actions); ContainerNode *m_rootNode; /* * Contains the container which is searched for in ::container . */ QString m_containerName; /* * List of all clients */ QList m_clients; QString attrName; BuildStateStack m_stateStack; }; QString KXMLGUIFactory::readConfigFile(const QString &filename, const QString &_componentName) { QString componentName = _componentName.isEmpty() ? QCoreApplication::applicationName() : _componentName; QString xml_file; if (!QDir::isRelativePath(filename)) { xml_file = filename; } else { // KF >= 5.1 (KXMLGUI_INSTALL_DIR) xml_file = QStandardPaths::locate(QStandardPaths::GenericDataLocation, QLatin1String("kxmlgui5/") + componentName + QLatin1Char('/') + filename); if (!QFile::exists(xml_file)) { // KF >= 5.4 (resource file) xml_file = QLatin1String(":/kxmlgui5/") + componentName + QLatin1Char('/') + filename; } bool warn = false; if (!QFile::exists(xml_file)) { // kdelibs4 / KF 5.0 solution xml_file = QStandardPaths::locate(QStandardPaths::GenericDataLocation, componentName + QLatin1Char('/') + filename); warn = true; } if (!QFile::exists(xml_file)) { // kdelibs4 / KF 5.0 solution, and the caller includes the component name // This was broken (lead to component/component/ in kdehome) and unnecessary // (they can specify it with setComponentName instead) xml_file = QStandardPaths::locate(QStandardPaths::GenericDataLocation, filename); warn = true; } if (warn && !xml_file.isEmpty()) { qCWarning(DEBUG_KXMLGUI) << "KXMLGUI file found at deprecated location" << xml_file << "-- please use ${KXMLGUI_INSTALL_DIR} to install these files instead."; } } QFile file(xml_file); if (xml_file.isEmpty() || !file.open(QIODevice::ReadOnly)) { qCritical() << "No such XML file" << filename; return QString(); } QByteArray buffer(file.readAll()); return QString::fromUtf8(buffer.constData(), buffer.size()); } bool KXMLGUIFactory::saveConfigFile(const QDomDocument &doc, const QString &filename, const QString &_componentName) { QString componentName = _componentName.isEmpty() ? QCoreApplication::applicationName() : _componentName; QString xml_file(filename); if (QDir::isRelativePath(xml_file)) xml_file = QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + QLatin1String("/kxmlgui5/") + componentName + QLatin1Char('/') + filename; QFileInfo fileInfo(xml_file); QDir().mkpath(fileInfo.absolutePath()); QFile file(xml_file); if (xml_file.isEmpty() || !file.open(QIODevice::WriteOnly)) { qCritical() << "Could not write to" << filename; return false; } // write out our document QTextStream ts(&file); ts.setCodec(QTextCodec::codecForName("UTF-8")); ts << doc; file.close(); return true; } KXMLGUIFactory::KXMLGUIFactory(KXMLGUIBuilder *builder, QObject *parent) : QObject(parent), d(new KXMLGUIFactoryPrivate) { d->builder = builder; d->guiClient = nullptr; if (d->builder) { d->builderContainerTags = d->builder->containerTags(); d->builderCustomTags = d->builder->customTags(); } } KXMLGUIFactory::~KXMLGUIFactory() { for (KXMLGUIClient *client : qAsConst(d->m_clients)) { client->setFactory(nullptr); } delete d; } void KXMLGUIFactory::addClient(KXMLGUIClient *client) { //qCDebug(DEBUG_KXMLGUI) << client; if (client->factory()) { if (client->factory() == this) { return; } else { client->factory()->removeClient(client); //just in case someone does stupid things ;-) } } if (d->emptyState()) { emit makingChanges(true); } d->pushState(); // QTime dt; dt.start(); d->guiClient = client; // add this client to our client list if (!d->m_clients.contains(client)) { d->m_clients.append(client); } //else //qCDebug(DEBUG_KXMLGUI) << "XMLGUI client already added " << client; // Tell the client that plugging in is process and // let it know what builder widget its mainwindow shortcuts // should be attached to. client->beginXMLPlug(d->builder->widget()); // try to use the build document for building the client's GUI, as the build document // contains the correct container state information (like toolbar positions, sizes, etc.) . // if there is non available, then use the "real" document. QDomDocument doc = client->xmlguiBuildDocument(); if (doc.documentElement().isNull()) { doc = client->domDocument(); } QDomElement docElement = doc.documentElement(); d->m_rootNode->index = -1; // cache some variables d->clientName = docElement.attribute(d->attrName); d->clientBuilder = client->clientBuilder(); if (d->clientBuilder) { d->clientBuilderContainerTags = d->clientBuilder->containerTags(); d->clientBuilderCustomTags = d->clientBuilder->customTags(); } else { d->clientBuilderContainerTags.clear(); d->clientBuilderCustomTags.clear(); } // load shortcut schemes, user-defined shortcuts and other action properties d->saveDefaultActionProperties(client->actionCollection()->actions()); if (!doc.isNull()) { d->refreshActionProperties(client, client->actionCollection()->actions(), doc); } BuildHelper(*d, d->m_rootNode).build(docElement); // let the client know that we built its GUI. client->setFactory(this); // call the finalizeGUI method, to fix up the positions of toolbars for example. // Note: the client argument is ignored d->builder->finalizeGUI(d->guiClient); // reset some variables, for safety d->BuildState::reset(); client->endXMLPlug(); d->popState(); emit clientAdded(client); // build child clients const auto children = client->childClients(); for (KXMLGUIClient *child : children) { addClient(child); } if (d->emptyState()) { emit makingChanges(false); } /* QString unaddedActions; Q_FOREACH (KActionCollection* ac, KActionCollection::allCollections()) Q_FOREACH (QAction* action, ac->actions()) if (action->associatedWidgets().isEmpty()) unaddedActions += action->objectName() + ' '; if (!unaddedActions.isEmpty()) qCWarning(DEBUG_KXMLGUI) << "The following actions are not plugged into the gui (shortcuts will not work): " << unaddedActions; */ // qCDebug(DEBUG_KXMLGUI) << "addClient took " << dt.elapsed(); } void KXMLGUIFactory::refreshActionProperties() { - Q_FOREACH (KXMLGUIClient *client, d->m_clients) { + for (KXMLGUIClient *client : qAsConst(d->m_clients)) { d->guiClient = client; QDomDocument doc = client->xmlguiBuildDocument(); if (doc.documentElement().isNull()) { client->reloadXML(); doc = client->domDocument(); } d->refreshActionProperties(client, client->actionCollection()->actions(), doc); } d->guiClient = nullptr; } static QString currentShortcutScheme() { const KConfigGroup cg = KSharedConfig::openConfig()->group("Shortcut Schemes"); return cg.readEntry("Current Scheme", "Default"); } // Find the right ActionProperties element, otherwise return null element static QDomElement findActionPropertiesElement(const QDomDocument &doc) { const QLatin1String tagActionProp("ActionProperties"); const QString schemeName = currentShortcutScheme(); QDomElement e = doc.documentElement().firstChildElement(); for (; !e.isNull(); e = e.nextSiblingElement()) { if (QString::compare(e.tagName(), tagActionProp, Qt::CaseInsensitive) == 0 && (e.attribute(QStringLiteral("scheme"), QStringLiteral("Default")) == schemeName)) { return e; } } return QDomElement(); } void KXMLGUIFactoryPrivate::refreshActionProperties(KXMLGUIClient *client, const QList &actions, const QDomDocument &doc) { // try to find and apply shortcuts schemes const QString schemeName = KShortcutSchemesHelper::currentShortcutSchemeName(); //qCDebug(DEBUG_KXMLGUI) << client->componentName() << ": applying shortcut scheme" << schemeName; if (schemeName != QLatin1String("Default")) { applyShortcutScheme(schemeName, client, actions); } else { // apply saved default shortcuts for (QAction *action : actions) { QVariant savedDefaultShortcut = action->property("_k_DefaultShortcut"); if (savedDefaultShortcut.isValid()) { QList shortcut = savedDefaultShortcut.value >(); action->setShortcuts(shortcut); action->setProperty("defaultShortcuts", QVariant::fromValue(shortcut)); //qCDebug(DEBUG_KXMLGUI) << "scheme said" << action->shortcut().toString() << "for action" << action->objectName(); } else { action->setShortcuts(QList()); } } } // try to find and apply user-defined shortcuts const QDomElement actionPropElement = findActionPropertiesElement(doc); if (!actionPropElement.isNull()) { applyActionProperties(actionPropElement); } } void KXMLGUIFactoryPrivate::saveDefaultActionProperties(const QList &actions) { // This method is called every time the user activated a new // kxmlguiclient. We only want to execute the following code only once in // the lifetime of an action. for (QAction *action : actions) { // Skip nullptr actions or those we have seen already. if (!action || action->property("_k_DefaultShortcut").isValid()) { continue; } // Check if the default shortcut is set QList defaultShortcut = action->property("defaultShortcuts").value >(); QList activeShortcut = action->shortcuts(); //qCDebug(DEBUG_KXMLGUI) << action->objectName() << "default=" << defaultShortcut.toString() << "active=" << activeShortcut.toString(); // Check if we have an empty default shortcut and an non empty // custom shortcut. Print out a warning and correct the mistake. if ((!activeShortcut.isEmpty()) && defaultShortcut.isEmpty()) { qCritical() << "Shortcut for action " << action->objectName() << action->text() << "set with QAction::setShortcut()! Use KActionCollection::setDefaultShortcut(s) instead."; action->setProperty("_k_DefaultShortcut", QVariant::fromValue(activeShortcut)); } else { action->setProperty("_k_DefaultShortcut", QVariant::fromValue(defaultShortcut)); } } } void KXMLGUIFactory::changeShortcutScheme(const QString &scheme) { qCDebug(DEBUG_KXMLGUI) << "Changing shortcut scheme to" << scheme; KConfigGroup cg = KSharedConfig::openConfig()->group("Shortcut Schemes"); cg.writeEntry("Current Scheme", scheme); refreshActionProperties(); } void KXMLGUIFactory::forgetClient(KXMLGUIClient *client) { d->m_clients.removeAll(client); } void KXMLGUIFactory::removeClient(KXMLGUIClient *client) { //qCDebug(DEBUG_KXMLGUI) << client; // don't try to remove the client's GUI if we didn't build it if (!client || client->factory() != this) { return; } if (d->emptyState()) { emit makingChanges(true); } // remove this client from our client list d->m_clients.removeAll(client); // remove child clients first (create a copy of the list just in case the // original list is modified directly or indirectly in removeClient()) const QList childClients(client->childClients()); for (KXMLGUIClient *child : childClients) { removeClient(child); } //qCDebug(DEBUG_KXMLGUI) << "calling removeRecursive"; d->pushState(); // cache some variables d->guiClient = client; d->clientName = client->domDocument().documentElement().attribute(d->attrName); d->clientBuilder = client->clientBuilder(); client->setFactory(nullptr); // if we don't have a build document for that client, yet, then create one by // cloning the original document, so that saving container information in the // DOM tree does not touch the original document. QDomDocument doc = client->xmlguiBuildDocument(); if (doc.documentElement().isNull()) { doc = client->domDocument().cloneNode(true).toDocument(); client->setXMLGUIBuildDocument(doc); } d->m_rootNode->destruct(doc.documentElement(), *d); // reset some variables d->BuildState::reset(); // This will destruct the KAccel object built around the given widget. client->prepareXMLUnplug(d->builder->widget()); d->popState(); if (d->emptyState()) { emit makingChanges(false); } emit clientRemoved(client); } QList KXMLGUIFactory::clients() const { return d->m_clients; } QWidget *KXMLGUIFactory::container(const QString &containerName, KXMLGUIClient *client, bool useTagName) { d->pushState(); d->m_containerName = containerName; d->guiClient = client; QWidget *result = d->findRecursive(d->m_rootNode, useTagName); d->guiClient = nullptr; d->m_containerName.clear(); d->popState(); return result; } QList KXMLGUIFactory::containers(const QString &tagName) { return d->findRecursive(d->m_rootNode, tagName); } void KXMLGUIFactory::reset() { d->m_rootNode->reset(); d->m_rootNode->clearChildren(); } void KXMLGUIFactory::resetContainer(const QString &containerName, bool useTagName) { if (containerName.isEmpty()) { return; } ContainerNode *container = d->m_rootNode->findContainer(containerName, useTagName); if (container && container->parent) { container->parent->removeChild(container); } } QWidget *KXMLGUIFactoryPrivate::findRecursive(KXMLGUI::ContainerNode *node, bool tag) { if (((!tag && node->name == m_containerName) || (tag && node->tagName == m_containerName)) && (!guiClient || node->client == guiClient)) { return node->container; } for (ContainerNode *child : qAsConst(node->children)) { QWidget *cont = findRecursive(child, tag); if (cont) { return cont; } } return nullptr; } // Case insensitive equality without calling toLower which allocates a new string static inline bool equals(const QString &str1, const char *str2) { return str1.compare(QLatin1String(str2), Qt::CaseInsensitive) == 0; } static inline bool equals(const QString &str1, const QString &str2) { return str1.compare(str2, Qt::CaseInsensitive) == 0; } QList KXMLGUIFactoryPrivate::findRecursive(KXMLGUI::ContainerNode *node, const QString &tagName) { QList res; if (equals(node->tagName, tagName)) { res.append(node->container); } for (KXMLGUI::ContainerNode *child : qAsConst(node->children)) { res << findRecursive(child, tagName); } return res; } void KXMLGUIFactory::plugActionList(KXMLGUIClient *client, const QString &name, const QList &actionList) { d->pushState(); d->guiClient = client; d->actionListName = name; d->actionList = actionList; d->clientName = client->domDocument().documentElement().attribute(d->attrName); d->m_rootNode->plugActionList(*d); // Load shortcuts for these new actions d->saveDefaultActionProperties(actionList); d->refreshActionProperties(client, actionList, client->domDocument()); d->BuildState::reset(); d->popState(); } void KXMLGUIFactory::unplugActionList(KXMLGUIClient *client, const QString &name) { d->pushState(); d->guiClient = client; d->actionListName = name; d->clientName = client->domDocument().documentElement().attribute(d->attrName); d->m_rootNode->unplugActionList(*d); d->BuildState::reset(); d->popState(); } void KXMLGUIFactoryPrivate::applyActionProperties(const QDomElement &actionPropElement, ShortcutOption shortcutOption) { for (QDomElement e = actionPropElement.firstChildElement(); !e.isNull(); e = e.nextSiblingElement()) { if (!equals(e.tagName(), "action")) { continue; } QAction *action = guiClient->action(e); if (!action) { continue; } configureAction(action, e.attributes(), shortcutOption); } } void KXMLGUIFactoryPrivate::configureAction(QAction *action, const QDomNamedNodeMap &attributes, ShortcutOption shortcutOption) { for (int i = 0; i < attributes.length(); i++) { QDomAttr attr = attributes.item(i).toAttr(); if (attr.isNull()) { continue; } configureAction(action, attr, shortcutOption); } } void KXMLGUIFactoryPrivate::configureAction(QAction *action, const QDomAttr &attribute, ShortcutOption shortcutOption) { QString attrName = attribute.name(); // If the attribute is a deprecated "accel", change to "shortcut". if (equals(attrName, "accel")) { attrName = QStringLiteral("shortcut"); } // No need to re-set name, particularly since it's "objectName" in Qt4 if (equals(attrName, "name")) { return; } if (equals(attrName, "icon")) { action->setIcon(QIcon::fromTheme(attribute.value())); return; } QVariant propertyValue; QVariant::Type propertyType = action->property(attrName.toLatin1().constData()).type(); bool isShortcut = (propertyType == QVariant::KeySequence); if (propertyType == QVariant::Int) { propertyValue = QVariant(attribute.value().toInt()); } else if (propertyType == QVariant::UInt) { propertyValue = QVariant(attribute.value().toUInt()); } else if (isShortcut) { // Setting the shortcut by property also sets the default shortcut (which is incorrect), so we have to do it directly if (attrName == QLatin1String("globalShortcut")) { #if HAVE_GLOBALACCEL KGlobalAccel::self()->setShortcut(action, QKeySequence::listFromString(attribute.value())); #endif } else { action->setShortcuts(QKeySequence::listFromString(attribute.value())); } if (shortcutOption & KXMLGUIFactoryPrivate::SetDefaultShortcut) { action->setProperty("defaultShortcuts", QVariant::fromValue(QKeySequence::listFromString(attribute.value()))); } } else { propertyValue = QVariant(attribute.value()); } if (!isShortcut && !action->setProperty(attrName.toLatin1().constData(), propertyValue)) { qCWarning(DEBUG_KXMLGUI) << "Error: Unknown action property " << attrName << " will be ignored!"; } } void KXMLGUIFactoryPrivate::applyShortcutScheme(const QString &schemeName, KXMLGUIClient *client, const QList &actions) { //First clear all existing shortcuts for (QAction *action : actions) { action->setShortcuts(QList()); // We clear the default shortcut as well because the shortcut scheme will set its own defaults action->setProperty("defaultShortcuts", QVariant::fromValue(QList())); } // Find the document for the shortcut scheme using the current application path. // This allows to install a single XML file for a shortcut scheme for kdevelop // rather than 10. // Also look for the current xmlguiclient path. // Per component xml files make sense for making kmail shortcuts available in kontact. QString schemeFileName = KShortcutSchemesHelper::shortcutSchemeFileName(client->componentName(), schemeName); if (schemeFileName.isEmpty()) { schemeFileName = KShortcutSchemesHelper::applicationShortcutSchemeFileName(schemeName); } if (schemeFileName.isEmpty()) { qCWarning(DEBUG_KXMLGUI) << client->componentName() << ": shortcut scheme file not found:" << schemeName << "after trying" << QCoreApplication::applicationName() << "and" << client->componentName(); return; } QDomDocument scheme; QFile schemeFile(schemeFileName); if (schemeFile.open(QIODevice::ReadOnly)) { qCDebug(DEBUG_KXMLGUI) << client->componentName() << ": found shortcut scheme XML" << schemeFileName; scheme.setContent(&schemeFile); } if (scheme.isNull()) { return; } QDomElement docElement = scheme.documentElement(); QDomElement actionPropElement = docElement.namedItem(QStringLiteral("ActionProperties")).toElement(); //Check if we really have the shortcut configuration here if (!actionPropElement.isNull()) { //qCDebug(DEBUG_KXMLGUI) << "Applying shortcut scheme for XMLGUI client" << client->componentName(); //Apply all shortcuts we have applyActionProperties(actionPropElement, KXMLGUIFactoryPrivate::SetDefaultShortcut); //} else { //qCDebug(DEBUG_KXMLGUI) << "Invalid shortcut scheme file"; } } int KXMLGUIFactory::configureShortcuts(bool letterCutsOk, bool bSaveSettings) { KShortcutsDialog dlg(KShortcutsEditor::AllActions, letterCutsOk ? KShortcutsEditor::LetterShortcutsAllowed : KShortcutsEditor::LetterShortcutsDisallowed, qobject_cast(parent())); for (KXMLGUIClient *client : qAsConst(d->m_clients)) { if (client) { qCDebug(DEBUG_KXMLGUI) << "Adding collection from client" << client->componentName() << "with" << client->actionCollection()->count() << "actions"; dlg.addCollection(client->actionCollection()); } } return dlg.configure(bSaveSettings); } // Find or create QDomElement KXMLGUIFactory::actionPropertiesElement(QDomDocument &doc) { // first, lets see if we have existing properties QDomElement elem = findActionPropertiesElement(doc); // if there was none, create one if (elem.isNull()) { elem = doc.createElement(QStringLiteral("ActionProperties")); elem.setAttribute(QStringLiteral("scheme"), currentShortcutScheme()); doc.documentElement().appendChild(elem); } return elem; } QDomElement KXMLGUIFactory::findActionByName(QDomElement &elem, const QString &sName, bool create) { const QLatin1String attrName("name"); for (QDomNode it = elem.firstChild(); !it.isNull(); it = it.nextSibling()) { QDomElement e = it.toElement(); if (e.attribute(attrName) == sName) { return e; } } if (create) { QDomElement act_elem = elem.ownerDocument().createElement(QStringLiteral("Action")); act_elem.setAttribute(attrName, sName); elem.appendChild(act_elem); return act_elem; } return QDomElement(); } diff --git a/src/kxmlguifactory_p.cpp b/src/kxmlguifactory_p.cpp index 6ff22e9..8dfb3a6 100644 --- a/src/kxmlguifactory_p.cpp +++ b/src/kxmlguifactory_p.cpp @@ -1,836 +1,836 @@ /* This file is part of the KDE libraries Copyright (C) 2001 Simon Hausmann 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 "kxmlguifactory_p.h" #include "kxmlguiclient.h" #include "kxmlguibuilder.h" #include "ktoolbar.h" #include #include #include "debug.h" #include using namespace KXMLGUI; void ActionList::plug(QWidget *container, int index) const { QAction *before = nullptr; // Insert after end of widget's current actions (default). if ((index < 0) || (index > container->actions().count())) { qCWarning(DEBUG_KXMLGUI) << "Index " << index << " is not within range (0 - " << container->actions().count() << ")"; } else if (index != container->actions().count()) { before = container->actions().at(index); // Insert before indexed action. } for (QAction *action : *this) { container->insertAction(before, action); // before = action; // BUG FIX: do not insert actions in reverse order. } } ContainerNode::ContainerNode(QWidget *_container, const QString &_tagName, const QString &_name, ContainerNode *_parent, KXMLGUIClient *_client, KXMLGUIBuilder *_builder, QAction *_containerAction, const QString &_mergingName, const QString &_groupName, const QStringList &customTags, const QStringList &containerTags) : parent(_parent), client(_client), builder(_builder), builderCustomTags(customTags), builderContainerTags(containerTags), container(_container), containerAction(_containerAction), tagName(_tagName), name(_name), groupName(_groupName), index(0), mergingName(_mergingName) { if (parent) { parent->children.append(this); } } ContainerNode::~ContainerNode() { qDeleteAll(children); qDeleteAll(clients); } void ContainerNode::removeChild(ContainerNode *child) { children.removeAll(child); deleteChild(child); } void ContainerNode::deleteChild(ContainerNode *child) { MergingIndexList::iterator mergingIt = findIndex(child->mergingName); adjustMergingIndices(-1, mergingIt, QString()); delete child; } /* * Find a merging index with the given name. Used to find an index defined by * or by a tag. */ MergingIndexList::iterator ContainerNode::findIndex(const QString &name) { return std::find_if(mergingIndices.begin(), mergingIndices.end(), [&name](const MergingIndex &idx) { return idx.mergingName == name; }); } /* * Find a container recursively with the given name. Either compares _name with the * container's tag name or the value of the container's name attribute. Specified by * the tag bool . */ ContainerNode *ContainerNode::findContainer(const QString &_name, bool tag) { if ((tag && tagName == _name) || (!tag && name == _name)) { return this; } for (ContainerNode *child : qAsConst(children)) { ContainerNode *res = child->findContainer(_name, tag); if (res) { return res; } } return nullptr; } /* * Finds a child container node (not recursively) with the given name and tagname. Explicitly * leaves out container widgets specified in the exludeList . Also ensures that the containers * belongs to currClient. */ ContainerNode *ContainerNode::findContainer(const QString &name, const QString &tagName, const QList *excludeList, KXMLGUIClient * /*currClient*/) { ContainerNode *res = nullptr; ContainerNodeList::ConstIterator nIt = children.constBegin(); if (!name.isEmpty()) { for (; nIt != children.constEnd(); ++nIt) if ((*nIt)->name == name && !excludeList->contains((*nIt)->container)) { res = *nIt; break; } return res; } if (!tagName.isEmpty()) for (; nIt != children.constEnd(); ++nIt) { if ((*nIt)->tagName == tagName && !excludeList->contains((*nIt)->container) /* * It is a bad idea to also compare the client, because * we don't want to do so in situations like these: * * * * ... * * other client: * * * ... * && (*nIt)->client == currClient ) */ ) { res = *nIt; break; } } return res; } ContainerClient *ContainerNode::findChildContainerClient(KXMLGUIClient *currentGUIClient, const QString &groupName, const MergingIndexList::iterator &mergingIdx) { if (!clients.isEmpty()) { for (ContainerClient *client : qAsConst(clients)) { if (client->client == currentGUIClient) { if (groupName.isEmpty()) { return client; } if (groupName == client->groupName) { return client; } } } } ContainerClient *client = new ContainerClient; client->client = currentGUIClient; client->groupName = groupName; if (mergingIdx != mergingIndices.end()) { client->mergingName = (*mergingIdx).mergingName; } clients.append(client); return client; } void ContainerNode::plugActionList(BuildState &state) { MergingIndexList::iterator mIt(mergingIndices.begin()); MergingIndexList::iterator mEnd(mergingIndices.end()); for (; mIt != mEnd; ++mIt) { plugActionList(state, mIt); } - Q_FOREACH (ContainerNode *child, children) { + for (ContainerNode *child : qAsConst(children)) { child->plugActionList(state); } } void ContainerNode::plugActionList(BuildState &state, const MergingIndexList::iterator &mergingIdxIt) { const QLatin1String tagActionList("actionlist"); const MergingIndex &mergingIdx = *mergingIdxIt; if (mergingIdx.clientName != state.clientName) { return; } if (!mergingIdx.mergingName.startsWith(tagActionList)) { return; } const QString k = mergingIdx.mergingName.mid(tagActionList.size()); if (k != state.actionListName) { return; } ContainerClient *client = findChildContainerClient(state.guiClient, QString(), mergingIndices.end()); client->actionLists.insert(k, state.actionList); state.actionList.plug(container, mergingIdx.value); adjustMergingIndices(state.actionList.count(), mergingIdxIt, QString()); } void ContainerNode::unplugActionList(BuildState &state) { MergingIndexList::iterator mIt(mergingIndices.begin()); MergingIndexList::iterator mEnd(mergingIndices.end()); for (; mIt != mEnd; ++mIt) { unplugActionList(state, mIt); } - Q_FOREACH (ContainerNode *child, children) { + for (ContainerNode *child : qAsConst(children)) { child->unplugActionList(state); } } void ContainerNode::unplugActionList(BuildState &state, const MergingIndexList::iterator &mergingIdxIt) { const QLatin1String tagActionList("actionlist"); MergingIndex mergingIdx = *mergingIdxIt; QString k = mergingIdx.mergingName; if (k.indexOf(tagActionList) == -1) { return; } k.remove(0, tagActionList.size()); if (mergingIdx.clientName != state.clientName) { return; } if (k != state.actionListName) { return; } ContainerClient *client = findChildContainerClient(state.guiClient, QString(), mergingIndices.end()); ActionListMap::Iterator lIt(client->actionLists.find(k)); if (lIt == client->actionLists.end()) { return; } removeActions(lIt.value()); client->actionLists.erase(lIt); } void ContainerNode::adjustMergingIndices(int offset, const MergingIndexList::iterator &it, const QString ¤tClientName) { MergingIndexList::iterator mergingIt = it; MergingIndexList::iterator mergingEnd = mergingIndices.end(); for (; mergingIt != mergingEnd; ++mergingIt) { if ((*mergingIt).clientName != currentClientName) { (*mergingIt).value += offset; } } index += offset; } bool ContainerNode::destruct(QDomElement element, BuildState &state) //krazy:exclude=passbyvalue (this is correct QDom usage, and a ref wouldn't allow passing doc.documentElement() as argument) { destructChildren(element, state); unplugActions(state); // remove all merging indices the client defined QMutableVectorIterator cmIt = mergingIndices; while (cmIt.hasNext()) if (cmIt.next().clientName == state.clientName) { cmIt.remove(); } // ### check for merging index count, too? if (clients.isEmpty() && children.isEmpty() && container && client == state.guiClient) { QWidget *parentContainer = nullptr; if (parent && parent->container) { parentContainer = parent->container; } Q_ASSERT(builder); builder->removeContainer(container, parentContainer, element, containerAction); client = nullptr; return true; } if (client == state.guiClient) { client = nullptr; } return false; } void ContainerNode::destructChildren(const QDomElement &element, BuildState &state) { QMutableListIterator childIt = children; while (childIt.hasNext()) { ContainerNode *childNode = childIt.next(); QDomElement childElement = findElementForChild(element, childNode); // destruct returns true in case the container really got deleted if (childNode->destruct(childElement, state)) { deleteChild(childNode); childIt.remove(); } } } QDomElement ContainerNode::findElementForChild(const QDomElement &baseElement, ContainerNode *childNode) { // ### slow for (QDomNode n = baseElement.firstChild(); !n.isNull(); n = n.nextSibling()) { QDomElement e = n.toElement(); if (e.tagName().toLower() == childNode->tagName && e.attribute(QStringLiteral("name")) == childNode->name) { return e; } } return QDomElement(); } void ContainerNode::unplugActions(BuildState &state) { if (!container) { return; } QMutableListIterator clientIt(clients); while (clientIt.hasNext()) { //only unplug the actions of the client we want to remove, as the container might be owned //by a different client ContainerClient *cClient = clientIt.next(); if (cClient->client == state.guiClient) { unplugClient(cClient); delete cClient; clientIt.remove(); } } } void ContainerNode::removeActions(const QList &actions) { for (QAction *action : actions) { const int pos = container->actions().indexOf(action); if (pos != -1) { container->removeAction(action); for (MergingIndex &idx : mergingIndices) { if (idx.value > pos) { --idx.value; } } --index; } } } void ContainerNode::unplugClient(ContainerClient *client) { assert(builder); KToolBar *bar = qobject_cast(container); if (bar) { bar->removeXMLGUIClient(client->client); } // now quickly remove all custom elements (i.e. separators) and unplug all actions removeActions(client->customElements); removeActions(client->actions); // unplug all actionslists for (const auto &actionList : qAsConst(client->actionLists)) { removeActions(actionList); } } void ContainerNode::reset() { for (ContainerNode *child : qAsConst(children)) { child->reset(); } if (client) { client->setFactory(nullptr); } } int ContainerNode::calcMergingIndex(const QString &mergingName, MergingIndexList::iterator &it, BuildState &state, bool ignoreDefaultMergingIndex) { const MergingIndexList::iterator mergingIt = findIndex(mergingName.isEmpty() ? state.clientName : mergingName); const MergingIndexList::iterator mergingEnd = mergingIndices.end(); if (ignoreDefaultMergingIndex || (mergingIt == mergingEnd && state.currentDefaultMergingIt == mergingEnd)) { it = mergingEnd; return index; } if (mergingIt != mergingEnd) { it = mergingIt; } else { it = state.currentDefaultMergingIt; } return (*it).value; } void ContainerNode::dump(int offset) { QString indent; indent.fill(QLatin1Char(' '), offset); qCDebug(DEBUG_KXMLGUI) << qPrintable(indent) << name << tagName << groupName << mergingName << mergingIndices; for (ContainerNode *child : qAsConst(children)) { child->dump(offset + 2); } } // TODO: return a struct with 3 members rather than 1 ret val + 2 out params int BuildHelper::calcMergingIndex(const QDomElement &element, MergingIndexList::iterator &it, QString &group) { const QLatin1String attrGroup("group"); bool haveGroup = false; group = element.attribute(attrGroup); if (!group.isEmpty()) { group.prepend(attrGroup); haveGroup = true; } int idx; if (haveGroup) { idx = parentNode->calcMergingIndex(group, it, m_state, ignoreDefaultMergingIndex); } else { it = m_state.currentClientMergingIt; if (it == parentNode->mergingIndices.end()) { idx = parentNode->index; } else { idx = (*it).value; } } return idx; } BuildHelper::BuildHelper(BuildState &state, ContainerNode *node) : containerClient(nullptr), ignoreDefaultMergingIndex(false), m_state(state), parentNode(node) { // create a list of supported container and custom tags customTags = m_state.builderCustomTags; containerTags = m_state.builderContainerTags; if (parentNode->builder != m_state.builder) { customTags += parentNode->builderCustomTags; containerTags += parentNode->builderContainerTags; } if (m_state.clientBuilder) { customTags = m_state.clientBuilderCustomTags + customTags; containerTags = m_state.clientBuilderContainerTags + containerTags; } m_state.currentDefaultMergingIt = parentNode->findIndex(QStringLiteral("")); parentNode->calcMergingIndex(QString(), m_state.currentClientMergingIt, m_state, /*ignoreDefaultMergingIndex*/ false); } void BuildHelper::build(const QDomElement &element) { for (QDomNode n = element.firstChild(); !n.isNull(); n = n.nextSibling()) { QDomElement e = n.toElement(); if (e.isNull()) { continue; } processElement(e); } } void BuildHelper::processElement(const QDomElement &e) { QString tag(e.tagName().toLower()); QString currName(e.attribute(QStringLiteral("name"))); const bool isActionTag = (tag == QLatin1String("action")); if (isActionTag || customTags.indexOf(tag) != -1) { processActionOrCustomElement(e, isActionTag); } else if (containerTags.indexOf(tag) != -1) { processContainerElement(e, tag, currName); } else if (tag == QLatin1String("merge") || tag == QLatin1String("definegroup") || tag == QLatin1String("actionlist")) { processMergeElement(tag, currName, e); } else if (tag == QLatin1String("state")) { processStateElement(e); } } void BuildHelper::processActionOrCustomElement(const QDomElement &e, bool isActionTag) { if (!parentNode->container) { return; } MergingIndexList::iterator it(m_state.currentClientMergingIt); QString group; int idx = calcMergingIndex(e, it, group); containerClient = parentNode->findChildContainerClient(m_state.guiClient, group, it); bool guiElementCreated = false; if (isActionTag) { guiElementCreated = processActionElement(e, idx); } else { guiElementCreated = processCustomElement(e, idx); } if (guiElementCreated) { // adjust any following merging indices and the current running index for the container parentNode->adjustMergingIndices(1, it, m_state.clientName); } } bool BuildHelper::processActionElement(const QDomElement &e, int idx) { assert(m_state.guiClient); // look up the action and plug it in QAction *action = m_state.guiClient->action(e); if (!action) { return false; } //qCDebug(DEBUG_KXMLGUI) << e.attribute(QStringLiteral("name")) << "->" << action << "inserting at idx=" << idx; QAction *before = nullptr; if (idx >= 0 && idx < parentNode->container->actions().count()) { before = parentNode->container->actions().at(idx); } parentNode->container->insertAction(before, action); // save a reference to the plugged action, in order to properly unplug it afterwards. containerClient->actions.append(action); return true; } bool BuildHelper::processCustomElement(const QDomElement &e, int idx) { assert(parentNode->builder); QAction *action = parentNode->builder->createCustomElement(parentNode->container, idx, e); if (!action) { return false; } containerClient->customElements.append(action); return true; } void BuildHelper::processStateElement(const QDomElement &element) { QString stateName = element.attribute(QStringLiteral("name")); if (stateName.isEmpty()) { return; } for (QDomNode n = element.firstChild(); !n.isNull(); n = n.nextSibling()) { QDomElement e = n.toElement(); if (e.isNull()) { continue; } QString tagName = e.tagName().toLower(); if (tagName != QLatin1String("enable") && tagName != QLatin1String("disable")) { continue; } const bool processingActionsToEnable = (tagName == QLatin1String("enable")); // process action names for (QDomNode n2 = n.firstChild(); !n2.isNull(); n2 = n2.nextSibling()) { QDomElement actionEl = n2.toElement(); if (actionEl.tagName().compare(QLatin1String("action"), Qt::CaseInsensitive) != 0) { continue; } QString actionName = actionEl.attribute(QStringLiteral("name")); if (actionName.isEmpty()) { return; } if (processingActionsToEnable) { m_state.guiClient->addStateActionEnabled(stateName, actionName); } else { m_state.guiClient->addStateActionDisabled(stateName, actionName); } } } } void BuildHelper::processMergeElement(const QString &tag, const QString &name, const QDomElement &e) { const QLatin1String tagDefineGroup("definegroup"); const QLatin1String tagActionList("actionlist"); const QLatin1String defaultMergingName(""); const QLatin1String attrGroup("group"); QString mergingName(name); if (mergingName.isEmpty()) { if (tag == tagDefineGroup) { qCritical() << "cannot define group without name!" << endl; return; } if (tag == tagActionList) { qCritical() << "cannot define actionlist without name!" << endl; return; } mergingName = defaultMergingName; } if (tag == tagDefineGroup) { mergingName.prepend(attrGroup); //avoid possible name clashes by prepending // "group" to group definitions } else if (tag == tagActionList) { mergingName.prepend(tagActionList); } if (parentNode->findIndex(mergingName) != parentNode->mergingIndices.end()) { return; //do not allow the redefinition of merging indices! } MergingIndexList::iterator mIt(parentNode->mergingIndices.end()); QString group(e.attribute(attrGroup)); if (!group.isEmpty()) { group.prepend(attrGroup); } // calculate the index of the new merging index. Usually this does not need any calculation, // we just want the last available index (i.e. append) . But in case the tag appears // "inside" another tag from a previously build client, then we have to use the // "parent's" index. That's why we call calcMergingIndex here. MergingIndex newIdx; newIdx.value = parentNode->calcMergingIndex(group, mIt, m_state, ignoreDefaultMergingIndex); newIdx.mergingName = mergingName; newIdx.clientName = m_state.clientName; // if that merging index is "inside" another one, then append it right after the "parent". if (mIt != parentNode->mergingIndices.end()) { parentNode->mergingIndices.insert(++mIt, newIdx); } else { parentNode->mergingIndices.append(newIdx); } if (mergingName == defaultMergingName) { ignoreDefaultMergingIndex = true; } // re-calculate the running default and client merging indices // (especially important in case the QList data got reallocated due to growing!) m_state.currentDefaultMergingIt = parentNode->findIndex(defaultMergingName); parentNode->calcMergingIndex(QString(), m_state.currentClientMergingIt, m_state, ignoreDefaultMergingIndex); } void BuildHelper::processContainerElement(const QDomElement &e, const QString &tag, const QString &name) { ContainerNode *containerNode = parentNode->findContainer(name, tag, &containerList, m_state.guiClient); if (!containerNode) { MergingIndexList::iterator it(m_state.currentClientMergingIt); QString group; int idx = calcMergingIndex(e, it, group); QAction *containerAction; KXMLGUIBuilder *builder; QWidget *container = createContainer(parentNode->container, idx, e, containerAction, &builder); // no container? (probably some tag or so ;-) if (!container) { return; } parentNode->adjustMergingIndices(1, it, m_state.clientName); // Check that the container widget is not already in parentNode. Q_ASSERT(std::find_if(parentNode->children.constBegin(), parentNode->children.constEnd(), [container](ContainerNode *child) { return child->container == container; }) == parentNode->children.constEnd()); containerList.append(container); QString mergingName; if (it != parentNode->mergingIndices.end()) { mergingName = (*it).mergingName; } QStringList cusTags = m_state.builderCustomTags; QStringList conTags = m_state.builderContainerTags; if (builder != m_state.builder) { cusTags = m_state.clientBuilderCustomTags; conTags = m_state.clientBuilderContainerTags; } containerNode = new ContainerNode(container, tag, name, parentNode, m_state.guiClient, builder, containerAction, mergingName, group, cusTags, conTags); } else { if (tag == QLatin1String("toolbar")) { KToolBar *bar = qobject_cast(containerNode->container); if (bar) { if (m_state.guiClient && !m_state.guiClient->xmlFile().isEmpty()) { bar->addXMLGUIClient(m_state.guiClient); } } else { qCWarning(DEBUG_KXMLGUI) << "toolbar container is not a KToolBar"; } } } BuildHelper(m_state, containerNode).build(e); // and re-calculate running values, for better performance m_state.currentDefaultMergingIt = parentNode->findIndex(QStringLiteral("")); parentNode->calcMergingIndex(QString(), m_state.currentClientMergingIt, m_state, ignoreDefaultMergingIndex); } QWidget *BuildHelper::createContainer(QWidget *parent, int index, const QDomElement &element, QAction *&containerAction, KXMLGUIBuilder **builder) { QWidget *res = nullptr; if (m_state.clientBuilder) { res = m_state.clientBuilder->createContainer(parent, index, element, containerAction); if (res) { *builder = m_state.clientBuilder; return res; } } KXMLGUIClient *oldClient = m_state.builder->builderClient(); m_state.builder->setBuilderClient(m_state.guiClient); res = m_state.builder->createContainer(parent, index, element, containerAction); m_state.builder->setBuilderClient(oldClient); if (res) { *builder = m_state.builder; } return res; } void BuildState::reset() { clientName.clear(); actionListName.clear(); actionList.clear(); guiClient = nullptr; clientBuilder = nullptr; currentDefaultMergingIt = currentClientMergingIt = MergingIndexList::iterator(); } QDebug operator<<(QDebug stream, const MergingIndex &mi) { QDebugStateSaver saver(stream); stream.nospace() << "clientName=" << mi.clientName << " mergingName=" << mi.mergingName << " value=" << mi.value; return stream; }