diff --git a/libs/libkis/Krita.cpp b/libs/libkis/Krita.cpp
index 0275aeb97e..b60c1c3dcc 100644
--- a/libs/libkis/Krita.cpp
+++ b/libs/libkis/Krita.cpp
@@ -1,377 +1,377 @@
/*
* Copyright (c) 2016 Boudewijn Rempt
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include "Krita.h"
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include "View.h"
#include "Document.h"
#include "Window.h"
#include "Extension.h"
#include "DockWidgetFactoryBase.h"
#include "Filter.h"
#include "InfoObject.h"
#include "Resource.h"
Krita* Krita::s_instance = 0;
struct Krita::Private {
Private() {}
QList extensions;
bool batchMode {false};
Notifier *notifier{new Notifier()};
};
Krita::Krita(QObject *parent)
: QObject(parent)
, d(new Private)
{
qRegisterMetaType();
}
Krita::~Krita()
{
qDeleteAll(d->extensions);
delete d->notifier;
delete d;
}
QList Krita::actions() const
{
QList actionList;
KisMainWindow *mainWindow = KisPart::instance()->currentMainwindow();
if (!mainWindow) {
return actionList;
}
KActionCollection *actionCollection = mainWindow->actionCollection();
Q_FOREACH(QAction *action, actionCollection->actions()) {
actionList << new Action(action->objectName(), action);
}
return actionList;
}
Action *Krita::action(const QString &name) const
{
KisMainWindow *mainWindow = KisPart::instance()->currentMainwindow();
if (!mainWindow) {
return 0;
}
KActionCollection *actionCollection = mainWindow->actionCollection();
QAction *action = actionCollection->action(name);
if (action) {
return new Action(name, action);
}
return 0;
}
Document* Krita::activeDocument() const
{
KisMainWindow *mainWindow = KisPart::instance()->currentMainwindow();
if (!mainWindow) {
return 0;
}
KisView *view = mainWindow->activeView();
if (!view) {
return 0;
}
KisDocument *document = view->document();
return new Document(document);
}
void Krita::setActiveDocument(Document* value)
{
Q_FOREACH(KisView *view, KisPart::instance()->views()) {
if (view->document() == value->document().data()) {
view->activateWindow();
break;
}
}
}
bool Krita::batchmode() const
{
return d->batchMode;
}
void Krita::setBatchmode(bool value)
{
d->batchMode = value;
}
QList Krita::documents() const
{
QList ret;
foreach(QPointer doc, KisPart::instance()->documents()) {
ret << new Document(doc);
}
return ret;
}
QStringList Krita::filters() const
{
QStringList ls = KisFilterRegistry::instance()->keys();
qSort(ls);
return ls;
}
Filter *Krita::filter(const QString &name) const
{
if (!filters().contains(name)) return 0;
Filter *filter = new Filter();
filter->setName(name);
KisFilterSP f = KisFilterRegistry::instance()->value(name);
KisFilterConfigurationSP fc = f->defaultConfiguration();
InfoObject *info = new InfoObject(fc);
filter->setConfiguration(info);
return filter;
}
QStringList Krita::profiles(const QString &colorModel, const QString &colorDepth) const
{
QSet profileNames;
QString id = KoColorSpaceRegistry::instance()->colorSpaceId(colorModel, colorDepth);
QList profiles = KoColorSpaceRegistry::instance()->profilesFor(id);
Q_FOREACH(const KoColorProfile *profile, profiles) {
profileNames << profile->name();
}
return profileNames.toList();
}
bool Krita::addProfile(const QString &profilePath)
{
KoColorSpaceEngine *iccEngine = KoColorSpaceEngineRegistry::instance()->get("icc");
return iccEngine->addProfile(profilePath);
}
Notifier* Krita::notifier() const
{
return d->notifier;
}
QString Krita::version() const
{
return KritaVersionWrapper::versionString(true);
}
QList Krita::views() const
{
QList ret;
foreach(QPointer view, KisPart::instance()->views()) {
ret << new View(view);
}
return ret;
}
Window *Krita::activeWindow() const
{
KisMainWindow *mainWindow = KisPart::instance()->currentMainwindow();
if (!mainWindow) {
return 0;
}
return new Window(mainWindow);
}
QList Krita::windows() const
{
QList ret;
foreach(QPointer mainWin, KisPart::instance()->mainWindows()) {
ret << new Window(mainWin);
}
return ret;
}
QMap Krita::resources(const QString &type) const
{
QMap resources = QMap ();
if (type == "pattern") {
KoResourceServer* server = KoResourceServerProvider::instance()->patternServer();
Q_FOREACH (KoResource *res, server->resources()) {
resources[res->name()] = new Resource(res);
}
}
else if (type == "gradient") {
KoResourceServer* server = KoResourceServerProvider::instance()->gradientServer();
Q_FOREACH (KoResource *res, server->resources()) {
resources[res->name()] = new Resource(res);
}
}
else if (type == "brush") {
KisBrushResourceServer* server = KisBrushServer::instance()->brushServer();
Q_FOREACH (KisBrushSP res, server->resources()) {
resources[res->name()] = new Resource(res.data());
}
}
else if (type == "preset") {
KisPaintOpPresetResourceServer* server = KisResourceServerProvider::instance()->paintOpPresetServer();
Q_FOREACH (KisPaintOpPresetSP res, server->resources()) {
resources[res->name()] = new Resource(res.data());
}
}
else if (type == "palette") {
KoResourceServer* server = KoResourceServerProvider::instance()->paletteServer();
Q_FOREACH (KoResource *res, server->resources()) {
resources[res->name()] = new Resource(res);
}
}
else if (type == "workspace") {
KoResourceServer< KisWorkspaceResource >* server = KisResourceServerProvider::instance()->workspaceServer();
Q_FOREACH (KoResource *res, server->resources()) {
resources[res->name()] = new Resource(res);
}
}
return resources;
}
Document* Krita::createDocument(int width, int height, const QString &name, const QString &colorModel, const QString &colorDepth, const QString &profile)
{
KisDocument *document = KisPart::instance()->createDocument();
KisPart::instance()->addDocument(document);
const KoColorSpace *cs = KoColorSpaceRegistry::instance()->colorSpace(colorModel, colorDepth, profile);
Q_ASSERT(cs);
QColor qc(Qt::white);
qc.setAlpha(0);
KoColor bgColor(qc, cs);
if (!document->newImage(name, width, height, cs, bgColor, true, 1, "", 100.0)) {
qDebug() << "Could not create a new image";
return 0;
}
Q_ASSERT(document->image());
qDebug() << document->image()->objectName();
return new Document(document);
}
Document* Krita::openDocument(const QString &filename)
{
KisDocument *document = KisPart::instance()->createDocument();
KisPart::instance()->addDocument(document);
- document->openUrl(QUrl::fromLocalFile(filename), KisDocument::OPEN_URL_FLAG_DO_NOT_ADD_TO_RECENT_FILES);
+ document->openUrl(QUrl::fromLocalFile(filename), KisDocument::DontAddToRecent);
return new Document(document);
}
Window* Krita::openWindow()
{
KisMainWindow *mw = KisPart::instance()->createMainWindow();
return new Window(mw);
}
Action *Krita::createAction(const QString &text)
{
KisAction *action = new KisAction(text, this);
KisPart::instance()->addScriptAction(action);
return new Action(action->objectName(), action);
}
void Krita::addExtension(Extension* extension)
{
d->extensions.append(extension);
}
QList< Extension* > Krita::extensions()
{
return d->extensions;
}
void Krita::writeSetting(const QString &group, const QString &name, const QString &value)
{
KConfigGroup grp = KSharedConfig::openConfig()->group(group);
grp.writeEntry(name, value);
}
QString Krita::readSetting(const QString &group, const QString &name, const QString &defaultValue)
{
KConfigGroup grp = KSharedConfig::openConfig()->group(group);
return grp.readEntry(name, defaultValue);
}
void Krita::addDockWidgetFactory(DockWidgetFactoryBase* factory)
{
KoDockRegistry::instance()->add(factory);
}
Krita* Krita::instance()
{
if (!s_instance)
{
s_instance = new Krita;
}
return s_instance;
}
/**
* Scripter.fromVariant(variant)
* variant is a QVariant
* returns instance of QObject-subclass
*
* This is a helper method for PyQt because PyQt cannot cast a variant to a QObject or QWidget
*/
QObject *Krita::fromVariant(const QVariant& v)
{
if (v.canConvert< QWidget* >())
{
QObject* obj = qvariant_cast< QWidget* >(v);
return obj;
}
else if (v.canConvert< QObject* >())
{
QObject* obj = qvariant_cast< QObject* >(v);
return obj;
}
else
return 0;
}
diff --git a/libs/ui/KisApplication.cpp b/libs/ui/KisApplication.cpp
index 56b658a4e5..62818ce564 100644
--- a/libs/ui/KisApplication.cpp
+++ b/libs/ui/KisApplication.cpp
@@ -1,762 +1,762 @@
/* This file is part of the KDE project
Copyright (C) 1998, 1999 Torben Weis
Copyright (C) 2009 Thomas Zander
Copyright (C) 2012 Boudewijn Rempt
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Library General Public
License as published by the Free Software Foundation; either
version 2 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Library General Public License for more details.
You should have received a copy of the GNU Library General Public License
along with this library; see the file COPYING.LIB. If not, write to
the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#include "KisApplication.h"
#include
#ifdef Q_OS_WIN
#include
#include
#endif
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include "KoGlobal.h"
#include "KoConfig.h"
#include
#include
#include
#include "thememanager.h"
#include "KisPrintJob.h"
#include "KisDocument.h"
#include "KisMainWindow.h"
#include "KisAutoSaveRecoveryDialog.h"
#include "KisPart.h"
#include
#include "kis_md5_generator.h"
#include "kis_splash_screen.h"
#include "kis_config.h"
#include "flake/kis_shape_selection.h"
#include
#include
#include
#include
#include
#include
#include
#include "kisexiv2/kis_exiv2.h"
#include "KisApplicationArguments.h"
#include
#include "kis_action_registry.h"
#include
#include
#include
#include "kis_image_barrier_locker.h"
#include "opengl/kis_opengl.h"
#include "kis_spin_box_unit_manager.h"
#include "kis_document_aware_spin_box_unit_manager.h"
#include
namespace {
const QTime appStartTime(QTime::currentTime());
}
class KisApplicationPrivate
{
public:
KisApplicationPrivate()
: splashScreen(0)
{}
QPointer splashScreen;
};
class KisApplication::ResetStarting
{
public:
ResetStarting(KisSplashScreen *splash, int fileCount)
: m_splash(splash)
, m_fileCount(fileCount)
{
}
~ResetStarting() {
if (m_splash) {
KConfigGroup cfg( KSharedConfig::openConfig(), "SplashScreen");
bool hideSplash = cfg.readEntry("HideSplashAfterStartup", false);
if (m_fileCount > 0 || hideSplash) {
m_splash->hide();
}
else {
m_splash->setWindowFlags(Qt::Dialog);
QRect r(QPoint(), m_splash->size());
m_splash->move(QApplication::desktop()->availableGeometry().center() - r.center());
m_splash->setWindowTitle(qAppName());
m_splash->setParent(0);
Q_FOREACH (QObject *o, m_splash->children()) {
QWidget *w = qobject_cast(o);
if (w && w->isHidden()) {
w->setVisible(true);
}
}
m_splash->show();
m_splash->activateWindow();
}
}
}
QPointer m_splash;
int m_fileCount;
};
KisApplication::KisApplication(const QString &key, int &argc, char **argv)
: QtSingleApplication(key, argc, argv)
, d(new KisApplicationPrivate)
, m_autosaveDialog(0)
, m_mainWindow(0)
, m_batchRun(false)
{
QCoreApplication::addLibraryPath(QCoreApplication::applicationDirPath());
setApplicationDisplayName("Krita");
setApplicationName("krita");
// Note: Qt docs suggest we set this, but if we do, we get resource paths of the form of krita/krita, which is weird.
// setOrganizationName("krita");
setOrganizationDomain("krita.org");
QString version = KritaVersionWrapper::versionString(true);
setApplicationVersion(version);
setWindowIcon(KisIconUtils::loadIcon("calligrakrita"));
if (qgetenv("KRITA_NO_STYLE_OVERRIDE").isEmpty()) {
QStringList styles = QStringList() << "breeze" << "fusion" << "plastique";
if (!styles.contains(style()->objectName().toLower())) {
Q_FOREACH (const QString & style, styles) {
if (!setStyle(style)) {
qDebug() << "No" << style << "available.";
}
else {
qDebug() << "Set style" << style;
break;
}
}
}
}
else {
qDebug() << "Style override disabled, using" << style()->objectName();
}
KisOpenGL::initialize();
qDebug() << "krita has opengl" << KisOpenGL::hasOpenGL();
}
#if defined(Q_OS_WIN) && defined(ENV32BIT)
typedef BOOL (WINAPI *LPFN_ISWOW64PROCESS) (HANDLE, PBOOL);
LPFN_ISWOW64PROCESS fnIsWow64Process;
BOOL isWow64()
{
BOOL bIsWow64 = FALSE;
//IsWow64Process is not available on all supported versions of Windows.
//Use GetModuleHandle to get a handle to the DLL that contains the function
//and GetProcAddress to get a pointer to the function if available.
fnIsWow64Process = (LPFN_ISWOW64PROCESS) GetProcAddress(
GetModuleHandle(TEXT("kernel32")),"IsWow64Process");
if(0 != fnIsWow64Process)
{
if (!fnIsWow64Process(GetCurrentProcess(),&bIsWow64))
{
//handle error
}
}
return bIsWow64;
}
#endif
void KisApplication::initializeGlobals(const KisApplicationArguments &args)
{
int dpiX = args.dpiX();
int dpiY = args.dpiY();
if (dpiX > 0 && dpiY > 0) {
KoDpi::setDPI(dpiX, dpiY);
}
}
void KisApplication::addResourceTypes()
{
// All Krita's resource types
KoResourcePaths::addResourceType("kis_pics", "data", "/pics/");
KoResourcePaths::addResourceType("kis_images", "data", "/images/");
KoResourcePaths::addResourceType("icc_profiles", "data", "/profiles/");
KoResourcePaths::addResourceType("metadata_schema", "data", "/metadata/schemas/");
KoResourcePaths::addResourceType("kis_brushes", "data", "/brushes/");
KoResourcePaths::addResourceType("kis_taskset", "data", "/taskset/");
KoResourcePaths::addResourceType("kis_taskset", "data", "/taskset/");
KoResourcePaths::addResourceType("gmic_definitions", "data", "/gmic/");
KoResourcePaths::addResourceType("kis_resourcebundles", "data", "/bundles/");
KoResourcePaths::addResourceType("kis_defaultpresets", "data", "/defaultpresets/");
KoResourcePaths::addResourceType("kis_paintoppresets", "data", "/paintoppresets/");
KoResourcePaths::addResourceType("kis_workspaces", "data", "/workspaces/");
KoResourcePaths::addResourceType("psd_layer_style_collections", "data", "/asl");
KoResourcePaths::addResourceType("ko_patterns", "data", "/patterns/", true);
KoResourcePaths::addResourceType("ko_gradients", "data", "/gradients/");
KoResourcePaths::addResourceType("ko_gradients", "data", "/gradients/", true);
KoResourcePaths::addResourceType("ko_palettes", "data", "/palettes/", true);
KoResourcePaths::addResourceType("kis_shortcuts", "data", "/shortcuts/");
KoResourcePaths::addResourceType("kis_actions", "data", "/actions");
KoResourcePaths::addResourceType("icc_profiles", "data", "/color/icc");
KoResourcePaths::addResourceType("ko_effects", "data", "/effects/");
KoResourcePaths::addResourceType("tags", "data", "/tags/");
KoResourcePaths::addResourceType("templates", "data", "/templates");
KoResourcePaths::addResourceType("pythonscripts", "data", "/pykrita");
// // Extra directories to look for create resources. (Does anyone actually use that anymore?)
// KoResourcePaths::addResourceDir("ko_gradients", "/usr/share/create/gradients/gimp");
// KoResourcePaths::addResourceDir("ko_gradients", QDir::homePath() + QString("/.create/gradients/gimp"));
// KoResourcePaths::addResourceDir("ko_patterns", "/usr/share/create/patterns/gimp");
// KoResourcePaths::addResourceDir("ko_patterns", QDir::homePath() + QString("/.create/patterns/gimp"));
// KoResourcePaths::addResourceDir("kis_brushes", "/usr/share/create/brushes/gimp");
// KoResourcePaths::addResourceDir("kis_brushes", QDir::homePath() + QString("/.create/brushes/gimp"));
// KoResourcePaths::addResourceDir("ko_palettes", "/usr/share/create/swatches");
// KoResourcePaths::addResourceDir("ko_palettes", QDir::homePath() + QString("/.create/swatches"));
// Make directories for all resources we can save, and tags
QDir d;
d.mkpath(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + "/tags/");
d.mkpath(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + "/asl/");
d.mkpath(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + "/bundles/");
d.mkpath(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + "/gradients/");
d.mkpath(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + "/paintoppresets/");
d.mkpath(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + "/palettes/");
d.mkpath(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + "/patterns/");
d.mkpath(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + "/taskset/");
d.mkpath(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + "/workspaces/");
d.mkpath(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + "/input/");
}
void KisApplication::loadResources()
{
setSplashScreenLoadingText(i18n("Loading Gradients..."));
processEvents();
KoResourceServerProvider::instance()->gradientServer(true);
// Load base resources
setSplashScreenLoadingText(i18n("Loading Patterns..."));
processEvents();
KoResourceServerProvider::instance()->patternServer(true);
setSplashScreenLoadingText(i18n("Loading Palettes..."));
processEvents();
KoResourceServerProvider::instance()->paletteServer(false);
setSplashScreenLoadingText(i18n("Loading Brushes..."));
processEvents();
KisBrushServer::instance()->brushServer(true);
// load paintop presets
setSplashScreenLoadingText(i18n("Loading Paint Operations..."));
processEvents();
KisResourceServerProvider::instance()->paintOpPresetServer(true);
setSplashScreenLoadingText(i18n("Loading Resource Bundles..."));
processEvents();
KisResourceServerProvider::instance()->resourceBundleServer();
}
void KisApplication::loadPlugins()
{
KoShapeRegistry* r = KoShapeRegistry::instance();
r->add(new KisShapeSelectionFactory());
KisActionRegistry::instance();
KisFilterRegistry::instance();
KisGeneratorRegistry::instance();
KisPaintOpRegistry::instance();
KoColorSpaceRegistry::instance();
// Load the krita-specific tools
setSplashScreenLoadingText(i18n("Loading Plugins for Krita/Tool..."));
processEvents();
KoPluginLoader::instance()->load(QString::fromLatin1("Krita/Tool"),
QString::fromLatin1("[X-Krita-Version] == 28"));
// Load dockers
setSplashScreenLoadingText(i18n("Loading Plugins for Krita/Dock..."));
processEvents();
KoPluginLoader::instance()->load(QString::fromLatin1("Krita/Dock"),
QString::fromLatin1("[X-Krita-Version] == 28"));
// XXX_EXIV: make the exiv io backends real plugins
setSplashScreenLoadingText(i18n("Loading Plugins Exiv/IO..."));
processEvents();
KisExiv2::initialize();
}
bool KisApplication::start(const KisApplicationArguments &args)
{
KisConfig cfg;
#if defined(Q_OS_WIN)
#ifdef ENV32BIT
if (isWow64() && !cfg.readEntry("WarnedAbout32Bits", false)) {
QMessageBox::information(0,
i18nc("@title:window", "Krita: Warning"),
i18n("You are running a 32 bits build on a 64 bits Windows.\n"
"This is not recommended.\n"
"Please download and install the x64 build instead."));
cfg.writeEntry("WarnedAbout32Bits", true);
}
#endif
#endif
QString opengl = cfg.canvasState();
if (opengl == "OPENGL_NOT_TRIED" ) {
cfg.setCanvasState("TRY_OPENGL");
}
else if (opengl != "OPENGL_SUCCESS") {
cfg.setCanvasState("OPENGL_FAILED");
}
setSplashScreenLoadingText(i18n("Initializing Globals"));
processEvents();
initializeGlobals(args);
const bool doTemplate = args.doTemplate();
const bool print = args.print();
const bool exportAs = args.exportAs();
const bool exportAsPdf = args.exportAsPdf();
const QString exportFileName = args.exportFileName();
m_batchRun = (print || exportAs || exportAsPdf || !exportFileName.isEmpty());
// print & exportAsPdf do user interaction ATM
const bool needsMainWindow = !exportAs;
// only show the mainWindow when no command-line mode option is passed
// TODO: fix print & exportAsPdf to work without mainwindow shown
const bool showmainWindow = !exportAs; // would be !batchRun;
const bool showSplashScreen = !m_batchRun && qEnvironmentVariableIsEmpty("NOSPLASH");// && qgetenv("XDG_CURRENT_DESKTOP") != "GNOME";
if (showSplashScreen && d->splashScreen) {
d->splashScreen->show();
d->splashScreen->repaint();
processEvents();
}
KoHashGeneratorProvider::instance()->setGenerator("MD5", new KisMD5Generator());
// Initialize all Krita directories etc.
KoGlobal::initialize();
KConfigGroup group(KSharedConfig::openConfig(), "theme");
Digikam::ThemeManager themeManager;
themeManager.setCurrentTheme(group.readEntry("Theme", "Krita dark"));
ResetStarting resetStarting(d->splashScreen, args.filenames().count()); // remove the splash when done
Q_UNUSED(resetStarting);
// Make sure we can save resources and tags
setSplashScreenLoadingText(i18n("Adding resource types"));
processEvents();
addResourceTypes();
// Load all resources and tags before the plugins do that
loadResources();
// Load the plugins
loadPlugins();
if (needsMainWindow) {
// show a mainWindow asap, if we want that
setSplashScreenLoadingText(i18n("Loading Main Window..."));
processEvents();
m_mainWindow = KisPart::instance()->createMainWindow();
if (showmainWindow) {
m_mainWindow->initializeGeometry();
m_mainWindow->show();
}
}
short int numberOfOpenDocuments = 0; // number of documents open
// Check for autosave files that can be restored, if we're not running a batchrun (test, print, export to pdf)
if (!m_batchRun) {
checkAutosaveFiles();
}
setSplashScreenLoadingText(QString()); // done loading, so clear out label
processEvents();
//configure the unit manager
KisSpinBoxUnitManagerFactory::setDefaultUnitManagerBuilder(new KisDocumentAwareSpinBoxUnitManagerBuilder());
connect(this, &KisApplication::aboutToQuit, &KisSpinBoxUnitManagerFactory::clearUnitManagerBuilder); //ensure the builder is destroyed when the application leave.
//the new syntax slot syntax allow to connect to a non q_object static method.
// Get the command line arguments which we have to parse
int argsCount = args.filenames().count();
if (argsCount > 0) {
// Loop through arguments
short int nPrinted = 0;
for (int argNumber = 0; argNumber < argsCount; argNumber++) {
QString fileName = args.filenames().at(argNumber);
// are we just trying to open a template?
if (doTemplate) {
// called in mix with batch options? ignore and silently skip
if (m_batchRun) {
continue;
}
if (createNewDocFromTemplate(fileName, m_mainWindow)) {
++numberOfOpenDocuments;
}
// now try to load
}
else {
if (exportAs) {
QString outputMimetype = KisMimeDatabase::mimeTypeForFile(exportFileName);
if (outputMimetype == "application/octetstream") {
dbgKrita << i18n("Mimetype not found, try using the -mimetype option") << endl;
return 1;
}
KisDocument *doc = KisPart::instance()->createDocument();
doc->setFileBatchMode(m_batchRun);
doc->openUrl(QUrl::fromLocalFile(fileName));
qApp->processEvents(); // For vector layers to be updated
doc->setFileBatchMode(true);
doc->setOutputMimeType(outputMimetype.toLatin1());
if (!doc->exportDocument(QUrl::fromLocalFile(exportFileName))) {
dbgKrita << "Could not export " << fileName << "to" << exportFileName << ":" << doc->errorMessage();
}
nPrinted++;
QTimer::singleShot(0, this, SLOT(quit()));
}
else if (m_mainWindow) {
KisMainWindow::OpenFlags flags = m_batchRun ? KisMainWindow::BatchMode : KisMainWindow::None;
if (m_mainWindow->openDocument(QUrl::fromLocalFile(fileName), flags)) {
if (print) {
m_mainWindow->slotFilePrint();
nPrinted++;
// TODO: trigger closing of app once printing is done
}
else if (exportAsPdf) {
KisPrintJob *job = m_mainWindow->exportToPdf(exportFileName);
if (job)
connect (job, SIGNAL(destroyed(QObject*)), m_mainWindow,
SLOT(slotFileQuit()), Qt::QueuedConnection);
nPrinted++;
} else {
// Normal case, success
numberOfOpenDocuments++;
}
} else {
// .... if failed
// delete doc; done by openDocument
}
}
}
}
if (m_batchRun) {
return nPrinted > 0;
}
}
// fixes BUG:369308 - Krita crashing on splash screen when loading.
// trying to open a file before Krita has loaded can cause it to hang and crash
if (d->splashScreen) {
d->splashScreen->displayLinks();
d->splashScreen->displayRecentFiles();
}
// not calling this before since the program will quit there.
return true;
}
KisApplication::~KisApplication()
{
delete d;
}
void KisApplication::setSplashScreen(QWidget *splashScreen)
{
d->splashScreen = qobject_cast(splashScreen);
}
void KisApplication::setSplashScreenLoadingText(QString textToLoad)
{
if (d->splashScreen) {
d->splashScreen->loadingLabel->setText(textToLoad);
d->splashScreen->repaint();
}
}
void KisApplication::hideSplashScreen()
{
if (d->splashScreen) {
// hide the splashscreen to see the dialog
d->splashScreen->hide();
}
}
bool KisApplication::notify(QObject *receiver, QEvent *event)
{
try {
return QApplication::notify(receiver, event);
} catch (std::exception &e) {
qWarning("Error %s sending event %i to object %s",
e.what(), event->type(), qPrintable(receiver->objectName()));
} catch (...) {
qWarning("Error sending event %i to object %s",
event->type(), qPrintable(receiver->objectName()));
}
return false;
}
void KisApplication::remoteArguments(QByteArray message, QObject *socket)
{
Q_UNUSED(socket);
// check if we have any mainwindow
KisMainWindow *mw = qobject_cast(qApp->activeWindow());
if (!mw) {
mw = KisPart::instance()->mainWindows().first();
}
if (!mw) {
return;
}
KisApplicationArguments args = KisApplicationArguments::deserialize(message);
const bool doTemplate = args.doTemplate();
const int argsCount = args.filenames().count();
if (argsCount > 0) {
// Loop through arguments
for (int argNumber = 0; argNumber < argsCount; ++argNumber) {
QString filename = args.filenames().at(argNumber);
// are we just trying to open a template?
if (doTemplate) {
createNewDocFromTemplate(filename, mw);
}
else if (QFile(filename).exists()) {
KisMainWindow::OpenFlags flags = m_batchRun ? KisMainWindow::BatchMode : KisMainWindow::None;
mw->openDocument(QUrl::fromLocalFile(filename), flags);
}
}
}
}
void KisApplication::fileOpenRequested(const QString &url)
{
KisMainWindow *mainWindow = KisPart::instance()->mainWindows().first();
if (mainWindow) {
KisMainWindow::OpenFlags flags = m_batchRun ? KisMainWindow::BatchMode : KisMainWindow::None;
mainWindow->openDocument(QUrl::fromLocalFile(url), flags);
}
}
void KisApplication::checkAutosaveFiles()
{
if (m_batchRun) return;
// Check for autosave files from a previous run. There can be several, and
// we want to offer a restore for every one. Including a nice thumbnail!
QStringList filters;
filters << QString(".krita-*-*-autosave.kra");
#ifdef Q_OS_WIN
QDir dir = QDir::temp();
#else
QDir dir = QDir::home();
#endif
// all autosave files for our application
- m_autosaveFiles = dir.entryList(filters, QDir::Files | QDir::Hidden);
+ QStringList autosaveFiles = dir.entryList(filters, QDir::Files | QDir::Hidden);
// Allow the user to make their selection
- if (m_autosaveFiles.size() > 0) {
+ if (autosaveFiles.size() > 0) {
if (d->splashScreen) {
// hide the splashscreen to see the dialog
d->splashScreen->hide();
}
- m_autosaveDialog = new KisAutoSaveRecoveryDialog(m_autosaveFiles, activeWindow());
+ m_autosaveDialog = new KisAutoSaveRecoveryDialog(autosaveFiles, activeWindow());
QDialog::DialogCode result = (QDialog::DialogCode) m_autosaveDialog->exec();
if (result == QDialog::Accepted) {
QStringList filesToRecover = m_autosaveDialog->recoverableFiles();
- Q_FOREACH (const QString &autosaveFile, m_autosaveFiles) {
+ Q_FOREACH (const QString &autosaveFile, autosaveFiles) {
if (!filesToRecover.contains(autosaveFile)) {
QFile::remove(dir.absolutePath() + "/" + autosaveFile);
}
}
- m_autosaveFiles = filesToRecover;
+ autosaveFiles = filesToRecover;
} else {
- m_autosaveFiles.clear();
+ autosaveFiles.clear();
}
- if (m_autosaveFiles.size() > 0) {
+ if (autosaveFiles.size() > 0) {
QList autosaveUrls;
- Q_FOREACH (const QString &autoSaveFile, m_autosaveFiles) {
+ Q_FOREACH (const QString &autoSaveFile, autosaveFiles) {
const QUrl url = QUrl::fromLocalFile(dir.absolutePath() + QLatin1Char('/') + autoSaveFile);
autosaveUrls << url;
}
if (m_mainWindow) {
Q_FOREACH (const QUrl &url, autosaveUrls) {
KisMainWindow::OpenFlags flags = m_batchRun ? KisMainWindow::BatchMode : KisMainWindow::None;
- m_mainWindow->openDocument(url, flags);
+ m_mainWindow->openDocument(url, flags | KisMainWindow::RecoveryFile);
}
}
}
// cleanup
delete m_autosaveDialog;
m_autosaveDialog = nullptr;
}
}
bool KisApplication::createNewDocFromTemplate(const QString &fileName, KisMainWindow *mainWindow)
{
QString templatePath;
const QUrl templateUrl = QUrl::fromLocalFile(fileName);
if (QFile::exists(fileName)) {
templatePath = templateUrl.toLocalFile();
dbgUI << "using full path...";
}
else {
QString desktopName(fileName);
const QString templatesResourcePath = QStringLiteral("templates/");
QStringList paths = KoResourcePaths::findAllResources("data", templatesResourcePath + "*/" + desktopName);
if (paths.isEmpty()) {
paths = KoResourcePaths::findAllResources("data", templatesResourcePath + desktopName);
}
if (paths.isEmpty()) {
QMessageBox::critical(0, i18nc("@title:window", "Krita"),
i18n("No template found for: %1", desktopName));
} else if (paths.count() > 1) {
QMessageBox::critical(0, i18nc("@title:window", "Krita"),
i18n("Too many templates found for: %1", desktopName));
} else {
templatePath = paths.at(0);
}
}
if (!templatePath.isEmpty()) {
QUrl templateBase;
templateBase.setPath(templatePath);
KDesktopFile templateInfo(templatePath);
QString templateName = templateInfo.readUrl();
QUrl templateURL;
templateURL.setPath(templateBase.adjusted(QUrl::RemoveFilename|QUrl::StripTrailingSlash).path() + '/' + templateName);
KisMainWindow::OpenFlags batchFlags = m_batchRun ? KisMainWindow::BatchMode : KisMainWindow::None;
if (mainWindow->openDocument(templateURL, KisMainWindow::Import | batchFlags)) {
dbgUI << "Template loaded...";
return true;
}
else {
QMessageBox::critical(0, i18nc("@title:window", "Krita"),
i18n("Template %1 failed to load.", templateURL.toDisplayString()));
}
}
return false;
}
void KisApplication::clearConfig()
{
KIS_ASSERT_RECOVER_RETURN(qApp->thread() == QThread::currentThread());
KSharedConfigPtr config = KSharedConfig::openConfig();
// find user settings file
bool createDir = false;
QString kritarcPath = KoResourcePaths::locateLocal("config", "kritarc", createDir);
QFile configFile(kritarcPath);
if (configFile.exists()) {
// clear file
if (configFile.open(QFile::WriteOnly)) {
configFile.close();
}
else {
QMessageBox::warning(0,
i18nc("@title:window", "Krita"),
i18n("Failed to clear %1\n\n"
"Please make sure no other program is using the file and try again.",
kritarcPath),
QMessageBox::Ok, QMessageBox::Ok);
}
}
// reload from disk; with the user file settings cleared,
// this should load any default configuration files shipping with the program
config->reparseConfiguration();
config->sync();
}
void KisApplication::askClearConfig()
{
Qt::KeyboardModifiers mods = QApplication::queryKeyboardModifiers();
bool askClearConfig = (mods & Qt::ControlModifier) && (mods & Qt::ShiftModifier) && (mods & Qt::AltModifier);
if (askClearConfig) {
bool ok = QMessageBox::question(0,
i18nc("@title:window", "Krita"),
i18n("Do you want to clear the settings file?"),
QMessageBox::Yes | QMessageBox::No, QMessageBox::No) == QMessageBox::Yes;
if (ok) {
clearConfig();
}
}
}
diff --git a/libs/ui/KisApplication.h b/libs/ui/KisApplication.h
index 2ee65c1657..4729e1be57 100644
--- a/libs/ui/KisApplication.h
+++ b/libs/ui/KisApplication.h
@@ -1,124 +1,123 @@
/* This file is part of the KDE project
Copyright (C) 1998, 1999 Torben Weis
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Library General Public
License as published by the Free Software Foundation; either
version 2 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Library General Public License for more details.
You should have received a copy of the GNU Library General Public License
along with this library; see the file COPYING.LIB. If not, write to
the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#ifndef KIS_APPLICATION_H
#define KIS_APPLICATION_H
#include
#include
#include "kritaui_export.h"
#include
class KisMainWindow;
class KisApplicationPrivate;
class QWidget;
class KisApplicationArguments;
class KisAutoSaveRecoveryDialog;
#include
/**
* @brief Base class for the %Krita app
*
* This class handles arguments given on the command line and
* shows a generic about dialog for the Krita app.
*
* In addition it adds the standard directories where Krita
* can find its images etc.
*
* If the last mainwindow becomes closed, KisApplication automatically
* calls QApplication::quit.
*/
class KRITAUI_EXPORT KisApplication : public QtSingleApplication
{
Q_OBJECT
public:
/**
* Creates an application object, adds some standard directories and
* initializes kimgio.
*/
explicit KisApplication(const QString &key, int &argc, char **argv);
/**
* Destructor.
*/
virtual ~KisApplication();
/**
* Call this to start the application.
*
* Parses command line arguments and creates the initial main windowss and docs
* from them (or an empty doc if no cmd-line argument is specified ).
*
* You must call this method directly before calling QApplication::exec.
*
* It is valid behaviour not to call this method at all. In this case you
* have to process your command line parameters by yourself.
*/
virtual bool start(const KisApplicationArguments &args);
/**
* Checks if user is holding ctrl+alt+shift keys and asks if the settings file should be cleared.
*
* Typically called during startup before reading the config.
*/
void askClearConfig();
/**
* Tell KisApplication to show this splashscreen when you call start();
* when start returns, the splashscreen is hidden. Use KSplashScreen
* to have the splash show correctly on Xinerama displays.
*/
void setSplashScreen(QWidget *splash);
void setSplashScreenLoadingText(QString);
void hideSplashScreen();
/// Overridden to handle exceptions from event handlers.
bool notify(QObject *receiver, QEvent *event);
void addResourceTypes();
void loadResources();
void loadPlugins();
void initializeGlobals(const KisApplicationArguments &args);
public Q_SLOTS:
void remoteArguments(QByteArray message, QObject*socket);
void fileOpenRequested(const QString & url);
private:
/// @return the number of autosavefiles opened
void checkAutosaveFiles();
bool createNewDocFromTemplate(const QString &fileName, KisMainWindow *m_mainWindow);
void clearConfig();
private:
KisApplicationPrivate * const d;
class ResetStarting;
friend class ResetStarting;
KisAutoSaveRecoveryDialog *m_autosaveDialog;
- QStringList m_autosaveFiles;
QPointer m_mainWindow; // The first mainwindow we create on startup
bool m_batchRun;
};
#endif
diff --git a/libs/ui/KisDocument.cpp b/libs/ui/KisDocument.cpp
index 067b243bf1..24f997fc8a 100644
--- a/libs/ui/KisDocument.cpp
+++ b/libs/ui/KisDocument.cpp
@@ -1,1738 +1,1875 @@
/* This file is part of the Krita project
*
* Copyright (C) 2014 Boudewijn Rempt
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public License
* along with this library; see the file COPYING.LIB. If not, write to
* the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#include "KisMainWindow.h" // XXX: remove
#include // XXX: remove
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
+#include
+#include
// Krita Image
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
// Local
#include "KisViewManager.h"
#include "kis_clipboard.h"
#include "widgets/kis_custom_image_widget.h"
#include "canvas/kis_canvas2.h"
#include "flake/kis_shape_controller.h"
#include "kis_statusbar.h"
#include "widgets/kis_progress_widget.h"
#include "kis_canvas_resource_provider.h"
#include "kis_resource_server_provider.h"
#include "kis_node_manager.h"
#include "KisPart.h"
#include "KisApplication.h"
#include "KisDocument.h"
#include "KisImportExportManager.h"
#include "KisPart.h"
#include "KisView.h"
#include "kis_grid_config.h"
#include "kis_guides_config.h"
#include "kis_image_barrier_lock_adapter.h"
#include
#include "kis_config_notifier.h"
// Define the protocol used here for embedded documents' URL
// This used to "store" but QUrl didn't like it,
// so let's simply make it "tar" !
#define STORE_PROTOCOL "tar"
// The internal path is a hack to make QUrl happy and for document children
#define INTERNAL_PROTOCOL "intern"
#define INTERNAL_PREFIX "intern:/"
// Warning, keep it sync in koStore.cc
#include
using namespace std;
/**********************************************************
*
* KisDocument
*
**********************************************************/
namespace {
class DocumentProgressProxy : public KoProgressProxy {
public:
KisMainWindow *m_mainWindow;
DocumentProgressProxy(KisMainWindow *mainWindow)
: m_mainWindow(mainWindow)
{
}
~DocumentProgressProxy() override {
// signal that the job is done
setValue(-1);
}
int maximum() const override {
return 100;
}
void setValue(int value) override {
if (m_mainWindow) {
m_mainWindow->slotProgress(value);
}
}
void setRange(int /*minimum*/, int /*maximum*/) override {
}
void setFormat(const QString &/*format*/) override {
}
};
}
//static
QString KisDocument::newObjectName()
{
static int s_docIFNumber = 0;
QString name; name.setNum(s_docIFNumber++); name.prepend("document_");
return name;
}
class UndoStack : public KUndo2Stack
{
public:
UndoStack(KisDocument *doc)
: KUndo2Stack(doc),
m_doc(doc)
{
}
void setIndex(int idx) override {
KisImageWSP image = this->image();
image->requestStrokeCancellation();
if(image->tryBarrierLock()) {
KUndo2Stack::setIndex(idx);
image->unlock();
}
}
void notifySetIndexChangedOneCommand() override {
KisImageWSP image = this->image();
image->unlock();
/**
* Some very weird commands may emit blocking signals to
* the GUI (e.g. KisGuiContextCommand). Here is the best thing
* we can do to avoid the deadlock
*/
while(!image->tryBarrierLock()) {
QApplication::processEvents();
}
}
void undo() override {
KisImageWSP image = this->image();
image->requestUndoDuringStroke();
if (image->tryUndoUnfinishedLod0Stroke() == UNDO_OK) {
return;
}
if(image->tryBarrierLock()) {
KUndo2Stack::undo();
image->unlock();
}
}
void redo() override {
KisImageWSP image = this->image();
if(image->tryBarrierLock()) {
KUndo2Stack::redo();
image->unlock();
}
}
private:
KisImageWSP image() {
KisImageWSP currentImage = m_doc->image();
Q_ASSERT(currentImage);
return currentImage;
}
private:
KisDocument *m_doc;
};
class Q_DECL_HIDDEN KisDocument::Private
{
public:
Private(KisDocument *q) :
docInfo(new KoDocumentInfo(q)), // deleted by QObject
importExportManager(new KisImportExportManager(q)), // deleted manually
undoStack(new UndoStack(q)), // deleted by QObject
m_bAutoDetectedMime(false),
modified(false),
readwrite(true),
firstMod(QDateTime::currentDateTime()),
lastMod(firstMod),
nserver(new KisNameServer(1)),
imageIdleWatcher(2000 /*ms*/),
savingLock(&savingMutex)
{
if (QLocale().measurementSystem() == QLocale::ImperialSystem) {
unit = KoUnit::Inch;
} else {
unit = KoUnit::Centimeter;
}
}
Private(const Private &rhs, KisDocument *q) :
docInfo(new KoDocumentInfo(*rhs.docInfo, q)),
unit(rhs.unit),
importExportManager(new KisImportExportManager(q)),
mimeType(rhs.mimeType),
outputMimeType(rhs.outputMimeType),
undoStack(new UndoStack(q)),
guidesConfig(rhs.guidesConfig),
m_bAutoDetectedMime(rhs.m_bAutoDetectedMime),
m_url(rhs.m_url),
m_file(rhs.m_file),
modified(rhs.modified),
readwrite(rhs.readwrite),
firstMod(rhs.firstMod),
lastMod(rhs.lastMod),
nserver(new KisNameServer(*rhs.nserver)),
preActivatedNode(rhs.preActivatedNode),
imageIdleWatcher(2000 /*ms*/),
assistants(rhs.assistants), // WARNING: assistants should not store pointers to the document!
gridConfig(rhs.gridConfig),
savingLock(&savingMutex)
{
}
~Private() {
// Don't delete m_d->shapeController because it's in a QObject hierarchy.
delete nserver;
}
KoDocumentInfo *docInfo = 0;
KoProgressUpdater *progressUpdater = 0;
KoProgressProxy *progressProxy = 0;
KoUnit unit;
KisImportExportManager *importExportManager = 0; // The filter-manager to use when loading/saving [for the options]
QByteArray mimeType; // The actual mimetype of the document
QByteArray outputMimeType; // The mimetype to use when saving
QTimer autoSaveTimer;
QString lastErrorMessage; // see openFile()
QString lastWarningMessage;
int autoSaveDelay = 300; // in seconds, 0 to disable.
bool modifiedAfterAutosave = false;
bool isAutosaving = false;
bool disregardAutosaveFailure = false;
KUndo2Stack *undoStack = 0;
KisGuidesConfig guidesConfig;
bool m_bAutoDetectedMime = false; // whether the mimetype in the arguments was detected by the part itself
QUrl m_url; // local url - the one displayed to the user.
QString m_file; // Local file - the only one the part implementation should deal with.
QMutex savingMutex;
bool modified = false;
bool readwrite = false;
QDateTime firstMod;
QDateTime lastMod;
KisNameServer *nserver;
KisImageSP image;
KisImageSP savingImage;
KisNodeSP preActivatedNode;
KisShapeController* shapeController = 0;
KoShapeController* koShapeController = 0;
KisIdleWatcher imageIdleWatcher;
QScopedPointer imageIdleConnection;
bool suppressProgress = false;
KoProgressProxy* fileProgressProxy = 0;
QList assistants;
KisGridConfig gridConfig;
StdLockableWrapper savingLock;
+ QScopedPointer backgroundSaveDocument;
+ QFuture childSavingFuture;
+
+ bool isRecovered = false;
+
void setImageAndInitIdleWatcher(KisImageSP _image) {
image = _image;
imageIdleWatcher.setTrackedImage(image);
if (image) {
imageIdleConnection.reset(
new KisSignalAutoConnection(
&imageIdleWatcher, SIGNAL(startedIdleMode()),
image.data(), SLOT(explicitRegenerateLevelOfDetail())));
}
}
class SafeSavingLocker;
+ class StrippedSafeSavingLocker;
};
class KisDocument::Private::SafeSavingLocker {
public:
SafeSavingLocker(KisDocument::Private *_d, KisDocument *document)
: d(_d)
, m_document(document)
, m_locked(false)
, m_imageLock(d->image, true)
{
const int realAutoSaveInterval = KisConfig().autoSaveInterval();
const int emergencyAutoSaveInterval = 10; // sec
/**
* Initial try to lock both objects. Locking the image guards
* us from any image composition threads running in the
* background, while savingMutex guards us from entering the
* saving code twice by autosave and main threads.
*
* Since we are trying to lock multiple objects, so we should
* do it in a safe manner.
*/
m_locked = std::try_lock(m_imageLock, d->savingLock) < 0;
if (!m_locked) {
if (d->isAutosaving) {
d->disregardAutosaveFailure = true;
if (realAutoSaveInterval) {
m_document->setAutoSaveDelay(emergencyAutoSaveInterval);
}
} else {
d->image->requestStrokeEnd();
QApplication::processEvents();
// one more try...
m_locked = std::try_lock(m_imageLock, d->savingLock) < 0;
}
}
if (m_locked) {
d->disregardAutosaveFailure = false;
}
}
~SafeSavingLocker() {
if (m_locked) {
m_imageLock.unlock();
d->savingLock.unlock();
const int realAutoSaveInterval = KisConfig().autoSaveInterval();
m_document->setAutoSaveDelay(realAutoSaveInterval);
}
}
bool successfullyLocked() const {
return m_locked;
}
private:
KisDocument::Private *d;
KisDocument *m_document;
bool m_locked;
KisImageBarrierLockAdapter m_imageLock;
};
+class KisDocument::Private::StrippedSafeSavingLocker {
+public:
+ StrippedSafeSavingLocker(QMutex *savingMutex, KisImageSP image)
+ : m_locked(false)
+ , m_image(image)
+ , m_savingLock(savingMutex)
+ , m_imageLock(image, true)
+
+ {
+ /**
+ * Initial try to lock both objects. Locking the image guards
+ * us from any image composition threads running in the
+ * background, while savingMutex guards us from entering the
+ * saving code twice by autosave and main threads.
+ *
+ * Since we are trying to lock multiple objects, so we should
+ * do it in a safe manner.
+ */
+ m_locked = std::try_lock(m_imageLock, m_savingLock) < 0;
+
+ if (!m_locked) {
+ m_image->requestStrokeEnd();
+ QApplication::processEvents();
+
+ // one more try...
+ m_locked = std::try_lock(m_imageLock, m_savingLock) < 0;
+ }
+ }
+
+ ~StrippedSafeSavingLocker() {
+ if (m_locked) {
+ m_imageLock.unlock();
+ m_savingLock.unlock();
+ }
+ }
+
+ bool successfullyLocked() const {
+ return m_locked;
+ }
+
+private:
+ bool m_locked;
+ KisImageSP m_image;
+ StdLockableWrapper m_savingLock;
+ KisImageBarrierLockAdapter m_imageLock;
+};
+
KisDocument::KisDocument()
: d(new Private(this))
{
connect(KisConfigNotifier::instance(), SIGNAL(configChanged()), SLOT(slotConfigChanged()));
connect(d->undoStack, SIGNAL(indexChanged(int)), this, SLOT(slotUndoStackIndexChanged(int)));
connect(&d->autoSaveTimer, SIGNAL(timeout()), this, SLOT(slotAutoSave()));
setObjectName(newObjectName());
// preload the krita resources
KisResourceServerProvider::instance();
d->shapeController = new KisShapeController(this, d->nserver),
d->koShapeController = new KoShapeController(0, d->shapeController),
slotConfigChanged();
}
KisDocument::KisDocument(const KisDocument &rhs)
: QObject(),
d(new Private(*rhs.d, this))
{
connect(KisConfigNotifier::instance(), SIGNAL(configChanged()), SLOT(slotConfigChanged()));
connect(d->undoStack, SIGNAL(indexChanged(int)), this, SLOT(slotUndoStackIndexChanged(int)));
connect(&d->autoSaveTimer, SIGNAL(timeout()), this, SLOT(slotAutoSave()));
setObjectName(rhs.objectName());
d->shapeController = new KisShapeController(this, d->nserver),
d->koShapeController = new KoShapeController(0, d->shapeController),
slotConfigChanged();
// clone the image with keeping the GUIDs of the layers intact
// NOTE: we expect the image to be locked!
setCurrentImage(rhs.image()->clone(true));
}
KisDocument::~KisDocument()
{
/**
* Push a timebomb, which will try to release the memory after
* the document has been deleted
*/
KisPaintDevice::createMemoryReleaseObject()->deleteLater();
d->autoSaveTimer.disconnect(this);
d->autoSaveTimer.stop();
delete d->importExportManager;
// Despite being QObject they needs to be deleted before the image
delete d->shapeController;
delete d->koShapeController;
if (d->image) {
d->image->notifyAboutToBeDeleted();
/**
* WARNING: We should wait for all the internal image jobs to
* finish before entering KisImage's destructor. The problem is,
* while execution of KisImage::~KisImage, all the weak shared
* pointers pointing to the image enter an inconsistent
* state(!). The shared counter is already zero and destruction
* has started, but the weak reference doesn't know about it,
* because KisShared::~KisShared hasn't been executed yet. So all
* the threads running in background and having weak pointers will
* enter the KisImage's destructor as well.
*/
d->image->requestStrokeCancellation();
d->image->waitForDone();
// clear undo commands that can still point to the image
d->undoStack->clear();
d->image->waitForDone();
KisImageWSP sanityCheckPointer = d->image;
Q_UNUSED(sanityCheckPointer);
// The following line trigger the deletion of the image
d->image.clear();
// check if the image has actually been deleted
KIS_SAFE_ASSERT_RECOVER_NOOP(!sanityCheckPointer.isValid());
}
delete d;
}
bool KisDocument::reload()
{
// XXX: reimplement!
return false;
}
bool KisDocument::exportDocument(const QUrl &_url, KisPropertiesConfigurationSP exportConfiguration)
{
//qDebug() << "exportDocument" << _url.toDisplayString() << "is autosaving" << d->isAutosaving;
bool ret;
//
// Preserve a lot of state here because we need to restore it in order to
// be able to fake a File --> Export. Can't do this in saveFile() because,
// for a start, KParts has already set url and m_file and because we need
// to restore the modified flag etc. and don't want to put a load on anyone
// reimplementing saveFile() (Note: importDocument() and exportDocument()
// will remain non-virtual).
//
QUrl oldURL = url();
QString oldFile = localFilePath();
//qDebug() << "\toldUrl" << oldURL << "oldFile" << oldFile << "export url" << _url;
bool wasModified = isModified();
// save...
ret = saveAs(_url, true, exportConfiguration);
//
// This is sooooo hacky :(
// Hopefully we will restore enough state.
//
dbgUI << "Restoring KisDocument state to before export";
// always restore url & m_file regardless of failure or success
//qDebug() << "\tafter saveAs: url" << url() << "local file path" << localFilePath();
setUrl(oldURL);
setLocalFilePath(oldFile);
//qDebug() << "\tafter restoring: url" << url() << "local file path" << localFilePath();
// on successful export we need to restore modified etc. too
// on failed export, mimetype/modified hasn't changed anyway
if (ret) {
setModified(wasModified);
}
return ret;
}
bool KisDocument::saveAs(const QUrl &url, bool showWarnings, KisPropertiesConfigurationSP exportConfiguration)
{
//qDebug() << "saveAs" << url;
if (!url.isValid() || !url.isLocalFile()) {
errKrita << "saveAs: Malformed URL " << url.url() << endl;
return false;
}
QUrl originalURL = d->m_url;
QString originalFilePath = d->m_file;
d->m_url = url; // Store where to upload in saveToURL
d->m_file = d->m_url.toLocalFile();
bool result = save(showWarnings, exportConfiguration); // Save local file and upload local file
if (!result) {
d->m_url = originalURL;
d->m_file = originalFilePath;
}
return result;
}
bool KisDocument::save(bool showWarnings, KisPropertiesConfigurationSP exportConfiguration)
{
//qDebug() << "save" << d->m_file << d->m_url << url() << localFilePath();
if (d->m_file.isEmpty()) { // document was created empty
d->m_file = d->m_url.toLocalFile();
}
updateEditingTime(true);
setFileProgressProxy();
setUrl(url());
bool ok = saveFile(localFilePath(), showWarnings, exportConfiguration);
clearFileProgressProxy();
if (ok) {
setModified( false );
emit completed();
return true; // Nothing to do
}
else {
emit canceled(QString());
}
return false;
}
QByteArray KisDocument::serializeToNativeByteArray()
{
QByteArray byteArray;
QBuffer buffer(&byteArray);
QScopedPointer filter(KisImportExportManager::filterForMimeType(nativeFormatMimeType(), KisImportExportManager::Export));
filter->setBatchMode(true);
filter->setMimeType(nativeFormatMimeType());
if (!prepareLocksForSaving()) {
return byteArray;
}
if (filter->convert(this, &buffer) != KisImportExportFilter::OK) {
qWarning() << "serializeToByteArray():: Could not export to our native format";
}
unlockAfterSaving();
return byteArray;
}
bool KisDocument::saveFile(const QString &filePath, bool showWarnings, KisPropertiesConfigurationSP exportConfiguration)
{
if (!prepareLocksForSaving()) {
return false;
}
// Unset the error message
setErrorMessage("");
// Save it to be able to restore it after a failed save
const bool wasModified = isModified();
bool ret = false;
bool suppressErrorDialog = fileBatchMode();
KisImportExportFilter::ConversionStatus status = KisImportExportFilter::OK;
//qDebug() << "saveFile" << localFilePath() << QFileInfo(localFilePath()).exists() << !QFileInfo(localFilePath()).isWritable();
if (QFileInfo(localFilePath()).exists() && !QFileInfo(localFilePath()).isWritable()) {
setErrorMessage(i18n("%1 cannot be written to. Please save under a different name.", localFilePath()));
}
else {
// The output format is set by KisMainWindow, and by openFile
QByteArray outputMimeType = d->outputMimeType;
if (outputMimeType.isEmpty()) {
outputMimeType = d->outputMimeType = nativeFormatMimeType();
}
//qDebug() << "saveFile. Is Autosaving?" << isAutosaving() << "url" << filePath << d->outputMimeType;
KisConfig cfg;
if (cfg.backupFile()) {
Q_ASSERT(url().isLocalFile());
KBackup::backupFile(url().toLocalFile());
}
qApp->processEvents();
setFileProgressUpdater(i18n("Saving Document"));
//qDebug() << "saving to tempory file" << tempororaryFileName;
status = d->importExportManager->exportDocument(localFilePath(), filePath, outputMimeType, showWarnings , exportConfiguration);
ret = (status == KisImportExportFilter::OK);
suppressErrorDialog = (fileBatchMode() || isAutosaving() || status == KisImportExportFilter::UserCancelled || status == KisImportExportFilter::BadConversionGraph);
//qDebug() << "Export status was" << status;
if (ret) {
if (!d->isAutosaving && !d->suppressProgress) {
QPointer updater = d->progressUpdater->startSubtask(1, "clear undo stack");
updater->setProgress(0);
d->undoStack->setClean();
updater->setProgress(100);
} else {
d->undoStack->setClean();
}
if (errorMessage().isEmpty()) {
if (!isAutosaving()) {
removeAutoSaveFiles();
}
}
else {
ret = false;
qWarning() << "Error while saving:" << errorMessage();
}
// Restart the autosave timer
// (we don't want to autosave again 2 seconds after a real save)
if (!isAutosaving()) {
setAutoSaveDelay(d->autoSaveDelay);
}
d->mimeType = outputMimeType;
}
}
if (!ret) {
if (!suppressErrorDialog) {
if (errorMessage().isEmpty()) {
setErrorMessage(KisImportExportFilter::conversionStatusString(status));
}
if (errorMessage().isEmpty()) {
QMessageBox::critical(0, i18nc("@title:window", "Krita"), i18n("Could not save\n%1", filePath));
} else {
QMessageBox::critical(0, i18nc("@title:window", "Krita"), i18n("Could not save %1\nReason: %2", filePath, errorMessage()));
}
}
// couldn't save file so this new URL is invalid
// FIXME: we should restore the current document's true URL instead of
// setting it to nothing otherwise anything that depends on the URL
// being correct will not work (i.e. the document will be called
// "Untitled" which may not be true)
//
// Update: now the URL is restored in KisMainWindow but really, this
// should still be fixed in KisDocument/KParts (ditto for file).
// We still resetURL() here since we may or may not have been called
// by KisMainWindow - Clarence
resetURL();
// As we did not save, restore the "was modified" status
setModified(wasModified);
}
emit sigSavingFinished();
clearFileProgressUpdater();
unlockAfterSaving();
return ret;
}
QByteArray KisDocument::mimeType() const
{
return d->mimeType;
}
void KisDocument::setMimeType(const QByteArray & mimeType)
{
d->mimeType = mimeType;
}
void KisDocument::setOutputMimeType(const QByteArray & mimeType)
{
d->outputMimeType = mimeType;
}
QByteArray KisDocument::outputMimeType() const
{
return d->outputMimeType;
}
bool KisDocument::fileBatchMode() const
{
return d->importExportManager->batchMode();
}
void KisDocument::setFileBatchMode(const bool batchMode)
{
d->importExportManager->setBatchMode(batchMode);
}
void KisDocument::slotAutoSave()
{
- //qDebug() << "slotAutoSave. Modified:" << d->modified << "modifiedAfterAutosave" << d->modified << "url" << url() << localFilePath();
+ if (!d->modified || !d->modifiedAfterAutosave) return;
- if (!d->isAutosaving && d->modified && d->modifiedAfterAutosave) {
+ QScopedPointer clonedDocument;
- connect(this, SIGNAL(sigProgress(int)), KisPart::instance()->currentMainwindow(), SLOT(slotProgress(int)));
- emit statusBarMessage(i18n("Autosaving..."));
- d->isAutosaving = true;
- QString autoSaveFileName = generateAutoSaveFileName(localFilePath());
+ {
+ Private::StrippedSafeSavingLocker locker(&d->savingMutex, d->image);
+ if (!locker.successfullyLocked()) {
+ const int emergencyAutoSaveInterval = 10; // sec
+ setAutoSaveDelay(emergencyAutoSaveInterval);
+ return;
+ }
+ clonedDocument.reset(new KisDocument(*this));
+ }
+ KIS_SAFE_ASSERT_RECOVER_RETURN(clonedDocument);
- QByteArray mimetype = d->outputMimeType;
- d->outputMimeType = nativeFormatMimeType();
- bool ret = exportDocument(QUrl::fromLocalFile(autoSaveFileName));
- d->outputMimeType = mimetype;
+ // we block saving until the current saving is finished!
+ if (!d->savingMutex.tryLock()) return;
- if (ret) {
- d->modifiedAfterAutosave = false;
- d->autoSaveTimer.stop(); // until the next change
- }
- d->isAutosaving = false;
+ KIS_ASSERT_RECOVER_RETURN(!d->backgroundSaveDocument);
+ d->backgroundSaveDocument.reset(clonedDocument.take());
+ emit statusBarMessage(i18n("Autosaving..."));
+ connect(d->backgroundSaveDocument.data(), SIGNAL(sigBackgroundSavingFinished(KisImportExportFilter::ConversionStatus)), SLOT(slotChildCompletedSavingInBackground(KisImportExportFilter::ConversionStatus)));
+
+ const QString autoSaveFileName = generateAutoSaveFileName(localFilePath());
+ bool started =
+ d->backgroundSaveDocument->startExportInBackground(QUrl::fromLocalFile(autoSaveFileName),
+ nativeFormatMimeType());
+
+ if (!started) {
+ const int emergencyAutoSaveInterval = 10; // sec
+ setAutoSaveDelay(emergencyAutoSaveInterval);
+ }
+}
+
+void KisDocument::slotChildCompletedSavingInBackground(KisImportExportFilter::ConversionStatus status)
+{
+ KIS_SAFE_ASSERT_RECOVER(!d->savingMutex.tryLock()) {
+ d->savingMutex.unlock();
+ return;
+ }
+
+ KIS_SAFE_ASSERT_RECOVER_RETURN(d->backgroundSaveDocument);
+ d->backgroundSaveDocument.take()->deleteLater();
+ d->savingMutex.unlock();
+
+ if (status != KisImportExportFilter::OK) {
+ const int emergencyAutoSaveInterval = 10; // sec
+ setAutoSaveDelay(emergencyAutoSaveInterval);
+ emit statusBarMessage(i18n("Error during autosave! Partition full?"));
+ } else {
+ d->modifiedAfterAutosave = false;
+ KisConfig cfg;
+ d->autoSaveDelay = cfg.autoSaveInterval();
+ d->autoSaveTimer.stop(); // until the next change
emit clearStatusBarMessage();
- disconnect(this, SIGNAL(sigProgress(int)), KisPart::instance()->currentMainwindow(), SLOT(slotProgress(int)));
+ }
+}
- if (!ret && !d->disregardAutosaveFailure) {
- emit statusBarMessage(i18n("Error during autosave! Partition full?"));
- }
+bool KisDocument::startExportInBackground(const QUrl &url,
+ const QByteArray &mimeType)
+{
+ const QString filePath = url.toLocalFile();
+
+ d->savingImage = d->image;
+ d->childSavingFuture =
+ d->importExportManager->exportDocumentAsyc(filePath,
+ filePath,
+ mimeType,
+ false, 0);
+
+ if (d->childSavingFuture.isCanceled()) return false;
+
+ typedef QFutureWatcher StatusWatcher;
+ StatusWatcher *watcher = new StatusWatcher();
+ watcher->setFuture(d->childSavingFuture);
+
+ connect(watcher, SIGNAL(finished()), SLOT(finishExportInBackground()));
+ connect(watcher, SIGNAL(finished()), watcher, SLOT(deleteLater()));
+
+ return true;
+}
+
+void KisDocument::finishExportInBackground()
+{
+ KIS_SAFE_ASSERT_RECOVER(d->childSavingFuture.isFinished()) {
+ emit sigBackgroundSavingFinished(KisImportExportFilter::InternalError);
+ return;
}
+
+ KisImportExportFilter::ConversionStatus status =
+ d->childSavingFuture.result();
+
+ d->savingImage.clear();
+ d->childSavingFuture = QFuture();
+ emit sigBackgroundSavingFinished(status);
}
void KisDocument::setReadWrite(bool readwrite)
{
d->readwrite = readwrite;
setAutoSaveDelay(d->autoSaveDelay);
Q_FOREACH (KisMainWindow *mainWindow, KisPart::instance()->mainWindows()) {
mainWindow->setReadWrite(readwrite);
}
}
void KisDocument::setAutoSaveDelay(int delay)
{
//qDebug() << "setting autosave delay from" << d->autoSaveDelay << "to" << delay;
d->autoSaveDelay = delay;
if (isReadWrite() && d->autoSaveDelay > 0) {
d->autoSaveTimer.start(d->autoSaveDelay * 1000);
}
else {
d->autoSaveTimer.stop();
}
}
KoDocumentInfo *KisDocument::documentInfo() const
{
return d->docInfo;
}
bool KisDocument::isModified() const
{
return d->modified;
}
QPixmap KisDocument::generatePreview(const QSize& size)
{
KisImageSP image = d->image;
if (d->savingImage) image = d->savingImage;
if (image) {
QRect bounds = image->bounds();
QSize newSize = bounds.size();
newSize.scale(size, Qt::KeepAspectRatio);
QPixmap px = QPixmap::fromImage(image->convertToQImage(newSize, 0));
if (px.size() == QSize(0,0)) {
px = QPixmap(newSize);
QPainter gc(&px);
QBrush checkBrush = QBrush(KisCanvasWidgetBase::createCheckersImage(newSize.width() / 5));
gc.fillRect(px.rect(), checkBrush);
gc.end();
}
return px;
}
return QPixmap(size);
}
QString KisDocument::generateAutoSaveFileName(const QString & path) const
{
QString retval;
// Using the extension allows to avoid relying on the mime magic when opening
const QString extension (".kra");
if (path.isEmpty()) {
// Never saved?
#ifdef Q_OS_WIN
// On Windows, use the temp location (https://bugs.kde.org/show_bug.cgi?id=314921)
retval = QString("%1%2.%3-%4-%5-autosave%6").arg(QDir::tempPath()).arg(QDir::separator()).arg("krita").arg(qApp->applicationPid()).arg(objectName()).arg(extension);
#else
// On Linux, use a temp file in $HOME then. Mark it with the pid so two instances don't overwrite each other's autosave file
retval = QString("%1%2.%3-%4-%5-autosave%6").arg(QDir::homePath()).arg(QDir::separator()).arg("krita").arg(qApp->applicationPid()).arg(objectName()).arg(extension);
#endif
} else {
QFileInfo fi(path);
QString dir = fi.absolutePath();
QString filename = fi.fileName();
retval = QString("%1%2.%3-autosave%4").arg(dir).arg(QDir::separator()).arg(filename).arg(extension);
}
//qDebug() << "generateAutoSaveFileName() for path" << path << ":" << retval;
return retval;
}
bool KisDocument::importDocument(const QUrl &_url)
{
bool ret;
dbgUI << "url=" << _url.url();
// open...
ret = openUrl(_url);
// reset url & m_file (kindly? set by KisParts::openUrl()) to simulate a
// File --> Import
if (ret) {
dbgUI << "success, resetting url";
resetURL();
setTitleModified();
}
return ret;
}
-bool KisDocument::openUrl(const QUrl &_url, KisDocument::OpenUrlFlags flags)
+bool KisDocument::openUrl(const QUrl &_url, OpenFlags flags)
{
if (!_url.isLocalFile()) {
return false;
}
dbgUI << "url=" << _url.url();
d->lastErrorMessage.clear();
// Reimplemented, to add a check for autosave files and to improve error reporting
if (!_url.isValid()) {
d->lastErrorMessage = i18n("Malformed URL\n%1", _url.url()); // ## used anywhere ?
return false;
}
QUrl url(_url);
bool autosaveOpened = false;
if (url.isLocalFile() && !fileBatchMode()) {
QString file = url.toLocalFile();
QString asf = generateAutoSaveFileName(file);
if (QFile::exists(asf)) {
KisApplication *kisApp = static_cast(qApp);
kisApp->hideSplashScreen();
//dbgUI <<"asf=" << asf;
// ## TODO compare timestamps ?
int res = QMessageBox::warning(0,
i18nc("@title:window", "Krita"),
i18n("An autosaved file exists for this document.\nDo you want to open the autosaved file instead?"),
QMessageBox::Yes | QMessageBox::No | QMessageBox::Cancel, QMessageBox::Yes);
switch (res) {
case QMessageBox::Yes :
url.setPath(asf);
autosaveOpened = true;
break;
case QMessageBox::No :
QFile::remove(asf);
break;
default: // Cancel
return false;
}
}
}
bool ret = openUrlInternal(url);
- if (autosaveOpened) {
- resetURL(); // Force save to act like 'Save As'
+ if (autosaveOpened || flags & RecoveryFile) {
setReadWrite(true); // enable save button
setModified(true);
+ setRecovered(true);
}
else {
- if( !(flags & OPEN_URL_FLAG_DO_NOT_ADD_TO_RECENT_FILES) ) {
+ if (!(flags & DontAddToRecent)) {
KisPart::instance()->addRecentURLToAllMainWindows(_url);
}
if (ret) {
// Detect readonly local-files; remote files are assumed to be writable
QFileInfo fi(url.toLocalFile());
setReadWrite(fi.isWritable());
}
+
+ setRecovered(false);
}
+
return ret;
}
class DlgLoadMessages : public KoDialog {
public:
DlgLoadMessages(const QString &title, const QString &message, const QStringList &warnings) {
setWindowTitle(title);
setWindowIcon(KisIconUtils::loadIcon("dialog-warning"));
QWidget *page = new QWidget(this);
QVBoxLayout *layout = new QVBoxLayout(page);
QHBoxLayout *hlayout = new QHBoxLayout();
QLabel *labelWarning= new QLabel();
labelWarning->setPixmap(KisIconUtils::loadIcon("dialog-warning").pixmap(32, 32));
hlayout->addWidget(labelWarning);
hlayout->addWidget(new QLabel(message));
layout->addLayout(hlayout);
QTextBrowser *browser = new QTextBrowser();
QString warning = "";
if (warnings.size() == 1) {
warning += " Reason:
";
}
else {
warning += " Reasons:
";
}
warning += "";
Q_FOREACH(const QString &w, warnings) {
warning += "\n- " + w + "
";
}
warning += "
";
browser->setHtml(warning);
browser->setMinimumHeight(200);
browser->setMinimumWidth(400);
layout->addWidget(browser);
setMainWidget(page);
setButtons(KoDialog::Ok);
resize(minimumSize());
}
};
bool KisDocument::openFile()
{
//dbgUI <<"for" << localFilePath();
if (!QFile::exists(localFilePath())) {
QMessageBox::critical(0, i18nc("@title:window", "Krita"), i18n("File %1 does not exist.", localFilePath()));
return false;
}
QString filename = localFilePath();
QString typeName = mimeType();
if (typeName.isEmpty()) {
typeName = KisMimeDatabase::mimeTypeForFile(filename);
}
//qDebug() << "mimetypes 4:" << typeName;
// Allow to open backup files, don't keep the mimetype application/x-trash.
if (typeName == "application/x-trash") {
QString path = filename;
while (path.length() > 0) {
path.chop(1);
typeName = KisMimeDatabase::mimeTypeForFile(path);
//qDebug() << "\t" << path << typeName;
if (!typeName.isEmpty()) {
break;
}
}
//qDebug() << "chopped" << filename << "to" << path << "Was trash, is" << typeName;
}
dbgUI << localFilePath() << "type:" << typeName;
setFileProgressUpdater(i18n("Opening Document"));
KisImportExportFilter::ConversionStatus status;
status = d->importExportManager->importDocument(localFilePath(), typeName);
if (status != KisImportExportFilter::OK) {
QString msg = KisImportExportFilter::conversionStatusString(status);
if (!msg.isEmpty()) {
DlgLoadMessages dlg(i18nc("@title:window", "Krita"),
i18n("Could not open %2.\nReason: %1.", msg, prettyPathOrUrl()),
errorMessage().split("\n") + warningMessage().split("\n"));
dlg.exec();
}
clearFileProgressUpdater();
return false;
}
else if (!warningMessage().isEmpty()) {
DlgLoadMessages dlg(i18nc("@title:window", "Krita"),
i18n("There were problems opening %1.", prettyPathOrUrl()),
warningMessage().split("\n"));
dlg.exec();
setUrl(QUrl());
}
setMimeTypeAfterLoading(typeName);
emit sigLoadingFinished();
if (!d->suppressProgress && d->progressUpdater) {
QPointer updater = d->progressUpdater->startSubtask(1, "clear undo stack");
updater->setProgress(0);
undoStack()->clear();
updater->setProgress(100);
clearFileProgressUpdater();
} else {
undoStack()->clear();
}
return true;
}
KoProgressUpdater *KisDocument::progressUpdater() const
{
return d->progressUpdater;
}
void KisDocument::setProgressProxy(KoProgressProxy *progressProxy)
{
d->progressProxy = progressProxy;
}
KoProgressProxy* KisDocument::progressProxy() const
{
if (!d->progressProxy) {
KisMainWindow *mainWindow = 0;
if (KisPart::instance()->mainwindowCount() > 0) {
mainWindow = KisPart::instance()->mainWindows()[0];
}
d->progressProxy = new DocumentProgressProxy(mainWindow);
}
return d->progressProxy;
}
// shared between openFile and koMainWindow's "create new empty document" code
void KisDocument::setMimeTypeAfterLoading(const QString& mimeType)
{
d->mimeType = mimeType.toLatin1();
d->outputMimeType = d->mimeType;
}
bool KisDocument::loadNativeFormat(const QString & file_)
{
return openUrl(QUrl::fromLocalFile(file_));
}
void KisDocument::setModified()
{
d->modified = true;
}
void KisDocument::setModified(bool mod)
{
if (mod) {
updateEditingTime(false);
}
if (d->isAutosaving) // ignore setModified calls due to autosaving
return;
if ( !d->readwrite && d->modified ) {
errKrita << "Can't set a read-only document to 'modified' !" << endl;
return;
}
//dbgUI<<" url:" << url.path();
//dbgUI<<" mod="<docInfo->aboutInfo("editing-time").toInt() + d->firstMod.secsTo(d->lastMod)));
d->firstMod = now;
} else if (firstModDelta > 60 || forceStoreElapsed) {
d->docInfo->setAboutInfo("editing-time", QString::number(d->docInfo->aboutInfo("editing-time").toInt() + firstModDelta));
d->firstMod = now;
}
d->lastMod = now;
}
QString KisDocument::prettyPathOrUrl() const
{
QString _url(url().toDisplayString());
#ifdef Q_OS_WIN
if (url().isLocalFile()) {
_url = QDir::toNativeSeparators(_url);
}
#endif
return _url;
}
// Get caption from document info (title(), in about page)
QString KisDocument::caption() const
{
QString c;
if (documentInfo()) {
c = documentInfo()->aboutInfo("title");
}
const QString _url(url().fileName());
if (!c.isEmpty() && !_url.isEmpty()) {
c = QString("%1 - %2").arg(c).arg(_url);
}
else if (c.isEmpty()) {
c = _url; // Fall back to document URL
}
return c;
}
void KisDocument::setTitleModified()
{
emit titleModified(caption(), isModified());
}
QDomDocument KisDocument::createDomDocument(const QString& tagName, const QString& version) const
{
return createDomDocument("krita", tagName, version);
}
//static
QDomDocument KisDocument::createDomDocument(const QString& appName, const QString& tagName, const QString& version)
{
QDomImplementation impl;
QString url = QString("http://www.calligra.org/DTD/%1-%2.dtd").arg(appName).arg(version);
QDomDocumentType dtype = impl.createDocumentType(tagName,
QString("-//KDE//DTD %1 %2//EN").arg(appName).arg(version),
url);
// The namespace URN doesn't need to include the version number.
QString namespaceURN = QString("http://www.calligra.org/DTD/%1").arg(appName);
QDomDocument doc = impl.createDocument(namespaceURN, tagName, dtype);
doc.insertBefore(doc.createProcessingInstruction("xml", "version=\"1.0\" encoding=\"UTF-8\""), doc.documentElement());
return doc;
}
bool KisDocument::isNativeFormat(const QByteArray& mimetype) const
{
if (mimetype == nativeFormatMimeType())
return true;
return extraNativeMimeTypes().contains(mimetype);
}
void KisDocument::setErrorMessage(const QString& errMsg)
{
d->lastErrorMessage = errMsg;
}
QString KisDocument::errorMessage() const
{
return d->lastErrorMessage;
}
void KisDocument::setWarningMessage(const QString& warningMsg)
{
d->lastWarningMessage = warningMsg;
}
QString KisDocument::warningMessage() const
{
return d->lastWarningMessage;
}
void KisDocument::removeAutoSaveFiles()
{
//qDebug() << "removeAutoSaveFiles";
// Eliminate any auto-save file
QString asf = generateAutoSaveFileName(localFilePath()); // the one in the current dir
//qDebug() << "\tfilename:" << asf << "exists:" << QFile::exists(asf);
if (QFile::exists(asf)) {
//qDebug() << "\tremoving autosavefile" << asf;
QFile::remove(asf);
}
asf = generateAutoSaveFileName(QString()); // and the one in $HOME
//qDebug() << "Autsavefile in $home" << asf;
if (QFile::exists(asf)) {
//qDebug() << "\tremoving autsavefile 2" << asf;
QFile::remove(asf);
}
}
KoUnit KisDocument::unit() const
{
return d->unit;
}
void KisDocument::setUnit(const KoUnit &unit)
{
if (d->unit != unit) {
d->unit = unit;
emit unitChanged(unit);
}
}
KUndo2Stack *KisDocument::undoStack()
{
return d->undoStack;
}
KisImportExportManager *KisDocument::importExportManager() const
{
return d->importExportManager;
}
void KisDocument::addCommand(KUndo2Command *command)
{
if (command)
d->undoStack->push(command);
}
void KisDocument::beginMacro(const KUndo2MagicString & text)
{
d->undoStack->beginMacro(text);
}
void KisDocument::endMacro()
{
d->undoStack->endMacro();
}
void KisDocument::slotUndoStackIndexChanged(int idx)
{
// even if the document was already modified, call setModified to re-start autosave timer
setModified(idx != d->undoStack->cleanIndex());
}
void KisDocument::slotConfigChanged()
{
KisConfig cfg;
d->undoStack->setUndoLimit(cfg.undoStackLimit());
setAutoSaveDelay(cfg.autoSaveInterval());
}
void KisDocument::clearUndoHistory()
{
d->undoStack->clear();
}
KisGridConfig KisDocument::gridConfig() const
{
return d->gridConfig;
}
void KisDocument::setGridConfig(const KisGridConfig &config)
{
d->gridConfig = config;
}
const KisGuidesConfig& KisDocument::guidesConfig() const
{
return d->guidesConfig;
}
void KisDocument::setGuidesConfig(const KisGuidesConfig &data)
{
if (d->guidesConfig == data) return;
d->guidesConfig = data;
emit sigGuidesConfigChanged(d->guidesConfig);
}
void KisDocument::resetURL() {
setUrl(QUrl());
setLocalFilePath(QString());
}
KoDocumentInfoDlg *KisDocument::createDocumentInfoDialog(QWidget *parent, KoDocumentInfo *docInfo) const
{
return new KoDocumentInfoDlg(parent, docInfo);
}
bool KisDocument::isReadWrite() const
{
return d->readwrite;
}
QUrl KisDocument::url() const
{
return d->m_url;
}
bool KisDocument::closeUrl(bool promptToSave)
{
if (promptToSave) {
if ( isReadWrite() && isModified()) {
Q_FOREACH (KisView *view, KisPart::instance()->views()) {
if (view && view->document() == this) {
if (!view->queryClose()) {
return false;
}
}
}
}
}
// Not modified => ok and delete temp file.
d->mimeType = QByteArray();
// It always succeeds for a read-only part,
// but the return value exists for reimplementations
// (e.g. pressing cancel for a modified read-write part)
return true;
}
void KisDocument::setUrl(const QUrl &url)
{
d->m_url = url;
}
QString KisDocument::localFilePath() const
{
return d->m_file;
}
void KisDocument::setLocalFilePath( const QString &localFilePath )
{
d->m_file = localFilePath;
}
bool KisDocument::openUrlInternal(const QUrl &url)
{
if ( !url.isValid() )
return false;
if (d->m_bAutoDetectedMime) {
d->mimeType = QByteArray();
d->m_bAutoDetectedMime = false;
}
QByteArray mimetype = d->mimeType;
if ( !closeUrl() )
return false;
d->mimeType = mimetype;
setUrl(url);
d->m_file.clear();
if (d->m_url.isLocalFile()) {
d->m_file = d->m_url.toLocalFile();
bool ret;
// set the mimetype only if it was not already set (for example, by the host application)
if (d->mimeType.isEmpty()) {
// get the mimetype of the file
// using findByUrl() to avoid another string -> url conversion
QString mime = KisMimeDatabase::mimeTypeForFile(d->m_url.toLocalFile());
d->mimeType = mime.toLocal8Bit();
d->m_bAutoDetectedMime = true;
}
setFileProgressProxy();
setUrl(d->m_url);
ret = openFile();
clearFileProgressProxy();
if (ret) {
emit completed();
} else {
emit canceled(QString());
}
return ret;
}
return false;
}
bool KisDocument::newImage(const QString& name,
qint32 width, qint32 height,
const KoColorSpace* cs,
const KoColor &bgColor, bool backgroundAsLayer,
int numberOfLayers,
const QString &description, const double imageResolution)
{
Q_ASSERT(cs);
KisConfig cfg;
KisImageSP image;
KisPaintLayerSP layer;
if (!cs) return false;
QApplication::setOverrideCursor(Qt::BusyCursor);
image = new KisImage(createUndoStore(), width, height, cs, name);
Q_CHECK_PTR(image);
connect(image, SIGNAL(sigImageModified()), this, SLOT(setImageModified()), Qt::UniqueConnection);
image->setResolution(imageResolution, imageResolution);
image->assignImageProfile(cs->profile());
documentInfo()->setAboutInfo("title", name);
if (name != i18n("Unnamed") && !name.isEmpty()) {
setUrl(QUrl::fromLocalFile(QDesktopServices::storageLocation(QDesktopServices::PicturesLocation) + '/' + name + ".kra"));
}
documentInfo()->setAboutInfo("abstract", description);
layer = new KisPaintLayer(image.data(), image->nextLayerName(), OPACITY_OPAQUE_U8, cs);
Q_CHECK_PTR(layer);
if (backgroundAsLayer) {
image->setDefaultProjectionColor(KoColor(cs));
if (bgColor.opacityU8() == OPACITY_OPAQUE_U8) {
layer->paintDevice()->setDefaultPixel(bgColor);
} else {
// Hack: with a semi-transparent background color, the projection isn't composited right if we just set the default pixel
KisFillPainter painter;
painter.begin(layer->paintDevice());
painter.fillRect(0, 0, width, height, bgColor, bgColor.opacityU8());
}
} else {
image->setDefaultProjectionColor(bgColor);
}
layer->setDirty(QRect(0, 0, width, height));
image->addNode(layer.data(), image->rootLayer().data());
setCurrentImage(image);
for(int i = 1; i < numberOfLayers; ++i) {
KisPaintLayerSP layer = new KisPaintLayer(image, image->nextLayerName(), OPACITY_OPAQUE_U8, cs);
image->addNode(layer, image->root(), i);
layer->setDirty(QRect(0, 0, width, height));
}
cfg.defImageWidth(width);
cfg.defImageHeight(height);
cfg.defImageResolution(imageResolution);
cfg.defColorModel(image->colorSpace()->colorModelId().id());
cfg.setDefaultColorDepth(image->colorSpace()->colorDepthId().id());
cfg.defColorProfile(image->colorSpace()->profile()->name());
QApplication::restoreOverrideCursor();
return true;
}
KoShapeBasedDocumentBase *KisDocument::shapeController() const
{
return d->shapeController;
}
KoShapeLayer* KisDocument::shapeForNode(KisNodeSP layer) const
{
return d->shapeController->shapeForNode(layer);
}
vKisNodeSP KisDocument::activeNodes() const
{
vKisNodeSP nodes;
Q_FOREACH (KisView *v, KisPart::instance()->views()) {
if (v->document() == this && v->viewManager()) {
KisNodeSP activeNode = v->viewManager()->activeNode();
if (activeNode && !nodes.contains(activeNode)) {
if (activeNode->inherits("KisMask")) {
activeNode = activeNode->parent();
}
nodes.append(activeNode);
}
}
}
return nodes;
}
QList KisDocument::assistants() const
{
return d->assistants;
}
void KisDocument::setAssistants(const QList value)
{
d->assistants = value;
}
void KisDocument::setPreActivatedNode(KisNodeSP activatedNode)
{
d->preActivatedNode = activatedNode;
}
KisNodeSP KisDocument::preActivatedNode() const
{
return d->preActivatedNode;
}
void KisDocument::setFileProgressUpdater(const QString &text)
{
d->suppressProgress = d->importExportManager->batchMode();
if (!d->suppressProgress) {
d->progressUpdater = new KoProgressUpdater(d->progressProxy, KoProgressUpdater::Unthreaded);
d->progressUpdater->start(100, text);
d->importExportManager->setProgresUpdater(d->progressUpdater);
if (KisPart::instance()->currentMainwindow()) {
connect(this, SIGNAL(sigProgress(int)), KisPart::instance()->currentMainwindow(), SLOT(slotProgress(int)));
connect(KisPart::instance()->currentMainwindow(), SIGNAL(sigProgressCanceled()), this, SIGNAL(sigProgressCanceled()));
}
}
}
void KisDocument::clearFileProgressUpdater()
{
if (!d->suppressProgress && d->progressUpdater) {
if (KisPart::instance()->currentMainwindow()) {
disconnect(KisPart::instance()->currentMainwindow(), SIGNAL(sigProgressCanceled()), this, SIGNAL(sigProgressCanceled()));
disconnect(this, SIGNAL(sigProgress(int)), KisPart::instance()->currentMainwindow(), SLOT(slotProgress(int)));
}
delete d->progressUpdater;
d->importExportManager->setProgresUpdater(0);
d->progressUpdater = 0;
}
}
void KisDocument::setFileProgressProxy()
{
if (!d->progressProxy && !d->importExportManager->batchMode()) {
d->fileProgressProxy = progressProxy();
} else {
d->fileProgressProxy = 0;
}
}
void KisDocument::clearFileProgressProxy()
{
if (d->fileProgressProxy) {
setProgressProxy(0);
delete d->fileProgressProxy;
d->fileProgressProxy = 0;
}
}
KisImageWSP KisDocument::image() const
{
return d->image;
}
KisImageSP KisDocument::savingImage() const
{
return d->savingImage;
}
void KisDocument::setCurrentImage(KisImageSP image)
{
if (d->image) {
// Disconnect existing sig/slot connections
d->image->disconnect(this);
d->shapeController->setImage(0);
d->image = 0;
}
if (!image) return;
d->setImageAndInitIdleWatcher(image);
d->shapeController->setImage(image);
setModified(false);
connect(d->image, SIGNAL(sigImageModified()), this, SLOT(setImageModified()), Qt::UniqueConnection);
d->image->initialRefreshGraph();
setAutoSaveDelay(KisConfig().autoSaveInterval());
}
void KisDocument::setImageModified()
{
setModified(true);
}
KisUndoStore* KisDocument::createUndoStore()
{
return new KisDocumentUndoStore(this);
}
bool KisDocument::isAutosaving() const
{
return d->isAutosaving;
}
bool KisDocument::prepareLocksForSaving()
{
KisImageSP copiedImage;
// XXX: Restore this when
// a) cloning works correctly and
// b) doesn't take ages because it needs to refresh its entire graph and finally,
// c) we do use the saving image to save in the background.
{
Private::SafeSavingLocker locker(d, this);
if (locker.successfullyLocked()) {
copiedImage = d->image; //->clone(true);
}
else if (!isAutosaving()) {
// even though it is a recovery operation, we should ensure we do not enter saving twice!
std::unique_lock> l(d->savingLock, std::try_to_lock);
if (l.owns_lock()) {
d->lastErrorMessage = i18n("The image was still busy while saving. Your saved image might be incomplete.");
d->image->lock();
copiedImage = d->image; //->clone(true);
//copiedImage->initialRefreshGraph();
d->image->unlock();
}
}
}
bool result = false;
// ensure we do not enter saving twice
if (copiedImage && d->savingMutex.tryLock()) {
d->savingImage = copiedImage;
result = true;
} else {
qWarning() << "Could not lock the document for saving!";
d->lastErrorMessage = i18n("Could not lock the image for saving.");
}
return result;
}
void KisDocument::unlockAfterSaving()
{
d->savingImage = 0;
d->savingMutex.unlock();
}
diff --git a/libs/ui/KisDocument.h b/libs/ui/KisDocument.h
index 2caa4a9c0f..0943e25ae3 100644
--- a/libs/ui/KisDocument.h
+++ b/libs/ui/KisDocument.h
@@ -1,612 +1,626 @@
/* This file is part of the Krita project
*
* Copyright (C) 2014 Boudewijn Rempt
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public License
* along with this library; see the file COPYING.LIB. If not, write to
* the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#ifndef KISDOCUMENT_H
#define KISDOCUMENT_H
#include
#include
#include
#include
#include
#include
#include
+#include
#include
#include
#include
#include
#include "kritaui_export.h"
class QString;
class KUndo2Command;
class KoUnit;
class KoColor;
class KoColorSpace;
class KoShapeBasedDocumentBase;
class KoShapeLayer;
class KoStore;
class KoOdfReadStore;
class KoDocumentInfo;
class KoProgressUpdater;
class KoProgressProxy;
class KoDocumentInfoDlg;
class KisImportExportManager;
class KisUndoStore;
class KisPaintingAssistant;
class KisPart;
class KisGridConfig;
class KisGuidesConfig;
class QDomDocument;
class KisPart;
#define KIS_MIME_TYPE "application/x-krita"
/**
* The %Calligra document class
*
* This class provides some functionality each %Calligra document should have.
*
* @short The %Calligra document class
*/
class KRITAUI_EXPORT KisDocument : public QObject, public KoDocumentBase
{
Q_OBJECT
protected:
explicit KisDocument();
/**
* @brief KisDocument makes a deep copy of the document \p rhs.
* The caller *must* ensure that the image is properly
* locked and is in consistent state before asking for
* cloning.
* @param rhs the source document to copy from
*/
explicit KisDocument(const KisDocument &rhs);
public:
-
- enum OpenUrlFlags {
- OPEN_URL_FLAG_NONE = 1 << 0,
- OPEN_URL_FLAG_DO_NOT_ADD_TO_RECENT_FILES = 1 << 1,
+ enum OpenFlag {
+ None = 0,
+ DontAddToRecent = 0x1,
+ RecoveryFile = 0x2
};
+ Q_DECLARE_FLAGS(OpenFlags, OpenFlag)
/**
* Destructor.
*
* The destructor does not delete any attached KisView objects and it does not
* delete the attached widget as returned by widget().
*/
virtual ~KisDocument();
/**
* @brief reload Reloads the document from the original url
* @return the result of loading the document
*/
bool reload();
/**
* @brief openUrl Open an URL
* @param url The URL to open
* @param flags Control specific behavior
* @return success status
*/
- bool openUrl(const QUrl &url, OpenUrlFlags flags = OPEN_URL_FLAG_NONE);
+ bool openUrl(const QUrl &url, OpenFlags flags = None);
/**
* Opens the document given by @p url, without storing the URL
* in the KisDocument.
* Call this instead of openUrl() to implement KisMainWindow's
* File --> Import feature.
*
* @note This will call openUrl(). To differentiate this from an ordinary
* Open operation (in any reimplementation of openUrl() or openFile())
* call isImporting().
*/
bool importDocument(const QUrl &url);
/**
* Saves the document as @p url without changing the state of the
* KisDocument (URL, modified flag etc.). Call this instead of
* KisParts::ReadWritePart::saveAs() to implement KisMainWindow's
* File --> Export feature.
*/
bool exportDocument(const QUrl &url, KisPropertiesConfigurationSP exportConfiguration = 0);
/**
* @brief Sets whether the document can be edited or is read only.
*
* This recursively applied to all child documents and
* KisView::updateReadWrite is called for every attached
* view.
*/
void setReadWrite(bool readwrite = true);
/**
* To be preferred when a document exists. It is fast when calling
* it multiple times since it caches the result that readNativeFormatMimeType()
* delivers.
* This comes from the X-KDE-NativeMimeType key in the .desktop file.
*/
static QByteArray nativeFormatMimeType() { return KIS_MIME_TYPE; }
/// Checks whether a given mimetype can be handled natively.
bool isNativeFormat(const QByteArray& mimetype) const;
/// Returns a list of the mimetypes considered "native", i.e. which can
/// be saved by KisDocument without a filter, in *addition* to the main one
static QStringList extraNativeMimeTypes() { return QStringList() << KIS_MIME_TYPE; }
/**
* Returns the actual mimetype of the document
*/
QByteArray mimeType() const;
/**
* @brief Sets the mime type for the document.
*
* When choosing "save as" this is also the mime type
* selected by default.
*/
void setMimeType(const QByteArray & mimeType);
/**
* @brief Set the format in which the document should be saved.
*
* This is called on loading, and in "save as", so you shouldn't
* have to call it.
*
* @param mimeType the mime type (format) to use.
*/
void setOutputMimeType(const QByteArray & mimeType);
QByteArray outputMimeType() const;
/**
* @return true if file operations should inhibit the option dialog
*/
bool fileBatchMode() const;
/**
* @param batchMode if true, do not show the option dialog for file operations.
*/
void setFileBatchMode(const bool batchMode);
/**
* Sets the error message to be shown to the user (use i18n()!)
* when loading or saving fails.
* If you asked the user about something and they chose "Cancel",
*/
void setErrorMessage(const QString& errMsg);
/**
* Return the last error message. Usually KisDocument takes care of
* showing it; this method is mostly provided for non-interactive use.
*/
QString errorMessage() const;
/**
* Sets the warning message to be shown to the user (use i18n()!)
* when loading or saving fails.
*/
void setWarningMessage(const QString& warningMsg);
/**
* Return the last warning message set by loading or saving. Warnings
* mean that the document could not be completely loaded, but the errors
* were not absolutely fatal.
*/
QString warningMessage() const;
/**
* @brief Generates a preview picture of the document
* @note The preview is used in the File Dialog and also to create the Thumbnail
*/
QPixmap generatePreview(const QSize& size);
/**
* Tells the document that its title has been modified, either because
* the modified status changes (this is done by setModified() ) or
* because the URL or the document-info's title changed.
*/
void setTitleModified();
/**
* @brief Sets the document to empty.
*
* Used after loading a template
* (which is not empty, but not the user's input).
*
* @see isEmpty()
*/
void setEmpty(bool empty = true);
/**
* Return a correctly created QDomDocument for this KisDocument,
* including processing instruction, complete DOCTYPE tag (with systemId and publicId), and root element.
* @param tagName the name of the tag for the root element
* @param version the DTD version (usually the application's version).
*/
QDomDocument createDomDocument(const QString& tagName, const QString& version) const;
/**
* Return a correctly created QDomDocument for an old (1.3-style) %Calligra document,
* including processing instruction, complete DOCTYPE tag (with systemId and publicId), and root element.
* This static method can be used e.g. by filters.
* @param appName the app's instance name, e.g. words, kspread, kpresenter etc.
* @param tagName the name of the tag for the root element, e.g. DOC for words/kpresenter.
* @param version the DTD version (usually the application's version).
*/
static QDomDocument createDomDocument(const QString& appName, const QString& tagName, const QString& version);
/**
* Loads a document in the native format from a given URL.
* Reimplement if your native format isn't XML.
*
* @param file the file to load - usually KReadOnlyPart::m_file or the result of a filter
*/
bool loadNativeFormat(const QString & file);
/**
* Activate/deactivate/configure the autosave feature.
* @param delay in seconds, 0 to disable
*/
void setAutoSaveDelay(int delay);
/**
* @return the information concerning this document.
* @see KoDocumentInfo
*/
KoDocumentInfo *documentInfo() const;
/**
* @return the object to report progress to.
*
* This is only not zero if loading or saving is in progress.
*
* One can add more KoUpdaters to it to make the progress reporting more
* accurate. If no active progress reporter is present, 0 is returned.
**/
KoProgressUpdater *progressUpdater() const;
/**
* Set a custom progress proxy to use to report loading
* progress to.
*/
void setProgressProxy(KoProgressProxy *progressProxy);
KoProgressProxy* progressProxy() const;
/**
* Performs a cleanup of unneeded backup files
*/
void removeAutoSaveFiles();
/**
* Returns true if this document or any of its internal child documents are modified.
*/
bool isModified() const;
/**
* @return caption of the document
*
* Caption is of the form "[title] - [url]",
* built out of the document info (title) and pretty-printed
* document URL.
* If the title is not present, only the URL it returned.
*/
QString caption() const;
/**
* Sets the document URL to empty URL
* KParts doesn't allow this, but %Calligra apps have e.g. templates
* After using loadNativeFormat on a template, one wants
* to set the url to QUrl()
*/
void resetURL();
/**
* @internal (public for KisMainWindow)
*/
void setMimeTypeAfterLoading(const QString& mimeType);
/**
* Returns the unit used to display all measures/distances.
*/
KoUnit unit() const;
/**
* Sets the unit used to display all measures/distances.
*/
void setUnit(const KoUnit &unit);
KisGridConfig gridConfig() const;
void setGridConfig(const KisGridConfig &config);
/// returns the guides data for this document.
const KisGuidesConfig& guidesConfig() const;
void setGuidesConfig(const KisGuidesConfig &data);
void clearUndoHistory();
/**
* Sets the modified flag on the document. This means that it has
* to be saved or not before deleting it.
*/
void setModified(bool _mod);
+ void setRecovered(bool value);
+ bool isRecovered() const;
+
void updateEditingTime(bool forceStoreElapsed);
/**
* Returns the global undo stack
*/
KUndo2Stack *undoStack();
/**
* @brief importExportManager gives access to the internal import/export manager
* @return the document's import/export manager
*/
KisImportExportManager *importExportManager() const;
/**
* @brief serializeToNativeByteArray daves the document into a .kra file wtitten
* to a memory-based byte-array
* @return a byte array containing the .kra file
*/
QByteArray serializeToNativeByteArray();
public Q_SLOTS:
/**
* Adds a command to the undo stack and executes it by calling the redo() function.
* @param command command to add to the undo stack
*/
void addCommand(KUndo2Command *command);
/**
* Begins recording of a macro command. At the end endMacro needs to be called.
* @param text command description
*/
void beginMacro(const KUndo2MagicString &text);
/**
* Ends the recording of a macro command.
*/
void endMacro();
Q_SIGNALS:
/**
* This signal is emitted when the unit is changed by setUnit().
* It is common to connect views to it, in order to change the displayed units
* (e.g. in the rulers)
*/
void unitChanged(const KoUnit &unit);
/**
* Progress info while loading or saving. The value is in percents (i.e. a number between 0 and 100)
* Your KisDocument-derived class should emit the signal now and then during load/save.
* KisMainWindow will take care of displaying a progress bar automatically.
*/
void sigProgress(int value);
/**
* Progress cancel button pressed
* This is emitted by KisDocument
*/
void sigProgressCanceled();
/**
* Emitted e.g. at the beginning of a save operation
* This is emitted by KisDocument and used by KisView to display a statusbar message
*/
void statusBarMessage(const QString& text);
/**
* Emitted e.g. at the end of a save operation
* This is emitted by KisDocument and used by KisView to clear the statusbar message
*/
void clearStatusBarMessage();
/**
* Emitted when the document is modified
*/
void modified(bool);
void titleModified(const QString &caption, bool isModified);
void sigLoadingFinished();
void sigSavingFinished();
void sigGuidesConfigChanged(const KisGuidesConfig &config);
+ void sigBackgroundSavingFinished(KisImportExportFilter::ConversionStatus status);
+
+private Q_SLOTS:
+ void finishExportInBackground();
+ void slotChildCompletedSavingInBackground(KisImportExportFilter::ConversionStatus status);
private:
friend class KisPart;
friend class SafeSavingLocker;
+ bool startExportInBackground(const QUrl &url,
+ const QByteArray &mimeType);
+
/**
* Generate a name for the document.
*/
QString newObjectName();
QString generateAutoSaveFileName(const QString & path) const;
/**
* Loads a document
*
* Applies a filter if necessary, and calls loadNativeFormat in any case
* You should not have to reimplement, except for very special cases.
*
* NOTE: this method also creates a new KisView instance!
*
* This method is called from the KReadOnlyPart::openUrl method.
*/
bool openFile();
/**
* Saves a document
*
* Applies a filter if necessary, and calls exportDocument in any case
* You should not have to reimplement, except for very special cases.
*/
bool saveFile(const QString &filePath, bool showWarnings, KisPropertiesConfigurationSP exportConfiguration = 0);
/** @internal */
void setModified();
public:
bool isAutosaving() const;
public:
QString localFilePath() const;
void setLocalFilePath( const QString &localFilePath );
KoDocumentInfoDlg* createDocumentInfoDialog(QWidget *parent, KoDocumentInfo *docInfo) const;
bool isReadWrite() const;
QUrl url() const;
void setUrl(const QUrl &url);
bool closeUrl(bool promptToSave = true);
bool saveAs(const QUrl &url, bool showWarnings, KisPropertiesConfigurationSP exportConfigration = 0);
/**
* Create a new image that has this document as a parent and
* replace the current image with this image.
*/
bool newImage(const QString& name, qint32 width, qint32 height, const KoColorSpace * cs, const KoColor &bgColor, bool backgroundAsLayer,
int numberOfLayers, const QString &imageDescription, const double imageResolution);
KisImageWSP image() const;
/**
* @brief savingImage provides a detached, shallow copy of the original image that must be used when saving.
* Any strokes in progress will not be applied to this image, so the result might be missing some data. On
* the other hand, it won't block.
*
* @return a shallow copy of the original image, or 0 is saving is not in progress
*/
KisImageSP savingImage() const;
/**
* Adds progressproxy for file operations
*/
void setFileProgressProxy();
/**
* Clears progressproxy for file operations
*/
void clearFileProgressProxy();
/**
* Adds progressupdater for file operations
*/
void setFileProgressUpdater(const QString &text);
/**
* Clears progressupdater for file operations
*/
void clearFileProgressUpdater();
/**
* Set the current image to the specified image and turn undo on.
*/
void setCurrentImage(KisImageSP image);
KisUndoStore* createUndoStore();
/**
* The shape controller matches internal krita image layers with
* the flake shape hierarchy.
*/
KoShapeBasedDocumentBase * shapeController() const;
KoShapeLayer* shapeForNode(KisNodeSP layer) const;
/**
* @return a list of all layers that are active in all current views
*/
vKisNodeSP activeNodes() const;
/**
* set the list of nodes that were marked as currently active
*/
void setPreActivatedNode(KisNodeSP activatedNode);
/**
* @return the node that was set as active during loading
*/
KisNodeSP preActivatedNode() const;
QList assistants() const;
void setAssistants(const QList value);
bool save(bool showWarnings, KisPropertiesConfigurationSP exportConfiguration);
Q_SIGNALS:
void completed();
void canceled(const QString &);
private Q_SLOTS:
void setImageModified();
void slotAutoSave();
/// Called by the undo stack when undo or redo is called
void slotUndoStackIndexChanged(int idx);
void slotConfigChanged();
private:
bool prepareLocksForSaving();
void unlockAfterSaving();
QString prettyPathOrUrl() const;
bool openUrlInternal(const QUrl &url);
class Private;
Private *const d;
};
+Q_DECLARE_OPERATORS_FOR_FLAGS(KisDocument::OpenFlags)
Q_DECLARE_METATYPE(KisDocument*)
#endif
diff --git a/libs/ui/KisImportExportManager.cpp b/libs/ui/KisImportExportManager.cpp
index cee4193915..c18919cc89 100644
--- a/libs/ui/KisImportExportManager.cpp
+++ b/libs/ui/KisImportExportManager.cpp
@@ -1,527 +1,584 @@
/*
* Copyright (C) 2016 Boudewijn Rempt
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public License
* along with this library; see the file COPYING.LIB. If not, write to
* the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#include "KisImportExportManager.h"
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
+#include
+#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include "kis_config.h"
#include "KisImportExportFilter.h"
#include "KisDocument.h"
#include
#include
#include "kis_painter.h"
#include "kis_guides_config.h"
#include "kis_grid_config.h"
#include "kis_popup_button.h"
#include
#include "kis_async_action_feedback.h"
// static cache for import and export mimetypes
QStringList KisImportExportManager::m_importMimeTypes;
QStringList KisImportExportManager::m_exportMimeTypes;
class Q_DECL_HIDDEN KisImportExportManager::Private
{
public:
bool batchMode {false};
QPointer progressUpdater {0};
};
+struct KisImportExportManager::ConversionResult {
+ ConversionResult()
+ {
+ }
+
+ ConversionResult(const QFuture &futureStatus)
+ : m_isAsync(true),
+ m_futureStatus(futureStatus)
+ {
+ }
+
+ ConversionResult(KisImportExportFilter::ConversionStatus status)
+ : m_isAsync(false),
+ m_status(status)
+ {
+ }
+
+ bool isAsync() const {
+ return m_isAsync;
+ }
+
+ QFuture futureStatus() const {
+ return m_futureStatus;
+ }
+
+ KisImportExportFilter::ConversionStatus status() const {
+ return m_status;
+ }
+
+private:
+ bool m_isAsync = false;
+ QFuture m_futureStatus;
+ KisImportExportFilter::ConversionStatus m_status = KisImportExportFilter::UsageError;
+};
+
+
KisImportExportManager::KisImportExportManager(KisDocument* document)
: m_document(document)
, d(new Private)
{
}
KisImportExportManager::~KisImportExportManager()
{
delete d;
}
KisImportExportFilter::ConversionStatus KisImportExportManager::importDocument(const QString& location, const QString& mimeType)
{
- return convert(Import, location, location, mimeType, false, 0);
+ ConversionResult result = convert(Import, location, location, mimeType, false, 0, false);
+ KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(!result.isAsync(), KisImportExportFilter::UsageError);
+
+ return result.status();
}
-KisImportExportFilter::ConversionStatus KisImportExportManager::exportDocument(const QString& location, const QString& realLocation, QByteArray& mimeType, bool showWarnings, KisPropertiesConfigurationSP exportConfiguration)
+KisImportExportFilter::ConversionStatus KisImportExportManager::exportDocument(const QString& location, const QString& realLocation, const QByteArray& mimeType, bool showWarnings, KisPropertiesConfigurationSP exportConfiguration)
{
- return convert(Export, location, realLocation, mimeType, showWarnings, exportConfiguration);
+ ConversionResult result = convert(Export, location, realLocation, mimeType, showWarnings, exportConfiguration, false);
+ KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(!result.isAsync(), KisImportExportFilter::UsageError);
+
+ return result.status();
+}
+
+QFuture KisImportExportManager::exportDocumentAsyc(const QString &location, const QString &realLocation, const QByteArray &mimeType, bool showWarnings, KisPropertiesConfigurationSP exportConfiguration)
+{
+ ConversionResult result = convert(Export, location, realLocation, mimeType, showWarnings, exportConfiguration, true);
+ KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(result.isAsync(), QFuture());
+
+ return result.futureStatus();
}
// The static method to figure out to which parts of the
// graph this mimetype has a connection to.
QStringList KisImportExportManager::mimeFilter(Direction direction)
{
// Find the right mimetype by the extension
QSet mimeTypes;
// mimeTypes << KisDocument::nativeFormatMimeType() << "application/x-krita-paintoppreset" << "image/openraster";
if (direction == KisImportExportManager::Import) {
if (m_importMimeTypes.isEmpty()) {
KoJsonTrader trader;
QListlist = trader.query("Krita/FileFilter", "");
Q_FOREACH(QPluginLoader *loader, list) {
QJsonObject json = loader->metaData().value("MetaData").toObject();
Q_FOREACH(const QString &mimetype, json.value("X-KDE-Import").toString().split(",", QString::SkipEmptyParts)) {
//qDebug() << "Adding import mimetype" << mimetype << KisMimeDatabase::descriptionForMimeType(mimetype) << "from plugin" << loader;
mimeTypes << mimetype;
}
}
qDeleteAll(list);
m_importMimeTypes = mimeTypes.toList();
}
return m_importMimeTypes;
}
else if (direction == KisImportExportManager::Export) {
if (m_exportMimeTypes.isEmpty()) {
KoJsonTrader trader;
QListlist = trader.query("Krita/FileFilter", "");
Q_FOREACH(QPluginLoader *loader, list) {
QJsonObject json = loader->metaData().value("MetaData").toObject();
Q_FOREACH(const QString &mimetype, json.value("X-KDE-Export").toString().split(",", QString::SkipEmptyParts)) {
//qDebug() << "Adding export mimetype" << mimetype << KisMimeDatabase::descriptionForMimeType(mimetype) << "from plugin" << loader;
mimeTypes << mimetype;
}
}
qDeleteAll(list);
m_exportMimeTypes = mimeTypes.toList();
}
return m_exportMimeTypes;
}
return QStringList();
}
KisImportExportFilter *KisImportExportManager::filterForMimeType(const QString &mimetype, KisImportExportManager::Direction direction)
{
int weight = -1;
KisImportExportFilter *filter = 0;
QListlist = KoJsonTrader::instance()->query("Krita/FileFilter", "");
Q_FOREACH(QPluginLoader *loader, list) {
QJsonObject json = loader->metaData().value("MetaData").toObject();
QString directionKey = direction == Export ? "X-KDE-Export" : "X-KDE-Import";
if (json.value(directionKey).toString().split(",", QString::SkipEmptyParts).contains(mimetype)) {
KLibFactory *factory = qobject_cast(loader->instance());
if (!factory) {
warnUI << loader->errorString();
continue;
}
QObject* obj = factory->create(0);
if (!obj || !obj->inherits("KisImportExportFilter")) {
delete obj;
continue;
}
KisImportExportFilter *f = qobject_cast(obj);
if (!f) {
delete obj;
continue;
}
int w = json.value("X-KDE-Weight").toInt();
if (w > weight) {
delete filter;
filter = f;
f->setObjectName(loader->fileName());
weight = w;
}
}
}
qDeleteAll(list);
if (filter) {
filter->setMimeType(mimetype);
}
return filter;
}
void KisImportExportManager::setBatchMode(const bool batch)
{
d->batchMode = batch;
}
bool KisImportExportManager::batchMode(void) const
{
return d->batchMode;
}
void KisImportExportManager::setProgresUpdater(KoProgressUpdater *updater)
{
d->progressUpdater = updater;
}
QString KisImportExportManager::askForAudioFileName(const QString &defaultDir, QWidget *parent)
{
KoFileDialog dialog(parent, KoFileDialog::ImportFiles, "ImportAudio");
if (!defaultDir.isEmpty()) {
dialog.setDefaultDir(defaultDir);
}
QStringList mimeTypes;
mimeTypes << "audio/mpeg";
mimeTypes << "audio/ogg";
mimeTypes << "audio/vorbis";
mimeTypes << "audio/vnd.wave";
mimeTypes << "audio/flac";
dialog.setMimeTypeFilters(mimeTypes);
dialog.setCaption(i18nc("@titile:window", "Open Audio"));
return dialog.filename();
}
-KisImportExportFilter::ConversionStatus KisImportExportManager::convert(KisImportExportManager::Direction direction, const QString &location, const QString& realLocation, const QString &mimeType, bool showWarnings, KisPropertiesConfigurationSP exportConfiguration)
+KisImportExportManager::ConversionResult KisImportExportManager::convert(KisImportExportManager::Direction direction, const QString &location, const QString& realLocation, const QString &mimeType, bool showWarnings, KisPropertiesConfigurationSP exportConfiguration, bool isAsync)
{
// export configuration is supported for export only
KIS_SAFE_ASSERT_RECOVER_NOOP(direction == Export || !bool(exportConfiguration));
QString typeName = mimeType;
if (typeName.isEmpty()) {
typeName = KisMimeDatabase::mimeTypeForFile(location);
}
QSharedPointer filter(filterForMimeType(typeName, direction));
if (!filter) {
return KisImportExportFilter::FilterCreationError;
}
filter->setFilename(location);
filter->setRealFilename(realLocation);
filter->setBatchMode(batchMode());
filter->setMimeType(typeName);
if (d->progressUpdater) {
filter->setUpdater(d->progressUpdater->startSubtask());
}
QByteArray from, to;
if (direction == Export) {
from = m_document->nativeFormatMimeType();
to = typeName.toLatin1();
}
else {
from = typeName.toLatin1();
to = m_document->nativeFormatMimeType();
}
KIS_ASSERT_RECOVER_RETURN_VALUE(
direction == Import || direction == Export,
KisImportExportFilter::BadConversionGraph);
- KisImportExportFilter::ConversionStatus status = KisImportExportFilter::OK;
+ ConversionResult result = KisImportExportFilter::OK;
if (direction == Import) {
+ // async importing is not yet supported!
+ KIS_SAFE_ASSERT_RECOVER_NOOP(!isAsync);
+
if (!batchMode()) {
KisAsyncActionFeedback f(i18n("Opening document..."), 0);
- status = f.runAction(std::bind(&KisImportExportManager::doImport, this, location, filter));
+ result = f.runAction(std::bind(&KisImportExportManager::doImport, this, location, filter));
} else {
- status = doImport(location, filter);
+ result = doImport(location, filter);
}
} else /* if (direction == Export) */ {
if (!exportConfiguration) {
exportConfiguration = filter->lastSavedConfiguration(from, to);
}
if (exportConfiguration) {
fillStaticExportConfigurationProperties(exportConfiguration);
}
bool alsoAsKra = false;
if (!askUserAboutExportConfiguration(filter, exportConfiguration,
from, to,
batchMode(), showWarnings,
&alsoAsKra)) {
return KisImportExportFilter::UserCancelled;
}
- if (!batchMode()) {
+ if (isAsync) {
+ result = QtConcurrent::run(std::bind(&KisImportExportManager::doExport, this, location, filter, exportConfiguration, alsoAsKra));
+ } else if (!batchMode()) {
KisAsyncActionFeedback f(i18n("Saving document..."), 0);
- status = f.runAction(std::bind(&KisImportExportManager::doExport, this, location, filter, exportConfiguration, alsoAsKra));
+ result = f.runAction(std::bind(&KisImportExportManager::doExport, this, location, filter, exportConfiguration, alsoAsKra));
} else {
- status = doExport(location, filter, exportConfiguration, alsoAsKra);
+ result = doExport(location, filter, exportConfiguration, alsoAsKra);
}
if (exportConfiguration) {
KisConfig().setExportConfiguration(typeName, exportConfiguration);
}
}
- return status;
+ return result;
}
void KisImportExportManager::fillStaticExportConfigurationProperties(KisPropertiesConfigurationSP exportConfiguration)
{
// Fill with some meta information about the image
KisImageSP image = m_document->image();
KisPaintDeviceSP dev = image->projection();
const KoColorSpace* cs = dev->colorSpace();
const bool isThereAlpha =
KisPainter::checkDeviceHasTransparency(image->projection());
exportConfiguration->setProperty(KisImportExportFilter::ImageContainsTransparencyTag, isThereAlpha);
exportConfiguration->setProperty(KisImportExportFilter::ColorModelIDTag, cs->colorModelId().id());
exportConfiguration->setProperty(KisImportExportFilter::ColorDepthIDTag, cs->colorDepthId().id());
const bool sRGB =
(cs->profile()->name().contains(QLatin1String("srgb"), Qt::CaseInsensitive) &&
!cs->profile()->name().contains(QLatin1String("g10")));
exportConfiguration->setProperty(KisImportExportFilter::sRGBTag, sRGB);
}
bool
KisImportExportManager::askUserAboutExportConfiguration(
QSharedPointer filter,
KisPropertiesConfigurationSP exportConfiguration,
const QByteArray &from,
const QByteArray &to,
const bool batchMode, const bool showWarnings,
bool *alsoAsKra)
{
const QString mimeUserDescription = KisMimeDatabase::descriptionForMimeType(to);
QStringList warnings;
QStringList errors;
{
KisPreExportChecker checker;
checker.check(m_document->image(), filter->exportChecks());
warnings = checker.warnings();
errors = checker.errors();
}
KisConfigWidget *wdg = filter->createConfigurationWidget(0, from, to);
// Extra checks that cannot be done by the checker, because the checker only has access to the image.
if (!m_document->assistants().isEmpty() && to != m_document->nativeFormatMimeType()) {
warnings.append(i18nc("image conversion warning", "The image contains assistants. The assistants will not be saved."));
}
if (m_document->guidesConfig().hasGuides() && to != m_document->nativeFormatMimeType()) {
warnings.append(i18nc("image conversion warning", "The image contains guides. The guides will not be saved."));
}
if (!m_document->gridConfig().isDefault() && to != m_document->nativeFormatMimeType()) {
warnings.append(i18nc("image conversion warning", "The image contains a custom grid configuration. The configuration will not be saved."));
}
if (!batchMode && !errors.isEmpty()) {
QString error = ""
+ i18n("Error: cannot save this image as a %1.", mimeUserDescription)
+ " Reasons:
"
+ "";
Q_FOREACH(const QString &w, errors) {
error += "\n- " + w + "
";
}
error += "
";
QMessageBox::critical(KisPart::instance()->currentMainwindow(), i18nc("@title:window", "Krita: Export Error"), error);
return false;
}
if (!batchMode && (wdg || !warnings.isEmpty())) {
KoDialog dlg;
dlg.setButtons(KoDialog::Ok | KoDialog::Cancel);
dlg.setWindowTitle(mimeUserDescription);
QWidget *page = new QWidget(&dlg);
QVBoxLayout *layout = new QVBoxLayout(page);
if (showWarnings && !warnings.isEmpty()) {
QHBoxLayout *hLayout = new QHBoxLayout();
QLabel *labelWarning = new QLabel();
labelWarning->setPixmap(KisIconUtils::loadIcon("dialog-warning").pixmap(32, 32));
hLayout->addWidget(labelWarning);
KisPopupButton *bn = new KisPopupButton(0);
bn->setText(i18nc("Keep the extra space at the end of the sentence, please", "Warning: saving as %1 will lose information from your image. ", mimeUserDescription));
hLayout->addWidget(bn);
layout->addLayout(hLayout);
QTextBrowser *browser = new QTextBrowser();
browser->setMinimumWidth(bn->width());
bn->setPopupWidget(browser);
QString warning = ""
+ i18n("You will lose information when saving this image as a %1.", mimeUserDescription);
if (warnings.size() == 1) {
warning += " Reason:
";
}
else {
warning += " Reasons:";
}
warning += "";
Q_FOREACH(const QString &w, warnings) {
warning += "\n- " + w + "
";
}
warning += "
";
browser->setHtml(warning);
}
if (wdg) {
QGroupBox *box = new QGroupBox(i18n("Options"));
QVBoxLayout *boxLayout = new QVBoxLayout(box);
wdg->setConfiguration(exportConfiguration);
boxLayout->addWidget(wdg);
layout->addWidget(box);
}
QCheckBox *chkAlsoAsKra = 0;
if (showWarnings && !warnings.isEmpty()) {
chkAlsoAsKra = new QCheckBox(i18n("Also save your image as a Krita file."));
chkAlsoAsKra->setChecked(KisConfig().readEntry("AlsoSaveAsKra", false));
layout->addWidget(chkAlsoAsKra);
}
dlg.setMainWidget(page);
dlg.resize(dlg.minimumSize());
if (showWarnings || wdg) {
if (!dlg.exec()) {
return false;
}
}
*alsoAsKra = false;
if (chkAlsoAsKra) {
KisConfig().writeEntry("AlsoSaveAsKra", chkAlsoAsKra->isChecked());
*alsoAsKra = chkAlsoAsKra->isChecked();
}
if (wdg) {
*exportConfiguration = *wdg->configuration();
}
}
return true;
}
KisImportExportFilter::ConversionStatus KisImportExportManager::doImport(const QString &location, QSharedPointer filter)
{
QFile file(location);
if (!file.exists()) {
return KisImportExportFilter::FileNotFound;
}
if (filter->supportsIO() && !file.open(QFile::ReadOnly)) {
return KisImportExportFilter::FileNotFound;
}
KisImportExportFilter::ConversionStatus status =
filter->convert(m_document, &file, KisPropertiesConfigurationSP());
if (file.isOpen()) {
file.close();
}
return status;
}
KisImportExportFilter::ConversionStatus KisImportExportManager::doExport(const QString &location, QSharedPointer filter, KisPropertiesConfigurationSP exportConfiguration, bool alsoAsKra)
{
KisImportExportFilter::ConversionStatus status =
doExportImpl(location, filter, exportConfiguration);
if (alsoAsKra && status == KisImportExportFilter::OK) {
QString kraLocation = location + ".kra";
QByteArray mime = m_document->nativeFormatMimeType();
QSharedPointer filter(
filterForMimeType(QString::fromLatin1(mime), Export));
KIS_SAFE_ASSERT_RECOVER_NOOP(filter);
if (filter) {
filter->setFilename(kraLocation);
KisPropertiesConfigurationSP kraExportConfiguration =
filter->lastSavedConfiguration(mime, mime);
status = doExportImpl(kraLocation, filter, kraExportConfiguration);
} else {
status = KisImportExportFilter::FilterCreationError;
}
}
return status;
}
KisImportExportFilter::ConversionStatus KisImportExportManager::doExportImpl(const QString &location, QSharedPointer filter, KisPropertiesConfigurationSP exportConfiguration)
{
QSaveFile file(location);
file.setDirectWriteFallback(true);
if (filter->supportsIO() && !file.open(QFile::WriteOnly)) {
file.cancelWriting();
return KisImportExportFilter::CreationError;
}
KisImportExportFilter::ConversionStatus status =
filter->convert(m_document, &file, exportConfiguration);
if (status != KisImportExportFilter::OK) {
file.cancelWriting();
} else {
file.commit();
}
return status;
}
#include
diff --git a/libs/ui/KisImportExportManager.h b/libs/ui/KisImportExportManager.h
index 7cdceea544..0429b89404 100644
--- a/libs/ui/KisImportExportManager.h
+++ b/libs/ui/KisImportExportManager.h
@@ -1,155 +1,161 @@
/*
* Copyright (C) 2016 Boudewijn Rempt
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public License
* along with this library; see the file COPYING.LIB. If not, write to
* the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#ifndef KIS_IMPORT_EXPORT_MANAGER_H
#define KIS_IMPORT_EXPORT_MANAGER_H
#include
#include
#include
#include
#include "KisImportExportFilter.h"
#include "kritaui_export.h"
class KisDocument;
class KoProgressUpdater;
+template
+class QFuture;
+
/**
* @brief The class managing all the filters.
*
* This class manages all filters for a %Calligra application. Normally
* you will not have to use it, since KisMainWindow takes care of loading
* and saving documents.
*
* @ref KisFilter
*
* @author Kalle Dalheimer
* @author Torben Weis
* @author Werner Trobin
*/
class KRITAUI_EXPORT KisImportExportManager : public QObject
{
Q_OBJECT
public:
/**
* This enum is used to distinguish the import/export cases
*/
enum Direction { Import = 1, Export = 2 };
/**
* Create a filter manager for a document
*/
explicit KisImportExportManager(KisDocument *document);
public:
virtual ~KisImportExportManager();
/**
* Imports the specified document and returns the resultant filename
* (most likely some file in /tmp).
* @p path can be either a URL or a filename.
* @p documentMimeType gives importDocument a hint about what type
* the document may be. It can be left empty.
*
* @return status signals the success/error of the conversion.
* If the QString which is returned isEmpty() and the status is OK,
* then we imported the file directly into the document.
*/
KisImportExportFilter::ConversionStatus importDocument(const QString &location, const QString &mimeType);
/**
* @brief Exports the given file/document to the specified URL/mimetype.
*
* If @p mimeType is empty, then the closest matching Calligra part is searched
* and when the method returns @p mimeType contains this mimetype.
* Oh, well, export is a C++ keyword ;)
*/
- KisImportExportFilter::ConversionStatus exportDocument(const QString &location, const QString& realLocation, QByteArray &mimeType, bool showWarnings = true, KisPropertiesConfigurationSP exportConfiguration = 0);
+ KisImportExportFilter::ConversionStatus exportDocument(const QString &location, const QString& realLocation, const QByteArray &mimeType, bool showWarnings = true, KisPropertiesConfigurationSP exportConfiguration = 0);
+
+ QFuture exportDocumentAsyc(const QString &location, const QString& realLocation, const QByteArray &mimeType, bool showWarnings = true, KisPropertiesConfigurationSP exportConfiguration = 0);
///@name Static API
//@{
/**
* Suitable for passing to KoFileDialog::setMimeTypeFilters. The default mime
* gets set by the "users" of this method, as we do not have enough
* information here.
* Optionally, @p extraNativeMimeTypes are added after the native mimetype.
*/
static QStringList mimeFilter(Direction direction);
/**
* @brief filterForMimeType loads the relevant import/export plugin and returns it. The caller
* is responsible for deleting it!
* @param mimetype the mimetype we want to import/export. If there's more than one plugin, the one
* with the highest weight as defined in the json description will be taken
* @param direction import or export
* @return a pointer to the filter plugin or 0 if none could be found
*/
static KisImportExportFilter *filterForMimeType(const QString &mimetype, Direction direction);
/**
* Set the filter manager is batch mode (no dialog shown)
* instead of the interactive mode (dialog shown)
*/
void setBatchMode(const bool batch);
/**
* Get if the filter manager is batch mode (true)
* or in interactive mode (true)
*/
bool batchMode(void) const;
void setProgresUpdater(KoProgressUpdater *updater);
static QString askForAudioFileName(const QString &defaultDir, QWidget *parent);
private Q_SLOTS:
private:
- KisImportExportFilter::ConversionStatus convert(Direction direction, const QString &location, const QString& realLocation, const QString &mimeType, bool showWarnings, KisPropertiesConfigurationSP exportConfiguration);
+ struct ConversionResult;
+ ConversionResult convert(Direction direction, const QString &location, const QString& realLocation, const QString &mimeType, bool showWarnings, KisPropertiesConfigurationSP exportConfiguration, bool isAsync);
void fillStaticExportConfigurationProperties(KisPropertiesConfigurationSP exportConfiguration);
bool askUserAboutExportConfiguration(QSharedPointer filter, KisPropertiesConfigurationSP exportConfiguration, const QByteArray &from, const QByteArray &to, bool batchMode, const bool showWarnings, bool *alsoAsKra);
KisImportExportFilter::ConversionStatus doImport(const QString &location, QSharedPointer filter);
KisImportExportFilter::ConversionStatus doExport(const QString &location, QSharedPointer filter, KisPropertiesConfigurationSP exportConfiguration, bool alsoAsKra);
KisImportExportFilter::ConversionStatus doExportImpl(const QString &location, QSharedPointer filter, KisPropertiesConfigurationSP exportConfiguration);
// Private API
KisImportExportManager(const KisImportExportManager& rhs);
KisImportExportManager &operator=(const KisImportExportManager& rhs);
KisDocument *m_document;
/// A static cache for the availability checks of filters
static QStringList m_importMimeTypes;
static QStringList m_exportMimeTypes;
class Private;
Private * const d;
};
#endif // __KO_FILTER_MANAGER_H__
diff --git a/libs/ui/KisMainWindow.cpp b/libs/ui/KisMainWindow.cpp
index 2f346ccc2d..d91add89d2 100644
--- a/libs/ui/KisMainWindow.cpp
+++ b/libs/ui/KisMainWindow.cpp
@@ -1,2416 +1,2431 @@
/* This file is part of the KDE project
Copyright (C) 1998, 1999 Torben Weis
Copyright (C) 2000-2006 David Faure
Copyright (C) 2007, 2009 Thomas zander
Copyright (C) 2010 Benjamin Port
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Library General Public
License as published by the Free Software Foundation; either
version 2 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Library General Public License for more details.
You should have received a copy of the GNU Library General Public License
along with this library; see the file COPYING.LIB. If not, write to
the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#include "KisMainWindow.h"
#include
// qt includes
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#ifdef HAVE_KIO
#include
#endif
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include "KoDockFactoryBase.h"
#include "KoDockWidgetTitleBar.h"
#include "KoDocumentInfoDlg.h"
#include "KoDocumentInfo.h"
#include "KoFileDialog.h"
#include
#include
#include
#include
#include
#include "KoToolDocker.h"
#include
#include
#include
#include
#include
#include
#include "dialogs/kis_about_application.h"
#include "dialogs/kis_delayed_save_dialog.h"
#include "dialogs/kis_dlg_preferences.h"
#include "kis_action.h"
#include "kis_action_manager.h"
#include "KisApplication.h"
#include "kis_canvas2.h"
#include "kis_canvas_controller.h"
#include "kis_canvas_resource_provider.h"
#include "kis_clipboard.h"
#include "kis_config.h"
#include "kis_config_notifier.h"
#include "kis_custom_image_widget.h"
#include
#include "KisDocument.h"
#include "KisDocument.h"
#include "kis_group_layer.h"
#include "kis_icon_utils.h"
#include "kis_image_from_clipboard_widget.h"
#include "kis_image.h"
#include
#include "KisImportExportManager.h"
#include "kis_mainwindow_observer.h"
#include "kis_node.h"
#include "KisOpenPane.h"
#include "kis_paintop_box.h"
#include "KisPart.h"
#include "KisPrintJob.h"
#include "kis_resource_server_provider.h"
#include "kis_signal_compressor_with_param.h"
#include "KisView.h"
#include "KisViewManager.h"
#include "thememanager.h"
#include "kis_animation_importer.h"
#include "dialogs/kis_dlg_import_image_sequence.h"
#include "kis_animation_exporter.h"
#include
#ifdef Q_OS_WIN
#include
#endif
class ToolDockerFactory : public KoDockFactoryBase
{
public:
ToolDockerFactory() : KoDockFactoryBase() { }
QString id() const override {
return "sharedtooldocker";
}
QDockWidget* createDockWidget() override {
KoToolDocker* dockWidget = new KoToolDocker();
dockWidget->setTabEnabled(false);
return dockWidget;
}
DockPosition defaultDockPosition() const override {
return DockRight;
}
};
class Q_DECL_HIDDEN KisMainWindow::Private
{
public:
Private(KisMainWindow *parent)
: q(parent)
, dockWidgetMenu(new KActionMenu(i18nc("@action:inmenu", "&Dockers"), parent))
, windowMenu(new KActionMenu(i18nc("@action:inmenu", "&Window"), parent))
, documentMenu(new KActionMenu(i18nc("@action:inmenu", "New &View"), parent))
, mdiArea(new QMdiArea(parent))
, windowMapper(new QSignalMapper(parent))
, documentMapper(new QSignalMapper(parent))
{
}
~Private() {
qDeleteAll(toolbarList);
}
KisMainWindow *q {0};
KisViewManager *viewManager {0};
QPointer activeView;
QPointer progress;
QPointer progressCancel;
QMutex progressMutex;
QList toolbarList;
bool firstTime {true};
bool windowSizeDirty {false};
bool readOnly {false};
bool noCleanup {false};
KisAction *showDocumentInfo {0};
KisAction *saveAction {0};
KisAction *saveActionAs {0};
// KisAction *printAction;
// KisAction *printActionPreview;
// KisAction *exportPdf {0};
KisAction *importAnimation {0};
KisAction *closeAll {0};
// KisAction *reloadFile;
KisAction *importFile {0};
KisAction *exportFile {0};
KisAction *undo {0};
KisAction *redo {0};
KisAction *newWindow {0};
KisAction *close {0};
KisAction *mdiCascade {0};
KisAction *mdiTile {0};
KisAction *mdiNextWindow {0};
KisAction *mdiPreviousWindow {0};
KisAction *toggleDockers {0};
KisAction *toggleDockerTitleBars {0};
KisAction *expandingSpacers[2];
KActionMenu *dockWidgetMenu;
KActionMenu *windowMenu;
KActionMenu *documentMenu;
KHelpMenu *helpMenu {0};
KRecentFilesAction *recentFiles {0};
QUrl lastExportUrl;
QMap dockWidgetsMap;
QMap dockWidgetVisibilityMap;
QByteArray dockerStateBeforeHiding;
KoToolDocker *toolOptionsDocker {0};
QCloseEvent *deferredClosingEvent {0};
Digikam::ThemeManager *themeManager {0};
QMdiArea *mdiArea;
QMdiSubWindow *activeSubWindow {0};
QSignalMapper *windowMapper;
QSignalMapper *documentMapper;
QByteArray lastExportedFormat;
QScopedPointer > tabSwitchCompressor;
QMutex savingEntryMutex;
KisActionManager * actionManager() {
return viewManager->actionManager();
}
QTabBar* findTabBarHACK() {
QObjectList objects = mdiArea->children();
Q_FOREACH (QObject *object, objects) {
QTabBar *bar = qobject_cast(object);
if (bar) {
return bar;
}
}
return 0;
}
};
KisMainWindow::KisMainWindow()
: KXmlGuiWindow()
, d(new Private(this))
{
KisConfig cfg;
d->viewManager = new KisViewManager(this, actionCollection());
KConfigGroup group( KSharedConfig::openConfig(), "theme");
d->themeManager = new Digikam::ThemeManager(group.readEntry("Theme", "Krita dark"), this);
setAcceptDrops(true);
setStandardToolBarMenuEnabled(true);
setTabPosition(Qt::AllDockWidgetAreas, QTabWidget::North);
setDockNestingEnabled(true);
qApp->setStartDragDistance(25); // 25 px is a distance that works well for Tablet and Mouse events
#ifdef Q_OS_OSX
setUnifiedTitleAndToolBarOnMac(true);
#endif
connect(this, SIGNAL(restoringDone()), this, SLOT(forceDockTabFonts()));
connect(this, SIGNAL(themeChanged()), d->viewManager, SLOT(updateIcons()));
connect(KisPart::instance(), SIGNAL(documentClosed(QString)), SLOT(updateWindowMenu()));
connect(KisPart::instance(), SIGNAL(documentOpened(QString)), SLOT(updateWindowMenu()));
connect(KisConfigNotifier::instance(), SIGNAL(configChanged()), this, SLOT(configChanged()));
actionCollection()->addAssociatedWidget(this);
KoPluginLoader::instance()->load("Krita/ViewPlugin", "Type == 'Service' and ([X-Krita-Version] == 28)", KoPluginLoader::PluginsConfig(), d->viewManager);
KoToolBoxFactory toolBoxFactory;
QDockWidget *toolbox = createDockWidget(&toolBoxFactory);
toolbox->setFeatures(QDockWidget::DockWidgetMovable | QDockWidget::DockWidgetFloatable | QDockWidget::DockWidgetClosable);
if (cfg.toolOptionsInDocker()) {
ToolDockerFactory toolDockerFactory;
d->toolOptionsDocker = qobject_cast(createDockWidget(&toolDockerFactory));
d->toolOptionsDocker->toggleViewAction()->setEnabled(true);
}
QMap dockwidgetActions;
dockwidgetActions[toolbox->toggleViewAction()->text()] = toolbox->toggleViewAction();
Q_FOREACH (const QString & docker, KoDockRegistry::instance()->keys()) {
KoDockFactoryBase *factory = KoDockRegistry::instance()->value(docker);
QDockWidget *dw = createDockWidget(factory);
dockwidgetActions[dw->toggleViewAction()->text()] = dw->toggleViewAction();
}
if (d->toolOptionsDocker) {
dockwidgetActions[d->toolOptionsDocker->toggleViewAction()->text()] = d->toolOptionsDocker->toggleViewAction();
}
connect(KoToolManager::instance(), SIGNAL(toolOptionWidgetsChanged(KoCanvasController*, QList >)), this, SLOT(newOptionWidgets(KoCanvasController*, QList >)));
Q_FOREACH (QString title, dockwidgetActions.keys()) {
d->dockWidgetMenu->addAction(dockwidgetActions[title]);
}
Q_FOREACH (QDockWidget *wdg, dockWidgets()) {
if ((wdg->features() & QDockWidget::DockWidgetClosable) == 0) {
wdg->setVisible(true);
}
}
Q_FOREACH (KoCanvasObserverBase* observer, canvasObservers()) {
observer->setObservedCanvas(0);
KisMainwindowObserver* mainwindowObserver = dynamic_cast(observer);
if (mainwindowObserver) {
mainwindowObserver->setMainWindow(d->viewManager);
}
}
d->mdiArea->setHorizontalScrollBarPolicy(Qt::ScrollBarAsNeeded);
d->mdiArea->setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded);
d->mdiArea->setTabPosition(QTabWidget::North);
d->mdiArea->setTabsClosable(true);
setCentralWidget(d->mdiArea);
connect(d->mdiArea, SIGNAL(subWindowActivated(QMdiSubWindow*)), this, SLOT(subWindowActivated()));
connect(d->windowMapper, SIGNAL(mapped(QWidget*)), this, SLOT(setActiveSubWindow(QWidget*)));
connect(d->documentMapper, SIGNAL(mapped(QObject*)), this, SLOT(newView(QObject*)));
createActions();
setAutoSaveSettings("krita", false);
subWindowActivated();
updateWindowMenu();
if (isHelpMenuEnabled() && !d->helpMenu) {
// workaround for KHelpMenu (or rather KAboutData::applicationData()) internally
// not using the Q*Application metadata ATM, which results e.g. in the bugreport wizard
// not having the app version preset
// fixed hopefully in KF5 5.22.0, patch pending
QGuiApplication *app = qApp;
KAboutData aboutData(app->applicationName(), app->applicationDisplayName(), app->applicationVersion());
aboutData.setOrganizationDomain(app->organizationDomain().toUtf8());
d->helpMenu = new KHelpMenu(this, aboutData, false);
// workaround-less version:
// d->helpMenu = new KHelpMenu(this, QString()/*unused*/, false);
// The difference between using KActionCollection->addAction() is that
// these actions do not get tied to the MainWindow. What does this all do?
KActionCollection *actions = d->viewManager->actionCollection();
QAction *helpContentsAction = d->helpMenu->action(KHelpMenu::menuHelpContents);
QAction *whatsThisAction = d->helpMenu->action(KHelpMenu::menuWhatsThis);
QAction *reportBugAction = d->helpMenu->action(KHelpMenu::menuReportBug);
QAction *switchLanguageAction = d->helpMenu->action(KHelpMenu::menuSwitchLanguage);
QAction *aboutAppAction = d->helpMenu->action(KHelpMenu::menuAboutApp);
QAction *aboutKdeAction = d->helpMenu->action(KHelpMenu::menuAboutKDE);
if (helpContentsAction) {
actions->addAction(helpContentsAction->objectName(), helpContentsAction);
}
if (whatsThisAction) {
actions->addAction(whatsThisAction->objectName(), whatsThisAction);
}
if (reportBugAction) {
actions->addAction(reportBugAction->objectName(), reportBugAction);
}
if (switchLanguageAction) {
actions->addAction(switchLanguageAction->objectName(), switchLanguageAction);
}
if (aboutAppAction) {
actions->addAction(aboutAppAction->objectName(), aboutAppAction);
}
if (aboutKdeAction) {
actions->addAction(aboutKdeAction->objectName(), aboutKdeAction);
}
connect(d->helpMenu, SIGNAL(showAboutApplication()), SLOT(showAboutApplication()));
}
// KDE' libs 4''s help contents action is broken outside kde, for some reason... We can handle it just as easily ourselves
QAction *helpAction = actionCollection()->action("help_contents");
helpAction->disconnect();
connect(helpAction, SIGNAL(triggered()), this, SLOT(showManual()));
#if 0
//check for colliding shortcuts
QSet existingShortcuts;
Q_FOREACH (QAction* action, actionCollection()->actions()) {
if(action->shortcut() == QKeySequence(0)) {
continue;
}
dbgKrita << "shortcut " << action->text() << " " << action->shortcut();
Q_ASSERT(!existingShortcuts.contains(action->shortcut()));
existingShortcuts.insert(action->shortcut());
}
#endif
configChanged();
// If we have customized the toolbars, load that first
setLocalXMLFile(KoResourcePaths::locateLocal("data", "krita.xmlgui"));
setXMLFile(":/kxmlgui5/krita.xmlgui");
guiFactory()->addClient(this);
// Create and plug toolbar list for Settings menu
QList toolbarList;
Q_FOREACH (QWidget* it, guiFactory()->containers("ToolBar")) {
KToolBar * toolBar = ::qobject_cast(it);
if (toolBar) {
if (toolBar->objectName() == "BrushesAndStuff") {
toolBar->setEnabled(false);
}
KToggleAction* act = new KToggleAction(i18n("Show %1 Toolbar", toolBar->windowTitle()), this);
actionCollection()->addAction(toolBar->objectName().toUtf8(), act);
act->setCheckedState(KGuiItem(i18n("Hide %1 Toolbar", toolBar->windowTitle())));
connect(act, SIGNAL(toggled(bool)), this, SLOT(slotToolbarToggled(bool)));
act->setChecked(!toolBar->isHidden());
toolbarList.append(act);
} else
warnUI << "Toolbar list contains a " << it->metaObject()->className() << " which is not a toolbar!";
}
plugActionList("toolbarlist", toolbarList);
setToolbarList(toolbarList);
applyToolBarLayout();
d->viewManager->updateGUI();
d->viewManager->updateIcons();
#ifdef Q_OS_WIN
auto w = qApp->activeWindow();
if (w) QWindowsWindowFunctions::setHasBorderInFullScreen(w->windowHandle(), true);
#endif
QTimer::singleShot(1000, this, SLOT(checkSanity()));
{
using namespace std::placeholders; // For _1 placeholder
std::function callback(
std::bind(&KisMainWindow::switchTab, this, _1));
d->tabSwitchCompressor.reset(
new KisSignalCompressorWithParam(500, callback, KisSignalCompressor::FIRST_INACTIVE));
}
}
void KisMainWindow::setNoCleanup(bool noCleanup)
{
d->noCleanup = noCleanup;
}
KisMainWindow::~KisMainWindow()
{
// Q_FOREACH (QAction *ac, actionCollection()->actions()) {
// QAction *action = qobject_cast(ac);
// if (action) {
// dbgKrita << "objectName()
// << "icon=" << action->icon().name()
// << "text=" << action->text().replace("&", "&")
// << "whatsThis=" << action->whatsThis()
// << "toolTip=" << action->toolTip().replace("", "").replace("", "")
// << "iconText=" << action->iconText().replace("&", "&")
// << "shortcut=" << action->shortcut(QAction::ActiveShortcut).toString()
// << "defaultShortcut=" << action->shortcut(QAction::DefaultShortcut).toString()
// << "isCheckable=" << QString((action->isChecked() ? "true" : "false"))
// << "statusTip=" << action->statusTip()
// << "/>" ;
// }
// else {
// dbgKrita << "Got a QAction:" << ac->objectName();
// }
// }
// The doc and view might still exist (this is the case when closing the window)
KisPart::instance()->removeMainWindow(this);
if (d->noCleanup)
return;
delete d->viewManager;
delete d;
}
void KisMainWindow::addView(KisView *view)
{
if (d->activeView == view) return;
if (d->activeView) {
d->activeView->disconnect(this);
}
showView(view);
updateCaption();
emit restoringDone();
if (d->activeView) {
connect(d->activeView, SIGNAL(titleModified(QString,bool)), SLOT(slotDocumentTitleModified(QString,bool)));
}
}
void KisMainWindow::showView(KisView *imageView)
{
if (imageView && activeView() != imageView) {
// XXX: find a better way to initialize this!
imageView->setViewManager(d->viewManager);
imageView->canvasBase()->setFavoriteResourceManager(d->viewManager->paintOpBox()->favoriteResourcesManager());
imageView->slotLoadingFinished();
QMdiSubWindow *subwin = d->mdiArea->addSubWindow(imageView);
subwin->setAttribute(Qt::WA_DeleteOnClose, true);
connect(subwin, SIGNAL(destroyed()), SLOT(updateWindowMenu()));
KisConfig cfg;
subwin->setOption(QMdiSubWindow::RubberBandMove, cfg.readEntry("mdi_rubberband", cfg.useOpenGL()));
subwin->setOption(QMdiSubWindow::RubberBandResize, cfg.readEntry("mdi_rubberband", cfg.useOpenGL()));
subwin->setWindowIcon(qApp->windowIcon());
/**
* Hack alert!
*
* Here we explicitly request KoToolManager to emit all the tool
* activation signals, to reinitialize the tool options docker.
*
* That is needed due to a design flaw we have in the
* initialization procedure. The tool in the KoToolManager is
* initialized in KisView::setViewManager() calls, which
* happens early enough. During this call the tool manager
* requests KoCanvasControllerWidget to emit the signal to
* update the widgets in the tool docker. *But* at that moment
* of time the view is not yet connected to the main window,
* because it happens in KisViewManager::setCurrentView a bit
* later. This fact makes the widgets updating signals be lost
* and never reach the tool docker.
*
* So here we just explicitly call the tool activation stub.
*/
KoToolManager::instance()->initializeCurrentToolForCanvas();
if (d->mdiArea->subWindowList().size() == 1) {
imageView->showMaximized();
}
else {
imageView->show();
}
// No, no, no: do not try to call this _before_ the show() has
// been called on the view; only when that has happened is the
// opengl context active, and very bad things happen if we tell
// the dockers to update themselves with a view if the opengl
// context is not active.
setActiveView(imageView);
updateWindowMenu();
updateCaption();
}
}
void KisMainWindow::slotPreferences()
{
if (KisDlgPreferences::editPreferences()) {
KisConfigNotifier::instance()->notifyConfigChanged();
// XXX: should this be changed for the views in other windows as well?
Q_FOREACH (QPointer koview, KisPart::instance()->views()) {
KisViewManager *view = qobject_cast(koview);
if (view) {
// Update the settings for all nodes -- they don't query
// KisConfig directly because they need the settings during
// compositing, and they don't connect to the config notifier
// because nodes are not QObjects (because only one base class
// can be a QObject).
KisNode* node = dynamic_cast(view->image()->rootLayer().data());
node->updateSettings();
}
}
d->viewManager->showHideScrollbars();
}
}
void KisMainWindow::slotThemeChanged()
{
// save theme changes instantly
KConfigGroup group( KSharedConfig::openConfig(), "theme");
group.writeEntry("Theme", d->themeManager->currentThemeName());
// reload action icons!
Q_FOREACH (QAction *action, actionCollection()->actions()) {
KisIconUtils::updateIcon(action);
}
emit themeChanged();
}
void KisMainWindow::updateReloadFileAction(KisDocument *doc)
{
Q_UNUSED(doc);
// d->reloadFile->setEnabled(doc && !doc->url().isEmpty());
}
void KisMainWindow::setReadWrite(bool readwrite)
{
d->saveAction->setEnabled(readwrite);
d->importFile->setEnabled(readwrite);
d->readOnly = !readwrite;
updateCaption();
}
void KisMainWindow::addRecentURL(const QUrl &url)
{
dbgUI << "KisMainWindow::addRecentURL url=" << url.toDisplayString();
// Add entry to recent documents list
// (call coming from KisDocument because it must work with cmd line, template dlg, file/open, etc.)
if (!url.isEmpty()) {
bool ok = true;
if (url.isLocalFile()) {
QString path = url.adjusted(QUrl::StripTrailingSlash).toLocalFile();
const QStringList tmpDirs = KoResourcePaths::resourceDirs("tmp");
for (QStringList::ConstIterator it = tmpDirs.begin() ; ok && it != tmpDirs.end() ; ++it)
if (path.contains(*it))
ok = false; // it's in the tmp resource
#ifdef HAVE_KIO
if (ok) {
KRecentDocument::add(QUrl::fromLocalFile(path));
}
#endif
}
#ifdef HAVE_KIO
else {
KRecentDocument::add(url.adjusted(QUrl::StripTrailingSlash));
}
#endif
if (ok) {
d->recentFiles->addUrl(url);
}
saveRecentFiles();
}
}
void KisMainWindow::saveRecentFiles()
{
// Save list of recent files
KSharedConfigPtr config = KSharedConfig::openConfig();
d->recentFiles->saveEntries(config->group("RecentFiles"));
config->sync();
// Tell all windows to reload their list, after saving
// Doesn't work multi-process, but it's a start
Q_FOREACH (KMainWindow* window, KMainWindow::memberList())
static_cast(window)->reloadRecentFileList();
}
void KisMainWindow::reloadRecentFileList()
{
d->recentFiles->loadEntries( KSharedConfig::openConfig()->group("RecentFiles"));
}
void KisMainWindow::updateCaption()
{
if (!d->mdiArea->activeSubWindow()) {
updateCaption(QString(), false);
}
else if (d->activeView && d->activeView->document()){
QString caption( d->activeView->document()->caption() );
if (d->readOnly) {
caption += ' ' + i18n("(write protected)");
}
d->activeView->setWindowTitle(caption);
updateCaption(caption, d->activeView->document()->isModified());
if (!d->activeView->document()->url().fileName().isEmpty())
d->saveAction->setToolTip(i18n("Save as %1", d->activeView->document()->url().fileName()));
else
d->saveAction->setToolTip(i18n("Save"));
}
}
void KisMainWindow::updateCaption(const QString & caption, bool mod)
{
dbgUI << "KisMainWindow::updateCaption(" << caption << "," << mod << ")";
#ifdef KRITA_ALPHA
setCaption(QString("ALPHA %1: %2").arg(KRITA_ALPHA).arg(caption), mod);
return;
#endif
#ifdef KRITA_BETA
setCaption(QString("BETA %1: %2").arg(KRITA_BETA).arg(caption), mod);
return;
#endif
#ifdef KRITA_RC
setCaption(QString("RELEASE CANDIDATE %1: %2").arg(KRITA_RC).arg(caption), mod);
return;
#endif
setCaption(caption, mod);
}
KisView *KisMainWindow::activeView() const
{
if (d->activeView) {
return d->activeView;
}
return 0;
}
bool KisMainWindow::openDocument(const QUrl &url, OpenFlags flags)
{
if (!QFile(url.toLocalFile()).exists()) {
if (!flags && BatchMode) {
QMessageBox::critical(0, i18nc("@title:window", "Krita"), i18n("The file %1 does not exist.", url.url()));
}
d->recentFiles->removeUrl(url); //remove the file from the recent-opened-file-list
saveRecentFiles();
return false;
}
return openDocumentInternal(url, flags);
}
bool KisMainWindow::openDocumentInternal(const QUrl &url, OpenFlags flags)
{
if (!url.isLocalFile()) {
qDebug() << "KisMainWindow::openDocumentInternal. Not a local file:" << url;
return false;
}
KisDocument *newdoc = KisPart::instance()->createDocument();
if (flags & BatchMode) {
newdoc->setFileBatchMode(true);
}
d->firstTime = true;
connect(newdoc, SIGNAL(sigProgress(int)), this, SLOT(slotProgress(int)));
connect(newdoc, SIGNAL(completed()), this, SLOT(slotLoadCompleted()));
connect(newdoc, SIGNAL(canceled(const QString &)), this, SLOT(slotLoadCanceled(const QString &)));
- bool openRet = !(flags & Import) ? newdoc->openUrl(url) : newdoc->importDocument(url);
+
+ KisDocument::OpenFlags openFlags = KisDocument::None;
+ if (flags & RecoveryFile) {
+ openFlags |= KisDocument::RecoveryFile;
+ }
+
+ bool openRet = !(flags & Import) ? newdoc->openUrl(url, openFlags) : newdoc->importDocument(url);
+
+
if (!openRet) {
delete newdoc;
return false;
}
KisPart::instance()->addDocument(newdoc);
updateReloadFileAction(newdoc);
if (!QFileInfo(url.toLocalFile()).isWritable()) {
setReadWrite(false);
}
return true;
}
void KisMainWindow::addViewAndNotifyLoadingCompleted(KisDocument *document)
{
KisView *view = KisPart::instance()->createView(document, resourceManager(), actionCollection(), this);
addView(view);
emit guiLoadingFinished();
}
QStringList KisMainWindow::showOpenFileDialog(bool isImporting)
{
KoFileDialog dialog(this, KoFileDialog::ImportFiles, "OpenDocument");
dialog.setDefaultDir(QDesktopServices::storageLocation(QDesktopServices::PicturesLocation));
dialog.setMimeTypeFilters(KisImportExportManager::mimeFilter(KisImportExportManager::Import));
dialog.setCaption(isImporting ? i18n("Import Images") : i18n("Open Images"));
return dialog.filenames();
}
// Separate from openDocument to handle async loading (remote URLs)
void KisMainWindow::slotLoadCompleted()
{
KisDocument *newdoc = qobject_cast(sender());
if (newdoc && newdoc->image()) {
addViewAndNotifyLoadingCompleted(newdoc);
disconnect(newdoc, SIGNAL(sigProgress(int)), this, SLOT(slotProgress(int)));
disconnect(newdoc, SIGNAL(completed()), this, SLOT(slotLoadCompleted()));
disconnect(newdoc, SIGNAL(canceled(const QString &)), this, SLOT(slotLoadCanceled(const QString &)));
emit loadCompleted();
}
}
void KisMainWindow::slotLoadCanceled(const QString & errMsg)
{
dbgUI << "KisMainWindow::slotLoadCanceled";
if (!errMsg.isEmpty()) // empty when canceled by user
QMessageBox::critical(this, i18nc("@title:window", "Krita"), errMsg);
// ... can't delete the document, it's the one who emitted the signal...
KisDocument* doc = qobject_cast(sender());
Q_ASSERT(doc);
disconnect(doc, SIGNAL(sigProgress(int)), this, SLOT(slotProgress(int)));
disconnect(doc, SIGNAL(completed()), this, SLOT(slotLoadCompleted()));
disconnect(doc, SIGNAL(canceled(const QString &)), this, SLOT(slotLoadCanceled(const QString &)));
}
void KisMainWindow::slotSaveCanceled(const QString &errMsg)
{
dbgUI << "KisMainWindow::slotSaveCanceled";
if (!errMsg.isEmpty()) // empty when canceled by user
QMessageBox::critical(this, i18nc("@title:window", "Krita"), errMsg);
slotSaveCompleted();
}
void KisMainWindow::slotSaveCompleted()
{
dbgUI << "KisMainWindow::slotSaveCompleted";
KisDocument* doc = qobject_cast(sender());
Q_ASSERT(doc);
disconnect(doc, SIGNAL(sigProgress(int)), this, SLOT(slotProgress(int)));
disconnect(doc, SIGNAL(completed()), this, SLOT(slotSaveCompleted()));
disconnect(doc, SIGNAL(canceled(const QString &)), this, SLOT(slotSaveCanceled(const QString &)));
if (d->deferredClosingEvent) {
KXmlGuiWindow::closeEvent(d->deferredClosingEvent);
}
}
bool KisMainWindow::hackIsSaving() const
{
StdLockableWrapper wrapper(&d->savingEntryMutex);
std::unique_lock> l(wrapper, std::try_to_lock);
return !l.owns_lock();
}
bool KisMainWindow::saveDocument(KisDocument *document, bool saveas, bool isExporting)
{
if (!document) {
return true;
}
/**
* Make sure that we cannot enter this method twice!
*
* The lower level functions may call processEvents() so
* double-entry is quite possible to achieve. Here we try to lock
* the mutex, and if it is failed, just cancel saving.
*/
StdLockableWrapper wrapper(&d->savingEntryMutex);
std::unique_lock> l(wrapper, std::try_to_lock);
if (!l.owns_lock()) return false;
// no busy wait for saving because it is dangerous!
KisDelayedSaveDialog dlg(document->image(), KisDelayedSaveDialog::SaveDialog, 0, this);
dlg.blockIfImageIsBusy();
if (dlg.result() == KisDelayedSaveDialog::Rejected) {
return false;
} else if (dlg.result() == KisDelayedSaveDialog::Ignored) {
QMessageBox::critical(0,
i18nc("@title:window", "Krita"),
i18n("You are saving a file while the image is "
"still rendering. The saved file may be "
"incomplete or corrupted.\n\n"
"Please select a location where the original "
"file will not be overridden!"));
saveas = true;
}
+ if (document->isRecovered()) {
+ saveas = true;
+ }
+
bool reset_url;
if (document->url().isEmpty()) {
reset_url = true;
saveas = true;
} else {
reset_url = false;
}
connect(document, SIGNAL(sigProgress(int)), this, SLOT(slotProgress(int)));
connect(document, SIGNAL(completed()), this, SLOT(slotSaveCompleted()));
connect(document, SIGNAL(canceled(const QString &)), this, SLOT(slotSaveCanceled(const QString &)));
QUrl oldURL = document->url();
QString oldFile = document->localFilePath();
QByteArray _native_format = document->nativeFormatMimeType();
QByteArray oldOutputFormat = document->outputMimeType();
QUrl suggestedURL = document->url();
QStringList mimeFilter;
mimeFilter = KisImportExportManager::mimeFilter(KisImportExportManager::Export);
if (!mimeFilter.contains(oldOutputFormat) && !isExporting) {
dbgUI << "KisMainWindow::saveDocument no export filter for" << oldOutputFormat;
// --- don't setOutputMimeType in case the user cancels the Save As
// dialog and then tries to just plain Save ---
// suggest a different filename extension (yes, we fortunately don't all live in a world of magic :))
QString suggestedFilename = suggestedURL.fileName();
if (!suggestedFilename.isEmpty()) { // ".kra" looks strange for a name
int c = suggestedFilename.lastIndexOf('.');
const QString ext = KisMimeDatabase::suffixesForMimeType(_native_format).first();
if (!ext.isEmpty()) {
if (c < 0)
suggestedFilename = suggestedFilename + "." + ext;
else
suggestedFilename = suggestedFilename.left(c) + "." + ext;
} else { // current filename extension wrong anyway
if (c > 0) {
// this assumes that a . signifies an extension, not just a .
suggestedFilename = suggestedFilename.left(c);
}
}
suggestedURL = suggestedURL.adjusted(QUrl::RemoveFilename);
suggestedURL.setPath(suggestedURL.path() + suggestedFilename);
}
// force the user to choose outputMimeType
saveas = true;
}
bool ret = false;
if (document->url().isEmpty() || saveas) {
// if you're just File/Save As'ing to change filter options you
// don't want to be reminded about overwriting files etc.
bool justChangingFilterOptions = false;
KoFileDialog dialog(this, KoFileDialog::SaveFile, "SaveAs");
dialog.setCaption(i18n("untitled"));
if (isExporting && !d->lastExportUrl.isEmpty()) {
dialog.setDefaultDir(d->lastExportUrl.toLocalFile());
}
else {
dialog.setDefaultDir(suggestedURL.toLocalFile());
}
// Default to all supported file types if user is exporting, otherwise use Krita default
dialog.setMimeTypeFilters(mimeFilter, QString(_native_format));
QUrl newURL = QUrl::fromUserInput(dialog.filename());
if (newURL.isLocalFile()) {
QString fn = newURL.toLocalFile();
if (QFileInfo(fn).completeSuffix().isEmpty()) {
fn.append(KisMimeDatabase::suffixesForMimeType(_native_format).first());
newURL = QUrl::fromLocalFile(fn);
}
}
if (document->documentInfo()->aboutInfo("title") == i18n("Unnamed")) {
QString fn = newURL.toLocalFile();
QFileInfo info(fn);
document->documentInfo()->setAboutInfo("title", info.baseName());
}
QByteArray outputFormat = _native_format;
QString outputFormatString = KisMimeDatabase::mimeTypeForFile(newURL.toLocalFile());
outputFormat = outputFormatString.toLatin1();
if (!isExporting) {
justChangingFilterOptions = (newURL == document->url()) && (outputFormat == document->mimeType());
}
else {
justChangingFilterOptions = (newURL == d->lastExportUrl) && (outputFormat == d->lastExportedFormat);
}
bool bOk = true;
if (newURL.isEmpty()) {
bOk = false;
}
if (bOk) {
bool wantToSave = true;
// don't change this line unless you know what you're doing :)
if (!justChangingFilterOptions) {
if (!document->isNativeFormat(outputFormat))
wantToSave = true;
}
if (wantToSave) {
//
// Note:
// If the user is stupid enough to Export to the current URL,
// we do _not_ change this operation into a Save As. Reasons
// follow:
//
// 1. A check like "isExporting && oldURL == newURL"
// doesn't _always_ work on case-insensitive filesystems
// and inconsistent behaviour is bad.
// 2. It is probably not a good idea to change document->mimeType
// and friends because the next time the user File/Save's,
// (not Save As) they won't be expecting that they are
// using their File/Export settings
//
// As a bad side-effect of this, the modified flag will not
// be updated and it is possible that what is currently on
// their screen is not what is stored on disk (through loss
// of formatting). But if you are dumb enough to change
// mimetype but not the filename, then arguably, _you_ are
// the "bug" :)
//
// - Clarence
//
document->setOutputMimeType(outputFormat);
if (!isExporting) { // Save As
ret = document->saveAs(newURL, true);
if (ret) {
dbgUI << "Successful Save As!";
addRecentURL(newURL);
setReadWrite(true);
} else {
dbgUI << "Failed Save As!";
document->setUrl(oldURL);
document->setLocalFilePath(oldFile);
document->setOutputMimeType(oldOutputFormat);
}
} else { // Export
ret = document->exportDocument(newURL);
if (ret) {
// a few file dialog convenience things
d->lastExportUrl = newURL;
d->lastExportedFormat = outputFormat;
}
// always restore output format
document->setOutputMimeType(oldOutputFormat);
}
} // if (wantToSave) {
else
ret = false;
} // if (bOk) {
else
ret = false;
} else { // saving
// We cannot "export" into the currently
// opened document. We are not Gimp.
KIS_ASSERT_RECOVER_NOOP(!isExporting);
// be sure document has the correct outputMimeType!
if (document->isModified()) {
ret = document->save(true, 0);
}
if (!ret) {
dbgUI << "Failed Save!";
document->setUrl(oldURL);
document->setLocalFilePath(oldFile);
}
}
+ if (ret && !isExporting) {
+ document->setRecovered(false);
+ }
if (!ret && reset_url)
document->resetURL(); //clean the suggested filename as the save dialog was rejected
updateReloadFileAction(document);
updateCaption();
return ret;
}
void KisMainWindow::undo()
{
if (activeView()) {
activeView()->undoAction()->trigger();
d->undo->setText(activeView()->undoAction()->text());
}
}
void KisMainWindow::redo()
{
if (activeView()) {
activeView()->redoAction()->trigger();
d->redo->setText(activeView()->redoAction()->text());
}
}
void KisMainWindow::closeEvent(QCloseEvent *e)
{
d->mdiArea->closeAllSubWindows();
QAction *action= d->viewManager->actionCollection()->action("view_show_canvas_only");
if ((action) && (action->isChecked())) {
action->setChecked(false);
}
KConfigGroup cfg( KSharedConfig::openConfig(), "MainWindow");
cfg.writeEntry("ko_geometry", saveGeometry().toBase64());
cfg.writeEntry("ko_windowstate", saveState().toBase64());
{
KConfigGroup group( KSharedConfig::openConfig(), "theme");
group.writeEntry("Theme", d->themeManager->currentThemeName());
}
QList childrenList = d->mdiArea->subWindowList();
if (childrenList.isEmpty()) {
d->deferredClosingEvent = e;
if (!d->dockerStateBeforeHiding.isEmpty()) {
restoreState(d->dockerStateBeforeHiding);
}
statusBar()->setVisible(true);
menuBar()->setVisible(true);
saveWindowSettings();
if (d->noCleanup)
return;
Q_FOREACH (QMdiSubWindow *subwin, d->mdiArea->subWindowList()) {
KisView *view = dynamic_cast(subwin);
if (view) {
KisPart::instance()->removeView(view);
}
}
if (!d->dockWidgetVisibilityMap.isEmpty()) { // re-enable dockers for persistency
Q_FOREACH (QDockWidget* dockWidget, d->dockWidgetsMap)
dockWidget->setVisible(d->dockWidgetVisibilityMap.value(dockWidget));
}
} else {
e->setAccepted(false);
}
}
void KisMainWindow::saveWindowSettings()
{
KSharedConfigPtr config = KSharedConfig::openConfig();
if (d->windowSizeDirty ) {
dbgUI << "KisMainWindow::saveWindowSettings";
KConfigGroup group = config->group("MainWindow");
KWindowConfig::saveWindowSize(windowHandle(), group);
config->sync();
d->windowSizeDirty = false;
}
if (!d->activeView || d->activeView->document()) {
// Save toolbar position into the config file of the app, under the doc's component name
KConfigGroup group = KSharedConfig::openConfig()->group("krita");
saveMainWindowSettings(group);
// Save collapsable state of dock widgets
for (QMap::const_iterator i = d->dockWidgetsMap.constBegin();
i != d->dockWidgetsMap.constEnd(); ++i) {
if (i.value()->widget()) {
KConfigGroup dockGroup = group.group(QString("DockWidget ") + i.key());
dockGroup.writeEntry("Collapsed", i.value()->widget()->isHidden());
dockGroup.writeEntry("Locked", i.value()->property("Locked").toBool());
dockGroup.writeEntry("DockArea", (int) dockWidgetArea(i.value()));
dockGroup.writeEntry("xPosition", (int) i.value()->widget()->x());
dockGroup.writeEntry("yPosition", (int) i.value()->widget()->y());
dockGroup.writeEntry("width", (int) i.value()->widget()->width());
dockGroup.writeEntry("height", (int) i.value()->widget()->height());
}
}
}
KSharedConfig::openConfig()->sync();
resetAutoSaveSettings(); // Don't let KMainWindow override the good stuff we wrote down
}
void KisMainWindow::resizeEvent(QResizeEvent * e)
{
d->windowSizeDirty = true;
KXmlGuiWindow::resizeEvent(e);
}
void KisMainWindow::setActiveView(KisView* view)
{
d->activeView = view;
updateCaption();
actionCollection()->action("edit_undo")->setText(activeView()->undoAction()->text());
actionCollection()->action("edit_redo")->setText(activeView()->redoAction()->text());
d->viewManager->setCurrentView(view);
}
void KisMainWindow::dragEnterEvent(QDragEnterEvent *event)
{
if (event->mimeData()->hasUrls() ||
event->mimeData()->hasFormat("application/x-krita-node") ||
event->mimeData()->hasFormat("application/x-qt-image")) {
event->accept();
}
}
void KisMainWindow::dropEvent(QDropEvent *event)
{
if (event->mimeData()->hasUrls() && event->mimeData()->urls().size() > 0) {
Q_FOREACH (const QUrl &url, event->mimeData()->urls()) {
openDocument(url, None);
}
}
}
void KisMainWindow::dragMoveEvent(QDragMoveEvent * event)
{
QTabBar *tabBar = d->findTabBarHACK();
if (!tabBar && d->mdiArea->viewMode() == QMdiArea::TabbedView) {
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)!";
}
if (tabBar && tabBar->isVisible()) {
QPoint pos = tabBar->mapFromGlobal(mapToGlobal(event->pos()));
if (tabBar->rect().contains(pos)) {
const int tabIndex = tabBar->tabAt(pos);
if (tabIndex >= 0 && tabBar->currentIndex() != tabIndex) {
d->tabSwitchCompressor->start(tabIndex);
}
} else if (d->tabSwitchCompressor->isActive()) {
d->tabSwitchCompressor->stop();
}
}
}
void KisMainWindow::dragLeaveEvent(QDragLeaveEvent * /*event*/)
{
if (d->tabSwitchCompressor->isActive()) {
d->tabSwitchCompressor->stop();
}
}
void KisMainWindow::switchTab(int index)
{
QTabBar *tabBar = d->findTabBarHACK();
if (!tabBar) return;
tabBar->setCurrentIndex(index);
}
void KisMainWindow::slotFileNew()
{
const QStringList mimeFilter = KisImportExportManager::mimeFilter(KisImportExportManager::Import);
KisOpenPane *startupWidget = new KisOpenPane(this, mimeFilter, QStringLiteral("templates/"));
startupWidget->setWindowModality(Qt::WindowModal);
KisConfig cfg;
int w = cfg.defImageWidth();
int h = cfg.defImageHeight();
const double resolution = cfg.defImageResolution();
const QString colorModel = cfg.defColorModel();
const QString colorDepth = cfg.defaultColorDepth();
const QString colorProfile = cfg.defColorProfile();
CustomDocumentWidgetItem item;
item.widget = new KisCustomImageWidget(startupWidget,
w,
h,
resolution,
colorModel,
colorDepth,
colorProfile,
i18n("Unnamed"));
item.icon = "document-new";
startupWidget->addCustomDocumentWidget(item.widget, item.title, item.icon);
QSize sz = KisClipboard::instance()->clipSize();
if (sz.isValid() && sz.width() != 0 && sz.height() != 0) {
w = sz.width();
h = sz.height();
}
item.widget = new KisImageFromClipboard(startupWidget,
w,
h,
resolution,
colorModel,
colorDepth,
colorProfile,
i18n("Unnamed"));
item.title = i18n("Create from Clipboard");
item.icon = "tab-new";
startupWidget->addCustomDocumentWidget(item.widget, item.title, item.icon);
// calls deleteLater
connect(startupWidget, SIGNAL(documentSelected(KisDocument*)), KisPart::instance(), SLOT(startCustomDocument(KisDocument*)));
// calls deleteLater
connect(startupWidget, SIGNAL(openTemplate(const QUrl&)), KisPart::instance(), SLOT(openTemplate(const QUrl&)));
startupWidget->exec();
// Cancel calls deleteLater...
}
void KisMainWindow::slotImportFile()
{
dbgUI << "slotImportFile()";
slotFileOpen(true);
}
void KisMainWindow::slotFileOpen(bool isImporting)
{
QStringList urls = showOpenFileDialog(isImporting);
if (urls.isEmpty())
return;
Q_FOREACH (const QString& url, urls) {
if (!url.isEmpty()) {
OpenFlags flags = isImporting ? Import : None;
bool res = openDocument(QUrl::fromLocalFile(url), flags);
if (!res) {
warnKrita << "Loading" << url << "failed";
}
}
}
}
void KisMainWindow::slotFileOpenRecent(const QUrl &url)
{
(void) openDocument(QUrl::fromLocalFile(url.toLocalFile()), None);
}
void KisMainWindow::slotFileSave()
{
if (saveDocument(d->activeView->document(), false, false)) {
emit documentSaved();
}
}
void KisMainWindow::slotFileSaveAs()
{
if (saveDocument(d->activeView->document(), true, false)) {
emit documentSaved();
}
}
void KisMainWindow::slotExportFile()
{
if (saveDocument(d->activeView->document(), true, true)) {
emit documentSaved();
}
}
KoCanvasResourceManager *KisMainWindow::resourceManager() const
{
return d->viewManager->resourceProvider()->resourceManager();
}
int KisMainWindow::viewCount() const
{
return d->mdiArea->subWindowList().size();
}
bool KisMainWindow::restoreWorkspace(const QByteArray &state)
{
QByteArray oldState = saveState();
const bool showTitlebars = KisConfig().showDockerTitleBars();
// needed because otherwise the layout isn't correctly restored in some situations
Q_FOREACH (QDockWidget *dock, dockWidgets()) {
dock->hide();
dock->titleBarWidget()->setVisible(showTitlebars);
}
bool success = KXmlGuiWindow::restoreState(state);
if (!success) {
KXmlGuiWindow::restoreState(oldState);
Q_FOREACH (QDockWidget *dock, dockWidgets()) {
if (dock->titleBarWidget()) {
dock->titleBarWidget()->setVisible(showTitlebars || dock->isFloating());
}
}
return false;
}
Q_FOREACH (QDockWidget *dock, dockWidgets()) {
if (dock->titleBarWidget()) {
const bool isCollapsed = (dock->widget() && dock->widget()->isHidden()) || !dock->widget();
dock->titleBarWidget()->setVisible(showTitlebars || (dock->isFloating() && isCollapsed));
}
}
return success;
}
KisViewManager *KisMainWindow::viewManager() const
{
return d->viewManager;
}
void KisMainWindow::slotDocumentInfo()
{
if (!d->activeView->document())
return;
KoDocumentInfo *docInfo = d->activeView->document()->documentInfo();
if (!docInfo)
return;
KoDocumentInfoDlg *dlg = d->activeView->document()->createDocumentInfoDialog(this, docInfo);
if (dlg->exec()) {
if (dlg->isDocumentSaved()) {
d->activeView->document()->setModified(false);
} else {
d->activeView->document()->setModified(true);
}
d->activeView->document()->setTitleModified();
}
delete dlg;
}
bool KisMainWindow::slotFileCloseAll()
{
Q_FOREACH (QMdiSubWindow *subwin, d->mdiArea->subWindowList()) {
if (subwin) {
if(!subwin->close())
return false;
}
}
updateCaption();
return true;
}
void KisMainWindow::slotFileQuit()
{
if(!slotFileCloseAll())
return;
close();
Q_FOREACH (QPointer mainWin, KisPart::instance()->mainWindows()) {
if (mainWin != this) {
if(!mainWin->slotFileCloseAll())
return;
mainWin->close();
}
}
}
void KisMainWindow::slotFilePrint()
{
if (!activeView())
return;
KisPrintJob *printJob = activeView()->createPrintJob();
if (printJob == 0)
return;
applyDefaultSettings(printJob->printer());
QPrintDialog *printDialog = activeView()->createPrintDialog( printJob, this );
if (printDialog && printDialog->exec() == QDialog::Accepted) {
printJob->printer().setPageMargins(0.0, 0.0, 0.0, 0.0, QPrinter::Point);
printJob->printer().setPaperSize(QSizeF(activeView()->image()->width() / (72.0 * activeView()->image()->xRes()),
activeView()->image()->height()/ (72.0 * activeView()->image()->yRes())),
QPrinter::Inch);
printJob->startPrinting(KisPrintJob::DeleteWhenDone);
}
else {
delete printJob;
}
delete printDialog;
}
void KisMainWindow::slotFilePrintPreview()
{
if (!activeView())
return;
KisPrintJob *printJob = activeView()->createPrintJob();
if (printJob == 0)
return;
/* Sets the startPrinting() slot to be blocking.
The Qt print-preview dialog requires the printing to be completely blocking
and only return when the full document has been printed.
By default the KisPrintingDialog is non-blocking and
multithreading, setting blocking to true will allow it to be used in the preview dialog */
printJob->setProperty("blocking", true);
QPrintPreviewDialog *preview = new QPrintPreviewDialog(&printJob->printer(), this);
printJob->setParent(preview); // will take care of deleting the job
connect(preview, SIGNAL(paintRequested(QPrinter*)), printJob, SLOT(startPrinting()));
preview->exec();
delete preview;
}
KisPrintJob* KisMainWindow::exportToPdf(QString pdfFileName)
{
if (!activeView())
return 0;
if (!activeView()->document())
return 0;
KoPageLayout pageLayout;
pageLayout.width = 0;
pageLayout.height = 0;
pageLayout.topMargin = 0;
pageLayout.bottomMargin = 0;
pageLayout.leftMargin = 0;
pageLayout.rightMargin = 0;
if (pdfFileName.isEmpty()) {
KConfigGroup group = KSharedConfig::openConfig()->group("File Dialogs");
QString defaultDir = group.readEntry("SavePdfDialog");
if (defaultDir.isEmpty())
defaultDir = QDesktopServices::storageLocation(QDesktopServices::DocumentsLocation);
QUrl startUrl = QUrl::fromLocalFile(defaultDir);
KisDocument* pDoc = d->activeView->document();
/** if document has a file name, take file name and replace extension with .pdf */
if (pDoc && pDoc->url().isValid()) {
startUrl = pDoc->url();
QString fileName = startUrl.toLocalFile();
fileName = fileName.replace( QRegExp( "\\.\\w{2,5}$", Qt::CaseInsensitive ), ".pdf" );
startUrl = startUrl.adjusted(QUrl::RemoveFilename);
startUrl.setPath(startUrl.path() + fileName );
}
QPointer layoutDlg(new KoPageLayoutDialog(this, pageLayout));
layoutDlg->setWindowModality(Qt::WindowModal);
if (layoutDlg->exec() != QDialog::Accepted || !layoutDlg) {
delete layoutDlg;
return 0;
}
pageLayout = layoutDlg->pageLayout();
delete layoutDlg;
KoFileDialog dialog(this, KoFileDialog::SaveFile, "OpenDocument");
dialog.setCaption(i18n("Export as PDF"));
dialog.setDefaultDir(startUrl.toLocalFile());
dialog.setMimeTypeFilters(QStringList() << "application/pdf");
QUrl url = QUrl::fromUserInput(dialog.filename());
pdfFileName = url.toLocalFile();
if (pdfFileName.isEmpty())
return 0;
}
KisPrintJob *printJob = activeView()->createPrintJob();
if (printJob == 0)
return 0;
if (isHidden()) {
printJob->setProperty("noprogressdialog", true);
}
applyDefaultSettings(printJob->printer());
// TODO for remote files we have to first save locally and then upload.
printJob->printer().setOutputFileName(pdfFileName);
printJob->printer().setDocName(pdfFileName);
printJob->printer().setColorMode(QPrinter::Color);
if (pageLayout.format == KoPageFormat::CustomSize) {
printJob->printer().setPaperSize(QSizeF(pageLayout.width, pageLayout.height), QPrinter::Millimeter);
} else {
printJob->printer().setPaperSize(KoPageFormat::printerPageSize(pageLayout.format));
}
printJob->printer().setPageMargins(pageLayout.leftMargin, pageLayout.topMargin, pageLayout.rightMargin, pageLayout.bottomMargin, QPrinter::Millimeter);
switch (pageLayout.orientation) {
case KoPageFormat::Portrait:
printJob->printer().setOrientation(QPrinter::Portrait);
break;
case KoPageFormat::Landscape:
printJob->printer().setOrientation(QPrinter::Landscape);
break;
}
//before printing check if the printer can handle printing
if (!printJob->canPrint()) {
QMessageBox::critical(this, i18nc("@title:window", "Krita"), i18n("Cannot export to the specified file"));
}
printJob->startPrinting(KisPrintJob::DeleteWhenDone);
return printJob;
}
void KisMainWindow::importAnimation()
{
if (!activeView()) return;
KisDocument *document = activeView()->document();
if (!document) return;
KisDlgImportImageSequence dlg(this, document);
if (dlg.exec() == QDialog::Accepted) {
QStringList files = dlg.files();
int firstFrame = dlg.firstFrame();
int step = dlg.step();
document->setFileProgressProxy();
document->setFileProgressUpdater(i18n("Import frames"));
KisAnimationImporter importer(document);
KisImportExportFilter::ConversionStatus status = importer.import(files, firstFrame, step);
document->clearFileProgressUpdater();
document->clearFileProgressProxy();
if (status != KisImportExportFilter::OK && status != KisImportExportFilter::InternalError) {
QString msg = KisImportExportFilter::conversionStatusString(status);
if (!msg.isEmpty())
QMessageBox::critical(0, i18nc("@title:window", "Krita"), i18n("Could not finish import animation:\n%1", msg));
}
activeView()->canvasBase()->refetchDataFromImage();
}
}
void KisMainWindow::slotConfigureToolbars()
{
KConfigGroup group = KSharedConfig::openConfig()->group("krita");
saveMainWindowSettings(group);
KEditToolBar edit(factory(), this);
connect(&edit, SIGNAL(newToolBarConfig()), this, SLOT(slotNewToolbarConfig()));
(void) edit.exec();
applyToolBarLayout();
}
void KisMainWindow::slotNewToolbarConfig()
{
applyMainWindowSettings(KSharedConfig::openConfig()->group("krita"));
KXMLGUIFactory *factory = guiFactory();
Q_UNUSED(factory);
// Check if there's an active view
if (!d->activeView)
return;
plugActionList("toolbarlist", d->toolbarList);
applyToolBarLayout();
}
void KisMainWindow::slotToolbarToggled(bool toggle)
{
//dbgUI <<"KisMainWindow::slotToolbarToggled" << sender()->name() <<" toggle=" << true;
// The action (sender) and the toolbar have the same name
KToolBar * bar = toolBar(sender()->objectName());
if (bar) {
if (toggle) {
bar->show();
}
else {
bar->hide();
}
if (d->activeView && d->activeView->document()) {
KConfigGroup group = KSharedConfig::openConfig()->group("krita");
saveMainWindowSettings(group);
}
} else
warnUI << "slotToolbarToggled : Toolbar " << sender()->objectName() << " not found!";
}
void KisMainWindow::viewFullscreen(bool fullScreen)
{
KisConfig cfg;
cfg.setFullscreenMode(fullScreen);
if (fullScreen) {
setWindowState(windowState() | Qt::WindowFullScreen); // set
} else {
setWindowState(windowState() & ~Qt::WindowFullScreen); // reset
}
}
void KisMainWindow::slotProgress(int value)
{
qApp->processEvents();
StdLockableWrapper wrapper(&d->progressMutex);
std::unique_lock> l(wrapper, std::try_to_lock);
if (!l.owns_lock()) return;
dbgUI << "KisMainWindow::slotProgress" << value;
if (value <= -1 || value >= 100) {
if (d->progress) {
statusBar()->removeWidget(d->progress);
delete d->progress;
d->progress = 0;
disconnect(d->progressCancel, SIGNAL(clicked()), this, SLOT(slotProgressCanceled()));
statusBar()->removeWidget(d->progressCancel);
delete d->progressCancel;
d->progressCancel = 0;
}
d->firstTime = true;
return;
}
if (d->firstTime || !d->progress) {
// The statusbar might not even be created yet.
// So check for that first, and create it if necessary
QStatusBar *bar = findChild();
if (!bar) {
statusBar()->show();
QApplication::sendPostedEvents(this, QEvent::ChildAdded);
}
if (d->progress) {
statusBar()->removeWidget(d->progress);
delete d->progress;
d->progress = 0;
disconnect(d->progressCancel, SIGNAL(clicked()), this, SLOT(slotProgressCanceled()));
statusBar()->removeWidget(d->progressCancel);
delete d->progressCancel;
d->progress = 0;
}
d->progressCancel = new QToolButton(statusBar());
d->progressCancel->setMaximumHeight(statusBar()->fontMetrics().height());
d->progressCancel->setIcon(KisIconUtils::loadIcon("process-stop"));
statusBar()->addPermanentWidget(d->progressCancel);
d->progress = new QProgressBar(statusBar());
d->progress->setMaximumHeight(statusBar()->fontMetrics().height());
d->progress->setRange(0, 100);
statusBar()->addPermanentWidget(d->progress);
connect(d->progressCancel, SIGNAL(clicked()), this, SLOT(slotProgressCanceled()));
d->progress->show();
d->progressCancel->show();
d->firstTime = false;
}
if (!d->progress.isNull()) {
d->progress->setValue(value);
}
qApp->processEvents();
}
void KisMainWindow::slotProgressCanceled()
{
emit sigProgressCanceled();
}
void KisMainWindow::setMaxRecentItems(uint _number)
{
d->recentFiles->setMaxItems(_number);
}
void KisMainWindow::slotReloadFile()
{
KisDocument* document = d->activeView->document();
if (!document || document->url().isEmpty())
return;
if (document->isModified()) {
bool ok = QMessageBox::question(this,
i18nc("@title:window", "Krita"),
i18n("You will lose all changes made since your last save\n"
"Do you want to continue?"),
QMessageBox::Yes | QMessageBox::No, QMessageBox::Yes) == QMessageBox::Yes;
if (!ok)
return;
}
QUrl url = document->url();
saveWindowSettings();
if (!document->reload()) {
QMessageBox::critical(this, i18nc("@title:window", "Krita"), i18n("Error: Could not reload this document"));
}
return;
}
QDockWidget* KisMainWindow::createDockWidget(KoDockFactoryBase* factory)
{
QDockWidget* dockWidget = 0;
if (!d->dockWidgetsMap.contains(factory->id())) {
dockWidget = factory->createDockWidget();
// It is quite possible that a dock factory cannot create the dock; don't
// do anything in that case.
if (!dockWidget) {
warnKrita << "Could not create docker for" << factory->id();
return 0;
}
KoDockWidgetTitleBar *titleBar = dynamic_cast(dockWidget->titleBarWidget());
// Check if the dock widget is supposed to be collapsable
if (!dockWidget->titleBarWidget()) {
titleBar = new KoDockWidgetTitleBar(dockWidget);
dockWidget->setTitleBarWidget(titleBar);
titleBar->setCollapsable(factory->isCollapsable());
}
titleBar->setFont(KoDockRegistry::dockFont());
dockWidget->setObjectName(factory->id());
dockWidget->setParent(this);
if (dockWidget->widget() && dockWidget->widget()->layout())
dockWidget->widget()->layout()->setContentsMargins(1, 1, 1, 1);
Qt::DockWidgetArea side = Qt::RightDockWidgetArea;
bool visible = true;
switch (factory->defaultDockPosition()) {
case KoDockFactoryBase::DockTornOff:
dockWidget->setFloating(true); // position nicely?
break;
case KoDockFactoryBase::DockTop:
side = Qt::TopDockWidgetArea; break;
case KoDockFactoryBase::DockLeft:
side = Qt::LeftDockWidgetArea; break;
case KoDockFactoryBase::DockBottom:
side = Qt::BottomDockWidgetArea; break;
case KoDockFactoryBase::DockRight:
side = Qt::RightDockWidgetArea; break;
case KoDockFactoryBase::DockMinimized:
default:
side = Qt::RightDockWidgetArea;
visible = false;
}
KConfigGroup group = KSharedConfig::openConfig()->group("krita").group("DockWidget " + factory->id());
side = static_cast(group.readEntry("DockArea", static_cast(side)));
if (side == Qt::NoDockWidgetArea) side = Qt::RightDockWidgetArea;
addDockWidget(side, dockWidget);
if (!visible) {
dockWidget->hide();
}
bool collapsed = factory->defaultCollapsed();
bool locked = false;
group = KSharedConfig::openConfig()->group("krita").group("DockWidget " + factory->id());
collapsed = group.readEntry("Collapsed", collapsed);
locked = group.readEntry("Locked", locked);
//dbgKrita << "docker" << factory->id() << dockWidget << "collapsed" << collapsed << "locked" << locked << "titlebar" << titleBar;
if (titleBar && collapsed)
titleBar->setCollapsed(true);
if (titleBar && locked)
titleBar->setLocked(true);
d->dockWidgetsMap.insert(factory->id(), dockWidget);
}
else {
dockWidget = d->dockWidgetsMap[factory->id()];
}
#ifdef Q_OS_OSX
dockWidget->setAttribute(Qt::WA_MacSmallSize, true);
#endif
dockWidget->setFont(KoDockRegistry::dockFont());
connect(dockWidget, SIGNAL(dockLocationChanged(Qt::DockWidgetArea)), this, SLOT(forceDockTabFonts()));
return dockWidget;
}
void KisMainWindow::forceDockTabFonts()
{
Q_FOREACH (QObject *child, children()) {
if (child->inherits("QTabBar")) {
((QTabBar *)child)->setFont(KoDockRegistry::dockFont());
}
}
}
QList KisMainWindow::dockWidgets() const
{
return d->dockWidgetsMap.values();
}
QDockWidget* KisMainWindow::dockWidget(const QString &id)
{
if (!d->dockWidgetsMap.contains(id)) return 0;
return d->dockWidgetsMap[id];
}
QList KisMainWindow::canvasObservers() const
{
QList observers;
Q_FOREACH (QDockWidget *docker, dockWidgets()) {
KoCanvasObserverBase *observer = dynamic_cast(docker);
if (observer) {
observers << observer;
}
else {
warnKrita << docker << "is not a canvas observer";
}
}
return observers;
}
void KisMainWindow::toggleDockersVisibility(bool visible)
{
if (!visible) {
d->dockerStateBeforeHiding = saveState();
Q_FOREACH (QObject* widget, children()) {
if (widget->inherits("QDockWidget")) {
QDockWidget* dw = static_cast(widget);
if (dw->isVisible()) {
dw->hide();
}
}
}
}
else {
restoreState(d->dockerStateBeforeHiding);
}
}
void KisMainWindow::setToolbarList(QList toolbarList)
{
qDeleteAll(d->toolbarList);
d->toolbarList = toolbarList;
}
void KisMainWindow::slotDocumentTitleModified(const QString &caption, bool mod)
{
updateCaption();
updateCaption(caption, mod);
updateReloadFileAction(d->activeView ? d->activeView->document() : 0);
}
void KisMainWindow::subWindowActivated()
{
bool enabled = (activeKisView() != 0);
d->mdiCascade->setEnabled(enabled);
d->mdiNextWindow->setEnabled(enabled);
d->mdiPreviousWindow->setEnabled(enabled);
d->mdiTile->setEnabled(enabled);
d->close->setEnabled(enabled);
d->closeAll->setEnabled(enabled);
setActiveSubWindow(d->mdiArea->activeSubWindow());
Q_FOREACH (QToolBar *tb, toolBars()) {
if (tb->objectName() == "BrushesAndStuff") {
tb->setEnabled(enabled);
}
}
updateCaption();
d->actionManager()->updateGUI();
}
void KisMainWindow::updateWindowMenu()
{
QMenu *menu = d->windowMenu->menu();
menu->clear();
menu->addAction(d->newWindow);
menu->addAction(d->documentMenu);
QMenu *docMenu = d->documentMenu->menu();
docMenu->clear();
Q_FOREACH (QPointer doc, KisPart::instance()->documents()) {
if (doc) {
QString title = doc->url().toDisplayString();
if (title.isEmpty() && doc->image()) {
title = doc->image()->objectName();
}
QAction *action = docMenu->addAction(title);
action->setIcon(qApp->windowIcon());
connect(action, SIGNAL(triggered()), d->documentMapper, SLOT(map()));
d->documentMapper->setMapping(action, doc);
}
}
menu->addSeparator();
menu->addAction(d->close);
menu->addAction(d->closeAll);
if (d->mdiArea->viewMode() == QMdiArea::SubWindowView) {
menu->addSeparator();
menu->addAction(d->mdiTile);
menu->addAction(d->mdiCascade);
}
menu->addSeparator();
menu->addAction(d->mdiNextWindow);
menu->addAction(d->mdiPreviousWindow);
menu->addSeparator();
QList windows = d->mdiArea->subWindowList();
for (int i = 0; i < windows.size(); ++i) {
QPointerchild = qobject_cast(windows.at(i)->widget());
if (child && child->document()) {
QString text;
if (i < 9) {
text = i18n("&%1 %2", i + 1, child->document()->url().toDisplayString());
}
else {
text = i18n("%1 %2", i + 1, child->document()->url().toDisplayString());
}
QAction *action = menu->addAction(text);
action->setIcon(qApp->windowIcon());
action->setCheckable(true);
action->setChecked(child == activeKisView());
connect(action, SIGNAL(triggered()), d->windowMapper, SLOT(map()));
d->windowMapper->setMapping(action, windows.at(i));
}
}
updateCaption();
}
void KisMainWindow::setActiveSubWindow(QWidget *window)
{
if (!window) return;
QMdiSubWindow *subwin = qobject_cast(window);
//dbgKrita << "setActiveSubWindow();" << subwin << d->activeSubWindow;
if (subwin && subwin != d->activeSubWindow) {
KisView *view = qobject_cast(subwin->widget());
//dbgKrita << "\t" << view << activeView();
if (view && view != activeView()) {
d->mdiArea->setActiveSubWindow(subwin);
setActiveView(view);
}
d->activeSubWindow = subwin;
}
updateWindowMenu();
d->actionManager()->updateGUI();
}
void KisMainWindow::configChanged()
{
KisConfig cfg;
QMdiArea::ViewMode viewMode = (QMdiArea::ViewMode)cfg.readEntry("mdi_viewmode", (int)QMdiArea::TabbedView);
d->mdiArea->setViewMode(viewMode);
Q_FOREACH (QMdiSubWindow *subwin, d->mdiArea->subWindowList()) {
subwin->setOption(QMdiSubWindow::RubberBandMove, cfg.readEntry("mdi_rubberband", cfg.useOpenGL()));
subwin->setOption(QMdiSubWindow::RubberBandResize, cfg.readEntry("mdi_rubberband", cfg.useOpenGL()));
}
KConfigGroup group( KSharedConfig::openConfig(), "theme");
d->themeManager->setCurrentTheme(group.readEntry("Theme", "Krita dark"));
d->actionManager()->updateGUI();
QBrush brush(cfg.getMDIBackgroundColor());
d->mdiArea->setBackground(brush);
QString backgroundImage = cfg.getMDIBackgroundImage();
if (backgroundImage != "") {
QImage image(backgroundImage);
QBrush brush(image);
d->mdiArea->setBackground(brush);
}
d->mdiArea->update();
}
void KisMainWindow::newView(QObject *document)
{
KisDocument *doc = qobject_cast(document);
addViewAndNotifyLoadingCompleted(doc);
d->actionManager()->updateGUI();
}
void KisMainWindow::newWindow()
{
KisPart::instance()->createMainWindow()->show();
}
void KisMainWindow::closeCurrentWindow()
{
d->mdiArea->currentSubWindow()->close();
d->actionManager()->updateGUI();
}
void KisMainWindow::checkSanity()
{
// print error if the lcms engine is not available
if (!KoColorSpaceEngineRegistry::instance()->contains("icc")) {
// need to wait 1 event since exiting here would not work.
m_errorMessage = i18n("The Krita LittleCMS color management plugin is not installed. Krita will quit now.");
m_dieOnError = true;
QTimer::singleShot(0, this, SLOT(showErrorAndDie()));
return;
}
KisPaintOpPresetResourceServer * rserver = KisResourceServerProvider::instance()->paintOpPresetServer();
if (rserver->resources().isEmpty()) {
m_errorMessage = i18n("Krita cannot find any brush presets! Krita will quit now.");
m_dieOnError = true;
QTimer::singleShot(0, this, SLOT(showErrorAndDie()));
return;
}
}
void KisMainWindow::showErrorAndDie()
{
QMessageBox::critical(0, i18nc("@title:window", "Installation error"), m_errorMessage);
if (m_dieOnError) {
exit(10);
}
}
void KisMainWindow::showAboutApplication()
{
KisAboutApplication dlg(this);
dlg.exec();
}
QPointerKisMainWindow::activeKisView()
{
if (!d->mdiArea) return 0;
QMdiSubWindow *activeSubWindow = d->mdiArea->activeSubWindow();
//dbgKrita << "activeKisView" << activeSubWindow;
if (!activeSubWindow) return 0;
return qobject_cast(activeSubWindow->widget());
}
void KisMainWindow::newOptionWidgets(KoCanvasController *controller, const QList > &optionWidgetList)
{
KIS_ASSERT_RECOVER_NOOP(controller == KoToolManager::instance()->activeCanvasController());
bool isOurOwnView = false;
Q_FOREACH (QPointer view, KisPart::instance()->views()) {
if (view && view->canvasController() == controller) {
isOurOwnView = view->mainWindow() == this;
}
}
if (!isOurOwnView) return;
Q_FOREACH (QWidget *w, optionWidgetList) {
#ifdef Q_OS_OSX
w->setAttribute(Qt::WA_MacSmallSize, true);
#endif
w->setFont(KoDockRegistry::dockFont());
}
if (d->toolOptionsDocker) {
d->toolOptionsDocker->setOptionWidgets(optionWidgetList);
}
else {
d->viewManager->paintOpBox()->newOptionWidgets(optionWidgetList);
}
}
void KisMainWindow::applyDefaultSettings(QPrinter &printer) {
if (!d->activeView) return;
QString title = d->activeView->document()->documentInfo()->aboutInfo("title");
if (title.isEmpty()) {
title = d->activeView->document()->url().fileName();
// strip off the native extension (I don't want foobar.kwd.ps when printing into a file)
QString extension = KisMimeDatabase::suffixesForMimeType(d->activeView->document()->outputMimeType()).first();
if (title.endsWith(extension)) {
title.chop(extension.length());
}
}
if (title.isEmpty()) {
// #139905
title = i18n("%1 unsaved document (%2)", qApp->applicationDisplayName(),
QLocale().toString(QDate::currentDate(), QLocale::ShortFormat));
}
printer.setDocName(title);
}
void KisMainWindow::createActions()
{
KisActionManager *actionManager = d->actionManager();
KisConfig cfg;
actionManager->createStandardAction(KStandardAction::New, this, SLOT(slotFileNew()));
actionManager->createStandardAction(KStandardAction::Open, this, SLOT(slotFileOpen()));
actionManager->createStandardAction(KStandardAction::Quit, this, SLOT(slotFileQuit()));
actionManager->createStandardAction(KStandardAction::ConfigureToolbars, this, SLOT(slotConfigureToolbars()));
actionManager->createStandardAction(KStandardAction::FullScreen, this, SLOT(viewFullscreen(bool)));
d->recentFiles = KStandardAction::openRecent(this, SLOT(slotFileOpenRecent(QUrl)), actionCollection());
connect(d->recentFiles, SIGNAL(recentListCleared()), this, SLOT(saveRecentFiles()));
KSharedConfigPtr configPtr = KSharedConfig::openConfig();
d->recentFiles->loadEntries(configPtr->group("RecentFiles"));
d->saveAction = actionManager->createStandardAction(KStandardAction::Save, this, SLOT(slotFileSave()));
d->saveAction->setActivationFlags(KisAction::ACTIVE_IMAGE);
d->saveActionAs = actionManager->createStandardAction(KStandardAction::SaveAs, this, SLOT(slotFileSaveAs()));
d->saveActionAs->setActivationFlags(KisAction::ACTIVE_IMAGE);
// d->printAction = actionManager->createStandardAction(KStandardAction::Print, this, SLOT(slotFilePrint()));
// d->printAction->setActivationFlags(KisAction::ACTIVE_IMAGE);
// d->printActionPreview = actionManager->createStandardAction(KStandardAction::PrintPreview, this, SLOT(slotFilePrintPreview()));
// d->printActionPreview->setActivationFlags(KisAction::ACTIVE_IMAGE);
d->undo = actionManager->createStandardAction(KStandardAction::Undo, this, SLOT(undo()));
d->undo ->setActivationFlags(KisAction::ACTIVE_IMAGE);
d->redo = actionManager->createStandardAction(KStandardAction::Redo, this, SLOT(redo()));
d->redo->setActivationFlags(KisAction::ACTIVE_IMAGE);
// d->exportPdf = actionManager->createAction("file_export_pdf");
// connect(d->exportPdf, SIGNAL(triggered()), this, SLOT(exportToPdf()));
d->importAnimation = actionManager->createAction("file_import_animation");
connect(d->importAnimation, SIGNAL(triggered()), this, SLOT(importAnimation()));
d->closeAll = actionManager->createAction("file_close_all");
connect(d->closeAll, SIGNAL(triggered()), this, SLOT(slotFileCloseAll()));
// d->reloadFile = actionManager->createAction("file_reload_file");
// d->reloadFile->setActivationFlags(KisAction::CURRENT_IMAGE_MODIFIED);
// connect(d->reloadFile, SIGNAL(triggered(bool)), this, SLOT(slotReloadFile()));
d->importFile = actionManager->createAction("file_import_file");
connect(d->importFile, SIGNAL(triggered(bool)), this, SLOT(slotImportFile()));
d->exportFile = actionManager->createAction("file_export_file");
connect(d->exportFile, SIGNAL(triggered(bool)), this, SLOT(slotExportFile()));
/* The following entry opens the document information dialog. Since the action is named so it
intends to show data this entry should not have a trailing ellipses (...). */
d->showDocumentInfo = actionManager->createAction("file_documentinfo");
connect(d->showDocumentInfo, SIGNAL(triggered(bool)), this, SLOT(slotDocumentInfo()));
d->themeManager->setThemeMenuAction(new KActionMenu(i18nc("@action:inmenu", "&Themes"), this));
d->themeManager->registerThemeActions(actionCollection());
connect(d->themeManager, SIGNAL(signalThemeChanged()), this, SLOT(slotThemeChanged()));
d->toggleDockers = actionManager->createAction("view_toggledockers");
cfg.showDockers(true);
d->toggleDockers->setChecked(true);
connect(d->toggleDockers, SIGNAL(toggled(bool)), SLOT(toggleDockersVisibility(bool)));
d->toggleDockerTitleBars = actionManager->createAction("view_toggledockertitlebars");
d->toggleDockerTitleBars->setChecked(cfg.showDockerTitleBars());
connect(d->toggleDockerTitleBars, SIGNAL(toggled(bool)), SLOT(showDockerTitleBars(bool)));
actionCollection()->addAction("settings_dockers_menu", d->dockWidgetMenu);
actionCollection()->addAction("window", d->windowMenu);
d->mdiCascade = actionManager->createAction("windows_cascade");
connect(d->mdiCascade, SIGNAL(triggered()), d->mdiArea, SLOT(cascadeSubWindows()));
d->mdiTile = actionManager->createAction("windows_tile");
connect(d->mdiTile, SIGNAL(triggered()), d->mdiArea, SLOT(tileSubWindows()));
d->mdiNextWindow = actionManager->createAction("windows_next");
connect(d->mdiNextWindow, SIGNAL(triggered()), d->mdiArea, SLOT(activateNextSubWindow()));
d->mdiPreviousWindow = actionManager->createAction("windows_previous");
connect(d->mdiPreviousWindow, SIGNAL(triggered()), d->mdiArea, SLOT(activatePreviousSubWindow()));
d->newWindow = actionManager->createAction("view_newwindow");
connect(d->newWindow, SIGNAL(triggered(bool)), this, SLOT(newWindow()));
d->close = actionManager->createAction("file_close");
connect(d->close, SIGNAL(triggered()), SLOT(closeCurrentWindow()));
actionManager->createStandardAction(KStandardAction::Preferences, this, SLOT(slotPreferences()));
for (int i = 0; i < 2; i++) {
d->expandingSpacers[i] = new KisAction(i18n("Expanding Spacer"));
d->expandingSpacers[i]->setDefaultWidget(new QWidget(this));
d->expandingSpacers[i]->defaultWidget()->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
actionManager->addAction(QString("expanding_spacer_%1").arg(i), d->expandingSpacers[i]);
}
}
void KisMainWindow::applyToolBarLayout()
{
const bool isPlastiqueStyle = style()->objectName() == "plastique";
Q_FOREACH (KToolBar *toolBar, toolBars()) {
toolBar->layout()->setSpacing(4);
if (isPlastiqueStyle) {
toolBar->setContentsMargins(0, 0, 0, 2);
}
//Hide text for buttons with an icon in the toolbar
Q_FOREACH (QAction *ac, toolBar->actions()){
if (ac->icon().pixmap(QSize(1,1)).isNull() == false){
ac->setPriority(QAction::LowPriority);
}else {
ac->setIcon(QIcon());
}
}
}
}
void KisMainWindow::initializeGeometry()
{
// if the user didn's specify the geometry on the command line (does anyone do that still?),
// we first figure out some good default size and restore the x,y position. See bug 285804Z.
KConfigGroup cfg( KSharedConfig::openConfig(), "MainWindow");
QByteArray geom = QByteArray::fromBase64(cfg.readEntry("ko_geometry", QByteArray()));
if (!restoreGeometry(geom)) {
const int scnum = QApplication::desktop()->screenNumber(parentWidget());
QRect desk = QApplication::desktop()->availableGeometry(scnum);
// if the desktop is virtual then use virtual screen size
if (QApplication::desktop()->isVirtualDesktop()) {
desk = QApplication::desktop()->availableGeometry(QApplication::desktop()->screen(scnum));
}
quint32 x = desk.x();
quint32 y = desk.y();
quint32 w = 0;
quint32 h = 0;
// Default size -- maximize on small screens, something useful on big screens
const int deskWidth = desk.width();
if (deskWidth > 1024) {
// a nice width, and slightly less than total available
// height to componensate for the window decs
w = (deskWidth / 3) * 2;
h = (desk.height() / 3) * 2;
}
else {
w = desk.width();
h = desk.height();
}
x += (desk.width() - w) / 2;
y += (desk.height() - h) / 2;
move(x,y);
setGeometry(geometry().x(), geometry().y(), w, h);
}
restoreWorkspace(QByteArray::fromBase64(cfg.readEntry("ko_windowstate", QByteArray())));
}
void KisMainWindow::showManual()
{
QDesktopServices::openUrl(QUrl("https://docs.krita.org"));
}
void KisMainWindow::showDockerTitleBars(bool show)
{
Q_FOREACH (QDockWidget *dock, dockWidgets()) {
if (dock->titleBarWidget()) {
const bool isCollapsed = (dock->widget() && dock->widget()->isHidden()) || !dock->widget();
dock->titleBarWidget()->setVisible(show || (dock->isFloating() && isCollapsed));
}
}
KisConfig cfg;
cfg.setShowDockerTitleBars(show);
}
void KisMainWindow::moveEvent(QMoveEvent *e)
{
if (qApp->desktop()->screenNumber(this) != qApp->desktop()->screenNumber(e->oldPos())) {
KisConfigNotifier::instance()->notifyConfigChanged();
}
}
#include
diff --git a/libs/ui/KisMainWindow.h b/libs/ui/KisMainWindow.h
index 91d00e86f5..fc034fb6ee 100644
--- a/libs/ui/KisMainWindow.h
+++ b/libs/ui/KisMainWindow.h
@@ -1,470 +1,471 @@
/* This file is part of the KDE project
Copyright (C) 1998, 1999 Torben Weis
Copyright (C) 2000-2004 David Faure
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Library General Public
License as published by the Free Software Foundation; either
version 2 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Library General Public License for more details.
You should have received a copy of the GNU Library General Public License
along with this library; see the file COPYING.LIB. If not, write to
the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
Boston, MA 02110-1301, USA.
*/
#ifndef KIS_MAIN_WINDOW_H
#define KIS_MAIN_WINDOW_H
#include "kritaui_export.h"
#include
#include
#include
#include
#include
#include
#include "KisView.h"
class QCloseEvent;
class QMoveEvent;
struct KoPageLayout;
class KoCanvasResourceManager;
class KisDocument;
class KisPrintJob;
class KoDockFactoryBase;
class QDockWidget;
class KisView;
class KisViewManager;
class KoCanvasController;
/**
* @brief Main window for Krita
*
* This class is used to represent a main window within a Krita session. Each
* main window contains a menubar and some toolbars, and potentially several
* views of several canvases.
*
*/
class KRITAUI_EXPORT KisMainWindow : public KXmlGuiWindow, public KoCanvasSupervisor
{
Q_OBJECT
public:
enum OpenFlag {
- None,
- Import,
- BatchMode
+ None = 0,
+ Import = 0x1,
+ BatchMode = 0x2,
+ RecoveryFile = 0x4
};
Q_DECLARE_FLAGS(OpenFlags, OpenFlag)
public:
/**
* Constructor.
*
* Initializes a Calligra main window (with its basic GUI etc.).
*/
explicit KisMainWindow();
/**
* Destructor.
*/
virtual ~KisMainWindow();
/**
* Update caption from document info - call when document info
* (title in the about page) changes.
*/
void updateCaption();
// If noCleanup is set, KisMainWindow will not delete the root document
// or part manager on destruction.
void setNoCleanup(bool noCleanup);
/**
* @brief showView shows the given view. Override this if you want to show
* the view in a different way than by making it the central widget, for instance
* as an QMdiSubWindow
*/
virtual void showView(KisView *view);
/**
* @returns the currently active view
*/
KisView *activeView() const;
/**
* Sets the maximum number of recent documents entries.
*/
void setMaxRecentItems(uint _number);
/**
* The document opened a URL -> store into recent documents list.
*/
void addRecentURL(const QUrl &url);
/**
* Load the desired document and show it.
* @param url the URL to open
*
* @return TRUE on success.
*/
bool openDocument(const QUrl &url, OpenFlags flags);
/**
* Saves the document, asking for a filename if necessary.
*
* @param saveas if set to TRUE the user is always prompted for a filename
* @param silent if set to TRUE rootDocument()->setTitleModified will not be called.
*
* @return TRUE on success, false on error or cancel
* (don't display anything in this case, the error dialog box is also implemented here
* but restore the original URL in slotFileSaveAs)
*/
bool saveDocument(KisDocument *document, bool saveas, bool isExporting);
void setReadWrite(bool readwrite);
/// Return the list of dock widgets belonging to this main window.
QList dockWidgets() const;
QDockWidget* dockWidget(const QString &id);
QList canvasObservers() const;
KoCanvasResourceManager *resourceManager() const;
int viewCount() const;
/**
* A wrapper around restoreState
* @param state the saved state
* @return TRUE on success
*/
bool restoreWorkspace(const QByteArray &state);
KisViewManager *viewManager() const;
void addViewAndNotifyLoadingCompleted(KisDocument *document);
QStringList showOpenFileDialog(bool isImporting);
/**
* Shows if the main window is saving anything right now. If the
* user presses Ctrl+W too fast, then the document can be close
* before the saving is completed. I'm not sure if it is fixable
* in any way without avoiding using porcessEvents()
* everywhere (DK)
*
* Don't use it unless you have no option.
*/
bool hackIsSaving() const;
Q_SIGNALS:
/**
* This signal is emitted if the document has been saved successfully.
*/
void documentSaved();
/// This signal is emitted when this windows has finished loading of a
/// document. The document may be opened in another window in the end.
/// In this case, the signal means there is no link between the window
/// and the document anymore.
void loadCompleted();
/// This signal is emitted right after the docker states have been succefully restored from config
void restoringDone();
/// This signal is emitted when the color theme changes
void themeChanged();
/// This signal is emitted when the shortcut key configuration has changed
void keyBindingsChanged();
void guiLoadingFinished();
/// This signal is emitted when the user clicked on the progressbar cancel
void sigProgressCanceled();
public Q_SLOTS:
/**
* Slot for opening a new document.
*
* If the current document is empty, the new document replaces it.
* If not, a new mainwindow will be opened for showing the document.
*/
void slotFileNew();
/**
* Slot for opening a saved file.
*
* If the current document is empty, the opened document replaces it.
* If not a new mainwindow will be opened for showing the opened file.
*/
void slotFileOpen(bool isImporting = false);
/**
* Slot for opening a file among the recently opened files.
*
* If the current document is empty, the opened document replaces it.
* If not a new mainwindow will be opened for showing the opened file.
*/
void slotFileOpenRecent(const QUrl &);
/**
* @brief slotPreferences open the preferences dialog
*/
void slotPreferences();
/**
* Saves the current document with the current name.
*/
void slotFileSave();
// XXX: disabled
KisPrintJob* exportToPdf(QString pdfFileName = QString());
void slotProgress(int value);
/**
* Update the option widgets to the argument ones, removing the currently set widgets.
*/
void newOptionWidgets(KoCanvasController *controller, const QList > & optionWidgetList);
private Q_SLOTS:
/**
* Save the list of recent files.
*/
void saveRecentFiles();
void slotLoadCompleted();
void slotLoadCanceled(const QString &);
void slotSaveCompleted();
void slotSaveCanceled(const QString &);
void forceDockTabFonts();
void slotProgressCanceled();
/**
* @internal
*/
void slotDocumentTitleModified(const QString &caption, bool mod);
/**
* Prints the actual document.
*/
void slotFilePrint();
/**
* Saves the current document with a new name.
*/
void slotFileSaveAs();
void slotFilePrintPreview();
void importAnimation();
/**
* Show a dialog with author and document information.
*/
void slotDocumentInfo();
/**
* Closes all open documents.
*/
bool slotFileCloseAll();
/**
* @brief showAboutApplication show the about box
*/
virtual void showAboutApplication();
/**
* Closes the mainwindow.
*/
void slotFileQuit();
/**
* Configure toolbars.
*/
void slotConfigureToolbars();
/**
* Post toolbar config.
* (Plug action lists back in, etc.)
*/
void slotNewToolbarConfig();
/**
* Shows or hides a toolbar
*/
void slotToolbarToggled(bool toggle);
/**
* Toggle full screen on/off.
*/
void viewFullscreen(bool fullScreen);
/**
* Toggle docker titlebars on/off.
*/
void showDockerTitleBars(bool show);
/**
* Reload file
*/
void slotReloadFile();
/**
* File --> Import
*
* This will call slotFileOpen().
*/
void slotImportFile();
/**
* File --> Export
*
* This will call slotFileSaveAs().
*/
void slotExportFile();
/**
* Hide the dockers
*/
void toggleDockersVisibility(bool visible);
/**
* Handle theme changes from theme manager
*/
void slotThemeChanged();
void undo();
void redo();
void updateWindowMenu();
void setActiveSubWindow(QWidget *window);
void configChanged();
void newView(QObject *document);
void newWindow();
void closeCurrentWindow();
void checkSanity();
/// Quits Krita with error message from m_errorMessage.
void showErrorAndDie();
protected:
void closeEvent(QCloseEvent * e);
void resizeEvent(QResizeEvent * e);
// QWidget overrides
virtual void dragEnterEvent(QDragEnterEvent * event);
virtual void dropEvent(QDropEvent * event);
virtual void dragMoveEvent(QDragMoveEvent * event);
virtual void dragLeaveEvent(QDragLeaveEvent * event);
void setToolbarList(QList toolbarList);
public Q_SLOTS:
/**
* Add a the given view to the list of views of this mainwindow.
* This is a private implementation. For public usage please use
* newView() and addViewAndNotifyLoadingCompleted().
*/
void addView(KisView *view);
/// Set the active view, this will update the undo/redo actions
void setActiveView(KisView *view);
void subWindowActivated();
private:
friend class KisApplication;
/**
* Returns the dockwidget specified by the @p factory. If the dock widget doesn't exist yet it's created.
* Add a "view_palette_action_menu" action to your view menu if you want to use closable dock widgets.
* @param factory the factory used to create the dock widget if needed
* @return the dock widget specified by @p factory (may be 0)
*/
QDockWidget* createDockWidget(KoDockFactoryBase* factory);
bool openDocumentInternal(const QUrl &url, KisMainWindow::OpenFlags flags = 0);
/**
* Reloads the recent documents list.
*/
void reloadRecentFileList();
/**
* Updates the window caption based on the document info and path.
*/
void updateCaption(const QString & caption, bool mod);
void updateReloadFileAction(KisDocument *doc);
void saveWindowSettings();
QPointeractiveKisView();
void applyDefaultSettings(QPrinter &printer);
void createActions();
void applyToolBarLayout();
protected:
void moveEvent(QMoveEvent *e);
private Q_SLOTS:
void initializeGeometry();
void showManual();
void switchTab(int index);
private:
/**
* Struct used in the list created by createCustomDocumentWidgets()
*/
struct CustomDocumentWidgetItem {
/// Pointer to the custom document widget
QWidget *widget;
/// title used in the sidebar. If left empty it will be displayed as "Custom Document"
QString title;
/// icon used in the sidebar. If left empty it will use the unknown icon
QString icon;
};
class Private;
Private * const d;
QString m_errorMessage;
bool m_dieOnError;
};
Q_DECLARE_OPERATORS_FOR_FLAGS(KisMainWindow::OpenFlags)
#endif
diff --git a/libs/ui/kis_animation_importer.cpp b/libs/ui/kis_animation_importer.cpp
index dad3afda2f..db7257170e 100644
--- a/libs/ui/kis_animation_importer.cpp
+++ b/libs/ui/kis_animation_importer.cpp
@@ -1,129 +1,129 @@
/*
* Copyright (c) 2015 Jouni Pentikäinen
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include "kis_animation_importer.h"
#include
#include "KoColorSpace.h"
#include "KisPart.h"
#include "KisDocument.h"
#include "kis_image.h"
#include "kis_undo_adapter.h"
#include "kis_paint_layer.h"
#include "kis_group_layer.h"
#include "kis_raster_keyframe_channel.h"
#include "commands/kis_image_layer_add_command.h"
struct KisAnimationImporter::Private
{
KisImageSP image;
KisDocument *document;
bool stop;
};
KisAnimationImporter::KisAnimationImporter(KisImageSP image)
: m_d(new Private())
{
m_d->document= 0;
m_d->image = image;
m_d->stop = false;
}
KisAnimationImporter::KisAnimationImporter(KisDocument* document)
: m_d(new Private())
{
m_d->document= document;
m_d->image = document->image();
m_d->stop = false;
}
KisAnimationImporter::~KisAnimationImporter()
{}
KisImportExportFilter::ConversionStatus KisAnimationImporter::import(QStringList files, int firstFrame, int step)
{
Q_ASSERT(step > 0);
bool batchMode;
if (m_d->document) {
batchMode= m_d->document->fileBatchMode();
if (!batchMode) {
emit m_d->document->statusBarMessage(i18n("Import frames"));
emit m_d->document->sigProgress(0);
connect(m_d->document, SIGNAL(sigProgressCanceled()), this, SLOT(cancel()));
}
} else batchMode = false;
m_d->image->lock();
KisUndoAdapter *undo = m_d->image->undoAdapter();
undo->beginMacro(kundo2_i18n("Import animation"));
QScopedPointer importDoc(KisPart::instance()->createDocument());
importDoc->setFileBatchMode(true);
KisImportExportFilter::ConversionStatus status = KisImportExportFilter::OK;
int frame = firstFrame;
KisRasterKeyframeChannel *contentChannel = 0;
Q_FOREACH(QString file, files) {
- bool successfullyLoaded = importDoc->openUrl(QUrl::fromLocalFile(file), KisDocument::OPEN_URL_FLAG_DO_NOT_ADD_TO_RECENT_FILES);
+ bool successfullyLoaded = importDoc->openUrl(QUrl::fromLocalFile(file), KisDocument::DontAddToRecent);
if (!successfullyLoaded) {
status = KisImportExportFilter::InternalError;
break;
}
if (m_d->stop) {
status = KisImportExportFilter::ProgressCancelled;
break;
}
if (frame == firstFrame) {
const KoColorSpace *cs = importDoc->image()->colorSpace();
KisPaintLayerSP paintLayer = new KisPaintLayer(m_d->image, m_d->image->nextLayerName(), OPACITY_OPAQUE_U8, cs);
undo->addCommand(new KisImageLayerAddCommand(m_d->image, paintLayer, m_d->image->rootLayer(), m_d->image->rootLayer()->childCount()));
paintLayer->enableAnimation();
contentChannel = qobject_cast(paintLayer->getKeyframeChannel(KisKeyframeChannel::Content.id(), true));
}
if (!batchMode) {
emit m_d->document->sigProgress((frame - firstFrame) * 100 / (step * files.size()));
}
contentChannel->importFrame(frame, importDoc->image()->projection(), NULL);
frame += step;
}
if (!batchMode) {
disconnect(m_d->document, SIGNAL(sigProgressCanceled()), this, SLOT(cancel()));
emit m_d->document->sigProgress(100);
emit m_d->document->clearStatusBarMessage();
}
undo->endMacro();
m_d->image->unlock();
return status;
}
void KisAnimationImporter::cancel()
{
m_d->stop = true;
}
diff --git a/libs/ui/kis_safe_document_loader.cpp b/libs/ui/kis_safe_document_loader.cpp
index 335178de4e..eb2f6cccaa 100644
--- a/libs/ui/kis_safe_document_loader.cpp
+++ b/libs/ui/kis_safe_document_loader.cpp
@@ -1,182 +1,182 @@
/*
* Copyright (c) 2013 Dmitry Kazakov
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include "kis_safe_document_loader.h"
#include
#include
#include
#include
#include
#include
#include "KisDocument.h"
#include "kis_image.h"
#include "kis_signal_compressor.h"
#include "KisPart.h"
Q_GLOBAL_STATIC(QFileSystemWatcher, s_fileSystemWatcher)
struct KisSafeDocumentLoader::Private
{
Private()
: fileChangedSignalCompressor(500 /* ms */, KisSignalCompressor::POSTPONE),
isLoading(false),
fileChangedFlag(false)
{
}
QScopedPointer doc;
KisSignalCompressor fileChangedSignalCompressor;
QTimer delayedLoadTimer;
bool isLoading {true};
bool fileChangedFlag {false};
QString path;
QString temporaryPath;
qint64 initialFileSize;
QDateTime initialFileTimeStamp;
};
KisSafeDocumentLoader::KisSafeDocumentLoader(const QString &path, QObject *parent)
: QObject(parent),
m_d(new Private())
{
connect(s_fileSystemWatcher, SIGNAL(fileChanged(QString)),
SLOT(fileChanged(QString)));
connect(s_fileSystemWatcher, SIGNAL(fileChanged(QString)),
&m_d->fileChangedSignalCompressor, SLOT(start()));
connect(&m_d->fileChangedSignalCompressor, SIGNAL(timeout()),
SLOT(fileChangedCompressed()));
connect(&m_d->delayedLoadTimer, SIGNAL(timeout()),
SLOT(delayedLoadStart()));
m_d->delayedLoadTimer.setSingleShot(true);
m_d->delayedLoadTimer.setInterval(100 /* ms */);
setPath(path);
}
KisSafeDocumentLoader::~KisSafeDocumentLoader()
{
s_fileSystemWatcher->removePath(m_d->path);
delete m_d;
}
void KisSafeDocumentLoader::setPath(const QString &path)
{
if (path.isEmpty()) return;
if (!m_d->path.isEmpty()) {
s_fileSystemWatcher->removePath(m_d->path);
}
m_d->path = path;
s_fileSystemWatcher->addPath(m_d->path);
}
void KisSafeDocumentLoader::reloadImage()
{
fileChangedCompressed(true);
}
void KisSafeDocumentLoader::fileChanged(QString path)
{
if (path == m_d->path) {
m_d->fileChangedFlag = true;
}
}
void KisSafeDocumentLoader::fileChangedCompressed(bool sync)
{
if (m_d->isLoading) return;
QFileInfo initialFileInfo(m_d->path);
m_d->initialFileSize = initialFileInfo.size();
m_d->initialFileTimeStamp = initialFileInfo.lastModified();
// it may happen when the file is flushed by
// so other application
if (!m_d->initialFileSize) return;
m_d->isLoading = true;
m_d->fileChangedFlag = false;
m_d->temporaryPath =
QDir::tempPath() + QDir::separator() +
QString("krita_file_layer_copy_%1_%2.%3")
.arg(QApplication::applicationPid())
.arg(qrand())
.arg(initialFileInfo.suffix());
QFile::copy(m_d->path, m_d->temporaryPath);
if (!sync) {
m_d->delayedLoadTimer.start();
} else {
QApplication::processEvents();
delayedLoadStart();
}
}
void KisSafeDocumentLoader::delayedLoadStart()
{
QFileInfo originalInfo(m_d->path);
QFileInfo tempInfo(m_d->temporaryPath);
bool successfullyLoaded = false;
if (!m_d->fileChangedFlag &&
originalInfo.size() == m_d->initialFileSize &&
originalInfo.lastModified() == m_d->initialFileTimeStamp &&
tempInfo.size() == m_d->initialFileSize) {
m_d->doc.reset(KisPart::instance()->createDocument());
successfullyLoaded = m_d->doc->openUrl(QUrl::fromLocalFile(m_d->temporaryPath),
- KisDocument::OPEN_URL_FLAG_DO_NOT_ADD_TO_RECENT_FILES);
+ KisDocument::DontAddToRecent);
} else {
dbgKrita << "File was modified externally. Restarting.";
dbgKrita << ppVar(m_d->fileChangedFlag);
dbgKrita << ppVar(m_d->initialFileSize);
dbgKrita << ppVar(m_d->initialFileTimeStamp);
dbgKrita << ppVar(originalInfo.size());
dbgKrita << ppVar(originalInfo.lastModified());
dbgKrita << ppVar(tempInfo.size());
}
QFile::remove(m_d->temporaryPath);
m_d->isLoading = false;
if (!successfullyLoaded) {
// Restart the attempt
m_d->fileChangedSignalCompressor.start();
}
else {
KisPaintDeviceSP paintDevice = new KisPaintDevice(m_d->doc->image()->colorSpace());
KisPaintDeviceSP projection = m_d->doc->image()->projection();
paintDevice->makeCloneFrom(projection, projection->extent());
emit loadingFinished(paintDevice, m_d->doc->image()->xRes(), m_d->doc->image()->yRes());
}
m_d->doc.reset();
}
diff --git a/plugins/impex/csv/csv_loader.cpp b/plugins/impex/csv/csv_loader.cpp
index 4cc3f017a9..caac11481d 100644
--- a/plugins/impex/csv/csv_loader.cpp
+++ b/plugins/impex/csv/csv_loader.cpp
@@ -1,486 +1,486 @@
/*
* Copyright (c) 2016 Laszlo Fazekas
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#include "csv_loader.h"
#include
#include
#include
#include