diff --git a/libs/global/kis_dom_utils.h b/libs/global/kis_dom_utils.h
index e5b8ae3043..8b8d9d2c3e 100644
--- a/libs/global/kis_dom_utils.h
+++ b/libs/global/kis_dom_utils.h
@@ -1,274 +1,295 @@
/*
* Copyright (c) 2014 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.
*/
#ifndef __KIS_DOM_UTILS_H
#define __KIS_DOM_UTILS_H
#include
#include
#include
#include
#include
#include
+#include
#include
#include "kritaglobal_export.h"
#include "kis_debug.h"
namespace KisDomUtils {
inline QString toString(const QString &value) {
return value;
}
template
inline QString toString(T value) {
return QString::number(value);
}
inline QString toString(float value) {
QString str;
QTextStream stream;
stream.setString(&str, QIODevice::WriteOnly);
stream.setRealNumberPrecision(FLT_DIG);
stream << value;
return str;
}
inline QString toString(double value) {
QString str;
QTextStream stream;
stream.setString(&str, QIODevice::WriteOnly);
stream.setRealNumberPrecision(11);
stream << value;
return str;
}
inline int toInt(const QString &str) {
bool ok = false;
int value = 0;
QLocale c(QLocale::German);
value = str.toInt(&ok);
if (!ok) {
value = c.toInt(str, &ok);
}
if (!ok) {
warnKrita << "WARNING: KisDomUtils::toInt failed:" << ppVar(str);
value = 0;
}
return value;
}
inline double toDouble(const QString &str) {
bool ok = false;
double value = 0;
QLocale c(QLocale::German);
/**
* A special workaround to handle ','/'.' decimal point
* in different locales. Added for backward compatibility,
* because we used to save qreals directly using
*
* e.setAttribute("w", (qreal)value),
*
* which did local-aware conversion.
*/
value = str.toDouble(&ok);
if (!ok) {
value = c.toDouble(str, &ok);
}
if (!ok) {
warnKrita << "WARNING: KisDomUtils::toDouble failed:" << ppVar(str);
value = 0;
}
return value;
}
+ inline QString qColorToQString(QColor color)
+ {
+ // color channels will usually have 0-255
+ QString customColor = QString::number(color.red()).append(",")
+ .append(QString::number(color.green())).append(",")
+ .append(QString::number(color.blue())).append(",")
+ .append(QString::number(color.alpha()));
+
+ return customColor;
+ }
+
+ inline QColor qStringToQColor(QString colorString)
+ {
+ QStringList colorComponents = colorString.split(',');
+ return QColor(colorComponents[0].toInt(), colorComponents[1].toInt(), colorComponents[2].toInt(), colorComponents[3].toInt());
+ }
+
+
+
+
/**
* Save a value of type QRect into an XML tree. A child for \p parent
* is created and assigned a tag \p tag. The corresponding value can
* be fetched from the XML using loadValue() later.
*
* \see loadValue()
*/
void KRITAGLOBAL_EXPORT saveValue(QDomElement *parent, const QString &tag, const QRect &rc);
void KRITAGLOBAL_EXPORT saveValue(QDomElement *parent, const QString &tag, const QSize &size);
void KRITAGLOBAL_EXPORT saveValue(QDomElement *parent, const QString &tag, const QPoint &pt);
void KRITAGLOBAL_EXPORT saveValue(QDomElement *parent, const QString &tag, const QPointF &pt);
void KRITAGLOBAL_EXPORT saveValue(QDomElement *parent, const QString &tag, const QVector3D &pt);
void KRITAGLOBAL_EXPORT saveValue(QDomElement *parent, const QString &tag, const QTransform &t);
/**
* Save a value of a scalar type into an XML tree. A child for \p parent
* is created and assigned a tag \p tag. The corresponding value can
* be fetched from the XML using loadValue() later.
*
* \see loadValue()
*/
template
void saveValue(QDomElement *parent, const QString &tag, T value)
{
QDomDocument doc = parent->ownerDocument();
QDomElement e = doc.createElement(tag);
parent->appendChild(e);
e.setAttribute("type", "value");
e.setAttribute("value", toString(value));
}
/**
* Save a vector of values into an XML tree. A child for \p parent is
* created and assigned a tag \p tag. The values in the array should
* have a type supported by saveValue() overrides. The corresponding
* vector can be fetched from the XML using loadValue() later.
*
* \see loadValue()
*/
template class Container, typename T>
void saveValue(QDomElement *parent, const QString &tag, const Container &array)
{
QDomDocument doc = parent->ownerDocument();
QDomElement e = doc.createElement(tag);
parent->appendChild(e);
e.setAttribute("type", "array");
int i = 0;
Q_FOREACH (const T &v, array) {
saveValue(&e, QString("item_%1").arg(i++), v);
}
}
/**
* Find an element with tag \p tag which is a child of \p parent. The element should
* be the only element with the provided tag in this parent.
*
* \return true is the element with \p tag is found and it is unique
*/
bool KRITAGLOBAL_EXPORT findOnlyElement(const QDomElement &parent, const QString &tag, QDomElement *el, QStringList *errorMessages = 0);
/**
* Load an object from an XML element, which is a child of \p parent and has
* a tag \p tag.
*
* \return true if the object is successfully loaded and is unique
*
* \see saveValue()
*/
bool KRITAGLOBAL_EXPORT loadValue(const QDomElement &e, float *v);
bool KRITAGLOBAL_EXPORT loadValue(const QDomElement &e, double *v);
bool KRITAGLOBAL_EXPORT loadValue(const QDomElement &e, QSize *size);
bool KRITAGLOBAL_EXPORT loadValue(const QDomElement &e, QRect *rc);
bool KRITAGLOBAL_EXPORT loadValue(const QDomElement &e, QPoint *pt);
bool KRITAGLOBAL_EXPORT loadValue(const QDomElement &e, QPointF *pt);
bool KRITAGLOBAL_EXPORT loadValue(const QDomElement &e, QVector3D *pt);
bool KRITAGLOBAL_EXPORT loadValue(const QDomElement &e, QTransform *t);
bool KRITAGLOBAL_EXPORT loadValue(const QDomElement &e, QString *value);
namespace Private {
bool KRITAGLOBAL_EXPORT checkType(const QDomElement &e, const QString &expectedType);
}
/**
* Load a scalar value from an XML element, which is a child of \p parent
* and has a tag \p tag.
*
* \return true if the object is successfully loaded and is unique
*
* \see saveValue()
*/
template
typename std::enable_if::value, bool>::type
loadValue(const QDomElement &e, T *value)
{
if (!Private::checkType(e, "value")) return false;
QVariant v(e.attribute("value", "no-value"));
*value = v.value();
return true;
}
/**
* A special adapter method that makes vector- and tag-based methods
* work with environment parameter uniformly.
*/
template
typename std::enable_if::value, bool>::type
loadValue(const QDomElement &parent, T *value, const E &/*env*/) {
return KisDomUtils::loadValue(parent, value);
}
/**
* Load an array from an XML element, which is a child of \p parent
* and has a tag \p tag.
*
* \return true if the object is successfully loaded and is unique
*
* \see saveValue()
*/
template class Container, typename T, typename E = std::tuple<>>
bool loadValue(const QDomElement &e, Container *array, const E &env = E())
{
if (!Private::checkType(e, "array")) return false;
QDomElement child = e.firstChildElement();
while (!child.isNull()) {
T value;
if (!loadValue(child, &value, env)) return false;
*array << value;
child = child.nextSiblingElement();
}
return true;
}
template >
bool loadValue(const QDomElement &parent, const QString &tag, T *value, const E &env = E())
{
QDomElement e;
if (!findOnlyElement(parent, tag, &e)) return false;
return loadValue(e, value, env);
}
KRITAGLOBAL_EXPORT QDomElement findElementByAttibute(QDomNode parent,
const QString &tag,
const QString &attribute,
const QString &key);
KRITAGLOBAL_EXPORT bool removeElements(QDomElement &parent, const QString &tag);
}
#endif /* __KIS_DOM_UTILS_H */
diff --git a/libs/ui/KisDocument.cpp b/libs/ui/KisDocument.cpp
index 2f81942877..799179ee8e 100644
--- a/libs/ui/KisDocument.cpp
+++ b/libs/ui/KisDocument.cpp
@@ -1,1792 +1,1805 @@
/* 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 "kis_layer_utils.h"
// 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 "KisResourceServerProvider.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 "KisReferenceImagesLayer.h"
#include
#include "kis_config_notifier.h"
#include "kis_async_action_feedback.h"
#include "KisCloneDocumentStroke.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;
namespace {
constexpr int errorMessageTimeout = 5000;
constexpr int successMessageTimeout = 1000;
}
/**********************************************************
*
* KisDocument
*
**********************************************************/
//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
, autoSaveTimer(new QTimer(q))
, 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)
, batchMode(false)
{
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)
, autoSaveTimer(new QTimer(q))
, 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(0) // the node is from another hierarchy!
, imageIdleWatcher(2000 /*ms*/)
, assistants(rhs.assistants) // WARNING: assistants should not store pointers to the document!
+ , globalAssistantsColor(rhs.globalAssistantsColor)
, gridConfig(rhs.gridConfig)
, savingLock(&savingMutex)
, batchMode(rhs.batchMode)
{
}
~Private() {
// Don't delete m_d->shapeController because it's in a QObject hierarchy.
delete nserver;
}
KoDocumentInfo *docInfo = 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;
int autoSaveFailureCount = 0;
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;
KisNodeWSP preActivatedNode;
KisShapeController* shapeController = 0;
KoShapeController* koShapeController = 0;
KisIdleWatcher imageIdleWatcher;
QScopedPointer imageIdleConnection;
QList assistants;
+ QColor globalAssistantsColor;
+
KisSharedPtr referenceImagesLayer;
KisGridConfig gridConfig;
StdLockableWrapper savingLock;
bool modifiedWhileSaving = false;
QScopedPointer backgroundSaveDocument;
QPointer savingUpdater;
QFuture childSavingFuture;
KritaUtils::ExportFileJob backgroundSaveJob;
bool isRecovered = false;
bool batchMode { false };
void setImageAndInitIdleWatcher(KisImageSP _image) {
image = _image;
imageIdleWatcher.setTrackedImage(image);
if (image) {
imageIdleConnection.reset(
new KisSignalAutoConnection(
&imageIdleWatcher, SIGNAL(startedIdleMode()),
image.data(), SLOT(explicitRegenerateLevelOfDetail())));
}
}
class StrippedSafeSavingLocker;
};
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(cleanChanged(bool)), this, SLOT(slotUndoStackCleanChanged(bool)));
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(cleanChanged(bool)), this, SLOT(slotUndoStackCleanChanged(bool)));
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), false);
if (rhs.d->preActivatedNode) {
// since we clone uuid's, we can use them for lacating new
// nodes. Otherwise we would need to use findSymmetricClone()
d->preActivatedNode =
KisLayerUtils::findNodeByUuid(d->image->root(), rhs.d->preActivatedNode->uuid());
}
}
KisDocument::~KisDocument()
{
// wait until all the pending operations are in progress
waitForSavingToComplete();
/**
* 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;
}
KisDocument *KisDocument::clone()
{
return new KisDocument(*this);
}
bool KisDocument::exportDocumentImpl(const KritaUtils::ExportFileJob &job, KisPropertiesConfigurationSP exportConfiguration)
{
QFileInfo filePathInfo(job.filePath);
if (filePathInfo.exists() && !filePathInfo.isWritable()) {
slotCompleteSavingDocument(job,
KisImportExportFilter::CreationError,
i18n("%1 cannot be written to. Please save under a different name.", job.filePath));
return false;
}
KisConfig cfg;
if (cfg.backupFile() && filePathInfo.exists()) {
KBackup::backupFile(job.filePath);
}
KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(!job.mimeType.isEmpty(), false);
const QString actionName =
job.flags & KritaUtils::SaveIsExporting ?
i18n("Exporting Document...") :
i18n("Saving Document...");
bool started =
initiateSavingInBackground(actionName,
this, SLOT(slotCompleteSavingDocument(KritaUtils::ExportFileJob, KisImportExportFilter::ConversionStatus,QString)),
job, exportConfiguration);
if (!started) {
emit canceled(QString());
}
return started;
}
bool KisDocument::exportDocument(const QUrl &url, const QByteArray &mimeType, bool showWarnings, KisPropertiesConfigurationSP exportConfiguration)
{
using namespace KritaUtils;
SaveFlags flags = SaveIsExporting;
if (showWarnings) {
flags |= SaveShowWarnings;
}
return exportDocumentImpl(KritaUtils::ExportFileJob(url.toLocalFile(),
mimeType,
flags),
exportConfiguration);
}
bool KisDocument::saveAs(const QUrl &url, const QByteArray &mimeType, bool showWarnings, KisPropertiesConfigurationSP exportConfiguration)
{
using namespace KritaUtils;
return exportDocumentImpl(ExportFileJob(url.toLocalFile(),
mimeType,
showWarnings ? SaveShowWarnings : SaveNone),
exportConfiguration);
}
bool KisDocument::save(bool showWarnings, KisPropertiesConfigurationSP exportConfiguration)
{
return saveAs(url(), mimeType(), showWarnings, exportConfiguration);
}
QByteArray KisDocument::serializeToNativeByteArray()
{
QByteArray byteArray;
QBuffer buffer(&byteArray);
QScopedPointer filter(KisImportExportManager::filterForMimeType(nativeFormatMimeType(), KisImportExportManager::Export));
filter->setBatchMode(true);
filter->setMimeType(nativeFormatMimeType());
Private::StrippedSafeSavingLocker locker(&d->savingMutex, d->image);
if (!locker.successfullyLocked()) {
return byteArray;
}
d->savingImage = d->image;
if (filter->convert(this, &buffer) != KisImportExportFilter::OK) {
qWarning() << "serializeToByteArray():: Could not export to our native format";
}
return byteArray;
}
void KisDocument::slotCompleteSavingDocument(const KritaUtils::ExportFileJob &job, KisImportExportFilter::ConversionStatus status, const QString &errorMessage)
{
if (status == KisImportExportFilter::UserCancelled)
return;
const QString fileName = QFileInfo(job.filePath).fileName();
if (status != KisImportExportFilter::OK) {
emit statusBarMessage(i18nc("%1 --- failing file name, %2 --- error message",
"Error during saving %1: %2",
fileName,
exportErrorToUserMessage(status, errorMessage)), errorMessageTimeout);
if (!fileBatchMode()) {
const QString filePath = job.filePath;
QMessageBox::critical(0, i18nc("@title:window", "Krita"), i18n("Could not save %1\nReason: %2", filePath, errorMessage));
}
} else {
if (!(job.flags & KritaUtils::SaveIsExporting)) {
setUrl(QUrl::fromLocalFile(job.filePath));
setLocalFilePath(job.filePath);
setMimeType(job.mimeType);
updateEditingTime(true);
if (!d->modifiedWhileSaving) {
d->undoStack->setClean();
}
setRecovered(false);
removeAutoSaveFiles();
}
emit completed();
emit sigSavingFinished();
emit statusBarMessage(i18n("Finished saving %1", fileName), successMessageTimeout);
}
}
QByteArray KisDocument::mimeType() const
{
return d->mimeType;
}
void KisDocument::setMimeType(const QByteArray & mimeType)
{
d->mimeType = mimeType;
}
bool KisDocument::fileBatchMode() const
{
return d->batchMode;
}
void KisDocument::setFileBatchMode(const bool batchMode)
{
d->batchMode = batchMode;
}
KisDocument* KisDocument::lockAndCloneForSaving()
{
// force update of all the asynchronous nodes before cloning
QApplication::processEvents();
KisLayerUtils::forceAllDelayedNodesUpdate(d->image->root());
KisMainWindow *window = KisPart::instance()->currentMainwindow();
if (window) {
if (window->viewManager()) {
if (!window->viewManager()->blockUntilOperationsFinished(d->image)) {
return 0;
}
}
}
Private::StrippedSafeSavingLocker locker(&d->savingMutex, d->image);
if (!locker.successfullyLocked()) {
return 0;
}
return new KisDocument(*this);
}
bool KisDocument::exportDocumentSync(const QUrl &url, const QByteArray &mimeType, KisPropertiesConfigurationSP exportConfiguration)
{
Private::StrippedSafeSavingLocker locker(&d->savingMutex, d->image);
if (!locker.successfullyLocked()) {
return false;
}
d->savingImage = d->image;
const QString fileName = url.toLocalFile();
KisImportExportFilter::ConversionStatus status =
d->importExportManager->
exportDocument(fileName, fileName, mimeType, false, exportConfiguration);
d->savingImage = 0;
return status == KisImportExportFilter::OK;
}
bool KisDocument::initiateSavingInBackground(const QString actionName,
const QObject *receiverObject, const char *receiverMethod,
const KritaUtils::ExportFileJob &job,
KisPropertiesConfigurationSP exportConfiguration)
{
return initiateSavingInBackground(actionName, receiverObject, receiverMethod,
job, exportConfiguration, std::unique_ptr());
}
bool KisDocument::initiateSavingInBackground(const QString actionName,
const QObject *receiverObject, const char *receiverMethod,
const KritaUtils::ExportFileJob &job,
KisPropertiesConfigurationSP exportConfiguration,
std::unique_ptr &&optionalClonedDocument)
{
KIS_ASSERT_RECOVER_RETURN_VALUE(job.isValid(), false);
QScopedPointer clonedDocument;
if (!optionalClonedDocument) {
clonedDocument.reset(lockAndCloneForSaving());
} else {
clonedDocument.reset(optionalClonedDocument.release());
}
// we block saving until the current saving is finished!
if (!clonedDocument || !d->savingMutex.tryLock()) {
return false;
}
KIS_ASSERT_RECOVER_RETURN_VALUE(!d->backgroundSaveDocument, false);
KIS_ASSERT_RECOVER_RETURN_VALUE(!d->backgroundSaveJob.isValid(), false);
d->backgroundSaveDocument.reset(clonedDocument.take());
d->backgroundSaveJob = job;
d->modifiedWhileSaving = false;
if (d->backgroundSaveJob.flags & KritaUtils::SaveInAutosaveMode) {
d->backgroundSaveDocument->d->isAutosaving = true;
}
connect(d->backgroundSaveDocument.data(),
SIGNAL(sigBackgroundSavingFinished(KisImportExportFilter::ConversionStatus, const QString&)),
this,
SLOT(slotChildCompletedSavingInBackground(KisImportExportFilter::ConversionStatus, const QString&)));
connect(this, SIGNAL(sigCompleteBackgroundSaving(KritaUtils::ExportFileJob,KisImportExportFilter::ConversionStatus,QString)),
receiverObject, receiverMethod, Qt::UniqueConnection);
bool started =
d->backgroundSaveDocument->startExportInBackground(actionName,
job.filePath,
job.filePath,
job.mimeType,
job.flags & KritaUtils::SaveShowWarnings,
exportConfiguration);
if (!started) {
// the state should have been deinitialized in slotChildCompletedSavingInBackground()
KIS_SAFE_ASSERT_RECOVER (!d->backgroundSaveDocument && !d->backgroundSaveJob.isValid()) {
d->backgroundSaveDocument.take()->deleteLater();
d->savingMutex.unlock();
d->backgroundSaveJob = KritaUtils::ExportFileJob();
}
}
return started;
}
void KisDocument::slotChildCompletedSavingInBackground(KisImportExportFilter::ConversionStatus status, const QString &errorMessage)
{
KIS_SAFE_ASSERT_RECOVER(!d->savingMutex.tryLock()) {
d->savingMutex.unlock();
return;
}
KIS_SAFE_ASSERT_RECOVER_RETURN(d->backgroundSaveDocument);
if (d->backgroundSaveJob.flags & KritaUtils::SaveInAutosaveMode) {
d->backgroundSaveDocument->d->isAutosaving = false;
}
d->backgroundSaveDocument.take()->deleteLater();
d->savingMutex.unlock();
KIS_SAFE_ASSERT_RECOVER_RETURN(d->backgroundSaveJob.isValid());
const KritaUtils::ExportFileJob job = d->backgroundSaveJob;
d->backgroundSaveJob = KritaUtils::ExportFileJob();
emit sigCompleteBackgroundSaving(job, status, errorMessage);
}
void KisDocument::slotAutoSaveImpl(std::unique_ptr &&optionalClonedDocument)
{
if (!d->modified || !d->modifiedAfterAutosave) return;
const QString autoSaveFileName = generateAutoSaveFileName(localFilePath());
emit statusBarMessage(i18n("Autosaving... %1", autoSaveFileName), successMessageTimeout);
const bool hadClonedDocument = bool(optionalClonedDocument);
bool started = false;
if (d->image->isIdle() || hadClonedDocument) {
started = initiateSavingInBackground(i18n("Autosaving..."),
this, SLOT(slotCompleteAutoSaving(KritaUtils::ExportFileJob, KisImportExportFilter::ConversionStatus, const QString&)),
KritaUtils::ExportFileJob(autoSaveFileName, nativeFormatMimeType(), KritaUtils::SaveIsExporting | KritaUtils::SaveInAutosaveMode),
0,
std::move(optionalClonedDocument));
} else {
emit statusBarMessage(i18n("Autosaving postponed: document is busy..."), errorMessageTimeout);
}
if (!started && !hadClonedDocument && d->autoSaveFailureCount >= 3) {
KisCloneDocumentStroke *stroke = new KisCloneDocumentStroke(this);
connect(stroke, SIGNAL(sigDocumentCloned(KisDocument*)),
this, SLOT(slotInitiateAsyncAutosaving(KisDocument*)),
Qt::BlockingQueuedConnection);
KisStrokeId strokeId = d->image->startStroke(stroke);
d->image->endStroke(strokeId);
setInfiniteAutoSaveInterval();
} else if (!started) {
setEmergencyAutoSaveInterval();
} else {
d->modifiedAfterAutosave = false;
}
}
void KisDocument::slotAutoSave()
{
slotAutoSaveImpl(std::unique_ptr());
}
void KisDocument::slotInitiateAsyncAutosaving(KisDocument *clonedDocument)
{
slotAutoSaveImpl(std::unique_ptr(clonedDocument));
}
void KisDocument::slotCompleteAutoSaving(const KritaUtils::ExportFileJob &job, KisImportExportFilter::ConversionStatus status, const QString &errorMessage)
{
Q_UNUSED(job);
const QString fileName = QFileInfo(job.filePath).fileName();
if (status != KisImportExportFilter::OK) {
setEmergencyAutoSaveInterval();
emit statusBarMessage(i18nc("%1 --- failing file name, %2 --- error message",
"Error during autosaving %1: %2",
fileName,
exportErrorToUserMessage(status, errorMessage)), errorMessageTimeout);
} else {
KisConfig cfg;
d->autoSaveDelay = cfg.autoSaveInterval();
if (!d->modifiedWhileSaving) {
d->autoSaveTimer->stop(); // until the next change
d->autoSaveFailureCount = 0;
} else {
setNormalAutoSaveInterval();
}
emit statusBarMessage(i18n("Finished autosaving %1", fileName), successMessageTimeout);
}
}
bool KisDocument::startExportInBackground(const QString &actionName,
const QString &location,
const QString &realLocation,
const QByteArray &mimeType,
bool showWarnings,
KisPropertiesConfigurationSP exportConfiguration)
{
d->savingImage = d->image;
KisMainWindow *window = KisPart::instance()->currentMainwindow();
if (window) {
if (window->viewManager()) {
d->savingUpdater = window->viewManager()->createThreadedUpdater(actionName);
d->importExportManager->setUpdater(d->savingUpdater);
}
}
KisImportExportFilter::ConversionStatus initializationStatus;
d->childSavingFuture =
d->importExportManager->exportDocumentAsyc(location,
realLocation,
mimeType,
initializationStatus,
showWarnings,
exportConfiguration);
if (initializationStatus != KisImportExportFilter::ConversionStatus::OK) {
if (d->savingUpdater) {
d->savingUpdater->cancel();
}
d->savingImage.clear();
emit sigBackgroundSavingFinished(initializationStatus, this->errorMessage());
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();
const QString errorMessage = this->errorMessage();
d->savingImage.clear();
d->childSavingFuture = QFuture();
d->lastErrorMessage.clear();
if (d->savingUpdater) {
d->savingUpdater->setProgress(100);
}
emit sigBackgroundSavingFinished(status, errorMessage);
}
void KisDocument::setReadWrite(bool readwrite)
{
d->readwrite = readwrite;
setNormalAutoSaveInterval();
Q_FOREACH (KisMainWindow *mainWindow, KisPart::instance()->mainWindows()) {
mainWindow->setReadWrite(readwrite);
}
}
void KisDocument::setAutoSaveDelay(int delay)
{
if (isReadWrite() && delay > 0) {
d->autoSaveTimer->start(delay * 1000);
} else {
d->autoSaveTimer->stop();
}
}
void KisDocument::setNormalAutoSaveInterval()
{
setAutoSaveDelay(d->autoSaveDelay);
d->autoSaveFailureCount = 0;
}
void KisDocument::setEmergencyAutoSaveInterval()
{
const int emergencyAutoSaveInterval = 10; /* sec */
setAutoSaveDelay(emergencyAutoSaveInterval);
d->autoSaveFailureCount++;
}
void KisDocument::setInfiniteAutoSaveInterval()
{
setAutoSaveDelay(-1);
}
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");
QRegularExpression autosavePattern("^\\..+-autosave.kra$");
QFileInfo fi(path);
QString dir = fi.absolutePath();
QString filename = fi.fileName();
if (path.isEmpty() || autosavePattern.match(filename).hasMatch()) {
// 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 {
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, 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 || flags & RecoveryFile) {
setReadWrite(true); // enable save button
setModified(true);
setRecovered(true);
}
else {
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("warning"));
QWidget *page = new QWidget(this);
QVBoxLayout *layout = new QVBoxLayout(page);
QHBoxLayout *hlayout = new QHBoxLayout();
QLabel *labelWarning= new QLabel();
labelWarning->setPixmap(KisIconUtils::loadIcon("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;
KisMainWindow *window = KisPart::instance()->currentMainwindow();
if (window && window->viewManager()) {
KoUpdaterPtr updater = window->viewManager()->createUnthreadedUpdater(i18n("Opening document"));
d->importExportManager->setUpdater(updater);
}
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();
}
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();
undoStack()->clear();
return true;
}
// 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;
const QString _url(url().fileName());
// if URL is empty...it is probably an unsaved file
if (_url.isEmpty()) {
c = " [" + i18n("Not Saved") + "] ";
} else {
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::slotUndoStackCleanChanged(bool value)
{
setModified(!value);
}
void KisDocument::slotConfigChanged()
{
KisConfig cfg;
d->undoStack->setUndoLimit(cfg.undoStackLimit());
d->autoSaveDelay = cfg.autoSaveInterval();
setNormalAutoSaveInterval();
}
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;
}
setUrl(d->m_url);
ret = openFile();
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);
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;
}
bool KisDocument::isSaving() const
{
const bool result = d->savingMutex.tryLock();
if (result) {
d->savingMutex.unlock();
}
return !result;
}
void KisDocument::waitForSavingToComplete()
{
if (isSaving()) {
KisAsyncActionFeedback f(i18nc("progress dialog message when the user closes the document that is being saved", "Waiting for saving to complete..."), 0);
f.waitForMutex(&d->savingMutex);
}
}
KoShapeBasedDocumentBase *KisDocument::shapeController() const
{
return d->shapeController;
}
KoShapeLayer* KisDocument::shapeForNode(KisNodeSP layer) const
{
return d->shapeController->shapeForNode(layer);
}
QList KisDocument::assistants() const
{
return d->assistants;
}
void KisDocument::setAssistants(const QList &value)
{
d->assistants = value;
}
KisSharedPtr KisDocument::referenceImagesLayer() const
{
return d->referenceImagesLayer.data();
}
void KisDocument::setReferenceImagesLayer(KisSharedPtr layer, bool updateImage)
{
if (updateImage) {
if (layer) {
d->image->addNode(layer);
} else {
d->image->removeNode(d->referenceImagesLayer);
}
}
d->referenceImagesLayer = layer;
}
void KisDocument::setPreActivatedNode(KisNodeSP activatedNode)
{
d->preActivatedNode = activatedNode;
}
KisNodeSP KisDocument::preActivatedNode() const
{
return d->preActivatedNode;
}
KisImageWSP KisDocument::image() const
{
return d->image;
}
KisImageSP KisDocument::savingImage() const
{
return d->savingImage;
}
void KisDocument::setCurrentImage(KisImageSP image, bool forceInitialUpdate)
{
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);
if (forceInitialUpdate) {
d->image->initialRefreshGraph();
}
}
void KisDocument::hackPreliminarySetImage(KisImageSP image)
{
KIS_SAFE_ASSERT_RECOVER_RETURN(!d->image);
d->setImageAndInitIdleWatcher(image);
d->shapeController->setImage(image);
}
void KisDocument::setImageModified()
{
setModified(true);
}
KisUndoStore* KisDocument::createUndoStore()
{
return new KisDocumentUndoStore(this);
}
bool KisDocument::isAutosaving() const
{
return d->isAutosaving;
}
QString KisDocument::exportErrorToUserMessage(KisImportExportFilter::ConversionStatus status, const QString &errorMessage)
{
return errorMessage.isEmpty() ? KisImportExportFilter::conversionStatusString(status) : errorMessage;
}
+
+void KisDocument::setAssistantsGlobalColor(QColor color)
+{
+ d->globalAssistantsColor = color;
+}
+
+QColor KisDocument::assistantsGlobalColor()
+{
+ return d->globalAssistantsColor;
+}
diff --git a/libs/ui/KisDocument.h b/libs/ui/KisDocument.h
index 95efd93385..693a2e15ca 100644
--- a/libs/ui/KisDocument.h
+++ b/libs/ui/KisDocument.h
@@ -1,641 +1,647 @@
/* 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
#include
#include
#include "kritaui_export.h"
#include
class QString;
class KUndo2Command;
class KoUnit;
class KoColor;
class KoColorSpace;
class KoShapeBasedDocumentBase;
class KoShapeLayer;
class KoStore;
class KoOdfReadStore;
class KoDocumentInfo;
class KoDocumentInfoDlg;
class KisImportExportManager;
class KisUndoStore;
class KisPart;
class KisGridConfig;
class KisGuidesConfig;
class QDomDocument;
class KisReferenceImagesLayer;
#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 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().
*/
~KisDocument() override;
/**
* @brief reload Reloads the document from the original url
* @return the result of loading the document
*/
bool reload();
/**
* @brief creates a clone of the document and returns it. Please make sure that you
* hold all the necessary locks on the image before asking for a clone!
*/
KisDocument* clone();
/**
* @brief openUrl Open an URL
* @param url The URL to open
* @param flags Control specific behavior
* @return success status
*/
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, const QByteArray &mimeType, bool showWarnings = false, KisPropertiesConfigurationSP exportConfiguration = 0);
bool exportDocumentSync(const QUrl &url, const QByteArray &mimeType, KisPropertiesConfigurationSP exportConfiguration = 0);
private:
bool exportDocumentImpl(const KritaUtils::ExportFileJob &job, KisPropertiesConfigurationSP exportConfiguration);
public:
/**
* @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 override;
/**
* @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) override;
/**
* @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);
/**
* Set standard autosave interval that is set by a config file
*/
void setNormalAutoSaveInterval();
/**
* Set emergency interval that autosave uses when the image is busy,
* by default it is 10 sec
*/
void setEmergencyAutoSaveInterval();
/**
* Disable autosave
*/
void setInfiniteAutoSaveInterval();
/**
* @return the information concerning this document.
* @see KoDocumentInfo
*/
KoDocumentInfo *documentInfo() 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 override;
/**
* @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();
/**
* @brief isInSaving shown if the document has any (background) saving process or not
* @return true if there is some saving in action
*/
bool isInSaving() const;
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);
/**
* 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, int timeout = 0);
/**
* 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, const QString &errorMessage);
void sigCompleteBackgroundSaving(const KritaUtils::ExportFileJob &job, KisImportExportFilter::ConversionStatus status, const QString &errorMessage);
private Q_SLOTS:
void finishExportInBackground();
void slotChildCompletedSavingInBackground(KisImportExportFilter::ConversionStatus status, const QString &errorMessage);
void slotCompleteAutoSaving(const KritaUtils::ExportFileJob &job, KisImportExportFilter::ConversionStatus status, const QString &errorMessage);
void slotCompleteSavingDocument(const KritaUtils::ExportFileJob &job, KisImportExportFilter::ConversionStatus status, const QString &errorMessage);
void slotInitiateAsyncAutosaving(KisDocument *clonedDocument);
private:
friend class KisPart;
friend class SafeSavingLocker;
bool initiateSavingInBackground(const QString actionName,
const QObject *receiverObject, const char *receiverMethod,
const KritaUtils::ExportFileJob &job,
KisPropertiesConfigurationSP exportConfiguration,
std::unique_ptr &&optionalClonedDocument);
bool initiateSavingInBackground(const QString actionName,
const QObject *receiverObject, const char *receiverMethod,
const KritaUtils::ExportFileJob &job,
KisPropertiesConfigurationSP exportConfiguration);
bool startExportInBackground(const QString &actionName, const QString &location,
const QString &realLocation,
const QByteArray &mimeType,
bool showWarnings,
KisPropertiesConfigurationSP exportConfiguration);
/**
* Activate/deactivate/configure the autosave feature.
* @param delay in seconds, 0 to disable
*/
void setAutoSaveDelay(int delay);
/**
* 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();
/** @internal */
void setModified();
public:
bool isAutosaving() const override;
public:
QString localFilePath() const override;
void setLocalFilePath( const QString &localFilePath );
KoDocumentInfoDlg* createDocumentInfoDialog(QWidget *parent, KoDocumentInfo *docInfo) const;
bool isReadWrite() const;
QUrl url() const override;
void setUrl(const QUrl &url) override;
bool closeUrl(bool promptToSave = true);
bool saveAs(const QUrl &url, const QByteArray &mimeType, 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);
bool isSaving() const;
void waitForSavingToComplete();
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;
/**
* Set the current image to the specified image and turn undo on.
*/
void setCurrentImage(KisImageSP image, bool forceInitialUpdate = true);
/**
* Set the image of the document preliminary, before the document
* has completed loading. Some of the document items (shapes) may want
* to access image properties (bounds and resolution), so we should provide
* it to them even before the entire image is loaded.
*
* Right now, the only use by KoShapeRegistry::createShapeFromOdf(), remove
* after it is deprecated.
*/
void hackPreliminarySetImage(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;
/**
* Set the list of nodes that was marked as currently active. Used *only*
* for saving loading. Never use it for tools or processing.
*/
void setPreActivatedNode(KisNodeSP activatedNode);
/**
* @return the node that was set as active during loading. Used *only*
* for saving loading. Never use it for tools or processing.
*/
KisNodeSP preActivatedNode() const;
/// @return the list of assistants associated with this document
QList assistants() const;
/// @replace the current list of assistants with @param value
void setAssistants(const QList &value);
+
+ void setAssistantsGlobalColor(QColor color);
+ QColor assistantsGlobalColor();
+
+
+
/**
* Get existing reference images layer or null if none exists.
*/
KisSharedPtr referenceImagesLayer() const;
void setReferenceImagesLayer(KisSharedPtr layer, bool updateImage);
bool save(bool showWarnings, KisPropertiesConfigurationSP exportConfiguration);
Q_SIGNALS:
void completed();
void canceled(const QString &);
private Q_SLOTS:
void setImageModified();
void slotAutoSave();
void slotUndoStackCleanChanged(bool value);
void slotConfigChanged();
private:
/**
* @brief try to clone the image. This method handles all the locking for you. If locking
* has failed, no cloning happens
* @return cloned document on success, null otherwise
*/
KisDocument *lockAndCloneForSaving();
QString exportErrorToUserMessage(KisImportExportFilter::ConversionStatus status, const QString &errorMessage);
QString prettyPathOrUrl() const;
bool openUrlInternal(const QUrl &url);
void slotAutoSaveImpl(std::unique_ptr &&optionalClonedDocument);
class Private;
Private *const d;
};
Q_DECLARE_OPERATORS_FOR_FLAGS(KisDocument::OpenFlags)
Q_DECLARE_METATYPE(KisDocument*)
#endif
diff --git a/libs/ui/KisView.cpp b/libs/ui/KisView.cpp
index b52aa6c36c..ca2f0d8682 100644
--- a/libs/ui/KisView.cpp
+++ b/libs/ui/KisView.cpp
@@ -1,1013 +1,1016 @@
/*
* 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 "KisView.h"
#include "KisView_p.h"
#include
#include
#include
#include "KoDocumentInfo.h"
#include "KoPageLayout.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
#include
#include "kis_canvas2.h"
#include "kis_canvas_controller.h"
#include "kis_canvas_resource_provider.h"
#include "kis_config.h"
#include "KisDocument.h"
#include "kis_image_manager.h"
#include "KisMainWindow.h"
#include "kis_mimedata.h"
#include "kis_mirror_axis.h"
#include "kis_node_commands_adapter.h"
#include "kis_node_manager.h"
#include "KisPart.h"
#include "KisPrintJob.h"
#include "kis_shape_controller.h"
#include "kis_tool_freehand.h"
#include "KisViewManager.h"
#include "kis_zoom_manager.h"
#include "kis_statusbar.h"
#include "kis_painting_assistants_decoration.h"
#include "KisReferenceImagesDecoration.h"
#include "kis_progress_widget.h"
#include "kis_signal_compressor.h"
#include "kis_filter_manager.h"
#include "kis_file_layer.h"
#include "krita_utils.h"
#include "input/kis_input_manager.h"
#include "KisRemoteFileFetcher.h"
//static
QString KisView::newObjectName()
{
static int s_viewIFNumber = 0;
QString name; name.setNum(s_viewIFNumber++); name.prepend("view_");
return name;
}
bool KisView::s_firstView = true;
class Q_DECL_HIDDEN KisView::Private
{
public:
Private(KisView *_q,
KisDocument *document,
KoCanvasResourceManager *resourceManager,
KActionCollection *actionCollection)
: actionCollection(actionCollection)
, viewConverter()
, canvasController(_q, actionCollection)
, canvas(&viewConverter, resourceManager, _q, document->shapeController())
, zoomManager(_q, &this->viewConverter, &this->canvasController)
, paintingAssistantsDecoration(new KisPaintingAssistantsDecoration(_q))
, referenceImagesDecoration(new KisReferenceImagesDecoration(_q, document))
, floatingMessageCompressor(100, KisSignalCompressor::POSTPONE)
{
}
bool inOperation; //in the middle of an operation (no screen refreshing)?
QPointer document; // our KisDocument
QWidget *tempActiveWidget = 0;
/**
* Signals the document has been deleted. Can't use document==0 since this
* only happens in ~QObject, and views get deleted by ~KisDocument.
* XXX: either provide a better justification to do things this way, or
* rework the mechanism.
*/
bool documentDeleted = false;
KActionCollection* actionCollection;
KisCoordinatesConverter viewConverter;
KisCanvasController canvasController;
KisCanvas2 canvas;
KisZoomManager zoomManager;
KisViewManager *viewManager = 0;
KisNodeSP currentNode;
KisPaintingAssistantsDecorationSP paintingAssistantsDecoration;
KisReferenceImagesDecorationSP referenceImagesDecoration;
bool isCurrent = false;
bool showFloatingMessage = false;
QPointer savedFloatingMessage;
KisSignalCompressor floatingMessageCompressor;
QMdiSubWindow *subWindow{nullptr};
bool softProofing = false;
bool gamutCheck = false;
// Hmm sorry for polluting the private class with such a big inner class.
// At the beginning it was a little struct :)
class StatusBarItem
{
public:
StatusBarItem(QWidget * widget, int stretch, bool permanent)
: m_widget(widget),
m_stretch(stretch),
m_permanent(permanent),
m_connected(false),
m_hidden(false) {}
bool operator==(const StatusBarItem& rhs) {
return m_widget == rhs.m_widget;
}
bool operator!=(const StatusBarItem& rhs) {
return m_widget != rhs.m_widget;
}
QWidget * widget() const {
return m_widget;
}
void ensureItemShown(QStatusBar * sb) {
Q_ASSERT(m_widget);
if (!m_connected) {
if (m_permanent)
sb->addPermanentWidget(m_widget, m_stretch);
else
sb->addWidget(m_widget, m_stretch);
if(!m_hidden)
m_widget->show();
m_connected = true;
}
}
void ensureItemHidden(QStatusBar * sb) {
if (m_connected) {
m_hidden = m_widget->isHidden();
sb->removeWidget(m_widget);
m_widget->hide();
m_connected = false;
}
}
private:
QWidget * m_widget = 0;
int m_stretch;
bool m_permanent;
bool m_connected = false;
bool m_hidden = false;
};
};
KisView::KisView(KisDocument *document, KoCanvasResourceManager *resourceManager, KActionCollection *actionCollection, QWidget *parent)
: QWidget(parent)
, d(new Private(this, document, resourceManager, actionCollection))
{
Q_ASSERT(document);
connect(document, SIGNAL(titleModified(QString,bool)), this, SIGNAL(titleModified(QString,bool)));
setObjectName(newObjectName());
d->document = document;
setFocusPolicy(Qt::StrongFocus);
QStatusBar * sb = statusBar();
if (sb) { // No statusbar in e.g. konqueror
connect(d->document, SIGNAL(statusBarMessage(const QString&, int)),
this, SLOT(slotSavingStatusMessage(const QString&, int)));
connect(d->document, SIGNAL(clearStatusBarMessage()),
this, SLOT(slotClearStatusText()));
}
d->canvas.setup();
KisConfig cfg;
d->canvasController.setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOn);
d->canvasController.setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOn);
d->canvasController.setVastScrolling(cfg.vastScrolling());
d->canvasController.setCanvas(&d->canvas);
d->zoomManager.setup(d->actionCollection);
connect(&d->canvasController, SIGNAL(documentSizeChanged()), &d->zoomManager, SLOT(slotScrollAreaSizeChanged()));
setAcceptDrops(true);
connect(d->document, SIGNAL(sigLoadingFinished()), this, SLOT(slotLoadingFinished()));
connect(d->document, SIGNAL(sigSavingFinished()), this, SLOT(slotSavingFinished()));
d->canvas.addDecoration(d->paintingAssistantsDecoration);
d->paintingAssistantsDecoration->setVisible(true);
+ d->canvas.paintingAssistantsDecoration()->setGlobalAssistantsColor(d->document->assistantsGlobalColor());
+
+
d->canvas.addDecoration(d->referenceImagesDecoration);
d->referenceImagesDecoration->setVisible(true);
d->showFloatingMessage = cfg.showCanvasMessages();
}
KisView::~KisView()
{
if (d->viewManager) {
if (d->viewManager->filterManager()->isStrokeRunning()) {
d->viewManager->filterManager()->cancel();
}
d->viewManager->mainWindow()->notifyChildViewDestroyed(this);
}
KoToolManager::instance()->removeCanvasController(&d->canvasController);
d->canvasController.setCanvas(0);
KisPart::instance()->removeView(this);
delete d;
}
void KisView::notifyCurrentStateChanged(bool isCurrent)
{
d->isCurrent = isCurrent;
if (!d->isCurrent && d->savedFloatingMessage) {
d->savedFloatingMessage->removeMessage();
}
KisInputManager *inputManager = globalInputManager();
if (d->isCurrent) {
inputManager->attachPriorityEventFilter(&d->canvasController);
} else {
inputManager->detachPriorityEventFilter(&d->canvasController);
}
}
void KisView::setShowFloatingMessage(bool show)
{
d->showFloatingMessage = show;
}
void KisView::showFloatingMessageImpl(const QString &message, const QIcon& icon, int timeout, KisFloatingMessage::Priority priority, int alignment)
{
if (!d->viewManager) return;
if(d->isCurrent && d->showFloatingMessage && d->viewManager->qtMainWindow()) {
if (d->savedFloatingMessage) {
d->savedFloatingMessage->tryOverrideMessage(message, icon, timeout, priority, alignment);
} else {
d->savedFloatingMessage = new KisFloatingMessage(message, this->canvasBase()->canvasWidget(), false, timeout, priority, alignment);
d->savedFloatingMessage->setShowOverParent(true);
d->savedFloatingMessage->setIcon(icon);
connect(&d->floatingMessageCompressor, SIGNAL(timeout()), d->savedFloatingMessage, SLOT(showMessage()));
d->floatingMessageCompressor.start();
}
}
}
bool KisView::canvasIsMirrored() const
{
return d->canvas.xAxisMirrored() || d->canvas.yAxisMirrored();
}
void KisView::setViewManager(KisViewManager *view)
{
d->viewManager = view;
KoToolManager::instance()->addController(&d->canvasController);
KoToolManager::instance()->registerToolActions(d->actionCollection, &d->canvasController);
dynamic_cast(d->document->shapeController())->setInitialShapeForCanvas(&d->canvas);
if (resourceProvider()) {
resourceProvider()->slotImageSizeChanged();
}
if (d->viewManager && d->viewManager->nodeManager()) {
d->viewManager->nodeManager()->nodesUpdated();
}
connect(image(), SIGNAL(sigSizeChanged(const QPointF&, const QPointF&)), this, SLOT(slotImageSizeChanged(const QPointF&, const QPointF&)));
connect(image(), SIGNAL(sigResolutionChanged(double,double)), this, SLOT(slotImageResolutionChanged()));
// executed in a context of an image thread
connect(image(), SIGNAL(sigNodeAddedAsync(KisNodeSP)),
SLOT(slotImageNodeAdded(KisNodeSP)),
Qt::DirectConnection);
// executed in a context of the gui thread
connect(this, SIGNAL(sigContinueAddNode(KisNodeSP)),
SLOT(slotContinueAddNode(KisNodeSP)),
Qt::AutoConnection);
// executed in a context of an image thread
connect(image(), SIGNAL(sigRemoveNodeAsync(KisNodeSP)),
SLOT(slotImageNodeRemoved(KisNodeSP)),
Qt::DirectConnection);
// executed in a context of the gui thread
connect(this, SIGNAL(sigContinueRemoveNode(KisNodeSP)),
SLOT(slotContinueRemoveNode(KisNodeSP)),
Qt::AutoConnection);
d->viewManager->updateGUI();
KoToolManager::instance()->switchToolRequested("KritaShape/KisToolBrush");
}
KisViewManager* KisView::viewManager() const
{
return d->viewManager;
}
void KisView::slotImageNodeAdded(KisNodeSP node)
{
emit sigContinueAddNode(node);
}
void KisView::slotContinueAddNode(KisNodeSP newActiveNode)
{
/**
* When deleting the last layer, root node got selected. We should
* fix it when the first layer is added back.
*
* Here we basically reimplement what Qt's view/model do. But
* since they are not connected, we should do it manually.
*/
if (!d->isCurrent &&
(!d->currentNode || !d->currentNode->parent())) {
d->currentNode = newActiveNode;
}
}
void KisView::slotImageNodeRemoved(KisNodeSP node)
{
emit sigContinueRemoveNode(KritaUtils::nearestNodeAfterRemoval(node));
}
void KisView::slotContinueRemoveNode(KisNodeSP newActiveNode)
{
if (!d->isCurrent) {
d->currentNode = newActiveNode;
}
}
KoZoomController *KisView::zoomController() const
{
return d->zoomManager.zoomController();
}
KisZoomManager *KisView::zoomManager() const
{
return &d->zoomManager;
}
KisCanvasController *KisView::canvasController() const
{
return &d->canvasController;
}
KisCanvasResourceProvider *KisView::resourceProvider() const
{
if (d->viewManager) {
return d->viewManager->resourceProvider();
}
return 0;
}
KisInputManager* KisView::globalInputManager() const
{
return d->viewManager ? d->viewManager->inputManager() : 0;
}
KisCanvas2 *KisView::canvasBase() const
{
return &d->canvas;
}
KisImageWSP KisView::image() const
{
if (d->document) {
return d->document->image();
}
return 0;
}
KisCoordinatesConverter *KisView::viewConverter() const
{
return &d->viewConverter;
}
void KisView::dragEnterEvent(QDragEnterEvent *event)
{
if (event->mimeData()->hasImage()
|| event->mimeData()->hasUrls()
|| event->mimeData()->hasFormat("application/x-krita-node")) {
event->accept();
// activate view if it should accept the drop
this->setFocus();
} else {
event->ignore();
}
}
void KisView::dropEvent(QDropEvent *event)
{
KisImageWSP kisimage = image();
Q_ASSERT(kisimage);
QPoint cursorPos = canvasBase()->coordinatesConverter()->widgetToImage(event->pos()).toPoint();
QRect imageBounds = kisimage->bounds();
QPoint pasteCenter;
bool forceRecenter;
if (event->keyboardModifiers() & Qt::ShiftModifier &&
imageBounds.contains(cursorPos)) {
pasteCenter = cursorPos;
forceRecenter = true;
} else {
pasteCenter = imageBounds.center();
forceRecenter = false;
}
if (event->mimeData()->hasFormat("application/x-krita-node") ||
event->mimeData()->hasImage())
{
KisShapeController *kritaShapeController =
dynamic_cast(d->document->shapeController());
QList nodes =
KisMimeData::loadNodes(event->mimeData(), imageBounds,
pasteCenter, forceRecenter,
kisimage, kritaShapeController);
Q_FOREACH (KisNodeSP node, nodes) {
if (node) {
KisNodeCommandsAdapter adapter(viewManager());
if (!viewManager()->nodeManager()->activeLayer()) {
adapter.addNode(node, kisimage->rootLayer() , 0);
} else {
adapter.addNode(node,
viewManager()->nodeManager()->activeLayer()->parent(),
viewManager()->nodeManager()->activeLayer());
}
}
}
}
else if (event->mimeData()->hasUrls()) {
QList urls = event->mimeData()->urls();
if (urls.length() > 0) {
QMenu popup;
popup.setObjectName("drop_popup");
QAction *insertAsNewLayer = new QAction(i18n("Insert as New Layer"), &popup);
QAction *insertManyLayers = new QAction(i18n("Insert Many Layers"), &popup);
QAction *insertAsNewFileLayer = new QAction(i18n("Insert as New File Layer"), &popup);
QAction *insertManyFileLayers = new QAction(i18n("Insert Many File Layers"), &popup);
QAction *openInNewDocument = new QAction(i18n("Open in New Document"), &popup);
QAction *openManyDocuments = new QAction(i18n("Open Many Documents"), &popup);
QAction *insertAsReferenceImage = new QAction(i18n("Insert as Reference Image"), &popup);
QAction *insertAsReferenceImages = new QAction(i18n("Insert as Reference Images"), &popup);
QAction *cancel = new QAction(i18n("Cancel"), &popup);
popup.addAction(insertAsNewLayer);
popup.addAction(insertAsNewFileLayer);
popup.addAction(openInNewDocument);
popup.addAction(insertAsReferenceImage);
popup.addAction(insertManyLayers);
popup.addAction(insertManyFileLayers);
popup.addAction(openManyDocuments);
popup.addAction(insertAsReferenceImages);
insertAsNewLayer->setEnabled(image() && urls.count() == 1);
insertAsNewFileLayer->setEnabled(image() && urls.count() == 1);
openInNewDocument->setEnabled(urls.count() == 1);
insertAsReferenceImage->setEnabled(image() && urls.count() == 1);
insertManyLayers->setEnabled(image() && urls.count() > 1);
insertManyFileLayers->setEnabled(image() && urls.count() > 1);
openManyDocuments->setEnabled(urls.count() > 1);
insertAsReferenceImages->setEnabled(image() && urls.count() > 1);
popup.addSeparator();
popup.addAction(cancel);
QAction *action = popup.exec(QCursor::pos());
if (action != 0 && action != cancel) {
QTemporaryFile *tmp = 0;
for (QUrl url : urls) {
if (!url.isLocalFile()) {
// download the file and substitute the url
KisRemoteFileFetcher fetcher;
tmp = new QTemporaryFile();
tmp->setAutoRemove(true);
if (!fetcher.fetchFile(url, tmp)) {
qDebug() << "Fetching" << url << "failed";
continue;
}
url = url.fromLocalFile(tmp->fileName());
}
if (url.isLocalFile()) {
if (action == insertAsNewLayer || action == insertManyLayers) {
d->viewManager->imageManager()->importImage(url);
activateWindow();
}
else if (action == insertAsNewFileLayer || action == insertManyFileLayers) {
KisNodeCommandsAdapter adapter(viewManager());
KisFileLayer *fileLayer = new KisFileLayer(image(), "", url.toLocalFile(),
KisFileLayer::None, image()->nextLayerName(), OPACITY_OPAQUE_U8);
adapter.addNode(fileLayer, viewManager()->activeNode()->parent(), viewManager()->activeNode());
}
else if (action == openInNewDocument || action == openManyDocuments) {
if (mainWindow()) {
mainWindow()->openDocument(url, KisMainWindow::None);
}
}
else if (action == insertAsReferenceImage || action == insertAsReferenceImages) {
auto *reference = KisReferenceImage::fromFile(url.toLocalFile(), d->viewConverter);
reference->setPosition(d->viewConverter.imageToDocument(cursorPos));
d->referenceImagesDecoration->addReferenceImage(reference);
KoToolManager::instance()->switchToolRequested("ToolReferenceImages");
}
}
delete tmp;
tmp = 0;
}
}
}
}
}
KisDocument *KisView::document() const
{
return d->document;
}
void KisView::setDocument(KisDocument *document)
{
d->document->disconnect(this);
d->document = document;
QStatusBar *sb = statusBar();
if (sb) { // No statusbar in e.g. konqueror
connect(d->document, SIGNAL(statusBarMessage(const QString&, int)),
this, SLOT(slotSavingStatusMessage(const QString&, int)));
connect(d->document, SIGNAL(clearStatusBarMessage()),
this, SLOT(slotClearStatusText()));
}
}
void KisView::setDocumentDeleted()
{
d->documentDeleted = true;
}
QPrintDialog *KisView::createPrintDialog(KisPrintJob *printJob, QWidget *parent)
{
Q_UNUSED(parent);
QPrintDialog *printDialog = new QPrintDialog(&printJob->printer(), this);
printDialog->setMinMax(printJob->printer().fromPage(), printJob->printer().toPage());
printDialog->setEnabledOptions(printJob->printDialogOptions());
return printDialog;
}
KisMainWindow * KisView::mainWindow() const
{
return dynamic_cast(window());
}
void KisView::setSubWindow(QMdiSubWindow *subWindow)
{
d->subWindow = subWindow;
}
QStatusBar * KisView::statusBar() const
{
KisMainWindow *mw = mainWindow();
return mw ? mw->statusBar() : 0;
}
void KisView::slotSavingStatusMessage(const QString &text, int timeout, bool isAutoSaving)
{
QStatusBar *sb = statusBar();
if (sb) {
sb->showMessage(text, timeout);
}
KisConfig cfg;
if (!sb || sb->isHidden() ||
(!isAutoSaving && cfg.forceShowSaveMessages()) ||
(cfg.forceShowAutosaveMessages() && isAutoSaving)) {
viewManager()->showFloatingMessage(text, QIcon());
}
}
void KisView::slotClearStatusText()
{
QStatusBar *sb = statusBar();
if (sb) {
sb->clearMessage();
}
}
QList KisView::createChangeUnitActions(bool addPixelUnit)
{
UnitActionGroup* unitActions = new UnitActionGroup(d->document, addPixelUnit, this);
return unitActions->actions();
}
void KisView::closeEvent(QCloseEvent *event)
{
// Check whether we're the last (user visible) view
int viewCount = KisPart::instance()->viewCount(document());
if (viewCount > 1 || !isVisible()) {
// there are others still, so don't bother the user
event->accept();
return;
}
if (queryClose()) {
d->viewManager->statusBar()->setView(0);
event->accept();
return;
}
event->ignore();
}
bool KisView::queryClose()
{
if (!document())
return true;
document()->waitForSavingToComplete();
if (document()->isModified()) {
QString name;
if (document()->documentInfo()) {
name = document()->documentInfo()->aboutInfo("title");
}
if (name.isEmpty())
name = document()->url().fileName();
if (name.isEmpty())
name = i18n("Untitled");
int res = QMessageBox::warning(this,
i18nc("@title:window", "Krita"),
i18n("The document '%1' has been modified.
Do you want to save it?
", name),
QMessageBox::Yes | QMessageBox::No | QMessageBox::Cancel, QMessageBox::Yes);
switch (res) {
case QMessageBox::Yes : {
bool isNative = (document()->mimeType() == document()->nativeFormatMimeType());
if (!viewManager()->mainWindow()->saveDocument(document(), !isNative, false))
return false;
break;
}
case QMessageBox::No : {
KisImageSP image = document()->image();
image->requestStrokeCancellation();
viewManager()->blockUntilOperationsFinishedForced(image);
document()->removeAutoSaveFiles();
document()->setModified(false); // Now when queryClose() is called by closeEvent it won't do anything.
break;
}
default : // case QMessageBox::Cancel :
return false;
}
}
return true;
}
void KisView::resetImageSizeAndScroll(bool changeCentering,
const QPointF &oldImageStillPoint,
const QPointF &newImageStillPoint)
{
const KisCoordinatesConverter *converter = d->canvas.coordinatesConverter();
QPointF oldPreferredCenter = d->canvasController.preferredCenter();
/**
* Calculating the still point in old coordinates depending on the
* parameters given
*/
QPointF oldStillPoint;
if (changeCentering) {
oldStillPoint =
converter->imageToWidget(oldImageStillPoint) +
converter->documentOffset();
} else {
QSize oldDocumentSize = d->canvasController.documentSize();
oldStillPoint = QPointF(0.5 * oldDocumentSize.width(), 0.5 * oldDocumentSize.height());
}
/**
* Updating the document size
*/
QSizeF size(image()->width() / image()->xRes(), image()->height() / image()->yRes());
KoZoomController *zc = d->zoomManager.zoomController();
zc->setZoom(KoZoomMode::ZOOM_CONSTANT, zc->zoomAction()->effectiveZoom());
zc->setPageSize(size);
zc->setDocumentSize(size, true);
/**
* Calculating the still point in new coordinates depending on the
* parameters given
*/
QPointF newStillPoint;
if (changeCentering) {
newStillPoint =
converter->imageToWidget(newImageStillPoint) +
converter->documentOffset();
} else {
QSize newDocumentSize = d->canvasController.documentSize();
newStillPoint = QPointF(0.5 * newDocumentSize.width(), 0.5 * newDocumentSize.height());
}
d->canvasController.setPreferredCenter(oldPreferredCenter - oldStillPoint + newStillPoint);
}
void KisView::syncLastActiveNodeToDocument()
{
KisDocument *doc = document();
if (doc) {
doc->setPreActivatedNode(d->currentNode);
}
}
void KisView::saveViewState(KisPropertiesConfiguration &config) const
{
config.setProperty("file", d->document->url());
config.setProperty("window", mainWindow()->windowStateConfig().name());
if (d->subWindow) {
config.setProperty("geometry", d->subWindow->saveGeometry().toBase64());
}
config.setProperty("zoomMode", (int)zoomController()->zoomMode());
config.setProperty("zoom", d->canvas.coordinatesConverter()->zoom());
d->canvasController.saveCanvasState(config);
}
void KisView::restoreViewState(const KisPropertiesConfiguration &config)
{
if (d->subWindow) {
QByteArray geometry = QByteArray::fromBase64(config.getString("geometry", "").toLatin1());
d->subWindow->restoreGeometry(QByteArray::fromBase64(geometry));
}
qreal zoom = config.getFloat("zoom", 1.0f);
int zoomMode = config.getInt("zoomMode", (int)KoZoomMode::ZOOM_PAGE);
d->zoomManager.zoomController()->setZoom((KoZoomMode::Mode)zoomMode, zoom);
d->canvasController.restoreCanvasState(config);
}
void KisView::setCurrentNode(KisNodeSP node)
{
d->currentNode = node;
d->canvas.slotTrySwitchShapeManager();
syncLastActiveNodeToDocument();
}
KisNodeSP KisView::currentNode() const
{
return d->currentNode;
}
KisLayerSP KisView::currentLayer() const
{
KisNodeSP node;
KisMaskSP mask = currentMask();
if (mask) {
node = mask->parent();
}
else {
node = d->currentNode;
}
return qobject_cast(node.data());
}
KisMaskSP KisView::currentMask() const
{
return dynamic_cast(d->currentNode.data());
}
KisSelectionSP KisView::selection()
{
KisLayerSP layer = currentLayer();
if (layer)
return layer->selection(); // falls through to the global
// selection, or 0 in the end
if (image()) {
return image()->globalSelection();
}
return 0;
}
void KisView::slotSoftProofing(bool softProofing)
{
d->softProofing = softProofing;
QString message;
if (canvasBase()->image()->colorSpace()->colorDepthId().id().contains("F"))
{
message = i18n("Soft Proofing doesn't work in floating point.");
viewManager()->showFloatingMessage(message,QIcon());
return;
}
if (softProofing){
message = i18n("Soft Proofing turned on.");
} else {
message = i18n("Soft Proofing turned off.");
}
viewManager()->showFloatingMessage(message,QIcon());
canvasBase()->slotSoftProofing(softProofing);
}
void KisView::slotGamutCheck(bool gamutCheck)
{
d->gamutCheck = gamutCheck;
QString message;
if (canvasBase()->image()->colorSpace()->colorDepthId().id().contains("F"))
{
message = i18n("Gamut Warnings don't work in floating point.");
viewManager()->showFloatingMessage(message,QIcon());
return;
}
if (gamutCheck){
message = i18n("Gamut Warnings turned on.");
if (!d->softProofing){
message += "\n "+i18n("But Soft Proofing is still off.");
}
} else {
message = i18n("Gamut Warnings turned off.");
}
viewManager()->showFloatingMessage(message,QIcon());
canvasBase()->slotGamutCheck(gamutCheck);
}
bool KisView::softProofing()
{
return d->softProofing;
}
bool KisView::gamutCheck()
{
return d->gamutCheck;
}
void KisView::slotLoadingFinished()
{
if (!document()) return;
/**
* Cold-start of image size/resolution signals
*/
slotImageResolutionChanged();
if (image()->locked()) {
// If this is the first view on the image, the image will have been locked
// so unlock it.
image()->blockSignals(false);
image()->unlock();
}
canvasBase()->initializeImage();
/**
* Dirty hack alert
*/
d->zoomManager.zoomController()->setAspectMode(true);
if (viewConverter()) {
viewConverter()->setZoomMode(KoZoomMode::ZOOM_PAGE);
}
connect(image(), SIGNAL(sigColorSpaceChanged(const KoColorSpace*)), this, SIGNAL(sigColorSpaceChanged(const KoColorSpace*)));
connect(image(), SIGNAL(sigProfileChanged(const KoColorProfile*)), this, SIGNAL(sigProfileChanged(const KoColorProfile*)));
connect(image(), SIGNAL(sigSizeChanged(QPointF,QPointF)), this, SIGNAL(sigSizeChanged(QPointF,QPointF)));
KisNodeSP activeNode = document()->preActivatedNode();
if (!activeNode) {
activeNode = image()->rootLayer()->lastChild();
}
while (activeNode && !activeNode->inherits("KisLayer")) {
activeNode = activeNode->prevSibling();
}
setCurrentNode(activeNode);
zoomManager()->updateImageBoundsSnapping();
}
void KisView::slotSavingFinished()
{
if (d->viewManager && d->viewManager->mainWindow()) {
d->viewManager->mainWindow()->updateCaption();
}
}
KisPrintJob * KisView::createPrintJob()
{
return new KisPrintJob(image());
}
void KisView::slotImageResolutionChanged()
{
resetImageSizeAndScroll(false);
zoomManager()->updateImageBoundsSnapping();
zoomManager()->updateGUI();
// update KoUnit value for the document
if (resourceProvider()) {
resourceProvider()->resourceManager()->
setResource(KoCanvasResourceManager::Unit, d->canvas.unit());
}
}
void KisView::slotImageSizeChanged(const QPointF &oldStillPoint, const QPointF &newStillPoint)
{
resetImageSizeAndScroll(true, oldStillPoint, newStillPoint);
zoomManager()->updateImageBoundsSnapping();
zoomManager()->updateGUI();
}
void KisView::closeView()
{
d->subWindow->close();
}
diff --git a/libs/ui/kis_painting_assistant.cc b/libs/ui/kis_painting_assistant.cc
index 0d315b175c..0845eda282 100644
--- a/libs/ui/kis_painting_assistant.cc
+++ b/libs/ui/kis_painting_assistant.cc
@@ -1,822 +1,823 @@
/*
* Copyright (c) 2008,2011 Cyrille Berger
* Copyright (c) 2010 Geoffry Song
* Copyright (c) 2017 Scott Petrovic
*
* 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
#include "kis_painting_assistant.h"
#include "kis_coordinates_converter.h"
#include "kis_debug.h"
+#include "kis_dom_utils.h"
#include
#include "kis_tool.h"
#include
#include
#include
#include
#include
#include
#include
Q_GLOBAL_STATIC(KisPaintingAssistantFactoryRegistry, s_instance)
struct KisPaintingAssistantHandle::Private {
QList assistants;
char handle_type;
};
KisPaintingAssistantHandle::KisPaintingAssistantHandle(double x, double y) : QPointF(x, y), d(new Private)
{
}
KisPaintingAssistantHandle::KisPaintingAssistantHandle(QPointF p) : QPointF(p), d(new Private)
{
}
KisPaintingAssistantHandle::KisPaintingAssistantHandle(const KisPaintingAssistantHandle& rhs)
: QPointF(rhs)
, KisShared()
, d(new Private)
{
}
KisPaintingAssistantHandle& KisPaintingAssistantHandle::operator=(const QPointF & pt)
{
setX(pt.x());
setY(pt.y());
return *this;
}
void KisPaintingAssistantHandle::setType(char type)
{
d->handle_type = type;
}
char KisPaintingAssistantHandle::handleType()
{
return d->handle_type;
}
KisPaintingAssistantHandle::~KisPaintingAssistantHandle()
{
Q_ASSERT(d->assistants.empty());
delete d;
}
void KisPaintingAssistantHandle::registerAssistant(KisPaintingAssistant* assistant)
{
Q_ASSERT(!d->assistants.contains(assistant));
d->assistants.append(assistant);
}
void KisPaintingAssistantHandle::unregisterAssistant(KisPaintingAssistant* assistant)
{
d->assistants.removeOne(assistant);
Q_ASSERT(!d->assistants.contains(assistant));
}
bool KisPaintingAssistantHandle::containsAssistant(KisPaintingAssistant* assistant)
{
return d->assistants.contains(assistant);
}
void KisPaintingAssistantHandle::mergeWith(KisPaintingAssistantHandleSP handle)
{
if(this->handleType()== HandleType::NORMAL || handle.data()->handleType()== HandleType::SIDE) {
return;
}
Q_FOREACH (KisPaintingAssistant* assistant, handle->d->assistants) {
if (!assistant->handles().contains(this)) {
assistant->replaceHandle(handle, this);
}
}
}
void KisPaintingAssistantHandle::uncache()
{
Q_FOREACH (KisPaintingAssistant* assistant, d->assistants) {
assistant->uncache();
}
}
struct KisPaintingAssistant::Private {
QString id;
QString name;
bool isSnappingActive;
bool outlineVisible;
QList handles,sideHandles;
QPixmapCache::Key cached;
QRect cachedRect; // relative to boundingRect().topLeft()
KisPaintingAssistantHandleSP topLeft, bottomLeft, topRight, bottomRight, topMiddle, bottomMiddle, rightMiddle, leftMiddle;
KisCanvas2* m_canvas = 0;
struct TranslationInvariantTransform {
qreal m11, m12, m21, m22;
TranslationInvariantTransform() { }
TranslationInvariantTransform(const QTransform& t) : m11(t.m11()), m12(t.m12()), m21(t.m21()), m22(t.m22()) { }
bool operator==(const TranslationInvariantTransform& b) {
return m11 == b.m11 && m12 == b.m12 && m21 == b.m21 && m22 == b.m22;
}
} cachedTransform;
QColor assistantGlobalColor; // color to paint with if a custom color is not set
bool useCustomColor = false;
QColor assistantCustomColor;
};
void KisPaintingAssistant::setAssistantGlobalColor(QColor color)
{
d->assistantGlobalColor = color;
}
bool KisPaintingAssistant::useCustomColor()
{
return d->useCustomColor;
}
void KisPaintingAssistant::setUseCustomColor(bool useCustomColor)
{
d->useCustomColor = useCustomColor;
}
void KisPaintingAssistant::setAssistantCustomColor(QColor color)
{
d->assistantCustomColor = color;
}
QColor KisPaintingAssistant::assistantsGlobalColor()
{
return d->assistantGlobalColor;
}
QColor KisPaintingAssistant::assistantCustomColor()
{
return d->assistantCustomColor;
}
KisPaintingAssistant::KisPaintingAssistant(const QString& id, const QString& name) : d(new Private)
{
d->id = id;
d->name = name;
d->isSnappingActive = true;
d->outlineVisible = true;
}
bool KisPaintingAssistant::isSnappingActive() const
{
return d->isSnappingActive;
}
void KisPaintingAssistant::setSnappingActive(bool set)
{
d->isSnappingActive = set;
}
void KisPaintingAssistant::drawPath(QPainter& painter, const QPainterPath &path, bool isSnappingOn)
{
QColor paintingColor = useCustomColor() ? assistantCustomColor() : d->assistantGlobalColor;
int alpha = paintingColor.alpha();
if (!isSnappingOn) {
alpha = alpha *0.2;
}
painter.save();
QPen pen_a(QColor(paintingColor.red(), paintingColor.green(), paintingColor.blue(), alpha), 2);
pen_a.setCosmetic(true);
painter.setPen(pen_a);
painter.drawPath(path);
painter.restore();
}
void KisPaintingAssistant::drawPreview(QPainter& painter, const QPainterPath &path)
{
QColor paintingColor = useCustomColor() ? assistantCustomColor() : d->assistantGlobalColor;
painter.save();
QPen pen_a(paintingColor, 1);
pen_a.setStyle(Qt::SolidLine);
pen_a.setCosmetic(true);
painter.setPen(pen_a);
painter.drawPath(path);
painter.restore();
}
void KisPaintingAssistant::initHandles(QList _handles)
{
Q_ASSERT(d->handles.isEmpty());
d->handles = _handles;
Q_FOREACH (KisPaintingAssistantHandleSP handle, _handles) {
handle->registerAssistant(this);
}
}
KisPaintingAssistant::~KisPaintingAssistant()
{
Q_FOREACH (KisPaintingAssistantHandleSP handle, d->handles) {
handle->unregisterAssistant(this);
}
if(!d->sideHandles.isEmpty()) {
Q_FOREACH (KisPaintingAssistantHandleSP handle, d->sideHandles) {
handle->unregisterAssistant(this);
}
}
delete d;
}
const QString& KisPaintingAssistant::id() const
{
return d->id;
}
const QString& KisPaintingAssistant::name() const
{
return d->name;
}
void KisPaintingAssistant::replaceHandle(KisPaintingAssistantHandleSP _handle, KisPaintingAssistantHandleSP _with)
{
Q_ASSERT(d->handles.contains(_handle));
d->handles.replace(d->handles.indexOf(_handle), _with);
Q_ASSERT(!d->handles.contains(_handle));
_handle->unregisterAssistant(this);
_with->registerAssistant(this);
}
void KisPaintingAssistant::addHandle(KisPaintingAssistantHandleSP handle, HandleType type)
{
Q_ASSERT(!d->handles.contains(handle));
if (HandleType::SIDE == type) {
d->sideHandles.append(handle);
} else {
d->handles.append(handle);
}
handle->registerAssistant(this);
handle.data()->setType(type);
}
void KisPaintingAssistant::drawAssistant(QPainter& gc, const QRectF& updateRect, const KisCoordinatesConverter* converter, bool useCache, KisCanvas2* canvas, bool assistantVisible, bool previewVisible)
{
Q_UNUSED(updateRect);
Q_UNUSED(previewVisible);
findPerspectiveAssistantHandleLocation();
if (!useCache) {
gc.save();
drawCache(gc, converter, assistantVisible);
gc.restore();
return;
}
const QRect bound = boundingRect();
if (bound.isEmpty()) {
return;
}
const QTransform transform = converter->documentToWidgetTransform();
const QRect widgetBound = transform.mapRect(bound);
const QRect paintRect = transform.mapRect(bound).intersected(gc.viewport());
if (paintRect.isEmpty()) return;
QPixmap cached;
bool found = QPixmapCache::find(d->cached, &cached);
if (!(found && d->cachedTransform == transform && d->cachedRect.translated(widgetBound.topLeft()).contains(paintRect))) {
const QRect cacheRect = gc.viewport().adjusted(-100, -100, 100, 100).intersected(widgetBound);
Q_ASSERT(!cacheRect.isEmpty());
if (cached.isNull() || cached.size() != cacheRect.size()) {
cached = QPixmap(cacheRect.size());
}
cached.fill(Qt::transparent);
QPainter painter(&cached);
painter.setRenderHint(QPainter::Antialiasing);
painter.setWindow(cacheRect);
drawCache(painter, converter, assistantVisible);
painter.end();
d->cachedTransform = transform;
d->cachedRect = cacheRect.translated(-widgetBound.topLeft());
d->cached = QPixmapCache::insert(cached);
}
gc.drawPixmap(paintRect, cached, paintRect.translated(-widgetBound.topLeft() - d->cachedRect.topLeft()));
if (canvas) {
d->m_canvas = canvas;
}
}
void KisPaintingAssistant::uncache()
{
d->cached = QPixmapCache::Key();
}
QRect KisPaintingAssistant::boundingRect() const
{
QRectF r;
Q_FOREACH (KisPaintingAssistantHandleSP h, handles()) {
r = r.united(QRectF(*h, QSizeF(1,1)));
}
return r.adjusted(-2, -2, 2, 2).toAlignedRect();
}
bool KisPaintingAssistant::isAssistantComplete() const
{
return true;
}
QByteArray KisPaintingAssistant::saveXml(QMap &handleMap)
{
QByteArray data;
QXmlStreamWriter xml(&data);
xml.writeStartDocument();
xml.writeStartElement("assistant");
xml.writeAttribute("type",d->id);
xml.writeAttribute("active", QString::number(d->isSnappingActive));
xml.writeAttribute("useCustomColor", QString::number(d->useCustomColor));
- xml.writeAttribute("customColor", KisPaintingAssistantsDecoration::qColorToQString(d->assistantCustomColor));
+ xml.writeAttribute("customColor", KisDomUtils::qColorToQString(d->assistantCustomColor));
saveCustomXml(&xml); // if any specific assistants have custom XML data to save to
// write individual handle data
xml.writeStartElement("handles");
Q_FOREACH (const KisPaintingAssistantHandleSP handle, d->handles) {
int id = handleMap.size();
if (!handleMap.contains(handle)){
handleMap.insert(handle, id);
}
id = handleMap.value(handle);
xml.writeStartElement("handle");
xml.writeAttribute("id", QString::number(id));
xml.writeAttribute("x", QString::number(double(handle->x()), 'f', 3));
xml.writeAttribute("y", QString::number(double(handle->y()), 'f', 3));
xml.writeEndElement();
}
xml.writeEndElement();
xml.writeEndElement();
xml.writeEndDocument();
return data;
}
void KisPaintingAssistant::saveCustomXml(QXmlStreamWriter* xml)
{
Q_UNUSED(xml);
}
void KisPaintingAssistant::loadXml(KoStore* store, QMap &handleMap, QString path)
{
int id = 0;
double x = 0.0, y = 0.0;
store->open(path);
QByteArray data = store->read(store->size());
QXmlStreamReader xml(data);
while (!xml.atEnd()) {
switch (xml.readNext()) {
case QXmlStreamReader::StartElement:
if (xml.name() == "assistant") {
QStringRef active = xml.attributes().value("active");
setSnappingActive( (active != "0") );
// load custom shared assistant properties
if ( xml.attributes().hasAttribute("useCustomColor")) {
QStringRef useCustomColor = xml.attributes().value("useCustomColor");
bool usingColor = false;
if (useCustomColor.toString() == "1") {
usingColor = true;
}
+
setUseCustomColor(usingColor);
}
if ( xml.attributes().hasAttribute("customColor")) {
QStringRef customColor = xml.attributes().value("customColor");
-
- setAssistantCustomColor( KisPaintingAssistantsDecoration::qStringToQColor(customColor.toString()) );
+ setAssistantCustomColor( KisDomUtils::qStringToQColor(customColor.toString()) );
}
}
loadCustomXml(&xml);
if (xml.name() == "handle") {
QString strId = xml.attributes().value("id").toString(),
strX = xml.attributes().value("x").toString(),
strY = xml.attributes().value("y").toString();
if (!strId.isEmpty() && !strX.isEmpty() && !strY.isEmpty()) {
id = strId.toInt();
x = strX.toDouble();
y = strY.toDouble();
if (!handleMap.contains(id)) {
handleMap.insert(id, new KisPaintingAssistantHandle(x, y));
}
}
addHandle(handleMap.value(id), HandleType::NORMAL);
}
break;
default:
break;
}
}
store->close();
}
bool KisPaintingAssistant::loadCustomXml(QXmlStreamReader* xml)
{
Q_UNUSED(xml);
return true;
}
void KisPaintingAssistant::saveXmlList(QDomDocument& doc, QDomElement& assistantsElement,int count)
{
if (d->id == "ellipse"){
QDomElement assistantElement = doc.createElement("assistant");
assistantElement.setAttribute("type", "ellipse");
assistantElement.setAttribute("filename", QString("ellipse%1.assistant").arg(count));
assistantsElement.appendChild(assistantElement);
}
else if (d->id == "spline"){
QDomElement assistantElement = doc.createElement("assistant");
assistantElement.setAttribute("type", "spline");
assistantElement.setAttribute("filename", QString("spline%1.assistant").arg(count));
assistantsElement.appendChild(assistantElement);
}
else if (d->id == "perspective"){
QDomElement assistantElement = doc.createElement("assistant");
assistantElement.setAttribute("type", "perspective");
assistantElement.setAttribute("filename", QString("perspective%1.assistant").arg(count));
assistantsElement.appendChild(assistantElement);
}
else if (d->id == "vanishing point"){
QDomElement assistantElement = doc.createElement("assistant");
assistantElement.setAttribute("type", "vanishing point");
assistantElement.setAttribute("filename", QString("vanishing point%1.assistant").arg(count));
assistantsElement.appendChild(assistantElement);
}
else if (d->id == "infinite ruler"){
QDomElement assistantElement = doc.createElement("assistant");
assistantElement.setAttribute("type", "infinite ruler");
assistantElement.setAttribute("filename", QString("infinite ruler%1.assistant").arg(count));
assistantsElement.appendChild(assistantElement);
}
else if (d->id == "parallel ruler"){
QDomElement assistantElement = doc.createElement("assistant");
assistantElement.setAttribute("type", "parallel ruler");
assistantElement.setAttribute("filename", QString("parallel ruler%1.assistant").arg(count));
assistantsElement.appendChild(assistantElement);
}
else if (d->id == "concentric ellipse"){
QDomElement assistantElement = doc.createElement("assistant");
assistantElement.setAttribute("type", "concentric ellipse");
assistantElement.setAttribute("filename", QString("concentric ellipse%1.assistant").arg(count));
assistantsElement.appendChild(assistantElement);
}
else if (d->id == "fisheye-point"){
QDomElement assistantElement = doc.createElement("assistant");
assistantElement.setAttribute("type", "fisheye-point");
assistantElement.setAttribute("filename", QString("fisheye-point%1.assistant").arg(count));
assistantsElement.appendChild(assistantElement);
}
else if (d->id == "ruler"){
QDomElement assistantElement = doc.createElement("assistant");
assistantElement.setAttribute("type", "ruler");
assistantElement.setAttribute("filename", QString("ruler%1.assistant").arg(count));
assistantsElement.appendChild(assistantElement);
}
}
void KisPaintingAssistant::findPerspectiveAssistantHandleLocation() {
QList hHandlesList;
QList vHandlesList;
uint vHole = 0,hHole = 0;
KisPaintingAssistantHandleSP oppHandle;
if (d->handles.size() == 4 && d->id == "perspective") {
//get the handle opposite to the first handle
oppHandle = oppHandleOne();
//Sorting handles into two list, X sorted and Y sorted into hHandlesList and vHandlesList respectively.
Q_FOREACH (const KisPaintingAssistantHandleSP handle,d->handles) {
hHandlesList.append(handle);
hHole = hHandlesList.size() - 1;
vHandlesList.append(handle);
vHole = vHandlesList.size() - 1;
/*
sort handles on the basis of X-coordinate
*/
while(hHole > 0 && hHandlesList.at(hHole -1).data()->x() > handle.data()->x()) {
hHandlesList.swap(hHole-1, hHole);
hHole = hHole - 1;
}
/*
sort handles on the basis of Y-coordinate
*/
while(vHole > 0 && vHandlesList.at(vHole -1).data()->y() > handle.data()->y()) {
vHandlesList.swap(vHole-1, vHole);
vHole = vHole - 1;
}
}
/*
give the handles their respective positions
*/
if(vHandlesList.at(0).data()->x() > vHandlesList.at(1).data()->x()) {
d->topLeft = vHandlesList.at(1);
d->topRight= vHandlesList.at(0);
}
else {
d->topLeft = vHandlesList.at(0);
d->topRight = vHandlesList.at(1);
}
if(vHandlesList.at(2).data()->x() > vHandlesList.at(3).data()->x()) {
d->bottomLeft = vHandlesList.at(3);
d->bottomRight = vHandlesList.at(2);
}
else {
d->bottomLeft= vHandlesList.at(2);
d->bottomRight = vHandlesList.at(3);
}
/*
find if the handles that should be opposite are actually oppositely positioned
*/
if (( (d->topLeft == d->handles.at(0).data() && d->bottomRight == oppHandle) ||
(d->topLeft == oppHandle && d->bottomRight == d->handles.at(0).data()) ||
(d->topRight == d->handles.at(0).data() && d->bottomLeft == oppHandle) ||
(d->topRight == oppHandle && d->bottomLeft == d->handles.at(0).data()) ) )
{}
else {
if(hHandlesList.at(0).data()->y() > hHandlesList.at(1).data()->y()) {
d->topLeft = hHandlesList.at(1);
d->bottomLeft= hHandlesList.at(0);
}
else {
d->topLeft = hHandlesList.at(0);
d->bottomLeft = hHandlesList.at(1);
}
if(hHandlesList.at(2).data()->y() > hHandlesList.at(3).data()->y()) {
d->topRight = hHandlesList.at(3);
d->bottomRight = hHandlesList.at(2);
}
else {
d->topRight= hHandlesList.at(2);
d->bottomRight = hHandlesList.at(3);
}
}
/*
Setting the middle handles as needed
*/
if(!d->bottomMiddle && !d->topMiddle && !d->leftMiddle && !d->rightMiddle) {
d->bottomMiddle = new KisPaintingAssistantHandle((d->bottomLeft.data()->x() + d->bottomRight.data()->x())*0.5,
(d->bottomLeft.data()->y() + d->bottomRight.data()->y())*0.5);
d->topMiddle = new KisPaintingAssistantHandle((d->topLeft.data()->x() + d->topRight.data()->x())*0.5,
(d->topLeft.data()->y() + d->topRight.data()->y())*0.5);
d->rightMiddle= new KisPaintingAssistantHandle((d->topRight.data()->x() + d->bottomRight.data()->x())*0.5,
(d->topRight.data()->y() + d->bottomRight.data()->y())*0.5);
d->leftMiddle= new KisPaintingAssistantHandle((d->bottomLeft.data()->x() + d->topLeft.data()->x())*0.5,
(d->bottomLeft.data()->y() + d->topLeft.data()->y())*0.5);
addHandle(d->rightMiddle.data(), HandleType::SIDE);
addHandle(d->leftMiddle.data(), HandleType::SIDE);
addHandle(d->bottomMiddle.data(), HandleType::SIDE);
addHandle(d->topMiddle.data(), HandleType::SIDE);
}
else
{
d->bottomMiddle.data()->operator =(QPointF((d->bottomLeft.data()->x() + d->bottomRight.data()->x())*0.5,
(d->bottomLeft.data()->y() + d->bottomRight.data()->y())*0.5));
d->topMiddle.data()->operator =(QPointF((d->topLeft.data()->x() + d->topRight.data()->x())*0.5,
(d->topLeft.data()->y() + d->topRight.data()->y())*0.5));
d->rightMiddle.data()->operator =(QPointF((d->topRight.data()->x() + d->bottomRight.data()->x())*0.5,
(d->topRight.data()->y() + d->bottomRight.data()->y())*0.5));
d->leftMiddle.data()->operator =(QPointF((d->bottomLeft.data()->x() + d->topLeft.data()->x())*0.5,
(d->bottomLeft.data()->y() + d->topLeft.data()->y())*0.5));
}
}
}
KisPaintingAssistantHandleSP KisPaintingAssistant::oppHandleOne()
{
QPointF intersection(0,0);
if((QLineF(d->handles.at(0).data()->toPoint(),d->handles.at(1).data()->toPoint()).intersect(QLineF(d->handles.at(2).data()->toPoint(),d->handles.at(3).data()->toPoint()), &intersection) != QLineF::NoIntersection)
&& (QLineF(d->handles.at(0).data()->toPoint(),d->handles.at(1).data()->toPoint()).intersect(QLineF(d->handles.at(2).data()->toPoint(),d->handles.at(3).data()->toPoint()), &intersection) != QLineF::UnboundedIntersection))
{
return d->handles.at(1);
}
else if((QLineF(d->handles.at(0).data()->toPoint(),d->handles.at(2).data()->toPoint()).intersect(QLineF(d->handles.at(1).data()->toPoint(),d->handles.at(3).data()->toPoint()), &intersection) != QLineF::NoIntersection)
&& (QLineF(d->handles.at(0).data()->toPoint(),d->handles.at(2).data()->toPoint()).intersect(QLineF(d->handles.at(1).data()->toPoint(),d->handles.at(3).data()->toPoint()), &intersection) != QLineF::UnboundedIntersection))
{
return d->handles.at(2);
}
else
{
return d->handles.at(3);
}
}
KisPaintingAssistantHandleSP KisPaintingAssistant::topLeft()
{
return d->topLeft;
}
const KisPaintingAssistantHandleSP KisPaintingAssistant::topLeft() const
{
return d->topLeft;
}
KisPaintingAssistantHandleSP KisPaintingAssistant::bottomLeft()
{
return d->bottomLeft;
}
const KisPaintingAssistantHandleSP KisPaintingAssistant::bottomLeft() const
{
return d->bottomLeft;
}
KisPaintingAssistantHandleSP KisPaintingAssistant::topRight()
{
return d->topRight;
}
const KisPaintingAssistantHandleSP KisPaintingAssistant::topRight() const
{
return d->topRight;
}
KisPaintingAssistantHandleSP KisPaintingAssistant::bottomRight()
{
return d->bottomRight;
}
const KisPaintingAssistantHandleSP KisPaintingAssistant::bottomRight() const
{
return d->bottomRight;
}
KisPaintingAssistantHandleSP KisPaintingAssistant::topMiddle()
{
return d->topMiddle;
}
const KisPaintingAssistantHandleSP KisPaintingAssistant::topMiddle() const
{
return d->topMiddle;
}
KisPaintingAssistantHandleSP KisPaintingAssistant::bottomMiddle()
{
return d->bottomMiddle;
}
const KisPaintingAssistantHandleSP KisPaintingAssistant::bottomMiddle() const
{
return d->bottomMiddle;
}
KisPaintingAssistantHandleSP KisPaintingAssistant::rightMiddle()
{
return d->rightMiddle;
}
const KisPaintingAssistantHandleSP KisPaintingAssistant::rightMiddle() const
{
return d->rightMiddle;
}
KisPaintingAssistantHandleSP KisPaintingAssistant::leftMiddle()
{
return d->leftMiddle;
}
const KisPaintingAssistantHandleSP KisPaintingAssistant::leftMiddle() const
{
return d->leftMiddle;
}
const QList& KisPaintingAssistant::handles() const
{
return d->handles;
}
QList KisPaintingAssistant::handles()
{
return d->handles;
}
const QList& KisPaintingAssistant::sideHandles() const
{
return d->sideHandles;
}
QList KisPaintingAssistant::sideHandles()
{
return d->sideHandles;
}
bool KisPaintingAssistant::areTwoPointsClose(const QPointF& pointOne, const QPointF& pointTwo)
{
int m_handleSize = 16;
QRectF handlerect(pointTwo - QPointF(m_handleSize * 0.5, m_handleSize * 0.5), QSizeF(m_handleSize, m_handleSize));
return handlerect.contains(pointOne);
}
KisPaintingAssistantHandleSP KisPaintingAssistant::closestCornerHandleFromPoint(QPointF point)
{
if (!d->m_canvas) {
return 0;
}
if (areTwoPointsClose(point, pixelToView(topLeft()->toPoint()))) {
return topLeft();
} else if (areTwoPointsClose(point, pixelToView(topRight()->toPoint()))) {
return topRight();
} else if (areTwoPointsClose(point, pixelToView(bottomLeft()->toPoint()))) {
return bottomLeft();
} else if (areTwoPointsClose(point, pixelToView(bottomRight()->toPoint()))) {
return bottomRight();
}
return 0;
}
QPointF KisPaintingAssistant::pixelToView(const QPoint pixelCoords) const
{
QPointF documentCoord = d->m_canvas->image()->pixelToDocument(pixelCoords);
return d->m_canvas->viewConverter()->documentToView(documentCoord);
}
double KisPaintingAssistant::norm2(const QPointF& p)
{
return p.x() * p.x() + p.y() * p.y();
}
/*
* KisPaintingAssistantFactory classes
*/
KisPaintingAssistantFactory::KisPaintingAssistantFactory()
{
}
KisPaintingAssistantFactory::~KisPaintingAssistantFactory()
{
}
KisPaintingAssistantFactoryRegistry::KisPaintingAssistantFactoryRegistry()
{
}
KisPaintingAssistantFactoryRegistry::~KisPaintingAssistantFactoryRegistry()
{
Q_FOREACH (const QString &id, keys()) {
delete get(id);
}
dbgRegistry << "deleting KisPaintingAssistantFactoryRegistry ";
}
KisPaintingAssistantFactoryRegistry* KisPaintingAssistantFactoryRegistry::instance()
{
return s_instance;
}
diff --git a/libs/ui/kis_painting_assistants_decoration.cpp b/libs/ui/kis_painting_assistants_decoration.cpp
index 0bba1dabe4..1cd491b3c4 100644
--- a/libs/ui/kis_painting_assistants_decoration.cpp
+++ b/libs/ui/kis_painting_assistants_decoration.cpp
@@ -1,494 +1,483 @@
/*
* Copyright (c) 2009 Cyrille Berger
* Copyright (c) 2017 Scott Petrovic
*
* 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_painting_assistants_decoration.h"
#include
#include
#include
#include
#include
#include
#include "kis_debug.h"
#include "KisDocument.h"
#include "kis_canvas2.h"
#include "kis_icon_utils.h"
#include "KisViewManager.h"
#include
#include
struct KisPaintingAssistantsDecoration::Private {
Private()
: assistantVisible(false)
, outlineVisible(false)
, snapOnlyOneAssistant(true)
, firstAssistant(0)
, aFirstStroke(false)
+ , m_globalAssistantsColor(QColor(176, 176, 176, 255)),
+ m_handleSize(14)
{}
bool assistantVisible;
bool outlineVisible;
bool snapOnlyOneAssistant;
KisPaintingAssistantSP firstAssistant;
KisPaintingAssistantSP selectedAssistant;
bool aFirstStroke;
- QColor m_globalAssistantsColor = QColor(176, 176, 176, 255); // kis_assistant_tool has same default color specified
+ QColor m_globalAssistantsColor;
bool m_isEditingAssistants = false;
bool m_outlineVisible = false;
- int m_handleSize = 14; // size of editor handles on assistants
+ int m_handleSize; // size of editor handles on assistants
// move, visibility, delete icons for each assistant. These only display while the assistant tool is active
// these icons will be covered by the kis_paintint_assistant_decoration with things like the perspective assistant
AssistantEditorData toolData;
QPixmap m_iconDelete = KisIconUtils::loadIcon("dialog-cancel").pixmap(toolData.deleteIconSize, toolData.deleteIconSize);
QPixmap m_iconSnapOn = KisIconUtils::loadIcon("visible").pixmap(toolData.snapIconSize, toolData.snapIconSize);
QPixmap m_iconSnapOff = KisIconUtils::loadIcon("novisible").pixmap(toolData.snapIconSize, toolData.snapIconSize);
QPixmap m_iconMove = KisIconUtils::loadIcon("transform-move").pixmap(toolData.moveIconSize, toolData.moveIconSize);
KisCanvas2 * m_canvas = 0;
};
KisPaintingAssistantsDecoration::KisPaintingAssistantsDecoration(QPointer parent) :
KisCanvasDecoration("paintingAssistantsDecoration", parent),
d(new Private)
{
setAssistantVisible(true);
setOutlineVisible(true);
d->snapOnlyOneAssistant = true; //turn on by default.
}
KisPaintingAssistantsDecoration::~KisPaintingAssistantsDecoration()
{
delete d;
}
void KisPaintingAssistantsDecoration::addAssistant(KisPaintingAssistantSP assistant)
{
QList assistants = view()->document()->assistants();
if (assistants.contains(assistant)) return;
assistants.append(assistant);
view()->document()->setAssistants(assistants);
setVisible(!assistants.isEmpty());
emit assistantChanged();
}
void KisPaintingAssistantsDecoration::removeAssistant(KisPaintingAssistantSP assistant)
{
QList assistants = view()->document()->assistants();
KIS_ASSERT_RECOVER_NOOP(assistants.contains(assistant));
if (assistants.removeAll(assistant)) {
view()->document()->setAssistants(assistants);
setVisible(!assistants.isEmpty());
emit assistantChanged();
}
}
void KisPaintingAssistantsDecoration::removeAll()
{
QList assistants = view()->document()->assistants();
assistants.clear();
view()->document()->setAssistants(assistants);
setVisible(!assistants.isEmpty());
emit assistantChanged();
}
QPointF KisPaintingAssistantsDecoration::adjustPosition(const QPointF& point, const QPointF& strokeBegin)
{
if (assistants().empty()) {
return point;
}
if (assistants().count() == 1) {
if(assistants().first()->isSnappingActive() == true){
QPointF newpoint = assistants().first()->adjustPosition(point, strokeBegin);
// check for NaN
if (newpoint.x() != newpoint.x()) return point;
return newpoint;
}
}
QPointF best = point;
double distance = DBL_MAX;
//the following tries to find the closest point to stroke-begin. It checks all assistants for the closest point//
if(!d->snapOnlyOneAssistant){
Q_FOREACH (KisPaintingAssistantSP assistant, assistants()) {
if(assistant->isSnappingActive() == true){//this checks if the assistant in question has it's snapping boolean turned on//
QPointF pt = assistant->adjustPosition(point, strokeBegin);
if (pt.x() != pt.x()) continue;
double dist = qAbs(pt.x() - point.x()) + qAbs(pt.y() - point.y());
if (dist < distance) {
best = pt;
distance = dist;
}
}
}
} else if (d->aFirstStroke==false) {
Q_FOREACH (KisPaintingAssistantSP assistant, assistants()) {
if(assistant->isSnappingActive() == true){//this checks if the assistant in question has it's snapping boolean turned on//
QPointF pt = assistant->adjustPosition(point, strokeBegin);
if (pt.x() != pt.x()) continue;
double dist = qAbs(pt.x() - point.x()) + qAbs(pt.y() - point.y());
if (dist < distance) {
best = pt;
distance = dist;
d->firstAssistant = assistant;
}
}
}
} else if(d->firstAssistant) {
//make sure there's a first assistant to begin with.//
best = d->firstAssistant->adjustPosition(point, strokeBegin);
} else {
d->aFirstStroke=false;
}
//this is here to be compatible with the movement in the perspective tool.
qreal dx = point.x() - strokeBegin.x(), dy = point.y() - strokeBegin.y();
if (dx * dx + dy * dy >= 4.0) {
// allow some movement before snapping
d->aFirstStroke=true;
}
return best;
}
void KisPaintingAssistantsDecoration::endStroke()
{
d->aFirstStroke = false;
Q_FOREACH (KisPaintingAssistantSP assistant, assistants()) {
assistant->endStroke();
}
}
void KisPaintingAssistantsDecoration::drawDecoration(QPainter& gc, const QRectF& updateRect, const KisCoordinatesConverter *converter,KisCanvas2* canvas)
{
if(assistants().length() == 0) {
return; // no assistants to worry about, ok to exit
}
if (!canvas) {
dbgFile<<"canvas does not exist in painting assistant decoration, you may have passed arguments incorrectly:"<