kmainwindow.cpp 30.2 KB
Newer Older
1 2 3 4 5 6 7 8 9 10
/*
    This file is part of the KDE libraries
    SPDX-FileCopyrightText: 2000 Reginald Stadlbauer <reggie@kde.org>
    SPDX-FileCopyrightText: 1997 Stephan Kulow <coolo@kde.org>
    SPDX-FileCopyrightText: 1997-2000 Sven Radej <radej@kde.org>
    SPDX-FileCopyrightText: 1997-2000 Matthias Ettrich <ettrich@kde.org>
    SPDX-FileCopyrightText: 1999 Chris Schlaeger <cs@kde.org>
    SPDX-FileCopyrightText: 2002 Joseph Wenninger <jowenn@kde.org>
    SPDX-FileCopyrightText: 2005-2006 Hamish Rodda <rodda@kde.org>
    SPDX-FileCopyrightText: 2000-2008 David Faure <faure@kde.org>
11

12
    SPDX-License-Identifier: LGPL-2.0-only
13
*/
14 15 16 17

#include "kmainwindow.h"

#include "kmainwindow_p.h"
Volker Krause's avatar
Volker Krause committed
18
#ifdef QT_DBUS_LIB
19
#include "kmainwindowiface_p.h"
Volker Krause's avatar
Volker Krause committed
20
#endif
21 22 23 24 25
#include "ktoolbarhandler_p.h"
#include "khelpmenu.h"
#include "ktoolbar.h"

#include <QApplication>
26 27 28
#include <QList>
#include <QObject>
#include <QTimer>
29 30 31 32 33 34 35
#include <QCloseEvent>
#include <QDockWidget>
#include <QMenuBar>
#include <QSessionManager>
#include <QStatusBar>
#include <QStyle>
#include <QWidget>
36
#include <QWindow>
Volker Krause's avatar
Volker Krause committed
37
#ifdef QT_DBUS_LIB
38
#include <QDBusConnection>
Volker Krause's avatar
Volker Krause committed
39
#endif
40 41 42 43 44 45 46 47 48 49 50 51

#include <ktoggleaction.h>
#include <kaboutdata.h>
#include <kconfig.h>
#include <ksharedconfig.h>
#include <klocalizedstring.h>
#include <kconfiggroup.h>
#include <kwindowconfig.h>
#include <kconfiggui.h>

//#include <ctype.h>

52 53
static const char WINDOW_PROPERTIES[]="WindowProperties";

54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73
static QMenuBar *internalMenuBar(KMainWindow *mw)
{
    return mw->findChild<QMenuBar *>(QString(), Qt::FindDirectChildrenOnly);
}

static QStatusBar *internalStatusBar(KMainWindow *mw)
{
    return mw->findChild<QStatusBar *>(QString(), Qt::FindDirectChildrenOnly);
}

/**

 * Listens to resize events from QDockWidgets. The KMainWindow
 * settings are set as dirty, as soon as at least one resize
 * event occurred. The listener is attached to the dock widgets
 * by dock->installEventFilter(dockResizeListener) inside
 * KMainWindow::event().
 */
class DockResizeListener : public QObject
{
74
    Q_OBJECT
75 76
public:
    DockResizeListener(KMainWindow *win);
Laurent Montel's avatar
Laurent Montel committed
77 78
    ~DockResizeListener() override;
    bool eventFilter(QObject *watched, QEvent *event) override;
79 80

private:
81
    KMainWindow *const m_win;
82 83 84 85 86 87 88 89 90 91 92 93 94 95
};

DockResizeListener::DockResizeListener(KMainWindow *win) :
    QObject(win),
    m_win(win)
{
}

DockResizeListener::~DockResizeListener()
{
}

bool DockResizeListener::eventFilter(QObject *watched, QEvent *event)
{
96
    switch (event->type()) {
97 98
    case QEvent::Resize:
    case QEvent::Move:
99
    case QEvent::Show:
100 101 102 103 104 105 106 107 108 109 110 111 112
    case QEvent::Hide:
        m_win->k_ptr->setSettingsDirty(KMainWindowPrivate::CompressCalls);
        break;

    default:
        break;
    }

    return QObject::eventFilter(watched, event);
}

KMWSessionManager::KMWSessionManager()
{
113 114 115 116
    connect(qApp, &QGuiApplication::saveStateRequest,
            this, &KMWSessionManager::saveState);
    connect(qApp, &QGuiApplication::commitDataRequest,
            this, &KMWSessionManager::commitData);
117 118 119 120 121 122
}

KMWSessionManager::~KMWSessionManager()
{
}

123
void KMWSessionManager::saveState(QSessionManager &sm)
124
{
125 126
    KConfigGui::setSessionConfig(sm.sessionId(), sm.sessionKey());

127
    KConfig *config = KConfigGui::sessionConfig();
128 129
    const auto windows = KMainWindow::memberList();
    if (!windows.isEmpty()) {
130 131
        // According to Jochen Wilhelmy <digisnap@cs.tu-berlin.de>, this
        // hook is useful for better document orientation
132
        windows.at(0)->saveGlobalProperties(config);
133 134 135
    }

    int n = 0;
136
    for (KMainWindow *mw : windows) {
137 138 139 140
        n++;
        mw->savePropertiesInternal(config, n);
    }

141 142
    KConfigGroup group(config, "Number");
    group.writeEntry("NumberOfWindows", n);
143 144 145 146 147 148 149 150

    // store new status to disk
    config->sync();

    // generate discard command for new file
    QString localFilePath =  QStandardPaths::writableLocation(QStandardPaths::GenericConfigLocation) + QLatin1Char('/') + config->name();
    if (QFile::exists(localFilePath)) {
        QStringList discard;
Laurent Montel's avatar
Laurent Montel committed
151
        discard << QStringLiteral("rm");
152 153 154
        discard << localFilePath;
        sm.setDiscardCommand(discard);
    }
155 156 157 158 159 160 161
}

void KMWSessionManager::commitData(QSessionManager &sm)
{
    if (!sm.allowsInteraction()) {
        return;
    }
162

163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179
    /*
       Purpose of this exercise: invoke queryClose() without actually closing the
       windows, because
       - queryClose() may contain session management code, so it must be invoked
       - actually closing windows may quit the application - cf.
         QGuiApplication::quitOnLastWindowClosed()
       - quitting the application and thus closing the session manager connection
         violates the X11 XSMP protocol.
         The exact requirement of XSMP that would be broken is,
         in the description of the client's state machine:

           save-yourself-done: (changing state is forbidden)

         Closing the session manager connection causes a state change.
         Worst of all, that is a real problem with ksmserver - it will not save
         applications that quit on their own in state save-yourself-done.
     */
180 181
    const auto windows = KMainWindow::memberList();
    for (KMainWindow *window : windows) {
182 183 184 185 186 187 188 189 190 191
        if (window->testAttribute(Qt::WA_WState_Hidden)) {
            continue;
        }
        QCloseEvent e;
        QApplication::sendEvent(window, &e);
        if (!e.isAccepted()) {
            sm.cancel();
            return;
        }
    }
192 193 194
}

Q_GLOBAL_STATIC(KMWSessionManager, ksm)
195
Q_GLOBAL_STATIC(QList<KMainWindow *>, sMemberList)
196

197
KMainWindow::KMainWindow(QWidget *parent, Qt::WindowFlags f)
198 199 200 201 202 203 204 205 206 207 208 209 210 211
    : QMainWindow(parent, f), k_ptr(new KMainWindowPrivate)
{
    k_ptr->init(this);
}

KMainWindow::KMainWindow(KMainWindowPrivate &dd, QWidget *parent, Qt::WindowFlags f)
    : QMainWindow(parent, f), k_ptr(&dd)
{
    k_ptr->init(this);
}

void KMainWindowPrivate::init(KMainWindow *_q)
{
    q = _q;
212
    QGuiApplication::setFallbackSessionManagementEnabled(false);
Kevin Funk's avatar
Kevin Funk committed
213
    q->setAnimated(q->style()->styleHint(QStyle::SH_Widget_Animate, nullptr, q));
214

215
    q->setAttribute(Qt::WA_DeleteOnClose);
216

Kevin Funk's avatar
Kevin Funk committed
217
    helpMenu = nullptr;
218 219 220 221 222 223 224 225 226 227

    //actionCollection()->setWidget( this );
#if 0
    QObject::connect(KGlobalSettings::self(), SIGNAL(settingsChanged(int)),
                     q, SLOT(_k_slotSettingsChanged(int)));
#endif

    // force KMWSessionManager creation
    ksm();

228
    sMemberList()->append(q);
229

230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246
    // Set the icon theme fallback to breeze (if not already set)
    // Most of our apps use "lots" of icons that most of the times
    // are only available with breeze, we still honour the user icon
    // theme but if the icon is not found there, we go to breeze
    // since it's almost sure it'll be there.
    // This should be done as soon as possible (preferably via
    // Q_COREAPP_STARTUP_FUNCTION), but as for now it cannot be done too soon
    // as at that point QPlatformTheme is not instantiated yet and breaks the
    // internal status of QIconLoader (see QTBUG-74252).
    // See also discussion at https://phabricator.kde.org/D22488
    // TODO: remove this once we depend on Qt 5.15.1, where this is fixed
#if QT_VERSION >= QT_VERSION_CHECK(5, 12, 0)
    if (QIcon::fallbackThemeName().isEmpty()) {
        QIcon::setFallbackThemeName(QStringLiteral("breeze"));
    }
#endif

247 248 249 250 251 252 253
    // If application is translated, load translator information for use in
    // KAboutApplicationDialog or other getters. The context and messages below
    // both must be exactly as listed, and are forced to be loaded from the
    // application's own message catalog instead of kxmlgui's.
    KAboutData aboutData(KAboutData::applicationData());
    if (aboutData.translators().isEmpty()) {
        aboutData.setTranslator(
Kevin Funk's avatar
Kevin Funk committed
254 255
                i18ndc(nullptr, "NAME OF TRANSLATORS", "Your names"),
                i18ndc(nullptr, "EMAIL OF TRANSLATORS", "Your emails"));
256 257 258 259

        KAboutData::setApplicationData(aboutData);
    }

260 261 262 263
    settingsDirty = false;
    autoSaveSettings = false;
    autoSaveWindowSize = true; // for compatibility
    //d->kaccel = actionCollection()->kaccel();
Kevin Funk's avatar
Kevin Funk committed
264 265
    settingsTimer = nullptr;
    sizeTimer = nullptr;
266
    positionTimer = nullptr;
267 268 269 270 271

    dockResizeListener = new DockResizeListener(_q);
    letDirtySettings = true;

    sizeApplied = false;
272
    suppressCloseEvent = false;
273 274
}

275
static bool endsWithHashNumber(const QString &s)
276
{
277 278 279 280 281 282 283
    for (int i = s.length() - 1;
            i > 0;
            --i) {
        if (s[ i ] == QLatin1Char('#') && i != s.length() - 1) {
            return true;    // ok
        }
        if (!s[ i ].isDigit()) {
284
            break;
285
        }
286 287 288 289 290 291
    }
    return false;
}

static inline bool isValidDBusObjectPathCharacter(const QChar &c)
{
Laurent Montel's avatar
Laurent Montel committed
292
    ushort u = c.unicode();
293
    return (u >= QLatin1Char('a') && u <= QLatin1Char('z'))
294 295 296
           || (u >= QLatin1Char('A') && u <= QLatin1Char('Z'))
           || (u >= QLatin1Char('0') && u <= QLatin1Char('9'))
           || (u == QLatin1Char('_')) || (u == QLatin1Char('/'));
297 298 299 300 301 302 303 304 305 306 307
}

void KMainWindowPrivate::polish(KMainWindow *q)
{
    // Set a unique object name. Required by session management, window management, and for the dbus interface.
    QString objname;
    QString s;
    int unusedNumber = 1;
    const QString name = q->objectName();
    bool startNumberingImmediately = true;
    bool tryReuse = false;
308 309
    if (name.isEmpty()) {
        // no name given
310
        objname = QStringLiteral("MainWindow#");
311 312
    } else if (name.endsWith(QLatin1Char('#'))) {
        // trailing # - always add a number  - KWin uses this for better grouping
313
        objname = name;
314 315
    } else if (endsWithHashNumber(name)) {
        // trailing # with a number - like above, try to use the given number first
316 317 318
        objname = name;
        tryReuse = true;
        startNumberingImmediately = false;
319
    } else {
320 321 322 323 324
        objname = name;
        startNumberingImmediately = false;
    }

    s = objname;
325
    if (startNumberingImmediately) {
326
        s += QLatin1Char('1');
327
    }
328

329 330
    for (;;) {
        const QList<QWidget *> list = qApp->topLevelWidgets();
331
        bool found = false;
332
        for (QWidget *w : list) {
333
            if (w != q && w->objectName() == s) {
334 335 336 337
                found = true;
                break;
            }
        }
338
        if (!found) {
339
            break;
340 341 342
        }
        if (tryReuse) {
            objname = name.left(name.length() - 1);   // lose the hash
343 344 345
            unusedNumber = 0; // start from 1 below
            tryReuse = false;
        }
346
        s.setNum(++unusedNumber);
347 348
        s = objname + s;
    }
349
    q->setObjectName(s);
350 351 352 353
    if (!q->window()) {
         q->winId(); // workaround for setWindowRole() crashing, and set also window role, just in case TT
         q->setWindowRole(s);   // will keep insisting that object name suddenly should not be used for window role
    }
354 355 356 357

    dbusName = QLatin1Char('/') + QCoreApplication::applicationName() + QLatin1Char('/');
    dbusName += q->objectName().replace(QLatin1Char('/'), QLatin1Char('_'));
    // Clean up for dbus usage: any non-alphanumeric char should be turned into '_'
358 359 360
    for (QChar &c : dbusName) {
        if (!isValidDBusObjectPathCharacter(c)) {
            c = QLatin1Char('_');
361
        }
362 363
    }

Volker Krause's avatar
Volker Krause committed
364
#ifdef QT_DBUS_LIB
365
    QDBusConnection::sessionBus().registerObject(dbusName, q, QDBusConnection::ExportScriptableSlots |
366 367 368 369
            QDBusConnection::ExportScriptableProperties |
            QDBusConnection::ExportNonScriptableSlots |
            QDBusConnection::ExportNonScriptableProperties |
            QDBusConnection::ExportAdaptors);
Volker Krause's avatar
Volker Krause committed
370
#endif
371 372 373 374 375 376 377 378 379 380 381 382 383 384 385
}

void KMainWindowPrivate::setSettingsDirty(CallCompression callCompression)
{
    if (!letDirtySettings) {
        return;
    }

    settingsDirty = true;
    if (autoSaveSettings) {
        if (callCompression == CompressCalls) {
            if (!settingsTimer) {
                settingsTimer = new QTimer(q);
                settingsTimer->setInterval(500);
                settingsTimer->setSingleShot(true);
386 387
                QObject::connect(settingsTimer, &QTimer::timeout,
                                 q, &KMainWindow::saveAutoSaveSettings);
388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408
            }
            settingsTimer->start();
        } else {
            q->saveAutoSaveSettings();
        }
    }
}

void KMainWindowPrivate::setSizeDirty()
{
    if (autoSaveWindowSize) {
        if (!sizeTimer) {
            sizeTimer = new QTimer(q);
            sizeTimer->setInterval(500);
            sizeTimer->setSingleShot(true);
            QObject::connect(sizeTimer, SIGNAL(timeout()), q, SLOT(_k_slotSaveAutoSaveSize()));
        }
        sizeTimer->start();
    }
}

409 410 411 412 413 414 415 416 417 418 419 420 421
void KMainWindowPrivate::setPositionDirty()
{
    if (autoSaveWindowSize) {
        if (!positionTimer) {
            positionTimer = new QTimer(q);
            positionTimer->setInterval(500);
            positionTimer->setSingleShot(true);
            QObject::connect(positionTimer, SIGNAL(timeout()), q, SLOT(_k_slotSaveAutoSavePosition()));
        }
        positionTimer->start();
    }
}

422 423
KMainWindow::~KMainWindow()
{
424
    sMemberList()->removeAll(this);
425 426 427 428
    delete static_cast<QObject *>(k_ptr->dockResizeListener);  //so we don't get anymore events after k_ptr is destroyed
    delete k_ptr;
}

429
#if KXMLGUI_BUILD_DEPRECATED_SINCE(5, 0)
430
QMenu *KMainWindow::helpMenu(const QString &aboutAppText, bool showWhatsThis)
431 432
{
    K_D(KMainWindow);
433 434 435 436 437 438
    if (!d->helpMenu) {
        if (aboutAppText.isEmpty()) {
            d->helpMenu = new KHelpMenu(this, KAboutData::applicationData(), showWhatsThis);
        } else {
            d->helpMenu = new KHelpMenu(this, aboutAppText, showWhatsThis);
        }
439

440
        if (!d->helpMenu) {
Kevin Funk's avatar
Kevin Funk committed
441
            return nullptr;
442
        }
443 444 445 446 447
    }

    return d->helpMenu->menu();
}

448
QMenu *KMainWindow::customHelpMenu(bool showWhatsThis)
449 450 451
{
    K_D(KMainWindow);
    if (!d->helpMenu) {
452
        d->helpMenu = new KHelpMenu(this, QString(), showWhatsThis);
453 454
        connect(d->helpMenu, &KHelpMenu::showAboutApplication,
                this, &KMainWindow::showAboutApplication);
455 456 457 458 459 460
    }

    return d->helpMenu->menu();
}
#endif

461
bool KMainWindow::canBeRestored(int number)
462 463
{
    KConfig *config = KConfigGui::sessionConfig();
464
    if (!config) {
465
        return false;
466
    }
467

468 469
    KConfigGroup group(config, "Number");
    const int n = group.readEntry("NumberOfWindows", 1);
470 471 472
    return number >= 1 && number <= n;
}

473
const QString KMainWindow::classNameOfToplevel(int number)
474 475
{
    KConfig *config = KConfigGui::sessionConfig();
476
    if (!config) {
477
        return QString();
478
    }
479

480 481
    KConfigGroup group(config, QByteArray(WINDOW_PROPERTIES).append(QByteArray::number(number)).constData());
    if (!group.hasKey("ClassName")) {
482
        return QString();
483
    } else {
484
        return group.readEntry("ClassName");
485
    }
486 487
}

488
bool KMainWindow::restore(int number, bool show)
489
{
490
    if (!canBeRestored(number)) {
491
        return false;
492
    }
493
    KConfig *config = KConfigGui::sessionConfig();
494 495
    if (readPropertiesInternal(config, number)) {
        if (show) {
496
            KMainWindow::show();
497
        }
498 499 500 501 502
        return false;
    }
    return false;
}

503
void KMainWindow::setCaption(const QString &caption)
504
{
505
    setPlainCaption(caption);
506 507
}

508
void KMainWindow::setCaption(const QString &caption, bool modified)
509
{
510
    QString title = caption;
511 512
    if (!title.contains(QLatin1String("[*]")) && !title.isEmpty()) { // append the placeholder so that the modified mechanism works
        title.append(QLatin1String(" [*]"));
513 514
    }
    setPlainCaption(title);
515
    setWindowModified(modified);
516 517
}

518
void KMainWindow::setPlainCaption(const QString &caption)
519
{
520
    setWindowTitle(caption);
521 522
}

523
void KMainWindow::appHelpActivated()
524 525
{
    K_D(KMainWindow);
526 527 528
    if (!d->helpMenu) {
        d->helpMenu = new KHelpMenu(this);
        if (!d->helpMenu) {
529
            return;
530
        }
531 532 533 534
    }
    d->helpMenu->appHelpActivated();
}

535
void KMainWindow::closeEvent(QCloseEvent *e)
536 537
{
    K_D(KMainWindow);
538
    if (d->suppressCloseEvent) {
539 540 541
        e->accept();
        return;
    }
542 543 544 545 546 547 548 549 550 551

    // Save settings if auto-save is enabled, and settings have changed
    if (d->settingsTimer && d->settingsTimer->isActive()) {
        d->settingsTimer->stop();
        saveAutoSaveSettings();
    }
    if (d->sizeTimer && d->sizeTimer->isActive()) {
        d->sizeTimer->stop();
        d->_k_slotSaveAutoSaveSize();
    }
552 553 554 555
    if (d->positionTimer && d->positionTimer->isActive()) {
        d->positionTimer->stop();
        d->_k_slotSaveAutoSavePosition();
    }
556 557

    if (queryClose()) {
558 559 560 561
        // widgets will start destroying themselves at this point and we don't
        // want to save state anymore after this as it might be incorrect
        d->autoSaveSettings = false;
        d->letDirtySettings = false;
562
        e->accept();
563 564 565
    } else {
        e->ignore();    //if the window should not be closed, don't close it
    }
566 567
    // If saving session, we are processing a fake close event, and might get the real one later.
    if (e->isAccepted() && qApp->isSavingSession())
568
        d->suppressCloseEvent = true;
569 570 571 572 573 574 575
}

bool KMainWindow::queryClose()
{
    return true;
}

576
void KMainWindow::saveGlobalProperties(KConfig *)
577 578 579
{
}

580
void KMainWindow::readGlobalProperties(KConfig *)
581 582 583
{
}

584
void KMainWindow::savePropertiesInternal(KConfig *config, int number)
585 586 587 588 589
{
    K_D(KMainWindow);
    const bool oldASWS = d->autoSaveWindowSize;
    d->autoSaveWindowSize = true; // make saveMainWindowSettings save the window size

590
    KConfigGroup cg(config, QByteArray(WINDOW_PROPERTIES).append(QByteArray::number(number)).constData());
591 592 593 594 595 596 597 598

    // store objectName, className, Width and Height  for later restoring
    // (Only useful for session management)
    cg.writeEntry("ObjectName", objectName());
    cg.writeEntry("ClassName", metaObject()->className());

    saveMainWindowSettings(cg); // Menubar, statusbar and Toolbar settings.

599
    cg = KConfigGroup(config, QByteArray::number(number).constData());
600 601 602 603 604 605 606 607 608 609 610
    saveProperties(cg);

    d->autoSaveWindowSize = oldASWS;
}

void KMainWindow::saveMainWindowSettings(KConfigGroup &cg)
{
    K_D(KMainWindow);
    //qDebug(200) << "KMainWindow::saveMainWindowSettings " << cg.name();

    // Called by session management - or if we want to save the window size anyway
611 612
    if (d->autoSaveWindowSize) {
        KWindowConfig::saveWindowSize(windowHandle(), cg);
613
        KWindowConfig::saveWindowPosition(windowHandle(), cg);
614
    }
615 616 617 618 619 620

    // One day will need to save the version number, but for now, assume 0
    // Utilise the QMainWindow::saveState() functionality.
    const QByteArray state = saveState();
    cg.writeEntry("State", state.toBase64());

621
    QStatusBar *sb = internalStatusBar(this);
622
    if (sb) {
623 624 625 626 627
        if (!cg.hasDefault("StatusBar") && !sb->isHidden()) {
            cg.revertToDefault("StatusBar");
        } else {
            cg.writeEntry("StatusBar", sb->isHidden() ? "Disabled" : "Enabled");
        }
628 629
    }

630
    QMenuBar *mb = internalMenuBar(this);
631
    if (mb) {
632 633 634 635 636
        if (!cg.hasDefault("MenuBar") && !mb->isHidden()) {
            cg.revertToDefault("MenuBar");
        } else {
            cg.writeEntry("MenuBar", mb->isHidden() ? "Disabled" : "Enabled");
        }
637 638
    }

639
    if (!autoSaveSettings() || cg.name() == autoSaveGroup()) {
640
        // TODO should be cg == d->autoSaveGroup, to compare both kconfig and group name
641
        if (!cg.hasDefault("ToolBarsMovable") && !KToolBar::toolBarsLocked()) {
642
            cg.revertToDefault("ToolBarsMovable");
643
        } else {
644
            cg.writeEntry("ToolBarsMovable", KToolBar::toolBarsLocked() ? "Disabled" : "Enabled");
645
        }
646 647 648
    }

    int n = 1; // Toolbar counter. toolbars are counted from 1,
649 650
    const auto toolBars = this->toolBars();
    for (KToolBar *toolbar : toolBars) {
651
        QByteArray groupName("Toolbar");
652 653
        // Give a number to the toolbar, but prefer a name if there is one,
        // because there's no real guarantee on the ordering of toolbars
654
        groupName += (toolbar->objectName().isEmpty() ? QByteArray::number(n) : QByteArray(" ").append(toolbar->objectName().toUtf8()));
655

656
        KConfigGroup toolbarGroup(&cg, groupName.constData());
657 658 659 660 661
        toolbar->saveSettings(toolbarGroup);
        n++;
    }
}

662
bool KMainWindow::readPropertiesInternal(KConfig *config, int number)
663 664 665 666 667 668
{
    K_D(KMainWindow);

    const bool oldLetDirtySettings = d->letDirtySettings;
    d->letDirtySettings = false;

669 670 671
    if (number == 1) {
        readGlobalProperties(config);
    }
672 673

    // in order they are in toolbar list
674
    KConfigGroup cg(config, QByteArray(WINDOW_PROPERTIES).append(QByteArray::number(number)).constData());
675 676

    // restore the object name (window role)
677
    if (cg.hasKey("ObjectName")) {
678
        setObjectName(cg.readEntry("ObjectName"));
679
    }
680 681

    d->sizeApplied = false; // since we are changing config file, reload the size of the window
682
    // if necessary. Do it before the call to applyMainWindowSettings.
683 684
    applyMainWindowSettings(cg); // Menubar, statusbar and toolbar settings.

685
    KConfigGroup grp(config, QByteArray::number(number).constData());
686 687 688 689 690 691 692
    readProperties(grp);

    d->letDirtySettings = oldLetDirtySettings;

    return true;
}

693
void KMainWindow::applyMainWindowSettings(const KConfigGroup &cg)
694 695 696 697 698 699 700 701 702
{
    K_D(KMainWindow);
    //qDebug(200) << "KMainWindow::applyMainWindowSettings " << cg.name();

    QWidget *focusedWidget = QApplication::focusWidget();

    const bool oldLetDirtySettings = d->letDirtySettings;
    d->letDirtySettings = false;

703
    if (!d->sizeApplied && !window()) {
704
        winId(); // ensure there's a window created
705
        KWindowConfig::restoreWindowSize(windowHandle(), cg);
706 707 708 709
        // NOTICE: QWindow::setGeometry() does NOT impact the backing QWidget geometry even if the platform
        // window was created -> QTBUG-40584. We therefore copy the size here.
        // TODO: remove once this was resolved in QWidget QPA
        resize(windowHandle()->size());
710 711 712 713 714 715 716 717 718 719 720 721 722 723

        // Let the user opt out of KDE apps remembering window sizes if they
        // find it annoying or it doesn't work for them due to other bugs.
        // When called with no args, this looks at kdeglobals
        KSharedConfigPtr config = KSharedConfig::openConfig();
        KConfigGroup group(config, "General");
        if (group.readEntry("AllowKDEAppsToRememberWindowPositions", true)) {
            // TODO: how should we handle the case when a new instance is opened
            // and there's stored window position data? Cascade the new window?
            // Don't restore position and let the window manager handle it? Can
            // we even know this from here?
            KWindowConfig::restoreWindowPosition(windowHandle(), cg);
            d->sizeApplied = true;
        }
724 725
    }

726
    QStatusBar *sb = internalStatusBar(this);
727 728
    if (sb) {
        QString entry = cg.readEntry("StatusBar", "Enabled");
729
        sb->setVisible( entry != QLatin1String("Disabled") );
730 731
    }

732
    QMenuBar *mb = internalMenuBar(this);
733
    if (mb) {
734
        QString entry = cg.readEntry("MenuBar", "Enabled");
735
        mb->setVisible( entry != QLatin1String("Disabled") );
736 737
    }

738 739
    if (!autoSaveSettings() || cg.name() == autoSaveGroup()) {   // TODO should be cg == d->autoSaveGroup, to compare both kconfig and group name
        QString entry = cg.readEntry("ToolBarsMovable", "Disabled");
740
        KToolBar::setToolBarsLocked(entry == QLatin1String("Disabled"));
741 742 743
    }

    int n = 1; // Toolbar counter. toolbars are counted from 1,
744 745
    const auto toolBars = this->toolBars();
    for (KToolBar *toolbar : toolBars) {
746
        QByteArray groupName("Toolbar");
747 748
        // Give a number to the toolbar, but prefer a name if there is one,
        // because there's no real guarantee on the ordering of toolbars
749
        groupName += (toolbar->objectName().isEmpty() ? QByteArray::number(n) : QByteArray(" ").append(toolbar->objectName().toUtf8()));
750

751
        KConfigGroup toolbarGroup(&cg, groupName.constData());
752
        toolbar->applySettings(toolbarGroup);
753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771
        n++;
    }

    QByteArray state;
    if (cg.hasKey("State")) {
        state = cg.readEntry("State", state);
        state = QByteArray::fromBase64(state);
        // One day will need to load the version number, but for now, assume 0
        restoreState(state);
    }

    if (focusedWidget) {
        focusedWidget->setFocus();
    }

    d->settingsDirty = false;
    d->letDirtySettings = oldLetDirtySettings;
}

772
#if KXMLGUI_BUILD_DEPRECATED_SINCE(5, 0)
773
void KMainWindow::restoreWindowSize(const KConfigGroup &cg)
774 775 776 777 778
{
    KWindowConfig::restoreWindowSize(windowHandle(), cg);
}
#endif

779
#if KXMLGUI_BUILD_DEPRECATED_SINCE(5, 0)
780
void KMainWindow::saveWindowSize(KConfigGroup &cg) const
781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797
{
    KWindowConfig::saveWindowSize(windowHandle(), cg);
}
#endif

void KMainWindow::setSettingsDirty()
{
    K_D(KMainWindow);
    d->setSettingsDirty();
}

bool KMainWindow::settingsDirty() const
{
    K_D(const KMainWindow);
    return d->settingsDirty;
}

798
void KMainWindow::setAutoSaveSettings(const QString &groupName, bool saveWindowSize)
799 800 801 802
{
    setAutoSaveSettings(KConfigGroup(KSharedConfig::openConfig(), groupName), saveWindowSize);
}

803 804
void KMainWindow::setAutoSaveSettings(const KConfigGroup &group,
                                      bool saveWindowSize)
805
{
806 807 808 809
    // We re making a little assumption that if you want to save the window
    // size, you probably also want to save the window position too
    // This avoids having to re-implement a new version of
    // KMainWindow::setAutoSaveSettings that handles these cases independently
810 811 812 813 814 815 816 817
    K_D(KMainWindow);
    d->autoSaveSettings = true;
    d->autoSaveGroup = group;
    d->autoSaveWindowSize = saveWindowSize;

    if (!saveWindowSize && d->sizeTimer) {
        d->sizeTimer->stop();
    }
818
    if (!saveWindowSize && d->positionTimer) {
819
        d->positionTimer->stop();
820
    }
821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 854 855

    // Now read the previously saved settings
    applyMainWindowSettings(d->autoSaveGroup);
}

void KMainWindow::resetAutoSaveSettings()
{
    K_D(KMainWindow);
    d->autoSaveSettings = false;
    if (d->settingsTimer) {
        d->settingsTimer->stop();
    }
}

bool KMainWindow::autoSaveSettings() const
{
    K_D(const KMainWindow);
    return d->autoSaveSettings;
}

QString KMainWindow::autoSaveGroup() const
{
    K_D(const KMainWindow);
    return d->autoSaveSettings ? d->autoSaveGroup.name() : QString();
}

KConfigGroup KMainWindow::autoSaveConfigGroup() const
{
    K_D(const KMainWindow);
    return d->autoSaveSettings ? d->autoSaveGroup : KConfigGroup();
}

void KMainWindow::saveAutoSaveSettings()
{
    K_D(KMainWindow);
856
    Q_ASSERT(d->autoSaveSettings);
857 858 859 860 861 862
    //qDebug(200) << "KMainWindow::saveAutoSaveSettings -> saving settings";
    saveMainWindowSettings(d->autoSaveGroup);
    d->autoSaveGroup.sync();
    d->settingsDirty = false;
}

863
bool KMainWindow::event(QEvent *ev)
864 865
{
    K_D(KMainWindow);
866
    switch (ev->type()) {
867
    case QEvent::Move:
868
        d->setPositionDirty();
869
        Q_FALLTHROUGH();
870 871 872 873 874 875
    case QEvent::Resize:
        d->setSizeDirty();
        break;
    case QEvent::Polish:
        d->polish(this);
        break;
876 877 878 879 880 881
    case QEvent::ChildPolished: {
        QChildEvent *event = static_cast<QChildEvent *>(ev);
        QDockWidget *dock = qobject_cast<QDockWidget *>(event->child());
        KToolBar *toolbar = qobject_cast<KToolBar *>(event->child());
        QMenuBar *menubar = qobject_cast<QMenuBar *>(event->child());
        if (dock) {
882 883 884 885
            connect(dock, &QDockWidget::dockLocationChanged,
                    this, &KMainWindow::setSettingsDirty);
            connect(dock, &QDockWidget::topLevelChanged,
                    this, &KMainWindow::setSettingsDirty);
886 887 888 889 890 891 892 893 894 895 896 897

            // there is no signal emitted if the size of the dock changes,
            // hence install an event filter instead
            dock->installEventFilter(k_ptr->dockResizeListener);
        } else if (toolbar) {
            // there is no signal emitted if the size of the toolbar changes,
            // hence install an event filter instead
            toolbar->installEventFilter(k_ptr->dockResizeListener);
        } else if (menubar) {
            // there is no signal emitted if the size of the menubar changes,
            // hence install an event filter instead
            menubar->installEventFilter(k_ptr->dockResizeListener);
898
        }
899 900 901 902 903 904 905 906
    }
    break;
    case QEvent::ChildRemoved: {
        QChildEvent *event = static_cast<QChildEvent *>(ev);
        QDockWidget *dock = qobject_cast<QDockWidget *>(event->child());
        KToolBar *toolbar = qobject_cast<KToolBar *>(event->child());
        QMenuBar *menubar = qobject_cast<QMenuBar *>(event->child());
        if (dock) {
907 908 909 910
            disconnect(dock, &QDockWidget::dockLocationChanged,
                       this, &KMainWindow::setSettingsDirty);
            disconnect(dock, &QDockWidget::topLevelChanged,
                       this, &KMainWindow::setSettingsDirty);
911 912 913 914 915
            dock->removeEventFilter(k_ptr->dockResizeListener);
        } else if (toolbar) {
            toolbar->removeEventFilter(k_ptr->dockResizeListener);
        } else if (menubar) {
            menubar->removeEventFilter(k_ptr->dockResizeListener);
916
        }
917 918
    }
    break;
919 920 921
    default:
        break;
    }
922
    return QMainWindow::event(ev);
923 924 925 926 927 928 929 930 931 932 933 934 935 936 937 938 939
}

bool KMainWindow::hasMenuBar()
{
    return internalMenuBar(this);
}

void KMainWindowPrivate::_k_slotSettingsChanged(int category)
{
    Q_UNUSED(category);

    // This slot will be called when the style KCM changes settings that need
    // to be set on the already running applications.

    // At this level (KMainWindow) the only thing we need to restore is the
    // animations setting (whether the user wants builtin animations or not).

Kevin Funk's avatar
Kevin Funk committed
940
    q->setAnimated(q->style()->styleHint(QStyle::SH_Widget_Animate, nullptr, q));
941 942 943 944 945 946 947 948 949
}

void KMainWindowPrivate::_k_slotSaveAutoSaveSize()
{
    if (autoSaveGroup.isValid()) {
        KWindowConfig::saveWindowSize(q->windowHandle(), autoSaveGroup);
    }
}

950 951 952 953 954 955 956
void KMainWindowPrivate::_k_slotSaveAutoSavePosition()
{
    if (autoSaveGroup.isValid()) {
        KWindowConfig::saveWindowPosition(q->windowHandle(), autoSaveGroup);
    }
}

957
KToolBar *KMainWindow::toolBar(const QString &name)
958 959
{
    QString childName = name;
960 961 962
    if (childName.isEmpty()) {
        childName = QStringLiteral("mainToolBar");
    }
963

964 965
    KToolBar *tb = findChild<KToolBar *>(childName);
    if (tb) {
966
        return tb;
967
    }
968

969
    KToolBar *toolbar = new KToolBar(childName, this); // non-XMLGUI toolbar
970 971 972
    return toolbar;
}

973
QList<KToolBar *> KMainWindow::toolBars() const
974
{
975
    QList<KToolBar *> ret;
976

Nicolas Fella's avatar
Nicolas Fella committed
977 978
    const auto theChildren = children();
    for (QObject *child : theChildren)
979
        if (KToolBar *toolBar = qobject_cast<KToolBar *>(child)) {
980
            ret.append(toolBar);
981
        }
982 983 984 985

    return ret;
}

986 987 988 989
QList<KMainWindow *> KMainWindow::memberList()
{
    return *sMemberList();
}
990 991 992 993 994 995 996

QString KMainWindow::dbusName() const
{
    return k_func()->dbusName;
}

#include "moc_kmainwindow.cpp"
997
#include "kmainwindow.moc"