Krita Source Code Documentation
Loading...
Searching...
No Matches
KisMainWindow.cpp
Go to the documentation of this file.
1/* This file is part of the KDE project
2 SPDX-FileCopyrightText: 1998, 1999 Torben Weis <weis@kde.org>
3 SPDX-FileCopyrightText: 2000-2006 David Faure <faure@kde.org>
4 SPDX-FileCopyrightText: 2007, 2009 Thomas zander <zander@kde.org>
5 SPDX-FileCopyrightText: 2010 Benjamin Port <port.benjamin@gmail.com>
6
7 SPDX-License-Identifier: LGPL-2.0-or-later
8*/
9
10#include "KisMainWindow.h"
11
12#include <KoConfig.h>
13
14// qt includes
15#include <QApplication>
16#include <QByteArray>
17#include <QCloseEvent>
18#include <QStandardPaths>
19#include <QDesktopServices>
20#include <QScreen>
21#include <QDialog>
22#include <QDockWidget>
23#include <QIcon>
24#include <QInputDialog>
25#include <QLayout>
26#include <QMdiArea>
27#include <QMdiSubWindow>
28#include <QMutex>
29#include <QMutexLocker>
30#include <QPointer>
31#include <KisSignalMapper.h>
32#include <QTabBar>
33#include <QMoveEvent>
34#include <QUrl>
35#include <QMessageBox>
36#include <QStatusBar>
37#include <QStyleFactory>
38#include <QMenu>
39#include <QMenuBar>
40#include <KisMimeDatabase.h>
41#include <QMimeData>
42#include <QStackedWidget>
43#include <QProxyStyle>
44#include <QScreen>
45#include <QAction>
46#include <QWindow>
47#include <QTemporaryDir>
48#include <QScrollArea>
49#include <QActionGroup>
50
51#include <kactioncollection.h>
52#include <kactionmenu.h>
53#include <kis_debug.h>
54#include <kedittoolbar.h>
55#include <khelpmenu.h>
56#include <klocalizedstring.h>
57#include <kaboutdata.h>
62#include "kis_icon_utils.h"
63#include <krecentfilesaction.h>
64#include "krita_utils.h"
65#include <ktoggleaction.h>
66#include <ktoolbar.h>
67#include <kmainwindow.h>
68#include <kxmlguiwindow.h>
69#include <kxmlguifactory.h>
70#include <kxmlguiclient.h>
71#include <kguiitem.h>
72#include <kwindowconfig.h>
73#include <kacceleratormanager.h>
74
75#include <KoResourcePaths.h>
76#include <KoToolFactoryBase.h>
77#include <KoToolRegistry.h>
78#include <KoDockFactoryBase.h>
81#include <KoDocumentInfoDlg.h>
82#include <KoDocumentInfo.h>
83#include <KoFileDialog.h>
84#include <kis_icon.h>
85#include <KoToolManager.h>
86#include "KoToolDocker.h"
88#include <KoDockRegistry.h>
89#include <KoPluginLoader.h>
90#include <KoColorSpaceEngine.h>
91#include <KoUpdater.h>
92#include <KisResourceModel.h>
94#include <KisResourceIterator.h>
95#include <KisResourceTypes.h>
96#include <KisResourceCacheDb.h>
97#include <KisStorageModel.h>
99#include <KisPlaybackEngine.h>
100
101#ifdef Q_OS_ANDROID
102#include "KisAndroidDonations.h"
104#include <QtAndroid>
105#include <KisAndroidUtils.h>
106#endif
107
109#include <KisUsageLogger.h>
114#include "kis_action_manager.h"
115#include "KisApplication.h"
116#include "kis_canvas2.h"
119#include "kis_clipboard.h"
120#include "kis_config.h"
121#include "kis_config_notifier.h"
125#include <KisDocument.h>
126#include "kis_group_layer.h"
128#include "kis_image.h"
133#include "kis_node.h"
134#include "KisOpenPane.h"
135#include "kis_paintop_box.h"
136#include "KisPart.h"
139#include "kis_statusbar.h"
140#include "KisView.h"
141#include "KisViewManager.h"
142#include "thememanager.h"
149#include "KisWelcomePageWidget.h"
151#include <KritaVersionWrapper.h>
152#include "KisCanvasWindow.h"
153#include "kis_action.h"
154#include <katecommandbar.h>
156#include "KisUiFont.h"
160#include "KisToolBarStateModel.h"
161#include <config-qmdiarea-always-show-subwindow-title.h>
162#include <config-qt-patches-present.h>
164
165#include <mutex>
166
167#if defined(Q_OS_ANDROID) && KRITA_QT_HAS_ANDROID_QPLATFORMSCREEN_DENSITY_ADJUSTMENT
168#include <KisAndroidScaling.h>
169#endif
170
172{
173public:
175
176 QString id() const override {
177 return "sharedtooldocker";
178 }
179
180 QDockWidget* createDockWidget() override {
181 KoToolDocker* dockWidget = new KoToolDocker();
182 return dockWidget;
183 }
184
186 return DockRight;
187 }
188};
189
190class Q_DECL_HIDDEN KisMainWindow::Private
191{
192public:
193 Private(KisMainWindow *parent, QUuid id)
194 : q(parent)
195 , id(id)
196 , styleMenu(new KActionMenu(i18nc("@action:inmenu", "Styles"), parent))
197 , dockWidgetMenu(new KActionMenu(i18nc("@action:inmenu", "&Dockers"), parent))
198 , windowMenu(new KActionMenu(i18nc("@action:inmenu", "&Window"), parent))
199 , documentMenu(new KActionMenu(i18nc("@action:inmenu", "New &View"), parent))
200 , workspaceMenu(new KActionMenu(i18nc("@action:inmenu", "Wor&kspace"), parent))
201 , welcomePage(new KisWelcomePageWidget(parent))
202 , widgetStack(new QStackedWidget(parent))
203 , mdiArea(new QMdiArea(parent))
204 , windowMapper(new KisSignalMapper(parent))
205 , documentMapper(new KisSignalMapper(parent))
206 {
207 if (id.isNull()) this->id = QUuid::createUuid();
208
209 welcomeScroller = new QScrollArea();
210 welcomeScroller->setHorizontalScrollBarPolicy(Qt::ScrollBarAsNeeded);
211 welcomeScroller->setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded);
212 welcomeScroller->setWidget(welcomePage);
213 welcomeScroller->setWidgetResizable(true);
214
215 widgetStack->addWidget(welcomeScroller);
216 widgetStack->addWidget(mdiArea);
217 mdiArea->setTabsMovable(true);
218 mdiArea->setActivationOrder(QMdiArea::ActivationHistoryOrder);
219 mdiArea->setDocumentMode(true);
220 mdiArea->setOption(QMdiArea::DontMaximizeSubWindowOnActivation);
221#ifdef HAVE_QMDIAREA_ALWAYS_SHOW_SUBWINDOW_TITLE
222 mdiArea->setOption(QMdiArea::AlwaysShowSubwindowNameInTitleBar);
223#endif /* HAVE_QMDIAREA_ALWAYS_SHOW_SUBWINDOW_TITLE */
224
225 commandBar = new KateCommandBar(parent);
226 }
227
229 qDeleteAll(toolbarList);
230 }
231
232 KisMainWindow *q {nullptr};
233 QUuid id;
234
235 KisViewManager *viewManager {nullptr};
236
239
241
242 bool firstTime {true};
243 bool windowSizeDirty {false};
244#ifdef Q_OS_ANDROID
245 bool flashWindowHackInProgress {false};
246#endif
247
248 KisAction *showDocumentInfo {nullptr};
249 KisAction *saveAction {nullptr};
250 KisAction *saveActionAs {nullptr};
251 KisAction *importAnimation {nullptr};
252 KisAction *importVideoAnimation {nullptr};
253 KisAction *renderAnimation {nullptr};
254 KisAction *renderAnimationAgain {nullptr};
255 KisAction *closeAll {nullptr};
256 KisAction *importFile {nullptr};
257 KisAction *exportFile {nullptr};
258 KisAction *exportFileAdvance {nullptr};
259 KisAction *undo {nullptr};
260 KisAction *redo {nullptr};
261 KisAction *close {nullptr};
262 KisAction *mdiCascade {nullptr};
263 KisAction *mdiTile {nullptr};
264 KisAction *mdiNextWindow {nullptr};
265 KisAction *mdiPreviousWindow {nullptr};
266 KisAction *toggleDockers {nullptr};
267 KisAction *resetConfigurations {nullptr};
268 KisAction *toggleDockerTitleBars {nullptr};
269#ifdef Q_OS_ANDROID
270 KisAction *showDonationManagementDialog {nullptr};
271 KisAction *manageSubscriptions {nullptr};
272#if KRITA_QT_HAS_ANDROID_QPLATFORMSCREEN_DENSITY_ADJUSTMENT
273 KisAction *changeInterfaceScale {nullptr};
274#endif
275#else
276 KisAction *toggleDetachCanvas {nullptr};
277 KisAction *newWindow {nullptr};
278#endif
279 KisAction *fullScreenMode {nullptr};
280 KisAction *showSessionManager {nullptr};
281 KisAction *commandBarAction {nullptr};
282 KisAction *expandingSpacers[2];
283
284 KActionMenu *styleMenu {nullptr};
285 QActionGroup* styleActions {nullptr};
286 QMap<QString, QAction*> actionMap;
287
288 KActionMenu *dockWidgetMenu {nullptr};
289 KActionMenu *windowMenu {nullptr};
290 KActionMenu *documentMenu {nullptr};
291 KActionMenu *workspaceMenu {nullptr};
292
293 KisKHelpMenu *helpMenu {nullptr};
294
295 KRecentFilesAction *recentFiles {nullptr};
296 KisResourceModel *workspacemodel {nullptr};
297
298 QScopedPointer<KisUndoActionsUpdateManager> undoActionsUpdateManager;
299
301
302 QMap<QString, QDockWidget *> dockWidgetsMap;
304 KoToolDocker *toolOptionsDocker {nullptr};
305
306 QCloseEvent *deferredClosingEvent {nullptr};
307#ifndef Q_OS_HAIKU
308 Digikam::ThemeManager *themeManager {nullptr};
309#endif
310 QScrollArea *welcomeScroller {nullptr};
311 KisWelcomePageWidget *welcomePage {nullptr};
312
313
314 QStackedWidget *widgetStack {nullptr};
315
316 QMdiArea *mdiArea {nullptr};
317 QMdiSubWindow *activeSubWindow {nullptr};
318 KisSignalMapper *windowMapper {nullptr};
319 KisSignalMapper *documentMapper {nullptr};
320 KisCanvasWindow *canvasWindow {nullptr};
321
323 QScopedPointer<KisSignalCompressorWithParam<int> > tabSwitchCompressor;
325
326 KConfigGroup windowStateConfig;
327
329
330 KateCommandBar *commandBar {nullptr};
331
333 return viewManager->actionManager();
334 }
335
336 QTabBar* findTabBarHACK() {
337 QObjectList objects = mdiArea->children();
338 Q_FOREACH (QObject *object, objects) {
339 QTabBar *bar = qobject_cast<QTabBar*>(object);
340 if (bar) {
341 return bar;
342 }
343 }
344 return 0;
345 }
346};
347
349{
350 QWidget *widget;
351public:
352 ScopedWidgetDisabler(QWidget *widget_)
353 : widget(widget_)
354 {
355 widget->setEnabled(false);
356 }
357
359 {
360 widget->setEnabled(true);
361 }
362};
363
365 : KXmlGuiWindow()
366 , d(new Private(this, uuid))
367{
368 KisScopedPerformanceLogger perfLog(QStringLiteral("KisMainWindow::KisMainWindow"));
369
370 KAcceleratorManager::setNoAccel(this);
371
372 d->workspacemodel = new KisResourceModel(ResourceType::Workspaces, this);
373 connect(d->workspacemodel, SIGNAL(modelReset()), this, SLOT(updateWindowMenu()));
374 connect(d->workspacemodel, SIGNAL(rowsInserted(QModelIndex,int,int)), this, SLOT(updateWindowMenu()));
375 connect(d->workspacemodel, SIGNAL(rowsRemoved(QModelIndex,int,int)), this, SLOT(updateWindowMenu()));
376
377 d->viewManager = new KisViewManager(this, actionCollection());
378 KConfigGroup group( KSharedConfig::openConfig(), "theme");
379#ifndef Q_OS_HAIKU
380 d->themeManager = new Digikam::ThemeManager(group.readEntry("Theme", "Krita dark"), this);
381#endif
382 d->windowStateConfig = KSharedConfig::openConfig()->group("MainWindow");
383
385 setTabPosition(Qt::AllDockWidgetAreas, QTabWidget::North);
386 setDockNestingEnabled(true);
387#ifdef Q_OS_ANDROID
388 // On Android, the animations for docks and menus are not only slow, but
389 // also can cause crashes in some circumstances. Turning them off feels
390 // better and avoids those.
391 setAnimated(false);
392#endif
393
394 qApp->setStartDragDistance(25); // 25 px is a distance that works well for Tablet and Mouse events
395
396 connect(this, SIGNAL(restoringDone()), this, SLOT(forceDockTabFonts()));
397 connect(this, SIGNAL(themeChanged()), d->viewManager, SLOT(updateIcons()));
398 connect(this, SIGNAL(themeChanged()), KoToolManager::instance(), SLOT(themeChanged()));
399 connect(KisPart::instance(), SIGNAL(documentClosed(QString)), SLOT(updateWindowMenu()));
400 connect(KisPart::instance(), SIGNAL(documentOpened(QString)), SLOT(updateWindowMenu()));
401 connect(KisConfigNotifier::instance(), SIGNAL(configChanged()), this, SLOT(configChanged()));
402
404 KoPluginLoader::instance()->load("Krita/ViewPlugin", KoPluginLoader::PluginsConfig(), d->viewManager, false);
405
406 // Load the per-application plugins (Right now, only Python) We do this only once, when the first mainwindow is being created.
407 KoPluginLoader::instance()->load("Krita/ApplicationPlugin", KoPluginLoader::PluginsConfig(), qApp, true);
408
409 KoToolBoxFactory toolBoxFactory;
410 QDockWidget *toolbox = createDockWidget(&toolBoxFactory);
411
412 KisConfig cfg(true);
413 if (cfg.toolOptionsInDocker()) {
414 ToolDockerFactory toolDockerFactory;
415 d->toolOptionsDocker = qobject_cast<KoToolDocker*>(createDockWidget(&toolDockerFactory));
416 if (d->toolOptionsDocker) {
417 d->toolOptionsDocker->toggleViewAction()->setEnabled(true);
418 }
419 }
420
421 QMap<QString, QAction*> dockwidgetActions;
422
423 if (toolbox) {
424 dockwidgetActions[toolbox->toggleViewAction()->text()] = toolbox->toggleViewAction();
425 }
426
428 QDockWidget *dw = createDockWidget(factory);
429 if (dw) {
430 dockwidgetActions[dw->toggleViewAction()->text()] = dw->toggleViewAction();
431 }
432 }
433
434 if (d->toolOptionsDocker) {
435 dockwidgetActions[d->toolOptionsDocker->toggleViewAction()->text()] = d->toolOptionsDocker->toggleViewAction();
436 }
438
439 Q_FOREACH (QString title, dockwidgetActions.keys()) {
440 d->dockWidgetMenu->addAction(dockwidgetActions[title]);
441 }
442
443
444 // Style menu actions
445 d->styleActions = new QActionGroup(this);
446 QAction * action;
447
448#ifndef Q_OS_ANDROID
449
450 Q_FOREACH (QString styleName, QStyleFactory::keys()) {
451
452 action = new QAction(styleName, d->styleActions);
453 action->setCheckable(true);
454 d->actionMap.insert(styleName, action);
455 d->styleMenu->addAction(d->actionMap.value(styleName));
456 }
457
458 // select the config value, or the current style if that does not exist
459 QString styleFromConfig = cfg.widgetStyle().toLower();
460 QString styleToSelect = styleFromConfig.isEmpty() ? style()->objectName().toLower() : styleFromConfig;
461
462 Q_FOREACH (auto key, d->actionMap.keys()) {
463 if(key.toLower() == styleToSelect) { // does the key match selection
464 d->actionMap.value(key)->setChecked(true);
465 }
466 }
467
468 connect(d->styleActions, SIGNAL(triggered(QAction*)),
469 this, SLOT(slotUpdateWidgetStyle()));
470#endif
471
472 // Load all the actions from the tool plugins
473 // ToolBoxDocker needs them when at setViewManager()
474 Q_FOREACH(KoToolFactoryBase *toolFactory, KoToolRegistry::instance()->values()) {
475 toolFactory->createActions(actionCollection());
476 }
477
478
479 Q_FOREACH (QDockWidget *wdg, dockWidgets()) {
480 if ((wdg->features() & QDockWidget::DockWidgetClosable) == 0) {
481 wdg->setVisible(true);
482 }
483 }
484
485 Q_FOREACH (KoCanvasObserverBase* observer, canvasObservers()) {
486 observer->setObservedCanvas(0);
487 KisMainwindowObserver* mainwindowObserver = dynamic_cast<KisMainwindowObserver*>(observer);
488 if (mainwindowObserver) {
489 mainwindowObserver->setViewManager(d->viewManager);
490 }
491 }
492
493 d->mdiArea->setHorizontalScrollBarPolicy(Qt::ScrollBarAsNeeded);
494 d->mdiArea->setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded);
495 d->mdiArea->setTabPosition(QTabWidget::North);
496 d->mdiArea->setTabsClosable(true);
497 d->mdiArea->setAcceptDrops(true);
498
499 themeChanged(); // updates icon styles
500
501 setCentralWidget(d->widgetStack);
502 d->widgetStack->setCurrentIndex(0);
503
504 connect(d->mdiArea, SIGNAL(subWindowActivated(QMdiSubWindow*)), this, SLOT(subWindowActivated()));
505 connect(d->windowMapper, SIGNAL(mapped(QWidget*)), this, SLOT(setActiveSubWindow(QWidget*)));
506 connect(d->documentMapper, SIGNAL(mapped(QObject*)), this, SLOT(newView(QObject*)));
507
508 d->canvasWindow = new KisCanvasWindow(this);
509 actionCollection()->addAssociatedWidget(d->canvasWindow);
510
512
513 // the welcome screen needs to grab actions...so make sure this line goes after the createAction() so they exist
514 d->welcomePage->setMainWindow(this);
515
516 d->recentFiles->setRecentFilesModel(&KisRecentDocumentsModelWrapper::instance()->model());
517
520
521 if (isHelpMenuEnabled() && !d->helpMenu) {
522 d->helpMenu = new KisKHelpMenu(this, KAboutData::applicationData(), false);
523
524 // workaround-less version:
525 // d->helpMenu = new KisKHelpMenu(this, QString()/*unused*/, false);
526
527 // The difference between using KisKActionCollection->addAction() is that
528 // these actions do not get tied to the MainWindow. What does this all do?
529 KisKActionCollection *actions = d->viewManager->actionCollection();
530 QAction *helpContentsAction = d->helpMenu->action(KisKHelpMenu::menuHelpContents);
531 QAction *whatsThisAction = d->helpMenu->action(KisKHelpMenu::menuWhatsThis);
532 QAction *reportBugAction = d->helpMenu->action(KisKHelpMenu::menuReportBug);
533 QAction *switchLanguageAction = d->helpMenu->action(KisKHelpMenu::menuSwitchLanguage);
534 QAction *aboutAppAction = d->helpMenu->action(KisKHelpMenu::menuAboutApp);
535 QAction *aboutKdeAction = d->helpMenu->action(KisKHelpMenu::menuAboutKDE);
536
537 if (helpContentsAction) {
538 actions->addAction(helpContentsAction->objectName(), helpContentsAction);
539 }
540 if (whatsThisAction) {
541 actions->addAction(whatsThisAction->objectName(), whatsThisAction);
542 }
543 if (reportBugAction) {
544 actions->addAction(reportBugAction->objectName(), reportBugAction);
545 }
546 if (switchLanguageAction) {
547 actions->addAction(switchLanguageAction->objectName(), switchLanguageAction);
548 }
549 if (aboutAppAction) {
550 actions->addAction(aboutAppAction->objectName(), aboutAppAction);
551 }
552 if (aboutKdeAction) {
553 actions->addAction(aboutKdeAction->objectName(), aboutKdeAction);
554 }
555
556 connect(d->helpMenu, SIGNAL(showAboutApplication()), SLOT(showAboutApplication()));
557 }
558
559 // Custom handling of help action for manual URL outside docs.kde.org.
560 QAction *helpAction = actionCollection()->action("help_contents");
561 helpAction->disconnect(SIGNAL(triggered()));
562 connect(helpAction, SIGNAL(triggered()), this, SLOT(showManual()));
563
564#if 0
565 //check for colliding shortcuts
566 QSet<QKeySequence> existingShortcuts;
567 Q_FOREACH (QAction* action, actionCollection()->actions()) {
568 if(action->shortcut() == QKeySequence(0)) {
569 continue;
570 }
571 dbgKrita << "shortcut " << action->text() << " " << action->shortcut();
572 Q_ASSERT(!existingShortcuts.contains(action->shortcut()));
573 existingShortcuts.insert(action->shortcut());
574 }
575#endif
576
578
579 // Make sure the python plugins create their actions in time
581
582 // If we have customized the toolbars, load that first
583 setLocalXMLFile(KoResourcePaths::locateLocal("data", "krita5.xmlgui"));
584 setXMLFile(":/kxmlgui5/krita5.xmlgui");
585
586 guiFactory()->addClient(this);
588 connect(guiFactory(), SIGNAL(makingChanges(bool)), SLOT(slotXmlGuiMakingChanges(bool)));
589
590 // Create and plug toolbar list for Settings menu
592 Q_FOREACH (QWidget* it, guiFactory()->containers("ToolBar")) {
593 KisToolBar * toolBar = ::qobject_cast<KisToolBar *>(it);
594 if (toolBar) {
595 KToggleAction* act = new KToggleAction(i18n("Show %1 Toolbar", toolBar->windowTitle()), this);
596 actionCollection()->addAction(toolBar->objectName().toUtf8(), act);
597 act->setCheckedState(KGuiItem(i18n("Hide %1 Toolbar", toolBar->windowTitle())));
598 connect(act, SIGNAL(toggled(bool)), this, SLOT(slotToolbarToggled(bool)));
599 act->setChecked(!toolBar->isHidden());
600 toolbarList.append(act);
601 } else {
602 warnUI << "Toolbar list contains a " << it->metaObject()->className() << " which is not a toolbar!";
603 }
604 }
605
606 plugActionList("toolbarlist", toolbarList);
607 d->toolbarList = toolbarList;
608
610
611 d->viewManager->updateGUI();
612 d->viewManager->updateIcons();
613
614 QTimer::singleShot(1000, Qt::CoarseTimer, this, SLOT(checkSanity()));
615
616 {
617 using namespace std::placeholders; // For _1 placeholder
618 std::function<void (int)> callback(
619 std::bind(&KisMainWindow::switchTab, this, _1));
620
621 d->tabSwitchCompressor.reset(
623 }
624
625
626 if (cfg.readEntry("CanvasOnlyActive", false)) {
627 QString currentWorkspace = cfg.readEntry<QString>("CurrentWorkspace", "Default");
629 KisWorkspaceResourceSP workspace = rserver->resource("", "", currentWorkspace);
630 if (workspace) {
631 restoreWorkspace(workspace);
632 }
633 cfg.writeEntry("CanvasOnlyActive", false);
634 menuBar()->setVisible(true);
635 }
636
637 this->winId(); // Ensures the native window has been created.
638
639#ifdef Q_OS_ANDROID
640 // HACK: This prevents the mainWindow from going beyond the screen with no
641 // way to bring it back. Apparently the size doesn't matter here as long as
642 // it remains fixed?
643 setFixedSize(KisApplication::primaryScreen()->availableGeometry().size());
644
645 QScreen *s = QGuiApplication::primaryScreen();
646 s->setOrientationUpdateMask(Qt::LandscapeOrientation|Qt::InvertedLandscapeOrientation|Qt::PortraitOrientation|Qt::InvertedPortraitOrientation);
647 connect(s, SIGNAL(orientationChanged(Qt::ScreenOrientation)), this, SLOT(orientationChanged()));
648
649#if KRITA_QT_HAS_ANDROID_QPLATFORMSCREEN_DENSITY_ADJUSTMENT
650 // When the screen's UI scale changes, we have to hide and show it to get
651 // its contents to update, otherwise it fails to re-render altogether.
653 if (androidScaling) {
654 connect(androidScaling,
656 this,
657 &KisMainWindow::slotFlashWindowHack,
658 Qt::QueuedConnection);
659 }
660#endif
661
662 // When Krita starts, Java side sends an event to set applicationState() to active. But, before
663 // the event could reach KisApplication's platform integration, it is cleared by KisOpenGLModeProber::probeFormat.
664 // So, we send it manually when MainWindow shows up.
665 QAndroidJniObject::callStaticMethod<void>("org/qtproject/qt5/android/QtNative", "setApplicationState", "(I)V", Qt::ApplicationActive);
666#endif
667
668 setAcceptDrops(true);
669 QTabBar *tabBar = d->findTabBarHACK();
670 if (tabBar) {
671 tabBar->setElideMode(Qt::ElideRight);
672 // load customized tab style
674 tabBar->setExpanding(true);
675 tabBar->setAcceptDrops(true);
676 tabBar->setChangeCurrentOnDrag(true);
677 }
678
679 applyMainWindowSettings(d->windowStateConfig);
680}
681
683{
684 // Q_FOREACH (QAction *ac, actionCollection()->actions()) {
685 // QAction *action = qobject_cast<QAction*>(ac);
686 // if (action) {
687 // qDebug() << "<Action"
688 // << "\n\tname=" << action->objectName()
689 // << "\n\ticon=" << action->icon().name()
690 // << "\n\ttext=" << action->text().replace("&", "&amp;")
691 // << "\n\twhatsThis=" << action->whatsThis()
692 // << "\n\ttoolTip=" << action->toolTip().replace("<html>", "").replace("</html>", "")
693 // << "\n\ticonText=" << action->iconText().replace("&", "&amp;")
694 // << "\n\tshortcut=" << action->shortcut().toString()
695 // << "\n\tisCheckable=" << QString((action->isChecked() ? "true" : "false"))
696 // << "\n\tstatusTip=" << action->statusTip()
697 // << "\n/>\n" ;
698 // }
699 // else {
700 // dbgKrita << "Got a non-qaction:" << ac->objectName();
701 // }
702 // }
703
713 Q_FOREACH (QMdiSubWindow *subwin, d->mdiArea->subWindowList()) {
714 if (subwin) {
715 (void) subwin->close();
716 delete subwin;
717 }
718 }
719
720 // The doc and view might still exist (this is the case when closing the window)
722
723 delete d->viewManager;
724 delete d;
725
726}
727
729{
730 return 0;
731}
732
733QUuid KisMainWindow::id() const {
734 return d->id;
735}
736
737void KisMainWindow::addView(KisView *view, QMdiSubWindow *subWindow)
738{
739 if (d->activeView == view && !subWindow) return;
740
741 if (d->activeView) {
742 d->activeView->disconnect(this);
743 }
744
745 // register the newly created view in the input manager
747
748 showView(view, subWindow);
749 Q_EMIT restoringDone();
750
751// QTabBar *tabBar = d->findTabBarHACK();
752// Q_FOREACH(QObject *c, tabBar->children()) {
753// if (QWidget *w = qobject_cast<QWidget*>(c)) {
754// w->setAcceptDrops(true);
755// }
756// }
757}
758
760{
767 if (view->canvasBase() == viewManager()->canvasBase()) {
769 }
770}
771
772
773void KisMainWindow::showView(KisView *imageView, QMdiSubWindow *subwin)
774{
775 if (imageView && activeView() != imageView) {
776 // XXX: find a better way to initialize this!
777 imageView->setViewManager(d->viewManager);
778
779 imageView->canvasBase()->setFavoriteResourceManager(d->viewManager->paintOpBox()->favoriteResourcesManager());
780 imageView->slotLoadingFinished();
781
782 if (!subwin) {
783 QMdiSubWindow *currentSubWin = d->mdiArea->currentSubWindow();
784 bool shouldMaximize = currentSubWin ? currentSubWin->isMaximized() : true;
785 subwin = d->mdiArea->addSubWindow(imageView);
786
799 const bool osManagedSurfacePresent = KisPlatformPluginInterfaceFactory::instance()->surfaceColorManagedByOS();
800 const bool useNativeSurfaceForCanvas = KisConfig(true).effectiveShouldUseNativeSurfaceForCanvas(osManagedSurfacePresent);
801 if (useNativeSurfaceForCanvas) {
802 subwin->setAttribute(Qt::WA_DontCreateNativeAncestors);
803 subwin->setAttribute(Qt::WA_NativeWindow);
804 }
805
806 if (shouldMaximize) {
807 subwin->setWindowState(Qt::WindowMaximized);
808 }
809 } else {
810 subwin->setWidget(imageView);
811 }
812 imageView->setSubWindow(subwin);
813 subwin->setAttribute(Qt::WA_DeleteOnClose, true);
814 connect(subwin, SIGNAL(destroyed()), SLOT(updateWindowMenu()));
815
816 KisConfig cfg(true);
817 subwin->setOption(QMdiSubWindow::RubberBandMove, cfg.readEntry<int>("mdi_rubberband", cfg.useOpenGL()));
818 subwin->setOption(QMdiSubWindow::RubberBandResize, cfg.readEntry<int>("mdi_rubberband", cfg.useOpenGL()));
819 subwin->setWindowIcon(qApp->windowIcon());
820
821#ifdef Q_OS_MACOS
822 connect(subwin, SIGNAL(destroyed()), SLOT(updateSubwindowFlags()));
824#endif
825
826 if (d->mdiArea->subWindowList().size() == 1) {
827 imageView->showMaximized();
828 }
829 else {
830 imageView->show();
831 }
832
854
855 // No, no, no: do not try to call this _before_ the show() has
856 // been called on the view; only when that has happened is the
857 // opengl context active, and very bad things happen if we tell
858 // the dockers to update themselves with a view if the opengl
859 // context is not active.
860 setActiveView(imageView);
861
863 } else {
865 }
866}
867
869{
870 std::optional<KisDlgPreferences::PageDesc>page = std::nullopt;
871
872 KisAction* action = d->actionManager()->actionByName("options_configure");
873 QVariant data = action->data();
874 if (data.userType() == QMetaType::QVariantList) {
875 // Reset the data first
876 action->setData(QVariant());
877 QList<QVariant> list = data.toList();
878
879 Q_ASSERT(list.length() == 2);
880 Q_ASSERT(list[0].userType() == QMetaType::Int);
881 Q_ASSERT(list[1].userType() == QMetaType::Int);
882
884 int t = list[1].toInt();
886 }
887
888 QScopedPointer<KisDlgPreferences> dlgPreferences(new KisDlgPreferences(this));
889
890 if (dlgPreferences->editPreferences(page)) {
894
895 // XXX: should this be changed for the views in other windows as well?
896 Q_FOREACH (QPointer<KisView> koview, KisPart::instance()->views()) {
897 KisViewManager *view = qobject_cast<KisViewManager*>(koview);
898 if (view) {
899 // Update the settings for all nodes -- they don't query
900 // KisConfig directly because they need the settings during
901 // compositing, and they don't connect to the config notifier
902 // because nodes are not QObjects (because only one base class
903 // can be a QObject).
904 KisNode* node = dynamic_cast<KisNode*>(view->image()->rootLayer().data());
905 if (node) {
906 node->updateSettings();
907 }
908 }
909
910 }
912
913 d->viewManager->showHideScrollbars();
914 }
915}
916
918{
919 // reload action icons!
920 Q_FOREACH (QAction *action, actionCollection()->actions()) {
922 }
923 if (d->mdiArea) {
924 d->mdiArea->setPalette(qApp->palette());
925 for (int i=0; i<d->mdiArea->subWindowList().size(); i++) {
926 QMdiSubWindow *window = d->mdiArea->subWindowList().at(i);
927 if (window) {
928 window->setPalette(qApp->palette());
929 KisView *view = qobject_cast<KisView*>(window->widget());
930 if (view) {
931 view->slotThemeChanged(qApp->palette());
932 }
933 }
934 }
935 }
936
938
939 // Update toolbars
940 {
941 Q_FOREACH (KisToolBar* aToolBar, toolBars()) {
942 QObjectList objects;
943 objects.append(aToolBar);
944 while (!objects.isEmpty()) {
945 QWidget* widget = qobject_cast<QWidget*>(objects.takeFirst());
946 if (widget) {
947 objects.append(widget->children());
948 widget->setPalette(qApp->palette());
949 }
950 }
951 }
952 }
953}
954
956{
957 KConfigGroup group(KSharedConfig::openConfig(), "theme");
958#ifndef Q_OS_HAIKU
959 if (group.readEntry("Theme", "") == d->themeManager->currentThemeName()) return;
960
961 // save theme changes instantly
962 group.writeEntry("Theme", d->themeManager->currentThemeName());
963#endif
964 updateTheme();
965
966 // Make the other top level windows update as well
967 Q_FOREACH (QWidget* topLevelWidget, qApp->topLevelWidgets()) {
968 if (topLevelWidget == this) {
969 // Skip if the current top level widget is this window
970 continue;
971 }
972 if (topLevelWidget->isHidden()) {
973 // Skip unwanted top level widgets like menus, dropdowns, tooltips, etc.
974 continue;
975 }
976 KisMainWindow *topLevelMainWindow = qobject_cast<KisMainWindow*>(topLevelWidget);
977 if (topLevelMainWindow) {
978 topLevelMainWindow->updateTheme();
979 Q_EMIT topLevelMainWindow->themeChanged();
980 } else {
981 QObjectList objects;
982 objects.append(topLevelWidget);
983 while (!objects.isEmpty()) {
984 QWidget* widget = qobject_cast<QWidget*>(objects.takeFirst());
985 if (widget) {
986 objects.append(widget->children());
988 }
989 }
990 }
991 }
992
994
995 Q_EMIT themeChanged();
996}
997
999{
1000 // update MDI area theme
1001 // Tab close button override
1002 // just switch this icon out for all OSs so it is easier to see
1003 QString closeButtonImageUrl;
1004 QString closeButtonHoverColor;
1006 closeButtonImageUrl = QStringLiteral(":/dark_close-tab.svg");
1007 closeButtonHoverColor = QStringLiteral("lightcoral");
1008 } else {
1009 closeButtonImageUrl = QStringLiteral(":/light_close-tab.svg");
1010 closeButtonHoverColor = QStringLiteral("darkred");
1011 }
1012 QString tabStyleSheet = QStringLiteral(R"(
1013 QTabBar::close-button {
1014 image: url(%1);
1015 padding: 1.5px;
1016 border-radius: 3px;
1017 }
1018 QTabBar::close-button:hover {
1019 background-color: %2;
1020 }
1021 QTabBar::close-button:pressed {
1022 background-color: red;
1023 }
1024
1025 QHeaderView::section {
1026 padding: 7px;
1027 }
1028
1029 )")
1030 .arg(closeButtonImageUrl, closeButtonHoverColor);
1031
1032
1033 QTabBar* tabBar = d->findTabBarHACK();
1034 if (tabBar) {
1035 tabBar->setStyleSheet(tabStyleSheet);
1036 }
1037
1038}
1039
1041{
1042 return centralWidget() != d->widgetStack;
1043}
1044
1046{
1047#ifdef Q_OS_ANDROID
1048 if (detach) {
1049 QMessageBox::warning(this, i18nc("@title:window", "Krita"),
1050 "Detach Canvas is unsupported on Android");
1051 }
1052#else
1053 if (detach == canvasDetached()) return;
1054
1055 QWidget *outgoingWidget = centralWidget() ? takeCentralWidget() : nullptr;
1056 QWidget *incomingWidget = d->canvasWindow->swapMainWidget(outgoingWidget);
1057
1058 if (incomingWidget) {
1059 setCentralWidget(incomingWidget);
1060 }
1061
1062 if (detach) {
1063 d->canvasWindow->show();
1064 } else {
1065 d->canvasWindow->hide();
1066 }
1067 d->toggleDetachCanvas->setChecked(detach);
1068#endif
1069}
1070
1071QWidget * KisMainWindow::canvasWindow() const
1072{
1073 return d->canvasWindow;
1074}
1075
1080
1082{
1083 KisRecentFilesManager::instance()->remove(QUrl::fromLocalFile(url));
1084}
1085
1086void KisMainWindow::slotUpdateSaveActionTitle(const QString &documentPath)
1087{
1088 const QString fileName = QFileInfo(documentPath).fileName();
1089
1090 if (!fileName.isEmpty()) {
1091 d->saveAction->setToolTip(i18n("Save as %1", fileName));
1092 }
1093 else {
1094 d->saveAction->setToolTip(i18n("Save"));
1095 }
1096}
1097
1099{
1100 Q_UNUSED(readWrite);
1101 d->actionManager()->updateGUI();
1102}
1103
1105{
1106 if (d->activeView) {
1107 return d->activeView;
1108 }
1109 return 0;
1110}
1111
1112bool KisMainWindow::openDocument(const QString &path, OpenFlags flags)
1113{
1114 ScopedWidgetDisabler disabler(d->welcomePage->dropFrameBorder);
1115 QApplication::processEvents(); // make UI more responsive
1116
1117 if (!QFile(path).exists()) {
1118 if (!(flags & BatchMode)) {
1119 QMessageBox::critical(qApp->activeWindow(), i18nc("@title:window", "Krita"), i18n("The file %1 does not exist.", path));
1120 }
1121 KisRecentFilesManager::instance()->remove(QUrl::fromLocalFile(path)); //remove the file from the recent-opened-file-list
1122 return false;
1123 }
1124 return openDocumentInternal(path, flags);
1125}
1126
1127bool KisMainWindow::openDocumentInternal(const QString &path, OpenFlags flags)
1128{
1129 if (!QFile(path).exists()) {
1130 qWarning() << "KisMainWindow::openDocumentInternal. Could not open:" << path;
1131 return false;
1132 }
1133
1135
1136 if (flags & BatchMode) {
1137 newdoc->setFileBatchMode(true);
1138 }
1139
1140 d->firstTime = true;
1141 connect(newdoc, SIGNAL(completed()), this, SLOT(slotLoadCompleted()));
1142 connect(newdoc, SIGNAL(canceled(QString)), this, SLOT(slotLoadCanceled(QString)));
1143
1144 KisDocument::OpenFlags openFlags = KisDocument::None;
1145 // XXX: Why this duplication of OpenFlags...
1146 if (flags & RecoveryFile) {
1147 openFlags |= KisDocument::RecoveryFile;
1148 }
1149
1150 bool openRet = !(flags & Import) ? newdoc->openPath(path, openFlags) : newdoc->importDocument(path);
1151
1152 if (!openRet) {
1153 delete newdoc;
1154 return false;
1155 }
1156
1157 KisPart::instance()->addDocument(newdoc);
1158
1159 // Try to determine whether this was an unnamed autosave
1160 if (flags & RecoveryFile &&
1161 ( path.startsWith(QDir::tempPath())
1162 || path.startsWith(QDir::homePath())
1163 ) &&
1164 ( QFileInfo(path).fileName().startsWith(".krita")
1165 || QFileInfo(path).fileName().startsWith("krita")
1166 )
1167 )
1168 {
1169 QString path = QStandardPaths::writableLocation(QStandardPaths::PicturesLocation);
1170 if (!QFileInfo(path).exists()) {
1171 path = QStandardPaths::writableLocation(QStandardPaths::HomeLocation);
1172 }
1173 newdoc->setPath(path + "/" + newdoc->objectName() + ".kra");
1174 }
1175
1176 return true;
1177}
1178
1180 Q_FOREACH(QMdiSubWindow *subwindow, d->mdiArea->subWindowList()) {
1181 KisView *view = qobject_cast<KisView*>(subwindow->widget());
1183
1184 if (view) {
1185 if (view->document() == document) {
1186 setActiveSubWindow(subwindow);
1187 return;
1188 }
1189 }
1190 }
1191
1193}
1194
1196 QMdiSubWindow *subWindow)
1197{
1198 showWelcomeScreen(false); // see workaround in function header
1199
1200 KisView *view = KisPart::instance()->createView(document, d->viewManager, this);
1201 addView(view, subWindow);
1202
1203 Q_EMIT guiLoadingFinished();
1204
1205#ifdef Q_OS_ANDROID
1206 // HACK: When opening documents on Android, the main window sometimes fails
1207 // to update until the application is shunted to the background and brought
1208 // back or the menu bar is fiddled with. Flickering the window fixes this.
1209 // Having a docker that uses QML somehow also fixes this, so this hack is
1210 // gone again in 5.3 with the introduction of the text properties docker.
1211 // UNHACK: But not on Xiaomi devices! There flickering the window makes it
1212 // sorta exit fullscreen, adding black bars at the top and bottom. Just
1213 // opening a document seems to work fine on these devices though, it delays
1214 // for a moment then displays fine, so just skip this there I guess.
1216 slotFlashWindowHack();
1217 }
1218#endif
1219
1220 return view;
1221}
1222
1224{
1225 KoFileDialog dialog(this, KoFileDialog::ImportFiles, "OpenDocument");
1226 dialog.setDefaultDir(QStandardPaths::writableLocation(QStandardPaths::PicturesLocation));
1228 dialog.setCaption(isImporting ? i18n("Import Images") : i18n("Open Images"));
1229
1230 QStringList filenames = dialog.filenames();
1231 filenames.sort();
1232
1233 return filenames;
1234}
1235
1236// Separate from openDocument to handle async loading (remote URLs)
1238{
1239 KisDocument *newdoc = qobject_cast<KisDocument*>(sender());
1240 if (newdoc && newdoc->image()) {
1242
1243 disconnect(newdoc, SIGNAL(completed()), this, SLOT(slotLoadCompleted()));
1244 disconnect(newdoc, SIGNAL(canceled(QString)), this, SLOT(slotLoadCanceled(QString)));
1245
1246 Q_EMIT loadCompleted();
1247 }
1248}
1249
1250void KisMainWindow::slotLoadCanceled(const QString &errMsg)
1251{
1252 KisUsageLogger::log(QString("Loading canceled: %1.").arg(errMsg));
1253 KisDocument* doc = qobject_cast<KisDocument*>(sender());
1254 Q_ASSERT(doc);
1255 disconnect(doc, SIGNAL(completed()), this, SLOT(slotLoadCompleted()));
1256 disconnect(doc, SIGNAL(canceled(QString)), this, SLOT(slotLoadCanceled(QString)));
1257}
1258
1259void KisMainWindow::slotSaveCanceled(const QString &errMsg)
1260{
1261 if (!errMsg.isEmpty()) { // empty when cancelled by user
1262 KisUsageLogger::log(QString("Saving cancelled. Error:").arg(errMsg));
1263 QMessageBox::critical(this, i18nc("@title:window", "Krita"), errMsg);
1264 }
1265 else {
1266 KisUsageLogger::log(QString("Saving cancelled by the user."));
1267 }
1269}
1270
1272{
1273 KisUsageLogger::log(QString("Saving Completed"));
1274 KisDocument* doc = qobject_cast<KisDocument*>(sender());
1275 if (doc) {
1276 disconnect(doc, SIGNAL(completed()), this, SLOT(slotSaveCompleted()));
1277 disconnect(doc, SIGNAL(canceled(QString)), this, SLOT(slotSaveCanceled(QString)));
1278 }
1279
1280 if (d->deferredClosingEvent) {
1281 KXmlGuiWindow::closeEvent(d->deferredClosingEvent);
1282 }
1283}
1284
1286{
1287 std::unique_lock<QMutex> l(d->savingEntryMutex, std::try_to_lock);
1288 return !l.owns_lock();
1289}
1290
1291bool KisMainWindow::installBundle(const QString &fileName) const
1292{
1293 QFileInfo from(fileName);
1294 QFileInfo to(QStringLiteral("%1/%2").arg(KoResourcePaths::getAppDataLocation(), from.fileName()));
1295 if (to.exists()) {
1296 QFile::remove(to.canonicalFilePath());
1297 }
1298 return QFile::copy(fileName, to.absoluteFilePath());
1299}
1300
1302{
1303 int size = 256;
1304 qreal scale = qreal(size)/qreal(qMax(geometry().width(), geometry().height()));
1305 QImage layoutThumbnail = QImage(qRound(geometry().width()*scale), qRound(geometry().height()*scale), QImage::Format_ARGB32);
1306 QPainter gc(&layoutThumbnail);
1307 gc.fillRect(0, 0, layoutThumbnail.width(), layoutThumbnail.height(), this->palette().dark());
1308
1309 Q_FOREACH(const QObject *child, children()) {
1310 if (child->isWidgetType()) {
1311 const QWidget *w = static_cast<const QWidget *>(child);
1312
1313 if (w->isVisible() && !w->property("_kis_excludeFromLayoutThumbnail").toBool()) {
1314 QRect wRect = QRectF(w->geometry().x()*scale
1315 , w->geometry().y()*scale
1316 , w->geometry().width()*scale
1317 , w->geometry().height()*scale
1318 ).toRect();
1319
1320 wRect = wRect.intersected(layoutThumbnail.rect().adjusted(-1, -1, -1, -1));
1321
1322 gc.setBrush(this->palette().window());
1323 if (w == d->widgetStack) {
1324 gc.setBrush(d->mdiArea->background());
1325 }
1326 gc.setPen(this->palette().windowText().color());
1327 gc.drawRect(wRect);
1328 }
1329 }
1330 }
1331 gc.end();
1332 return layoutThumbnail;
1333}
1334
1335bool KisMainWindow::saveDocument(KisDocument *document, bool saveas, bool isExporting, bool isAdvancedExporting )
1336{
1337 if (!document) {
1338 return true;
1339 }
1340
1348 std::unique_lock<QMutex> l(d->savingEntryMutex, std::try_to_lock);
1349 if (!l.owns_lock()) return false;
1350
1351 // no busy wait for saving because it is dangerous!
1352 KisDelayedSaveDialog dlg(document->image(), KisDelayedSaveDialog::SaveDialog, 0, this);
1353 dlg.blockIfImageIsBusy();
1354
1355 if (dlg.result() == KisDelayedSaveDialog::Rejected) {
1356 return false;
1357 }
1358 else if (dlg.result() == KisDelayedSaveDialog::Ignored) {
1359 QMessageBox::critical(qApp->activeWindow(),
1360 i18nc("@title:window", "Krita"),
1361 i18n("You are saving a file while the image is "
1362 "still rendering. The saved file may be "
1363 "incomplete or corrupted.\n\n"
1364 "Please select a location where the original "
1365 "file will not be overridden!"));
1366
1367
1368 saveas = true;
1369 }
1370
1371 if (document->isRecovered()) {
1372 saveas = true;
1373 }
1374
1375 if (document->path().isEmpty()) {
1376 saveas = true;
1377 }
1378
1379 QFile target(document->path());
1380 if (!target.exists()) { // Possible when file is moved/unmounted during Krita session.
1381 saveas = true;
1382 }
1383
1384 connect(document, SIGNAL(completed()), this, SLOT(slotSaveCompleted()));
1385 connect(document, SIGNAL(canceled(QString)), this, SLOT(slotSaveCanceled(QString)));
1386
1387 QByteArray nativeFormat = document->nativeFormatMimeType();
1388 QByteArray oldMimeFormat = document->mimeType();
1389
1390 QUrl suggestedURL = QUrl::fromLocalFile(document->path());
1391
1393
1395 if (!mimeFilter.contains(oldMimeFormat)) {
1396 dbgUI << "KisMainWindow::saveDocument no export filter for" << oldMimeFormat;
1397
1398 // --- don't setOutputMimeType in case the user cancels the Save As
1399 // dialog and then tries to just plain Save ---
1400
1401 // suggest a different filename extension (yes, we fortunately don't all live in a world of magic :))
1402 QString suggestedFilename = QFileInfo(suggestedURL.toLocalFile()).completeBaseName();
1403
1404 if (!suggestedFilename.isEmpty()) { // ".kra" looks strange for a name
1405 suggestedFilename = suggestedFilename + "." + KisMimeDatabase::suffixesForMimeType(KIS_MIME_TYPE).first();
1406 suggestedURL = suggestedURL.adjusted(QUrl::RemoveFilename);
1407 suggestedURL.setPath(suggestedURL.path() + suggestedFilename);
1408 }
1409
1410 // force the user to choose outputMimeType
1411 saveas = true;
1412 }
1413
1414 bool ret = false;
1415
1416 if (document->path().isEmpty() || isExporting || saveas) {
1417 // if you're just File/Save As'ing to change filter options you
1418 // don't want to be reminded about overwriting files etc.
1419 bool justChangingFilterOptions = false;
1420
1421 KoFileDialog dialog(this, KoFileDialog::SaveFile, "SaveAs");
1422 dialog.setCaption(isExporting ? i18n("Exporting") : i18n("Saving As"));
1423
1424 //qDebug() << ">>>>>" << isExporting << d->lastExportLocation << d->lastExportedFormat << QString::fromLatin1(document->mimeType());
1425
1426 if (isExporting && !d->lastExportLocation.isEmpty() && !d->lastExportLocation.contains(QDir::tempPath())) {
1427
1428 // Use the location where we last exported to, if it's set, as the opening location for the file dialog
1429 QString proposedPath = QFileInfo(d->lastExportLocation).absolutePath();
1430 // If the document doesn't have a filename yet, use the title
1431 QString proposedFileName = suggestedURL.isEmpty() ? document->documentInfo()->aboutInfo("title") : QFileInfo(suggestedURL.toLocalFile()).completeBaseName();
1432 // Use the last mimetype we exported to by default
1433 QString proposedMimeType = d->lastExportedFormat.isEmpty() ? "" : d->lastExportedFormat;
1434 QString proposedExtension = KisMimeDatabase::suffixesForMimeType(proposedMimeType).first().remove("*,");
1435
1436 // Set the default dir: this overrides the one loaded from the config file, since we're exporting and the lastExportLocation is not empty
1437 dialog.setDefaultDir(proposedPath + "/" + proposedFileName + "." + proposedExtension, true);
1438 dialog.setMimeTypeFilters(mimeFilter, proposedMimeType);
1439 }
1440 else {
1441 //This section only runs when the user haven't exported yet during the session, behavior set in the File Handling, Default MimeType setting.
1442 KisConfig cfg(true);
1443 QByteArray default_mime_type = cfg.exportMimeType(false).toUtf8();
1444 QString proposedMimeType = QString::fromLatin1(default_mime_type);
1445
1446 // Get the last used location for saving
1447 KConfigGroup group = KSharedConfig::openConfig()->group("File Dialogs");
1448 QString proposedPath = group.readEntry("SaveAs", "");
1449 // if that is empty, get the last used location for loading
1450 if (proposedPath.isEmpty()) {
1451 proposedPath = group.readEntry("OpenDocument", "");
1452 }
1453 // If that is empty, too, use the Pictures location.
1454 if (proposedPath.isEmpty()) {
1455 proposedPath = QStandardPaths::writableLocation(QStandardPaths::PicturesLocation);
1456 }
1457 // But only use that if the suggestedUrl, that is, the document's own url is empty, otherwise
1458 // open the location where the document currently is.
1459
1460 //For if the user picked All Files Supported as the default, where there would not be an extension
1461 if(default_mime_type == "all/mime"){
1462 dialog.setDefaultDir(suggestedURL.isEmpty() ? proposedPath : proposedPath + "/" + QFileInfo(suggestedURL.toLocalFile()).completeBaseName(), true);
1463 }
1464
1465 if (default_mime_type != "all/mime" && !default_mime_type.isEmpty()) {
1466 QString proposedExtension = KisMimeDatabase::suffixesForMimeType(proposedMimeType).first().remove("*,");
1467 //This line is responsible for setting filename, which also manipulates filters.
1468 dialog.setDefaultDir(suggestedURL.isEmpty() ? proposedPath : proposedPath + "/" + QFileInfo(suggestedURL.toLocalFile()).completeBaseName() + "." + proposedExtension, true);
1469 }
1470
1471 if (!isExporting) {
1472 // If Saving, use the document's mimetype, or if that is empty, kra, which is the safest.
1473 dialog.setDefaultDir(suggestedURL.isEmpty() ? proposedPath : suggestedURL.toLocalFile(), true);
1474 default_mime_type = document->mimeType().isEmpty() ? nativeFormat : document->mimeType();
1475 }
1476 dialog.setMimeTypeFilters(mimeFilter, QString::fromLatin1(default_mime_type));
1477 }
1478
1479 QString newFilePath = dialog.filename();
1480
1481 bool bOk = true;
1482 if (newFilePath.isEmpty()) {
1483 bOk = false;
1484 }
1485
1486 if (bOk) {
1487 if (document->documentInfo()->aboutInfo("title") == i18n("Unnamed")) {
1488 QString fn = newFilePath;
1489 QFileInfo info(fn);
1490 document->documentInfo()->setAboutInfo("title", info.completeBaseName());
1491 }
1492
1493 QByteArray outputFormat = nativeFormat;
1494
1495 QString outputFormatString = KisMimeDatabase::mimeTypeForFile(newFilePath, false);
1496 outputFormat = outputFormatString.toLatin1();
1497
1498
1499 if (!isExporting) {
1500 justChangingFilterOptions = (newFilePath == document->path()) && (outputFormat == document->mimeType());
1501 }
1502 else {
1503 QString path = QFileInfo(d->lastExportLocation).absolutePath();
1504 QString filename = QFileInfo(document->path()).completeBaseName();
1505 justChangingFilterOptions = (QFileInfo(newFilePath).absolutePath() == path)
1506 && (QFileInfo(newFilePath).completeBaseName() == filename)
1507 && (outputFormat == d->lastExportedFormat);
1508 }
1509
1510 bool wantToSave = true;
1511
1512 // don't change this line unless you know what you're doing :)
1513 if (!justChangingFilterOptions) {
1514 if (!document->isNativeFormat(outputFormat))
1515 wantToSave = true;
1516 }
1517
1518 if (wantToSave) {
1519 if (!isExporting) { // Save As
1520 ret = document->saveAs(newFilePath, outputFormat, true);
1521 if (ret) {
1522 dbgUI << "Successful Save As!";
1523 KisPart::instance()->queueAddRecentURLToAllMainWindowsOnFileSaved(QUrl::fromLocalFile(newFilePath));
1524 } else {
1525 dbgUI << "Failed Save As!";
1526
1527 }
1528 }
1529 else { // Export
1530 ret = document->exportDocument(newFilePath, outputFormat, isAdvancedExporting, false);
1531 if (ret) {
1532 d->lastExportLocation = newFilePath;
1533 d->lastExportedFormat = outputFormat;
1534 }
1535 }
1536
1537 } // if (wantToSave) {
1538 else
1539 ret = false;
1540 } // if (bOk) {
1541 else
1542 ret = false;
1543 } else { // saving
1544 // We cannot "export" into the currently
1545 // opened document. We are not Gimp.
1546 KIS_ASSERT_RECOVER_NOOP(!isExporting);
1547
1548 // be sure document has the correct outputMimeType!
1549 if (document->isModified()) {
1550 ret = document->save(true, 0);
1551 }
1552
1553 if (!ret) {
1554 dbgUI << "Failed Save!";
1555 }
1556 }
1557
1558 return ret;
1559}
1560
1562{
1563 if (activeView()) {
1564 activeView()->document()->undoStack()->undo();
1565 }
1566}
1567
1569{
1570 if (activeView()) {
1571 activeView()->document()->undoStack()->redo();
1572 }
1573}
1574
1575void KisMainWindow::closeEvent(QCloseEvent *e)
1576{
1577 if (hackIsSaving()) {
1578 e->setAccepted(false);
1579 return;
1580 }
1581
1582 if (!KisPart::instance()->closingSession()) {
1583 QAction *action= d->viewManager->actionCollection()->action("view_show_canvas_only");
1584 if ((action) && (action->isChecked())) {
1585 action->setChecked(false);
1586 }
1587
1588 // Save session when last window is closed
1589 if (KisPart::instance()->mainwindowCount() == 1) {
1590 bool closeAllowed = KisPart::instance()->closeSession();
1591
1592 if (!closeAllowed) {
1593 e->setAccepted(false);
1594 return;
1595 }
1596 }
1597 }
1598
1599 d->mdiArea->closeAllSubWindows();
1600
1601 QList<QMdiSubWindow*> childrenList = d->mdiArea->subWindowList();
1602
1603 if (childrenList.isEmpty()) {
1604 // TODO: that assignment looks really suspicious, because `e`
1605 // is destroyed right after the exit of this function
1606 d->deferredClosingEvent = e;
1607 d->canvasWindow->close();
1608 } else {
1609 e->setAccepted(false);
1610 }
1611}
1612
1614{
1615 KSharedConfigPtr config = KSharedConfig::openConfig();
1616
1617 if (d->windowSizeDirty ) {
1618 dbgUI << "KisMainWindow::saveWindowSettings";
1619 KConfigGroup group = d->windowStateConfig;
1620 KWindowConfig::saveWindowSize(windowHandle(), group);
1621 config->sync();
1622 d->windowSizeDirty = false;
1623 }
1624
1625 if (!d->activeView || d->activeView->document()) {
1626
1627 // Save toolbar position into the config file of the app, under the doc's component name
1628 KConfigGroup group = d->windowStateConfig;
1630
1631 // Save state of dock widgets
1632 for (QMap<QString, QDockWidget*>::const_iterator i = d->dockWidgetsMap.constBegin();
1633 i != d->dockWidgetsMap.constEnd(); ++i) {
1634 if (i.value()->widget()) {
1635 KConfigGroup dockGroup = group.group(QString("DockWidget ") + i.key());
1636 dockGroup.writeEntry("Locked", i.value()->property("Locked").toBool());
1637 dockGroup.writeEntry("DockArea", (int) dockWidgetArea(i.value()));
1638 dockGroup.writeEntry("xPosition", (int) i.value()->widget()->x());
1639 dockGroup.writeEntry("yPosition", (int) i.value()->widget()->y());
1640
1641 dockGroup.writeEntry("width", (int) i.value()->widget()->width());
1642 dockGroup.writeEntry("height", (int) i.value()->widget()->height());
1643 }
1644 }
1645
1646 }
1647
1648 KSharedConfig::openConfig()->sync();
1649 resetAutoSaveSettings(); // Don't let KisKMainWindow override the good stuff we wrote down
1650
1651}
1652
1653void KisMainWindow::resizeEvent(QResizeEvent * e)
1654{
1655 d->windowSizeDirty = true;
1656 KXmlGuiWindow::resizeEvent(e);
1657}
1658
1659
1660void KisMainWindow::dragMoveEvent(QDragMoveEvent *event)
1661{
1662 dragMove(event);
1663 event->accept();
1664}
1665
1666void KisMainWindow::dragLeaveEvent(QDragLeaveEvent *event)
1667{
1668 dragLeave();
1669 event->accept();
1670}
1671
1673{
1674 QAction *action= d->viewManager->actionCollection()->action("view_show_canvas_only");
1675 return !action || !action->isChecked();
1676}
1677
1678void KisMainWindow::showEvent(QShowEvent *event)
1679{
1680 // we're here because, we need to make sure everything (dockers, toolbars etc) is loaded and ready before
1681 // we can hide it.
1682 if (!event->spontaneous()) {
1683 setMainWindowLayoutForCurrentMainWidget(d->widgetStack->currentIndex(), false);
1684 }
1685#ifdef Q_OS_ANDROID
1686 // The user can conceivably purchase a product from the splash screen while
1687 // Krita is still loading. In that case, the "pending" flag will be set. The
1688 // dialog in question will clear the flag.
1690 if (androidDonations && androidDonations->isShowDonationManagementDialogPending()) {
1691 QTimer::singleShot(0, this, &KisMainWindow::slotShowDonationManagementDialog);
1692 }
1693#endif
1694 return KXmlGuiWindow::showEvent(event);
1695}
1696
1697void KisMainWindow::setMainWindowLayoutForCurrentMainWidget(int widgetIndex, bool widgetIndexChanged)
1698{
1699
1700 if (widgetIndex == 0) {
1701 if (widgetIndexChanged) {
1706
1707 if (d->mdiArea->subWindowList().isEmpty()) {
1712 saveWindowState(true);
1713 } else {
1714 saveMainWindowSettings(d->windowStateConfig);
1715 }
1716 }
1718 }
1719 else {
1720 setAutoSaveSettings(d->windowStateConfig, false);
1721 statusBar()->setVisible(KisConfig(true).showStatusBar());
1722 }
1723
1724 QList<QAction *> actions = d->dockWidgetMenu->menu()->actions();
1725 actions.append(toolBarMenuAction()->menu()->actions());
1726 for (QAction *action : actions) {
1727 if (action) {
1728 action->setEnabled(widgetIndex);
1729 }
1730 }
1731}
1732
1734{
1735 // This makes sure we don't save window state when we're in welcome page mode, because all the dockers
1736 // etc are hidden while the user is here.
1738
1739 toggleDockersVisibility(false, true);
1740 if (statusBar()) {
1741 statusBar()->hide();
1742 }
1743 QList<QToolBar *> toolbars = findChildren<QToolBar *>();
1744 for (QToolBar *toolbar : toolbars) {
1745 if (toolbar->objectName() != "mainToolBar") {
1746 toolbar->hide();
1747 }
1748 }
1749}
1750
1752{
1753 d->activeView = view;
1754
1755 if (d->undoActionsUpdateManager) {
1756 d->undoActionsUpdateManager->setCurrentDocument(view ? view->document() : 0);
1757 }
1758
1759 d->viewManager->setCurrentView(view);
1760
1761 d->activeViewConnections.clear();
1762 d->activeViewConnections.addConnection(view->document(),
1763 SIGNAL(sigPathChanged(QString)),
1764 this, SLOT(slotUpdateSaveActionTitle(QString)));
1765 slotUpdateSaveActionTitle(view->document()->path());
1766 d->activeViewConnections.addConnection(view->document(),
1767 SIGNAL(sigReadWriteChanged(bool)),
1768 this, SLOT(slotUpdateReadWriteMode(bool)));
1769 slotUpdateReadWriteMode(view->document()->isReadWrite());
1770
1772
1773 Q_EMIT activeViewChanged();
1774}
1775
1777{
1778 d->activeViewConnections.clear();
1779 slotUpdateSaveActionTitle(QString());
1781}
1782
1783void KisMainWindow::dragMove(QDragMoveEvent * event)
1784{
1785 QTabBar *tabBar = d->findTabBarHACK();
1786
1787 if (!tabBar && d->mdiArea->viewMode() == QMdiArea::TabbedView) {
1788 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)!";
1789 }
1790
1791 if (tabBar && tabBar->isVisible()) {
1792 QPoint pos = tabBar->mapFromGlobal(mapToGlobal(event->pos()));
1793 if (tabBar->rect().contains(pos)) {
1794 const int tabIndex = tabBar->tabAt(pos);
1795
1796 if (tabIndex >= 0 && tabBar->currentIndex() != tabIndex) {
1797 d->tabSwitchCompressor->start(tabIndex);
1798 }
1799 } else if (d->tabSwitchCompressor->isActive()) {
1800 d->tabSwitchCompressor->stop();
1801 }
1802 }
1803}
1804
1806{
1807 if (d->tabSwitchCompressor->isActive()) {
1808 d->tabSwitchCompressor->stop();
1809 }
1810}
1811
1812
1814{
1815 QTabBar *tabBar = d->findTabBarHACK();
1816 if (!tabBar) return;
1817
1818 tabBar->setCurrentIndex(index);
1819}
1820
1822{
1823 const int currentIndex = show ? 0 : 1;
1824 if (d->widgetStack->currentIndex() != currentIndex) {
1825 setUpdatesEnabled(false);
1826 // These have to be done in different sequence to avoid graphical
1827 // layout glitch during the switch.
1828 if (show) {
1829 setMainWindowLayoutForCurrentMainWidget(currentIndex, true);
1830 d->widgetStack->setCurrentIndex(currentIndex);
1831 } else {
1832 d->widgetStack->setCurrentIndex(currentIndex);
1833 setMainWindowLayoutForCurrentMainWidget(currentIndex, true);
1834 }
1835 setUpdatesEnabled(true);
1836 }
1837}
1838
1840{
1842 dlg->exec();
1843 dlg->deleteLater();
1844}
1845
1847{
1848 dbgUI << "slotImportFile()";
1849 slotFileOpen(true);
1850}
1851
1852
1853void KisMainWindow::slotFileOpen(bool isImporting)
1854{
1855 QStringList urls = showOpenFileDialog(isImporting);
1856
1857 if (urls.isEmpty())
1858 return;
1859
1860 Q_FOREACH (const QString& url, urls) {
1861
1862 if (!url.isEmpty()) {
1863 OpenFlags flags = isImporting ? Import : None;
1864 bool res = openDocument(url, flags);
1865 if (!res) {
1866 warnKrita << "Loading" << url << "failed";
1867 }
1868 }
1869 }
1870}
1871
1873{
1874 (void) openDocument(url.toLocalFile(), None);
1875}
1876
1878{
1879 if (saveDocument(d->activeView->document(), false, false,false)) {
1880 Q_EMIT documentSaved();
1881 }
1882}
1883
1885{
1886#if defined(Q_OS_ANDROID) && KRITA_QT_HAS_ANDROID_QPLATFORMSCREEN_DENSITY_ADJUSTMENT
1888 if (androidScaling) {
1889 androidScaling->showDialog();
1890 }
1891#endif
1892}
1893
1895{
1896 if (saveDocument(d->activeView->document(), true, false,false)) {
1897 Q_EMIT documentSaved();
1898 }
1899}
1900
1902{
1903 if (saveDocument(d->activeView->document(), true, true,false)) {
1904 Q_EMIT documentSaved();
1905 }
1906}
1908{
1909 if (saveDocument(d->activeView->document(), true, true,true)) {
1910 Q_EMIT documentSaved();
1911 }
1912}
1913
1917
1918#ifdef Q_OS_ANDROID
1919void KisMainWindow::slotShowDonationManagementDialog()
1920{
1921 // Don't show the donation management dialog on top of another dialog
1922 // that may have triggered a donation flow, such as the bundle manager.
1923 QWidget *win = qApp->activeWindow();
1924 if (win && !qobject_cast<KisMainWindow *>(win) && (win->isModal() || win->windowModality() != Qt::NonModal)) {
1925 return;
1926 }
1927
1928 // We don't use `exec` here because the purchase stuff runs in Android's
1929 // event loop, so it's legitimately possible that we get hit by another
1930 // request to show the donation management dialog while it's already up
1931 // and it's more convenient for the dialog to handle the deduplication.
1932 QString objectName = QStringLiteral("kisdonationmanagementdialog");
1933 KisDonationManagementDialog *dlg = findChild<KisDonationManagementDialog *>(objectName, Qt::FindDirectChildrenOnly);
1934 if (dlg) {
1935 dlg->reshow();
1936 } else {
1937 dlg = new KisDonationManagementDialog(this);
1938
1939 QAction *action = actionCollection()->action("manage_supporter_bundles");
1940 if (action) {
1941 connect(dlg, &KisDonationManagementDialog::sigShowSupporterBundles, action, &QAction::trigger);
1942 }
1943
1944 dlg->setAttribute(Qt::WA_DeleteOnClose);
1945 dlg->setObjectName(objectName);
1946 dlg->show();
1947 }
1948}
1949
1950void KisMainWindow::slotFlashWindowHack()
1951{
1952 if (!d->flashWindowHackInProgress) {
1953 d->flashWindowHackInProgress = true;
1954 QTimer::singleShot(0, this, [this] {
1955 hide();
1956 QTimer::singleShot(0, this, [this] {
1957 show();
1958 d->flashWindowHackInProgress = false;
1959 });
1960 });
1961 }
1962}
1963#endif
1964
1966{
1967 return d->viewManager->canvasResourceProvider()->resourceManager();
1968}
1969
1971{
1972 return d->mdiArea->subWindowList().size();
1973}
1974
1975const KConfigGroup &KisMainWindow::windowStateConfig() const
1976{
1977 return d->windowStateConfig;
1978}
1979
1980void KisMainWindow::saveWindowState(bool restoreNormalState)
1981{
1982 // We don't need to save welcome page's layout
1983 if (d->widgetStack->currentIndex() == 0) {
1984 // TODO(sh_zam): We should still save position/geometry, right?
1985 return;
1986 }
1987
1988 if (restoreNormalState) {
1989 QAction *showCanvasOnly = d->viewManager->actionCollection()->action("view_show_canvas_only");
1990
1991 if (showCanvasOnly && showCanvasOnly->isChecked()) {
1992 showCanvasOnly->setChecked(false);
1993 }
1994
1995 d->windowStateConfig.writeEntry("ko_geometry", saveGeometry().toBase64());
1996 d->windowStateConfig.writeEntry("State", saveState().toBase64());
1997
1998 // if the dockers are hidden at this time, save their state.
1999 if (!d->toggleDockers->isChecked()) {
2000 restoreState(d->dockerStateBeforeHiding);
2001 }
2002
2003 statusBar()->setVisible(true);
2004 menuBar()->setVisible(true);
2005
2007
2008 } else {
2009 saveMainWindowSettings(d->windowStateConfig);
2010 }
2011
2012}
2013
2014bool KisMainWindow::restoreWorkspaceState(const QByteArray &state)
2015{
2016 QByteArray oldState = saveState();
2017 const bool showTitlebars = KisConfig(false).showDockerTitleBars();
2018
2019 // needed because otherwise the layout isn't correctly restored in some situations
2020 Q_FOREACH (QDockWidget *dock, dockWidgets()) {
2021 if (dock) {
2022 dock->setProperty("Locked", false); // Unlock invisible dockers
2023 dock->toggleViewAction()->setEnabled(true);
2024 dock->hide();
2025 if (dock->titleBarWidget() && !dock->titleBarWidget()->inherits("KisUtilityTitleBar")) {
2026 dock->titleBarWidget()->setVisible(showTitlebars);
2027 }
2028 }
2029 }
2030
2031 bool success = KXmlGuiWindow::restoreState(state);
2032
2033 if (!success) {
2034 KXmlGuiWindow::restoreState(oldState);
2035 Q_FOREACH (QDockWidget *dock, dockWidgets()) {
2036 if (dock->titleBarWidget() && !dock->titleBarWidget()->inherits("KisUtilityTitleBar")) {
2037 dock->titleBarWidget()->setVisible(showTitlebars || dock->isFloating());
2038 }
2039 }
2040 return false;
2041 }
2042 return success;
2043}
2044
2046{
2047 QString md5 = sender()->property("md5").toString();
2049 KoResourceSP resource = rserver->resource(md5, "", "");
2050 if (resource) {
2051 restoreWorkspace(resource);
2052 }
2053 else {
2054 qWarning() << "Could not retrieve resource for" << md5;
2055 }
2056}
2057
2059{
2060 QList<KisKActionCollection *> actionCollections;
2061
2062 auto clients = guiFactory()->clients();
2063 int actionsCount = 0;
2064 for (const KisKXMLGUIClient *c : clients) {
2065 if (!c) {
2066 continue;
2067 }
2068 if (auto collection = c->actionCollection()) {
2069 actionCollections.append(collection);
2070 actionsCount += collection->count();
2071 }
2072 }
2073
2074 if (activeKisView()) {
2075 KisKActionCollection *layerActionCollection = new KisKActionCollection(0, "layeractions (disposable)");
2076 layerActionCollection->setComponentDisplayName(i18n("Layers/Masks"));
2077 KisNodeActivationActionCreatorVisitor v(layerActionCollection, viewManager()->nodeManager());
2078 activeKisView()->image()->rootLayer()->accept(v);
2079 actionCollections.append(layerActionCollection);
2080 actionsCount += layerActionCollection->count();
2081 }
2082
2083 d->commandBar->updateBar(actionCollections, actionsCount);
2084
2085 // The following line is needed to work around input method not working
2086 // on Windows.
2087 // See https://bugs.kde.org/show_bug.cgi?id=395598
2088 // and https://bugs.kde.org/show_bug.cgi?id=438122
2089 d->commandBar->activateWindow();
2090
2091 // The following line is present in Kate's version and was ported over
2092 // but I am sceptical of its use. I worry that it may subtly cause other
2093 // issues, and since the command bar appears to work fine without it, I
2094 // believe it may be better to leave it out. -- Alvin
2095 // centralWidget()->setFocusProxy(d->commandBar);
2096}
2097
2098void KisMainWindow::slotStoragesWarning(const QString &/*location*/)
2099{
2100 QString warning;
2102 warning = i18n("You don't have any resource bundles enabled.");
2103 }
2104
2105 if (!checkPaintOpAvailable()) {
2106 warning += i18n("\nThere are no brush presets available. Please enable a bundle that has presets before continuing.\n");
2107 QMessageBox::critical(this, i18nc("@title:window", "Krita"), warning);
2108
2109 QAction *action = actionCollection()->action("manage_bundles");
2110 if (action) {
2111 action->trigger();
2112 }
2113 }
2114
2116 QMessageBox::warning(this, i18nc("@title:window", "Krita"), warning + i18n("\nOnly your local resources are available."));
2117 }
2118
2119}
2120
2122{
2123 KisWorkspaceResourceSP workspace = res.dynamicCast<KisWorkspaceResource>();
2124
2125 bool success = restoreWorkspaceState(workspace->dockerState());
2126
2127 const bool showTitlebars = KisConfig(false).showDockerTitleBars();
2128 Q_FOREACH (QDockWidget *dock, dockWidgets()) {
2129 if (dock->titleBarWidget() && !dock->titleBarWidget()->inherits("KisUtilityTitleBar")) {
2130 dock->titleBarWidget()->setVisible(showTitlebars || dock->isFloating());
2131 }
2132 }
2133
2134 if (activeKisView()) {
2135 activeKisView()->resourceProvider()->notifyLoadingWorkspace(workspace);
2136 }
2137
2138 d->viewManager->notifyWorkspaceLoaded();
2139
2140 return success;
2141}
2142
2144{
2145 QByteArray currentWorkspace = saveState();
2146
2147 if (!d->workspaceBorrowedBy.isNull()) {
2148 if (other->id() == d->workspaceBorrowedBy) {
2149 // We're swapping our original workspace back
2150 d->workspaceBorrowedBy = QUuid();
2151 return currentWorkspace;
2152 } else {
2153 // Get our original workspace back before swapping with a third window
2154 KisMainWindow *borrower = KisPart::instance()->windowById(d->workspaceBorrowedBy);
2155 if (borrower) {
2156 QByteArray originalLayout = borrower->borrowWorkspace(this);
2157 borrower->restoreWorkspaceState(currentWorkspace);
2158
2159 d->workspaceBorrowedBy = other->id();
2160 return originalLayout;
2161 }
2162 }
2163 }
2164
2165 d->workspaceBorrowedBy = other->id();
2166 return currentWorkspace;
2167}
2168
2170{
2171 QByteArray workspaceA = a->borrowWorkspace(b);
2172 QByteArray workspaceB = b->borrowWorkspace(a);
2173
2174 a->restoreWorkspaceState(workspaceB);
2175 b->restoreWorkspaceState(workspaceA);
2176}
2177
2179{
2180 return d->viewManager;
2181}
2182
2184{
2185 if (!d->activeView->document())
2186 return;
2187
2188 KoDocumentInfo *docInfo = d->activeView->document()->documentInfo();
2189
2190 if (!docInfo)
2191 return;
2192
2193 KoDocumentInfoDlg *dlg = d->activeView->document()->createDocumentInfoDialog(this, docInfo);
2194
2195 if (dlg->exec()) {
2196 if (dlg->isDocumentSaved()) {
2197 d->activeView->document()->setModified(false);
2198 } else {
2199 d->activeView->document()->setModified(true);
2200 }
2201 }
2202
2203 delete dlg;
2204}
2205
2207{
2208 Q_FOREACH (QMdiSubWindow *subwin, d->mdiArea->subWindowList()) {
2209 if (subwin) {
2210 if(!subwin->close())
2211 return false;
2212 }
2213 }
2214
2215 return true;
2216}
2217
2219{
2220 // Do not close while KisMainWindow has the savingEntryMutex locked, bug409395.
2221 // After the background saving job is initiated, KisDocument blocks closing
2222 // while it saves itself.
2223 if (hackIsSaving()) {
2224 return;
2225 }
2227}
2228
2230{
2231 if (!activeView()) return;
2232
2233 KisDocument *document = activeView()->document();
2234 if (!document) return;
2235
2236 KisDlgImportImageSequence dlg(this, document);
2237
2238 if (dlg.exec() == QDialog::Accepted) {
2239 QStringList files = dlg.files();
2240 int firstFrame = dlg.firstFrame();
2241 int step = dlg.step();
2242 bool startFrom1 = dlg.startFrom1();
2243 bool autoAddHoldframes = dlg.autoAddHoldframes();
2244
2245
2246 KoUpdaterPtr updater =
2247 !document->fileBatchMode() ? viewManager()->createUnthreadedUpdater(i18n("Import frames")) : 0;
2248 KisAnimationImporter importer(document->image(), updater);
2249 int isAscending = dlg.isAscending();
2250 KisImportExportErrorCode status = importer.import(files, firstFrame, step, autoAddHoldframes, startFrom1, isAscending); // modify here, add a flag
2251
2252 if (!status.isOk() && !status.isInternalError()) {
2253 QString msg = status.errorMessage();
2254 if (!msg.isEmpty())
2255 QMessageBox::critical(qApp->activeWindow(), i18nc("@title:window", "Krita"), i18n("Could not finish import animation:\n%1", msg));
2256 }
2257 activeView()->canvasBase()->refetchDataFromImage();
2258 }
2259}
2260
2262{
2263 // Importing video requires ffmpeg, which is not available on Android.
2264#ifndef Q_OS_ANDROID
2265 KisDocument *document;
2267
2268 if (dlg.exec() == QDialog::Accepted) {
2269 const QTemporaryDir outputLocation(QDir::tempPath() + QDir::separator() + "krita" + QDir::separator() + "import_files");
2270 RenderedFrames renderedFrames = dlg.renderFrames(QDir(outputLocation.path()));
2271 dbgFile << "Frames rendered to directory: " << outputLocation.path();
2272 QStringList documentInfoList = dlg.documentInfo();
2273
2274 if (renderedFrames.isEmpty()) return;
2275
2276 dbgFile << "Animation Import options: " << documentInfoList;
2277
2278 int firstFrame = 0;
2279 const int step = documentInfoList[0].toInt();
2280 const int fps = documentInfoList[1].toInt();
2281 const int totalFrames = renderedFrames.framesNeedRelocation() ? (renderedFrames.renderedFrameTargetTimes.last() + 1) : renderedFrames.size() * step;
2282 const QString name = QFileInfo(documentInfoList[3]).fileName();
2283 const bool useCurrentDocument = documentInfoList[4].toInt();
2284 bool useDocumentColorSpace = false;
2285
2286 if ( useCurrentDocument ) {
2287 document = activeView()->document();
2288
2289 dbgFile << "Current frames:" << document->image()->animationInterface()->totalLength() << "total frames:" << totalFrames;
2290 if ( document->image()->animationInterface()->totalLength() < totalFrames ) {
2291 document->image()->animationInterface()->setDocumentRangeStartFrame(0);
2292 document->image()->animationInterface()->setDocumentRangeEndFrame(totalFrames);
2293 }
2294 } else {
2295 const int width = documentInfoList[5].toInt();
2296 const int height = documentInfoList[6].toInt();
2297 const double resolution = documentInfoList[7].toDouble();
2298
2299 const QString colorModel = documentInfoList[8];
2300 const QString colorDepth = documentInfoList[9];
2301 const QString profile = documentInfoList[10];
2302 useDocumentColorSpace = profile != "Default";
2303
2304 document = KisPart::instance()->createDocument();
2305 document->setObjectName(name);
2306
2307 KisPart::instance()->addDocument(document, false);
2308 const KoColorSpace *cs = KoColorSpaceRegistry::instance()->colorSpace(colorModel, colorDepth, profile);
2309 Q_ASSERT(cs);
2310
2311 QColor qc(Qt::white);
2312 qc.setAlpha(0);
2313 KoColor bgColor(qc, cs);
2314
2315 if (!document->newImage(name, width, height, cs, bgColor, KisConfig::RASTER_LAYER, 1, "", double(resolution / 72) )) {
2316 QMessageBox::critical(qApp->activeWindow(), i18nc("@title:window", "Krita"), i18n("Failed to create new document. Animation import aborted."));
2317 return;
2318 }
2319
2320 document->image()->animationInterface()->setFramerate(fps);
2321 document->image()->animationInterface()->setDocumentRangeStartFrame(0);
2322 document->image()->animationInterface()->setDocumentRangeEndFrame(totalFrames);
2323
2324 this->showDocument(document);
2325 }
2326
2327 KoUpdaterPtr updater =
2328 !document->fileBatchMode() ? viewManager()->createUnthreadedUpdater(i18n("Import frames")) : 0;
2329 KisAnimationImporter importer(document->image(), updater);
2330 KisImportExportErrorCode status = importer.import(renderedFrames.renderedFrameFiles, firstFrame, step, false, false, 0, useDocumentColorSpace, renderedFrames.renderedFrameTargetTimes);
2331
2332 if (!status.isOk() && !status.isInternalError()) {
2333 QString msg = status.errorMessage();
2334 if (!msg.isEmpty())
2335 QMessageBox::critical(qApp->activeWindow(), i18nc("@title:window", "Krita"), i18n("Could not finish import animation:\n%1", msg));
2336 }
2337
2338 activeView()->canvasBase()->refetchDataFromImage();
2339 document->image()->refreshGraphAsync();
2340 document->image()->waitForDone();
2341
2342 }
2343#endif
2344}
2345
2347{
2348 if (!activeView()) return;
2349
2350 KisImageSP image = viewManager()->image();
2351
2352 if (!image) return;
2353 if (!image->animationInterface()->hasAnimation()) return;
2354
2355 KisDocument *doc = viewManager()->document();
2356
2357 KisDlgAnimationRenderer dlgAnimationRenderer(doc, viewManager()->mainWindow());
2358 dlgAnimationRenderer.setCaption(i18n("Render Animation"));
2359 if (dlgAnimationRenderer.exec() == QDialog::Accepted) {
2360 KisAnimationRenderingOptions encoderOptions = dlgAnimationRenderer.getEncoderOptions();
2361 KisAnimationRender::render(doc, viewManager(), encoderOptions);
2362 }
2363}
2364
2366{
2367 if (!activeView()) return;
2368
2369 KisImageSP image = viewManager()->image();
2370
2371 if (!image) return;
2372 if (!image->animationInterface()->hasAnimation()) return;
2373
2374 KisDocument *doc = viewManager()->document();
2375
2376 KisConfig cfg(true);
2377
2378 KisPropertiesConfigurationSP settings = cfg.exportConfiguration("ANIMATION_EXPORT");
2379
2380 KisAnimationRenderingOptions encoderOptions;
2381 encoderOptions.fromProperties(settings);
2382
2383 KisAnimationRender::render(doc, viewManager(), encoderOptions);
2384}
2385
2387{
2389 KisKEditToolBar edit(factory(), this);
2390 connect(&edit, SIGNAL(newToolBarConfig()), this, SLOT(slotNewToolbarConfig()));
2391 (void) edit.exec();
2393}
2394
2396{
2397 KisApplication *kisApp = static_cast<KisApplication*>(qApp);
2398 kisApp->askResetConfig();
2399}
2400
2402{
2403 applyMainWindowSettings(d->windowStateConfig);
2404
2406 Q_UNUSED(factory);
2407
2408 // Check if there's an active view
2409 if (!d->activeView)
2410 return;
2411
2412 plugActionList("toolbarlist", d->toolbarList);
2413
2421}
2422
2424{
2425 //dbgUI <<"KisMainWindow::slotToolbarToggled" << sender()->name() <<" toggle=" << true;
2426 // The action (sender) and the toolbar have the same name
2427 KisToolBar * bar = toolBar(sender()->objectName());
2428 if (bar) {
2429 if (toggle) {
2430 bar->show();
2431 }
2432 else {
2433 bar->hide();
2434 }
2435
2436 if (d->activeView && d->activeView->document()) {
2438 }
2439 } else
2440 warnUI << "slotToolbarToggled : Toolbar " << sender()->objectName() << " not found!";
2441}
2442
2444{
2445#ifdef Q_OS_ANDROID
2446 // Full-screening on Android applies to the entire application, not this
2447 // window in particular. Qt tries to paper over that matter, but it causes
2448 // some pretty horrid flickering when the main window gets hidden. We just
2449 // talk to the Android interface directly to get around that.
2451 d->fullScreenMode->setChecked(fullScreen);
2452#else
2453 KisConfig cfg(false);
2454 cfg.setFullscreenMode(fullScreen);
2455
2456 if (fullScreen) {
2457 setWindowState(windowState() | Qt::WindowFullScreen); // set
2458 } else {
2459 setWindowState(windowState() & ~Qt::WindowFullScreen); // reset
2460 }
2461 d->fullScreenMode->setChecked(isFullScreen());
2462#endif
2463}
2464
2466{
2467 QDockWidget* dockWidget = 0;
2468 const bool showTitlebars = KisConfig(false).showDockerTitleBars();
2469
2470 if (!d->dockWidgetsMap.contains(factory->id())) {
2471 KisScopedPerformanceLogger perfLog(QStringLiteral("KisMainWindow::createDockWidget(%1)").arg(factory->id()));
2472 dockWidget = factory->createDockWidget();
2473 KAcceleratorManager::setNoAccel(dockWidget);
2474
2475 // It is quite possible that a dock factory cannot create the dock; don't
2476 // do anything in that case.
2477 if (!dockWidget) {
2478 warnKrita << "Could not create docker for" << factory->id();
2479 return 0;
2480 }
2481
2482 KoDockWidgetTitleBar *titleBar = dynamic_cast<KoDockWidgetTitleBar*>(dockWidget->titleBarWidget());
2483
2484 // Check if the dock widget is supposed to be collapsible
2485 if (!dockWidget->titleBarWidget()) {
2486 titleBar = new KoDockWidgetTitleBar(dockWidget);
2487 dockWidget->setTitleBarWidget(titleBar);
2488 }
2489 if (titleBar) {
2490 titleBar->setFont(KisUiFont::dockFont());
2491 }
2492
2493 if (dockWidget->titleBarWidget() && !dockWidget->titleBarWidget()->inherits("KisUtilityTitleBar")) {
2494 dockWidget->titleBarWidget()->setVisible(KisConfig(true).showDockerTitleBars());
2495 }
2496
2497 dockWidget->setObjectName(factory->id());
2498 dockWidget->setParent(this);
2499 if (!showTitlebars) {
2500 if (dockWidget->titleBarWidget() && !dockWidget->titleBarWidget()->inherits("KisUtilityTitleBar")) {
2501 dockWidget->titleBarWidget()->setVisible(false);
2502 }
2503 dockWidget->setFeatures(QDockWidget::NoDockWidgetFeatures);
2504 }
2505 if (dockWidget->widget() && dockWidget->widget()->layout())
2506 dockWidget->widget()->layout()->setContentsMargins(1, 1, 1, 1);
2507
2508 Qt::DockWidgetArea side = Qt::RightDockWidgetArea;
2509 bool visible = true;
2510
2511 switch (factory->defaultDockPosition()) {
2513 dockWidget->setFloating(true); // position nicely?
2514 break;
2516 side = Qt::TopDockWidgetArea; break;
2518 side = Qt::LeftDockWidgetArea; break;
2520 side = Qt::BottomDockWidgetArea; break;
2522 side = Qt::RightDockWidgetArea; break;
2524 default:
2525 side = Qt::RightDockWidgetArea;
2526 visible = false;
2527 }
2528
2529 KConfigGroup group = d->windowStateConfig.group("DockWidget " + factory->id());
2530 side = static_cast<Qt::DockWidgetArea>(group.readEntry("DockArea", static_cast<int>(side)));
2531 if (side == Qt::NoDockWidgetArea) side = Qt::RightDockWidgetArea;
2532
2533 addDockWidget(side, dockWidget);
2534 if (!visible) {
2535 dockWidget->hide();
2536 }
2537
2538 bool locked = group.readEntry("Locked", false);
2539 if (titleBar && locked) {
2540 titleBar->setLocked(true);
2541 }
2542 else if ( dockWidget->titleBarWidget()->inherits("KisUtilityTitleBar") && locked){
2543 KisUtilityTitleBar *utilityTitleBar = dynamic_cast<KisUtilityTitleBar*>(dockWidget->titleBarWidget());
2544 utilityTitleBar->setLocked(true);
2545 }
2546
2547 d->dockWidgetsMap.insert(factory->id(), dockWidget);
2548 }
2549 else {
2550 dockWidget = d->dockWidgetsMap[factory->id()];
2551 }
2552
2553#ifdef Q_OS_MACOS
2554 dockWidget->setAttribute(Qt::WA_MacSmallSize, true);
2555#endif
2556 dockWidget->setFont(KisUiFont::dockFont());
2557
2558 connect(dockWidget, SIGNAL(dockLocationChanged(Qt::DockWidgetArea)), this, SLOT(forceDockTabFonts()));
2559
2560 return dockWidget;
2561}
2562
2564{
2565 Q_FOREACH (QObject *child, children()) {
2566 if (child->inherits("QTabBar")) {
2567 ((QTabBar *)child)->setFont(KisUiFont::dockFont());
2568 }
2569 }
2570}
2571
2573{
2574 KisConfig cfg(true);
2575 QString themeFromConfig = cfg.widgetStyle();
2576
2577 Q_FOREACH (auto key, d->actionMap.keys()) { // find checked style to save to config
2578 if(d->actionMap.value(key)->isChecked()) {
2579 cfg.setWidgetStyle(key);
2580 qApp->setProperty(currentUnderlyingStyleNameProperty, key);
2581 qApp->setStyle(key);
2582
2583 // When switching to a style that uses system colors, reset the theme
2584#ifndef Q_OS_HAIKU
2585 QString currentStyle = qApp->style()->objectName();
2586#if (QT_VERSION < QT_VERSION_CHECK(6, 0, 0))
2587 if (currentStyle == "macintosh" || currentStyle == "windowsvista") {
2588 d->themeManager->setCurrentTheme("System");
2589 }
2590#else
2591 if (currentStyle == "macos" || currentStyle == "windowsvista" || currentStyle == "windows11") {
2592 d->themeManager->setCurrentTheme("System");
2593 }
2594#endif
2595#endif
2596 }
2597 }
2598}
2599
2601{
2602 return d->dockWidgetsMap.values();
2603}
2604
2605QDockWidget* KisMainWindow::dockWidget(const QString &id)
2606{
2607 if (!d->dockWidgetsMap.contains(id)) return 0;
2608 return d->dockWidgetsMap[id];
2609}
2610
2612{
2614
2615 observers.append(static_cast<KoCanvasObserverBase*>(KisPart::instance()->playbackEngine()));
2616
2617 Q_FOREACH (QDockWidget *docker, dockWidgets()) {
2618 KoCanvasObserverBase *observer = dynamic_cast<KoCanvasObserverBase*>(docker);
2619 if (observer) {
2620 observers << observer;
2621 }
2622 else {
2623 warnKrita << docker << "is not a canvas observer";
2624 }
2625 }
2626
2627 return observers;
2628}
2629
2630
2631void KisMainWindow::toggleDockersVisibility(bool visible, bool onWelcomePage)
2632{
2633 if (!visible) {
2634 d->dockerStateBeforeHiding = saveState();
2635
2636 Q_FOREACH (QObject* widget, children()) {
2637 if (widget->inherits("QDockWidget")) {
2638 QDockWidget* dw = static_cast<QDockWidget*>(widget);
2639 if (dw->isVisible() && !(onWelcomePage && dw->property("ShowOnWelcomePage").toBool())) {
2640 dw->hide();
2641 }
2642 }
2643 }
2644 }
2645 else {
2646 restoreState(d->dockerStateBeforeHiding);
2647 }
2648}
2649
2651{
2652 bool enabled = (activeKisView() != 0);
2653
2654 d->mdiCascade->setEnabled(enabled);
2655 d->mdiNextWindow->setEnabled(enabled);
2656 d->mdiPreviousWindow->setEnabled(enabled);
2657 d->mdiTile->setEnabled(enabled);
2658 d->close->setEnabled(enabled);
2659 d->closeAll->setEnabled(enabled);
2660
2661 setActiveSubWindow(d->mdiArea->activeSubWindow());
2662
2672 QMdiSubWindow *subWindow = d->mdiArea->currentSubWindow();
2673 if (subWindow) {
2674 QMenu *menu = subWindow->systemMenu();
2675 if (menu && menu->actions().size() == 8) {
2676 Q_FOREACH (QAction *action, menu->actions()) {
2677 action->setShortcut(QKeySequence());
2678
2679 }
2680 menu->actions().last()->deleteLater();
2681 }
2682 }
2683
2684 d->actionManager()->updateGUI();
2685}
2686
2688{
2692 if (viewManager() && viewManager()->selectionManager()) {
2694 }
2695
2696 KisPart *kisPart = KisPart::instance();
2698 if (!layoutManager->primaryWorkspaceFollowsFocus()) return;
2699
2700 QUuid primary = layoutManager->primaryWindowId();
2701 if (primary.isNull()) return;
2702
2703 if (d->id == primary) {
2704 if (!d->workspaceBorrowedBy.isNull()) {
2705 KisMainWindow *borrower = kisPart->windowById(d->workspaceBorrowedBy);
2706 if (!borrower) return;
2707 swapWorkspaces(this, borrower);
2708 }
2709 } else {
2710 if (d->workspaceBorrowedBy == primary) return;
2711
2712 KisMainWindow *primaryWindow = kisPart->windowById(primary);
2713 if (!primaryWindow) return;
2714 swapWorkspaces(this, primaryWindow);
2715 }
2716}
2717
2718
2720{
2721 QMenu *menu = d->windowMenu->menu();
2722 menu->clear();
2723
2724#ifndef Q_OS_ANDROID
2725 menu->addAction(d->newWindow);
2726#endif
2727 menu->addAction(d->documentMenu);
2728
2729 QMenu *docMenu = d->documentMenu->menu();
2730 docMenu->clear();
2731
2732 QFontMetrics fontMetrics = docMenu->fontMetrics();
2733 QRect geom = this->geometry();
2734 QPoint p(geom.width() / 2 + geom.left(), geom.height() / 2 + geom.top());
2735 QScreen *screen = qApp->screenAt(p);
2736 int fileStringWidth = 300;
2737 if (screen) {
2738 fileStringWidth = int(screen->availableGeometry().width() * .40f);
2739 }
2740 Q_FOREACH (QPointer<KisDocument> doc, KisPart::instance()->documents()) {
2741 if (doc) {
2742 QString title = fontMetrics.elidedText(doc->path(), Qt::ElideMiddle, fileStringWidth);
2743 if (title.isEmpty() && doc->image()) {
2744 title = doc->image()->objectName();
2745 }
2746 QAction *action = docMenu->addAction(title);
2747 action->setIcon(qApp->windowIcon());
2748 connect(action, SIGNAL(triggered()), d->documentMapper, SLOT(map()));
2749 d->documentMapper->setMapping(action, doc);
2750 }
2751 }
2752
2753 menu->addAction(d->workspaceMenu);
2754 QMenu *workspaceMenu = d->workspaceMenu->menu();
2755 workspaceMenu->clear();
2757 KisResourceIterator resourceIterator(&resourceModel);
2758 KisMainWindow *m_this = this;
2759
2760 while (resourceIterator.hasNext()) {
2761 KisResourceItemSP resource = resourceIterator.next();
2762 QAction *action = workspaceMenu->addAction(resource->name());
2763 action->setProperty("md5", QVariant::fromValue<QString>(resource->md5sum()));
2764 connect(action, SIGNAL(triggered()), this, SLOT(restoreWorkspace()));
2765 }
2766 workspaceMenu->addSeparator();
2767 connect(workspaceMenu->addAction(i18nc("@action:inmenu", "&Import Workspace...")),
2768 &QAction::triggered,
2769 this,
2770 [&]()
2771 {
2773
2774 KoFileDialog dialog(0, KoFileDialog::OpenFile, "OpenDocument");
2775 dialog.setMimeTypeFilters(mimeTypes);
2776 dialog.setCaption(i18nc("@title:window", "Choose File to Add"));
2777 QString filename = dialog.filename();
2778
2780 });
2781
2782 connect(workspaceMenu->addAction(i18nc("@action:inmenu", "&New Workspace...")),
2783 &QAction::triggered,
2784 [=]() {
2785 QString name;
2786 name = QInputDialog::getText(this, i18nc("@title:window", "New Workspace..."),
2787 i18nc("@label:textbox", "Name:"));
2788 if (name.isEmpty()) {
2789 return;
2790 }
2791
2793 workspace->setDockerState(m_this->saveState());
2794 workspace->setImage(layoutThumbnail());
2795 workspace->setValid(true);
2796
2797 // this line must happen before we save the workspace to resource folder or other places
2798 // because it mostly just triggers palettes to be saved into the workspace
2799 d->viewManager->canvasResourceProvider()->notifySavingWorkspace(workspace);
2800 workspace->setValid(true);
2801
2802 workspace->setFilename(name.replace(" ", "_") + workspace->defaultFileExtension());
2803 workspace->setName(name);
2804
2806 });
2807
2808 // TODO: What to do about delete?
2809 // workspaceMenu->addAction(i18nc("@action:inmenu", "&Delete Workspace..."));
2810
2811 menu->addSeparator();
2812 menu->addAction(d->close);
2813 menu->addAction(d->closeAll);
2814 if (d->mdiArea->viewMode() == QMdiArea::SubWindowView) {
2815 menu->addSeparator();
2816 menu->addAction(d->mdiTile);
2817 menu->addAction(d->mdiCascade);
2818 }
2819 menu->addSeparator();
2820 menu->addAction(d->mdiNextWindow);
2821 menu->addAction(d->mdiPreviousWindow);
2822 menu->addSeparator();
2823
2824 QList<QMdiSubWindow *> windows = d->mdiArea->subWindowList();
2825 for (int i = 0; i < windows.size(); ++i) {
2826 QPointer<KisView>child = qobject_cast<KisView*>(windows.at(i)->widget());
2827 if (child && child->document()) {
2828 QString text;
2829 if (i < 9) {
2830 text = i18n("&%1 %2", i + 1, fontMetrics.elidedText(child->document()->path(), Qt::ElideMiddle, fileStringWidth));
2831 }
2832 else {
2833 text = i18n("%1 %2", i + 1, fontMetrics.elidedText(child->document()->path(), Qt::ElideMiddle, fileStringWidth));
2834 }
2835
2836 QAction *action = menu->addAction(text);
2837 action->setIcon(qApp->windowIcon());
2838 action->setCheckable(true);
2839 action->setChecked(child == activeKisView());
2840 connect(action, SIGNAL(triggered()), d->windowMapper, SLOT(map()));
2841 d->windowMapper->setMapping(action, windows.at(i));
2842 }
2843 }
2844
2845 bool showMdiArea = windows.count( ) > 0;
2846 if (!showMdiArea) {
2847 showWelcomeScreen(true); // see workaround in function in header
2848 }
2849
2850 // enable/disable the toolbox docker if there are no documents open
2851 Q_FOREACH (QObject* widget, children()) {
2852 if (widget->inherits("QDockWidget")) {
2853 QDockWidget* dw = static_cast<QDockWidget*>(widget);
2854
2855 if ( dw->objectName() == "ToolBox") {
2856 dw->setEnabled(showMdiArea);
2857 }
2858 }
2859 }
2860}
2861
2863{
2864 bool onlyOne = false;
2865 if (d->mdiArea->subWindowList().size() == 1 && d->mdiArea->viewMode() == QMdiArea::SubWindowView) {
2866 onlyOne = true;
2867 }
2868 Q_FOREACH (QMdiSubWindow *subwin, d->mdiArea->subWindowList()) {
2869 if (onlyOne) {
2870 subwin->setWindowFlags(subwin->windowFlags() | Qt::FramelessWindowHint);
2871 subwin->showMaximized();
2872 } else {
2873 subwin->setWindowFlags((subwin->windowFlags() | Qt::FramelessWindowHint) ^ Qt::FramelessWindowHint);
2874 }
2875 }
2876}
2877
2879{
2880 if (!window) {
2882 return;
2883 }
2884 QMdiSubWindow *subwin = qobject_cast<QMdiSubWindow *>(window);
2885 //dbgKrita << "setActiveSubWindow();" << subwin << d->activeSubWindow;
2886
2887 if (subwin && subwin != d->activeSubWindow) {
2888 KisView *view = qobject_cast<KisView *>(subwin->widget());
2889 //dbgKrita << "\t" << view << activeView();
2890 if (view && view != activeView()) {
2891 setActiveView(view);
2892 }
2893 d->activeSubWindow = subwin;
2894 }
2896 d->actionManager()->updateGUI();
2897}
2898
2900{
2901 KisConfig cfg(true);
2902 QMdiArea::ViewMode viewMode = (QMdiArea::ViewMode)cfg.readEntry<int>("mdi_viewmode", (int)QMdiArea::TabbedView);
2903 d->mdiArea->setViewMode(viewMode);
2904 //Do it here to avoid the wrong icon briefly flashing
2906
2907 Q_FOREACH (QMdiSubWindow *subwin, d->mdiArea->subWindowList()) {
2908 subwin->setOption(QMdiSubWindow::RubberBandMove, cfg.readEntry<int>("mdi_rubberband", cfg.useOpenGL()));
2909 subwin->setOption(QMdiSubWindow::RubberBandResize, cfg.readEntry<int>("mdi_rubberband", cfg.useOpenGL()));
2910 if (viewMode == QMdiArea::TabbedView) {
2911 subwin->setWindowState(Qt::WindowMaximized);
2912 }
2913
2921 if (viewMode == QMdiArea::TabbedView) {
2922 Qt::WindowFlags oldFlags = subwin->windowFlags();
2923 Qt::WindowFlags flags = oldFlags;
2924
2925 flags &= ~Qt::WindowStaysOnTopHint;
2926 flags &= ~Qt::WindowStaysOnBottomHint;
2927
2928 if (flags != oldFlags) {
2929 subwin->setWindowFlags(flags);
2930 subwin->showMaximized();
2931 }
2932 }
2933 }
2934#ifdef Q_OS_MACOS
2936#endif
2937
2938 KConfigGroup group( KSharedConfig::openConfig(), "theme");
2939#ifndef Q_OS_HAIKU
2940 d->themeManager->setCurrentTheme(group.readEntry("Theme", "Krita dark"));
2941#endif
2942 d->actionManager()->updateGUI();
2943
2944 QString s = cfg.getMDIBackgroundColor();
2946 QBrush brush(c.toQColor());
2947 d->mdiArea->setBackground(brush);
2948
2949 QString backgroundImage = cfg.getMDIBackgroundImage();
2950 if (backgroundImage != "") {
2951 QImage image(backgroundImage);
2952 QBrush brush(image);
2953 d->mdiArea->setBackground(brush);
2954 }
2955
2956 d->mdiArea->update();
2957
2958 qApp->setFont(KisUiFont::normalFont());
2959
2960 Q_FOREACH (QObject* widget, children()) {
2961 if (widget->inherits("QDockWidget")) {
2962 QDockWidget* dw = static_cast<QDockWidget*>(widget);
2963 dw->setFont(KisUiFont::dockFont());
2964 }
2965 }
2966}
2967
2968KisView* KisMainWindow::newView(QObject *document, QMdiSubWindow *subWindow)
2969{
2970 KisDocument *doc = qobject_cast<KisDocument*>(document);
2971 KisView *view = addViewAndNotifyLoadingCompleted(doc, subWindow);
2972 d->actionManager()->updateGUI();
2973
2974 return view;
2975}
2976
2978{
2979#ifdef Q_OS_ANDROID
2980 // Check if current mainwindow exists, just to be sure.
2981 if (KisPart::instance()->currentMainwindow()) {
2982 QMessageBox::warning(this, i18nc("@title:window", "Krita"),
2983 "Creating a New Main Window is unsupported on Android");
2984 return;
2985 }
2986#endif
2988 mainWindow->initializeGeometry();
2989 mainWindow->show();
2990}
2991
2993{
2994 if (d->mdiArea->currentSubWindow()) {
2995 d->mdiArea->currentSubWindow()->close();
2996 d->actionManager()->updateGUI();
2997 }
2998}
2999
3001{
3002 // print error if the lcms engine is not available
3003 if (!KoColorSpaceEngineRegistry::instance()->contains("icc")) {
3004 // need to wait 1 event since exiting here would not work.
3005 m_errorMessage = i18n("The Krita LittleCMS color management plugin is not installed. Krita will quit now.");
3006 m_dieOnError = true;
3007 QTimer::singleShot(0, this, SLOT(showErrorAndDie()));
3008 return;
3009 }
3010
3012
3013 // window is created signal (used in Python)
3014 // there must be some asynchronous things happening in the constructor, because the window cannot
3015 // be referenced until after this timeout is done
3017}
3018
3020{
3021 QMessageBox::critical(qApp->activeWindow(), i18nc("@title:window", "Installation error"), m_errorMessage);
3022 if (m_dieOnError) {
3023 exit(10);
3024 }
3025}
3026
3028{
3029 KisAboutApplication dlg(this);
3030 dlg.exec();
3031}
3032
3034{
3035 if (!d->mdiArea) return 0;
3036 QMdiSubWindow *activeSubWindow = d->mdiArea->activeSubWindow();
3037 //dbgKrita << "activeKisView" << activeSubWindow;
3038 if (!activeSubWindow) return 0;
3039 return qobject_cast<KisView*>(activeSubWindow->widget());
3040}
3041
3043{
3044 KIS_ASSERT_RECOVER_NOOP(controller == KoToolManager::instance()->activeCanvasController());
3045 bool isOurOwnView = false;
3046
3047 Q_FOREACH (QPointer<KisView> view, KisPart::instance()->views()) {
3048 if (view && view->canvasController() == controller) {
3049 isOurOwnView = view->mainWindow() == this;
3050 }
3051 }
3052
3053 if (!isOurOwnView) return;
3054
3055 Q_FOREACH (QWidget *w, optionWidgetList) {
3056#ifdef Q_OS_MACOS
3057 w->setAttribute(Qt::WA_MacSmallSize, true);
3058#endif
3059 w->setFont(KisUiFont::dockFont());
3060 }
3061
3062 if (d->toolOptionsDocker) {
3063 d->toolOptionsDocker->setOptionWidgets(optionWidgetList);
3064 }
3065 else {
3066 d->viewManager->paintOpBox()->newOptionWidgets(optionWidgetList);
3067 }
3068}
3069
3071{
3072 KisActionManager *actionManager = d->actionManager();
3073
3074
3075
3080 d->fullScreenMode = actionManager->createStandardAction(KStandardAction::FullScreen, this, SLOT(viewFullscreen(bool)));
3081
3082 d->recentFiles = KStandardAction::openRecent(this, SLOT(slotFileOpenRecent(QUrl)), actionCollection());
3083
3085 d->saveAction->setActivationFlags(KisAction::IMAGE_IS_WRITABLE);
3086
3088 d->saveActionAs->setActivationFlags(KisAction::ACTIVE_IMAGE);
3089
3091 d->undo->setActivationFlags(KisAction::ACTIVE_IMAGE);
3092
3094 d->redo->setActivationFlags(KisAction::ACTIVE_IMAGE);
3095
3096 d->undoActionsUpdateManager.reset(new KisUndoActionsUpdateManager(d->undo, d->redo));
3097 d->undoActionsUpdateManager->setCurrentDocument(d->activeView ? d->activeView->document() : 0);
3098
3099 d->importAnimation = actionManager->createAction("file_import_animation");
3100 connect(d->importAnimation, SIGNAL(triggered()), this, SLOT(importAnimation()));
3101
3102 // Importing video requires ffmpeg, which is not available on Android.
3103#ifndef Q_OS_ANDROID
3104 d->importVideoAnimation = actionManager->createAction("file_import_video_animation");
3105 connect(d->importVideoAnimation, SIGNAL(triggered()), this, SLOT(importVideoAnimation()));
3106#endif
3107
3108 d->renderAnimation = actionManager->createAction("render_animation");
3109 d->renderAnimation->setActivationFlags(KisAction::IMAGE_HAS_ANIMATION);
3110 connect( d->renderAnimation, SIGNAL(triggered()), this, SLOT(renderAnimation()));
3111
3112 d->renderAnimationAgain = actionManager->createAction("render_animation_again");
3113 d->renderAnimationAgain->setActivationFlags(KisAction::IMAGE_HAS_ANIMATION);
3114 connect( d->renderAnimationAgain, SIGNAL(triggered()), this, SLOT(renderAnimationAgain()));
3115
3116 d->closeAll = actionManager->createAction("file_close_all");
3117 connect(d->closeAll, SIGNAL(triggered()), this, SLOT(slotFileCloseAll()));
3118
3119 d->importFile = actionManager->createAction("file_import_file");
3120 d->importFile->setActivationFlags(KisAction::IMAGE_IS_WRITABLE);
3121 connect(d->importFile, SIGNAL(triggered(bool)), this, SLOT(slotImportFile()));
3122
3123 d->exportFile = actionManager->createAction("file_export_file");
3124 connect(d->exportFile, SIGNAL(triggered(bool)), this, SLOT(slotExportFile()));
3125
3126 d->exportFileAdvance = actionManager->createAction("file_export_advanced");
3127 connect(d->exportFileAdvance, SIGNAL(triggered(bool)), this, SLOT(slotExportAdvance()));
3128
3129 /* The following entry opens the document information dialog. Since the action is named so it
3130 intends to show data this entry should not have a trailing ellipses (...). */
3131 d->showDocumentInfo = actionManager->createAction("file_documentinfo");
3132 connect(d->showDocumentInfo, SIGNAL(triggered(bool)), this, SLOT(slotDocumentInfo()));
3133#ifndef Q_OS_HAIKU
3134 d->themeManager->setThemeMenuAction(new KActionMenu(i18nc("@action:inmenu", "&Themes"), this));
3135 d->themeManager->registerThemeActions(actionCollection());
3136 connect(d->themeManager, SIGNAL(signalThemeChanged()), this, SLOT(slotThemeChanged()), Qt::QueuedConnection);
3137 connect(this, SIGNAL(themeChanged()), d->welcomePage, SLOT(slotUpdateThemeColors()), Qt::UniqueConnection);
3138#endif
3139 d->toggleDockers = actionManager->createAction("view_toggledockers");
3140
3141
3142 KisConfig(true).showDockers(true);
3143 d->toggleDockers->setChecked(true);
3144 connect(d->toggleDockers, SIGNAL(toggled(bool)), SLOT(toggleDockersVisibility(bool)));
3145
3146 d->resetConfigurations = actionManager->createAction("reset_configurations");
3147 connect(d->resetConfigurations, SIGNAL(triggered()), this, SLOT(slotResetConfigurations()));
3148
3149#ifndef Q_OS_ANDROID
3150 d->toggleDetachCanvas = actionManager->createAction("view_detached_canvas");
3151 d->toggleDetachCanvas->setChecked(false);
3152 connect(d->toggleDetachCanvas, SIGNAL(toggled(bool)), SLOT(setCanvasDetached(bool)));
3153#endif
3154 setCanvasDetached(false);
3155
3156 d->toggleDockerTitleBars = actionManager->createAction("view_toggledockertitlebars");
3157 d->toggleDockerTitleBars->setChecked(KisConfig(false).showDockerTitleBars());
3158 connect(d->toggleDockerTitleBars, SIGNAL(toggled(bool)), SLOT(showDockerTitleBars(bool)));
3159
3160 actionCollection()->addAction("settings_dockers_menu", d->dockWidgetMenu);
3161 actionCollection()->addAction("window", d->windowMenu);
3162
3163 actionCollection()->addAction("style_menu", d->styleMenu); // for widget styles: breeze, fusion, etc
3164
3165 d->mdiCascade = actionManager->createAction("windows_cascade");
3166 connect(d->mdiCascade, SIGNAL(triggered()), d->mdiArea, SLOT(cascadeSubWindows()));
3167
3168 d->mdiTile = actionManager->createAction("windows_tile");
3169 connect(d->mdiTile, SIGNAL(triggered()), d->mdiArea, SLOT(tileSubWindows()));
3170
3171 d->mdiNextWindow = actionManager->createAction("windows_next");
3172 connect(d->mdiNextWindow, SIGNAL(triggered()), d->mdiArea, SLOT(activateNextSubWindow()));
3173
3174 d->mdiPreviousWindow = actionManager->createAction("windows_previous");
3175 connect(d->mdiPreviousWindow, SIGNAL(triggered()), d->mdiArea, SLOT(activatePreviousSubWindow()));
3176
3177#ifdef Q_OS_ANDROID
3178 d->showDonationManagementDialog = actionManager->createAction("manage_donations");
3179 connect(d->showDonationManagementDialog,
3180 &QAction::triggered,
3181 this,
3182 &KisMainWindow::slotShowDonationManagementDialog);
3183
3185 if (androidDonations) {
3186 d->manageSubscriptions = actionManager->createAction("manage_subscriptions");
3187 connect(d->manageSubscriptions,
3188 &QAction::triggered,
3189 androidDonations,
3191 connect(androidDonations,
3193 this,
3194 &KisMainWindow::slotShowDonationManagementDialog,
3195 Qt::QueuedConnection);
3196 }
3197#if KRITA_QT_HAS_ANDROID_QPLATFORMSCREEN_DENSITY_ADJUSTMENT
3199 if (androidScaling && androidScaling->isSupported()) {
3200 d->changeInterfaceScale = actionManager->createAction("change_interface_scale");
3201 connect(d->changeInterfaceScale, &QAction::triggered, this, &KisMainWindow::slotChangeInterfaceScale);
3202 }
3203#endif
3204#else
3205 d->newWindow = actionManager->createAction("view_newwindow");
3206 connect(d->newWindow, SIGNAL(triggered(bool)), this, SLOT(newWindow()));
3207#endif
3208
3210
3211 d->showSessionManager = actionManager->createAction("file_sessions");
3212 connect(d->showSessionManager, SIGNAL(triggered(bool)), this, SLOT(slotShowSessionManager()));
3213
3214 d->commandBarAction = actionManager->createAction("command_bar_open");
3215 connect(d->commandBarAction, SIGNAL(triggered(bool)), this, SLOT(openCommandBar()));
3216
3218
3219 for (int i = 0; i < 2; i++) {
3220 d->expandingSpacers[i] = new KisAction(i18n("Expanding Spacer"));
3221 d->expandingSpacers[i]->setDefaultWidget(new QWidget(this));
3222 d->expandingSpacers[i]->defaultWidget()->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
3223 actionManager->addAction(QString("expanding_spacer_%1").arg(i), d->expandingSpacers[i]);
3224 }
3225
3226 {
3227 using namespace KisWidgetConnectionUtils;
3228
3229 QAction *action = actionManager->createAction("lock_toolbars");
3230 connectControl(action, KisToolBar::toolBarStateModel(), "toolBarsLocked");
3231 }
3232}
3233
3235{
3236 Q_FOREACH (KisToolBar *toolBar, toolBars()) {
3237 toolBar->layout()->setSpacing(4);
3238 toolBar->setStyleSheet("QToolBar { border: none }"); // has a border in "Fusion" style that people don't like
3239
3240 // Hide text for buttons with an icon in the toolbar
3241 Q_FOREACH (QAction *ac, toolBar->actions()){
3242 if (ac->icon().pixmap(QSize(1,1)).isNull() == false){
3243 ac->setPriority(QAction::LowPriority);
3244 } else {
3245 ac->setIcon(QIcon());
3246 }
3247 }
3248 }
3249}
3250
3252{
3253 // if the user didn's specify the geometry on the command line (does anyone do that still?),
3254 // we first figure out some good default size and restore the x,y position. See bug 285804Z.
3255 KConfigGroup cfg = d->windowStateConfig;
3256 QByteArray geom = QByteArray::fromBase64(cfg.readEntry("ko_geometry", QByteArray()));
3257 if (!restoreGeometry(geom)) {
3258 QRect desk = this->screen()->availableGeometry();
3259
3260 quint32 x = desk.x();
3261 quint32 y = desk.y();
3262 quint32 w = 0;
3263 quint32 h = 0;
3264
3265 // Default size -- maximize on small screens, something useful on big screens
3266 const int deskWidth = desk.width();
3267 if (deskWidth > 1024) {
3268 // a nice width, and slightly less than total available
3269 // height to compensate for the window decs
3270 w = (deskWidth / 3) * 2;
3271 h = (desk.height() / 3) * 2;
3272 }
3273 else {
3274 w = desk.width();
3275 h = desk.height();
3276 }
3277
3278 x += (desk.width() - w) / 2;
3279 y += (desk.height() - h) / 2;
3280
3281 move(x,y);
3282 setGeometry(geometry().x(), geometry().y(), w, h);
3283 }
3284#ifdef Q_OS_ANDROID
3285 // We handle full-screening differently on Android, see viewFullScreen.
3286 bool fullScreen = KisAndroidUtils::isInFullScreen();
3287#else
3288 bool fullScreen = isFullScreen();
3289#endif
3290 d->fullScreenMode->setChecked(fullScreen);
3291}
3292
3294{
3295 QDesktopServices::openUrl(QUrl("https://docs.krita.org"));
3296}
3297
3299{
3300 Q_FOREACH (QDockWidget *dock, dockWidgets()) {
3301 if (dock->titleBarWidget() && !dock->titleBarWidget()->inherits("KisUtilityTitleBar")) {
3302 dock->titleBarWidget()->setVisible(show || dock->isFloating());
3303 }
3304 }
3305
3306 KisConfig cfg(true);
3307 cfg.setShowDockerTitleBars(show);
3308}
3309
3311{
3312 if (finished) {
3314 }
3315}
3316
3318{
3319 QScreen *screen = QGuiApplication::primaryScreen();
3320
3321 for (QWindow* window: QGuiApplication::topLevelWindows()) {
3322 // Android: we shouldn't transform Window managers independent of its child
3323 if ((window->type() == Qt::Popup)
3324 && (window->flags() & Qt::FramelessWindowHint) == 0
3325 && (window->geometry().topLeft() != QPoint(0, 0))) {
3326 // We are using reversed values. Because geometry returned is not the updated
3327 // one, but the previous one.
3328 int screenHeight = screen->geometry().width();
3329 int screenWidth = screen->geometry().height();
3330
3331 // scaling
3332 int new_x = (window->position().x() * screenWidth) / screenHeight;
3333 int new_y = (window->position().y() * screenHeight) / screenWidth;
3334
3335 // window width or height shouldn't change
3336 int winWidth = window->geometry().width();
3337 int winHeight = window->geometry().height();
3338
3339 // Try best to not let the window go beyond screen.
3340 if (new_x > screenWidth - winWidth) {
3341 new_x = screenWidth - winWidth;
3342 if (new_x < 0)
3343 new_x = 0;
3344 }
3345 if (new_y > screenHeight - winHeight) {
3346 new_y = screenHeight - winHeight;
3347 if (new_y < 0)
3348 new_y = 0;
3349 }
3350
3351 window->setPosition(QPoint(new_x, new_y));
3352 }
3353 }
3354}
3355
3366
3372
3373// One action has only one icon override, local XML contains ActionIconOverrides like so:
3374// <ActionIconOverrides>
3375// <Action name="erase_action" icon="wheel-sectors"/>
3376// <Action name="about_krita" icon="application-exit"/>
3377// </ActionIconOverrides>
3378// This function will rerender the actions with icons in UI (toolbars, shortcuts options etc.)
3380{
3381 QString xmlPath = KoResourcePaths::locateLocal("data", "krita5.xmlgui");
3382 QFile file(xmlPath);
3383 if (!file.exists()) {
3384 return;
3385 }
3386
3387 QDomDocument doc;
3388 if (!file.open(QIODevice::ReadOnly) || !doc.setContent(&file)) {
3389 return;
3390 }
3391
3392 QDomNodeList overridesList = doc.elementsByTagName(QStringLiteral("ActionIconOverrides"));
3393 if (!overridesList.isEmpty()) {
3394 QDomElement overridesElement = overridesList.at(0).toElement();
3395 QDomNodeList actions = overridesElement.elementsByTagName(QStringLiteral("Action"));
3396 for (int i = 0; i < actions.count(); ++i) {
3397 QDomElement actionElement = actions.at(i).toElement();
3398 QString name = actionElement.attribute(QStringLiteral("name"));
3399 QString icon = actionElement.attribute(QStringLiteral("icon"));
3400 if (!name.isEmpty() && !icon.isEmpty()) {
3401 QAction *qa = actionCollection()->action(name);
3402 if (qa) {
3403 qa->setIcon(KisIconUtils::loadIcon(icon));
3404 }
3405 }
3406 }
3407 }
3408}
3409
3411{
3412 // Add all actions with a menu property to the main window
3413 KisActionManager::synchronizeDynamicActions(this->actionCollection()->actions(), this->menuBar()->actions());
3414}
3415
3416#include <moc_KisMainWindow.cpp>
const Params2D p
qreal v
#define KIS_MIME_TYPE
Definition KisDocument.h:57
KisMagneticGraph::vertex_descriptor target(typename KisMagneticGraph::edge_descriptor e, KisMagneticGraph g)
QList< QString > QStringList
Recent files action.
KDE top level main window with predefined action layout
QAction * toolBarMenuAction()
void setStandardToolBarMenuEnabled(bool enable)
void applyMainWindowSettings(const KConfigGroup &config) override
bool event(QEvent *event) override
virtual KisKXMLGUIFactory * guiFactory()
bool isHelpMenuEnabled() const
A KisActionManager class keeps track of KisActions. These actions are always associated with the GUI....
KisAction * createAction(const QString &name)
static void synchronizeDynamicActions(QList< QAction * > newActions, const QList< QAction * > &menuBarActions)
void addAction(const QString &name, KisAction *action)
KisAction * createStandardAction(KStandardAction::StandardAction, const QObject *receiver, const char *member)
@ ACTIVE_IMAGE
Activate if there is at least one image.
Definition kis_action.h:43
@ IMAGE_IS_WRITABLE
Activate KisDocument::isReadWrite() is active.
Definition kis_action.h:62
@ IMAGE_HAS_ANIMATION
Activate if the image has an animation.
Definition kis_action.h:58
static KisAndroidDonations * instance()
void sigShowDonationManagementDialogRequested()
bool isShowDonationManagementDialogPending() const
void sigInterfaceScaleChanged()
static KisAndroidScaling * instance()
bool isSupported() const
void fromProperties(KisPropertiesConfigurationSP config)
Base class for the Krita app.
void setFavoriteResourceManager(KisFavoriteResourceManager *favoriteResourceManager)
static KisConfigNotifier * instance()
QString widgetStyle(bool defaultValue=false)
void setFullscreenMode(const bool value) const
void setWidgetStyle(QString name)
void writeEntry(const QString &name, const T &value)
Definition kis_config.h:892
QString getMDIBackgroundColor(bool defaultValue=false) const
QString getMDIBackgroundImage(bool defaultValue=false) const
bool showDockerTitleBars(bool defaultValue=false) const
bool effectiveShouldUseNativeSurfaceForCanvas(bool osManagedSurfacePresent) const
QString exportMimeType(bool defaultValue) const
void setShowDockerTitleBars(const bool value) const
bool toolOptionsInDocker(bool defaultValue=false) const
T readEntry(const QString &name, const T &defaultValue=T())
Definition kis_config.h:902
bool showDockers(bool defaultValue=false) const
bool useOpenGL(bool defaultValue=false) const
void setFileBatchMode(const bool batchMode)
KisImageSP image
void setPath(const QString &path)
bool importDocument(const QString &path)
bool openPath(const QString &path, OpenFlags flags=None)
openPath Open a Path
static KisImageConfigNotifier * instance()
KisGroupLayerSP rootLayer() const
KisImageAnimationInterface * animationInterface() const
static QStringList supportedMimeTypes(Direction direction)
void removeTrackedCanvas(KisCanvas2 *canvas)
void addTrackedCanvas(KisCanvas2 *canvas)
A container for a set of QAction objects.
void setComponentDisplayName(const QString &displayName)
Q_INVOKABLE QAction * addAction(const QString &name, QAction *action)
QAction * action(int index) const
void addAssociatedWidget(QWidget *widget)
A dialog used to customize or configure toolbars.
Standard KDE help menu with dialog boxes.
Definition khelpmenu.h:106
void setAutoSaveSettings(const QString &groupName=QLatin1String("MainWindow"), bool saveWindowSize=true)
void resetAutoSaveSettings()
void saveMainWindowSettings(KConfigGroup &config)
KisToolBar * toolBar(const QString &name=QString())
void closeEvent(QCloseEvent *) override
QList< KisToolBar * > toolBars() const
void setLocalXMLFile(const QString &file)
QAction * action(const char *name) const
KisKXMLGUIFactory * factory() const
void setXMLFile(const QString &file, bool merge=false, bool setXMLDoc=true)
void plugActionList(const QString &name, const QList< QAction * > &actionList)
virtual KisKActionCollection * actionCollection() const
void addClient(KisKXMLGUIClient *client)
QList< KisKXMLGUIClient * > clients() const
Main window for Krita.
QMenu * createPopupMenu() override
KisActionManager * actionManager()
bool installBundle(const QString &fileName) const
Copy the given file into the bundle directory.
QPointer< KisView > activeKisView()
virtual void showAboutApplication()
showAboutApplication show the about box
QScopedPointer< KisUndoActionsUpdateManager > undoActionsUpdateManager
QDockWidget * dockWidget(const QString &id)
void applyActionIconOverridesFromLocalXML()
KisMainWindow(QUuid id=QUuid())
void setCanvasDetached(bool detached)
void guiLoadingFinished()
void dragLeaveEvent(QDragLeaveEvent *event) override
QImage layoutThumbnail()
layoutThumbnail
QTabBar * findTabBarHACK()
KisAction * newWindow
KisSignalAutoConnectionsStore activeViewConnections
QStringList showOpenFileDialog(bool isImporting)
void slotUpdateWidgetStyle()
bool checkPaintOpAvailable()
void dragMove(QDragMoveEvent *event)
void showWelcomeScreen(bool show)
void setActiveSubWindow(QWidget *window)
bool windowsLayoutSavingAllowed() const override
void slotLoadCanceled(const QString &)
void slotConfigureToolbars()
QList< KoCanvasObserverBase * > canvasObservers() const override
void slotStoragesWarning(const QString &location=QString())
void slotNewToolbarConfig()
void themeChanged()
This signal is emitted when the color theme changes.
QMdiSubWindow * activeSubWindow
bool restoreWorkspaceState(const QByteArray &state)
KConfigGroup windowStateConfig
KisAction * renderAnimation
void updateSubwindowFlags()
void slotShowSessionManager()
void slotResetConfigurations()
void showErrorAndDie()
Quits Krita with error message from m_errorMessage.
QMap< QString, QAction * > actionMap
void slotUpdateReadWriteMode(bool readWrite)
bool checkActiveBundlesAvailable()
checkActiveStorages checks whether there is at least one bundle available and at least one paintop pr...
KisView * newView(QObject *document, QMdiSubWindow *subWindow=0)
bool openDocument(const QString &path, OpenFlags flags)
void toggleDockersVisibility(bool visible, bool onWelcomePage=false)
void showDocument(KisDocument *document)
void slotXmlGuiMakingChanges(bool finished)
void newOptionWidgets(KoCanvasController *controller, const QList< QPointer< QWidget > > &optionWidgetList)
KisView * addViewAndNotifyLoadingCompleted(KisDocument *document, QMdiSubWindow *subWindow=0)
QList< QDockWidget * > dockWidgets() const
Return the list of dock widgets belonging to this main window.
bool openDocumentInternal(const QString &path, KisMainWindow::OpenFlags f=KisMainWindow::OpenFlags())
QDockWidget * createDockWidget(KoDockFactoryBase *factory)
void loadCompleted()
void resizeEvent(QResizeEvent *e) override
KisAction * renderAnimationAgain
virtual void showView(KisView *view, QMdiSubWindow *subWindow=0)
showView shows the given view, in subWindow if not null, in a new tab otherwise.
void addView(KisView *view, QMdiSubWindow *subWindow=0)
void setMainWindowLayoutForCurrentMainWidget(int widgetIndex, bool widgetIndexChanged)
QPointer< KisView > activeView
void slotUpdateSaveActionTitle(const QString &documentPath)
Private *const d
void slotPreferences()
slotPreferences open the preferences dialog
void slotFileOpenRecent(const QUrl &)
QString lastExportLocation
bool saveDocument(KisDocument *document, bool saveas, bool isExporting, bool isAdvancedExporting=false)
QByteArray lastExportedFormat
void showEvent(QShowEvent *event) override
KisViewManager * viewManager
bool hackIsSaving() const
QString m_errorMessage
void slotFileOpen(bool isImporting=false)
KisAction * undo
QMap< QString, QDockWidget * > dockWidgetsMap
void saveWindowState(bool restoreNormalState=false)
bool canvasDetached() const
void slotChangeInterfaceScale()
KActionMenu * workspaceMenu
void viewFullscreen(bool fullScreen)
void documentSaved()
QList< QAction * > toolbarList
void restoringDone()
This signal is emitted right after the docker states have been successfully restored from config.
void activeViewChanged()
emitted when the current view has changed
KisCanvasWindow * canvasWindow
void notifyChildViewDestroyed(KisView *view)
void synchronizeDynamicActions()
void adjustLayoutForWelcomePage()
int viewCount() const
void setActiveView(KisView *view)
Set the active view, this will update the undo/redo actions.
void showDockerTitleBars(bool show)
static void swapWorkspaces(KisMainWindow *a, KisMainWindow *b)
KisAction * importAnimation
QByteArray dockerStateBeforeHiding
void removeRecentFile(QString url)
void slotSaveCanceled(const QString &)
KisAction * importVideoAnimation
Private(KisMainWindow *parent, QUuid id)
KisAction * redo
void switchTab(int index)
QByteArray borrowWorkspace(KisMainWindow *borrower)
~KisMainWindow() override
QScopedPointer< KisSignalCompressorWithParam< int > > tabSwitchCompressor
void dragMoveEvent(QDragMoveEvent *event) override
void slotToolbarToggled(bool toggle)
void closeEvent(QCloseEvent *e) override
KoCanvasResourceProvider * resourceManager() const
The KisMainwindowObserver class is an interface for dock widgets that want to keep track of the main ...
virtual void setViewManager(KisViewManager *kisview)=0
static QStringList suffixesForMimeType(const QString &mimeType)
static QString mimeTypeForFile(const QString &file, bool checkExistingFiles=true)
Find the mimetype for the given filename. The filename must include a suffix.
bool closeSession(bool keepWindows=false)
Definition KisPart.cpp:380
void removeMainWindow(KisMainWindow *mainWindow)
Definition KisPart.cpp:438
KisView * createView(KisDocument *document, KisViewManager *viewManager, QWidget *parent)
Definition KisPart.cpp:275
static KisPart * instance()
Definition KisPart.cpp:130
void addDocument(KisDocument *document, bool notify=true)
Definition KisPart.cpp:209
void queueAddRecentURLToAllMainWindowsOnFileSaved(QUrl url, QUrl oldUrl=QUrl())
Definition KisPart.cpp:604
void sigMainWindowCreated()
KisMainWindow * windowById(QUuid id) const
Definition KisPart.cpp:478
KisDocument * createDocument() const
Definition KisPart.cpp:228
void showSessionManager()
Definition KisPart.cpp:624
void notifyMainWindowIsBeingCreated(KisMainWindow *mainWindow)
notifyMainWindowIsBeingCreated emits the sigMainWindowCreated signal
Definition KisPart.cpp:269
KisMainWindow * createMainWindow(QUuid id=QUuid())
Definition KisPart.cpp:258
static KisPlatformPluginInterfaceFactory * instance()
static KisRecentDocumentsModelWrapper * instance()
void remove(const QUrl &url)
static KisRecentFilesManager * instance()
The KisResourceIterator class provides an iterator for a KisResourceModel.
const KisResourceItemSP next()
static KisResourceLoaderRegistry * instance()
QStringList mimeTypes(const QString &resourceType) const
The KisResourceModel class provides the main access to resources. It is possible to filter the resour...
static KisResourceServerProvider * instance()
KoResourceServer< KisWorkspaceResource > * workspaceServer()
KisPaintOpPresetResourceServer * paintOpPresetServer()
static QString storageTypeToUntranslatedString(StorageType storageType)
static KoResourceSP importResourceFileWithUserInput(QWidget *widgetParent, QString storageLocation, QString resourceType, QString resourceFilepath)
static bool addResourceWithUserInput(QWidget *widgetParent, KoResourceSP resource, QString storageLocation="")
The KisSignalMapper class bundles signals from identifiable senders.
@ ByStorageType
Pass a string list of storage types.
void setFilter(FilterType filterType, QVariant filter)
static KisStorageModel * instance()
Floatable toolbar with auto resize.
Definition ktoolbar.h:47
static KisToolBarStateModel * toolBarStateModel()
static void log(const QString &message)
Logs with date/time.
A special utility titlebar with a title and controls, as well as a central area for adding frequently...
KisDocument * document() const
void setCurrentView(KisView *view)
KisInputManager * inputManager() const
Filters events and sends them to canvas actions.
QPointer< KoUpdater > createUnthreadedUpdater(const QString &name)
create a new progress updater
KisSelectionManager * selectionManager()
KisImageWSP image() const
Return the image this view is displaying.
KisCanvas2 * canvasBase() const
Definition KisView.cpp:431
void slotThemeChanged(QPalette pal)
Definition KisView.cpp:1223
void setSubWindow(QMdiSubWindow *subWindow)
Definition KisView.cpp:1094
QPointer< KisDocument > document
Definition KisView.cpp:121
void slotLoadingFinished()
Definition KisView.cpp:1397
void setViewManager(KisViewManager *view)
Definition KisView.cpp:337
A widget for displaying if no documents are open. This will display in the MDI area.
static KisWindowLayoutManager * instance()
void activeDocumentChanged(KisDocument *document)
Resource for storing of workspaces.
void setObservedCanvas(KoCanvasBase *canvas)
static KoColorSpaceEngineRegistry * instance()
static KoColor fromXML(const QDomElement &elt, const QString &channelDepthId)
Definition KoColor.cpp:350
void toQColor(QColor *c) const
a convenience method for the above.
Definition KoColor.cpp:198
@ DockRight
Right of the centra widget.
@ DockTop
Above the central widget.
@ DockBottom
Below the central widget.
@ DockMinimized
Not docked, but reachable via the menu.
@ DockLeft
Left of the centra widget.
@ DockTornOff
Floating as its own top level window.
static KoDockRegistry * instance()
QList< KoDockFactoryBase * > sortedDockWidgetFactories()
A custom title bar for dock widgets.
The dialog that shows information about the document.
The class containing all meta information about a document.
void load(const QString &serviceType, const PluginsConfig &config=PluginsConfig(), QObject *owner=0, bool cache=true)
static KoPluginLoader * instance()
static QString locateLocal(const QString &type, const QString &filename, bool createDir=false)
static QString getAppDataLocation()
int resourceCount() const
QSharedPointer< T > resource(const QString &md5, const QString &fileName, const QString &name)
resource retrieves a resource. If the md5sum is not empty, the resource will only be retrieved if a r...
QList< QAction * > createActions(KisKActionCollection *actionCollection)
void initializeCurrentToolForCanvas()
static KoToolManager * instance()
Return the toolmanager singleton.
static KoToolRegistry * instance()
ScopedWidgetDisabler(QWidget *widget_)
DockPosition defaultDockPosition() const override
QDockWidget * createDockWidget() override
QString id() const override
#define KIS_ASSERT_RECOVER_NOOP(cond)
Definition kis_assert.h:97
#define KIS_SAFE_ASSERT_RECOVER_NOOP(cond)
Definition kis_assert.h:130
#define dbgKrita
Definition kis_debug.h:49
#define warnKrita
Definition kis_debug.h:94
#define dbgUI
Definition kis_debug.h:56
#define dbgFile
Definition kis_debug.h:57
#define warnUI
Definition kis_debug.h:101
constexpr const char * currentUnderlyingStyleNameProperty
Definition kis_global.h:116
typedef void(QOPENGLF_APIENTRYP PFNGLINVALIDATEBUFFERDATAPROC)(GLuint buffer)
KRecentFilesAction * openRecent(const QObject *recvr, const char *slot, QObject *parent)
const char * name(StandardAction id)
KRITAUI_EXPORT void show()
void setFullScreen(bool fullScreen)
KRITAUI_EXPORT bool render(KisDocument *doc, KisViewManager *viewManager, KisAnimationRenderingOptions encoderOptions)
int size(const Forest< T > &forest)
Definition KisForest.h:1232
QIcon loadIcon(const QString &name)
void updateIconCommon(QObject *object)
void updateIcon(QAbstractButton *button)
QFont normalFont()
Gets a font for normal UI widgets to use.
Definition KisUiFont.cpp:81
QFont dockFont()
Gets a font with a smallish font size for dock widgets to use.
Definition KisUiFont.cpp:98
const QString Workspaces
virtual void updateSettings()
const KoColorSpace * colorSpace(const QString &colorModelId, const QString &colorDepthId, const KoColorProfile *profile)
static KoColorSpaceRegistry * instance()
QList< int > renderedFrameTargetTimes