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 | |
57 | static QMenuBar *internalMenuBar(KMainWindow *mw) |
58 | { |
59 | return mw->findChild<QMenuBar *>(aName: QString(), options: Qt::FindDirectChildrenOnly); |
60 | } |
61 | |
62 | static 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 | */ |
75 | class DockResizeListener : public QObject |
76 | { |
77 | Q_OBJECT |
78 | public: |
79 | DockResizeListener(KMainWindow *win); |
80 | ~DockResizeListener() override; |
81 | bool eventFilter(QObject *watched, QEvent *event) override; |
82 | |
83 | private: |
84 | KMainWindow *const m_win; |
85 | }; |
86 | |
87 | DockResizeListener::DockResizeListener(KMainWindow *win) |
88 | : QObject(win) |
89 | , m_win(win) |
90 | { |
91 | } |
92 | |
93 | DockResizeListener::~DockResizeListener() |
94 | { |
95 | } |
96 | |
97 | bool 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 | |
114 | KMWSessionManager::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 | |
122 | KMWSessionManager::~KMWSessionManager() |
123 | { |
124 | } |
125 | |
126 | void 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 | |
164 | void 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 |
206 | Q_GLOBAL_STATIC(KMWSessionManager, ksm) |
207 | #endif |
208 | Q_GLOBAL_STATIC(QList<KMainWindow *>, sMemberList) |
209 | |
210 | KMainWindow::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 | |
219 | KMainWindow::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 | |
228 | void 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 | |
291 | static 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 | |
304 | static 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 | |
315 | void 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 | |
392 | void 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 | |
415 | void 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 | |
431 | KMainWindow::~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 | |
437 | bool 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 | |
449 | const 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 | |
464 | bool 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 | |
479 | void KMainWindow::setCaption(const QString &caption) |
480 | { |
481 | setPlainCaption(caption); |
482 | } |
483 | |
484 | void 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 | |
494 | void KMainWindow::setPlainCaption(const QString &caption) |
495 | { |
496 | setWindowTitle(caption); |
497 | } |
498 | |
499 | void 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 | |
511 | void 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 | |
553 | bool KMainWindow::queryClose() |
554 | { |
555 | return true; |
556 | } |
557 | |
558 | void KMainWindow::saveGlobalProperties(KConfig *) |
559 | { |
560 | } |
561 | |
562 | void KMainWindow::readGlobalProperties(KConfig *) |
563 | { |
564 | } |
565 | |
566 | void 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 | |
587 | void 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 | |
644 | bool 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 | |
675 | void 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 | |
765 | void KMainWindow::setSettingsDirty() |
766 | { |
767 | Q_D(KMainWindow); |
768 | d->setSettingsDirty(); |
769 | } |
770 | |
771 | bool KMainWindow::settingsDirty() const |
772 | { |
773 | Q_D(const KMainWindow); |
774 | return d->settingsDirty; |
775 | } |
776 | |
777 | void KMainWindow::setAutoSaveSettings(const QString &groupName, bool saveWindowSize) |
778 | { |
779 | setAutoSaveSettings(group: KConfigGroup(KSharedConfig::openConfig(), groupName), saveWindowSize); |
780 | } |
781 | |
782 | void 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 | |
801 | void KMainWindow::resetAutoSaveSettings() |
802 | { |
803 | Q_D(KMainWindow); |
804 | d->autoSaveSettings = false; |
805 | if (d->settingsTimer) { |
806 | d->settingsTimer->stop(); |
807 | } |
808 | } |
809 | |
810 | bool KMainWindow::autoSaveSettings() const |
811 | { |
812 | Q_D(const KMainWindow); |
813 | return d->autoSaveSettings; |
814 | } |
815 | |
816 | QString KMainWindow::autoSaveGroup() const |
817 | { |
818 | Q_D(const KMainWindow); |
819 | return d->autoSaveSettings ? d->autoSaveGroup.name() : QString(); |
820 | } |
821 | |
822 | KConfigGroup KMainWindow::autoSaveConfigGroup() const |
823 | { |
824 | Q_D(const KMainWindow); |
825 | return d->autoSaveSettings ? d->autoSaveGroup : KConfigGroup(); |
826 | } |
827 | |
828 | void KMainWindow::setStateConfigGroup(const QString &configGroup) |
829 | { |
830 | Q_D(KMainWindow); |
831 | d->m_stateConfigGroup = KSharedConfig::openStateConfig()->group(group: configGroup); |
832 | } |
833 | |
834 | KConfigGroup KMainWindow::stateConfigGroup() const |
835 | { |
836 | Q_D(const KMainWindow); |
837 | return d->getStateConfig(); |
838 | } |
839 | |
840 | void 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 | |
851 | bool 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 * = 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 * = 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 | |
909 | void 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 | |
926 | bool KMainWindow::hasMenuBar() |
927 | { |
928 | return internalMenuBar(mw: this); |
929 | } |
930 | |
931 | void 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 | |
944 | void KMainWindowPrivate::_k_slotSaveAutoSaveSize() |
945 | { |
946 | if (autoSaveGroup.isValid()) { |
947 | KWindowConfig::saveWindowSize(window: q->windowHandle(), config&: getStateConfig()); |
948 | } |
949 | } |
950 | |
951 | void KMainWindowPrivate::_k_slotSaveAutoSavePosition() |
952 | { |
953 | if (autoSaveGroup.isValid()) { |
954 | KWindowConfig::saveWindowPosition(window: q->windowHandle(), config&: getStateConfig()); |
955 | } |
956 | } |
957 | |
958 | KToolBar *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 | |
974 | QList<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 | |
988 | QList<KMainWindow *> KMainWindow::memberList() |
989 | { |
990 | return *sMemberList(); |
991 | } |
992 | |
993 | QString 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 | |