diff --git a/src/kmainwindow.cpp b/src/kmainwindow.cpp index 53f0a06..20ef876 100644 --- a/src/kmainwindow.cpp +++ b/src/kmainwindow.cpp @@ -1,936 +1,935 @@ /* 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; 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/kxmlguiwindow.cpp b/src/kxmlguiwindow.cpp index db92ad1..62e5781 100644 --- a/src/kxmlguiwindow.cpp +++ b/src/kxmlguiwindow.cpp @@ -1,450 +1,449 @@ /* 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) 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 "kxmlguiwindow.h" #include "debug.h" #include "kmainwindow_p.h" #include "kmessagebox.h" #include "kactioncollection.h" #ifdef QT_DBUS_LIB #include "kmainwindowiface_p.h" #endif #include "ktoolbarhandler_p.h" #include "kxmlguifactory.h" #include "kedittoolbar.h" #include "khelpmenu.h" #include "ktoolbar.h" #ifdef QT_DBUS_LIB #include #endif #include #include #include #include #include #include #include #include #include #include #include -#include #include #include #include #include class KXmlGuiWindowPrivate : public KMainWindowPrivate { public: void _k_slotFactoryMakingChanges(bool b) { // While the GUI factory is adding/removing clients, // don't let KMainWindow think those are changes made by the user // #105525 letDirtySettings = !b; } bool showHelpMenu: 1; QSize defaultSize; KDEPrivate::ToolBarHandler *toolBarHandler; KToggleAction *showStatusBarAction; QPointer toolBarEditor; KXMLGUIFactory *factory; }; KXmlGuiWindow::KXmlGuiWindow(QWidget *parent, Qt::WindowFlags f) : KMainWindow(*new KXmlGuiWindowPrivate, parent, f), KXMLGUIBuilder(this) { K_D(KXmlGuiWindow); d->showHelpMenu = true; d->toolBarHandler = nullptr; d->showStatusBarAction = nullptr; d->factory = nullptr; #ifdef QT_DBUS_LIB new KMainWindowInterface(this); #endif } QAction *KXmlGuiWindow::toolBarMenuAction() { K_D(KXmlGuiWindow); if (!d->toolBarHandler) { return nullptr; } return d->toolBarHandler->toolBarMenuAction(); } void KXmlGuiWindow::setupToolbarMenuActions() { K_D(KXmlGuiWindow); if (d->toolBarHandler) { d->toolBarHandler->setupActions(); } } KXmlGuiWindow::~KXmlGuiWindow() { K_D(KXmlGuiWindow); delete d->factory; } bool KXmlGuiWindow::event(QEvent *ev) { bool ret = KMainWindow::event(ev); if (ev->type() == QEvent::Polish) { #ifdef QT_DBUS_LIB QDBusConnection::sessionBus().registerObject(dbusName() + QLatin1String("/actions"), actionCollection(), QDBusConnection::ExportScriptableSlots | QDBusConnection::ExportScriptableProperties | QDBusConnection::ExportNonScriptableSlots | QDBusConnection::ExportNonScriptableProperties | QDBusConnection::ExportChildObjects); #endif } return ret; } void KXmlGuiWindow::setHelpMenuEnabled(bool showHelpMenu) { K_D(KXmlGuiWindow); d->showHelpMenu = showHelpMenu; } bool KXmlGuiWindow::isHelpMenuEnabled() const { K_D(const KXmlGuiWindow); return d->showHelpMenu; } KXMLGUIFactory *KXmlGuiWindow::guiFactory() { K_D(KXmlGuiWindow); if (!d->factory) { d->factory = new KXMLGUIFactory(this, this); connect(d->factory, SIGNAL(makingChanges(bool)), this, SLOT(_k_slotFactoryMakingChanges(bool))); } return d->factory; } void KXmlGuiWindow::configureToolbars() { K_D(KXmlGuiWindow); KConfigGroup cg(KSharedConfig::openConfig(), ""); saveMainWindowSettings(cg); if (!d->toolBarEditor) { d->toolBarEditor = new KEditToolBar(guiFactory(), this); d->toolBarEditor->setAttribute(Qt::WA_DeleteOnClose); connect(d->toolBarEditor, &KEditToolBar::newToolBarConfig, this, &KXmlGuiWindow::saveNewToolbarConfig); } d->toolBarEditor->show(); } void KXmlGuiWindow::saveNewToolbarConfig() { // createGUI(xmlFile()); // this loses any plugged-in guiclients, so we use remove+add instead. guiFactory()->removeClient(this); guiFactory()->addClient(this); KConfigGroup cg(KSharedConfig::openConfig(), ""); applyMainWindowSettings(cg); } void KXmlGuiWindow::setupGUI(StandardWindowOptions options, const QString &xmlfile) { setupGUI(QSize(), options, xmlfile); } void KXmlGuiWindow::setupGUI(const QSize &defaultSize, StandardWindowOptions options, const QString &xmlfile) { K_D(KXmlGuiWindow); if (options & Keys) { KStandardAction::keyBindings(guiFactory(), SLOT(configureShortcuts()), actionCollection()); } if ((options & StatusBar) && statusBar()) { createStandardStatusBarAction(); } if (options & ToolBar) { setStandardToolBarMenuEnabled(true); KStandardAction::configureToolbars(this, SLOT(configureToolbars()), actionCollection()); } d->defaultSize = defaultSize; if (options & Create) { createGUI(xmlfile); } if (d->defaultSize.isValid()) { resize(d->defaultSize); } else if (isHidden()) { adjustSize(); } if (options & Save) { const KConfigGroup cg(autoSaveConfigGroup()); if (cg.isValid()) { setAutoSaveSettings(cg); } else { setAutoSaveSettings(); } } } void KXmlGuiWindow::createGUI(const QString &xmlfile) { K_D(KXmlGuiWindow); // disabling the updates prevents unnecessary redraws //setUpdatesEnabled( false ); // just in case we are rebuilding, let's remove our old client guiFactory()->removeClient(this); // make sure to have an empty GUI QMenuBar *mb = menuBar(); if (mb) { mb->clear(); } qDeleteAll(toolBars()); // delete all toolbars // don't build a help menu unless the user ask for it if (d->showHelpMenu) { delete d->helpMenu; // we always want a help menu d->helpMenu = new KHelpMenu(this, KAboutData::applicationData(), true); KActionCollection *actions = actionCollection(); QAction *helpContentsAction = d->helpMenu->action(KHelpMenu::menuHelpContents); QAction *whatsThisAction = d->helpMenu->action(KHelpMenu::menuWhatsThis); QAction *reportBugAction = d->helpMenu->action(KHelpMenu::menuReportBug); QAction *switchLanguageAction = d->helpMenu->action(KHelpMenu::menuSwitchLanguage); QAction *aboutAppAction = d->helpMenu->action(KHelpMenu::menuAboutApp); QAction *aboutKdeAction = d->helpMenu->action(KHelpMenu::menuAboutKDE); QAction *donateAction = d->helpMenu->action(KHelpMenu::menuDonate); if (helpContentsAction) { actions->addAction(helpContentsAction->objectName(), helpContentsAction); } if (whatsThisAction) { actions->addAction(whatsThisAction->objectName(), whatsThisAction); } if (reportBugAction) { actions->addAction(reportBugAction->objectName(), reportBugAction); } if (switchLanguageAction) { actions->addAction(switchLanguageAction->objectName(), switchLanguageAction); } if (aboutAppAction) { actions->addAction(aboutAppAction->objectName(), aboutAppAction); } if (aboutKdeAction) { actions->addAction(aboutKdeAction->objectName(), aboutKdeAction); } if (donateAction) { actions->addAction(donateAction->objectName(), donateAction); } } const QString windowXmlFile = xmlfile.isNull() ? componentName() + QLatin1String("ui.rc") : xmlfile; // Help beginners who call setXMLFile and then setupGUI... if (!xmlFile().isEmpty() && xmlFile() != windowXmlFile) { qCWarning(DEBUG_KXMLGUI) << "You called setXMLFile(" << xmlFile() << ") and then createGUI or setupGUI," << "which also calls setXMLFile and will overwrite the file you have previously set.\n" << "You should call createGUI(" << xmlFile() << ") or setupGUI(," << xmlFile() << ") instead."; } // we always want to load in our global standards file loadStandardsXmlFile(); // now, merge in our local xml file. setXMLFile(windowXmlFile, true); // make sure we don't have any state saved already setXMLGUIBuildDocument(QDomDocument()); // do the actual GUI building guiFactory()->reset(); guiFactory()->addClient(this); checkAmbiguousShortcuts(); // setUpdatesEnabled( true ); } void KXmlGuiWindow::slotStateChanged(const QString &newstate) { stateChanged(newstate, KXMLGUIClient::StateNoReverse); } void KXmlGuiWindow::slotStateChanged(const QString &newstate, bool reverse) { stateChanged(newstate, reverse ? KXMLGUIClient::StateReverse : KXMLGUIClient::StateNoReverse); } void KXmlGuiWindow::setStandardToolBarMenuEnabled(bool enable) { K_D(KXmlGuiWindow); if (enable) { if (d->toolBarHandler) { return; } d->toolBarHandler = new KDEPrivate::ToolBarHandler(this); if (factory()) { factory()->addClient(d->toolBarHandler); } } else { if (!d->toolBarHandler) { return; } if (factory()) { factory()->removeClient(d->toolBarHandler); } delete d->toolBarHandler; d->toolBarHandler = nullptr; } } bool KXmlGuiWindow::isStandardToolBarMenuEnabled() const { K_D(const KXmlGuiWindow); return (d->toolBarHandler); } void KXmlGuiWindow::createStandardStatusBarAction() { K_D(KXmlGuiWindow); if (!d->showStatusBarAction) { d->showStatusBarAction = KStandardAction::showStatusbar(this, &KMainWindow::setSettingsDirty, actionCollection()); QStatusBar *sb = statusBar(); // Creates statusbar if it doesn't exist already. connect(d->showStatusBarAction, &QAction::toggled, sb, &QWidget::setVisible); d->showStatusBarAction->setChecked(sb->isHidden()); } else { // If the language has changed, we'll need to grab the new text and whatsThis QAction *tmpStatusBar = KStandardAction::showStatusbar(nullptr, nullptr, nullptr); d->showStatusBarAction->setText(tmpStatusBar->text()); d->showStatusBarAction->setWhatsThis(tmpStatusBar->whatsThis()); delete tmpStatusBar; } } void KXmlGuiWindow::finalizeGUI(bool /*force*/) { // FIXME: this really needs to be removed with a code more like the one we had on KDE3. // what we need to do here is to position correctly toolbars so they don't overlap. // Also, take in count plugins could provide their own toolbars and those also need to // be restored. if (autoSaveSettings() && autoSaveConfigGroup().isValid()) { applyMainWindowSettings(autoSaveConfigGroup()); } } void KXmlGuiWindow::applyMainWindowSettings(const KConfigGroup &config) { K_D(KXmlGuiWindow); KMainWindow::applyMainWindowSettings(config); QStatusBar *sb = findChild(); if (sb && d->showStatusBarAction) { d->showStatusBarAction->setChecked(!sb->isHidden()); } } // KDE5 TODO: change it to "using KXMLGUIBuilder::finalizeGUI;" in the header // and remove the reimplementation void KXmlGuiWindow::finalizeGUI(KXMLGUIClient *client) { KXMLGUIBuilder::finalizeGUI(client); } void KXmlGuiWindow::checkAmbiguousShortcuts() { QMap shortcuts; QAction *editCutAction = actionCollection()->action(QStringLiteral("edit_cut")); QAction *deleteFileAction = actionCollection()->action(QStringLiteral("deletefile")); const auto actions = actionCollection()->actions(); for (QAction *action : actions) { if (action->isEnabled()) { const auto actionShortcuts = action->shortcuts(); for (const QKeySequence &shortcut : actionShortcuts) { if (shortcut.isEmpty()) { continue; } const QString portableShortcutText = shortcut.toString(); const QAction *existingShortcutAction = shortcuts.value(portableShortcutText); if (existingShortcutAction) { // If the shortcut is already in use we give a warning, so that hopefully the developer will find it // There is one exception, if the conflicting shortcut is a non primary shortcut of "edit_cut" // and "deleteFileAction" is the other action since Shift+Delete is used for both in our default code bool showWarning = true; if ((action == editCutAction && existingShortcutAction == deleteFileAction) || (action == deleteFileAction && existingShortcutAction == editCutAction)) { QList editCutActionShortcuts = editCutAction->shortcuts(); if (editCutActionShortcuts.indexOf(shortcut) > 0) // alternate shortcut { editCutActionShortcuts.removeAll(shortcut); editCutAction->setShortcuts(editCutActionShortcuts); showWarning = false; } } if (showWarning) { const QString actionName = KLocalizedString::removeAcceleratorMarker(action->text()); const QString existingShortcutActionName = KLocalizedString::removeAcceleratorMarker(existingShortcutAction->text()); QString dontShowAgainString = existingShortcutActionName + actionName + shortcut.toString(); dontShowAgainString.remove(QLatin1Char('\\')); KMessageBox::information(this, i18n("There are two actions (%1, %2) that want to use the same shortcut (%3). This is most probably a bug. Please report it in bugs.kde.org", existingShortcutActionName, actionName, shortcut.toString(QKeySequence::NativeText)), i18n("Ambiguous Shortcuts"), dontShowAgainString, KMessageBox::Notify | KMessageBox::AllowLink); } } else { shortcuts.insert(portableShortcutText, action); } } } } } #include "moc_kxmlguiwindow.cpp"