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 | |
54 | static QMenuBar *internalMenuBar(KMainWindow *mw) |
55 | { |
56 | return mw->findChild<QMenuBar *>(aName: QString(), options: Qt::FindDirectChildrenOnly); |
57 | } |
58 | |
59 | static 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 | */ |
72 | class DockResizeListener : public QObject |
73 | { |
74 | Q_OBJECT |
75 | public: |
76 | DockResizeListener(KMainWindow *win); |
77 | ~DockResizeListener() override; |
78 | bool eventFilter(QObject *watched, QEvent *event) override; |
79 | |
80 | private: |
81 | KMainWindow *const m_win; |
82 | }; |
83 | |
84 | DockResizeListener::DockResizeListener(KMainWindow *win) |
85 | : QObject(win) |
86 | , m_win(win) |
87 | { |
88 | } |
89 | |
90 | DockResizeListener::~DockResizeListener() |
91 | { |
92 | } |
93 | |
94 | bool 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 | |
111 | KMWSessionManager::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 | |
119 | KMWSessionManager::~KMWSessionManager() |
120 | { |
121 | } |
122 | |
123 | void 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 | |
161 | void 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 |
203 | Q_GLOBAL_STATIC(KMWSessionManager, ksm) |
204 | #endif |
205 | Q_GLOBAL_STATIC(QList<KMainWindow *>, sMemberList) |
206 | |
207 | KMainWindow::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 | |
216 | KMainWindow::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 | |
225 | void 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 | |
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 | 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 | |
414 | void 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 | |
429 | KMainWindow::~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 | |
435 | bool 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 | |
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->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 | |
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 | |