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 "kactionconflictdetector_p.h"
18#include "kcheckaccelerators.h"
19#include "kmainwindow_p.h"
20#ifdef WITH_QTDBUS
21#include "kmainwindowiface_p.h"
22#endif
23#include "khelpmenu.h"
24#include "ktoolbar.h"
25#include "ktoolbarhandler_p.h"
26#include "ktooltiphelper.h"
27
28#include <QApplication>
29#include <QCloseEvent>
30#include <QDockWidget>
31#include <QFile>
32#include <QList>
33#include <QMenuBar>
34#include <QObject>
35#include <QRandomGenerator>
36#ifndef QT_NO_SESSIONMANAGER
37#include <QSessionManager>
38#endif
39#include <QStatusBar>
40#include <QStyle>
41#include <QTimer>
42#include <QWidget>
43#include <QWindow>
44#ifdef WITH_QTDBUS
45#include <QDBusConnection>
46#endif
47
48#include <KAboutData>
49#include <KConfig>
50#include <KConfigGroup>
51#include <KConfigGui>
52#include <KLocalizedString>
53#include <KSharedConfig>
54#include <KStandardShortcut>
55#include <KWindowConfig>
56
57static QMenuBar *internalMenuBar(KMainWindow *mw)
58{
59 return mw->findChild<QMenuBar *>(aName: QString(), options: Qt::FindDirectChildrenOnly);
60}
61
62static QStatusBar *internalStatusBar(KMainWindow *mw)
63{
64 return mw->findChild<QStatusBar *>(aName: QString(), options: Qt::FindDirectChildrenOnly);
65}
66
67/*!
68 * \internal
69 * Listens to resize events from QDockWidgets. The KMainWindow
70 * settings are set as dirty, as soon as at least one resize
71 * event occurred. The listener is attached to the dock widgets
72 * by dock->installEventFilter(dockResizeListener) inside
73 * KMainWindow::event().
74 */
75class DockResizeListener : public QObject
76{
77 Q_OBJECT
78public:
79 DockResizeListener(KMainWindow *win);
80 ~DockResizeListener() override;
81 bool eventFilter(QObject *watched, QEvent *event) override;
82
83private:
84 KMainWindow *const m_win;
85};
86
87DockResizeListener::DockResizeListener(KMainWindow *win)
88 : QObject(win)
89 , m_win(win)
90{
91}
92
93DockResizeListener::~DockResizeListener()
94{
95}
96
97bool DockResizeListener::eventFilter(QObject *watched, QEvent *event)
98{
99 switch (event->type()) {
100 case QEvent::Resize:
101 case QEvent::Move:
102 case QEvent::Show:
103 case QEvent::Hide:
104 m_win->d_ptr->setSettingsDirty(KMainWindowPrivate::CompressCalls);
105 break;
106
107 default:
108 break;
109 }
110
111 return QObject::eventFilter(watched, event);
112}
113
114KMWSessionManager::KMWSessionManager()
115{
116#ifndef QT_NO_SESSIONMANAGER
117 connect(qApp, signal: &QGuiApplication::saveStateRequest, context: this, slot: &KMWSessionManager::saveState);
118 connect(qApp, signal: &QGuiApplication::commitDataRequest, context: this, slot: &KMWSessionManager::commitData);
119#endif
120}
121
122KMWSessionManager::~KMWSessionManager()
123{
124}
125
126void KMWSessionManager::saveState(QSessionManager &sm)
127{
128#ifndef QT_NO_SESSIONMANAGER
129 KConfigGui::setSessionConfig(id: sm.sessionId(), key: sm.sessionKey());
130
131 KConfig *config = KConfigGui::sessionConfig();
132 const auto windows = KMainWindow::memberList();
133 if (!windows.isEmpty()) {
134 // According to Jochen Wilhelmy <digisnap@cs.tu-berlin.de>, this
135 // hook is useful for better document orientation
136 windows.at(i: 0)->saveGlobalProperties(sessionConfig: config);
137 }
138
139 int n = 0;
140 for (KMainWindow *mw : windows) {
141 n++;
142 mw->savePropertiesInternal(config, n);
143 }
144
145 KConfigGroup group(config, QStringLiteral("Number"));
146 group.writeEntry(key: "NumberOfWindows", value: n);
147
148 // store new status to disk
149 config->sync();
150
151 // generate discard command for new file
152 QString localFilePath = QStandardPaths::writableLocation(type: QStandardPaths::GenericConfigLocation) + QLatin1Char('/') + config->name();
153 if (QFile::exists(fileName: localFilePath)) {
154 QStringList discard;
155 discard << QStringLiteral("rm");
156 discard << localFilePath;
157 sm.setDiscardCommand(discard);
158 }
159#else
160 Q_UNUSED(sm)
161#endif // QT_NO_SESSIONMANAGER
162}
163
164void KMWSessionManager::commitData(QSessionManager &sm)
165{
166#ifndef QT_NO_SESSIONMANAGER
167 if (!sm.allowsInteraction()) {
168 return;
169 }
170
171 /*
172 Purpose of this exercise: invoke queryClose() without actually closing the
173 windows, because
174 - queryClose() may contain session management code, so it must be invoked
175 - actually closing windows may quit the application - cf.
176 QGuiApplication::quitOnLastWindowClosed()
177 - quitting the application and thus closing the session manager connection
178 violates the X11 XSMP protocol.
179 The exact requirement of XSMP that would be broken is,
180 in the description of the client's state machine:
181
182 save-yourself-done: (changing state is forbidden)
183
184 Closing the session manager connection causes a state change.
185 Worst of all, that is a real problem with ksmserver - it will not save
186 applications that quit on their own in state save-yourself-done.
187 */
188 const auto windows = KMainWindow::memberList();
189 for (KMainWindow *window : windows) {
190 if (window->testAttribute(attribute: Qt::WA_WState_Hidden)) {
191 continue;
192 }
193 QCloseEvent e;
194 QApplication::sendEvent(receiver: window, event: &e);
195 if (!e.isAccepted()) {
196 sm.cancel();
197 return;
198 }
199 }
200#else
201 Q_UNUSED(sm)
202#endif // QT_NO_SESSIONMANAGER
203}
204
205#ifndef QT_NO_SESSIONMANAGER
206Q_GLOBAL_STATIC(KMWSessionManager, ksm)
207#endif
208Q_GLOBAL_STATIC(QList<KMainWindow *>, sMemberList)
209
210KMainWindow::KMainWindow(QWidget *parent, Qt::WindowFlags flags)
211 : QMainWindow(parent, flags)
212 , d_ptr(new KMainWindowPrivate)
213{
214 Q_D(KMainWindow);
215
216 d->init(q: this);
217}
218
219KMainWindow::KMainWindow(KMainWindowPrivate &dd, QWidget *parent, Qt::WindowFlags f)
220 : QMainWindow(parent, f)
221 , d_ptr(&dd)
222{
223 Q_D(KMainWindow);
224
225 d->init(q: this);
226}
227
228void KMainWindowPrivate::init(KMainWindow *_q)
229{
230 q = _q;
231
232 q->setAnimated(q->style()->styleHint(stylehint: QStyle::SH_Widget_Animate, opt: nullptr, widget: q));
233
234 q->setAttribute(Qt::WA_DeleteOnClose);
235
236 helpMenu = nullptr;
237
238 // actionCollection()->setWidget( this );
239#if 0
240 QObject::connect(KGlobalSettings::self(), SIGNAL(settingsChanged(int)),
241 q, SLOT(_k_slotSettingsChanged(int)));
242#endif
243
244#ifndef QT_NO_SESSIONMANAGER
245 // force KMWSessionManager creation
246 ksm();
247#endif
248
249 sMemberList()->append(t: q);
250
251 // If application is translated, load translator information for use in
252 // KAboutApplicationDialog or other getters. The context and messages below
253 // both must be exactly as listed, and are forced to be loaded from the
254 // application's own message catalog instead of kxmlgui's.
255 KAboutData aboutData(KAboutData::applicationData());
256 if (aboutData.translators().isEmpty()) {
257 aboutData.setTranslator(name: i18ndc(domain: nullptr, context: "NAME OF TRANSLATORS", text: "Your names"), //
258 emailAddress: i18ndc(domain: nullptr, context: "EMAIL OF TRANSLATORS", text: "Your emails"));
259
260 KAboutData::setApplicationData(aboutData);
261 }
262
263 settingsDirty = false;
264 autoSaveSettings = false;
265 autoSaveWindowSize = true; // for compatibility
266 // d->kaccel = actionCollection()->kaccel();
267 settingsTimer = nullptr;
268 sizeTimer = nullptr;
269
270 dockResizeListener = new DockResizeListener(_q);
271 letDirtySettings = true;
272
273 sizeApplied = false;
274 suppressCloseEvent = false;
275
276 qApp->installEventFilter(filterObj: KToolTipHelper::instance());
277
278 // create the conflict detector only if some main window got created
279 // before we did that on library load, that might mess with plain Qt applications
280 // see bug 467130
281 static QPointer<KActionConflictDetector> conflictDetector;
282 if (!conflictDetector) {
283 conflictDetector = new KActionConflictDetector(QCoreApplication::instance());
284 QCoreApplication::instance()->installEventFilter(filterObj: conflictDetector);
285 }
286
287 // same for accelerator checking
288 KCheckAccelerators::initiateIfNeeded();
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 // don't trigger sync of config always at same time to avoid clashes if x instances are running
404 settingsTimer->setInterval(QRandomGenerator::global()->bounded(lowest: 500, highest: 1500));
405 settingsTimer->setSingleShot(true);
406 QObject::connect(sender: settingsTimer, signal: &QTimer::timeout, context: q, slot: &KMainWindow::saveAutoSaveSettings);
407 }
408 settingsTimer->start();
409 } else {
410 q->saveAutoSaveSettings();
411 }
412 }
413}
414
415void KMainWindowPrivate::setSizeDirty()
416{
417 if (autoSaveWindowSize) {
418 if (!sizeTimer) {
419 sizeTimer = new QTimer(q);
420 // don't trigger sync of config always at same time to avoid clashes if x instances are running
421 sizeTimer->setInterval(QRandomGenerator::global()->bounded(lowest: 500, highest: 1500));
422 sizeTimer->setSingleShot(true);
423 QObject::connect(sender: sizeTimer, signal: &QTimer::timeout, context: q, slot: [this]() {
424 _k_slotSaveAutoSaveSize();
425 });
426 }
427 sizeTimer->start();
428 }
429}
430
431KMainWindow::~KMainWindow()
432{
433 sMemberList()->removeAll(t: this);
434 delete static_cast<QObject *>(d_ptr->dockResizeListener); // so we don't get anymore events after d_ptr is destroyed
435}
436
437bool KMainWindow::canBeRestored(int numberOfInstances)
438{
439 KConfig *config = KConfigGui::sessionConfig();
440 if (!config) {
441 return false;
442 }
443
444 const KConfigGroup group(config, QStringLiteral("Number"));
445 const int n = group.readEntry(key: "NumberOfWindows", defaultValue: 0);
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->getStateConfig().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