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