File indexing completed on 2024-05-19 04:29:15
0001 /* This file is part of the KDE project 0002 SPDX-FileCopyrightText: 1998, 1999 Torben Weis <weis@kde.org> 0003 SPDX-FileCopyrightText: 2000-2006 David Faure <faure@kde.org> 0004 SPDX-FileCopyrightText: 2007, 2009 Thomas zander <zander@kde.org> 0005 SPDX-FileCopyrightText: 2010 Benjamin Port <port.benjamin@gmail.com> 0006 0007 SPDX-License-Identifier: LGPL-2.0-or-later 0008 */ 0009 0010 #include "KisMainWindow.h" 0011 0012 #include <KoConfig.h> 0013 0014 // qt includes 0015 #include <QApplication> 0016 #include <QByteArray> 0017 #include <QCloseEvent> 0018 #include <QStandardPaths> 0019 #include <QDesktopServices> 0020 #include <QDesktopWidget> 0021 #include <QDialog> 0022 #include <QDockWidget> 0023 #include <QIcon> 0024 #include <QInputDialog> 0025 #include <QLabel> 0026 #include <QLayout> 0027 #include <QMdiArea> 0028 #include <QMdiSubWindow> 0029 #include <QMutex> 0030 #include <QMutexLocker> 0031 #include <QPointer> 0032 #include <QToolButton> 0033 #include <KisSignalMapper.h> 0034 #include <QTabBar> 0035 #include <QMoveEvent> 0036 #include <QUrl> 0037 #include <QMessageBox> 0038 #include <QStatusBar> 0039 #include <QStyleFactory> 0040 #include <QMenu> 0041 #include <QMenuBar> 0042 #include <KisMimeDatabase.h> 0043 #include <QMimeData> 0044 #include <QStackedWidget> 0045 #include <QProxyStyle> 0046 #include <QScreen> 0047 #include <QAction> 0048 #include <QWindow> 0049 #include <QTemporaryDir> 0050 #include <QScrollArea> 0051 #include <kactioncollection.h> 0052 #include <kactionmenu.h> 0053 #include <kis_debug.h> 0054 #include <kedittoolbar.h> 0055 #include <khelpmenu.h> 0056 #include <klocalizedstring.h> 0057 #include <kaboutdata.h> 0058 #include <kis_workspace_resource.h> 0059 #include <input/kis_input_manager.h> 0060 #include "kis_selection_manager.h" 0061 #include "kis_icon_utils.h" 0062 #include <krecentfilesaction.h> 0063 #include "krita_utils.h" 0064 #include <ktoggleaction.h> 0065 #include <ktoolbar.h> 0066 #include <kmainwindow.h> 0067 #include <kxmlguiwindow.h> 0068 #include <kxmlguifactory.h> 0069 #include <kxmlguiclient.h> 0070 #include <kguiitem.h> 0071 #include <kwindowconfig.h> 0072 #include <kacceleratormanager.h> 0073 0074 #include <KoResourcePaths.h> 0075 #include <KoToolFactoryBase.h> 0076 #include <KoToolRegistry.h> 0077 #include <KoDockFactoryBase.h> 0078 #include <KoDockWidgetTitleBar.h> 0079 #include <KoDocumentInfoDlg.h> 0080 #include <KoDocumentInfo.h> 0081 #include <KoFileDialog.h> 0082 #include <kis_icon.h> 0083 #include <KoToolManager.h> 0084 #include <KoZoomController.h> 0085 #include "KoToolDocker.h" 0086 #include "toolbox/KoToolBoxFactory.h" 0087 #include <KoDockRegistry.h> 0088 #include <KoPluginLoader.h> 0089 #include <KoColorSpaceEngine.h> 0090 #include <KoUpdater.h> 0091 #include <KisResourceModel.h> 0092 #include <KisResourceLoaderRegistry.h> 0093 #include <KisResourceIterator.h> 0094 #include <KisResourceTypes.h> 0095 #include <KisResourceCacheDb.h> 0096 #include <KisStorageModel.h> 0097 #include <KisStorageFilterProxyModel.h> 0098 #include <KisPlaybackEngine.h> 0099 0100 #ifdef Q_OS_ANDROID 0101 #include <QtAndroid> 0102 #endif 0103 0104 #include <KisUsageLogger.h> 0105 #include <brushengine/kis_paintop_settings.h> 0106 #include "dialogs/kis_about_application.h" 0107 #include "dialogs/kis_delayed_save_dialog.h" 0108 #include "dialogs/kis_dlg_preferences.h" 0109 #include "kis_action_manager.h" 0110 #include "KisApplication.h" 0111 #include "kis_canvas2.h" 0112 #include "kis_canvas_controller.h" 0113 #include "kis_canvas_resource_provider.h" 0114 #include "kis_clipboard.h" 0115 #include "kis_config.h" 0116 #include "kis_config_notifier.h" 0117 #include "kis_custom_image_widget.h" 0118 #include "animation/KisAnimationRender.h" 0119 #include "animation/KisDlgAnimationRenderer.h" 0120 #include <KisDocument.h> 0121 #include "kis_group_layer.h" 0122 #include "kis_image_from_clipboard_widget.h" 0123 #include "kis_image.h" 0124 #include "kis_image_animation_interface.h" 0125 #include <KisImportExportFilter.h> 0126 #include "KisImportExportManager.h" 0127 #include "kis_mainwindow_observer.h" 0128 #include "kis_node.h" 0129 #include "KisOpenPane.h" 0130 #include "kis_paintop_box.h" 0131 #include "KisPart.h" 0132 #include "KisResourceServerProvider.h" 0133 #include "kis_signal_compressor_with_param.h" 0134 #include "kis_statusbar.h" 0135 #include "KisView.h" 0136 #include "KisViewManager.h" 0137 #include "thememanager.h" 0138 #include "kis_animation_importer.h" 0139 #include "dialogs/kis_dlg_import_image_sequence.h" 0140 #include "animation/KisDlgImportVideoAnimation.h" 0141 #include <KisImageConfigNotifier.h> 0142 #include "KisWindowLayoutManager.h" 0143 #include <KisUndoActionsUpdateManager.h> 0144 #include "KisWelcomePageWidget.h" 0145 #include "KisRecentDocumentsModelWrapper.h" 0146 #include <KritaVersionWrapper.h> 0147 #include "KisCanvasWindow.h" 0148 #include "kis_action.h" 0149 #include <katecommandbar.h> 0150 #include "KisNodeActivationActionCreatorVisitor.h" 0151 #include "KisUiFont.h" 0152 #include <KisResourceUserOperations.h> 0153 #include "KisRecentFilesManager.h" 0154 #include "KisWidgetConnectionUtils.h" 0155 #include "KisToolBarStateModel.h" 0156 #include <config-qmdiarea-always-show-subwindow-title.h> 0157 0158 #include <mutex> 0159 0160 class ToolDockerFactory : public KoDockFactoryBase 0161 { 0162 public: 0163 ToolDockerFactory() : KoDockFactoryBase() { } 0164 0165 QString id() const override { 0166 return "sharedtooldocker"; 0167 } 0168 0169 QDockWidget* createDockWidget() override { 0170 KoToolDocker* dockWidget = new KoToolDocker(); 0171 return dockWidget; 0172 } 0173 0174 DockPosition defaultDockPosition() const override { 0175 return DockRight; 0176 } 0177 }; 0178 0179 class Q_DECL_HIDDEN KisMainWindow::Private 0180 { 0181 public: 0182 Private(KisMainWindow *parent, QUuid id) 0183 : q(parent) 0184 , id(id) 0185 , styleMenu(new KActionMenu(i18nc("@action:inmenu", "Styles"), parent)) 0186 , dockWidgetMenu(new KActionMenu(i18nc("@action:inmenu", "&Dockers"), parent)) 0187 , windowMenu(new KActionMenu(i18nc("@action:inmenu", "&Window"), parent)) 0188 , documentMenu(new KActionMenu(i18nc("@action:inmenu", "New &View"), parent)) 0189 , workspaceMenu(new KActionMenu(i18nc("@action:inmenu", "Wor&kspace"), parent)) 0190 , welcomePage(new KisWelcomePageWidget(parent)) 0191 , widgetStack(new QStackedWidget(parent)) 0192 , mdiArea(new QMdiArea(parent)) 0193 , windowMapper(new KisSignalMapper(parent)) 0194 , documentMapper(new KisSignalMapper(parent)) 0195 { 0196 if (id.isNull()) this->id = QUuid::createUuid(); 0197 0198 welcomeScroller = new QScrollArea(); 0199 welcomeScroller->setHorizontalScrollBarPolicy(Qt::ScrollBarAsNeeded); 0200 welcomeScroller->setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded); 0201 welcomeScroller->setWidget(welcomePage); 0202 welcomeScroller->setWidgetResizable(true); 0203 0204 widgetStack->addWidget(welcomeScroller); 0205 widgetStack->addWidget(mdiArea); 0206 mdiArea->setTabsMovable(true); 0207 mdiArea->setActivationOrder(QMdiArea::ActivationHistoryOrder); 0208 mdiArea->setDocumentMode(true); 0209 mdiArea->setOption(QMdiArea::DontMaximizeSubWindowOnActivation); 0210 #ifdef HAVE_QMDIAREA_ALWAYS_SHOW_SUBWINDOW_TITLE 0211 mdiArea->setOption(QMdiArea::AlwaysShowSubwindowNameInTitleBar); 0212 #endif /* HAVE_QMDIAREA_ALWAYS_SHOW_SUBWINDOW_TITLE */ 0213 0214 commandBar = new KateCommandBar(parent); 0215 } 0216 0217 ~Private() { 0218 qDeleteAll(toolbarList); 0219 } 0220 0221 KisMainWindow *q {nullptr}; 0222 QUuid id; 0223 0224 KisViewManager *viewManager {nullptr}; 0225 0226 QPointer<KisView> activeView; 0227 KisSignalAutoConnectionsStore activeViewConnections; 0228 0229 QList<QAction *> toolbarList; 0230 0231 bool firstTime {true}; 0232 bool windowSizeDirty {false}; 0233 0234 KisAction *showDocumentInfo {nullptr}; 0235 KisAction *saveAction {nullptr}; 0236 KisAction *saveActionAs {nullptr}; 0237 KisAction *importAnimation {nullptr}; 0238 KisAction *importVideoAnimation {nullptr}; 0239 KisAction *renderAnimation {nullptr}; 0240 KisAction *renderAnimationAgain {nullptr}; 0241 KisAction *closeAll {nullptr}; 0242 KisAction *importFile {nullptr}; 0243 KisAction *exportFile {nullptr}; 0244 KisAction *exportFileAdvance {nullptr}; 0245 KisAction *undo {nullptr}; 0246 KisAction *redo {nullptr}; 0247 KisAction *close {nullptr}; 0248 KisAction *mdiCascade {nullptr}; 0249 KisAction *mdiTile {nullptr}; 0250 KisAction *mdiNextWindow {nullptr}; 0251 KisAction *mdiPreviousWindow {nullptr}; 0252 KisAction *toggleDockers {nullptr}; 0253 KisAction *resetConfigurations {nullptr}; 0254 KisAction *toggleDockerTitleBars {nullptr}; 0255 #ifndef Q_OS_ANDROID 0256 KisAction *toggleDetachCanvas {nullptr}; 0257 KisAction *newWindow {nullptr}; 0258 #endif 0259 KisAction *fullScreenMode {nullptr}; 0260 KisAction *showSessionManager {nullptr}; 0261 KisAction *commandBarAction {nullptr}; 0262 KisAction *expandingSpacers[2]; 0263 0264 KActionMenu *styleMenu {nullptr}; 0265 QActionGroup* styleActions {nullptr}; 0266 QMap<QString, QAction*> actionMap; 0267 0268 KActionMenu *dockWidgetMenu {nullptr}; 0269 KActionMenu *windowMenu {nullptr}; 0270 KActionMenu *documentMenu {nullptr}; 0271 KActionMenu *workspaceMenu {nullptr}; 0272 0273 KisKHelpMenu *helpMenu {nullptr}; 0274 0275 KRecentFilesAction *recentFiles {nullptr}; 0276 KisResourceModel *workspacemodel {nullptr}; 0277 0278 QScopedPointer<KisUndoActionsUpdateManager> undoActionsUpdateManager; 0279 0280 QString lastExportLocation; 0281 0282 QMap<QString, QDockWidget *> dockWidgetsMap; 0283 QByteArray dockerStateBeforeHiding; 0284 KoToolDocker *toolOptionsDocker {nullptr}; 0285 0286 QCloseEvent *deferredClosingEvent {nullptr}; 0287 0288 Digikam::ThemeManager *themeManager {nullptr}; 0289 0290 QScrollArea *welcomeScroller {nullptr}; 0291 KisWelcomePageWidget *welcomePage {nullptr}; 0292 0293 0294 QStackedWidget *widgetStack {nullptr}; 0295 0296 QMdiArea *mdiArea {nullptr}; 0297 QMdiSubWindow *activeSubWindow {nullptr}; 0298 KisSignalMapper *windowMapper {nullptr}; 0299 KisSignalMapper *documentMapper {nullptr}; 0300 KisCanvasWindow *canvasWindow {nullptr}; 0301 0302 QByteArray lastExportedFormat; 0303 QScopedPointer<KisSignalCompressorWithParam<int> > tabSwitchCompressor; 0304 QMutex savingEntryMutex; 0305 0306 KConfigGroup windowStateConfig; 0307 0308 QUuid workspaceBorrowedBy; 0309 0310 KateCommandBar *commandBar {nullptr}; 0311 0312 KisActionManager * actionManager() { 0313 return viewManager->actionManager(); 0314 } 0315 0316 QTabBar* findTabBarHACK() { 0317 QObjectList objects = mdiArea->children(); 0318 Q_FOREACH (QObject *object, objects) { 0319 QTabBar *bar = qobject_cast<QTabBar*>(object); 0320 if (bar) { 0321 return bar; 0322 } 0323 } 0324 return 0; 0325 } 0326 }; 0327 0328 class ScopedWidgetDisabler 0329 { 0330 QWidget *widget; 0331 public: 0332 ScopedWidgetDisabler(QWidget *widget_) 0333 : widget(widget_) 0334 { 0335 widget->setEnabled(false); 0336 } 0337 0338 ~ScopedWidgetDisabler() 0339 { 0340 widget->setEnabled(true); 0341 } 0342 }; 0343 0344 KisMainWindow::KisMainWindow(QUuid uuid) 0345 : KXmlGuiWindow() 0346 , d(new Private(this, uuid)) 0347 { 0348 KAcceleratorManager::setNoAccel(this); 0349 0350 d->workspacemodel = new KisResourceModel(ResourceType::Workspaces, this); 0351 connect(d->workspacemodel, SIGNAL(modelReset()), this, SLOT(updateWindowMenu())); 0352 connect(d->workspacemodel, SIGNAL(rowsInserted(QModelIndex,int,int)), this, SLOT(updateWindowMenu())); 0353 connect(d->workspacemodel, SIGNAL(rowsRemoved(QModelIndex,int,int)), this, SLOT(updateWindowMenu())); 0354 0355 d->viewManager = new KisViewManager(this, actionCollection()); 0356 KConfigGroup group( KSharedConfig::openConfig(), "theme"); 0357 d->themeManager = new Digikam::ThemeManager(group.readEntry("Theme", "Krita dark"), this); 0358 0359 d->windowStateConfig = KSharedConfig::openConfig()->group("MainWindow"); 0360 0361 setStandardToolBarMenuEnabled(true); 0362 setTabPosition(Qt::AllDockWidgetAreas, QTabWidget::North); 0363 setDockNestingEnabled(true); 0364 0365 qApp->setStartDragDistance(25); // 25 px is a distance that works well for Tablet and Mouse events 0366 0367 #ifdef Q_OS_MACOS 0368 setUnifiedTitleAndToolBarOnMac(true); 0369 #endif 0370 0371 connect(this, SIGNAL(restoringDone()), this, SLOT(forceDockTabFonts())); 0372 connect(this, SIGNAL(themeChanged()), d->viewManager, SLOT(updateIcons())); 0373 connect(this, SIGNAL(themeChanged()), KoToolManager::instance(), SLOT(themeChanged())); 0374 connect(KisPart::instance(), SIGNAL(documentClosed(QString)), SLOT(updateWindowMenu())); 0375 connect(KisPart::instance(), SIGNAL(documentOpened(QString)), SLOT(updateWindowMenu())); 0376 connect(KisConfigNotifier::instance(), SIGNAL(configChanged()), this, SLOT(configChanged())); 0377 0378 actionCollection()->addAssociatedWidget(this); 0379 KoPluginLoader::instance()->load("Krita/ViewPlugin", "Type == 'Service' and ([X-Krita-Version] == 28)", KoPluginLoader::PluginsConfig(), d->viewManager, false); 0380 0381 // Load the per-application plugins (Right now, only Python) We do this only once, when the first mainwindow is being created. 0382 KoPluginLoader::instance()->load("Krita/ApplicationPlugin", "Type == 'Service' and ([X-Krita-Version] == 28)", KoPluginLoader::PluginsConfig(), qApp, true); 0383 0384 KoToolBoxFactory toolBoxFactory; 0385 QDockWidget *toolbox = createDockWidget(&toolBoxFactory); 0386 0387 KisConfig cfg(true); 0388 if (cfg.toolOptionsInDocker()) { 0389 ToolDockerFactory toolDockerFactory; 0390 d->toolOptionsDocker = qobject_cast<KoToolDocker*>(createDockWidget(&toolDockerFactory)); 0391 if (d->toolOptionsDocker) { 0392 d->toolOptionsDocker->toggleViewAction()->setEnabled(true); 0393 } 0394 } 0395 0396 QMap<QString, QAction*> dockwidgetActions; 0397 0398 if (toolbox) { 0399 dockwidgetActions[toolbox->toggleViewAction()->text()] = toolbox->toggleViewAction(); 0400 } 0401 Q_FOREACH (const QString & docker, KoDockRegistry::instance()->keys()) { 0402 KoDockFactoryBase *factory = KoDockRegistry::instance()->value(docker); 0403 QDockWidget *dw = createDockWidget(factory); 0404 if (dw) { 0405 dockwidgetActions[dw->toggleViewAction()->text()] = dw->toggleViewAction(); 0406 } 0407 } 0408 0409 if (d->toolOptionsDocker) { 0410 dockwidgetActions[d->toolOptionsDocker->toggleViewAction()->text()] = d->toolOptionsDocker->toggleViewAction(); 0411 } 0412 connect(KoToolManager::instance(), SIGNAL(toolOptionWidgetsChanged(KoCanvasController*,QList<QPointer<QWidget> >)), this, SLOT(newOptionWidgets(KoCanvasController*,QList<QPointer<QWidget> >))); 0413 0414 Q_FOREACH (QString title, dockwidgetActions.keys()) { 0415 d->dockWidgetMenu->addAction(dockwidgetActions[title]); 0416 } 0417 0418 0419 // Style menu actions 0420 d->styleActions = new QActionGroup(this); 0421 QAction * action; 0422 0423 QStringList allowableStyles = QStringList() << "macintosh" << "breeze" << "fusion"; 0424 0425 Q_FOREACH (QString styleName, QStyleFactory::keys()) { 0426 #ifdef Q_OS_ANDROID 0427 // disable the style for android platform 0428 if (styleName.toLower().contains("android")) { 0429 continue; 0430 } 0431 #endif 0432 if (qgetenv("KRITA_NO_STYLE_OVERRIDE").isEmpty()) { 0433 if (!allowableStyles.contains(styleName.toLower())) { 0434 continue; 0435 } 0436 } 0437 action = new QAction(styleName, d->styleActions); 0438 action->setCheckable(true); 0439 d->actionMap.insert(styleName, action); 0440 d->styleMenu->addAction(d->actionMap.value(styleName)); 0441 } 0442 0443 0444 // select the config value, or the current style if that does not exist 0445 QString styleFromConfig = cfg.widgetStyle().toLower(); 0446 QString styleToSelect = styleFromConfig == "" ? style()->objectName().toLower() : styleFromConfig; 0447 0448 Q_FOREACH (auto key, d->actionMap.keys()) { 0449 if(key.toLower() == styleToSelect) { // does the key match selection 0450 d->actionMap.value(key)->setChecked(true); 0451 } 0452 } 0453 0454 connect(d->styleActions, SIGNAL(triggered(QAction*)), 0455 this, SLOT(slotUpdateWidgetStyle())); 0456 0457 0458 // Load all the actions from the tool plugins 0459 // ToolBoxDocker needs them when at setViewManager() 0460 Q_FOREACH(KoToolFactoryBase *toolFactory, KoToolRegistry::instance()->values()) { 0461 toolFactory->createActions(actionCollection()); 0462 } 0463 0464 0465 Q_FOREACH (QDockWidget *wdg, dockWidgets()) { 0466 if ((wdg->features() & QDockWidget::DockWidgetClosable) == 0) { 0467 wdg->setVisible(true); 0468 } 0469 } 0470 0471 Q_FOREACH (KoCanvasObserverBase* observer, canvasObservers()) { 0472 observer->setObservedCanvas(0); 0473 KisMainwindowObserver* mainwindowObserver = dynamic_cast<KisMainwindowObserver*>(observer); 0474 if (mainwindowObserver) { 0475 mainwindowObserver->setViewManager(d->viewManager); 0476 } 0477 } 0478 0479 d->mdiArea->setHorizontalScrollBarPolicy(Qt::ScrollBarAsNeeded); 0480 d->mdiArea->setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded); 0481 d->mdiArea->setTabPosition(QTabWidget::North); 0482 d->mdiArea->setTabsClosable(true); 0483 d->mdiArea->setAcceptDrops(true); 0484 0485 themeChanged(); // updates icon styles 0486 0487 setCentralWidget(d->widgetStack); 0488 d->widgetStack->setCurrentIndex(0); 0489 0490 connect(d->mdiArea, SIGNAL(subWindowActivated(QMdiSubWindow*)), this, SLOT(subWindowActivated())); 0491 connect(d->windowMapper, SIGNAL(mapped(QWidget*)), this, SLOT(setActiveSubWindow(QWidget*))); 0492 connect(d->documentMapper, SIGNAL(mapped(QObject*)), this, SLOT(newView(QObject*))); 0493 0494 d->canvasWindow = new KisCanvasWindow(this); 0495 actionCollection()->addAssociatedWidget(d->canvasWindow); 0496 0497 createActions(); 0498 0499 // the welcome screen needs to grab actions...so make sure this line goes after the createAction() so they exist 0500 d->welcomePage->setMainWindow(this); 0501 0502 d->recentFiles->setRecentFilesModel(&KisRecentDocumentsModelWrapper::instance()->model()); 0503 0504 applyMainWindowSettings(d->windowStateConfig); 0505 0506 subWindowActivated(); 0507 updateWindowMenu(); 0508 0509 if (isHelpMenuEnabled() && !d->helpMenu) { 0510 QGuiApplication *app = qApp; 0511 KAboutData aboutData(KAboutData::applicationData()); 0512 aboutData.setOrganizationDomain(app->organizationDomain().toUtf8()); 0513 0514 d->helpMenu = new KisKHelpMenu(this, aboutData, false); 0515 0516 // workaround-less version: 0517 // d->helpMenu = new KisKHelpMenu(this, QString()/*unused*/, false); 0518 0519 // The difference between using KisKActionCollection->addAction() is that 0520 // these actions do not get tied to the MainWindow. What does this all do? 0521 KisKActionCollection *actions = d->viewManager->actionCollection(); 0522 QAction *helpContentsAction = d->helpMenu->action(KisKHelpMenu::menuHelpContents); 0523 QAction *whatsThisAction = d->helpMenu->action(KisKHelpMenu::menuWhatsThis); 0524 QAction *reportBugAction = d->helpMenu->action(KisKHelpMenu::menuReportBug); 0525 QAction *switchLanguageAction = d->helpMenu->action(KisKHelpMenu::menuSwitchLanguage); 0526 QAction *aboutAppAction = d->helpMenu->action(KisKHelpMenu::menuAboutApp); 0527 QAction *aboutKdeAction = d->helpMenu->action(KisKHelpMenu::menuAboutKDE); 0528 0529 if (helpContentsAction) { 0530 actions->addAction(helpContentsAction->objectName(), helpContentsAction); 0531 } 0532 if (whatsThisAction) { 0533 actions->addAction(whatsThisAction->objectName(), whatsThisAction); 0534 } 0535 if (reportBugAction) { 0536 actions->addAction(reportBugAction->objectName(), reportBugAction); 0537 } 0538 if (switchLanguageAction) { 0539 actions->addAction(switchLanguageAction->objectName(), switchLanguageAction); 0540 } 0541 if (aboutAppAction) { 0542 actions->addAction(aboutAppAction->objectName(), aboutAppAction); 0543 } 0544 if (aboutKdeAction) { 0545 actions->addAction(aboutKdeAction->objectName(), aboutKdeAction); 0546 } 0547 0548 connect(d->helpMenu, SIGNAL(showAboutApplication()), SLOT(showAboutApplication())); 0549 } 0550 0551 // KDE' libs 4''s help contents action is broken outside kde, for some reason... We can handle it just as easily ourselves 0552 QAction *helpAction = actionCollection()->action("help_contents"); 0553 helpAction->disconnect(); 0554 connect(helpAction, SIGNAL(triggered()), this, SLOT(showManual())); 0555 0556 #if 0 0557 //check for colliding shortcuts 0558 QSet<QKeySequence> existingShortcuts; 0559 Q_FOREACH (QAction* action, actionCollection()->actions()) { 0560 if(action->shortcut() == QKeySequence(0)) { 0561 continue; 0562 } 0563 dbgKrita << "shortcut " << action->text() << " " << action->shortcut(); 0564 Q_ASSERT(!existingShortcuts.contains(action->shortcut())); 0565 existingShortcuts.insert(action->shortcut()); 0566 } 0567 #endif 0568 0569 configChanged(); 0570 0571 // Make sure the python plugins create their actions in time 0572 KisPart::instance()->notifyMainWindowIsBeingCreated(this); 0573 0574 // If we have customized the toolbars, load that first 0575 setLocalXMLFile(KoResourcePaths::locateLocal("data", "krita5.xmlgui")); 0576 setXMLFile(":/kxmlgui5/krita5.xmlgui"); 0577 0578 guiFactory()->addClient(this); 0579 connect(guiFactory(), SIGNAL(makingChanges(bool)), SLOT(slotXmlGuiMakingChanges(bool))); 0580 0581 // Create and plug toolbar list for Settings menu 0582 QList<QAction *> toolbarList; 0583 Q_FOREACH (QWidget* it, guiFactory()->containers("ToolBar")) { 0584 KisToolBar * toolBar = ::qobject_cast<KisToolBar *>(it); 0585 if (toolBar) { 0586 KToggleAction* act = new KToggleAction(i18n("Show %1 Toolbar", toolBar->windowTitle()), this); 0587 actionCollection()->addAction(toolBar->objectName().toUtf8(), act); 0588 act->setCheckedState(KGuiItem(i18n("Hide %1 Toolbar", toolBar->windowTitle()))); 0589 connect(act, SIGNAL(toggled(bool)), this, SLOT(slotToolbarToggled(bool))); 0590 act->setChecked(!toolBar->isHidden()); 0591 toolbarList.append(act); 0592 } else { 0593 warnUI << "Toolbar list contains a " << it->metaObject()->className() << " which is not a toolbar!"; 0594 } 0595 } 0596 0597 plugActionList("toolbarlist", toolbarList); 0598 d->toolbarList = toolbarList; 0599 0600 applyToolBarLayout(); 0601 0602 d->viewManager->updateGUI(); 0603 d->viewManager->updateIcons(); 0604 0605 QTimer::singleShot(1000, this, SLOT(checkSanity())); 0606 0607 { 0608 using namespace std::placeholders; // For _1 placeholder 0609 std::function<void (int)> callback( 0610 std::bind(&KisMainWindow::switchTab, this, _1)); 0611 0612 d->tabSwitchCompressor.reset( 0613 new KisSignalCompressorWithParam<int>(500, callback, KisSignalCompressor::FIRST_INACTIVE)); 0614 } 0615 0616 0617 if (cfg.readEntry("CanvasOnlyActive", false)) { 0618 QString currentWorkspace = cfg.readEntry<QString>("CurrentWorkspace", "Default"); 0619 KoResourceServer<KisWorkspaceResource> * rserver = KisResourceServerProvider::instance()->workspaceServer(); 0620 KisWorkspaceResourceSP workspace = rserver->resource("", "", currentWorkspace); 0621 if (workspace) { 0622 restoreWorkspace(workspace); 0623 } 0624 cfg.writeEntry("CanvasOnlyActive", false); 0625 menuBar()->setVisible(true); 0626 } 0627 0628 this->winId(); // Ensures the native window has been created. 0629 0630 #ifdef Q_OS_ANDROID 0631 // HACK: This prevents the mainWindow from going beyond the screen with no 0632 // way to bring it back. Apparently the size doesn't matter here as long as 0633 // it remains fixed? 0634 setFixedSize(KisApplication::primaryScreen()->availableGeometry().size()); 0635 0636 QScreen *s = QGuiApplication::primaryScreen(); 0637 s->setOrientationUpdateMask(Qt::LandscapeOrientation|Qt::InvertedLandscapeOrientation|Qt::PortraitOrientation|Qt::InvertedPortraitOrientation); 0638 connect(s, SIGNAL(orientationChanged(Qt::ScreenOrientation)), this, SLOT(orientationChanged())); 0639 0640 // When Krita starts, Java side sends an event to set applicationState() to active. But, before 0641 // the event could reach KisApplication's platform integration, it is cleared by KisOpenGLModeProber::probeFormat. 0642 // So, we send it manually when MainWindow shows up. 0643 QAndroidJniObject::callStaticMethod<void>("org/qtproject/qt5/android/QtNative", "setApplicationState", "(I)V", Qt::ApplicationActive); 0644 #endif 0645 0646 setAcceptDrops(true); 0647 QTabBar *tabBar = d->findTabBarHACK(); 0648 if (tabBar) { 0649 tabBar->setElideMode(Qt::ElideRight); 0650 // load customized tab style 0651 customizeTabBar(); 0652 tabBar->setExpanding(true); 0653 tabBar->setAcceptDrops(true); 0654 tabBar->setChangeCurrentOnDrag(true); 0655 } 0656 0657 0658 } 0659 0660 KisMainWindow::~KisMainWindow() 0661 { 0662 // Q_FOREACH (QAction *ac, actionCollection()->actions()) { 0663 // QAction *action = qobject_cast<QAction*>(ac); 0664 // if (action) { 0665 // qDebug() << "<Action" 0666 // << "\n\tname=" << action->objectName() 0667 // << "\n\ticon=" << action->icon().name() 0668 // << "\n\ttext=" << action->text().replace("&", "&") 0669 // << "\n\twhatsThis=" << action->whatsThis() 0670 // << "\n\ttoolTip=" << action->toolTip().replace("<html>", "").replace("</html>", "") 0671 // << "\n\ticonText=" << action->iconText().replace("&", "&") 0672 // << "\n\tshortcut=" << action->shortcut().toString() 0673 // << "\n\tisCheckable=" << QString((action->isChecked() ? "true" : "false")) 0674 // << "\n\tstatusTip=" << action->statusTip() 0675 // << "\n/>\n" ; 0676 // } 0677 // else { 0678 // dbgKrita << "Got a non-qaction:" << ac->objectName(); 0679 // } 0680 // } 0681 0682 // The doc and view might still exist (this is the case when closing the window) 0683 KisPart::instance()->removeMainWindow(this); 0684 0685 delete d->viewManager; 0686 delete d; 0687 0688 } 0689 0690 QMenu *KisMainWindow::createPopupMenu() 0691 { 0692 return 0; 0693 } 0694 0695 QUuid KisMainWindow::id() const { 0696 return d->id; 0697 } 0698 0699 void KisMainWindow::addView(KisView *view, QMdiSubWindow *subWindow) 0700 { 0701 if (d->activeView == view && !subWindow) return; 0702 0703 if (d->activeView) { 0704 d->activeView->disconnect(this); 0705 } 0706 0707 // register the newly created view in the input manager 0708 viewManager()->inputManager()->addTrackedCanvas(view->canvasBase()); 0709 0710 showView(view, subWindow); 0711 emit restoringDone(); 0712 0713 // QTabBar *tabBar = d->findTabBarHACK(); 0714 // Q_FOREACH(QObject *c, tabBar->children()) { 0715 // if (QWidget *w = qobject_cast<QWidget*>(c)) { 0716 // w->setAcceptDrops(true); 0717 // } 0718 // } 0719 } 0720 0721 void KisMainWindow::notifyChildViewDestroyed(KisView *view) 0722 { 0723 /** 0724 * If we are the last view of the window, Qt will not activate another tab 0725 * before destroying tab/window. In this case we should clear all the dangling 0726 * pointers manually by setting the current view to null 0727 */ 0728 viewManager()->inputManager()->removeTrackedCanvas(view->canvasBase()); 0729 if (view->canvasBase() == viewManager()->canvasBase()) { 0730 viewManager()->setCurrentView(0); 0731 } 0732 } 0733 0734 0735 void KisMainWindow::showView(KisView *imageView, QMdiSubWindow *subwin) 0736 { 0737 if (imageView && activeView() != imageView) { 0738 // XXX: find a better way to initialize this! 0739 imageView->setViewManager(d->viewManager); 0740 0741 imageView->canvasBase()->setFavoriteResourceManager(d->viewManager->paintOpBox()->favoriteResourcesManager()); 0742 imageView->slotLoadingFinished(); 0743 0744 if (!subwin) { 0745 QMdiSubWindow *currentSubWin = d->mdiArea->currentSubWindow(); 0746 bool shouldMaximize = currentSubWin ? currentSubWin->isMaximized() : true; 0747 subwin = d->mdiArea->addSubWindow(imageView); 0748 if (shouldMaximize) { 0749 subwin->setWindowState(Qt::WindowMaximized); 0750 } 0751 } else { 0752 subwin->setWidget(imageView); 0753 } 0754 imageView->setSubWindow(subwin); 0755 subwin->setAttribute(Qt::WA_DeleteOnClose, true); 0756 connect(subwin, SIGNAL(destroyed()), SLOT(updateWindowMenu())); 0757 0758 KisConfig cfg(true); 0759 subwin->setOption(QMdiSubWindow::RubberBandMove, cfg.readEntry<int>("mdi_rubberband", cfg.useOpenGL())); 0760 subwin->setOption(QMdiSubWindow::RubberBandResize, cfg.readEntry<int>("mdi_rubberband", cfg.useOpenGL())); 0761 subwin->setWindowIcon(qApp->windowIcon()); 0762 0763 #ifdef Q_OS_MACOS 0764 connect(subwin, SIGNAL(destroyed()), SLOT(updateSubwindowFlags())); 0765 updateSubwindowFlags(); 0766 #endif 0767 0768 if (d->mdiArea->subWindowList().size() == 1) { 0769 imageView->showMaximized(); 0770 } 0771 else { 0772 imageView->show(); 0773 } 0774 0775 /** 0776 * Hack alert! 0777 * 0778 * Here we explicitly request KoToolManager to emit all the tool 0779 * activation signals, to reinitialize the tool options docker. 0780 * 0781 * That is needed due to a design flaw we have in the 0782 * initialization procedure. The tool in the KoToolManager is 0783 * initialized in KisView::setViewManager() calls, which 0784 * happens early enough. During this call the tool manager 0785 * requests KoCanvasControllerWidget to emit the signal to 0786 * update the widgets in the tool docker. *But* at that moment 0787 * of time the view is not yet connected to the main window, 0788 * because it happens in KisViewManager::setCurrentView a bit 0789 * later. This fact makes the widgets updating signals be lost 0790 * and never reach the tool docker. 0791 * 0792 * So here we just explicitly call the tool activation stub. 0793 */ 0794 0795 KoToolManager::instance()->initializeCurrentToolForCanvas(); 0796 0797 // No, no, no: do not try to call this _before_ the show() has 0798 // been called on the view; only when that has happened is the 0799 // opengl context active, and very bad things happen if we tell 0800 // the dockers to update themselves with a view if the opengl 0801 // context is not active. 0802 setActiveView(imageView); 0803 0804 updateWindowMenu(); 0805 } else { 0806 unsetActiveView(); 0807 } 0808 } 0809 0810 void KisMainWindow::slotPreferences() 0811 { 0812 QScopedPointer<KisDlgPreferences> dlgPreferences(new KisDlgPreferences(this)); 0813 0814 if (dlgPreferences->editPreferences()) { 0815 KisConfigNotifier::instance()->notifyConfigChanged(); 0816 KisConfigNotifier::instance()->notifyPixelGridModeChanged(); 0817 KisImageConfigNotifier::instance()->notifyConfigChanged(); 0818 0819 // XXX: should this be changed for the views in other windows as well? 0820 Q_FOREACH (QPointer<KisView> koview, KisPart::instance()->views()) { 0821 KisViewManager *view = qobject_cast<KisViewManager*>(koview); 0822 if (view) { 0823 // Update the settings for all nodes -- they don't query 0824 // KisConfig directly because they need the settings during 0825 // compositing, and they don't connect to the config notifier 0826 // because nodes are not QObjects (because only one base class 0827 // can be a QObject). 0828 KisNode* node = dynamic_cast<KisNode*>(view->image()->rootLayer().data()); 0829 if (node) { 0830 node->updateSettings(); 0831 } 0832 } 0833 0834 } 0835 updateWindowMenu(); 0836 0837 d->viewManager->showHideScrollbars(); 0838 } 0839 } 0840 0841 void KisMainWindow::updateTheme() 0842 { 0843 // reload action icons! 0844 Q_FOREACH (QAction *action, actionCollection()->actions()) { 0845 KisIconUtils::updateIcon(action); 0846 } 0847 if (d->mdiArea) { 0848 d->mdiArea->setPalette(qApp->palette()); 0849 for (int i=0; i<d->mdiArea->subWindowList().size(); i++) { 0850 QMdiSubWindow *window = d->mdiArea->subWindowList().at(i); 0851 if (window) { 0852 window->setPalette(qApp->palette()); 0853 KisView *view = qobject_cast<KisView*>(window->widget()); 0854 if (view) { 0855 view->slotThemeChanged(qApp->palette()); 0856 } 0857 } 0858 } 0859 } 0860 0861 customizeTabBar(); 0862 0863 // Update toolbars 0864 { 0865 Q_FOREACH (KisToolBar* aToolBar, toolBars()) { 0866 QObjectList objects; 0867 objects.append(aToolBar); 0868 while (!objects.isEmpty()) { 0869 QWidget* widget = qobject_cast<QWidget*>(objects.takeFirst()); 0870 if (widget) { 0871 objects.append(widget->children()); 0872 widget->setPalette(qApp->palette()); 0873 } 0874 } 0875 } 0876 } 0877 } 0878 0879 void KisMainWindow::slotThemeChanged() 0880 { 0881 KConfigGroup group(KSharedConfig::openConfig(), "theme"); 0882 0883 if (group.readEntry("Theme", "") == d->themeManager->currentThemeName()) return; 0884 0885 // save theme changes instantly 0886 group.writeEntry("Theme", d->themeManager->currentThemeName()); 0887 0888 updateTheme(); 0889 0890 // Make the other top level windows update as well 0891 Q_FOREACH (QWidget* topLevelWidget, qApp->topLevelWidgets()) { 0892 if (topLevelWidget == this) { 0893 // Skip if the current top level widget is this window 0894 continue; 0895 } 0896 if (topLevelWidget->isHidden()) { 0897 // Skip unwanted top level widgets like menus, dropdowns, tooltips, etc. 0898 continue; 0899 } 0900 KisMainWindow *topLevelMainWindow = qobject_cast<KisMainWindow*>(topLevelWidget); 0901 if (topLevelMainWindow) { 0902 topLevelMainWindow->updateTheme(); 0903 emit topLevelMainWindow->themeChanged(); 0904 } else { 0905 QObjectList objects; 0906 objects.append(topLevelWidget); 0907 while (!objects.isEmpty()) { 0908 QWidget* widget = qobject_cast<QWidget*>(objects.takeFirst()); 0909 if (widget) { 0910 objects.append(widget->children()); 0911 KisIconUtils::updateIconCommon(widget); 0912 } 0913 } 0914 } 0915 } 0916 0917 emit themeChanged(); 0918 } 0919 0920 void KisMainWindow::customizeTabBar() 0921 { 0922 // update MDI area theme 0923 // Tab close button override 0924 // just switch this icon out for all OSs so it is easier to see 0925 QString closeButtonImageUrl; 0926 QString closeButtonHoverColor; 0927 if (KisIconUtils::useDarkIcons()) { 0928 closeButtonImageUrl = QStringLiteral(":/dark_close-tab.svg"); 0929 closeButtonHoverColor = QStringLiteral("lightcoral"); 0930 } else { 0931 closeButtonImageUrl = QStringLiteral(":/light_close-tab.svg"); 0932 closeButtonHoverColor = QStringLiteral("darkred"); 0933 } 0934 QString tabStyleSheet = QStringLiteral(R"( 0935 QTabBar::close-button { 0936 image: url(%1); 0937 padding-top: 3px; 0938 } 0939 QTabBar::close-button:hover { 0940 background-color: %2; 0941 } 0942 QTabBar::close-button:pressed { 0943 background-color: red; 0944 } 0945 0946 QHeaderView::section { 0947 padding: 7px; 0948 } 0949 0950 )") 0951 .arg(closeButtonImageUrl, closeButtonHoverColor); 0952 0953 0954 QTabBar* tabBar = d->findTabBarHACK(); 0955 if (tabBar) { 0956 tabBar->setStyleSheet(tabStyleSheet); 0957 } 0958 0959 } 0960 0961 bool KisMainWindow::canvasDetached() const 0962 { 0963 return centralWidget() != d->widgetStack; 0964 } 0965 0966 void KisMainWindow::setCanvasDetached(bool detach) 0967 { 0968 #ifdef Q_OS_ANDROID 0969 if (detach) { 0970 QMessageBox::warning(this, i18nc("@title:window", "Krita"), 0971 "Detach Canvas is unsupported on Android"); 0972 } 0973 #else 0974 if (detach == canvasDetached()) return; 0975 0976 QWidget *outgoingWidget = centralWidget() ? takeCentralWidget() : nullptr; 0977 QWidget *incomingWidget = d->canvasWindow->swapMainWidget(outgoingWidget); 0978 0979 if (incomingWidget) { 0980 setCentralWidget(incomingWidget); 0981 } 0982 0983 if (detach) { 0984 d->canvasWindow->show(); 0985 } else { 0986 d->canvasWindow->hide(); 0987 } 0988 d->toggleDetachCanvas->setChecked(detach); 0989 #endif 0990 } 0991 0992 QWidget * KisMainWindow::canvasWindow() const 0993 { 0994 return d->canvasWindow; 0995 } 0996 0997 void KisMainWindow::setReadWrite(bool readwrite) 0998 { 0999 d->saveAction->setEnabled(readwrite); 1000 d->importFile->setEnabled(readwrite); 1001 } 1002 1003 void KisMainWindow::clearRecentFiles() 1004 { 1005 KisRecentFilesManager::instance()->clear(); 1006 } 1007 1008 void KisMainWindow::removeRecentFile(QString url) 1009 { 1010 KisRecentFilesManager::instance()->remove(QUrl::fromLocalFile(url)); 1011 } 1012 1013 void KisMainWindow::slotUpdateSaveActionTitle(const QString &documentPath) 1014 { 1015 const QString fileName = QFileInfo(documentPath).fileName(); 1016 1017 if (!fileName.isEmpty()) { 1018 d->saveAction->setToolTip(i18n("Save as %1", fileName)); 1019 } 1020 else { 1021 d->saveAction->setToolTip(i18n("Save")); 1022 } 1023 } 1024 1025 KisView *KisMainWindow::activeView() const 1026 { 1027 if (d->activeView) { 1028 return d->activeView; 1029 } 1030 return 0; 1031 } 1032 1033 bool KisMainWindow::openDocument(const QString &path, OpenFlags flags) 1034 { 1035 ScopedWidgetDisabler disabler(d->welcomePage->dropFrameBorder); 1036 QApplication::processEvents(); // make UI more responsive 1037 1038 if (!QFile(path).exists()) { 1039 if (!(flags & BatchMode)) { 1040 QMessageBox::critical(qApp->activeWindow(), i18nc("@title:window", "Krita"), i18n("The file %1 does not exist.", path)); 1041 } 1042 KisRecentFilesManager::instance()->remove(QUrl::fromLocalFile(path)); //remove the file from the recent-opened-file-list 1043 return false; 1044 } 1045 return openDocumentInternal(path, flags); 1046 } 1047 1048 bool KisMainWindow::openDocumentInternal(const QString &path, OpenFlags flags) 1049 { 1050 if (!QFile(path).exists()) { 1051 qWarning() << "KisMainWindow::openDocumentInternal. Could not open:" << path; 1052 return false; 1053 } 1054 1055 KisDocument *newdoc = KisPart::instance()->createDocument(); 1056 1057 if (flags & BatchMode) { 1058 newdoc->setFileBatchMode(true); 1059 } 1060 1061 d->firstTime = true; 1062 connect(newdoc, SIGNAL(completed()), this, SLOT(slotLoadCompleted())); 1063 connect(newdoc, SIGNAL(canceled(QString)), this, SLOT(slotLoadCanceled(QString))); 1064 1065 KisDocument::OpenFlags openFlags = KisDocument::None; 1066 // XXX: Why this duplication of OpenFlags... 1067 if (flags & RecoveryFile) { 1068 openFlags |= KisDocument::RecoveryFile; 1069 } 1070 1071 bool openRet = !(flags & Import) ? newdoc->openPath(path, openFlags) : newdoc->importDocument(path); 1072 1073 if (!openRet) { 1074 delete newdoc; 1075 return false; 1076 } 1077 1078 KisPart::instance()->addDocument(newdoc); 1079 1080 // Try to determine whether this was an unnamed autosave 1081 if (flags & RecoveryFile && 1082 ( path.startsWith(QDir::tempPath()) 1083 || path.startsWith(QDir::homePath()) 1084 ) && 1085 ( QFileInfo(path).fileName().startsWith(".krita") 1086 || QFileInfo(path).fileName().startsWith("krita") 1087 ) 1088 ) 1089 { 1090 QString path = QStandardPaths::writableLocation(QStandardPaths::PicturesLocation); 1091 if (!QFileInfo(path).exists()) { 1092 path = QStandardPaths::writableLocation(QStandardPaths::HomeLocation); 1093 } 1094 newdoc->setPath(path + "/" + newdoc->objectName() + ".kra"); 1095 } 1096 1097 return true; 1098 } 1099 1100 void KisMainWindow::showDocument(KisDocument *document) { 1101 Q_FOREACH(QMdiSubWindow *subwindow, d->mdiArea->subWindowList()) { 1102 KisView *view = qobject_cast<KisView*>(subwindow->widget()); 1103 KIS_SAFE_ASSERT_RECOVER_NOOP(view); 1104 1105 if (view) { 1106 if (view->document() == document) { 1107 setActiveSubWindow(subwindow); 1108 return; 1109 } 1110 } 1111 } 1112 1113 addViewAndNotifyLoadingCompleted(document); 1114 } 1115 1116 KisView* KisMainWindow::addViewAndNotifyLoadingCompleted(KisDocument *document, 1117 QMdiSubWindow *subWindow) 1118 { 1119 showWelcomeScreen(false); // see workaround in function header 1120 1121 KisView *view = KisPart::instance()->createView(document, d->viewManager, this); 1122 addView(view, subWindow); 1123 1124 emit guiLoadingFinished(); 1125 1126 return view; 1127 } 1128 1129 QStringList KisMainWindow::showOpenFileDialog(bool isImporting) 1130 { 1131 KoFileDialog dialog(this, KoFileDialog::ImportFiles, "OpenDocument"); 1132 dialog.setDefaultDir(QStandardPaths::writableLocation(QStandardPaths::PicturesLocation)); 1133 dialog.setMimeTypeFilters(KisImportExportManager::supportedMimeTypes(KisImportExportManager::Import)); 1134 dialog.setCaption(isImporting ? i18n("Import Images") : i18n("Open Images")); 1135 1136 QStringList filenames = dialog.filenames(); 1137 filenames.sort(); 1138 1139 return filenames; 1140 } 1141 1142 // Separate from openDocument to handle async loading (remote URLs) 1143 void KisMainWindow::slotLoadCompleted() 1144 { 1145 KisDocument *newdoc = qobject_cast<KisDocument*>(sender()); 1146 if (newdoc && newdoc->image()) { 1147 addViewAndNotifyLoadingCompleted(newdoc); 1148 1149 disconnect(newdoc, SIGNAL(completed()), this, SLOT(slotLoadCompleted())); 1150 disconnect(newdoc, SIGNAL(canceled(QString)), this, SLOT(slotLoadCanceled(QString))); 1151 1152 emit loadCompleted(); 1153 } 1154 } 1155 1156 void KisMainWindow::slotLoadCanceled(const QString &errMsg) 1157 { 1158 KisUsageLogger::log(QString("Loading canceled: %1.").arg(errMsg)); 1159 KisDocument* doc = qobject_cast<KisDocument*>(sender()); 1160 Q_ASSERT(doc); 1161 disconnect(doc, SIGNAL(completed()), this, SLOT(slotLoadCompleted())); 1162 disconnect(doc, SIGNAL(canceled(QString)), this, SLOT(slotLoadCanceled(QString))); 1163 } 1164 1165 void KisMainWindow::slotSaveCanceled(const QString &errMsg) 1166 { 1167 if (!errMsg.isEmpty()) { // empty when cancelled by user 1168 KisUsageLogger::log(QString("Saving cancelled. Error:").arg(errMsg)); 1169 QMessageBox::critical(this, i18nc("@title:window", "Krita"), errMsg); 1170 } 1171 else { 1172 KisUsageLogger::log(QString("Saving cancelled by the user.")); 1173 } 1174 slotSaveCompleted(); 1175 } 1176 1177 void KisMainWindow::slotSaveCompleted() 1178 { 1179 KisUsageLogger::log(QString("Saving Completed")); 1180 KisDocument* doc = qobject_cast<KisDocument*>(sender()); 1181 if (doc) { 1182 disconnect(doc, SIGNAL(completed()), this, SLOT(slotSaveCompleted())); 1183 disconnect(doc, SIGNAL(canceled(QString)), this, SLOT(slotSaveCanceled(QString))); 1184 } 1185 1186 if (d->deferredClosingEvent) { 1187 KXmlGuiWindow::closeEvent(d->deferredClosingEvent); 1188 } 1189 } 1190 1191 bool KisMainWindow::hackIsSaving() const 1192 { 1193 std::unique_lock<QMutex> l(d->savingEntryMutex, std::try_to_lock); 1194 return !l.owns_lock(); 1195 } 1196 1197 bool KisMainWindow::installBundle(const QString &fileName) const 1198 { 1199 QFileInfo from(fileName); 1200 QFileInfo to(QStringLiteral("%1/%2").arg(KoResourcePaths::getAppDataLocation(), from.fileName())); 1201 if (to.exists()) { 1202 QFile::remove(to.canonicalFilePath()); 1203 } 1204 return QFile::copy(fileName, to.absoluteFilePath()); 1205 } 1206 1207 QImage KisMainWindow::layoutThumbnail() 1208 { 1209 int size = 256; 1210 qreal scale = qreal(size)/qreal(qMax(geometry().width(), geometry().height())); 1211 QImage layoutThumbnail = QImage(qRound(geometry().width()*scale), qRound(geometry().height()*scale), QImage::Format_ARGB32); 1212 QPainter gc(&layoutThumbnail); 1213 gc.fillRect(0, 0, layoutThumbnail.width(), layoutThumbnail.height(), this->palette().dark()); 1214 1215 Q_FOREACH(const QObject *child, children()) { 1216 if (child->isWidgetType()) { 1217 const QWidget *w = static_cast<const QWidget *>(child); 1218 1219 if (w->isVisible() && !w->property("_kis_excludeFromLayoutThumbnail").toBool()) { 1220 QRect wRect = QRectF(w->geometry().x()*scale 1221 , w->geometry().y()*scale 1222 , w->geometry().width()*scale 1223 , w->geometry().height()*scale 1224 ).toRect(); 1225 1226 wRect = wRect.intersected(layoutThumbnail.rect().adjusted(-1, -1, -1, -1)); 1227 1228 gc.setBrush(this->palette().window()); 1229 if (w == d->widgetStack) { 1230 gc.setBrush(d->mdiArea->background()); 1231 } 1232 gc.setPen(this->palette().windowText().color()); 1233 gc.drawRect(wRect); 1234 } 1235 } 1236 } 1237 gc.end(); 1238 return layoutThumbnail; 1239 } 1240 1241 bool KisMainWindow::saveDocument(KisDocument *document, bool saveas, bool isExporting, bool isAdvancedExporting ) 1242 { 1243 if (!document) { 1244 return true; 1245 } 1246 1247 /** 1248 * Make sure that we cannot enter this method twice! 1249 * 1250 * The lower level functions may call processEvents() so 1251 * double-entry is quite possible to achieve. Here we try to lock 1252 * the mutex, and if it is failed, just cancel saving. 1253 */ 1254 std::unique_lock<QMutex> l(d->savingEntryMutex, std::try_to_lock); 1255 if (!l.owns_lock()) return false; 1256 1257 // no busy wait for saving because it is dangerous! 1258 KisDelayedSaveDialog dlg(document->image(), KisDelayedSaveDialog::SaveDialog, 0, this); 1259 dlg.blockIfImageIsBusy(); 1260 1261 if (dlg.result() == KisDelayedSaveDialog::Rejected) { 1262 return false; 1263 } 1264 else if (dlg.result() == KisDelayedSaveDialog::Ignored) { 1265 QMessageBox::critical(qApp->activeWindow(), 1266 i18nc("@title:window", "Krita"), 1267 i18n("You are saving a file while the image is " 1268 "still rendering. The saved file may be " 1269 "incomplete or corrupted.\n\n" 1270 "Please select a location where the original " 1271 "file will not be overridden!")); 1272 1273 1274 saveas = true; 1275 } 1276 1277 if (document->isRecovered()) { 1278 saveas = true; 1279 } 1280 1281 if (document->path().isEmpty()) { 1282 saveas = true; 1283 } 1284 1285 connect(document, SIGNAL(completed()), this, SLOT(slotSaveCompleted())); 1286 connect(document, SIGNAL(canceled(QString)), this, SLOT(slotSaveCanceled(QString))); 1287 1288 QByteArray nativeFormat = document->nativeFormatMimeType(); 1289 QByteArray oldMimeFormat = document->mimeType(); 1290 1291 QUrl suggestedURL = QUrl::fromLocalFile(document->path()); 1292 1293 QStringList mimeFilter = KisImportExportManager::supportedMimeTypes(KisImportExportManager::Export); 1294 1295 mimeFilter = KisImportExportManager::supportedMimeTypes(KisImportExportManager::Export); 1296 if (!mimeFilter.contains(oldMimeFormat)) { 1297 dbgUI << "KisMainWindow::saveDocument no export filter for" << oldMimeFormat; 1298 1299 // --- don't setOutputMimeType in case the user cancels the Save As 1300 // dialog and then tries to just plain Save --- 1301 1302 // suggest a different filename extension (yes, we fortunately don't all live in a world of magic :)) 1303 QString suggestedFilename = QFileInfo(suggestedURL.toLocalFile()).completeBaseName(); 1304 1305 if (!suggestedFilename.isEmpty()) { // ".kra" looks strange for a name 1306 suggestedFilename = suggestedFilename + "." + KisMimeDatabase::suffixesForMimeType(KIS_MIME_TYPE).first(); 1307 suggestedURL = suggestedURL.adjusted(QUrl::RemoveFilename); 1308 suggestedURL.setPath(suggestedURL.path() + suggestedFilename); 1309 } 1310 1311 // force the user to choose outputMimeType 1312 saveas = true; 1313 } 1314 1315 bool ret = false; 1316 1317 if (document->path().isEmpty() || isExporting || saveas) { 1318 // if you're just File/Save As'ing to change filter options you 1319 // don't want to be reminded about overwriting files etc. 1320 bool justChangingFilterOptions = false; 1321 1322 KoFileDialog dialog(this, KoFileDialog::SaveFile, "SaveAs"); 1323 dialog.setCaption(isExporting ? i18n("Exporting") : i18n("Saving As")); 1324 1325 //qDebug() << ">>>>>" << isExporting << d->lastExportLocation << d->lastExportedFormat << QString::fromLatin1(document->mimeType()); 1326 1327 if (isExporting && !d->lastExportLocation.isEmpty() && !d->lastExportLocation.contains(QDir::tempPath())) { 1328 1329 // Use the location where we last exported to, if it's set, as the opening location for the file dialog 1330 QString proposedPath = QFileInfo(d->lastExportLocation).absolutePath(); 1331 // If the document doesn't have a filename yet, use the title 1332 QString proposedFileName = suggestedURL.isEmpty() ? document->documentInfo()->aboutInfo("title") : QFileInfo(suggestedURL.toLocalFile()).completeBaseName(); 1333 // Use the last mimetype we exported to by default 1334 QString proposedMimeType = d->lastExportedFormat.isEmpty() ? "" : d->lastExportedFormat; 1335 QString proposedExtension = KisMimeDatabase::suffixesForMimeType(proposedMimeType).first().remove("*,"); 1336 1337 // Set the default dir: this overrides the one loaded from the config file, since we're exporting and the lastExportLocation is not empty 1338 dialog.setDefaultDir(proposedPath + "/" + proposedFileName + "." + proposedExtension, true); 1339 dialog.setMimeTypeFilters(mimeFilter, proposedMimeType); 1340 } 1341 else { 1342 // Get the last used location for saving 1343 KConfigGroup group = KSharedConfig::openConfig()->group("File Dialogs"); 1344 QString proposedPath = group.readEntry("SaveAs", ""); 1345 // if that is empty, get the last used location for loading 1346 if (proposedPath.isEmpty()) { 1347 proposedPath = group.readEntry("OpenDocument", ""); 1348 } 1349 // If that is empty, too, use the Pictures location. 1350 if (proposedPath.isEmpty()) { 1351 proposedPath = QStandardPaths::writableLocation(QStandardPaths::PicturesLocation); 1352 } 1353 // But only use that if the suggestedUrl, that is, the document's own url is empty, otherwise 1354 // open the location where the document currently is. 1355 dialog.setDefaultDir(suggestedURL.isEmpty() ? proposedPath : suggestedURL.toLocalFile(), true); 1356 1357 // If exporting, default to all supported file types if user is exporting 1358 QByteArray default_mime_type = ""; 1359 if (!isExporting) { 1360 // otherwise use the document's mimetype, or if that is empty, kra, which is the safest. 1361 default_mime_type = document->mimeType().isEmpty() ? nativeFormat : document->mimeType(); 1362 } 1363 dialog.setMimeTypeFilters(mimeFilter, QString::fromLatin1(default_mime_type)); 1364 } 1365 1366 QString newFilePath = dialog.filename(); 1367 1368 if (document->documentInfo()->aboutInfo("title") == i18n("Unnamed")) { 1369 QString fn = newFilePath; 1370 QFileInfo info(fn); 1371 document->documentInfo()->setAboutInfo("title", info.completeBaseName()); 1372 } 1373 1374 QByteArray outputFormat = nativeFormat; 1375 1376 QString outputFormatString = KisMimeDatabase::mimeTypeForFile(newFilePath, false); 1377 outputFormat = outputFormatString.toLatin1(); 1378 1379 1380 if (!isExporting) { 1381 justChangingFilterOptions = (newFilePath == document->path()) && (outputFormat == document->mimeType()); 1382 } 1383 else { 1384 QString path = QFileInfo(d->lastExportLocation).absolutePath(); 1385 QString filename = QFileInfo(document->path()).completeBaseName(); 1386 justChangingFilterOptions = (QFileInfo(newFilePath).absolutePath() == path) 1387 && (QFileInfo(newFilePath).completeBaseName() == filename) 1388 && (outputFormat == d->lastExportedFormat); 1389 } 1390 1391 bool bOk = true; 1392 if (newFilePath.isEmpty()) { 1393 bOk = false; 1394 } 1395 1396 if (bOk) { 1397 bool wantToSave = true; 1398 1399 // don't change this line unless you know what you're doing :) 1400 if (!justChangingFilterOptions) { 1401 if (!document->isNativeFormat(outputFormat)) 1402 wantToSave = true; 1403 } 1404 1405 if (wantToSave) { 1406 if (!isExporting) { // Save As 1407 ret = document->saveAs(newFilePath, outputFormat, true); 1408 if (ret) { 1409 dbgUI << "Successful Save As!"; 1410 KisPart::instance()->queueAddRecentURLToAllMainWindowsOnFileSaved(QUrl::fromLocalFile(newFilePath)); 1411 setReadWrite(true); 1412 } else { 1413 dbgUI << "Failed Save As!"; 1414 1415 } 1416 } 1417 else { // Export 1418 ret = document->exportDocument(newFilePath, outputFormat, isAdvancedExporting); 1419 if (ret) { 1420 d->lastExportLocation = newFilePath; 1421 d->lastExportedFormat = outputFormat; 1422 } 1423 } 1424 1425 } // if (wantToSave) { 1426 else 1427 ret = false; 1428 } // if (bOk) { 1429 else 1430 ret = false; 1431 } else { // saving 1432 // We cannot "export" into the currently 1433 // opened document. We are not Gimp. 1434 KIS_ASSERT_RECOVER_NOOP(!isExporting); 1435 1436 // be sure document has the correct outputMimeType! 1437 if (document->isModified()) { 1438 ret = document->save(true, 0); 1439 } 1440 1441 if (!ret) { 1442 dbgUI << "Failed Save!"; 1443 } 1444 } 1445 1446 return ret; 1447 } 1448 1449 void KisMainWindow::undo() 1450 { 1451 if (activeView()) { 1452 activeView()->document()->undoStack()->undo(); 1453 } 1454 } 1455 1456 void KisMainWindow::redo() 1457 { 1458 if (activeView()) { 1459 activeView()->document()->undoStack()->redo(); 1460 } 1461 } 1462 1463 void KisMainWindow::closeEvent(QCloseEvent *e) 1464 { 1465 if (hackIsSaving()) { 1466 e->setAccepted(false); 1467 return; 1468 } 1469 1470 if (!KisPart::instance()->closingSession()) { 1471 QAction *action= d->viewManager->actionCollection()->action("view_show_canvas_only"); 1472 if ((action) && (action->isChecked())) { 1473 action->setChecked(false); 1474 } 1475 1476 // Save session when last window is closed 1477 if (KisPart::instance()->mainwindowCount() == 1) { 1478 bool closeAllowed = KisPart::instance()->closeSession(); 1479 1480 if (!closeAllowed) { 1481 e->setAccepted(false); 1482 return; 1483 } 1484 } 1485 } 1486 1487 d->mdiArea->closeAllSubWindows(); 1488 1489 QList<QMdiSubWindow*> childrenList = d->mdiArea->subWindowList(); 1490 1491 if (childrenList.isEmpty()) { 1492 // TODO: that assignment looks really suspicious, because `e` 1493 // is destroyed right after the exit of this function 1494 d->deferredClosingEvent = e; 1495 d->canvasWindow->close(); 1496 } else { 1497 e->setAccepted(false); 1498 } 1499 } 1500 1501 void KisMainWindow::saveWindowSettings() 1502 { 1503 KSharedConfigPtr config = KSharedConfig::openConfig(); 1504 1505 if (d->windowSizeDirty ) { 1506 dbgUI << "KisMainWindow::saveWindowSettings"; 1507 KConfigGroup group = d->windowStateConfig; 1508 KWindowConfig::saveWindowSize(windowHandle(), group); 1509 config->sync(); 1510 d->windowSizeDirty = false; 1511 } 1512 1513 if (!d->activeView || d->activeView->document()) { 1514 1515 // Save toolbar position into the config file of the app, under the doc's component name 1516 KConfigGroup group = d->windowStateConfig; 1517 saveMainWindowSettings(group); 1518 1519 // Save state of dock widgets 1520 for (QMap<QString, QDockWidget*>::const_iterator i = d->dockWidgetsMap.constBegin(); 1521 i != d->dockWidgetsMap.constEnd(); ++i) { 1522 if (i.value()->widget()) { 1523 KConfigGroup dockGroup = group.group(QString("DockWidget ") + i.key()); 1524 dockGroup.writeEntry("Locked", i.value()->property("Locked").toBool()); 1525 dockGroup.writeEntry("DockArea", (int) dockWidgetArea(i.value())); 1526 dockGroup.writeEntry("xPosition", (int) i.value()->widget()->x()); 1527 dockGroup.writeEntry("yPosition", (int) i.value()->widget()->y()); 1528 1529 dockGroup.writeEntry("width", (int) i.value()->widget()->width()); 1530 dockGroup.writeEntry("height", (int) i.value()->widget()->height()); 1531 } 1532 } 1533 1534 } 1535 1536 KSharedConfig::openConfig()->sync(); 1537 resetAutoSaveSettings(); // Don't let KisKMainWindow override the good stuff we wrote down 1538 1539 } 1540 1541 void KisMainWindow::resizeEvent(QResizeEvent * e) 1542 { 1543 d->windowSizeDirty = true; 1544 KXmlGuiWindow::resizeEvent(e); 1545 } 1546 1547 1548 void KisMainWindow::dragMoveEvent(QDragMoveEvent *event) 1549 { 1550 dragMove(event); 1551 event->accept(); 1552 } 1553 1554 void KisMainWindow::dragLeaveEvent(QDragLeaveEvent *event) 1555 { 1556 dragLeave(); 1557 event->accept(); 1558 } 1559 1560 bool KisMainWindow::windowsLayoutSavingAllowed() const 1561 { 1562 QAction *action= d->viewManager->actionCollection()->action("view_show_canvas_only"); 1563 return !action || !action->isChecked(); 1564 } 1565 1566 void KisMainWindow::showEvent(QShowEvent *event) 1567 { 1568 // we're here because, we need to make sure everything (dockers, toolbars etc) is loaded and ready before 1569 // we can hide it. 1570 if (!event->spontaneous()) { 1571 setMainWindowLayoutForCurrentMainWidget(d->widgetStack->currentIndex(), false); 1572 } 1573 return KXmlGuiWindow::showEvent(event); 1574 } 1575 1576 void KisMainWindow::setMainWindowLayoutForCurrentMainWidget(int widgetIndex, bool widgetIndexChanged) 1577 { 1578 1579 if (widgetIndex == 0) { 1580 if (widgetIndexChanged) { 1581 /// save the state of the window which existed up-to now (this is before we stop auto-saving). 1582 /// 1583 /// saving should happen if the call s has come from a real state change, but not from 1584 /// on-startup initialization 1585 1586 if (d->mdiArea->subWindowList().isEmpty()) { 1587 /** 1588 * When closing the latest subwindow we should restore the 1589 * canvas-only mode to save stuff properly 1590 */ 1591 saveWindowState(true); 1592 } else { 1593 saveMainWindowSettings(d->windowStateConfig); 1594 } 1595 } 1596 adjustLayoutForWelcomePage(); 1597 } 1598 else { 1599 setAutoSaveSettings(d->windowStateConfig, false); 1600 statusBar()->setVisible(KisConfig(true).showStatusBar()); 1601 } 1602 1603 QList<QAction *> actions = d->dockWidgetMenu->menu()->actions(); 1604 actions.append(toolBarMenuAction()->menu()->actions()); 1605 for (QAction *action : actions) { 1606 if (action) { 1607 action->setEnabled(widgetIndex); 1608 } 1609 } 1610 } 1611 1612 void KisMainWindow::adjustLayoutForWelcomePage() 1613 { 1614 // This makes sure we don't save window state when we're in welcome page mode, because all the dockers 1615 // etc are hidden while the user is here. 1616 resetAutoSaveSettings(); 1617 1618 toggleDockersVisibility(false, true); 1619 if (statusBar()) { 1620 statusBar()->hide(); 1621 } 1622 QList<QToolBar *> toolbars = findChildren<QToolBar *>(); 1623 for (QToolBar *toolbar : toolbars) { 1624 if (toolbar->objectName() == "BrushesAndStuff" || toolbar->objectName() == "editToolBar") { 1625 toolbar->hide(); 1626 } 1627 } 1628 } 1629 1630 void KisMainWindow::setActiveView(KisView* view) 1631 { 1632 d->activeView = view; 1633 1634 if (d->undoActionsUpdateManager) { 1635 d->undoActionsUpdateManager->setCurrentDocument(view ? view->document() : 0); 1636 } 1637 1638 d->viewManager->setCurrentView(view); 1639 1640 d->activeViewConnections.clear(); 1641 d->activeViewConnections.addConnection(view->document(), 1642 SIGNAL(sigPathChanged(QString)), 1643 this, SLOT(slotUpdateSaveActionTitle(QString))); 1644 slotUpdateSaveActionTitle(view->document()->path()); 1645 1646 KisWindowLayoutManager::instance()->activeDocumentChanged(view->document()); 1647 1648 emit activeViewChanged(); 1649 } 1650 1651 void KisMainWindow::unsetActiveView() 1652 { 1653 d->activeViewConnections.clear(); 1654 slotUpdateSaveActionTitle(QString()); 1655 } 1656 1657 void KisMainWindow::dragMove(QDragMoveEvent * event) 1658 { 1659 QTabBar *tabBar = d->findTabBarHACK(); 1660 1661 if (!tabBar && d->mdiArea->viewMode() == QMdiArea::TabbedView) { 1662 qWarning() << "WARNING!!! Cannot find QTabBar in the main window! Looks like Qt has changed behavior. Drag & Drop between multiple tabs might not work properly (tabs will not switch automatically)!"; 1663 } 1664 1665 if (tabBar && tabBar->isVisible()) { 1666 QPoint pos = tabBar->mapFromGlobal(mapToGlobal(event->pos())); 1667 if (tabBar->rect().contains(pos)) { 1668 const int tabIndex = tabBar->tabAt(pos); 1669 1670 if (tabIndex >= 0 && tabBar->currentIndex() != tabIndex) { 1671 d->tabSwitchCompressor->start(tabIndex); 1672 } 1673 } else if (d->tabSwitchCompressor->isActive()) { 1674 d->tabSwitchCompressor->stop(); 1675 } 1676 } 1677 } 1678 1679 void KisMainWindow::dragLeave() 1680 { 1681 if (d->tabSwitchCompressor->isActive()) { 1682 d->tabSwitchCompressor->stop(); 1683 } 1684 } 1685 1686 1687 void KisMainWindow::switchTab(int index) 1688 { 1689 QTabBar *tabBar = d->findTabBarHACK(); 1690 if (!tabBar) return; 1691 1692 tabBar->setCurrentIndex(index); 1693 } 1694 1695 void KisMainWindow::showWelcomeScreen(bool show) 1696 { 1697 const int currentIndex = show ? 0 : 1; 1698 if (d->widgetStack->currentIndex() != currentIndex) { 1699 setUpdatesEnabled(false); 1700 // These have to be done in different sequence to avoid graphical 1701 // layout glitch during the switch. 1702 if (show) { 1703 setMainWindowLayoutForCurrentMainWidget(currentIndex, true); 1704 d->widgetStack->setCurrentIndex(currentIndex); 1705 } else { 1706 d->widgetStack->setCurrentIndex(currentIndex); 1707 setMainWindowLayoutForCurrentMainWidget(currentIndex, true); 1708 } 1709 setUpdatesEnabled(true); 1710 } 1711 } 1712 1713 void KisMainWindow::slotFileNew() 1714 { 1715 const QStringList mimeFilter = KisImportExportManager::supportedMimeTypes(KisImportExportManager::Import); 1716 1717 KisOpenPane *startupWidget = new KisOpenPane(this, mimeFilter, QStringLiteral("templates/")); 1718 startupWidget->setWindowModality(Qt::WindowModal); 1719 startupWidget->setWindowTitle(i18n("Create new document")); 1720 1721 1722 KisConfig cfg(true); 1723 1724 int w = cfg.defImageWidth(); 1725 int h = cfg.defImageHeight(); 1726 const double resolution = cfg.defImageResolution(); 1727 const QString colorModel = cfg.defColorModel(); 1728 const QString colorDepth = cfg.defaultColorDepth(); 1729 const QString colorProfile = cfg.defColorProfile(); 1730 1731 1732 CustomDocumentWidgetItem item; 1733 item.widget = new KisCustomImageWidget(startupWidget, 1734 w, 1735 h, 1736 resolution, 1737 colorModel, 1738 colorDepth, 1739 colorProfile, 1740 i18n("Unnamed")); 1741 1742 item.icon = "document-new"; 1743 item.title = i18n("Custom Document"); 1744 startupWidget->addCustomDocumentWidget(item.widget, item.title, "Custom Document", item.icon); 1745 1746 item.widget = new KisImageFromClipboardWidget(startupWidget, 1747 0, 1748 0, 1749 resolution, 1750 colorModel, 1751 colorDepth, 1752 colorProfile, 1753 i18n("Unnamed")); 1754 1755 item.title = i18n("Create from Clipboard"); 1756 item.icon = "tab-new"; 1757 1758 startupWidget->addCustomDocumentWidget(item.widget, item.title, "Create from ClipBoard", item.icon); 1759 1760 connect(startupWidget, SIGNAL(documentSelected(KisDocument*)), KisPart::instance(), SLOT(startCustomDocument(KisDocument*))); 1761 connect(startupWidget, SIGNAL(openTemplate(QUrl)), KisPart::instance(), SLOT(openTemplate(QUrl))); 1762 1763 startupWidget->exec(); 1764 startupWidget->deleteLater(); 1765 } 1766 1767 void KisMainWindow::slotImportFile() 1768 { 1769 dbgUI << "slotImportFile()"; 1770 slotFileOpen(true); 1771 } 1772 1773 1774 void KisMainWindow::slotFileOpen(bool isImporting) 1775 { 1776 QStringList urls = showOpenFileDialog(isImporting); 1777 1778 if (urls.isEmpty()) 1779 return; 1780 1781 Q_FOREACH (const QString& url, urls) { 1782 1783 if (!url.isEmpty()) { 1784 OpenFlags flags = isImporting ? Import : None; 1785 bool res = openDocument(url, flags); 1786 if (!res) { 1787 warnKrita << "Loading" << url << "failed"; 1788 } 1789 } 1790 } 1791 } 1792 1793 void KisMainWindow::slotFileOpenRecent(const QUrl &url) 1794 { 1795 (void) openDocument(url.toLocalFile(), None); 1796 } 1797 1798 void KisMainWindow::slotFileSave() 1799 { 1800 if (saveDocument(d->activeView->document(), false, false,false)) { 1801 emit documentSaved(); 1802 } 1803 } 1804 1805 void KisMainWindow::slotFileSaveAs() 1806 { 1807 if (saveDocument(d->activeView->document(), true, false,false)) { 1808 emit documentSaved(); 1809 } 1810 } 1811 1812 void KisMainWindow::slotExportFile() 1813 { 1814 if (saveDocument(d->activeView->document(), true, true,false)) { 1815 emit documentSaved(); 1816 } 1817 } 1818 void KisMainWindow::slotExportAdvance() 1819 { 1820 if (saveDocument(d->activeView->document(), true, true,true)) { 1821 emit documentSaved(); 1822 } 1823 } 1824 1825 void KisMainWindow::slotShowSessionManager() { 1826 KisPart::instance()->showSessionManager(); 1827 } 1828 1829 KoCanvasResourceProvider *KisMainWindow::resourceManager() const 1830 { 1831 return d->viewManager->canvasResourceProvider()->resourceManager(); 1832 } 1833 1834 int KisMainWindow::viewCount() const 1835 { 1836 return d->mdiArea->subWindowList().size(); 1837 } 1838 1839 const KConfigGroup &KisMainWindow::windowStateConfig() const 1840 { 1841 return d->windowStateConfig; 1842 } 1843 1844 void KisMainWindow::saveWindowState(bool restoreNormalState) 1845 { 1846 // We don't need to save welcome page's layout 1847 if (d->widgetStack->currentIndex() == 0) { 1848 // TODO(sh_zam): We should still save position/geometry, right? 1849 return; 1850 } 1851 1852 if (restoreNormalState) { 1853 QAction *showCanvasOnly = d->viewManager->actionCollection()->action("view_show_canvas_only"); 1854 1855 if (showCanvasOnly && showCanvasOnly->isChecked()) { 1856 showCanvasOnly->setChecked(false); 1857 } 1858 1859 d->windowStateConfig.writeEntry("ko_geometry", saveGeometry().toBase64()); 1860 d->windowStateConfig.writeEntry("State", saveState().toBase64()); 1861 1862 // if the dockers are hidden at this time, save their state. 1863 if (!d->toggleDockers->isChecked()) { 1864 restoreState(d->dockerStateBeforeHiding); 1865 } 1866 1867 statusBar()->setVisible(true); 1868 menuBar()->setVisible(true); 1869 1870 saveWindowSettings(); 1871 1872 } else { 1873 saveMainWindowSettings(d->windowStateConfig); 1874 } 1875 1876 } 1877 1878 bool KisMainWindow::restoreWorkspaceState(const QByteArray &state) 1879 { 1880 QByteArray oldState = saveState(); 1881 const bool showTitlebars = KisConfig(false).showDockerTitleBars(); 1882 1883 // needed because otherwise the layout isn't correctly restored in some situations 1884 Q_FOREACH (QDockWidget *dock, dockWidgets()) { 1885 if (dock) { 1886 dock->setProperty("Locked", false); // Unlock invisible dockers 1887 dock->toggleViewAction()->setEnabled(true); 1888 dock->hide(); 1889 if (dock->titleBarWidget() && !dock->titleBarWidget()->inherits("KisUtilityTitleBar")) { 1890 dock->titleBarWidget()->setVisible(showTitlebars); 1891 } 1892 } 1893 } 1894 1895 bool success = KXmlGuiWindow::restoreState(state); 1896 1897 if (!success) { 1898 KXmlGuiWindow::restoreState(oldState); 1899 Q_FOREACH (QDockWidget *dock, dockWidgets()) { 1900 if (dock->titleBarWidget() && !dock->titleBarWidget()->inherits("KisUtilityTitleBar")) { 1901 dock->titleBarWidget()->setVisible(showTitlebars || dock->isFloating()); 1902 } 1903 } 1904 return false; 1905 } 1906 return success; 1907 } 1908 1909 void KisMainWindow::restoreWorkspace() 1910 { 1911 QString md5 = sender()->property("md5").toString(); 1912 KoResourceServer<KisWorkspaceResource> *rserver = KisResourceServerProvider::instance()->workspaceServer(); 1913 KoResourceSP resource = rserver->resource(md5, "", ""); 1914 if (resource) { 1915 restoreWorkspace(resource); 1916 } 1917 else { 1918 qWarning() << "Could not retrieve resource for" << md5; 1919 } 1920 } 1921 1922 void KisMainWindow::openCommandBar() 1923 { 1924 QList<KisKActionCollection *> actionCollections; 1925 1926 auto clients = guiFactory()->clients(); 1927 int actionsCount = 0; 1928 for (const KisKXMLGUIClient *c : clients) { 1929 if (!c) { 1930 continue; 1931 } 1932 if (auto collection = c->actionCollection()) { 1933 actionCollections.append(collection); 1934 actionsCount += collection->count(); 1935 } 1936 } 1937 1938 if (activeKisView()) { 1939 KisKActionCollection *layerActionCollection = new KisKActionCollection(0, "layeractions (disposable)"); 1940 layerActionCollection->setComponentDisplayName(i18n("Layers/Masks")); 1941 KisNodeActivationActionCreatorVisitor v(layerActionCollection, viewManager()->nodeManager()); 1942 activeKisView()->image()->rootLayer()->accept(v); 1943 actionCollections.append(layerActionCollection); 1944 actionsCount += layerActionCollection->count(); 1945 } 1946 1947 d->commandBar->updateBar(actionCollections, actionsCount); 1948 1949 // The following line is needed to work around input method not working 1950 // on Windows. 1951 // See https://bugs.kde.org/show_bug.cgi?id=395598 1952 // and https://bugs.kde.org/show_bug.cgi?id=438122 1953 d->commandBar->activateWindow(); 1954 1955 // The following line is present in Kate's version and was ported over 1956 // but I am sceptical of its use. I worry that it may subtly cause other 1957 // issues, and since the command bar appears to work fine without it, I 1958 // believe it may be better to leave it out. -- Alvin 1959 // centralWidget()->setFocusProxy(d->commandBar); 1960 } 1961 1962 void KisMainWindow::slotStoragesWarning(const QString &/*location*/) 1963 { 1964 QString warning; 1965 if (!checkActiveBundlesAvailable()) { 1966 warning = i18n("You don't have any resource bundles enabled."); 1967 } 1968 1969 if (!checkPaintOpAvailable()) { 1970 warning += i18n("\nThere are no brush presets available. Please enable a bundle that has presets before continuing.\n"); 1971 QMessageBox::critical(this, i18nc("@title:window", "Krita"), warning); 1972 1973 QAction *action = actionCollection()->action("manage_bundles"); 1974 if (action) { 1975 action->trigger(); 1976 } 1977 } 1978 1979 if (!checkActiveBundlesAvailable()) { 1980 QMessageBox::warning(this, i18nc("@title:window", "Krita"), warning + i18n("\nOnly your local resources are available.")); 1981 } 1982 1983 } 1984 1985 bool KisMainWindow::restoreWorkspace(KoResourceSP res) 1986 { 1987 KisWorkspaceResourceSP workspace = res.dynamicCast<KisWorkspaceResource>(); 1988 1989 bool success = restoreWorkspaceState(workspace->dockerState()); 1990 1991 const bool showTitlebars = KisConfig(false).showDockerTitleBars(); 1992 Q_FOREACH (QDockWidget *dock, dockWidgets()) { 1993 if (dock->titleBarWidget()) { 1994 dock->titleBarWidget()->setVisible(showTitlebars || dock->isFloating()); 1995 } 1996 } 1997 1998 if (activeKisView()) { 1999 activeKisView()->resourceProvider()->notifyLoadingWorkspace(workspace); 2000 } 2001 2002 d->viewManager->notifyWorkspaceLoaded(); 2003 2004 return success; 2005 } 2006 2007 QByteArray KisMainWindow::borrowWorkspace(KisMainWindow *other) 2008 { 2009 QByteArray currentWorkspace = saveState(); 2010 2011 if (!d->workspaceBorrowedBy.isNull()) { 2012 if (other->id() == d->workspaceBorrowedBy) { 2013 // We're swapping our original workspace back 2014 d->workspaceBorrowedBy = QUuid(); 2015 return currentWorkspace; 2016 } else { 2017 // Get our original workspace back before swapping with a third window 2018 KisMainWindow *borrower = KisPart::instance()->windowById(d->workspaceBorrowedBy); 2019 if (borrower) { 2020 QByteArray originalLayout = borrower->borrowWorkspace(this); 2021 borrower->restoreWorkspaceState(currentWorkspace); 2022 2023 d->workspaceBorrowedBy = other->id(); 2024 return originalLayout; 2025 } 2026 } 2027 } 2028 2029 d->workspaceBorrowedBy = other->id(); 2030 return currentWorkspace; 2031 } 2032 2033 void KisMainWindow::swapWorkspaces(KisMainWindow *a, KisMainWindow *b) 2034 { 2035 QByteArray workspaceA = a->borrowWorkspace(b); 2036 QByteArray workspaceB = b->borrowWorkspace(a); 2037 2038 a->restoreWorkspaceState(workspaceB); 2039 b->restoreWorkspaceState(workspaceA); 2040 } 2041 2042 KisViewManager *KisMainWindow::viewManager() const 2043 { 2044 return d->viewManager; 2045 } 2046 2047 void KisMainWindow::slotDocumentInfo() 2048 { 2049 if (!d->activeView->document()) 2050 return; 2051 2052 KoDocumentInfo *docInfo = d->activeView->document()->documentInfo(); 2053 2054 if (!docInfo) 2055 return; 2056 2057 KoDocumentInfoDlg *dlg = d->activeView->document()->createDocumentInfoDialog(this, docInfo); 2058 2059 if (dlg->exec()) { 2060 if (dlg->isDocumentSaved()) { 2061 d->activeView->document()->setModified(false); 2062 } else { 2063 d->activeView->document()->setModified(true); 2064 } 2065 } 2066 2067 delete dlg; 2068 } 2069 2070 bool KisMainWindow::slotFileCloseAll() 2071 { 2072 Q_FOREACH (QMdiSubWindow *subwin, d->mdiArea->subWindowList()) { 2073 if (subwin) { 2074 if(!subwin->close()) 2075 return false; 2076 } 2077 } 2078 2079 return true; 2080 } 2081 2082 void KisMainWindow::slotFileQuit() 2083 { 2084 // Do not close while KisMainWindow has the savingEntryMutex locked, bug409395. 2085 // After the background saving job is initiated, KisDocument blocks closing 2086 // while it saves itself. 2087 if (hackIsSaving()) { 2088 return; 2089 } 2090 KisPart::instance()->closeSession(); 2091 } 2092 2093 void KisMainWindow::importAnimation() 2094 { 2095 if (!activeView()) return; 2096 2097 KisDocument *document = activeView()->document(); 2098 if (!document) return; 2099 2100 KisDlgImportImageSequence dlg(this, document); 2101 2102 if (dlg.exec() == QDialog::Accepted) { 2103 QStringList files = dlg.files(); 2104 int firstFrame = dlg.firstFrame(); 2105 int step = dlg.step(); 2106 bool startFrom1 = dlg.startFrom1(); 2107 bool autoAddHoldframes = dlg.autoAddHoldframes(); 2108 2109 2110 KoUpdaterPtr updater = 2111 !document->fileBatchMode() ? viewManager()->createUnthreadedUpdater(i18n("Import frames")) : 0; 2112 KisAnimationImporter importer(document->image(), updater); 2113 int isAscending = dlg.isAscending(); 2114 KisImportExportErrorCode status = importer.import(files, firstFrame, step, autoAddHoldframes, startFrom1, isAscending); // modify here, add a flag 2115 2116 if (!status.isOk() && !status.isInternalError()) { 2117 QString msg = status.errorMessage(); 2118 if (!msg.isEmpty()) 2119 QMessageBox::critical(qApp->activeWindow(), i18nc("@title:window", "Krita"), i18n("Could not finish import animation:\n%1", msg)); 2120 } 2121 activeView()->canvasBase()->refetchDataFromImage(); 2122 } 2123 } 2124 2125 void KisMainWindow::importVideoAnimation() 2126 { 2127 KisDocument *document; 2128 KisDlgImportVideoAnimation dlg(this, activeView()); 2129 2130 if (dlg.exec() == QDialog::Accepted) { 2131 const QTemporaryDir outputLocation(QDir::tempPath() + QDir::separator() + "krita" + QDir::separator() + "import_files"); 2132 RenderedFrames renderedFrames = dlg.renderFrames(QDir(outputLocation.path())); 2133 dbgFile << "Frames rendered to directory: " << outputLocation.path(); 2134 QStringList documentInfoList = dlg.documentInfo(); 2135 2136 if (renderedFrames.isEmpty()) return; 2137 2138 dbgFile << "Animation Import options: " << documentInfoList; 2139 2140 int firstFrame = 0; 2141 const int step = documentInfoList[0].toInt(); 2142 const int fps = documentInfoList[1].toInt(); 2143 const int totalFrames = renderedFrames.framesNeedRelocation() ? (renderedFrames.renderedFrameTargetTimes.last() + 1) : renderedFrames.size() * step; 2144 const QString name = QFileInfo(documentInfoList[3]).fileName(); 2145 const bool useCurrentDocument = documentInfoList[4].toInt(); 2146 bool useDocumentColorSpace = false; 2147 2148 if ( useCurrentDocument ) { 2149 document = activeView()->document(); 2150 2151 dbgFile << "Current frames:" << document->image()->animationInterface()->totalLength() << "total frames:" << totalFrames; 2152 if ( document->image()->animationInterface()->totalLength() < totalFrames ) { 2153 document->image()->animationInterface()->setDocumentRangeStartFrame(0); 2154 document->image()->animationInterface()->setDocumentRangeEndFrame(totalFrames); 2155 } 2156 2157 } else { 2158 2159 const int width = documentInfoList[5].toInt(); 2160 const int height = documentInfoList[6].toInt(); 2161 const double resolution = documentInfoList[7].toDouble(); 2162 2163 const QString colorModel = documentInfoList[8]; 2164 const QString colorDepth = documentInfoList[9]; 2165 const QString profile = documentInfoList[10]; 2166 useDocumentColorSpace = profile != "Default"; 2167 2168 document = KisPart::instance()->createDocument(); 2169 document->setObjectName(name); 2170 2171 KisPart::instance()->addDocument(document, false); 2172 const KoColorSpace *cs = KoColorSpaceRegistry::instance()->colorSpace(colorModel, colorDepth, profile); 2173 Q_ASSERT(cs); 2174 2175 QColor qc(Qt::white); 2176 qc.setAlpha(0); 2177 KoColor bgColor(qc, cs); 2178 2179 if (!document->newImage(name, width, height, cs, bgColor, KisConfig::RASTER_LAYER, 1, "", double(resolution / 72) )) { 2180 QMessageBox::critical(qApp->activeWindow(), i18nc("@title:window", "Krita"), i18n("Failed to create new document. Animation import aborted.")); 2181 return; 2182 } 2183 2184 document->image()->animationInterface()->setFramerate(fps); 2185 document->image()->animationInterface()->setDocumentRangeStartFrame(0); 2186 document->image()->animationInterface()->setDocumentRangeEndFrame(totalFrames); 2187 2188 2189 this->showDocument(document); 2190 2191 } 2192 2193 KoUpdaterPtr updater = 2194 !document->fileBatchMode() ? viewManager()->createUnthreadedUpdater(i18n("Import frames")) : 0; 2195 KisAnimationImporter importer(document->image(), updater); 2196 KisImportExportErrorCode status = importer.import(renderedFrames.renderedFrameFiles, firstFrame, step, false, false, 0, useDocumentColorSpace, renderedFrames.renderedFrameTargetTimes); 2197 2198 if (!status.isOk() && !status.isInternalError()) { 2199 QString msg = status.errorMessage(); 2200 if (!msg.isEmpty()) 2201 QMessageBox::critical(qApp->activeWindow(), i18nc("@title:window", "Krita"), i18n("Could not finish import animation:\n%1", msg)); 2202 } 2203 2204 activeView()->canvasBase()->refetchDataFromImage(); 2205 document->image()->refreshGraph(); 2206 } 2207 } 2208 2209 void KisMainWindow::renderAnimation() 2210 { 2211 if (!activeView()) return; 2212 2213 KisImageSP image = viewManager()->image(); 2214 2215 if (!image) return; 2216 if (!image->animationInterface()->hasAnimation()) return; 2217 2218 KisDocument *doc = viewManager()->document(); 2219 2220 KisDlgAnimationRenderer dlgAnimationRenderer(doc, viewManager()->mainWindow()); 2221 dlgAnimationRenderer.setCaption(i18n("Render Animation")); 2222 if (dlgAnimationRenderer.exec() == QDialog::Accepted) { 2223 KisAnimationRenderingOptions encoderOptions = dlgAnimationRenderer.getEncoderOptions(); 2224 KisAnimationRender::render(doc, viewManager(), encoderOptions); 2225 } 2226 } 2227 2228 void KisMainWindow::renderAnimationAgain() 2229 { 2230 if (!activeView()) return; 2231 2232 KisImageSP image = viewManager()->image(); 2233 2234 if (!image) return; 2235 if (!image->animationInterface()->hasAnimation()) return; 2236 2237 KisDocument *doc = viewManager()->document(); 2238 2239 KisConfig cfg(true); 2240 2241 KisPropertiesConfigurationSP settings = cfg.exportConfiguration("ANIMATION_EXPORT"); 2242 2243 KisAnimationRenderingOptions encoderOptions; 2244 encoderOptions.fromProperties(settings); 2245 2246 KisAnimationRender::render(doc, viewManager(), encoderOptions); 2247 } 2248 2249 void KisMainWindow::slotConfigureToolbars() 2250 { 2251 saveWindowState(); 2252 KisKEditToolBar edit(factory(), this); 2253 connect(&edit, SIGNAL(newToolBarConfig()), this, SLOT(slotNewToolbarConfig())); 2254 (void) edit.exec(); 2255 applyToolBarLayout(); 2256 } 2257 2258 void KisMainWindow::slotResetConfigurations() 2259 { 2260 KisApplication *kisApp = static_cast<KisApplication*>(qApp); 2261 kisApp->askResetConfig(); 2262 } 2263 2264 void KisMainWindow::slotNewToolbarConfig() 2265 { 2266 applyMainWindowSettings(d->windowStateConfig); 2267 2268 KisKXMLGUIFactory *factory = guiFactory(); 2269 Q_UNUSED(factory); 2270 2271 // Check if there's an active view 2272 if (!d->activeView) 2273 return; 2274 2275 plugActionList("toolbarlist", d->toolbarList); 2276 applyToolBarLayout(); 2277 } 2278 2279 void KisMainWindow::slotToolbarToggled(bool toggle) 2280 { 2281 //dbgUI <<"KisMainWindow::slotToolbarToggled" << sender()->name() <<" toggle=" << true; 2282 // The action (sender) and the toolbar have the same name 2283 KisToolBar * bar = toolBar(sender()->objectName()); 2284 if (bar) { 2285 if (toggle) { 2286 bar->show(); 2287 } 2288 else { 2289 bar->hide(); 2290 } 2291 2292 if (d->activeView && d->activeView->document()) { 2293 saveWindowState(); 2294 } 2295 } else 2296 warnUI << "slotToolbarToggled : Toolbar " << sender()->objectName() << " not found!"; 2297 } 2298 2299 void KisMainWindow::viewFullscreen(bool fullScreen) 2300 { 2301 KisConfig cfg(false); 2302 cfg.setFullscreenMode(fullScreen); 2303 2304 if (fullScreen) { 2305 setWindowState(windowState() | Qt::WindowFullScreen); // set 2306 } else { 2307 setWindowState(windowState() & ~Qt::WindowFullScreen); // reset 2308 } 2309 d->fullScreenMode->setChecked(isFullScreen()); 2310 } 2311 2312 QDockWidget* KisMainWindow::createDockWidget(KoDockFactoryBase* factory) 2313 { 2314 QDockWidget* dockWidget = 0; 2315 const bool showTitlebars = KisConfig(false).showDockerTitleBars(); 2316 2317 if (!d->dockWidgetsMap.contains(factory->id())) { 2318 dockWidget = factory->createDockWidget(); 2319 KAcceleratorManager::setNoAccel(dockWidget); 2320 2321 // It is quite possible that a dock factory cannot create the dock; don't 2322 // do anything in that case. 2323 if (!dockWidget) { 2324 warnKrita << "Could not create docker for" << factory->id(); 2325 return 0; 2326 } 2327 2328 KoDockWidgetTitleBar *titleBar = dynamic_cast<KoDockWidgetTitleBar*>(dockWidget->titleBarWidget()); 2329 2330 // Check if the dock widget is supposed to be collapsible 2331 if (!dockWidget->titleBarWidget()) { 2332 titleBar = new KoDockWidgetTitleBar(dockWidget); 2333 dockWidget->setTitleBarWidget(titleBar); 2334 } 2335 if (titleBar) { 2336 titleBar->setFont(KisUiFont::dockFont()); 2337 } 2338 2339 if (dockWidget->titleBarWidget() && !dockWidget->titleBarWidget()->inherits("KisUtilityTitleBar")) { 2340 dockWidget->titleBarWidget()->setVisible(KisConfig(true).showDockerTitleBars()); 2341 } 2342 2343 dockWidget->setObjectName(factory->id()); 2344 dockWidget->setParent(this); 2345 if (!showTitlebars) { 2346 if (dockWidget->titleBarWidget() && !dockWidget->titleBarWidget()->inherits("KisUtilityTitleBar")) { 2347 dockWidget->titleBarWidget()->setVisible(false); 2348 } 2349 dockWidget->setFeatures(QDockWidget::NoDockWidgetFeatures); 2350 } 2351 if (dockWidget->widget() && dockWidget->widget()->layout()) 2352 dockWidget->widget()->layout()->setContentsMargins(1, 1, 1, 1); 2353 2354 Qt::DockWidgetArea side = Qt::RightDockWidgetArea; 2355 bool visible = true; 2356 2357 switch (factory->defaultDockPosition()) { 2358 case KoDockFactoryBase::DockTornOff: 2359 dockWidget->setFloating(true); // position nicely? 2360 break; 2361 case KoDockFactoryBase::DockTop: 2362 side = Qt::TopDockWidgetArea; break; 2363 case KoDockFactoryBase::DockLeft: 2364 side = Qt::LeftDockWidgetArea; break; 2365 case KoDockFactoryBase::DockBottom: 2366 side = Qt::BottomDockWidgetArea; break; 2367 case KoDockFactoryBase::DockRight: 2368 side = Qt::RightDockWidgetArea; break; 2369 case KoDockFactoryBase::DockMinimized: 2370 default: 2371 side = Qt::RightDockWidgetArea; 2372 visible = false; 2373 } 2374 2375 KConfigGroup group = d->windowStateConfig.group("DockWidget " + factory->id()); 2376 side = static_cast<Qt::DockWidgetArea>(group.readEntry("DockArea", static_cast<int>(side))); 2377 if (side == Qt::NoDockWidgetArea) side = Qt::RightDockWidgetArea; 2378 2379 addDockWidget(side, dockWidget); 2380 if (!visible) { 2381 dockWidget->hide(); 2382 } 2383 2384 bool locked = group.readEntry("Locked", false); 2385 if (titleBar && locked) { 2386 titleBar->setLocked(true); 2387 } 2388 2389 d->dockWidgetsMap.insert(factory->id(), dockWidget); 2390 } 2391 else { 2392 dockWidget = d->dockWidgetsMap[factory->id()]; 2393 } 2394 2395 #ifdef Q_OS_MACOS 2396 dockWidget->setAttribute(Qt::WA_MacSmallSize, true); 2397 #endif 2398 dockWidget->setFont(KisUiFont::dockFont()); 2399 2400 connect(dockWidget, SIGNAL(dockLocationChanged(Qt::DockWidgetArea)), this, SLOT(forceDockTabFonts())); 2401 2402 return dockWidget; 2403 } 2404 2405 void KisMainWindow::forceDockTabFonts() 2406 { 2407 Q_FOREACH (QObject *child, children()) { 2408 if (child->inherits("QTabBar")) { 2409 ((QTabBar *)child)->setFont(KisUiFont::dockFont()); 2410 } 2411 } 2412 } 2413 2414 void KisMainWindow::slotUpdateWidgetStyle() 2415 { 2416 KisConfig cfg(true); 2417 QString themeFromConfig = cfg.widgetStyle(); 2418 2419 Q_FOREACH (auto key, d->actionMap.keys()) { // find checked style to save to config 2420 if(d->actionMap.value(key)->isChecked()) { 2421 cfg.setWidgetStyle(key); 2422 qApp->setProperty(currentUnderlyingStyleNameProperty, key); 2423 qApp->setStyle(key); 2424 } 2425 } 2426 } 2427 2428 QList<QDockWidget*> KisMainWindow::dockWidgets() const 2429 { 2430 return d->dockWidgetsMap.values(); 2431 } 2432 2433 QDockWidget* KisMainWindow::dockWidget(const QString &id) 2434 { 2435 if (!d->dockWidgetsMap.contains(id)) return 0; 2436 return d->dockWidgetsMap[id]; 2437 } 2438 2439 QList<KoCanvasObserverBase*> KisMainWindow::canvasObservers() const 2440 { 2441 QList<KoCanvasObserverBase*> observers; 2442 2443 observers.append(static_cast<KoCanvasObserverBase*>(KisPart::instance()->playbackEngine())); 2444 2445 Q_FOREACH (QDockWidget *docker, dockWidgets()) { 2446 KoCanvasObserverBase *observer = dynamic_cast<KoCanvasObserverBase*>(docker); 2447 if (observer) { 2448 observers << observer; 2449 } 2450 else { 2451 warnKrita << docker << "is not a canvas observer"; 2452 } 2453 } 2454 2455 return observers; 2456 } 2457 2458 2459 void KisMainWindow::toggleDockersVisibility(bool visible, bool onWelcomePage) 2460 { 2461 if (!visible) { 2462 d->dockerStateBeforeHiding = saveState(); 2463 2464 Q_FOREACH (QObject* widget, children()) { 2465 if (widget->inherits("QDockWidget")) { 2466 QDockWidget* dw = static_cast<QDockWidget*>(widget); 2467 if (dw->isVisible() && !(onWelcomePage && dw->property("ShowOnWelcomePage").toBool())) { 2468 dw->hide(); 2469 } 2470 } 2471 } 2472 } 2473 else { 2474 restoreState(d->dockerStateBeforeHiding); 2475 } 2476 } 2477 2478 void KisMainWindow::subWindowActivated() 2479 { 2480 bool enabled = (activeKisView() != 0); 2481 2482 d->mdiCascade->setEnabled(enabled); 2483 d->mdiNextWindow->setEnabled(enabled); 2484 d->mdiPreviousWindow->setEnabled(enabled); 2485 d->mdiTile->setEnabled(enabled); 2486 d->close->setEnabled(enabled); 2487 d->closeAll->setEnabled(enabled); 2488 2489 setActiveSubWindow(d->mdiArea->activeSubWindow()); 2490 2491 /** 2492 * Qt has a weirdness, it has hardcoded shortcuts added to an action 2493 * in the window menu. We need to reset the shortcuts for that menu 2494 * to nothing, otherwise the shortcuts cannot be made configurable. 2495 * 2496 * See: https://bugs.kde.org/show_bug.cgi?id=352205 2497 * https://bugs.kde.org/show_bug.cgi?id=375524 2498 * https://bugs.kde.org/show_bug.cgi?id=398729 2499 */ 2500 QMdiSubWindow *subWindow = d->mdiArea->currentSubWindow(); 2501 if (subWindow) { 2502 QMenu *menu = subWindow->systemMenu(); 2503 if (menu && menu->actions().size() == 8) { 2504 Q_FOREACH (QAction *action, menu->actions()) { 2505 action->setShortcut(QKeySequence()); 2506 2507 } 2508 menu->actions().last()->deleteLater(); 2509 } 2510 } 2511 2512 d->actionManager()->updateGUI(); 2513 } 2514 2515 void KisMainWindow::windowFocused() 2516 { 2517 /** 2518 * Notify selection manager so that it could update selection mask overlay 2519 */ 2520 if (viewManager() && viewManager()->selectionManager()) { 2521 viewManager()->selectionManager()->selectionChanged(); 2522 } 2523 2524 KisPart *kisPart = KisPart::instance(); 2525 KisWindowLayoutManager *layoutManager = KisWindowLayoutManager::instance(); 2526 if (!layoutManager->primaryWorkspaceFollowsFocus()) return; 2527 2528 QUuid primary = layoutManager->primaryWindowId(); 2529 if (primary.isNull()) return; 2530 2531 if (d->id == primary) { 2532 if (!d->workspaceBorrowedBy.isNull()) { 2533 KisMainWindow *borrower = kisPart->windowById(d->workspaceBorrowedBy); 2534 if (!borrower) return; 2535 swapWorkspaces(this, borrower); 2536 } 2537 } else { 2538 if (d->workspaceBorrowedBy == primary) return; 2539 2540 KisMainWindow *primaryWindow = kisPart->windowById(primary); 2541 if (!primaryWindow) return; 2542 swapWorkspaces(this, primaryWindow); 2543 } 2544 } 2545 2546 2547 void KisMainWindow::updateWindowMenu() 2548 { 2549 QMenu *menu = d->windowMenu->menu(); 2550 menu->clear(); 2551 2552 #ifndef Q_OS_ANDROID 2553 menu->addAction(d->newWindow); 2554 #endif 2555 menu->addAction(d->documentMenu); 2556 2557 QMenu *docMenu = d->documentMenu->menu(); 2558 docMenu->clear(); 2559 2560 QFontMetrics fontMetrics = docMenu->fontMetrics(); 2561 #if (QT_VERSION >= QT_VERSION_CHECK(5, 10, 0)) 2562 QRect geom = this->geometry(); 2563 QPoint p(geom.width() / 2 + geom.left(), geom.height() / 2 + geom.top()); 2564 QScreen *screen = qApp->screenAt(p); 2565 2566 int fileStringWidth = 300; 2567 if (screen) { 2568 fileStringWidth = int(screen->availableGeometry().width() * .40f); 2569 } 2570 #else 2571 int fileStringWidth = int(QApplication::desktop()->screenGeometry(this).width() * .40f); 2572 #endif 2573 Q_FOREACH (QPointer<KisDocument> doc, KisPart::instance()->documents()) { 2574 if (doc) { 2575 QString title = fontMetrics.elidedText(doc->path(), Qt::ElideMiddle, fileStringWidth); 2576 if (title.isEmpty() && doc->image()) { 2577 title = doc->image()->objectName(); 2578 } 2579 QAction *action = docMenu->addAction(title); 2580 action->setIcon(qApp->windowIcon()); 2581 connect(action, SIGNAL(triggered()), d->documentMapper, SLOT(map())); 2582 d->documentMapper->setMapping(action, doc); 2583 } 2584 } 2585 2586 menu->addAction(d->workspaceMenu); 2587 QMenu *workspaceMenu = d->workspaceMenu->menu(); 2588 workspaceMenu->clear(); 2589 KisResourceModel resourceModel(ResourceType::Workspaces); 2590 KisResourceIterator resourceIterator(&resourceModel); 2591 KisMainWindow *m_this = this; 2592 2593 while (resourceIterator.hasNext()) { 2594 KisResourceItemSP resource = resourceIterator.next(); 2595 QAction *action = workspaceMenu->addAction(resource->name()); 2596 action->setProperty("md5", QVariant::fromValue<QString>(resource->md5sum())); 2597 connect(action, SIGNAL(triggered()), this, SLOT(restoreWorkspace())); 2598 } 2599 workspaceMenu->addSeparator(); 2600 connect(workspaceMenu->addAction(i18nc("@action:inmenu", "&Import Workspace...")), 2601 &QAction::triggered, 2602 this, 2603 [&]() 2604 { 2605 QStringList mimeTypes = KisResourceLoaderRegistry::instance()->mimeTypes(ResourceType::Workspaces); 2606 2607 KoFileDialog dialog(0, KoFileDialog::OpenFile, "OpenDocument"); 2608 dialog.setMimeTypeFilters(mimeTypes); 2609 dialog.setCaption(i18nc("@title:window", "Choose File to Add")); 2610 QString filename = dialog.filename(); 2611 2612 KisResourceUserOperations::importResourceFileWithUserInput(this, "", ResourceType::Workspaces, filename); 2613 }); 2614 2615 connect(workspaceMenu->addAction(i18nc("@action:inmenu", "&New Workspace...")), 2616 &QAction::triggered, 2617 [=]() { 2618 QString name; 2619 name = QInputDialog::getText(this, i18nc("@title:window", "New Workspace..."), 2620 i18nc("@label:textbox", "Name:")); 2621 if (name.isEmpty()) { 2622 return; 2623 } 2624 2625 KisWorkspaceResourceSP workspace(new KisWorkspaceResource("")); 2626 workspace->setDockerState(m_this->saveState()); 2627 workspace->setImage(layoutThumbnail()); 2628 workspace->setValid(true); 2629 2630 // this line must happen before we save the workspace to resource folder or other places 2631 // because it mostly just triggers palettes to be saved into the workspace 2632 d->viewManager->canvasResourceProvider()->notifySavingWorkspace(workspace); 2633 workspace->setValid(true); 2634 2635 workspace->setFilename(name.replace(" ", "_") + workspace->defaultFileExtension()); 2636 workspace->setName(name); 2637 2638 KisResourceUserOperations::addResourceWithUserInput(this, workspace); 2639 }); 2640 2641 // TODO: What to do about delete? 2642 // workspaceMenu->addAction(i18nc("@action:inmenu", "&Delete Workspace...")); 2643 2644 menu->addSeparator(); 2645 menu->addAction(d->close); 2646 menu->addAction(d->closeAll); 2647 if (d->mdiArea->viewMode() == QMdiArea::SubWindowView) { 2648 menu->addSeparator(); 2649 menu->addAction(d->mdiTile); 2650 menu->addAction(d->mdiCascade); 2651 } 2652 menu->addSeparator(); 2653 menu->addAction(d->mdiNextWindow); 2654 menu->addAction(d->mdiPreviousWindow); 2655 menu->addSeparator(); 2656 2657 QList<QMdiSubWindow *> windows = d->mdiArea->subWindowList(); 2658 for (int i = 0; i < windows.size(); ++i) { 2659 QPointer<KisView>child = qobject_cast<KisView*>(windows.at(i)->widget()); 2660 if (child && child->document()) { 2661 QString text; 2662 if (i < 9) { 2663 text = i18n("&%1 %2", i + 1, fontMetrics.elidedText(child->document()->path(), Qt::ElideMiddle, fileStringWidth)); 2664 } 2665 else { 2666 text = i18n("%1 %2", i + 1, fontMetrics.elidedText(child->document()->path(), Qt::ElideMiddle, fileStringWidth)); 2667 } 2668 2669 QAction *action = menu->addAction(text); 2670 action->setIcon(qApp->windowIcon()); 2671 action->setCheckable(true); 2672 action->setChecked(child == activeKisView()); 2673 connect(action, SIGNAL(triggered()), d->windowMapper, SLOT(map())); 2674 d->windowMapper->setMapping(action, windows.at(i)); 2675 } 2676 } 2677 2678 bool showMdiArea = windows.count( ) > 0; 2679 if (!showMdiArea) { 2680 showWelcomeScreen(true); // see workaround in function in header 2681 } 2682 2683 // enable/disable the toolbox docker if there are no documents open 2684 Q_FOREACH (QObject* widget, children()) { 2685 if (widget->inherits("QDockWidget")) { 2686 QDockWidget* dw = static_cast<QDockWidget*>(widget); 2687 2688 if ( dw->objectName() == "ToolBox") { 2689 dw->setEnabled(showMdiArea); 2690 } 2691 } 2692 } 2693 } 2694 2695 void KisMainWindow::updateSubwindowFlags() 2696 { 2697 bool onlyOne = false; 2698 if (d->mdiArea->subWindowList().size() == 1 && d->mdiArea->viewMode() == QMdiArea::SubWindowView) { 2699 onlyOne = true; 2700 } 2701 Q_FOREACH (QMdiSubWindow *subwin, d->mdiArea->subWindowList()) { 2702 if (onlyOne) { 2703 subwin->setWindowFlags(subwin->windowFlags() | Qt::FramelessWindowHint); 2704 subwin->showMaximized(); 2705 } else { 2706 subwin->setWindowFlags((subwin->windowFlags() | Qt::FramelessWindowHint) ^ Qt::FramelessWindowHint); 2707 } 2708 } 2709 } 2710 2711 void KisMainWindow::setActiveSubWindow(QWidget *window) 2712 { 2713 if (!window) { 2714 unsetActiveView(); 2715 return; 2716 } 2717 QMdiSubWindow *subwin = qobject_cast<QMdiSubWindow *>(window); 2718 //dbgKrita << "setActiveSubWindow();" << subwin << d->activeSubWindow; 2719 2720 if (subwin && subwin != d->activeSubWindow) { 2721 KisView *view = qobject_cast<KisView *>(subwin->widget()); 2722 //dbgKrita << "\t" << view << activeView(); 2723 if (view && view != activeView()) { 2724 setActiveView(view); 2725 } 2726 d->activeSubWindow = subwin; 2727 } 2728 updateWindowMenu(); 2729 d->actionManager()->updateGUI(); 2730 } 2731 2732 void KisMainWindow::configChanged() 2733 { 2734 KisConfig cfg(true); 2735 QMdiArea::ViewMode viewMode = (QMdiArea::ViewMode)cfg.readEntry<int>("mdi_viewmode", (int)QMdiArea::TabbedView); 2736 d->mdiArea->setViewMode(viewMode); 2737 Q_FOREACH (QMdiSubWindow *subwin, d->mdiArea->subWindowList()) { 2738 subwin->setOption(QMdiSubWindow::RubberBandMove, cfg.readEntry<int>("mdi_rubberband", cfg.useOpenGL())); 2739 subwin->setOption(QMdiSubWindow::RubberBandResize, cfg.readEntry<int>("mdi_rubberband", cfg.useOpenGL())); 2740 if (viewMode == QMdiArea::TabbedView) { 2741 subwin->setWindowState(Qt::WindowMaximized); 2742 } 2743 2744 /** 2745 * Dirty workaround for a bug in Qt (checked on Qt 5.6.1): 2746 * 2747 * If you make a window "Show on top" and then switch to the tabbed mode 2748 * the window will continue to be painted in its initial "mid-screen" 2749 * position. It will persist here until you explicitly switch to its tab. 2750 */ 2751 if (viewMode == QMdiArea::TabbedView) { 2752 Qt::WindowFlags oldFlags = subwin->windowFlags(); 2753 Qt::WindowFlags flags = oldFlags; 2754 2755 flags &= ~Qt::WindowStaysOnTopHint; 2756 flags &= ~Qt::WindowStaysOnBottomHint; 2757 2758 if (flags != oldFlags) { 2759 subwin->setWindowFlags(flags); 2760 subwin->showMaximized(); 2761 } 2762 } 2763 } 2764 #ifdef Q_OS_MACOS 2765 updateSubwindowFlags(); 2766 #endif 2767 2768 KConfigGroup group( KSharedConfig::openConfig(), "theme"); 2769 d->themeManager->setCurrentTheme(group.readEntry("Theme", "Krita dark")); 2770 d->actionManager()->updateGUI(); 2771 2772 QString s = cfg.getMDIBackgroundColor(); 2773 KoColor c = KoColor::fromXML(s); 2774 QBrush brush(c.toQColor()); 2775 d->mdiArea->setBackground(brush); 2776 2777 QString backgroundImage = cfg.getMDIBackgroundImage(); 2778 if (backgroundImage != "") { 2779 QImage image(backgroundImage); 2780 QBrush brush(image); 2781 d->mdiArea->setBackground(brush); 2782 } 2783 2784 d->mdiArea->update(); 2785 2786 qApp->setFont(KisUiFont::normalFont()); 2787 2788 Q_FOREACH (QObject* widget, children()) { 2789 if (widget->inherits("QDockWidget")) { 2790 QDockWidget* dw = static_cast<QDockWidget*>(widget); 2791 dw->setFont(KisUiFont::dockFont()); 2792 } 2793 } 2794 } 2795 2796 KisView* KisMainWindow::newView(QObject *document, QMdiSubWindow *subWindow) 2797 { 2798 KisDocument *doc = qobject_cast<KisDocument*>(document); 2799 KisView *view = addViewAndNotifyLoadingCompleted(doc, subWindow); 2800 d->actionManager()->updateGUI(); 2801 2802 return view; 2803 } 2804 2805 void KisMainWindow::newWindow() 2806 { 2807 #ifdef Q_OS_ANDROID 2808 // Check if current mainwindow exists, just to be sure. 2809 if (KisPart::instance()->currentMainwindow()) { 2810 QMessageBox::warning(this, i18nc("@title:window", "Krita"), 2811 "Creating a New Main Window is unsupported on Android"); 2812 return; 2813 } 2814 #endif 2815 KisMainWindow *mainWindow = KisPart::instance()->createMainWindow(); 2816 mainWindow->initializeGeometry(); 2817 mainWindow->show(); 2818 } 2819 2820 void KisMainWindow::closeCurrentWindow() 2821 { 2822 if (d->mdiArea->currentSubWindow()) { 2823 d->mdiArea->currentSubWindow()->close(); 2824 d->actionManager()->updateGUI(); 2825 } 2826 } 2827 2828 void KisMainWindow::checkSanity() 2829 { 2830 // print error if the lcms engine is not available 2831 if (!KoColorSpaceEngineRegistry::instance()->contains("icc")) { 2832 // need to wait 1 event since exiting here would not work. 2833 m_errorMessage = i18n("The Krita LittleCMS color management plugin is not installed. Krita will quit now."); 2834 m_dieOnError = true; 2835 QTimer::singleShot(0, this, SLOT(showErrorAndDie())); 2836 return; 2837 } 2838 2839 slotStoragesWarning(); 2840 2841 // window is created signal (used in Python) 2842 // there must be some asynchronous things happening in the constructor, because the window cannot 2843 // be referenced until after this timeout is done 2844 emit KisPart::instance()->sigMainWindowCreated(); 2845 } 2846 2847 void KisMainWindow::showErrorAndDie() 2848 { 2849 QMessageBox::critical(qApp->activeWindow(), i18nc("@title:window", "Installation error"), m_errorMessage); 2850 if (m_dieOnError) { 2851 exit(10); 2852 } 2853 } 2854 2855 void KisMainWindow::showAboutApplication() 2856 { 2857 KisAboutApplication dlg(this); 2858 dlg.exec(); 2859 } 2860 2861 QPointer<KisView> KisMainWindow::activeKisView() 2862 { 2863 if (!d->mdiArea) return 0; 2864 QMdiSubWindow *activeSubWindow = d->mdiArea->activeSubWindow(); 2865 //dbgKrita << "activeKisView" << activeSubWindow; 2866 if (!activeSubWindow) return 0; 2867 return qobject_cast<KisView*>(activeSubWindow->widget()); 2868 } 2869 2870 void KisMainWindow::newOptionWidgets(KoCanvasController *controller, const QList<QPointer<QWidget> > &optionWidgetList) 2871 { 2872 KIS_ASSERT_RECOVER_NOOP(controller == KoToolManager::instance()->activeCanvasController()); 2873 bool isOurOwnView = false; 2874 2875 Q_FOREACH (QPointer<KisView> view, KisPart::instance()->views()) { 2876 if (view && view->canvasController() == controller) { 2877 isOurOwnView = view->mainWindow() == this; 2878 } 2879 } 2880 2881 if (!isOurOwnView) return; 2882 2883 Q_FOREACH (QWidget *w, optionWidgetList) { 2884 #ifdef Q_OS_MACOS 2885 w->setAttribute(Qt::WA_MacSmallSize, true); 2886 #endif 2887 w->setFont(KisUiFont::dockFont()); 2888 } 2889 2890 if (d->toolOptionsDocker) { 2891 d->toolOptionsDocker->setOptionWidgets(optionWidgetList); 2892 } 2893 else { 2894 d->viewManager->paintOpBox()->newOptionWidgets(optionWidgetList); 2895 } 2896 } 2897 2898 void KisMainWindow::createActions() 2899 { 2900 KisActionManager *actionManager = d->actionManager(); 2901 2902 2903 2904 actionManager->createStandardAction(KStandardAction::New, this, SLOT(slotFileNew())); 2905 actionManager->createStandardAction(KStandardAction::Open, this, SLOT(slotFileOpen())); 2906 actionManager->createStandardAction(KStandardAction::Quit, this, SLOT(slotFileQuit())); 2907 actionManager->createStandardAction(KStandardAction::ConfigureToolbars, this, SLOT(slotConfigureToolbars())); 2908 d->fullScreenMode = actionManager->createStandardAction(KStandardAction::FullScreen, this, SLOT(viewFullscreen(bool))); 2909 2910 d->recentFiles = KStandardAction::openRecent(this, SLOT(slotFileOpenRecent(QUrl)), actionCollection()); 2911 2912 d->saveAction = actionManager->createStandardAction(KStandardAction::Save, this, SLOT(slotFileSave())); 2913 d->saveAction->setActivationFlags(KisAction::ACTIVE_IMAGE); 2914 2915 d->saveActionAs = actionManager->createStandardAction(KStandardAction::SaveAs, this, SLOT(slotFileSaveAs())); 2916 d->saveActionAs->setActivationFlags(KisAction::ACTIVE_IMAGE); 2917 2918 d->undo = actionManager->createStandardAction(KStandardAction::Undo, this, SLOT(undo())); 2919 d->undo->setActivationFlags(KisAction::ACTIVE_IMAGE); 2920 2921 d->redo = actionManager->createStandardAction(KStandardAction::Redo, this, SLOT(redo())); 2922 d->redo->setActivationFlags(KisAction::ACTIVE_IMAGE); 2923 2924 d->undoActionsUpdateManager.reset(new KisUndoActionsUpdateManager(d->undo, d->redo)); 2925 d->undoActionsUpdateManager->setCurrentDocument(d->activeView ? d->activeView->document() : 0); 2926 2927 d->importAnimation = actionManager->createAction("file_import_animation"); 2928 connect(d->importAnimation, SIGNAL(triggered()), this, SLOT(importAnimation())); 2929 2930 d->importVideoAnimation = actionManager->createAction("file_import_video_animation"); 2931 connect(d->importVideoAnimation, SIGNAL(triggered()), this, SLOT(importVideoAnimation())); 2932 2933 d->renderAnimation = actionManager->createAction("render_animation"); 2934 d->renderAnimation->setActivationFlags(KisAction::IMAGE_HAS_ANIMATION); 2935 connect( d->renderAnimation, SIGNAL(triggered()), this, SLOT(renderAnimation())); 2936 2937 d->renderAnimationAgain = actionManager->createAction("render_animation_again"); 2938 d->renderAnimationAgain->setActivationFlags(KisAction::IMAGE_HAS_ANIMATION); 2939 connect( d->renderAnimationAgain, SIGNAL(triggered()), this, SLOT(renderAnimationAgain())); 2940 2941 d->closeAll = actionManager->createAction("file_close_all"); 2942 connect(d->closeAll, SIGNAL(triggered()), this, SLOT(slotFileCloseAll())); 2943 2944 d->importFile = actionManager->createAction("file_import_file"); 2945 connect(d->importFile, SIGNAL(triggered(bool)), this, SLOT(slotImportFile())); 2946 2947 d->exportFile = actionManager->createAction("file_export_file"); 2948 connect(d->exportFile, SIGNAL(triggered(bool)), this, SLOT(slotExportFile())); 2949 2950 d->exportFileAdvance = actionManager->createAction("file_export_advanced"); 2951 connect(d->exportFileAdvance, SIGNAL(triggered(bool)), this, SLOT(slotExportAdvance())); 2952 2953 /* The following entry opens the document information dialog. Since the action is named so it 2954 intends to show data this entry should not have a trailing ellipses (...). */ 2955 d->showDocumentInfo = actionManager->createAction("file_documentinfo"); 2956 connect(d->showDocumentInfo, SIGNAL(triggered(bool)), this, SLOT(slotDocumentInfo())); 2957 2958 d->themeManager->setThemeMenuAction(new KActionMenu(i18nc("@action:inmenu", "&Themes"), this)); 2959 d->themeManager->registerThemeActions(actionCollection()); 2960 connect(d->themeManager, SIGNAL(signalThemeChanged()), this, SLOT(slotThemeChanged()), Qt::QueuedConnection); 2961 connect(this, SIGNAL(themeChanged()), d->welcomePage, SLOT(slotUpdateThemeColors()), Qt::UniqueConnection); 2962 d->toggleDockers = actionManager->createAction("view_toggledockers"); 2963 2964 2965 KisConfig(true).showDockers(true); 2966 d->toggleDockers->setChecked(true); 2967 connect(d->toggleDockers, SIGNAL(toggled(bool)), SLOT(toggleDockersVisibility(bool))); 2968 2969 d->resetConfigurations = actionManager->createAction("reset_configurations"); 2970 connect(d->resetConfigurations, SIGNAL(triggered()), this, SLOT(slotResetConfigurations())); 2971 2972 #ifndef Q_OS_ANDROID 2973 d->toggleDetachCanvas = actionManager->createAction("view_detached_canvas"); 2974 d->toggleDetachCanvas->setChecked(false); 2975 connect(d->toggleDetachCanvas, SIGNAL(toggled(bool)), SLOT(setCanvasDetached(bool))); 2976 #endif 2977 setCanvasDetached(false); 2978 2979 d->toggleDockerTitleBars = actionManager->createAction("view_toggledockertitlebars"); 2980 d->toggleDockerTitleBars->setChecked(KisConfig(false).showDockerTitleBars()); 2981 connect(d->toggleDockerTitleBars, SIGNAL(toggled(bool)), SLOT(showDockerTitleBars(bool))); 2982 2983 actionCollection()->addAction("settings_dockers_menu", d->dockWidgetMenu); 2984 actionCollection()->addAction("window", d->windowMenu); 2985 2986 actionCollection()->addAction("style_menu", d->styleMenu); // for widget styles: breeze, fusion, etc 2987 2988 d->mdiCascade = actionManager->createAction("windows_cascade"); 2989 connect(d->mdiCascade, SIGNAL(triggered()), d->mdiArea, SLOT(cascadeSubWindows())); 2990 2991 d->mdiTile = actionManager->createAction("windows_tile"); 2992 connect(d->mdiTile, SIGNAL(triggered()), d->mdiArea, SLOT(tileSubWindows())); 2993 2994 d->mdiNextWindow = actionManager->createAction("windows_next"); 2995 connect(d->mdiNextWindow, SIGNAL(triggered()), d->mdiArea, SLOT(activateNextSubWindow())); 2996 2997 d->mdiPreviousWindow = actionManager->createAction("windows_previous"); 2998 connect(d->mdiPreviousWindow, SIGNAL(triggered()), d->mdiArea, SLOT(activatePreviousSubWindow())); 2999 3000 #ifndef Q_OS_ANDROID 3001 d->newWindow = actionManager->createAction("view_newwindow"); 3002 connect(d->newWindow, SIGNAL(triggered(bool)), this, SLOT(newWindow())); 3003 #endif 3004 3005 d->close = actionManager->createStandardAction(KStandardAction::Close, this, SLOT(closeCurrentWindow())); 3006 3007 d->showSessionManager = actionManager->createAction("file_sessions"); 3008 connect(d->showSessionManager, SIGNAL(triggered(bool)), this, SLOT(slotShowSessionManager())); 3009 3010 d->commandBarAction = actionManager->createAction("command_bar_open"); 3011 connect(d->commandBarAction, SIGNAL(triggered(bool)), this, SLOT(openCommandBar())); 3012 3013 actionManager->createStandardAction(KStandardAction::Preferences, this, SLOT(slotPreferences())); 3014 3015 for (int i = 0; i < 2; i++) { 3016 d->expandingSpacers[i] = new KisAction(i18n("Expanding Spacer")); 3017 d->expandingSpacers[i]->setDefaultWidget(new QWidget(this)); 3018 d->expandingSpacers[i]->defaultWidget()->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); 3019 actionManager->addAction(QString("expanding_spacer_%1").arg(i), d->expandingSpacers[i]); 3020 } 3021 3022 { 3023 using namespace KisWidgetConnectionUtils; 3024 3025 QAction *action = actionManager->createAction("lock_toolbars"); 3026 connectControl(action, KisToolBar::toolBarStateModel(), "toolBarsLocked"); 3027 } 3028 } 3029 3030 void KisMainWindow::applyToolBarLayout() 3031 { 3032 Q_FOREACH (KisToolBar *toolBar, toolBars()) { 3033 toolBar->layout()->setSpacing(4); 3034 toolBar->setStyleSheet("QToolBar { border: none }"); // has a border in "Fusion" style that people don't like 3035 3036 // Hide text for buttons with an icon in the toolbar 3037 Q_FOREACH (QAction *ac, toolBar->actions()){ 3038 if (ac->icon().pixmap(QSize(1,1)).isNull() == false){ 3039 ac->setPriority(QAction::LowPriority); 3040 } else { 3041 ac->setIcon(QIcon()); 3042 } 3043 } 3044 } 3045 } 3046 3047 void KisMainWindow::initializeGeometry() 3048 { 3049 // if the user didn's specify the geometry on the command line (does anyone do that still?), 3050 // we first figure out some good default size and restore the x,y position. See bug 285804Z. 3051 KConfigGroup cfg = d->windowStateConfig; 3052 QByteArray geom = QByteArray::fromBase64(cfg.readEntry("ko_geometry", QByteArray())); 3053 if (!restoreGeometry(geom)) { 3054 const int scnum = QApplication::desktop()->screenNumber(parentWidget()); 3055 QRect desk = QGuiApplication::screens().at(scnum)->availableVirtualGeometry(); 3056 3057 quint32 x = desk.x(); 3058 quint32 y = desk.y(); 3059 quint32 w = 0; 3060 quint32 h = 0; 3061 3062 // Default size -- maximize on small screens, something useful on big screens 3063 const int deskWidth = desk.width(); 3064 if (deskWidth > 1024) { 3065 // a nice width, and slightly less than total available 3066 // height to compensate for the window decs 3067 w = (deskWidth / 3) * 2; 3068 h = (desk.height() / 3) * 2; 3069 } 3070 else { 3071 w = desk.width(); 3072 h = desk.height(); 3073 } 3074 3075 x += (desk.width() - w) / 2; 3076 y += (desk.height() - h) / 2; 3077 3078 move(x,y); 3079 setGeometry(geometry().x(), geometry().y(), w, h); 3080 } 3081 d->fullScreenMode->setChecked(isFullScreen()); 3082 } 3083 3084 void KisMainWindow::showManual() 3085 { 3086 QDesktopServices::openUrl(QUrl("https://docs.krita.org")); 3087 } 3088 3089 void KisMainWindow::showDockerTitleBars(bool show) 3090 { 3091 Q_FOREACH (QDockWidget *dock, dockWidgets()) { 3092 if (dock->titleBarWidget() && !dock->titleBarWidget()->inherits("KisUtilityTitleBar")) { 3093 dock->titleBarWidget()->setVisible(show || dock->isFloating()); 3094 } 3095 } 3096 3097 KisConfig cfg(true); 3098 cfg.setShowDockerTitleBars(show); 3099 } 3100 3101 void KisMainWindow::slotXmlGuiMakingChanges(bool finished) 3102 { 3103 if (finished) { 3104 subWindowActivated(); 3105 } 3106 } 3107 3108 void KisMainWindow::orientationChanged() 3109 { 3110 QScreen *screen = QGuiApplication::primaryScreen(); 3111 3112 for (QWindow* window: QGuiApplication::topLevelWindows()) { 3113 // Android: we shouldn't transform Window managers independent of its child 3114 if ((window->type() == Qt::Popup) 3115 && (window->flags() & Qt::FramelessWindowHint) == 0 3116 && (window->geometry().topLeft() != QPoint(0, 0))) { 3117 // We are using reversed values. Because geometry returned is not the updated 3118 // one, but the previous one. 3119 int screenHeight = screen->geometry().width(); 3120 int screenWidth = screen->geometry().height(); 3121 3122 // scaling 3123 int new_x = (window->position().x() * screenWidth) / screenHeight; 3124 int new_y = (window->position().y() * screenHeight) / screenWidth; 3125 3126 // window width or height shouldn't change 3127 int winWidth = window->geometry().width(); 3128 int winHeight = window->geometry().height(); 3129 3130 // Try best to not let the window go beyond screen. 3131 if (new_x > screenWidth - winWidth) { 3132 new_x = screenWidth - winWidth; 3133 if (new_x < 0) 3134 new_x = 0; 3135 } 3136 if (new_y > screenHeight - winHeight) { 3137 new_y = screenHeight - winHeight; 3138 if (new_y < 0) 3139 new_y = 0; 3140 } 3141 3142 window->setPosition(QPoint(new_x, new_y)); 3143 } 3144 } 3145 } 3146 3147 bool KisMainWindow::checkActiveBundlesAvailable() 3148 { 3149 KisStorageFilterProxyModel proxy; 3150 proxy.setSourceModel(KisStorageModel::instance()); 3151 proxy.setFilter(KisStorageFilterProxyModel::ByStorageType, 3152 QStringList() 3153 << KisResourceStorage::storageTypeToUntranslatedString(KisResourceStorage::StorageType::Bundle)); 3154 3155 return (proxy.rowCount() > 0); 3156 } 3157 3158 bool KisMainWindow::checkPaintOpAvailable() 3159 { 3160 KisPaintOpPresetResourceServer * rserver = KisResourceServerProvider::instance()->paintOpPresetServer(); 3161 return (rserver->resourceCount() > 0); 3162 } 3163 3164 #include <moc_KisMainWindow.cpp>