File indexing completed on 2024-05-12 16:01:58

0001 /*
0002  *  SPDX-FileCopyrightText: 2018 Jouni Pentikäinen <joupent@gmail.com>
0003  *
0004  *  SPDX-License-Identifier: GPL-2.0-or-later
0005  */
0006 
0007 #include "KisWindowLayoutManager.h"
0008 
0009 #include <QWidget>
0010 #include <QDesktopWidget>
0011 #include <QScreen>
0012 
0013 #include <kconfiggroup.h>
0014 #include <ksharedconfig.h>
0015 
0016 #include <KisApplication.h>
0017 #include <KisMainWindow.h>
0018 #include <KisPart.h>
0019 #include <kis_dom_utils.h>
0020 #include <KisResourceServerProvider.h>
0021 #include <KisSessionResource.h>
0022 
0023 Q_GLOBAL_STATIC(KisWindowLayoutManager, s_instance)
0024 
0025 struct KisWindowLayoutManager::Private {
0026     bool showImageInAllWindows{false};
0027     bool primaryWorkspaceFollowsFocus{false};
0028     QUuid primaryWindow;
0029 
0030     QVector<DisplayLayout*> displayLayouts;
0031 
0032     void loadDisplayLayouts() {
0033         KConfigGroup layoutsCfg(KSharedConfig::openConfig(), "DisplayLayouts");
0034         QStringList groups = layoutsCfg.groupList();
0035 
0036         Q_FOREACH(QString name, groups) {
0037             loadDisplayLayout(name, layoutsCfg.group(name));
0038         }
0039     }
0040 
0041     void loadDisplayLayout(const QString &name, KConfigGroup layoutCfg) {
0042         DisplayLayout *layout = new DisplayLayout();
0043         layout->name = name;
0044 
0045         int displayNumber = 1;
0046 
0047         while (true) {
0048             const QString displayDefinition = layoutCfg.readEntry(QString("Display%1").arg(displayNumber++), QString());
0049             if (displayDefinition.isEmpty()) break;
0050 
0051             // Just the resolution for now. Later we might want to split by a separator and include things like serial number, etc.
0052             const QString &resolutionStr = displayDefinition;
0053 
0054             QStringList dimensions = resolutionStr.split('x');
0055             if (dimensions.size() != 2) {
0056                 qWarning() << "Invalid display definition: " << displayDefinition;
0057                 break;
0058             }
0059 
0060             QSize resolution = QSize(
0061                 KisDomUtils::toInt(dimensions[0]),
0062                 KisDomUtils::toInt(dimensions[1])
0063             );
0064 
0065             layout->displays.append(Display{resolution});
0066         }
0067 
0068         layout->preferredWindowLayout = layoutCfg.readEntry("PreferredLayout", "");
0069 
0070         displayLayouts.append(layout);
0071     }
0072 
0073     void saveDisplayLayout(const DisplayLayout &layout) {
0074         KConfigGroup layoutsCfg(KSharedConfig::openConfig(), "DisplayLayouts");
0075         KConfigGroup layoutCfg = layoutsCfg.group(layout.name);
0076         layoutCfg.writeEntry("PreferredLayout", layout.preferredWindowLayout);
0077     }
0078 };
0079 
0080 bool KisWindowLayoutManager::Display::matches(QScreen* screen) const
0081 {
0082     return resolution == screen->geometry().size();
0083 }
0084 
0085 bool KisWindowLayoutManager::DisplayLayout::matches(QList<QScreen*> screens) const
0086 {
0087     if (screens.size() != displays.size()) return false;
0088 
0089     QVector<bool> matchedScreens(screens.size());
0090     Q_FOREACH(auto &expectedDisplay, displays) {
0091         int i;
0092         for (i = 0; i < screens.size(); i++) {
0093             if (matchedScreens[i]) continue;
0094 
0095             if (expectedDisplay.matches(screens[i])) {
0096                 matchedScreens[i] = true;
0097                 break;
0098             }
0099         }
0100 
0101         if (i == screens.size()) {
0102             return false;
0103         }
0104     }
0105 
0106     return true;
0107 }
0108 
0109 KisWindowLayoutManager * KisWindowLayoutManager::instance()
0110 {
0111     return s_instance;
0112 }
0113 
0114 KisWindowLayoutManager::KisWindowLayoutManager()
0115     : d(new Private)
0116 {
0117     d->loadDisplayLayouts();
0118 
0119     connect(qobject_cast<KisApplication*>(KisApplication::instance()),
0120             SIGNAL(focusChanged(QWidget*,QWidget*)),
0121             this, SLOT(slotFocusChanged(QWidget*,QWidget*)));
0122 
0123     connect(QApplication::desktop(), SIGNAL(resized(int)), this, SLOT(slotScreensChanged()));
0124     connect(QApplication::desktop(), SIGNAL(screenCountChanged(int)), this, SLOT(slotScreensChanged()));
0125 }
0126 
0127 KisWindowLayoutManager::~KisWindowLayoutManager() {
0128     Q_FOREACH(DisplayLayout *layout, d->displayLayouts) {
0129         delete layout;
0130     }
0131 }
0132 
0133 void KisWindowLayoutManager::setShowImageInAllWindowsEnabled(bool showInAll)
0134 {
0135     bool wasEnabled = d->showImageInAllWindows;
0136 
0137     d->showImageInAllWindows = showInAll;
0138 
0139     if (!wasEnabled && showInAll) {
0140         KisMainWindow *currentMainWindow = KisPart::instance()->currentMainwindow();
0141         if (currentMainWindow) {
0142             KisView *activeView = currentMainWindow->activeView();
0143             if (activeView) {
0144                 KisDocument *document = activeView->document();
0145                 if (document) {
0146                    activeDocumentChanged(document);
0147                 }
0148             }
0149         }
0150     }
0151 }
0152 
0153 bool KisWindowLayoutManager::isShowImageInAllWindowsEnabled() const
0154 {
0155     return d->showImageInAllWindows;
0156 }
0157 
0158 bool KisWindowLayoutManager::primaryWorkspaceFollowsFocus() const
0159 {
0160     return d->primaryWorkspaceFollowsFocus;
0161 }
0162 
0163 void KisWindowLayoutManager::setPrimaryWorkspaceFollowsFocus(bool enabled, QUuid primaryWindow)
0164 {
0165     d->primaryWorkspaceFollowsFocus = enabled;
0166     d->primaryWindow = primaryWindow;
0167 }
0168 
0169 QUuid KisWindowLayoutManager::primaryWindowId() const
0170 {
0171     return d->primaryWindow;
0172 }
0173 
0174 void KisWindowLayoutManager::activeDocumentChanged(KisDocument *document)
0175 {
0176     if (d->showImageInAllWindows) {
0177         Q_FOREACH(QPointer<KisMainWindow> window, KisPart::instance()->mainWindows()) {
0178             if (window->isHidden()) continue;
0179 
0180             const auto view = window->activeView();
0181             if (!view || view->document() != document) {
0182                 window->showDocument(document);
0183             }
0184         }
0185     }
0186 }
0187 
0188 void KisWindowLayoutManager::slotFocusChanged(QWidget *old, QWidget *now)
0189 {
0190     Q_UNUSED(old);
0191 
0192     if (!now) return;
0193     KisMainWindow *newMainWindow = qobject_cast<KisMainWindow*>(now->window());
0194     if (!newMainWindow) return;
0195 
0196     newMainWindow->windowFocused();
0197 }
0198 
0199 void KisWindowLayoutManager::setLastUsedLayout(KisWindowLayoutResource *layout)
0200 {
0201     // For automatic switching, only allow a window layout proper
0202     KisSessionResource *session = dynamic_cast<KisSessionResource*>(layout);
0203     if (session) return;
0204 
0205     QList<QScreen*> screens = QGuiApplication::screens();
0206     Q_FOREACH(DisplayLayout *displayLayout, d->displayLayouts) {
0207         if (displayLayout->matches(screens)) {
0208             displayLayout->preferredWindowLayout = layout->name();
0209             d->saveDisplayLayout(*displayLayout);
0210             break;
0211         }
0212     }
0213 }
0214 
0215 void KisWindowLayoutManager::slotScreensChanged()
0216 {
0217     QList<QScreen*> screens = QGuiApplication::screens();
0218 
0219     Q_FOREACH(const DisplayLayout *displayLayout, d->displayLayouts) {
0220         if (displayLayout->matches(screens)) {
0221             KoResourceServer<KisWindowLayoutResource> *windowLayoutServer = KisResourceServerProvider::instance()->windowLayoutServer();
0222             KisWindowLayoutResourceSP layout = windowLayoutServer->resource("", "", displayLayout->preferredWindowLayout);
0223 
0224             if (layout) {
0225                 setLastUsedLayout(layout.data());
0226                 layout->applyLayout();
0227                 return;
0228             }
0229         }
0230     }
0231 }