Krita Source Code Documentation
Loading...
Searching...
No Matches
KisPart.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-2005 David Faure <faure@kde.org>
4 * SPDX-FileCopyrightText: 2007-2008 Thorsten Zachmann <zachmann@kde.org>
5 * SPDX-FileCopyrightText: 2010-2012 Boudewijn Rempt <boud@valdyas.org>
6 * SPDX-FileCopyrightText: 2011 Inge Wallin <ingwa@kogmbh.com>
7 * SPDX-FileCopyrightText: 2015 Michael Abrahams <miabraha@gmail.com>
8 *
9 * SPDX-License-Identifier: LGPL-2.0-or-later
10 */
11
12#include "KisPart.h"
13
14#include <config-mlt.h>
15
16#include "KoProgressProxy.h"
17#include <KoCanvasController.h>
19#include <KoColorSpaceEngine.h>
20#include <KoCanvasBase.h>
21#include <KoToolManager.h>
24#include <kis_icon.h>
25
26#include "KisApplication.h"
27#include "KisMainWindow.h"
28#include "KisDocument.h"
29#include "KisView.h"
30#include "KisViewManager.h"
32#include "KoDocumentInfo.h"
33#include "KisUsageLogger.h"
34
35#include <kis_debug.h>
36#include <KoResourcePaths.h>
37#include <KoDialog.h>
38#include <QMessageBox>
39#include <QMenu>
40#include <QScopedPointer>
41#include <QMap>
42#include <QRegularExpression>
43
44#include <QMenuBar>
45#include <klocalizedstring.h>
46#include <kactioncollection.h>
47#include <kconfig.h>
48#include <kconfiggroup.h>
49#include <QKeySequence>
50
51#include <QApplication>
52#include <QDomDocument>
53#include <QDomElement>
54#include <QGlobalStatic>
55#include <KisMimeDatabase.h>
57
58#include <kis_group_layer.h>
59#include "kis_config.h"
64#include "kis_time_span.h"
65#include "kis_idle_watcher.h"
66#include "kis_image.h"
68#include "kis_color_manager.h"
69
71#include "kis_action.h"
72#include "kis_action_registry.h"
73#include "KisSessionResource.h"
74#include "KisBusyWaitBroker.h"
79#include "KisPlaybackEngine.h"
80#include "KisPlaybackEngineQT.h"
81
82#ifdef HAVE_MLT
84#endif
85
86Q_GLOBAL_STATIC(KisPart, s_instance)
87
88
89class Q_DECL_HIDDEN KisPart::Private
90{
91public:
93 : part(_part)
94 , idleWatcher(2500)
95 , animationCachePopulator(_part)
96 , playbackEngine(nullptr)
97 {
98 }
99
101 {
102 }
103
105
111 QScopedPointer<KisPlaybackEngine> playbackEngine;
112
114 bool closingSession{false};
115 QScopedPointer<KisSessionManagerDialog> sessionManager;
116
117 QMap<QUrl, QUrl> pendingAddRecentUrlMap;
118
120 Q_FOREACH(auto view, views) {
121 if (view && view->isVisible() && view->document() == document) {
122 return view->queryClose();
123 }
124 }
125
126 return true;
127 }
128};
129
130
132{
133 return s_instance;
134}
135
136namespace {
137void busyWaitWithFeedback(KisImageSP image)
138{
139 const int busyWaitDelay = 1000;
140 if (KisPart::instance()->currentMainwindow()) {
141 KisDelayedSaveDialog dialog(image, KisDelayedSaveDialog::ForcedDialog, busyWaitDelay, KisPart::instance()->currentMainwindow());
142 dialog.blockIfImageIsBusy();
143 }
144}
145}
146
148 : d(new Private(this))
149{
150 // Preload all the resources in the background
153 Q_UNUSED(KisColorManager::instance());
154
155 connect(this, SIGNAL(documentOpened(QString)),
156 this, SLOT(updateIdleWatcherConnections()));
157
158 connect(this, SIGNAL(documentClosed(QString)),
159 this, SLOT(updateIdleWatcherConnections()));
160
161 connect(KisActionRegistry::instance(), SIGNAL(shortcutsUpdated()),
162 this, SLOT(updateShortcuts()));
163 connect(&d->idleWatcher, SIGNAL(startedIdleMode()),
164 &d->animationCachePopulator, SLOT(slotRequestRegeneration()));
165 connect(&d->idleWatcher, SIGNAL(startedIdleMode()),
166 KisMemoryStatisticsServer::instance(), SLOT(tryForceUpdateMemoryStatisticsWhileIdle()));
167
168 // We start by loading the simple QTimer-based anim playback engine first.
169 // To save RAM, the MLT-based engine will be loaded later, once the KisImage in question becomes animated.
171
172 d->animationCachePopulator.slotRequestRegeneration();
173 KisBusyWaitBroker::instance()->setFeedbackCallback(&busyWaitWithFeedback);
174}
175
177{
178 while (!d->documents.isEmpty()) {
179 delete d->documents.takeFirst();
180 }
181
182 while (!d->views.isEmpty()) {
183 delete d->views.takeFirst();
184 }
185
186 while (!d->mainWindows.isEmpty()) {
187 delete d->mainWindows.takeFirst();
188 }
189
190 delete d;
191}
192
194{
195 QVector<KisImageSP> images;
196
197 Q_FOREACH (QPointer<KisDocument> document, documents()) {
198 if (document->image()) {
199 images << document->image();
200 }
201 }
202
203 d->idleWatcher.setTrackedImages(images);
204
208 d->idleWatcher.forceImageModified();
209}
210
211void KisPart::addDocument(KisDocument *document, bool notify)
212{
213 //dbgUI << "Adding document to part list" << document;
214 Q_ASSERT(document);
215 if (!d->documents.contains(document)) {
216 d->documents.append(document);
217 if (notify){
218 Q_EMIT documentOpened('/'+ objectName());
219 Q_EMIT sigDocumentAdded(document);
220 }
221 connect(document, SIGNAL(sigSavingFinished(QString)), SLOT(slotDocumentSaved(QString)));
222 }
223}
224
226{
227 return d->documents;
228}
229
231{
232 KisDocument *doc = new KisDocument();
233 return doc;
234}
235
237{
238 KisDocument *doc = new KisDocument(false);
239 return doc;
240}
241
242
244{
245 return d->documents.size();
246}
247
248void KisPart::removeDocument(KisDocument *document, bool deleteDocument)
249{
250 if (document) {
251 d->documents.removeAll(document);
252 Q_EMIT documentClosed('/' + objectName());
253 Q_EMIT sigDocumentRemoved(document->path());
254 if (deleteDocument) {
255 document->deleteLater();
256 }
257 }
258}
259
261{
262 KisMainWindow *mw = new KisMainWindow(id);
263 dbgUI <<"mainWindow" << (void*)mw << "added to view" << this;
264 d->mainWindows.append(mw);
265
267
268 return mw;
269}
270
275
276
278 KisViewManager *viewManager,
279 QWidget *parent)
280{
281 // If creating the canvas fails, record this and disable OpenGL next time
282 KisConfig cfg(false);
283 KConfigGroup grp( KSharedConfig::openConfig(), "crashprevention");
284 if (grp.readEntry("CreatingCanvas", false)) {
285 cfg.disableOpenGL();
286 }
287 if (cfg.canvasState() == "OPENGL_FAILED") {
288 cfg.disableOpenGL();
289 }
290 grp.writeEntry("CreatingCanvas", true);
291 grp.sync();
292
293 KisView *view = nullptr;
294 {
295 KisCursorOverrideLock cursorLock(Qt::WaitCursor);
296 view = new KisView(document, viewManager, parent);
297 }
298
299 // Record successful canvas creation
300 grp.writeEntry("CreatingCanvas", false);
301 grp.sync();
302
303 addView(view);
304
305 return view;
306}
307
309{
310 if (!view)
311 return;
312
313 if (!d->views.contains(view)) {
314 d->views.append(view);
315 }
316
317 Q_EMIT sigViewAdded(view);
318}
319
321{
322 if (!view) return;
323
331
332 Q_EMIT sigViewRemoved(view);
333
334 QPointer<KisDocument> doc = view->document();
335 d->views.removeAll(view);
336
337 if (doc) {
338 bool found = false;
339 Q_FOREACH (QPointer<KisView> view, d->views) {
340 if (view && view->document() == doc) {
341 found = true;
342 break;
343 }
344 }
345 if (!found) {
346 removeDocument(doc);
347 }
348 }
349}
350
352{
353 return d->views;
354}
355
357{
358 if (!doc) {
359 return d->views.count();
360 }
361 else {
362 int count = 0;
363 Q_FOREACH (QPointer<KisView> view, d->views) {
364 if (view && view->isVisible() && view->document() == doc) {
365 count++;
366 }
367 }
368 return count;
369 }
370}
371
372bool KisPart::closingSession() const
373{
374 return d->closingSession;
375}
376
378{
379 return s_instance.exists();
380}
381
382bool KisPart::closeSession(bool keepWindows)
383{
384 d->closingSession = true;
385
386 Q_FOREACH(auto document, d->documents) {
387 if (!d->queryCloseDocument(document.data())) {
388 d->closingSession = false;
389 return false;
390 }
391 }
392
393 if (d->currentSession) {
394 KisConfig kisCfg(false);
395 if (kisCfg.saveSessionOnQuit(false)) {
396
397 d->currentSession->storeCurrentWindows();
399 bool result = model.updateResource(d->currentSession);
400 Q_UNUSED(result);
401
402 KConfigGroup cfg = KSharedConfig::openConfig()->group("session");
403 cfg.writeEntry("previousSession", d->currentSession->name());
404 }
405
406 d->currentSession = nullptr;
407 }
408
409 if (!keepWindows) {
410 Q_FOREACH (auto window, d->mainWindows) {
411 window->close();
412 }
413
414 if (d->sessionManager) {
415 d->sessionManager->close();
416 }
417 }
418
419 d->closingSession = false;
420 return true;
421}
422
423void KisPart::slotDocumentSaved(const QString &filePath)
424{
425 // We used to use doc->path(), but it does not contain the correct output
426 // file path when doing an export, therefore we now pass it directly from
427 // the sigSavingFinished signal.
428 // KisDocument *doc = qobject_cast<KisDocument*>(sender());
429 Q_EMIT sigDocumentSaved(filePath);
430
431 QUrl url = QUrl::fromLocalFile(filePath);
433 if (!d->pendingAddRecentUrlMap.contains(url)) {
434 return;
435 }
436 QUrl oldUrl = d->pendingAddRecentUrlMap.take(url);
437 addRecentURLToAllMainWindows(url, oldUrl);
438}
439
441{
442 dbgUI <<"mainWindow" << (void*)mainWindow <<"removed from doc" << this;
443 if (mainWindow) {
444 d->mainWindows.removeAll(mainWindow);
445 }
446}
447
449{
450 return d->mainWindows;
451}
452
454{
455 return d->mainWindows.count();
456}
457
458
460{
461 QWidget *widget = qApp->activeWindow();
462 KisMainWindow *mainWindow = qobject_cast<KisMainWindow*>(widget);
463 while (!mainWindow && widget) {
464 widget = widget->parentWidget();
465 mainWindow = qobject_cast<KisMainWindow*>(widget);
466 }
467
468 if (!mainWindow && mainWindows().size() > 0) {
469 mainWindow = mainWindows().first();
470 }
471 return mainWindow;
472
473}
474
476{
477 return currentMainwindow();
478}
479
481{
482 Q_FOREACH(QPointer<KisMainWindow> mainWindow, d->mainWindows) {
483 if (mainWindow->id() == id) {
484 return mainWindow;
485 }
486 }
487
488 return nullptr;
489}
490
492{
493 return &d->idleWatcher;
494}
495
497{
498 return &d->animationCachePopulator;
499}
500
502{
503 return d->playbackEngine.data();
504}
505
507 KisImageAnimationInterface* animInterface = image->animationInterface();
508 if ( animInterface && animInterface->documentPlaybackRange().contains(frame)) {
509 d->animationCachePopulator.requestRegenerationWithPriorityFrame(image, frame);
510 }
511}
512
513void KisPart::openExistingFile(const QString &path)
514{
515 // TODO: refactor out this method!
516
519
521}
522
524{
525 // Update the UI actions. KisActionRegistry also takes care of updating
526 // shortcut hints in tooltips.
527 Q_FOREACH (KisMainWindow *mainWindow, d->mainWindows) {
528 KisKActionCollection *ac = mainWindow->actionCollection();
529
530 ac->updateShortcuts();
531 }
532}
533
534void KisPart::openTemplate(const QUrl &url)
535{
536 KisCursorOverrideLock cursorLock(Qt::BusyCursor);
537
538 KisDocument *document = createDocument();
539
540 bool ok = document->loadNativeFormat(url.toLocalFile());
541 document->setModified(false);
542 document->undoStack()->clear();
543 document->documentInfo()->resetMetaData();
544
545 if (ok) {
546 QString mimeType = KisMimeDatabase::mimeTypeForFile(url.toLocalFile());
547 // in case this is a open document template remove the -template from the end
548 mimeType.remove( QRegularExpression( "-template$" ) );
549 document->setMimeTypeAfterLoading(mimeType);
550 document->resetPath();
551 document->setReadWrite(true);
552 }
553 else {
554 if (document->errorMessage().isEmpty()) {
555 QMessageBox::critical(qApp->activeWindow(), i18nc("@title:window", "Krita"), i18n("Could not create document from template\n%1", document->localFilePath()));
556 }
557 else {
558 QMessageBox::critical(qApp->activeWindow(), i18nc("@title:window", "Krita"), i18n("Could not create document from template\n%1\nReason: %2", document->localFilePath(), document->errorMessage()));
559 }
560 delete document;
561 return;
562 }
563 QMap<QString, QString> dictionary;
564 // XXX: fill the dictionary from the desktop file
566 document->image()->rootLayer()->accept(v);
567
568 addDocument(document);
569
572}
573
574void KisPart::addRecentURLToAllMainWindows(QUrl url, QUrl oldUrl)
575{
576 // Add entry to recent documents list
577 // (call coming from KisDocument because it must work with cmd line, template dlg, file/open, etc.)
578 if (!url.isEmpty()) {
579 bool ok = true;
580 if (url.isLocalFile()) {
581 QString path = url.adjusted(QUrl::StripTrailingSlash).toLocalFile();
582 const QStringList tmpDirs = QStandardPaths::locateAll(QStandardPaths::TempLocation, "", QStandardPaths::LocateDirectory);
583 for (QStringList::ConstIterator it = tmpDirs.begin() ; ok && it != tmpDirs.end() ; ++it) {
584 if (path.contains(*it)) {
585 ok = false; // it's in the tmp resource
586 }
587 }
588
589 const QStringList templateDirs = KoResourcePaths::findDirs("templates");
590 for (QStringList::ConstIterator it = templateDirs.begin() ; ok && it != templateDirs.end() ; ++it) {
591 if (path.contains(*it)) {
592 ok = false; // it's in the templates directory.
593 break;
594 }
595 }
596 }
597 if (ok) {
598 if (!oldUrl.isEmpty()) {
600 }
602 }
603 }
604}
605
607{
608 d->pendingAddRecentUrlMap.insert(url, oldUrl);
609}
610
618
620{
622 KisViewManager *manager = mw ? mw->viewManager() : 0;
623 return manager ? manager->inputManager() : 0;
624}
625
627{
628 if (d->sessionManager.isNull()) {
629 d->sessionManager.reset(new KisSessionManagerDialog());
630 }
631
632 d->sessionManager->show();
633 d->sessionManager->activateWindow();
634}
635
637{
639 window->initializeGeometry();
640 window->show();
641
642}
643
644bool KisPart::restoreSession(const QString &sessionName)
645{
646 if (sessionName.isNull()) return false;
647
649 KisSessionResourceSP session = rserver->resource("", "", sessionName);
650 if (!session || !session->valid()) return false;
651
652 return restoreSession(session);
653}
654
656{
657 session->restore();
658 d->currentSession = session;
659 return true;
660}
661
663{
664 d->currentSession = session;
665}
666
668{
669#ifdef HAVE_MLT
670
671 // TODO: This is a slightly hacky workaround to loading the MLT engine over itself,
672 // as the QT-based engine no longer supports audio. Is there a better way?
673 if (d->playbackEngine->supportsAudio()) {
674 return;
675 }
676
678
679 if (canvas) {
680 d->playbackEngine->setObservedCanvas(canvas);
681 }
682
683#endif //HAVE_MLT
684}
685
687{
688#ifdef HAVE_MLT
689
690 d->playbackEngine.reset();
691
692#endif //HAVE_MLT
693}
694
696{
697 // make sure that the old engine is still alive until the end
698 // of the emitted signal
699 QScopedPointer backup(p_playbackEngine);
700 d->playbackEngine.swap(backup);
701
702 // Log all changes to playback engine for easier debugging.
703 // (See `krita.log` or `Help > Show Krita log for bug reports`.)
704 KisUsageLogger::log("Audio Playback Engine: " + QString(p_playbackEngine->metaObject()->className()));
705
706 Q_EMIT playbackEngineChanged(p_playbackEngine);
707}
708
709#include "moc_KisPart.cpp"
qreal v
quint64 part(quint64 n1, quint64 n2, int p)
Q_GLOBAL_STATIC(KisStoragePluginRegistry, s_instance)
static KisActionRegistry * instance()
void setFeedbackCallback(std::function< void(KisImageSP)> callback)
static KisBusyWaitBroker * instance()
static KisColorManager * instance()
void disableOpenGL() const
QString canvasState(bool defaultValue=false) const
bool saveSessionOnQuit(bool defaultValue) const
const KisTimeSpan & documentPlaybackRange() const
documentPlaybackRange
KisImageAnimationInterface * animationInterface() const
Central object to manage canvas input.
A container for a set of QAction objects.
virtual KisKActionCollection * actionCollection() const
Main window for Krita.
bool openDocument(const QString &path, OpenFlags flags)
KisView * addViewAndNotifyLoadingCompleted(KisDocument *document, QMdiSubWindow *subWindow=0)
KisViewManager * viewManager
bool hackIsSaving() const
void synchronizeDynamicActions()
static QString mimeTypeForFile(const QString &file, bool checkExistingFiles=true)
Find the mimetype for the given filename. The filename must include a suffix.
KisAnimationCachePopulator animationCachePopulator
Definition KisPart.cpp:110
bool closeSession(bool keepWindows=false)
Definition KisPart.cpp:382
void sigDocumentSaved(const QString &url)
QList< QPointer< KisDocument > > documents
Definition KisPart.cpp:108
Private(KisPart *_part)
Definition KisPart.cpp:92
void sigViewAdded(KisView *view)
void removeMainWindow(KisMainWindow *mainWindow)
Definition KisPart.cpp:440
void updateShortcuts()
Definition KisPart.cpp:523
bool queryCloseDocument(KisDocument *document)
Definition KisPart.cpp:119
void upgradeToPlaybackEngineMLT(class KoCanvasBase *canvas)
Definition KisPart.cpp:667
QList< QPointer< KisMainWindow > > mainWindows
Definition KisPart.cpp:107
void addRecentURLToAllMainWindows(QUrl url, QUrl oldUrl=QUrl())
Definition KisPart.cpp:574
void setPlaybackEngine(KisPlaybackEngine *p_playbackEngine)
Definition KisPart.cpp:695
void documentClosed(const QString &ref)
KisView * createView(KisDocument *document, KisViewManager *viewManager, QWidget *parent)
Definition KisPart.cpp:277
void openTemplate(const QUrl &url)
Definition KisPart.cpp:534
KisInputManager * currentInputManager()
Definition KisPart.cpp:619
QScopedPointer< KisSessionManagerDialog > sessionManager
Definition KisPart.cpp:115
KisSessionResourceSP currentSession
Definition KisPart.cpp:113
static KisPart * instance()
Definition KisPart.cpp:131
void sigDocumentAdded(KisDocument *document)
QScopedPointer< KisPlaybackEngine > playbackEngine
Definition KisPart.cpp:111
bool restoreSession(const QString &sessionName)
Definition KisPart.cpp:644
Private *const d
Definition KisPart.h:328
void slotDocumentSaved(const QString &filePath)
Definition KisPart.cpp:423
void addView(KisView *view)
Definition KisPart.cpp:308
void removeView(KisView *view)
Definition KisPart.cpp:320
QMap< QUrl, QUrl > pendingAddRecentUrlMap
Definition KisPart.cpp:117
void addDocument(KisDocument *document, bool notify=true)
Definition KisPart.cpp:211
void queueAddRecentURLToAllMainWindowsOnFileSaved(QUrl url, QUrl oldUrl=QUrl())
Definition KisPart.cpp:606
int documentCount() const
Definition KisPart.cpp:243
int viewCount(KisDocument *doc) const
Definition KisPart.cpp:356
void playbackEngineChanged(KisPlaybackEngine *newPlaybackEngine)
static bool exists()
Definition KisPart.cpp:377
KisAnimationCachePopulator * cachePopulator() const
Definition KisPart.cpp:496
KisMainWindow * currentMainwindow() const
Definition KisPart.cpp:459
void prioritizeFrameForCache(KisImageSP image, int frame)
Definition KisPart.cpp:506
KisIdleWatcher idleWatcher
Definition KisPart.cpp:109
void startBlankSession()
Definition KisPart.cpp:636
~KisPart() override
Definition KisPart.cpp:176
KisMainWindow * windowById(QUuid id) const
Definition KisPart.cpp:480
void removeDocument(KisDocument *document, bool deleteDocument=true)
Definition KisPart.cpp:248
KisDocument * createTemporaryDocument() const
Definition KisPart.cpp:236
void sigViewRemoved(KisView *view)
QWidget * currentMainwindowAsQWidget() const
Definition KisPart.cpp:475
void startCustomDocument(KisDocument *doc)
startCustomDocument adds the given document to the document list and deletes the sender()
Definition KisPart.cpp:611
KisDocument * createDocument() const
Definition KisPart.cpp:230
bool closingSession
Definition KisPart.cpp:114
QList< QPointer< KisView > > views
Definition KisPart.cpp:106
void setCurrentSession(KisSessionResourceSP session)
Definition KisPart.cpp:662
void documentOpened(const QString &ref)
void updateIdleWatcherConnections()
Definition KisPart.cpp:193
void openExistingFile(const QString &path)
Definition KisPart.cpp:513
void sigDocumentRemoved(const QString &filename)
int mainwindowCount() const
Definition KisPart.cpp:453
void unloadPlaybackEngine()
Definition KisPart.cpp:686
void showSessionManager()
Definition KisPart.cpp:626
void sigMainWindowIsBeingCreated(KisMainWindow *window)
void notifyMainWindowIsBeingCreated(KisMainWindow *mainWindow)
notifyMainWindowIsBeingCreated emits the sigMainWindowCreated signal
Definition KisPart.cpp:271
KisPart * part
Definition KisPart.cpp:104
KisMainWindow * createMainWindow(QUuid id=QUuid())
Definition KisPart.cpp:260
The KisPlaybackEngineMLT class is an implementation of KisPlaybackEngine that uses MLT (Media Lovin' ...
The KisPlaybackEngineQT class is an implementation of KisPlaybackEngine that drives animation playbac...
Krita's base animation playback engine for producing image frame changes and associated audio.
static KisRecentFileIconCache * instance()
void reloadFileIcon(const QUrl &url)
void remove(const QUrl &url)
static KisRecentFilesManager * instance()
The KisResourceModel class provides the main access to resources. It is possible to filter the resour...
bool updateResource(KoResourceSP resource) override
updateResource creates a new version of the resource in the storage and in the database....
static KisResourceServerProvider * instance()
KoResourceServer< KisSessionResource > * sessionServer()
bool contains(int time) const
KisTranslateLayerNamesVisitor::KisTranslateLayerNamesVisitor translates layer names from templates.
static void log(const QString &message)
Logs with date/time.
KisInputManager * inputManager() const
Filters events and sends them to canvas actions.
KisMainWindow * mainWindow() const
Definition KisView.cpp:1089
QPointer< KisDocument > document
Definition KisView.cpp:121
static QStringList findDirs(const QString &type)
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...
#define KIS_SAFE_ASSERT_RECOVER_RETURN(cond)
Definition kis_assert.h:128
#define KIS_ASSERT_RECOVER_RETURN(cond)
Definition kis_assert.h:75
#define dbgUI
Definition kis_debug.h:52
const QString Sessions
static KisMemoryStatisticsServer * instance()
static KoResourceServerProvider * instance()
KisCanvas2 * canvas