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