1/*
2 This file is part of the KDE libraries
3 SPDX-FileCopyrightText: 2000 Reginald Stadlbauer <reggie@kde.org>
4 SPDX-FileCopyrightText: 1997 Stephan Kulow <coolo@kde.org>
5 SPDX-FileCopyrightText: 1997-2000 Sven Radej <radej@kde.org>
6 SPDX-FileCopyrightText: 1997-2000 Matthias Ettrich <ettrich@kde.org>
7 SPDX-FileCopyrightText: 1999 Chris Schlaeger <cs@kde.org>
8 SPDX-FileCopyrightText: 2002 Joseph Wenninger <jowenn@kde.org>
9 SPDX-FileCopyrightText: 2005-2006 Hamish Rodda <rodda@kde.org>
10 SPDX-FileCopyrightText: 2000-2008 David Faure <faure@kde.org>
11
12 SPDX-License-Identifier: LGPL-2.0-only
13*/
14
15#include "kmainwindow.h"
16
17#include "kmainwindow_p.h"
18#ifdef WITH_QTDBUS
19#include "kmainwindowiface_p.h"
20#endif
21#include "khelpmenu.h"
22#include "ktoolbar.h"
23#include "ktoolbarhandler_p.h"
24#include "ktooltiphelper.h"
25
26#include <QApplication>
27#include <QCloseEvent>
28#include <QDockWidget>
29#include <QFile>
30#include <QList>
31#include <QMenuBar>
32#include <QObject>
33#ifndef QT_NO_SESSIONMANAGER
34#include <QSessionManager>
35#endif
36#include <QStatusBar>
37#include <QStyle>
38#include <QTimer>
39#include <QWidget>
40#include <QWindow>
41#ifdef WITH_QTDBUS
42#include <QDBusConnection>
43#endif
44
45#include <KAboutData>
46#include <KConfig>
47#include <KConfigGroup>
48#include <KConfigGui>
49#include <KLocalizedString>
50#include <KSharedConfig>
51#include <KStandardShortcut>
52#include <KWindowConfig>
53
54static QMenuBar *internalMenuBar(KMainWindow *mw)
55{
56 return mw->findChild<QMenuBar *>(aName: QString(), options: Qt::FindDirectChildrenOnly);
57}
58
59static QStatusBar *internalStatusBar(KMainWindow *mw)
60{
61 return mw->findChild<QStatusBar *>(aName: QString(), options: Qt::FindDirectChildrenOnly);
62}
63
64/**
65
66 * Listens to resize events from QDockWidgets. The KMainWindow
67 * settings are set as dirty, as soon as at least one resize
68 * event occurred. The listener is attached to the dock widgets
69 * by dock->installEventFilter(dockResizeListener) inside
70 * KMainWindow::event().
71 */
72class DockResizeListener : public QObject
73{
74 Q_OBJECT
75public:
76 DockResizeListener(KMainWindow *win);
77 ~DockResizeListener() override;
78 bool eventFilter(QObject *watched, QEvent *event) override;
79
80private:
81 KMainWindow *const m_win;
82};
83
84DockResizeListener::DockResizeListener(KMainWindow *win)
85 : QObject(win)
86 , m_win(win)
87{
88}
89
90DockResizeListener::~DockResizeListener()
91{
92}
93
94bool DockResizeListener::eventFilter(QObject *watched, QEvent *event)
95{
96 switch (event->type()) {
97 case QEvent::Resize:
98 case QEvent::Move:
99 case QEvent::Show:
100 case QEvent::Hide:
101 m_win->d_ptr->setSettingsDirty(KMainWindowPrivate::CompressCalls);
102 break;
103
104 default:
105 break;
106 }
107
108 return QObject::eventFilter(watched, event);
109}
110
111KMWSessionManager::KMWSessionManager()
112{
113#ifndef QT_NO_SESSIONMANAGER
114 connect(qApp, signal: &QGuiApplication::saveStateRequest, context: this, slot: &KMWSessionManager::saveState);
115 connect(qApp, signal: &QGuiApplication::commitDataRequest, context: this, slot: &KMWSessionManager::commitData);
116#endif
117}
118
119KMWSessionManager::~KMWSessionManager()
120{
121}
122
123void KMWSessionManager::saveState(QSessionManager &sm)
124{
125#ifndef QT_NO_SESSIONMANAGER
126 KConfigGui::setSessionConfig(id: sm.sessionId(), key: sm.sessionKey());
127
128 KConfig *config = KConfigGui::sessionConfig();
129 const auto windows = KMainWindow::memberList();
130 if (!windows.isEmpty()) {
131 // According to Jochen Wilhelmy <digisnap@cs.tu-berlin.de>, this
132 // hook is useful for better document orientation
133 windows.at(i: 0)->saveGlobalProperties(sessionConfig: config);
134 }
135
136 int n = 0;
137 for (KMainWindow *mw : windows) {
138 n++;
139 mw->savePropertiesInternal(config, n);
140 }
141
142 KConfigGroup group(config, QStringLiteral("Number"));
143 group.writeEntry(key: "NumberOfWindows", value: n);
144
145 // store new status to disk
146 config->sync();
147
148 // generate discard command for new file
149 QString localFilePath = QStandardPaths::writableLocation(type: QStandardPaths::GenericConfigLocation) + QLatin1Char('/') + config->name();
150 if (QFile::exists(fileName: localFilePath)) {
151 QStringList discard;
152 discard << QStringLiteral("rm");
153 discard << localFilePath;
154 sm.setDiscardCommand(discard);
155 }
156#else
157 Q_UNUSED(sm)
158#endif // QT_NO_SESSIONMANAGER
159}
160
161void KMWSessionManager::commitData(QSessionManager &sm)
162{
163#ifndef QT_NO_SESSIONMANAGER
164 if (!sm.allowsInteraction()) {
165 return;
166 }
167
168 /*
169 Purpose of this exercise: invoke queryClose() without actually closing the
170 windows, because
171 - queryClose() may contain session management code, so it must be invoked
172 - actually closing windows may quit the application - cf.
173 QGuiApplication::quitOnLastWindowClosed()
174 - quitting the application and thus closing the session manager connection
175 violates the X11 XSMP protocol.
176 The exact requirement of XSMP that would be broken is,
177 in the description of the client's state machine:
178
179 save-yourself-done: (changing state is forbidden)
180
181 Closing the session manager connection causes a state change.
182 Worst of all, that is a real problem with ksmserver - it will not save
183 applications that quit on their own in state save-yourself-done.
184 */
185 const auto windows = KMainWindow::memberList();
186 for (KMainWindow *window : windows) {
187 if (window->testAttribute(attribute: Qt::WA_WState_Hidden)) {
188 continue;
189 }
190 QCloseEvent e;
191 QApplication::sendEvent(receiver: window, event: &e);
192 if (!e.isAccepted()) {
193 sm.cancel();
194 return;
195 }
196 }
197#else
198 Q_UNUSED(sm)
199#endif // QT_NO_SESSIONMANAGER
200}
201
202#ifndef QT_NO_SESSIONMANAGER
203Q_GLOBAL_STATIC(KMWSessionManager, ksm)
204#endif
205Q_GLOBAL_STATIC(QList<KMainWindow *>, sMemberList)
206
207KMainWindow::KMainWindow(QWidget *parent, Qt::WindowFlags flags)
208 : QMainWindow(parent, flags)
209 , d_ptr(new KMainWindowPrivate)
210{
211 Q_D(KMainWindow);
212
213 d->init(q: this);
214}
215
216KMainWindow::KMainWindow(KMainWindowPrivate &dd, QWidget *parent, Qt::WindowFlags f)
217 : QMainWindow(parent, f)
218 , d_ptr(&dd)
219{
220 Q_D(KMainWindow);
221
222 d->init(q: this);
223}
224
225void KMainWindowPrivate::init(KMainWindow *_q)
226{
227 q = _q;
228
229 q->setAnimated(q->style()->styleHint(stylehint: QStyle::SH_Widget_Animate, opt: nullptr, widget: q));
230
231 q->setAttribute(Qt::WA_DeleteOnClose);
232
233 helpMenu = nullptr;
234
235 // actionCollection()->setWidget( this );
236#if 0
237 QObject::connect(KGlobalSettings::self(), SIGNAL(settingsChanged(int)),
238 q, SLOT(_k_slotSettingsChanged(int)));
239#endif
240
241#ifndef QT_NO_SESSIONMANAGER
242 // force KMWSessionManager creation
243 ksm();
244#endif
245
246 sMemberList()->append(t: q);
247
248 // Set the icon theme fallback to breeze (if not already set)
249 // Most of our apps use "lots" of icons that most of the times
250 // are only available with breeze, we still honour the user icon
251 // theme but if the icon is not found there, we go to breeze
252 // since it's almost sure it'll be there.
253 // This should be done as soon as possible (preferably via
254 // Q_COREAPP_STARTUP_FUNCTION), but as for now it cannot be done too soon
255 // as at that point QPlatformTheme is not instantiated yet and breaks the
256 // internal status of QIconLoader (see QTBUG-74252).
257 // See also discussion at https://phabricator.kde.org/D22488
258 // TODO: remove this once we depend on Qt 5.15.1, where this is fixed
259 if (QIcon::fallbackThemeName().isEmpty()) {
260 QIcon::setFallbackThemeName(QStringLiteral("breeze"));
261 }
262
263 // If application is translated, load translator information for use in
264 // KAboutApplicationDialog or other getters. The context and messages below
265 // both must be exactly as listed, and are forced to be loaded from the
266 // application's own message catalog instead of kxmlgui's.
267 KAboutData aboutData(KAboutData::applicationData());
268 if (aboutData.translators().isEmpty()) {
269 aboutData.setTranslator(name: i18ndc(domain: nullptr, context: "NAME OF TRANSLATORS", text: "Your names"), //
270 emailAddress: i18ndc(domain: nullptr, context: "EMAIL OF TRANSLATORS", text: "Your emails"));
271
272 KAboutData::setApplicationData(aboutData);
273 }
274
275 settingsDirty = false;
276 autoSaveSettings = false;
277 autoSaveWindowSize = true; // for compatibility
278 // d->kaccel = actionCollection()->kaccel();
279 settingsTimer = nullptr;
280 sizeTimer = nullptr;
281
282 dockResizeListener = new DockResizeListener(_q);
283 letDirtySettings = true;
284
285 sizeApplied = false;
286 suppressCloseEvent = false;
287
288 qApp->installEventFilter(filterObj: KToolTipHelper::instance());
289}
290
291static bool endsWithHashNumber(const QString &s)
292{
293 for (int i = s.length() - 1; i > 0; --i) {
294 if (s[i] == QLatin1Char('#') && i != s.length() - 1) {
295 return true; // ok
296 }
297 if (!s[i].isDigit()) {
298 break;
299 }
300 }
301 return false;
302}
303
304static inline bool isValidDBusObjectPathCharacter(const QChar &c)
305{
306 ushort u = c.unicode();
307 /* clang-format off */
308 return (u >= QLatin1Char('a') && u <= QLatin1Char('z'))
309 || (u >= QLatin1Char('A') && u <= QLatin1Char('Z'))
310 || (u >= QLatin1Char('0') && u <= QLatin1Char('9'))
311 || (u == QLatin1Char('_')) || (u == QLatin1Char('/'));
312 /* clang-format off */
313}
314
315void KMainWindowPrivate::polish(KMainWindow *q)
316{
317 // Set a unique object name. Required by session management, window management, and for the dbus interface.
318 QString objname;
319 QString s;
320 int unusedNumber = 1;
321 const QString name = q->objectName();
322 bool startNumberingImmediately = true;
323 bool tryReuse = false;
324 if (name.isEmpty()) {
325 // no name given
326 objname = QStringLiteral("MainWindow#");
327 } else if (name.endsWith(c: QLatin1Char('#'))) {
328 // trailing # - always add a number - KWin uses this for better grouping
329 objname = name;
330 } else if (endsWithHashNumber(s: name)) {
331 // trailing # with a number - like above, try to use the given number first
332 objname = name;
333 tryReuse = true;
334 startNumberingImmediately = false;
335 } else {
336 objname = name;
337 startNumberingImmediately = false;
338 }
339
340 s = objname;
341 if (startNumberingImmediately) {
342 s += QLatin1Char('1');
343 }
344
345 for (;;) {
346 const QList<QWidget *> list = qApp->topLevelWidgets();
347 bool found = false;
348 for (QWidget *w : list) {
349 if (w != q && w->objectName() == s) {
350 found = true;
351 break;
352 }
353 }
354 if (!found) {
355 break;
356 }
357 if (tryReuse) {
358 objname = name.left(n: name.length() - 1); // lose the hash
359 unusedNumber = 0; // start from 1 below
360 tryReuse = false;
361 }
362 s.setNum(n: ++unusedNumber);
363 s = objname + s;
364 }
365 q->setObjectName(s);
366 if (!q->window() || q->window() == q) {
367 q->winId(); // workaround for setWindowRole() crashing, and set also window role, just in case TT
368 q->setWindowRole(s); // will keep insisting that object name suddenly should not be used for window role
369 }
370
371 dbusName = QLatin1Char('/') + QCoreApplication::applicationName() + QLatin1Char('/');
372 dbusName += q->objectName().replace(before: QLatin1Char('/'), after: QLatin1Char('_'));
373 // Clean up for dbus usage: any non-alphanumeric char should be turned into '_'
374 for (QChar &c : dbusName) {
375 if (!isValidDBusObjectPathCharacter(c)) {
376 c = QLatin1Char('_');
377 }
378 }
379
380#ifdef WITH_QTDBUS
381 /* clang-format off */
382 constexpr auto opts = QDBusConnection::ExportScriptableSlots
383 | QDBusConnection::ExportScriptableProperties
384 | QDBusConnection::ExportNonScriptableSlots
385 | QDBusConnection::ExportNonScriptableProperties
386 | QDBusConnection::ExportAdaptors;
387 /* clang-format on */
388 QDBusConnection::sessionBus().registerObject(path: dbusName, object: q, options: opts);
389#endif
390}
391
392void KMainWindowPrivate::setSettingsDirty(CallCompression callCompression)
393{
394 if (!letDirtySettings) {
395 return;
396 }
397
398 settingsDirty = true;
399 if (autoSaveSettings) {
400 if (callCompression == CompressCalls) {
401 if (!settingsTimer) {
402 settingsTimer = new QTimer(q);
403 settingsTimer->setInterval(500);
404 settingsTimer->setSingleShot(true);
405 QObject::connect(sender: settingsTimer, signal: &QTimer::timeout, context: q, slot: &KMainWindow::saveAutoSaveSettings);
406 }
407 settingsTimer->start();
408 } else {
409 q->saveAutoSaveSettings();
410 }
411 }
412}
413
414void KMainWindowPrivate::setSizeDirty()
415{
416 if (autoSaveWindowSize) {
417 if (!sizeTimer) {
418 sizeTimer = new QTimer(q);
419 sizeTimer->setInterval(500);
420 sizeTimer->setSingleShot(true);
421 QObject::connect(sender: sizeTimer, signal: &QTimer::timeout, context: q, slot: [this]() {
422 _k_slotSaveAutoSaveSize();
423 });
424 }
425 sizeTimer->start();
426 }
427}
428
429KMainWindow::~KMainWindow()
430{
431 sMemberList()->removeAll(t: this);
432 delete static_cast<QObject *>(d_ptr->dockResizeListener); // so we don't get anymore events after d_ptr is destroyed
433}
434
435bool KMainWindow::canBeRestored(int numberOfInstances)
436{
437 KConfig *config = KConfigGui::sessionConfig();
438 if (!config) {
439 return false;
440 }
441
442 KConfigGroup group(config, QStringLiteral("Number"));
443 // TODO KF6: we should use 0 as the default value, not 1
444 // See also https://bugs.kde.org/show_bug.cgi?id=427552
445 const int n = group.readEntry(key: "NumberOfWindows", defaultValue: 1);
446 return numberOfInstances >= 1 && numberOfInstances <= n;
447}
448
449const QString KMainWindow::classNameOfToplevel(int instanceNumber)
450{
451 KConfig *config = KConfigGui::sessionConfig();
452 if (!config) {
453 return QString();
454 }
455
456 KConfigGroup group(config, QStringLiteral("WindowProperties%1").arg(a: instanceNumber));
457 if (!group.hasKey(key: "ClassName")) {
458 return QString();
459 } else {
460 return group.readEntry(key: "ClassName");
461 }
462}
463
464bool KMainWindow::restore(int numberOfInstances, bool show)
465{
466 if (!canBeRestored(numberOfInstances)) {
467 return false;
468 }
469 KConfig *config = KConfigGui::sessionConfig();
470 if (readPropertiesInternal(config, numberOfInstances)) {
471 if (show) {
472 KMainWindow::show();
473 }
474 return false;
475 }
476 return false;
477}
478
479void KMainWindow::setCaption(const QString &caption)
480{
481 setPlainCaption(caption);
482}
483
484void KMainWindow::setCaption(const QString &caption, bool modified)
485{
486 QString title = caption;
487 if (!title.contains(s: QLatin1String("[*]")) && !title.isEmpty()) { // append the placeholder so that the modified mechanism works
488 title.append(s: QLatin1String(" [*]"));
489 }
490 setPlainCaption(title);
491 setWindowModified(modified);
492}
493
494void KMainWindow::setPlainCaption(const QString &caption)
495{
496 setWindowTitle(caption);
497}
498
499void KMainWindow::appHelpActivated()
500{
501 Q_D(KMainWindow);
502 if (!d->helpMenu) {
503 d->helpMenu = new KHelpMenu(this);
504 if (!d->helpMenu) {
505 return;
506 }
507 }
508 d->helpMenu->appHelpActivated();
509}
510
511void KMainWindow::closeEvent(QCloseEvent *e)
512{
513 Q_D(KMainWindow);
514 if (d->suppressCloseEvent) {
515 e->accept();
516 return;
517 }
518
519 // Save settings if auto-save is enabled, and settings have changed
520 if (d->settingsTimer && d->settingsTimer->isActive()) {
521 d->settingsTimer->stop();
522 saveAutoSaveSettings();
523 }
524 if (d->sizeTimer && d->sizeTimer->isActive()) {
525 d->sizeTimer->stop();
526 d->_k_slotSaveAutoSaveSize();
527 }
528 // Delete the marker that says we don't want to restore the position of the
529 // next-opened instance; now that a window is closing, we do want to do this
530 if (d->autoSaveGroup.isValid()) {
531 d->getStateConfig().deleteEntry(key: "RestorePositionForNextInstance");
532 }
533 d->_k_slotSaveAutoSavePosition();
534
535 if (queryClose()) {
536 // widgets will start destroying themselves at this point and we don't
537 // want to save state anymore after this as it might be incorrect
538 d->autoSaveSettings = false;
539 d->letDirtySettings = false;
540 e->accept();
541 } else {
542 e->ignore(); // if the window should not be closed, don't close it
543 }
544
545#ifndef QT_NO_SESSIONMANAGER
546 // If saving session, we are processing a fake close event, and might get the real one later.
547 if (e->isAccepted() && qApp->isSavingSession()) {
548 d->suppressCloseEvent = true;
549 }
550#endif
551}
552
553bool KMainWindow::queryClose()
554{
555 return true;
556}
557
558void KMainWindow::saveGlobalProperties(KConfig *)
559{
560}
561
562void KMainWindow::readGlobalProperties(KConfig *)
563{
564}
565
566void KMainWindow::savePropertiesInternal(KConfig *config, int number)
567{
568 Q_D(KMainWindow);
569 const bool oldASWS = d->autoSaveWindowSize;
570 d->autoSaveWindowSize = true; // make saveMainWindowSettings save the window size
571
572 KConfigGroup cg(config, QStringLiteral("WindowProperties%1").arg(a: number));
573
574 // store objectName, className, Width and Height for later restoring
575 // (Only useful for session management)
576 cg.writeEntry(key: "ObjectName", value: objectName());
577 cg.writeEntry(key: "ClassName", value: metaObject()->className());
578
579 saveMainWindowSettings(config&: cg); // Menubar, statusbar and Toolbar settings.
580
581 cg = KConfigGroup(config, QString::number(number));
582 saveProperties(cg);
583
584 d->autoSaveWindowSize = oldASWS;
585}
586
587void KMainWindow::saveMainWindowSettings(KConfigGroup &cg)
588{
589 Q_D(KMainWindow);
590 // qDebug(200) << "KMainWindow::saveMainWindowSettings " << cg.name();
591
592 // Called by session management - or if we want to save the window size anyway
593 if (d->autoSaveWindowSize) {
594 KWindowConfig::saveWindowSize(window: windowHandle(), config&: d->getStateConfig());
595 KWindowConfig::saveWindowPosition(window: windowHandle(), config&: d->getStateConfig());
596 }
597
598 // One day will need to save the version number, but for now, assume 0
599 // Utilise the QMainWindow::saveState() functionality.
600 const QByteArray state = saveState();
601 d->getStateConfig().writeEntry(key: "State", value: state.toBase64());
602
603 QStatusBar *sb = internalStatusBar(mw: this);
604 if (sb) {
605 if (!cg.hasDefault(key: "StatusBar") && !sb->isHidden()) {
606 cg.revertToDefault(key: "StatusBar");
607 } else {
608 cg.writeEntry(key: "StatusBar", value: sb->isHidden() ? "Disabled" : "Enabled");
609 }
610 }
611
612 QMenuBar *mb = internalMenuBar(mw: this);
613
614 if (mb && !mb->isNativeMenuBar()) {
615 if (!cg.hasDefault(key: "MenuBar") && !mb->isHidden()) {
616 cg.revertToDefault(key: "MenuBar");
617 } else {
618 cg.writeEntry(key: "MenuBar", value: mb->isHidden() ? "Disabled" : "Enabled");
619 }
620 }
621
622 if (!autoSaveSettings() || cg.name() == autoSaveGroup()) {
623 // TODO should be cg == d->autoSaveGroup, to compare both kconfig and group name
624 if (!cg.hasDefault(key: "ToolBarsMovable") && !KToolBar::toolBarsLocked()) {
625 cg.revertToDefault(key: "ToolBarsMovable");
626 } else {
627 cg.writeEntry(key: "ToolBarsMovable", value: KToolBar::toolBarsLocked() ? "Disabled" : "Enabled");
628 }
629 }
630
631 int n = 1; // Toolbar counter. toolbars are counted from 1,
632 const auto toolBars = this->toolBars();
633 for (KToolBar *toolbar : toolBars) {
634 // Give a number to the toolbar, but prefer a name if there is one,
635 // because there's no real guarantee on the ordering of toolbars
636 const QString groupName = toolbar->objectName().isEmpty() ? QStringLiteral("Toolbar%1").arg(a: n) : (QStringLiteral("Toolbar ") + toolbar->objectName());
637
638 KConfigGroup toolbarGroup(&cg, groupName);
639 toolbar->saveSettings(cg&: toolbarGroup);
640 n++;
641 }
642}
643
644bool KMainWindow::readPropertiesInternal(KConfig *config, int number)
645{
646 Q_D(KMainWindow);
647
648 const bool oldLetDirtySettings = d->letDirtySettings;
649 d->letDirtySettings = false;
650
651 if (number == 1) {
652 readGlobalProperties(config);
653 }
654
655 // in order they are in toolbar list
656 KConfigGroup cg(config, QStringLiteral("WindowProperties%1").arg(a: number));
657
658 // restore the object name (window role)
659 if (cg.hasKey(key: "ObjectName")) {
660 setObjectName(cg.readEntry(key: "ObjectName"));
661 }
662
663 d->sizeApplied = false; // since we are changing config file, reload the size of the window
664 // if necessary. Do it before the call to applyMainWindowSettings.
665 applyMainWindowSettings(config: cg); // Menubar, statusbar and toolbar settings.
666
667 KConfigGroup grp(config, QString::number(number));
668 readProperties(grp);
669
670 d->letDirtySettings = oldLetDirtySettings;
671
672 return true;
673}
674
675void KMainWindow::applyMainWindowSettings(const KConfigGroup &_cg)
676{
677 Q_D(KMainWindow);
678 // qDebug(200) << "KMainWindow::applyMainWindowSettings " << cg.name();
679
680 KConfigGroup cg = _cg;
681 d->migrateStateDataIfNeeded(cg);
682
683 QWidget *focusedWidget = QApplication::focusWidget();
684
685 const bool oldLetDirtySettings = d->letDirtySettings;
686 d->letDirtySettings = false;
687
688 KConfigGroup stateConfig = d->getStateConfig();
689
690 if (!d->sizeApplied && (!window() || window() == this)) {
691 winId(); // ensure there's a window created
692 // Set the window's size from the existing widget geometry to respect the
693 // implicit size when there is no saved geometry in the config file for
694 // KWindowConfig::restoreWindowSize() to restore
695 // TODO: remove once QTBUG-40584 is fixed; see below
696 windowHandle()->setWidth(width());
697 windowHandle()->setHeight(height());
698 KWindowConfig::restoreWindowSize(window: windowHandle(), config: stateConfig);
699 // NOTICE: QWindow::setGeometry() does NOT impact the backing QWidget geometry even if the platform
700 // window was created -> QTBUG-40584. We therefore copy the size here.
701 // TODO: remove once this was resolved in QWidget QPA
702 resize(windowHandle()->size());
703 d->sizeApplied = true;
704
705 // Let the user opt out of KDE apps remembering window sizes if they
706 // find it annoying or it doesn't work for them due to other bugs.
707 KSharedConfigPtr config = KSharedConfig::openConfig();
708 KConfigGroup group(config, QStringLiteral("General"));
709 if (group.readEntry(key: "AllowKDEAppsToRememberWindowPositions", defaultValue: true)) {
710 if (stateConfig.readEntry(key: "RestorePositionForNextInstance", defaultValue: true)) {
711 KWindowConfig::restoreWindowPosition(window: windowHandle(), config: stateConfig);
712 // Save the fact that we now don't want to restore position
713 // anymore; if we did, the next instance would completely cover
714 // the existing one
715 stateConfig.writeEntry(key: "RestorePositionForNextInstance", value: false);
716 }
717 }
718 }
719
720 QStatusBar *sb = internalStatusBar(mw: this);
721 if (sb) {
722 QString entry = cg.readEntry(key: "StatusBar", aDefault: "Enabled");
723 sb->setVisible(entry != QLatin1String("Disabled"));
724 }
725
726 QMenuBar *mb = internalMenuBar(mw: this);
727 if (mb && !mb->isNativeMenuBar()) {
728 QString entry = cg.readEntry(key: "MenuBar", aDefault: "Enabled");
729 mb->setVisible(entry != QLatin1String("Disabled"));
730 }
731
732 if (!autoSaveSettings() || cg.name() == autoSaveGroup()) { // TODO should be cg == d->autoSaveGroup, to compare both kconfig and group name
733 QString entry = cg.readEntry(key: "ToolBarsMovable", aDefault: "Disabled");
734 KToolBar::setToolBarsLocked(entry == QLatin1String("Disabled"));
735 }
736
737 int n = 1; // Toolbar counter. toolbars are counted from 1,
738 const auto toolBars = this->toolBars();
739 for (KToolBar *toolbar : toolBars) {
740 // Give a number to the toolbar, but prefer a name if there is one,
741 // because there's no real guarantee on the ordering of toolbars
742 const QString groupName = toolbar->objectName().isEmpty() ? QStringLiteral("Toolbar%1").arg(a: n) : (QStringLiteral("Toolbar ") + toolbar->objectName());
743
744 KConfigGroup toolbarGroup(&cg, groupName);
745 toolbar->applySettings(cg: toolbarGroup);
746 n++;
747 }
748
749 if (stateConfig.hasKey(key: "State")) {
750 QByteArray state;
751 state = stateConfig.readEntry(key: "State", defaultValue: state);
752 state = QByteArray::fromBase64(base64: state);
753 // One day will need to load the version number, but for now, assume 0
754 restoreState(state);
755 }
756
757 if (focusedWidget) {
758 focusedWidget->setFocus();
759 }
760
761 d->settingsDirty = false;
762 d->letDirtySettings = oldLetDirtySettings;
763}
764
765void KMainWindow::setSettingsDirty()
766{
767 Q_D(KMainWindow);
768 d->setSettingsDirty();
769}
770
771bool KMainWindow::settingsDirty() const
772{
773 Q_D(const KMainWindow);
774 return d->settingsDirty;
775}
776
777void KMainWindow::setAutoSaveSettings(const QString &groupName, bool saveWindowSize)
778{
779 setAutoSaveSettings(group: KConfigGroup(KSharedConfig::openConfig(), groupName), saveWindowSize);
780}
781
782void KMainWindow::setAutoSaveSettings(const KConfigGroup &group, bool saveWindowSize)
783{
784 // We re making a little assumption that if you want to save the window
785 // size, you probably also want to save the window position too
786 // This avoids having to re-implement a new version of
787 // KMainWindow::setAutoSaveSettings that handles these cases independently
788 Q_D(KMainWindow);
789 d->autoSaveSettings = true;
790 d->autoSaveGroup = group;
791 d->autoSaveWindowSize = saveWindowSize;
792
793 if (!saveWindowSize && d->sizeTimer) {
794 d->sizeTimer->stop();
795 }
796
797 // Now read the previously saved settings
798 applyMainWindowSettings(cg: d->autoSaveGroup);
799}
800
801void KMainWindow::resetAutoSaveSettings()
802{
803 Q_D(KMainWindow);
804 d->autoSaveSettings = false;
805 if (d->settingsTimer) {
806 d->settingsTimer->stop();
807 }
808}
809
810bool KMainWindow::autoSaveSettings() const
811{
812 Q_D(const KMainWindow);
813 return d->autoSaveSettings;
814}
815
816QString KMainWindow::autoSaveGroup() const
817{
818 Q_D(const KMainWindow);
819 return d->autoSaveSettings ? d->autoSaveGroup.name() : QString();
820}
821
822KConfigGroup KMainWindow::autoSaveConfigGroup() const
823{
824 Q_D(const KMainWindow);
825 return d->autoSaveSettings ? d->autoSaveGroup : KConfigGroup();
826}
827
828void KMainWindow::setStateConfigGroup(const QString &configGroup)
829{
830 Q_D(KMainWindow);
831 d->m_stateConfigGroup = KSharedConfig::openStateConfig()->group(group: configGroup);
832}
833
834KConfigGroup KMainWindow::stateConfigGroup() const
835{
836 Q_D(const KMainWindow);
837 return d->getStateConfig();
838}
839
840void KMainWindow::saveAutoSaveSettings()
841{
842 Q_D(KMainWindow);
843 Q_ASSERT(d->autoSaveSettings);
844 // qDebug(200) << "KMainWindow::saveAutoSaveSettings -> saving settings";
845 saveMainWindowSettings(cg&: d->autoSaveGroup);
846 d->autoSaveGroup.sync();
847 d->m_stateConfigGroup.sync();
848 d->settingsDirty = false;
849}
850
851bool KMainWindow::event(QEvent *ev)
852{
853 Q_D(KMainWindow);
854 switch (ev->type()) {
855#if defined(Q_OS_WIN) || defined(Q_OS_OSX)
856 case QEvent::Move:
857#endif
858 case QEvent::Resize:
859 d->setSizeDirty();
860 break;
861 case QEvent::Polish:
862 d->polish(q: this);
863 break;
864 case QEvent::ChildPolished: {
865 QChildEvent *event = static_cast<QChildEvent *>(ev);
866 QDockWidget *dock = qobject_cast<QDockWidget *>(object: event->child());
867 KToolBar *toolbar = qobject_cast<KToolBar *>(object: event->child());
868 QMenuBar *menubar = qobject_cast<QMenuBar *>(object: event->child());
869 if (dock) {
870 connect(sender: dock, signal: &QDockWidget::dockLocationChanged, context: this, slot: &KMainWindow::setSettingsDirty);
871 connect(sender: dock, signal: &QDockWidget::topLevelChanged, context: this, slot: &KMainWindow::setSettingsDirty);
872
873 // there is no signal emitted if the size of the dock changes,
874 // hence install an event filter instead
875 dock->installEventFilter(filterObj: d->dockResizeListener);
876 } else if (toolbar) {
877 // there is no signal emitted if the size of the toolbar changes,
878 // hence install an event filter instead
879 toolbar->installEventFilter(filterObj: d->dockResizeListener);
880 } else if (menubar) {
881 // there is no signal emitted if the size of the menubar changes,
882 // hence install an event filter instead
883 menubar->installEventFilter(filterObj: d->dockResizeListener);
884 }
885 break;
886 }
887 case QEvent::ChildRemoved: {
888 QChildEvent *event = static_cast<QChildEvent *>(ev);
889 QDockWidget *dock = qobject_cast<QDockWidget *>(object: event->child());
890 KToolBar *toolbar = qobject_cast<KToolBar *>(object: event->child());
891 QMenuBar *menubar = qobject_cast<QMenuBar *>(object: event->child());
892 if (dock) {
893 disconnect(sender: dock, signal: &QDockWidget::dockLocationChanged, receiver: this, slot: &KMainWindow::setSettingsDirty);
894 disconnect(sender: dock, signal: &QDockWidget::topLevelChanged, receiver: this, slot: &KMainWindow::setSettingsDirty);
895 dock->removeEventFilter(obj: d->dockResizeListener);
896 } else if (toolbar) {
897 toolbar->removeEventFilter(obj: d->dockResizeListener);
898 } else if (menubar) {
899 menubar->removeEventFilter(obj: d->dockResizeListener);
900 }
901 break;
902 }
903 default:
904 break;
905 }
906 return QMainWindow::event(event: ev);
907}
908
909void KMainWindow::keyPressEvent(QKeyEvent *keyEvent)
910{
911 if (KStandardShortcut::openContextMenu().contains(t: QKeySequence(keyEvent->key() | keyEvent->modifiers()))) {
912 if (QWidget *widgetWithKeyboardFocus = qApp->focusWidget()) {
913 const QPoint centerOfWidget(widgetWithKeyboardFocus->width() / 2, widgetWithKeyboardFocus->height() / 2);
914 qApp->postEvent(receiver: widgetWithKeyboardFocus,
915 event: new QContextMenuEvent(QContextMenuEvent::Keyboard, centerOfWidget, widgetWithKeyboardFocus->mapToGlobal(centerOfWidget)));
916 return;
917 }
918 if (qApp->focusObject()) {
919 qApp->postEvent(qApp->focusObject(), event: new QContextMenuEvent(QContextMenuEvent::Keyboard, mapFromGlobal(QCursor::pos()), QCursor::pos()));
920 return;
921 }
922 }
923 QMainWindow::keyPressEvent(event: keyEvent);
924}
925
926bool KMainWindow::hasMenuBar()
927{
928 return internalMenuBar(mw: this);
929}
930
931void KMainWindowPrivate::_k_slotSettingsChanged(int category)
932{
933 Q_UNUSED(category);
934
935 // This slot will be called when the style KCM changes settings that need
936 // to be set on the already running applications.
937
938 // At this level (KMainWindow) the only thing we need to restore is the
939 // animations setting (whether the user wants builtin animations or not).
940
941 q->setAnimated(q->style()->styleHint(stylehint: QStyle::SH_Widget_Animate, opt: nullptr, widget: q));
942}
943
944void KMainWindowPrivate::_k_slotSaveAutoSaveSize()
945{
946 if (autoSaveGroup.isValid()) {
947 KWindowConfig::saveWindowSize(window: q->windowHandle(), config&: getStateConfig());
948 }
949}
950
951void KMainWindowPrivate::_k_slotSaveAutoSavePosition()
952{
953 if (autoSaveGroup.isValid()) {
954 KWindowConfig::saveWindowPosition(window: q->windowHandle(), config&: getStateConfig());
955 }
956}
957
958KToolBar *KMainWindow::toolBar(const QString &name)
959{
960 QString childName = name;
961 if (childName.isEmpty()) {
962 childName = QStringLiteral("mainToolBar");
963 }
964
965 KToolBar *tb = findChild<KToolBar *>(aName: childName);
966 if (tb) {
967 return tb;
968 }
969
970 KToolBar *toolbar = new KToolBar(childName, this); // non-XMLGUI toolbar
971 return toolbar;
972}
973
974QList<KToolBar *> KMainWindow::toolBars() const
975{
976 QList<KToolBar *> ret;
977
978 const auto theChildren = children();
979 for (QObject *child : theChildren) {
980 if (KToolBar *toolBar = qobject_cast<KToolBar *>(object: child)) {
981 ret.append(t: toolBar);
982 }
983 }
984
985 return ret;
986}
987
988QList<KMainWindow *> KMainWindow::memberList()
989{
990 return *sMemberList();
991}
992
993QString KMainWindow::dbusName() const
994{
995 Q_D(const KMainWindow);
996
997 return d->dbusName;
998}
999
1000#include "kmainwindow.moc"
1001#include "moc_kmainwindow.cpp"
1002#include "moc_kmainwindow_p.cpp"
1003

source code of kxmlgui/src/kmainwindow.cpp