diff --git a/libs/libkis/Document.cpp b/libs/libkis/Document.cpp
index 3257d9cc78..9d5ffbf5ff 100644
--- a/libs/libkis/Document.cpp
+++ b/libs/libkis/Document.cpp
@@ -1,1026 +1,1025 @@
/*
* Copyright (c) 2016 Boudewijn Rempt
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include "Document.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
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include "kis_animation_importer.h"
#include
#include
#include
#include
struct Document::Private {
Private() {}
QPointer document;
bool ownsDocument {false};
};
Document::Document(KisDocument *document, bool ownsDocument, QObject *parent)
: QObject(parent)
, d(new Private)
{
d->document = document;
d->ownsDocument = ownsDocument;
}
Document::~Document()
{
if (d->ownsDocument && d->document) {
KisPart::instance()->removeDocument(d->document);
delete d->document;
}
delete d;
}
bool Document::operator==(const Document &other) const
{
return (d->document == other.d->document);
}
bool Document::operator!=(const Document &other) const
{
return !(operator==(other));
}
bool Document::batchmode() const
{
if (!d->document) return false;
return d->document->fileBatchMode();
}
void Document::setBatchmode(bool value)
{
if (!d->document) return;
d->document->setFileBatchMode(value);
}
Node *Document::activeNode() const
{
QList activeNodes;
Q_FOREACH(QPointer view, KisPart::instance()->views()) {
if (view && view->document() == d->document) {
activeNodes << view->currentNode();
}
}
if (activeNodes.size() > 0) {
QList nodes = LibKisUtils::createNodeList(activeNodes, d->document->image());
return nodes.first();
}
return 0;
}
void Document::setActiveNode(Node* value)
{
if (!value->node()) return;
KisMainWindow *mainWin = KisPart::instance()->currentMainwindow();
if (!mainWin) return;
KisViewManager *viewManager = mainWin->viewManager();
if (!viewManager) return;
if (viewManager->document() != d->document) return;
KisNodeManager *nodeManager = viewManager->nodeManager();
if (!nodeManager) return;
KisNodeSelectionAdapter *selectionAdapter = nodeManager->nodeSelectionAdapter();
if (!selectionAdapter) return;
selectionAdapter->setActiveNode(value->node());
}
QList Document::topLevelNodes() const
{
if (!d->document) return QList();
Node n(d->document->image(), d->document->image()->rootLayer());
return n.childNodes();
}
Node *Document::nodeByName(const QString &name) const
{
if (!d->document) return 0;
KisNodeSP node = d->document->image()->rootLayer()->findChildByName(name);
if (node.isNull()) return 0;
return Node::createNode(d->document->image(), node);
}
QString Document::colorDepth() const
{
if (!d->document) return "";
return d->document->image()->colorSpace()->colorDepthId().id();
}
QString Document::colorModel() const
{
if (!d->document) return "";
return d->document->image()->colorSpace()->colorModelId().id();
}
QString Document::colorProfile() const
{
if (!d->document) return "";
return d->document->image()->colorSpace()->profile()->name();
}
bool Document::setColorProfile(const QString &value)
{
if (!d->document) return false;
if (!d->document->image()) return false;
const KoColorProfile *profile = KoColorSpaceRegistry::instance()->profileByName(value);
if (!profile) return false;
bool retval = d->document->image()->assignImageProfile(profile);
d->document->image()->waitForDone();
return retval;
}
bool Document::setColorSpace(const QString &colorModel, const QString &colorDepth, const QString &colorProfile)
{
if (!d->document) return false;
if (!d->document->image()) return false;
const KoColorSpace *colorSpace = KoColorSpaceRegistry::instance()->colorSpace(colorModel, colorDepth, colorProfile);
if (!colorSpace) return false;
d->document->image()->convertImageColorSpace(colorSpace,
KoColorConversionTransformation::IntentPerceptual,
KoColorConversionTransformation::HighQuality | KoColorConversionTransformation::NoOptimization);
d->document->image()->waitForDone();
return true;
}
QColor Document::backgroundColor()
{
if (!d->document) return QColor();
if (!d->document->image()) return QColor();
const KoColor color = d->document->image()->defaultProjectionColor();
return color.toQColor();
}
bool Document::setBackgroundColor(const QColor &color)
{
if (!d->document) return false;
if (!d->document->image()) return false;
KoColor background = KoColor(color, d->document->image()->colorSpace());
d->document->image()->setDefaultProjectionColor(background);
d->document->image()->setModified();
d->document->image()->initialRefreshGraph();
return true;
}
QString Document::documentInfo() const
{
QDomDocument doc = KisDocument::createDomDocument("document-info"
/*DTD name*/, "document-info" /*tag name*/, "1.1");
doc = d->document->documentInfo()->save(doc);
return doc.toString();
}
void Document::setDocumentInfo(const QString &document)
{
KoXmlDocument doc;
QString errorMsg;
int errorLine, errorColumn;
doc.setContent(document, &errorMsg, &errorLine, &errorColumn);
d->document->documentInfo()->load(doc);
}
QString Document::fileName() const
{
if (!d->document) return QString();
return d->document->url().toLocalFile();
}
void Document::setFileName(QString value)
{
if (!d->document) return;
QString mimeType = KisMimeDatabase::mimeTypeForFile(value, false);
d->document->setMimeType(mimeType.toLatin1());
d->document->setUrl(QUrl::fromLocalFile(value));
}
int Document::height() const
{
if (!d->document) return 0;
KisImageSP image = d->document->image();
if (!image) return 0;
return image->height();
}
void Document::setHeight(int value)
{
if (!d->document) return;
if (!d->document->image()) return;
resizeImage(d->document->image()->bounds().x(),
d->document->image()->bounds().y(),
d->document->image()->width(),
value);
}
QString Document::name() const
{
if (!d->document) return "";
return d->document->documentInfo()->aboutInfo("title");
}
void Document::setName(QString value)
{
if (!d->document) return;
d->document->documentInfo()->setAboutInfo("title", value);
}
int Document::resolution() const
{
if (!d->document) return 0;
KisImageSP image = d->document->image();
if (!image) return 0;
return qRound(d->document->image()->xRes() * 72);
}
void Document::setResolution(int value)
{
if (!d->document) return;
KisImageSP image = d->document->image();
if (!image) return;
KisFilterStrategy *strategy = KisFilterStrategyRegistry::instance()->get("Bicubic");
KIS_SAFE_ASSERT_RECOVER_RETURN(strategy);
image->scaleImage(image->size(), value / 72.0, value / 72.0, strategy);
image->waitForDone();
}
Node *Document::rootNode() const
{
if (!d->document) return 0;
KisImageSP image = d->document->image();
if (!image) return 0;
return Node::createNode(image, image->root());
}
Selection *Document::selection() const
{
if (!d->document) return 0;
if (!d->document->image()) return 0;
if (!d->document->image()->globalSelection()) return 0;
return new Selection(d->document->image()->globalSelection());
}
void Document::setSelection(Selection* value)
{
if (!d->document) return;
if (!d->document->image()) return;
if (value) {
d->document->image()->setGlobalSelection(value->selection());
}
else {
d->document->image()->setGlobalSelection(0);
}
}
int Document::width() const
{
if (!d->document) return 0;
KisImageSP image = d->document->image();
if (!image) return 0;
return image->width();
}
void Document::setWidth(int value)
{
if (!d->document) return;
if (!d->document->image()) return;
resizeImage(d->document->image()->bounds().x(),
d->document->image()->bounds().y(),
value,
d->document->image()->height());
}
int Document::xOffset() const
{
if (!d->document) return 0;
KisImageSP image = d->document->image();
if (!image) return 0;
return image->bounds().x();
}
void Document::setXOffset(int x)
{
if (!d->document) return;
if (!d->document->image()) return;
resizeImage(x,
d->document->image()->bounds().y(),
d->document->image()->width(),
d->document->image()->height());
}
int Document::yOffset() const
{
if (!d->document) return 0;
KisImageSP image = d->document->image();
if (!image) return 0;
return image->bounds().y();
}
void Document::setYOffset(int y)
{
if (!d->document) return;
if (!d->document->image()) return;
resizeImage(d->document->image()->bounds().x(),
y,
d->document->image()->width(),
d->document->image()->height());
}
double Document::xRes() const
{
if (!d->document) return 0.0;
if (!d->document->image()) return 0.0;
return d->document->image()->xRes()*72.0;
}
void Document::setXRes(double xRes) const
{
if (!d->document) return;
KisImageSP image = d->document->image();
if (!image) return;
KisFilterStrategy *strategy = KisFilterStrategyRegistry::instance()->get("Bicubic");
KIS_SAFE_ASSERT_RECOVER_RETURN(strategy);
image->scaleImage(image->size(), xRes / 72.0, image->yRes(), strategy);
image->waitForDone();
}
double Document::yRes() const
{
if (!d->document) return 0.0;
if (!d->document->image()) return 0.0;
return d->document->image()->yRes()*72.0;
}
void Document::setYRes(double yRes) const
{
if (!d->document) return;
KisImageSP image = d->document->image();
if (!image) return;
KisFilterStrategy *strategy = KisFilterStrategyRegistry::instance()->get("Bicubic");
KIS_SAFE_ASSERT_RECOVER_RETURN(strategy);
image->scaleImage(image->size(), image->xRes(), yRes / 72.0, strategy);
image->waitForDone();
}
QByteArray Document::pixelData(int x, int y, int w, int h) const
{
QByteArray ba;
if (!d->document) return ba;
KisImageSP image = d->document->image();
if (!image) return ba;
KisPaintDeviceSP dev = image->projection();
ba.resize(w * h * dev->pixelSize());
dev->readBytes(reinterpret_cast(ba.data()), x, y, w, h);
return ba;
}
bool Document::close()
{
bool retval = d->document->closeUrl(false);
Q_FOREACH(KisView *view, KisPart::instance()->views()) {
if (view->document() == d->document) {
view->close();
view->closeView();
view->deleteLater();
}
}
KisPart::instance()->removeDocument(d->document, !d->ownsDocument);
if (d->ownsDocument) {
delete d->document;
}
d->document = 0;
return retval;
}
void Document::crop(int x, int y, int w, int h)
{
if (!d->document) return;
KisImageSP image = d->document->image();
if (!image) return;
QRect rc(x, y, w, h);
image->cropImage(rc);
image->waitForDone();
}
bool Document::exportImage(const QString &filename, const InfoObject &exportConfiguration)
{
if (!d->document) return false;
const QString outputFormatString = KisMimeDatabase::mimeTypeForFile(filename, false);
const QByteArray outputFormat = outputFormatString.toLatin1();
return d->document->exportDocumentSync(QUrl::fromLocalFile(filename), outputFormat, exportConfiguration.configuration());
}
void Document::flatten()
{
if (!d->document) return;
if (!d->document->image()) return;
d->document->image()->flatten(0);
d->document->image()->waitForDone();
}
void Document::resizeImage(int x, int y, int w, int h)
{
if (!d->document) return;
KisImageSP image = d->document->image();
if (!image) return;
QRect rc;
rc.setX(x);
rc.setY(y);
rc.setWidth(w);
rc.setHeight(h);
image->resizeImage(rc);
image->waitForDone();
}
void Document::scaleImage(int w, int h, int xres, int yres, QString strategy)
{
if (!d->document) return;
KisImageSP image = d->document->image();
if (!image) return;
QRect rc = image->bounds();
rc.setWidth(w);
rc.setHeight(h);
KisFilterStrategy *actualStrategy = KisFilterStrategyRegistry::instance()->get(strategy);
if (!actualStrategy) actualStrategy = KisFilterStrategyRegistry::instance()->get("Bicubic");
image->scaleImage(rc.size(), xres/72, yres/72, actualStrategy);
image->waitForDone();
}
void Document::rotateImage(double radians)
{
if (!d->document) return;
KisImageSP image = d->document->image();
if (!image) return;
image->rotateImage(radians);
image->waitForDone();
}
void Document::shearImage(double angleX, double angleY)
{
if (!d->document) return;
KisImageSP image = d->document->image();
if (!image) return;
image->shear(angleX, angleY);
image->waitForDone();
}
bool Document::save()
{
if (!d->document) return false;
if (d->document->url().isEmpty()) return false;
bool retval = d->document->save(true, 0);
d->document->waitForSavingToComplete();
return retval;
}
bool Document::saveAs(const QString &filename)
{
if (!d->document) return false;
const QString outputFormatString = KisMimeDatabase::mimeTypeForFile(filename, false);
const QByteArray outputFormat = outputFormatString.toLatin1();
QUrl oldUrl = d->document->url();
d->document->setUrl(QUrl::fromLocalFile(filename));
bool retval = d->document->saveAs(QUrl::fromLocalFile(filename), outputFormat, true);
d->document->waitForSavingToComplete();
d->document->setUrl(oldUrl);
return retval;
}
Node* Document::createNode(const QString &name, const QString &nodeType)
{
if (!d->document) return 0;
if (!d->document->image()) return 0;
KisImageSP image = d->document->image();
Node *node = 0;
if (nodeType.toLower()== "paintlayer") {
node = new Node(image, new KisPaintLayer(image, name, OPACITY_OPAQUE_U8));
}
else if (nodeType.toLower() == "grouplayer") {
node = new Node(image, new KisGroupLayer(image, name, OPACITY_OPAQUE_U8));
}
else if (nodeType.toLower() == "filelayer") {
node = new Node(image, new KisFileLayer(image, name, OPACITY_OPAQUE_U8));
}
else if (nodeType.toLower() == "filterlayer") {
node = new Node(image, new KisAdjustmentLayer(image, name, 0, 0));
}
else if (nodeType.toLower() == "filllayer") {
node = new Node(image, new KisGeneratorLayer(image, name, 0, 0));
}
else if (nodeType.toLower() == "clonelayer") {
node = new Node(image, new KisCloneLayer(0, image, name, OPACITY_OPAQUE_U8));
}
else if (nodeType.toLower() == "vectorlayer") {
node = new Node(image, new KisShapeLayer(d->document->shapeController(), image, name, OPACITY_OPAQUE_U8));
}
else if (nodeType.toLower() == "transparencymask") {
node = new Node(image, new KisTransparencyMask(name));
}
else if (nodeType.toLower() == "filtermask") {
node = new Node(image, new KisFilterMask(name));
}
else if (nodeType.toLower() == "transformmask") {
node = new Node(image, new KisTransformMask(name));
}
else if (nodeType.toLower() == "selectionmask") {
node = new Node(image, new KisSelectionMask(image, name));
}
return node;
}
GroupLayer *Document::createGroupLayer(const QString &name)
{
if (!d->document) return 0;
if (!d->document->image()) return 0;
KisImageSP image = d->document->image();
return new GroupLayer(image, name);
}
FileLayer *Document::createFileLayer(const QString &name, const QString fileName, const QString scalingMethod)
{
if (!d->document) return 0;
if (!d->document->image()) return 0;
KisImageSP image = d->document->image();
return new FileLayer(image, name, this->fileName(), fileName, scalingMethod);
}
FilterLayer *Document::createFilterLayer(const QString &name, Filter &filter, Selection &selection)
{
if (!d->document) return 0;
if (!d->document->image()) return 0;
KisImageSP image = d->document->image();
return new FilterLayer(image, name, filter, selection);
}
FillLayer *Document::createFillLayer(const QString &name, const QString generatorName, InfoObject &configuration, Selection &selection)
{
if (!d->document) return 0;
if (!d->document->image()) return 0;
KisImageSP image = d->document->image();
KisGeneratorSP generator = KisGeneratorRegistry::instance()->value(generatorName);
if (generator) {
KisFilterConfigurationSP config = generator->factoryConfiguration(KisGlobalResourcesInterface::instance());
Q_FOREACH(const QString property, configuration.properties().keys()) {
config->setProperty(property, configuration.property(property));
}
return new FillLayer(image, name, config, selection);
}
return 0;
}
CloneLayer *Document::createCloneLayer(const QString &name, const Node *source)
{
if (!d->document) return 0;
if (!d->document->image()) return 0;
KisImageSP image = d->document->image();
KisLayerSP layer = qobject_cast(source->node().data());
return new CloneLayer(image, name, layer);
}
VectorLayer *Document::createVectorLayer(const QString &name)
{
if (!d->document) return 0;
if (!d->document->image()) return 0;
KisImageSP image = d->document->image();
return new VectorLayer(d->document->shapeController(), image, name);
}
FilterMask *Document::createFilterMask(const QString &name, Filter &filter, const Node *selection_source)
{
if (!d->document)
return 0;
if (!d->document->image())
return 0;
if(!selection_source)
return 0;
KisLayerSP layer = qobject_cast(selection_source->node().data());
if(layer.isNull())
return 0;
KisImageSP image = d->document->image();
FilterMask* mask = new FilterMask(image, name, filter);
qobject_cast(mask->node().data())->initSelection(layer);
return mask;
}
FilterMask *Document::createFilterMask(const QString &name, Filter &filter, Selection &selection)
{
if (!d->document)
return 0;
if (!d->document->image())
return 0;
KisImageSP image = d->document->image();
FilterMask* mask = new FilterMask(image, name, filter);
qobject_cast(mask->node().data())->setSelection(selection.selection());
return mask;
}
SelectionMask *Document::createSelectionMask(const QString &name)
{
if (!d->document) return 0;
if (!d->document->image()) return 0;
KisImageSP image = d->document->image();
return new SelectionMask(image, name);
}
-
QImage Document::projection(int x, int y, int w, int h) const
{
if (!d->document || !d->document->image()) return QImage();
return d->document->image()->convertToQImage(x, y, w, h, 0);
}
QImage Document::thumbnail(int w, int h) const
{
if (!d->document || !d->document->image()) return QImage();
return d->document->generatePreview(QSize(w, h)).toImage();
}
void Document::lock()
{
if (!d->document || !d->document->image()) return;
d->document->image()->barrierLock();
}
void Document::unlock()
{
if (!d->document || !d->document->image()) return;
d->document->image()->unlock();
}
void Document::waitForDone()
{
if (!d->document || !d->document->image()) return;
d->document->image()->waitForDone();
}
bool Document::tryBarrierLock()
{
if (!d->document || !d->document->image()) return false;
return d->document->image()->tryBarrierLock();
}
void Document::refreshProjection()
{
if (!d->document || !d->document->image()) return;
d->document->image()->refreshGraph();
}
QList Document::horizontalGuides() const
{
QList lines;
if (!d->document || !d->document->image()) return lines;
KisCoordinatesConverter converter;
converter.setImage(d->document->image());
QTransform transform = converter.imageToDocumentTransform().inverted();
QList untransformedLines = d->document->guidesConfig().horizontalGuideLines();
for (int i = 0; i< untransformedLines.size(); i++) {
qreal line = untransformedLines[i];
lines.append(transform.map(QPointF(line, line)).x());
}
return lines;
}
QList Document::verticalGuides() const
{
QList lines;
if (!d->document || !d->document->image()) return lines;
KisCoordinatesConverter converter;
converter.setImage(d->document->image());
QTransform transform = converter.imageToDocumentTransform().inverted();
QList untransformedLines = d->document->guidesConfig().verticalGuideLines();
for (int i = 0; i< untransformedLines.size(); i++) {
qreal line = untransformedLines[i];
lines.append(transform.map(QPointF(line, line)).y());
}
return lines;
}
bool Document::guidesVisible() const
{
return d->document->guidesConfig().showGuides();
}
bool Document::guidesLocked() const
{
return d->document->guidesConfig().lockGuides();
}
Document *Document::clone() const
{
if (!d->document) return 0;
QPointer clone = d->document->clone();
Document * newDocument = new Document(clone, d->ownsDocument);
clone->setParent(newDocument); // It's owned by the document, not KisPart
return newDocument;
}
void Document::setHorizontalGuides(const QList &lines)
{
if (!d->document) return;
KisGuidesConfig config = d->document->guidesConfig();
KisCoordinatesConverter converter;
converter.setImage(d->document->image());
QTransform transform = converter.imageToDocumentTransform();
QList transformedLines;
for (int i = 0; i< lines.size(); i++) {
qreal line = lines[i];
transformedLines.append(transform.map(QPointF(line, line)).x());
}
config.setHorizontalGuideLines(transformedLines);
d->document->setGuidesConfig(config);
}
void Document::setVerticalGuides(const QList &lines)
{
if (!d->document) return;
KisGuidesConfig config = d->document->guidesConfig();
KisCoordinatesConverter converter;
converter.setImage(d->document->image());
QTransform transform = converter.imageToDocumentTransform();
QList transformedLines;
for (int i = 0; i< lines.size(); i++) {
qreal line = lines[i];
transformedLines.append(transform.map(QPointF(line, line)).y());
}
config.setVerticalGuideLines(transformedLines);
d->document->setGuidesConfig(config);
}
void Document::setGuidesVisible(bool visible)
{
if (!d->document) return;
KisGuidesConfig config = d->document->guidesConfig();
config.setShowGuides(visible);
d->document->setGuidesConfig(config);
}
void Document::setGuidesLocked(bool locked)
{
if (!d->document) return;
KisGuidesConfig config = d->document->guidesConfig();
config.setLockGuides(locked);
d->document->setGuidesConfig(config);
}
bool Document::modified() const
{
if (!d->document) return false;
return d->document->isModified();
}
QRect Document::bounds() const
{
if (!d->document) return QRect();
return d->document->image()->bounds();
}
QPointer Document::document() const
{
return d->document;
}
void Document::setOwnsDocument(bool ownsDocument)
{
d->ownsDocument = ownsDocument;
}
/* Animation related function */
bool Document::importAnimation(const QList &files, int firstFrame, int step)
{
KisView *activeView = KisPart::instance()->currentMainwindow()->activeView();
KoUpdaterPtr updater = 0;
if (activeView && d->document->fileBatchMode()) {
updater = activeView->viewManager()->createUnthreadedUpdater(i18n("Import frames"));
}
KisAnimationImporter importer(d->document->image(), updater);
KisImportExportErrorCode status = importer.import(files, firstFrame, step);
return status.isOk();
}
int Document::framesPerSecond()
{
if (!d->document) return false;
if (!d->document->image()) return false;
return d->document->image()->animationInterface()->framerate();
}
void Document::setFramesPerSecond(int fps)
{
if (!d->document) return;
if (!d->document->image()) return;
d->document->image()->animationInterface()->setFramerate(fps);
}
void Document::setFullClipRangeStartTime(int startTime)
{
if (!d->document) return;
if (!d->document->image()) return;
d->document->image()->animationInterface()->setFullClipRangeStartTime(startTime);
}
int Document::fullClipRangeStartTime()
{
if (!d->document) return false;
if (!d->document->image()) return false;
return d->document->image()->animationInterface()->fullClipRange().start();
}
void Document::setFullClipRangeEndTime(int endTime)
{
if (!d->document) return;
if (!d->document->image()) return;
d->document->image()->animationInterface()->setFullClipRangeEndTime(endTime);
}
int Document::fullClipRangeEndTime()
{
if (!d->document) return false;
if (!d->document->image()) return false;
return d->document->image()->animationInterface()->fullClipRange().end();
}
int Document::animationLength()
{
if (!d->document) return false;
if (!d->document->image()) return false;
return d->document->image()->animationInterface()->totalLength();
}
void Document::setPlayBackRange(int start, int stop)
{
if (!d->document) return;
if (!d->document->image()) return;
const KisTimeRange newTimeRange = KisTimeRange(start, (stop-start));
d->document->image()->animationInterface()->setPlaybackRange(newTimeRange);
}
int Document::playBackStartTime()
{
if (!d->document) return false;
if (!d->document->image()) return false;
return d->document->image()->animationInterface()->playbackRange().start();
}
int Document::playBackEndTime()
{
if (!d->document) return false;
if (!d->document->image()) return false;
return d->document->image()->animationInterface()->playbackRange().end();
}
int Document::currentTime()
{
if (!d->document) return false;
if (!d->document->image()) return false;
return d->document->image()->animationInterface()->currentTime();
}
void Document::setCurrentTime(int time)
{
if (!d->document) return;
if (!d->document->image()) return;
return d->document->image()->animationInterface()->requestTimeSwitchWithUndo(time);
}
diff --git a/libs/libkis/Scratchpad.cpp b/libs/libkis/Scratchpad.cpp
index a900d2b718..798c7dcda0 100644
--- a/libs/libkis/Scratchpad.cpp
+++ b/libs/libkis/Scratchpad.cpp
@@ -1,70 +1,87 @@
/*
* Copyright (c) 2020 Scott Petrovic
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include "Scratchpad.h"
#include
#include
#include "kis_scratch_pad.h"
#include "Resource.h"
#include "View.h"
#include "Canvas.h"
#include
#include
#include
+#include
+
+
+struct Scratchpad::Private
+{
+ KisScratchPad *scratchpad = 0;
+};
+
Scratchpad::Scratchpad(View *view, const QColor & defaultColor, QWidget *parent)
- : KisScratchPad(parent)
+ : QWidget(parent), d(new Private)
{
- KisScratchPad::setupScratchPad(view->view()->resourceProvider(), defaultColor);
- KisScratchPad::setMinimumSize(50, 50);
+ d->scratchpad = new KisScratchPad();
+ d->scratchpad->setupScratchPad(view->view()->resourceProvider(), defaultColor);
+ d->scratchpad->setMinimumSize(50, 50);
+
+ setLayout(new QVBoxLayout());
+ layout()->addWidget(d->scratchpad);
}
Scratchpad::~Scratchpad()
{
}
void Scratchpad::setModeManually(bool value)
{
- KisScratchPad::setModeManually(value);
+ d->scratchpad->setModeManually(value);
}
void Scratchpad::setMode(QString modeType)
{
- KisScratchPad::setModeType(modeType);
+ d->scratchpad->setModeType(modeType);
+}
+
+void Scratchpad::linkCanvasZoom(bool value)
+{
+ d->scratchpad->linkCanvavsToZoomLevel(value);
}
-void Scratchpad::loadScratchpad(QImage image)
+void Scratchpad::loadScratchpadImage(QImage image)
{
- KisScratchPad::loadScratchpadImage(image);
+ d->scratchpad->loadScratchpadImage(image);
}
-QImage Scratchpad::copyScratchPadImage()
+QImage Scratchpad::copyScratchpadImageData()
{
- return KisScratchPad::copyScratchpadImageData();
+ return d->scratchpad->copyScratchpadImageData();
}
void Scratchpad::clear()
{
// need ability to set color
- KisScratchPad::fillDefault();
+ d->scratchpad->fillDefault();
}
void Scratchpad::setFillColor(QColor color)
{
- KisScratchPad::setFillColor(color);
+ d->scratchpad->setFillColor(color);
}
diff --git a/libs/libkis/Scratchpad.h b/libs/libkis/Scratchpad.h
index 96c79e3fb2..81cac3d9ca 100644
--- a/libs/libkis/Scratchpad.h
+++ b/libs/libkis/Scratchpad.h
@@ -1,74 +1,82 @@
/*
* Copyright (c) 2020 Scott Petrovic
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#ifndef LIBKIS_SCRATCHPAD_H
#define LIBKIS_SCRATCHPAD_H
#include
#include
#include
#include "kritalibkis_export.h"
#include "libkis.h"
#include "kis_scratch_pad.h"
#include "View.h"
class KoCanvasBase;
class Canvas; // This comes from Python. This would be maybe better
class KisView;
/**
* @brief The Scratchpad class
* A scratchpad is a type of blank canvas area that can be painted on
* with the normal painting devices
*
*/
-class KRITALIBKIS_EXPORT Scratchpad: public KisScratchPad
+class KRITALIBKIS_EXPORT Scratchpad: public QWidget
{
Q_OBJECT
public:
Scratchpad(View *view, const QColor & defaultColor, QWidget *parent = 0);
~Scratchpad();
-
public Q_SLOTS:
/**
* clears out scratchpad with color specfified set during setup
*/
void clear();
void setFillColor(QColor color);
/** Switches between a GUI controlling the current mode and when mouse clicks control mode
* Setting to true allows GUI to control the mode with explicity setting mode
*/
void setModeManually(bool value);
/// Manually set what mode scratchpad is in. Ignored if "setModeManually is set to false
void setMode(QString modeName);
+ /// Should the scratchpad share the zoom level with the canvas?
+ void linkCanvasZoom(bool value);
+
+
/// load scratchpad
- void loadScratchpad(QImage image);
+ void loadScratchpadImage(QImage image);
/// take what is on scratchpad area and grab image
- QImage copyScratchPadImage();
+ QImage copyScratchpadImageData();
+
+
+private:
+ struct Private;
+ const QScopedPointer d;
};
#endif // LIBKIS_SCRATCHPAD_H
diff --git a/libs/libkis/View.cpp b/libs/libkis/View.cpp
index 6152d92113..89b3a61dd8 100644
--- a/libs/libkis/View.cpp
+++ b/libs/libkis/View.cpp
@@ -1,298 +1,282 @@
/*
* Copyright (c) 2016 Boudewijn Rempt
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include "View.h"
#include
+#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include "Document.h"
#include "Canvas.h"
-#include "Scratchpad.h"
#include "Window.h"
#include "Resource.h"
#include "ManagedColor.h"
#include "LibKisUtils.h"
+
struct View::Private {
Private() {}
QPointer view;
-
- QList scratchpads;
};
View::View(KisView* view, QObject *parent)
: QObject(parent)
, d(new Private)
{
d->view = view;
}
View::~View()
{
delete d;
}
bool View::operator==(const View &other) const
{
return (d->view == other.d->view);
}
bool View::operator!=(const View &other) const
{
return !(operator==(other));
}
Window* View::window() const
{
if (!d->view) return 0;
KisMainWindow *mainwin = d->view->mainWindow();
Window *win = new Window(mainwin);
return win;
}
Document* View::document() const
{
if (!d->view) return 0;
Document *doc = new Document(d->view->document(), false);
return doc;
}
void View::setDocument(Document *document)
{
if (!d->view || !document || !document->document()) return;
d->view = d->view->replaceBy(document->document());
}
bool View::visible() const
{
if (!d->view) return false;
return d->view->isVisible();
}
void View::setVisible()
{
if (!d->view) return;
KisMainWindow *mainwin = d->view->mainWindow();
mainwin->setActiveView(d->view);
mainwin->subWindowActivated();
}
Canvas* View::canvas() const
{
if (!d->view) return 0;
Canvas *c = new Canvas(d->view->canvasBase());
return c;
}
KisView *View::view()
{
return d->view;
}
void View::activateResource(Resource *resource)
{
if (!d->view) return;
if (!resource) return;
KoResourceSP r = resource->resource();
if (!r) return;
if (r.dynamicCast()) {
QVariant v;
v.setValue(r);
d->view->canvasBase()->resourceManager()->setResource(KisCanvasResourceProvider::CurrentPattern, v);
}
else if (r.dynamicCast()) {
QVariant v;
v.setValue(r);
d->view->canvasBase()->resourceManager()->setResource(KisCanvasResourceProvider::CurrentGradient, v);
}
else if (r.dynamicCast()) {
d->view->viewManager()->paintOpBox()->resourceSelected(r);
}
}
-Scratchpad *View::createScratchpad(QColor bgColor)
-{
- if(view()) {
- d->scratchpads.append(new Scratchpad(this->canvas()->view(), bgColor));
- }
- return d->scratchpads.last();
-}
ManagedColor *View::foregroundColor() const
{
if (!d->view) return 0;
return new ManagedColor(d->view->resourceProvider()->fgColor());
}
void View::setForeGroundColor(ManagedColor *color)
{
if (!d->view) return;
d->view->resourceProvider()->setFGColor(color->color());
}
ManagedColor *View::backgroundColor() const
{
if (!d->view) return 0;
return new ManagedColor(d->view->resourceProvider()->bgColor());
}
void View::setBackGroundColor(ManagedColor *color)
{
if (!d->view) return;
d->view->resourceProvider()->setBGColor(color->color());
}
Resource *View::currentBrushPreset() const
{
if (!d->view) return 0;
return new Resource(d->view->resourceProvider()->currentPreset(), ResourceType::PaintOpPresets);
}
void View::setCurrentBrushPreset(Resource *resource)
{
activateResource(resource);
}
Resource *View::currentPattern() const
{
if (!d->view) return 0;
return new Resource(d->view->resourceProvider()->currentPattern(), ResourceType::Patterns);
}
void View::setCurrentPattern(Resource *resource)
{
activateResource(resource);
}
Resource *View::currentGradient() const
{
if (!d->view) return 0;
return new Resource(d->view->resourceProvider()->currentGradient(), ResourceType::Gradients);
}
void View::setCurrentGradient(Resource *resource)
{
activateResource(resource);
}
QString View::currentBlendingMode() const
{
if (!d->view) return "";
return d->view->resourceProvider()->currentCompositeOp();
}
void View::setCurrentBlendingMode(const QString &blendingMode)
{
if (!d->view) return;
d->view->resourceProvider()->setCurrentCompositeOp(blendingMode);
}
float View::HDRExposure() const
{
if (!d->view) return 0.0;
return d->view->resourceProvider()->HDRExposure();
}
void View::setHDRExposure(float exposure)
{
if (!d->view) return;
d->view->resourceProvider()->setHDRExposure(exposure);
}
float View::HDRGamma() const
{
if (!d->view) return 0.0;
return d->view->resourceProvider()->HDRGamma();
}
void View::setHDRGamma(float gamma)
{
if (!d->view) return;
d->view->resourceProvider()->setHDRGamma(gamma);
}
qreal View::paintingOpacity() const
{
if (!d->view) return 0.0;
return d->view->resourceProvider()->opacity();
}
void View::setPaintingOpacity(qreal opacity)
{
if (!d->view) return;
d->view->resourceProvider()->setOpacity(opacity);
}
qreal View::brushSize() const
{
if (!d->view) return 0.0;
return d->view->resourceProvider()->size();
}
void View::setBrushSize(qreal brushSize)
{
if (!d->view) return;
d->view->resourceProvider()->setSize(brushSize);
}
qreal View::paintingFlow() const
{
if (!d->view) return 0.0;
return d->view->resourceProvider()->flow();
}
void View::setPaintingFlow(qreal flow)
{
if (!d->view) return;
d->view->resourceProvider()->setFlow(flow);
}
QList View::selectedNodes() const
{
if (!d->view) return QList();
if (!d->view->viewManager()) return QList();
if (!d->view->viewManager()->nodeManager()) return QList();
KisNodeList selectedNodes = d->view->viewManager()->nodeManager()->selectedNodes();
return LibKisUtils::createNodeList(selectedNodes, d->view->image());
}
-
-QList View::scratchpads() const
-{
- if (!d->view) return QList();
- if (!d->view->viewManager()) return QList();
-
- return d->scratchpads;
-}
diff --git a/libs/libkis/View.h b/libs/libkis/View.h
index a70950d5c7..02ab27a548 100644
--- a/libs/libkis/View.h
+++ b/libs/libkis/View.h
@@ -1,173 +1,165 @@
/*
* Copyright (c) 2016 Boudewijn Rempt
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#ifndef LIBKIS_VIEW_H
#define LIBKIS_VIEW_H
#include
#include "kritalibkis_export.h"
#include "libkis.h"
class ManagedColor;
class Resource;
class Scratchpad;
class Node;
class KisView;
/**
* View represents one view on a document. A document can be
* shown in more than one view at a time.
*/
class KRITALIBKIS_EXPORT View : public QObject
{
Q_OBJECT
Q_DISABLE_COPY(View)
public:
explicit View(KisView *view, QObject *parent = 0);
~View() override;
bool operator==(const View &other) const;
bool operator!=(const View &other) const;
public Q_SLOTS:
/**
* @return the window this view is shown in.
*/
Window* window() const;
/**
* @return the document this view is showing.
*/
Document* document() const;
/**
* Reset the view to show @p document.
*/
void setDocument(Document *document);
/**
* @return true if the current view is visible, false if not.
*/
bool visible() const;
/**
* Make the current view visible.
*/
void setVisible();
/**
* @return the canvas this view is showing. The canvas controls
* things like zoom and rotation.
*/
Canvas* canvas() const;
/**
* @brief activateResource activates the given resource.
* @param resource: a pattern, gradient or paintop preset
*/
void activateResource(Resource *resource);
- /**
- * @brief creates a scratchpad widget to draw on.
- * It is stored in the scratchpad list for reference
- */
- Scratchpad *createScratchpad(QColor bgColor);
+
/**
* @brief foregroundColor allows access to the currently active color.
* This is nominally per canvas/view, but in practice per mainwindow.
* @code
color = Application.activeWindow().activeView().foregroundColor()
components = color.components()
components[0] = 1.0
components[1] = 0.6
components[2] = 0.7
color.setComponents(components)
Application.activeWindow().activeView().setForeGroundColor(color)
* @endcode
*/
ManagedColor *foregroundColor() const;
void setForeGroundColor(ManagedColor *color);
ManagedColor *backgroundColor() const;
void setBackGroundColor(ManagedColor *color);
Resource *currentBrushPreset() const;
void setCurrentBrushPreset(Resource *resource);
Resource *currentPattern() const;
void setCurrentPattern(Resource *resource);
Resource *currentGradient() const;
void setCurrentGradient(Resource *resource);
QString currentBlendingMode() const;
void setCurrentBlendingMode(const QString &blendingMode);
float HDRExposure() const;
void setHDRExposure(float exposure);
float HDRGamma() const;
void setHDRGamma(float gamma);
qreal paintingOpacity() const;
void setPaintingOpacity(qreal opacity);
qreal brushSize() const;
void setBrushSize(qreal brushSize);
qreal paintingFlow() const;
void setPaintingFlow(qreal flow);
/**
* @brief selectedNodes returns a list of Nodes that are selected in this view.
*
*
@code
from krita import *
w = Krita.instance().activeWindow()
v = w.activeView()
selected_nodes = v.selectedNodes()
print(selected_nodes)
@endcode
*
*
* @return a list of Node objects which may be empty.
*/
QList selectedNodes() const;
-
- /**
- * @brief Stores scratchpad widgets to draw on
- */
- QList scratchpads() const;
-
private:
friend class Window;
friend class Scratchpad;
+
+
KisView *view();
struct Private;
Private *const d;
};
#endif // LIBKIS_VIEW_H
diff --git a/libs/ui/KisDocument.cpp b/libs/ui/KisDocument.cpp
index 5b7708105b..198f337b36 100644
--- a/libs/ui/KisDocument.cpp
+++ b/libs/ui/KisDocument.cpp
@@ -1,2343 +1,2346 @@
/* 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 "kis_scratch_pad.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
// Krita Image
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include "kis_layer_utils.h"
#include "kis_selection_mask.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 "KisView.h"
#include "kis_grid_config.h"
#include "kis_guides_config.h"
#include "kis_image_barrier_lock_adapter.h"
#include "KisReferenceImagesLayer.h"
#include "dialogs/KisRecoverNamedAutosaveDialog.h"
#include
#include "kis_config_notifier.h"
#include "kis_async_action_feedback.h"
#include "KisCloneDocumentStroke.h"
#include
#include
#include "kis_simple_stroke_strategy.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)
: q(_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*/)
, globalAssistantsColor(KisConfig(true).defaultAssistantsColor())
, savingLock(&savingMutex)
, batchMode(false)
{
if (QLocale().measurementSystem() == QLocale::ImperialSystem) {
unit = KoUnit::Inch;
} else {
unit = KoUnit::Centimeter;
}
connect(&imageIdleWatcher, SIGNAL(startedIdleMode()), q, SLOT(slotPerformIdleRoutines()));
}
Private(const Private &rhs, KisDocument *_q)
: q(_q)
, docInfo(new KoDocumentInfo(*rhs.docInfo, _q))
, importExportManager(new KisImportExportManager(_q))
, autoSaveTimer(new QTimer(_q))
, undoStack(new UndoStack(_q))
, nserver(new KisNameServer(*rhs.nserver))
, preActivatedNode(0) // the node is from another hierarchy!
, imageIdleWatcher(2000 /*ms*/)
, savingLock(&savingMutex)
{
copyFromImpl(rhs, _q, CONSTRUCT);
connect(&imageIdleWatcher, SIGNAL(startedIdleMode()), q, SLOT(slotPerformIdleRoutines()));
}
~Private() {
// Don't delete m_d->shapeController because it's in a QObject hierarchy.
delete nserver;
}
KisDocument *q = 0;
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;
KisMirrorAxisConfig mirrorAxisConfig;
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 };
QString documentStorageID {QUuid::createUuid().toString()};
KisResourceStorageSP documentResourceStorage;
void syncDecorationsWrapperLayerState();
void setImageAndInitIdleWatcher(KisImageSP _image) {
image = _image;
imageIdleWatcher.setTrackedImage(image);
}
void copyFrom(const Private &rhs, KisDocument *q);
void copyFromImpl(const Private &rhs, KisDocument *q, KisDocument::CopyPolicy policy);
/// clones the palette list oldList
/// the ownership of the returned KoColorSet * belongs to the caller
class StrippedSafeSavingLocker;
};
void KisDocument::Private::syncDecorationsWrapperLayerState()
{
if (!this->image) return;
KisImageSP image = this->image;
KisDecorationsWrapperLayerSP decorationsLayer =
KisLayerUtils::findNodeByType(image->root());
const bool needsDecorationsWrapper =
gridConfig.showGrid() || (guidesConfig.showGuides() && guidesConfig.hasGuides()) || !assistants.isEmpty();
struct SyncDecorationsWrapperStroke : public KisSimpleStrokeStrategy {
SyncDecorationsWrapperStroke(KisDocument *document, bool needsDecorationsWrapper)
: KisSimpleStrokeStrategy(QLatin1String("sync-decorations-wrapper"),
kundo2_noi18n("start-isolated-mode")),
m_document(document),
m_needsDecorationsWrapper(needsDecorationsWrapper)
{
this->enableJob(JOB_INIT, true, KisStrokeJobData::SEQUENTIAL, KisStrokeJobData::EXCLUSIVE);
setClearsRedoOnStart(false);
}
void initStrokeCallback() override {
KisDecorationsWrapperLayerSP decorationsLayer =
KisLayerUtils::findNodeByType(m_document->image()->root());
if (m_needsDecorationsWrapper && !decorationsLayer) {
m_document->image()->addNode(new KisDecorationsWrapperLayer(m_document));
} else if (!m_needsDecorationsWrapper && decorationsLayer) {
m_document->image()->removeNode(decorationsLayer);
}
}
private:
KisDocument *m_document = 0;
bool m_needsDecorationsWrapper = false;
};
KisStrokeId id = image->startStroke(new SyncDecorationsWrapperStroke(q, needsDecorationsWrapper));
image->endStroke(id);
}
void KisDocument::Private::copyFrom(const Private &rhs, KisDocument *q)
{
copyFromImpl(rhs, q, KisDocument::REPLACE);
}
void KisDocument::Private::copyFromImpl(const Private &rhs, KisDocument *q, KisDocument::CopyPolicy policy)
{
if (policy == REPLACE) {
delete docInfo;
}
docInfo = (new KoDocumentInfo(*rhs.docInfo, q));
unit = rhs.unit;
mimeType = rhs.mimeType;
outputMimeType = rhs.outputMimeType;
if (policy == REPLACE) {
q->setGuidesConfig(rhs.guidesConfig);
q->setMirrorAxisConfig(rhs.mirrorAxisConfig);
q->setModified(rhs.modified);
q->setAssistants(KisPaintingAssistant::cloneAssistantList(rhs.assistants));
q->setGridConfig(rhs.gridConfig);
} else {
// in CONSTRUCT mode, we cannot use the functions of KisDocument
// because KisDocument does not yet have a pointer to us.
guidesConfig = rhs.guidesConfig;
mirrorAxisConfig = rhs.mirrorAxisConfig;
modified = rhs.modified;
assistants = KisPaintingAssistant::cloneAssistantList(rhs.assistants);
gridConfig = rhs.gridConfig;
}
m_bAutoDetectedMime = rhs.m_bAutoDetectedMime;
m_url = rhs.m_url;
m_file = rhs.m_file;
readwrite = rhs.readwrite;
firstMod = rhs.firstMod;
lastMod = rhs.lastMod;
// XXX: the display properties will be shared between different snapshots
globalAssistantsColor = rhs.globalAssistantsColor;
batchMode = rhs.batchMode;
// CHECK THIS! This is what happened to the palette list -- but is it correct here as well? Ask Dmitry!!!
// if (policy == REPLACE) {
// QList newPaletteList = clonePaletteList(rhs.paletteList);
// q->setPaletteList(newPaletteList, /* emitSignal = */ true);
// // we still do not own palettes if we did not
// } else {
// paletteList = rhs.paletteList;
// }
if (rhs.documentResourceStorage) {
if (policy == REPLACE) {
// Clone the resources, but don't add them to the database, only the editable
// version of the document should have those resources in the database.
documentResourceStorage = rhs.documentResourceStorage->clone();
}
else {
documentResourceStorage = rhs.documentResourceStorage;
}
}
}
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(QEventLoop::ExcludeUserInputEvents);
// 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(bool addStorage)
: 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());
if (addStorage) {
d->documentResourceStorage.reset(new KisResourceStorage(d->documentStorageID));
KisResourceLocator::instance()->addStorage(d->documentStorageID, d->documentResourceStorage);
}
// preload the krita resources
KisResourceServerProvider::instance();
d->shapeController = new KisShapeController(this, d->nserver);
d->koShapeController = new KoShapeController(0, d->shapeController);
d->shapeController->resourceManager()->setGlobalShapeController(d->koShapeController);
slotConfigChanged();
}
KisDocument::KisDocument(const KisDocument &rhs)
: QObject(),
d(new Private(*rhs.d, this))
{
copyFromDocumentImpl(rhs, CONSTRUCT);
}
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());
}
if (KisResourceLocator::instance()->hasStorage(d->documentStorageID)) {
KisResourceLocator::instance()->removeStorage(d->documentStorageID);
}
delete d;
}
QString KisDocument::uniqueID() const
{
return d->documentStorageID;
}
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, ImportExportCodes::NoAccessToWrite,
i18n("%1 cannot be written to. Please save under a different name.", job.filePath));
//return ImportExportCodes::NoAccessToWrite;
return false;
}
KisConfig cfg(true);
if (cfg.backupFile() && filePathInfo.exists()) {
QString backupDir;
switch(cfg.readEntry("backupfilelocation", 0)) {
case 1:
backupDir = QStandardPaths::writableLocation(QStandardPaths::HomeLocation);
break;
case 2:
backupDir = QStandardPaths::writableLocation(QStandardPaths::TempLocation);
break;
default:
// Do nothing: the empty string is user file location
break;
}
int numOfBackupsKept = cfg.readEntry("numberofbackupfiles", 1);
QString suffix = cfg.readEntry("backupfilesuffix", "~");
if (numOfBackupsKept == 1) {
if (!KBackup::simpleBackupFile(job.filePath, backupDir, suffix)) {
qWarning() << "Failed to create simple backup file!" << job.filePath << backupDir << suffix;
KisUsageLogger::log(QString("Failed to create a simple backup for %1 in %2.").arg(job.filePath).arg(backupDir.isEmpty() ? "the same location as the file" : backupDir));
return false;
}
else {
KisUsageLogger::log(QString("Create a simple backup for %1 in %2.").arg(job.filePath).arg(backupDir.isEmpty() ? "the same location as the file" : backupDir));
}
}
else if (numOfBackupsKept > 1) {
if (!KBackup::numberedBackupFile(job.filePath, backupDir, suffix, numOfBackupsKept)) {
qWarning() << "Failed to create numbered backup file!" << job.filePath << backupDir << suffix;
KisUsageLogger::log(QString("Failed to create a numbered backup for %2.").arg(job.filePath).arg(backupDir.isEmpty() ? "the same location as the file" : backupDir));
return false;
}
else {
KisUsageLogger::log(QString("Create a simple backup for %1 in %2.").arg(job.filePath).arg(backupDir.isEmpty() ? "the same location as the file" : backupDir));
}
}
}
//KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(!job.mimeType.isEmpty(), false);
if (job.mimeType.isEmpty()) {
KisImportExportErrorCode error = ImportExportCodes::FileFormatIncorrect;
slotCompleteSavingDocument(job, error, error.errorMessage());
return false;
}
const QString actionName =
job.flags & KritaUtils::SaveIsExporting ?
i18n("Exporting Document...") :
i18n("Saving Document...");
bool started =
initiateSavingInBackground(actionName,
this, SLOT(slotCompleteSavingDocument(KritaUtils::ExportFileJob, KisImportExportErrorCode ,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;
}
KisUsageLogger::log(QString("Exporting Document: %1 as %2. %3 * %4 pixels, %5 layers, %6 frames, %7 framerate. Export configuration: %8")
.arg(url.toLocalFile())
.arg(QString::fromLatin1(mimeType))
.arg(d->image->width())
.arg(d->image->height())
.arg(d->image->nlayers())
.arg(d->image->animationInterface()->totalLength())
.arg(d->image->animationInterface()->framerate())
.arg(exportConfiguration ? exportConfiguration->toXML() : "No configuration"));
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;
KisUsageLogger::log(QString("Saving Document %9 as %1 (mime: %2). %3 * %4 pixels, %5 layers. %6 frames, %7 framerate. Export configuration: %8")
.arg(_url.toLocalFile())
.arg(QString::fromLatin1(mimeType))
.arg(d->image->width())
.arg(d->image->height())
.arg(d->image->nlayers())
.arg(d->image->animationInterface()->totalLength())
.arg(d->image->animationInterface()->framerate())
.arg(exportConfiguration ? exportConfiguration->toXML() : "No configuration")
.arg(url().toLocalFile()));
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).isOk()) {
qWarning() << "serializeToByteArray():: Could not export to our native format";
}
return byteArray;
}
void KisDocument::slotCompleteSavingDocument(const KritaUtils::ExportFileJob &job, KisImportExportErrorCode status, const QString &errorMessage)
{
if (status.isCancelled())
return;
const QString fileName = QFileInfo(job.filePath).fileName();
if (!status.isOk()) {
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, exportErrorToUserMessage(status, errorMessage)));
}
} else {
if (!(job.flags & KritaUtils::SaveIsExporting)) {
const QString existingAutoSaveBaseName = localFilePath();
const bool wasRecovered = isRecovered();
setUrl(QUrl::fromLocalFile(job.filePath));
setLocalFilePath(job.filePath);
setMimeType(job.mimeType);
updateEditingTime(true);
if (!d->modifiedWhileSaving) {
/**
* If undo stack is already clean/empty, it doesn't emit any
* signals, so we might forget update document modified state
* (which was set, e.g. while recovering an autosave file)
*/
if (d->undoStack->isClean()) {
setModified(false);
} else {
d->undoStack->setClean();
}
}
setRecovered(false);
removeAutoSaveFiles(existingAutoSaveBaseName, wasRecovered);
}
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(QEventLoop::ExcludeUserInputEvents);
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);
}
KisDocument *KisDocument::lockAndCreateSnapshot()
{
KisDocument *doc = lockAndCloneForSaving();
if (doc) {
// clone the local resource storage and its contents -- that is, the old palette list
if (doc->d->documentResourceStorage) {
doc->d->documentResourceStorage = doc->d->documentResourceStorage->clone();
}
}
return doc;
}
void KisDocument::copyFromDocument(const KisDocument &rhs)
{
copyFromDocumentImpl(rhs, REPLACE);
}
void KisDocument::copyFromDocumentImpl(const KisDocument &rhs, CopyPolicy policy)
{
if (policy == REPLACE) {
d->copyFrom(*(rhs.d), this);
d->undoStack->clear();
} else {
// in CONSTRUCT mode, d should be already initialized
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()));
d->shapeController = new KisShapeController(this, d->nserver);
d->koShapeController = new KoShapeController(0, d->shapeController);
d->shapeController->resourceManager()->setGlobalShapeController(d->koShapeController);
}
setObjectName(rhs.objectName());
slotConfigChanged();
if (rhs.d->image) {
if (policy == REPLACE) {
d->image->barrierLock(/* readOnly = */ false);
rhs.d->image->barrierLock(/* readOnly = */ true);
d->image->copyFromImage(*(rhs.d->image));
d->image->unlock();
rhs.d->image->unlock();
setCurrentImage(d->image, /* forceInitialUpdate = */ true);
} else {
// clone the image with keeping the GUIDs of the layers intact
// NOTE: we expect the image to be locked!
setCurrentImage(rhs.image()->clone(/* exactCopy = */ true), /* forceInitialUpdate = */ false);
}
}
if (rhs.d->preActivatedNode) {
QQueue linearizedNodes;
KisLayerUtils::recursiveApplyNodes(rhs.d->image->root(),
[&linearizedNodes](KisNodeSP node) {
linearizedNodes.enqueue(node);
});
KisLayerUtils::recursiveApplyNodes(d->image->root(),
[&linearizedNodes, &rhs, this](KisNodeSP node) {
KisNodeSP refNode = linearizedNodes.dequeue();
if (rhs.d->preActivatedNode.data() == refNode.data()) {
d->preActivatedNode = node;
}
});
}
// reinitialize references' signal connection
KisReferenceImagesLayerSP referencesLayer = this->referenceImagesLayer();
setReferenceImagesLayer(referencesLayer, false);
KisDecorationsWrapperLayerSP decorationsLayer =
KisLayerUtils::findNodeByType(d->image->root());
if (decorationsLayer) {
decorationsLayer->setDocument(this);
}
if (policy == REPLACE) {
setModified(true);
}
}
bool KisDocument::exportDocumentSync(const QUrl &url, const QByteArray &mimeType, KisPropertiesConfigurationSP exportConfiguration)
{
{
/**
* The caller guarantees that no one else uses the document (usually,
* it is a temporary document created specifically for exporting), so
* we don't need to copy or lock the document. Instead we should just
* ensure the barrier lock is synced and then released.
*/
Private::StrippedSafeSavingLocker locker(&d->savingMutex, d->image);
if (!locker.successfullyLocked()) {
return false;
}
}
d->savingImage = d->image;
const QString fileName = url.toLocalFile();
KisImportExportErrorCode status =
d->importExportManager->
exportDocument(fileName, fileName, mimeType, false, exportConfiguration);
d->savingImage = 0;
return status.isOk();
}
+
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;
}
auto waitForImage = [] (KisImageSP image) {
KisMainWindow *window = KisPart::instance()->currentMainwindow();
if (window) {
if (window->viewManager()) {
window->viewManager()->blockUntilOperationsFinishedForced(image);
}
}
};
{
KisNodeSP newRoot = clonedDocument->image()->root();
KIS_SAFE_ASSERT_RECOVER(!KisLayerUtils::hasDelayedNodeWithUpdates(newRoot)) {
KisLayerUtils::forceAllDelayedNodesUpdate(newRoot);
waitForImage(clonedDocument->image());
}
}
if (clonedDocument->image()->hasOverlaySelectionMask()) {
clonedDocument->image()->setOverlaySelectionMask(0);
waitForImage(clonedDocument->image());
}
KisConfig cfg(true);
if (cfg.trimKra()) {
clonedDocument->image()->cropImage(clonedDocument->image()->bounds());
clonedDocument->image()->purgeUnusedData(false);
waitForImage(clonedDocument->image());
}
KIS_SAFE_ASSERT_RECOVER(clonedDocument->image()->isIdle()) {
waitForImage(clonedDocument->image());
}
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(KisImportExportErrorCode, QString)),
this,
SLOT(slotChildCompletedSavingInBackground(KisImportExportErrorCode, QString)));
connect(this, SIGNAL(sigCompleteBackgroundSaving(KritaUtils::ExportFileJob, KisImportExportErrorCode, 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(KisImportExportErrorCode status, const QString &errorMessage)
{
KIS_ASSERT_RECOVER_RETURN(isSaving());
KIS_ASSERT_RECOVER(d->backgroundSaveDocument) {
d->savingMutex.unlock();
return;
}
if (d->backgroundSaveJob.flags & KritaUtils::SaveInAutosaveMode) {
d->backgroundSaveDocument->d->isAutosaving = false;
}
d->backgroundSaveDocument.take()->deleteLater();
KIS_ASSERT_RECOVER(d->backgroundSaveJob.isValid()) {
d->savingMutex.unlock();
return;
}
const KritaUtils::ExportFileJob job = d->backgroundSaveJob;
d->backgroundSaveJob = KritaUtils::ExportFileJob();
// unlock at the very end
d->savingMutex.unlock();
QFileInfo fi(job.filePath);
KisUsageLogger::log(QString("Completed saving %1 (mime: %2). Result: %3. Size: %4. MD5 Hash: %5")
.arg(job.filePath)
.arg(QString::fromLatin1(job.mimeType))
.arg(!status.isOk() ? exportErrorToUserMessage(status, errorMessage) : "OK")
.arg(fi.size())
.arg(fi.size() > 10000000 ? "FILE_BIGGER_10MB" : QString::fromLatin1(KoMD5Generator().generateHash(job.filePath).toHex())));
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);
KisUsageLogger::log(QString("Autosaving: %1").arg(autoSaveFileName));
const bool hadClonedDocument = bool(optionalClonedDocument);
bool started = false;
if (d->image->isIdle() || hadClonedDocument) {
started = initiateSavingInBackground(i18n("Autosaving..."),
this, SLOT(slotCompleteAutoSaving(KritaUtils::ExportFileJob, KisImportExportErrorCode, 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::slotPerformIdleRoutines()
{
d->image->explicitRegenerateLevelOfDetail();
/// TODO: automatical purging is disabled for now: it modifies
/// data managers without creating a transaction, which breaks
/// undo.
// d->image->purgeUnusedData(true);
}
void KisDocument::slotCompleteAutoSaving(const KritaUtils::ExportFileJob &job, KisImportExportErrorCode status, const QString &errorMessage)
{
Q_UNUSED(job);
const QString fileName = QFileInfo(job.filePath).fileName();
if (!status.isOk()) {
setEmergencyAutoSaveInterval();
emit statusBarMessage(i18nc("%1 --- failing file name, %2 --- error message",
"Error during autosaving %1: %2",
fileName,
exportErrorToUserMessage(status, errorMessage)), errorMessageTimeout);
} else {
KisConfig cfg(true);
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);
}
}
KisImportExportErrorCode initializationStatus(ImportExportCodes::OK);
d->childSavingFuture =
d->importExportManager->exportDocumentAsyc(location,
realLocation,
mimeType,
initializationStatus,
showWarnings,
exportConfiguration);
if (!initializationStatus.isOk()) {
if (d->savingUpdater) {
d->savingUpdater->cancel();
}
d->savingImage.clear();
emit sigBackgroundSavingFinished(initializationStatus, initializationStatus.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(ImportExportCodes::InternalError, "");
return;
}
KisImportExportErrorCode status =
d->childSavingFuture.result();
const QString errorMessage = status.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");
QString prefix = KisConfig(true).readEntry("autosavefileshidden") ? QString(".") : QString();
QRegularExpression autosavePattern1("^\\..+-autosave.kra$");
QRegularExpression autosavePattern2("^.+-autosave.kra$");
QFileInfo fi(path);
QString dir = fi.absolutePath();
QString filename = fi.fileName();
if (path.isEmpty() || autosavePattern1.match(filename).hasMatch() || autosavePattern2.match(filename).hasMatch() || !fi.isWritable()) {
// 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%7%3-%4-%5-autosave%6").arg(QDir::tempPath()).arg('/').arg("krita").arg(qApp->applicationPid()).arg(objectName()).arg(extension).arg(prefix);
#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%7%3-%4-%5-autosave%6").arg(QDir::homePath()).arg('/').arg("krita").arg(qApp->applicationPid()).arg(objectName()).arg(extension).arg(prefix);
#endif
} else {
retval = QString("%1%2%5%3-autosave%4").arg(dir).arg('/').arg(filename).arg(extension).arg(prefix);
}
//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);
QString original = "";
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();
//qDebug() <<"asf=" << asf;
// ## TODO compare timestamps ?
KisRecoverNamedAutosaveDialog dlg(0, file, asf);
dlg.exec();
int res = dlg.result();
switch (res) {
case KisRecoverNamedAutosaveDialog::OpenAutosave :
original = file;
url.setPath(asf);
autosaveOpened = true;
break;
case KisRecoverNamedAutosaveDialog::OpenMainFile :
KisUsageLogger::log(QString("Removing autosave file: %1").arg(asf));
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);
setUrl(QUrl::fromLocalFile(original)); // since it was an autosave, it will be a local file
setLocalFilePath(original);
}
else {
if (ret) {
if (!(flags & DontAddToRecent)) {
KisPart::instance()->addRecentURLToAllMainWindows(_url);
}
// 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()) && !fileBatchMode()) {
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();
KoUpdaterPtr updater;
if (window && window->viewManager()) {
updater = window->viewManager()->createUnthreadedUpdater(i18n("Opening document"));
d->importExportManager->setUpdater(updater);
}
KisImportExportErrorCode status = d->importExportManager->importDocument(localFilePath(), typeName);
if (!status.isOk()) {
if (window && window->viewManager()) {
updater->cancel();
}
QString msg = status.errorMessage();
if (!msg.isEmpty() && !fileBatchMode()) {
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() && !fileBatchMode()) {
DlgLoadMessages dlg(i18nc("@title:window", "Krita"),
i18n("There were problems opening %1.", prettyPathOrUrl()),
warningMessage().split("\n"));
dlg.exec();
setUrl(QUrl());
}
setMimeTypeAfterLoading(typeName);
d->syncDecorationsWrapperLayerState();
emit sigLoadingFinished();
undoStack()->clear();
return true;
}
void KisDocument::autoSaveOnPause()
{
if (!d->modified || !d->modifiedAfterAutosave)
return;
const QString autoSaveFileName = generateAutoSaveFileName(localFilePath());
QUrl url("file:/" + autoSaveFileName);
bool started = exportDocumentSync(url, nativeFormatMimeType());
if (started)
{
d->modifiedAfterAutosave = false;
dbgAndroid << "autoSaveOnPause successful";
}
else
{
qWarning() << "Could not auto-save when paused";
}
}
// 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(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(const QString &autosaveBaseName, bool wasRecovered)
{
// Eliminate any auto-save file
QString asf = generateAutoSaveFileName(autosaveBaseName); // the one in the current dir
if (QFile::exists(asf)) {
KisUsageLogger::log(QString("Removing autosave file: %1").arg(asf));
QFile::remove(asf);
}
asf = generateAutoSaveFileName(QString()); // and the one in $HOME
if (QFile::exists(asf)) {
KisUsageLogger::log(QString("Removing autosave file: %1").arg(asf));
QFile::remove(asf);
}
QList expressions;
expressions << QRegularExpression("^\\..+-autosave.kra$")
<< QRegularExpression("^.+-autosave.kra$");
Q_FOREACH(const QRegularExpression &rex, expressions) {
if (wasRecovered &&
!autosaveBaseName.isEmpty() &&
rex.match(QFileInfo(autosaveBaseName).fileName()).hasMatch() &&
QFile::exists(autosaveBaseName)) {
KisUsageLogger::log(QString("Removing autosave file: %1").arg(autosaveBaseName));
QFile::remove(autosaveBaseName);
}
}
}
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(true);
if (d->undoStack->undoLimit() != cfg.undoStackLimit()) {
if (!d->undoStack->isClean()) {
d->undoStack->clear();
}
d->undoStack->setUndoLimit(cfg.undoStackLimit());
}
d->autoSaveDelay = cfg.autoSaveInterval();
setNormalAutoSaveInterval();
}
void KisDocument::slotImageRootChanged()
{
d->syncDecorationsWrapperLayerState();
}
void KisDocument::clearUndoHistory()
{
d->undoStack->clear();
}
KisGridConfig KisDocument::gridConfig() const
{
return d->gridConfig;
}
void KisDocument::setGridConfig(const KisGridConfig &config)
{
if (d->gridConfig != config) {
d->gridConfig = config;
d->syncDecorationsWrapperLayerState();
emit sigGridConfigChanged(config);
}
}
QList KisDocument::paletteList()
{
qDebug() << "PALETTELIST storage" << d->documentResourceStorage;
QList _paletteList;
if (d->documentResourceStorage.isNull()) {
qWarning() << "No documentstorage for palettes";
return _paletteList;
}
QSharedPointer iter = d->documentResourceStorage->resources(ResourceType::Palettes);
while (iter->hasNext()) {
iter->next();
KoResourceSP resource = iter->resource();
if (resource && resource->valid()) {
_paletteList << resource.dynamicCast();
}
}
return _paletteList;
}
void KisDocument::setPaletteList(const QList &paletteList, bool emitSignal)
{
qDebug() << "SET PALETTE LIST" << paletteList.size() << "storage" << d->documentResourceStorage;
QList oldPaletteList;
if (d->documentResourceStorage) {
QSharedPointer iter = d->documentResourceStorage->resources(ResourceType::Palettes);
while (iter->hasNext()) {
iter->next();
KoResourceSP resource = iter->resource();
if (resource && resource->valid()) {
oldPaletteList << resource.dynamicCast();
}
}
if (oldPaletteList != paletteList) {
KisResourceModel *resourceModel = KisResourceModelProvider::resourceModel(ResourceType::Palettes);
Q_FOREACH(KoColorSetSP palette, oldPaletteList) {
resourceModel->removeResource(palette);
}
Q_FOREACH(KoColorSetSP palette, paletteList) {
qDebug()<< "loading palette into document" << palette->filename();
resourceModel->addResource(palette, d->documentStorageID);
}
if (emitSignal) {
emit sigPaletteListChanged(oldPaletteList, paletteList);
}
}
}
}
const KisGuidesConfig& KisDocument::guidesConfig() const
{
return d->guidesConfig;
}
void KisDocument::setGuidesConfig(const KisGuidesConfig &data)
{
if (d->guidesConfig == data) return;
d->guidesConfig = data;
d->syncDecorationsWrapperLayerState();
emit sigGuidesConfigChanged(d->guidesConfig);
}
const KisMirrorAxisConfig& KisDocument::mirrorAxisConfig() const
{
return d->mirrorAxisConfig;
}
void KisDocument::setMirrorAxisConfig(const KisMirrorAxisConfig &config)
{
if (d->mirrorAxisConfig == config) {
return;
}
d->mirrorAxisConfig = config;
setModified(true);
emit sigMirrorAxisConfigChanged();
}
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, KisConfig::BackgroundStyle bgStyle,
int numberOfLayers,
const QString &description, const double imageResolution)
{
Q_ASSERT(cs);
KisImageSP image;
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());
image->waitForDone();
documentInfo()->setAboutInfo("title", name);
documentInfo()->setAboutInfo("abstract", description);
KisConfig cfg(false);
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());
bool autopin = cfg.autoPinLayersToTimeline();
KisLayerSP bgLayer;
if (bgStyle == KisConfig::RASTER_LAYER || bgStyle == KisConfig::FILL_LAYER) {
KoColor strippedAlpha = bgColor;
strippedAlpha.setOpacity(OPACITY_OPAQUE_U8);
if (bgStyle == KisConfig::RASTER_LAYER) {
bgLayer = new KisPaintLayer(image.data(), "Background", OPACITY_OPAQUE_U8, cs);;
bgLayer->paintDevice()->setDefaultPixel(strippedAlpha);
bgLayer->setPinnedToTimeline(autopin);
} else if (bgStyle == KisConfig::FILL_LAYER) {
KisFilterConfigurationSP filter_config = KisGeneratorRegistry::instance()->get("color")->defaultConfiguration(KisGlobalResourcesInterface::instance());
filter_config->setProperty("color", strippedAlpha.toQColor());
filter_config->createLocalResourcesSnapshot();
bgLayer = new KisGeneratorLayer(image.data(), "Background Fill", filter_config, image->globalSelection());
}
bgLayer->setOpacity(bgColor.opacityU8());
if (numberOfLayers > 1) {
//Lock bg layer if others are present.
bgLayer->setUserLocked(true);
}
}
else { // KisConfig::CANVAS_COLOR (needs an unlocked starting layer).
image->setDefaultProjectionColor(bgColor);
bgLayer = new KisPaintLayer(image.data(), image->nextLayerName(), OPACITY_OPAQUE_U8, cs);
}
Q_CHECK_PTR(bgLayer);
image->addNode(bgLayer.data(), image->rootLayer().data());
bgLayer->setDirty(QRect(0, 0, width, height));
setCurrentImage(image);
for(int i = 1; i < numberOfLayers; ++i) {
KisPaintLayerSP layer = new KisPaintLayer(image, image->nextLayerName(), OPACITY_OPAQUE_U8, cs);
layer->setPinnedToTimeline(autopin);
image->addNode(layer, image->root(), i);
layer->setDirty(QRect(0, 0, width, height));
}
KisUsageLogger::log(QString("Created image \"%1\", %2 * %3 pixels, %4 dpi. Color model: %6 %5 (%7). Layers: %8")
.arg(name)
.arg(width).arg(height)
.arg(imageResolution * 72.0)
.arg(image->colorSpace()->colorModelId().name())
.arg(image->colorSpace()->colorDepthId().name())
.arg(image->colorSpace()->profile()->name())
.arg(numberOfLayers));
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);
}
}
KoShapeControllerBase *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)
{
if (d->assistants != value) {
d->assistants = value;
d->syncDecorationsWrapperLayerState();
emit sigAssistantsChanged();
}
}
KisReferenceImagesLayerSP KisDocument::referenceImagesLayer() const
{
if (!d->image) return KisReferenceImagesLayerSP();
KisReferenceImagesLayerSP referencesLayer =
KisLayerUtils::findNodeByType(d->image->root());
return referencesLayer;
}
void KisDocument::setReferenceImagesLayer(KisSharedPtr layer, bool updateImage)
{
KisReferenceImagesLayerSP currentReferenceLayer = referenceImagesLayer();
if (currentReferenceLayer == layer) {
return;
}
if (currentReferenceLayer) {
currentReferenceLayer->disconnect(this);
}
if (updateImage) {
if (currentReferenceLayer) {
d->image->removeNode(currentReferenceLayer);
}
if (layer) {
d->image->addNode(layer);
}
}
currentReferenceLayer = layer;
if (currentReferenceLayer) {
connect(currentReferenceLayer, SIGNAL(sigUpdateCanvas(QRectF)),
this, SIGNAL(sigReferenceImagesChanged()));
}
emit sigReferenceImagesLayerChanged(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->setUndoStore(new KisDumbUndoStore());
d->image->disconnect(this);
d->shapeController->setImage(0);
d->image = 0;
}
if (!image) return;
if (d->documentResourceStorage){
d->documentResourceStorage->setMetaData(KisResourceStorage::s_meta_name, image->objectName());
}
d->setImageAndInitIdleWatcher(image);
d->image->setUndoStore(new KisDocumentUndoStore(this));
d->shapeController->setImage(image);
setModified(false);
connect(d->image, SIGNAL(sigImageModified()), this, SLOT(setImageModified()), Qt::UniqueConnection);
connect(d->image, SIGNAL(sigLayersChangedAsync()), this, SLOT(slotImageRootChanged()));
if (forceInitialUpdate) {
d->image->initialRefreshGraph();
}
}
void KisDocument::hackPreliminarySetImage(KisImageSP image)
{
KIS_SAFE_ASSERT_RECOVER_RETURN(!d->image);
// we set image without connecting idle-watcher, because loading
// hasn't been finished yet
d->image = image;
d->shapeController->setImage(image);
}
void KisDocument::setImageModified()
{
// we only set as modified if undo stack is not at clean state
setModified(!d->undoStack->isClean());
}
KisUndoStore* KisDocument::createUndoStore()
{
return new KisDocumentUndoStore(this);
}
bool KisDocument::isAutosaving() const
{
return d->isAutosaving;
}
QString KisDocument::exportErrorToUserMessage(KisImportExportErrorCode status, const QString &errorMessage)
{
return errorMessage.isEmpty() ? status.errorMessage() : errorMessage;
}
void KisDocument::setAssistantsGlobalColor(QColor color)
{
d->globalAssistantsColor = color;
}
QColor KisDocument::assistantsGlobalColor()
{
return d->globalAssistantsColor;
}
QRectF KisDocument::documentBounds() const
{
QRectF bounds = d->image->bounds();
KisReferenceImagesLayerSP referenceImagesLayer = this->referenceImagesLayer();
if (referenceImagesLayer) {
bounds |= referenceImagesLayer->boundingImageRect();
}
return bounds;
}
diff --git a/libs/ui/KisDocument.h b/libs/ui/KisDocument.h
index 37f6ddc7ae..9f41bbe4be 100644
--- a/libs/ui/KisDocument.h
+++ b/libs/ui/KisDocument.h
@@ -1,707 +1,708 @@
/* 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 "kis_scratch_pad.h"
#include "kritaui_export.h"
#include
class QString;
class KUndo2Command;
class KoUnit;
class KoColor;
class KoColorSpace;
class KoShapeControllerBase;
class KoShapeLayer;
class KoStore;
class KoDocumentInfo;
class KoDocumentInfoDlg;
class KisImportExportManager;
class KisUndoStore;
class KisPart;
class KisGridConfig;
class KisGuidesConfig;
class KisMirrorAxisConfig;
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
{
Q_OBJECT
protected:
explicit KisDocument(bool addStorage = true);
/**
* @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();
/**
* @brief uniqueID is a temporary unique ID that identifies the document. It is
* generated on creation and can be used to uniquely associated temporary objects
* with this document.
*
* @return the temporary unique id for this document.
*/
QString uniqueID() const;
/**
* @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);
/**
* Exports he document is a synchronous way. The caller must ensure that the
* image is not accessed by any other actors, because the exporting happens
* without holding the image lock.
*/
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;
/**
* @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);
/**
* @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(const QString &autosaveBaseName, bool wasRecovered);
/**
* Returns true if this document or any of its internal child documents are modified.
*/
bool isModified() const;
/**
* @return caption of the document
*
* Caption is of the form "[title] - [url]",
* built out of the document info (title) and pretty-printed
* document URL.
* If the title is not present, only the URL it returned.
*/
QString caption() const;
/**
* Sets the document URL to empty URL
* KParts doesn't allow this, but %Calligra apps have e.g. templates
* After using loadNativeFormat on a template, one wants
* to set the url to QUrl()
*/
void resetURL();
/**
* @internal (public for KisMainWindow)
*/
void setMimeTypeAfterLoading(const QString& mimeType);
/**
* Returns the unit used to display all measures/distances.
*/
KoUnit unit() const;
/**
* Sets the unit used to display all measures/distances.
*/
void setUnit(const KoUnit &unit);
KisGridConfig gridConfig() const;
void setGridConfig(const KisGridConfig &config);
/// returns the guides data for this document.
const KisGuidesConfig& guidesConfig() const;
void setGuidesConfig(const KisGuidesConfig &data);
/**
* @brief paletteList returns all the palettes found in the document's local resource storage
*/
QList paletteList();
/**
* @brief setPaletteList replaces the palettes in the document's local resource storage with the list
* of palettes passed to this function. It will then emitsigPaletteListChanged with both the old and
* the new list, if emitsignal is true.
*/
void setPaletteList(const QList &paletteList, bool emitSignal = false);
const KisMirrorAxisConfig& mirrorAxisConfig() const;
void setMirrorAxisConfig(const KisMirrorAxisConfig& config);
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(KisImportExportErrorCode status, const QString &errorMessage);
void sigCompleteBackgroundSaving(const KritaUtils::ExportFileJob &job, KisImportExportErrorCode status, const QString &errorMessage);
void sigReferenceImagesChanged();
void sigMirrorAxisConfigChanged();
void sigGridConfigChanged(const KisGridConfig &config);
void sigReferenceImagesLayerChanged(KisSharedPtr layer);
/**
* Emitted when the palette list has changed.
* The pointers in oldPaletteList are to be deleted by the resource server.
**/
void sigPaletteListChanged(const QList &oldPaletteList, const QList &newPaletteList);
void sigAssistantsChanged();
private Q_SLOTS:
void finishExportInBackground();
void slotChildCompletedSavingInBackground(KisImportExportErrorCode status, const QString &errorMessage);
void slotCompleteAutoSaving(const KritaUtils::ExportFileJob &job, KisImportExportErrorCode status, const QString &errorMessage);
void slotCompleteSavingDocument(const KritaUtils::ExportFileJob &job, KisImportExportErrorCode status, const QString &errorMessage);
void slotInitiateAsyncAutosaving(KisDocument *clonedDocument);
void slotPerformIdleRoutines();
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();
public:
bool isAutosaving() const;
public:
QString localFilePath() const;
void setLocalFilePath( const QString &localFilePath );
KoDocumentInfoDlg* createDocumentInfoDialog(QWidget *parent, KoDocumentInfo *docInfo) const;
bool isReadWrite() const;
QUrl url() const;
void setUrl(const QUrl &url);
bool closeUrl(bool promptToSave = true);
bool saveAs(const QUrl &url, 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, KisConfig::BackgroundStyle bgStyle,
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.
*/
KoShapeControllerBase * 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);
/**
* Return the bounding box of the image and associated elements (e.g. reference images)
*/
QRectF documentBounds() const;
/**
* @brief Start saving when android activity is pushed to the background
*/
void autoSaveOnPause();
Q_SIGNALS:
void completed();
void canceled(const QString &);
private Q_SLOTS:
void setImageModified();
void slotAutoSave();
void slotUndoStackCleanChanged(bool value);
void slotConfigChanged();
void slotImageRootChanged();
/**
* @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();
public:
KisDocument *lockAndCreateSnapshot();
void copyFromDocument(const KisDocument &rhs);
private:
enum CopyPolicy {
CONSTRUCT = 0, ///< we are copy-constructing a new KisDocument
REPLACE ///< we are replacing the current KisDocument with another
};
void copyFromDocumentImpl(const KisDocument &rhs, CopyPolicy policy);
QString exportErrorToUserMessage(KisImportExportErrorCode 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 11644f97bb..d5c668fd89 100644
--- a/libs/ui/KisView.cpp
+++ b/libs/ui/KisView.cpp
@@ -1,1028 +1,1029 @@
/*
* 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
#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 "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"
#include "kis_selection_manager.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,
KisViewManager *viewManager)
: actionCollection(viewManager->actionCollection())
, viewConverter()
, canvasController(_q, viewManager->mainWindow(), viewManager->actionCollection())
, canvas(&viewConverter, viewManager->canvasResourceProvider()->resourceManager(), viewManager->mainWindow(), _q, document->shapeController())
, zoomManager(_q, &this->viewConverter, &this->canvasController)
, viewManager(viewManager)
, 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;
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, KisViewManager *viewManager, QWidget *parent)
: QWidget(parent)
, d(new Private(this, document, viewManager))
{
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(QString,int)),
this, SLOT(slotSavingStatusMessage(QString,int)));
connect(d->document, SIGNAL(clearStatusBarMessage()),
this, SLOT(slotClearStatusText()));
}
d->canvas.setup();
KisConfig cfg(false);
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->referenceImagesDecoration);
d->referenceImagesDecoration->setVisible(true);
d->canvas.addDecoration(d->paintingAssistantsDecoration);
d->paintingAssistantsDecoration->setVisible(true);
d->showFloatingMessage = cfg.showCanvasMessages();
d->zoomManager.updateScreenResolution(this);
}
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);
}
/**
* When current view is changed, currently selected node is also changed,
* therefore we should update selection overlay mask
*/
viewManager()->selectionManager()->selectionChanged();
}
bool KisView::isCurrent() const
{
return d->isCurrent;
}
void KisView::setShowFloatingMessage(bool show)
{
d->showFloatingMessage = show;
}
void KisView::showFloatingMessage(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(QPointF,QPointF)), this, SLOT(slotImageSizeChanged(QPointF,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->canvasResourceProvider();
}
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)
{
//qDebug() << "KisView::dragEnterEvent formats" << event->mimeData()->formats() << "urls" << event->mimeData()->urls() << "has images" << event->mimeData()->hasImage();
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;
}
//qDebug() << "KisView::dropEvent() formats" << event->mimeData()->formats() << "urls" << event->mimeData()->urls() << "has images" << event->mimeData()->hasImage();
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)) {
qWarning() << "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, this);
if (reference) {
reference->setPosition(d->viewConverter.imageToDocument(cursorPos));
d->referenceImagesDecoration->addReferenceImage(reference);
KoToolManager::instance()->switchToolRequested("ToolReferenceImages");
}
}
}
delete tmp;
tmp = 0;
}
}
}
}
}
void KisView::dragMoveEvent(QDragMoveEvent *event)
{
//qDebug() << "KisView::dragMoveEvent";
if (event->mimeData()->hasUrls() ||
event->mimeData()->hasFormat("application/x-krita-node") ||
event->mimeData()->hasFormat("application/x-qt-image")) {
event->accept();
}
}
KisDocument *KisView::document() const
{
return d->document;
}
KisView *KisView::replaceBy(KisDocument *document)
{
KisMainWindow *window = mainWindow();
QMdiSubWindow *subWindow = d->subWindow;
delete this;
return window->newView(document, subWindow);
}
KisMainWindow * KisView::mainWindow() const
{
return d->viewManager->mainWindow();
}
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(true);
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()) {
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()->localFilePath(), document()->isRecovered());
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::slotScreenChanged()
{
d->zoomManager.updateScreenResolution(this);
}
void KisView::slotThemeChanged(QPalette pal)
{
this->setPalette(pal);
for (int i=0; ichildren().size();i++) {
QWidget *w = qobject_cast ( this->children().at(i));
if (w) {
w->setPalette(pal);
}
}
if (canvasBase()) {
canvasBase()->canvasWidget()->setPalette(pal);
}
if (canvasController()) {
canvasController()->setPalette(pal);
}
}
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 {
QSizeF 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(),
d->zoomManager.resolutionX(), d->zoomManager.resolutionY());
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 {
QSizeF 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);
connect(d->viewManager->mainWindow(), SIGNAL(screenChanged()), SLOT(slotScreenChanged()));
zoomManager()->updateImageBoundsSnapping();
}
void KisView::slotSavingFinished()
{
if (d->viewManager && d->viewManager->mainWindow()) {
d->viewManager->mainWindow()->updateCaption();
}
}
void KisView::slotImageResolutionChanged()
{
resetImageSizeAndScroll(false);
zoomManager()->updateImageBoundsSnapping();
zoomManager()->updateGuiAfterDocumentSize();
// update KoUnit value for the document
if (resourceProvider()) {
resourceProvider()->resourceManager()->
setResource(KoCanvasResourceProvider::Unit, d->canvas.unit());
}
}
void KisView::slotImageSizeChanged(const QPointF &oldStillPoint, const QPointF &newStillPoint)
{
resetImageSizeAndScroll(true, oldStillPoint, newStillPoint);
zoomManager()->updateImageBoundsSnapping();
zoomManager()->updateGuiAfterDocumentSize();
}
void KisView::closeView()
{
d->subWindow->close();
}
diff --git a/libs/ui/KisView.h b/libs/ui/KisView.h
index b58e282d89..83e76de3fb 100644
--- a/libs/ui/KisView.h
+++ b/libs/ui/KisView.h
@@ -1,286 +1,289 @@
/* This file is part of the KDE project
Copyright (C) 1998, 1999 Torben Weis
Copyright (C) 2007 Thomas Zander
Copyright (C) 2010 Benjamin Port
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Library General Public
License as published by the Free Software Foundation; either
version 2 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Library General Public License for more details.
You should have received a copy of the GNU Library General Public License
along with this library; see the file COPYING.LIB. If not, write to
the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
Boston, MA 02110-1301, USA.
*/
#ifndef KIS_VIEW_H
#define KIS_VIEW_H
#include
#include
#include
#include
#include "kritaui_export.h"
#include "widgets/kis_floating_message.h"
class KisDocument;
class KisMainWindow;
class KisCanvasController;
class KisZoomManager;
class KisCanvas2;
class KisViewManager;
class KisDocument;
class KisCanvasResourceProvider;
class KisCoordinatesConverter;
class KisInputManager;
class KoZoomController;
class KoZoomController;
class KoCanvasResourceProvider;
// KDE classes
class QAction;
class KActionCollection;
class KConfigGroup;
// Qt classes
class QDragEnterEvent;
class QDragMoveEvent;
class QDropEvent;
class QPrintDialog;
class QCloseEvent;
class QStatusBar;
class QMdiSubWindow;
/**
* This class is used to display a @ref KisDocument.
*
* Multiple views can be attached to one document at a time.
*/
class KRITAUI_EXPORT KisView : public QWidget
{
Q_OBJECT
public:
/**
* Creates a new view for the document.
*/
KisView(KisDocument *document, KisViewManager *viewManager, QWidget *parent = 0);
~KisView() override;
// Temporary while teasing apart view and mainwindow
void setViewManager(KisViewManager *view);
KisViewManager *viewManager() const;
public:
/**
* Retrieves the document object of this view.
*/
KisDocument *document() const;
/**
* Deletes the view and creates a new one, displaying @p document,
* in the same sub-window.
*
* @return the new view
*/
KisView *replaceBy(KisDocument *document);
/**
* @return the KisMainWindow in which this view is currently.
*/
KisMainWindow *mainWindow() const;
/**
* Tells this view which subwindow it is part of.
*/
void setSubWindow(QMdiSubWindow *subWindow);
/**
* @return the statusbar of the KisMainWindow in which this view is currently.
*/
QStatusBar *statusBar() const;
+
+
+
/**
* This adds a widget to the statusbar for this view.
* If you use this method instead of using statusBar() directly,
* KisView will take care of removing the items when the view GUI is deactivated
* and readding them when it is reactivated.
* The parameters are the same as QStatusBar::addWidget().
*/
void addStatusBarItem(QWidget * widget, int stretch = 0, bool permanent = false);
/**
* Remove a widget from the statusbar for this view.
*/
void removeStatusBarItem(QWidget * widget);
/**
* Return the zoomController for this view.
*/
KoZoomController *zoomController() const;
/// create a list of actions that when activated will change the unit on the document.
QList createChangeUnitActions(bool addPixelUnit = false);
void closeView();
public:
/**
* The zoommanager handles everything action-related to zooming
*/
KisZoomManager *zoomManager() const;
/**
* The CanvasController decorates the canvas with scrollbars
* and knows where to start painting on the canvas widget, i.e.,
* the document offset.
*/
KisCanvasController *canvasController() const;
KisCanvasResourceProvider *resourceProvider() const;
/**
* Filters events and sends them to canvas actions. Shared
* among all the views/canvases
*
* NOTE: May be null while initialization!
*/
KisInputManager* globalInputManager() const;
/**
* @return the canvas object
*/
KisCanvas2 *canvasBase() const;
/// @return the image this view is displaying
KisImageWSP image() const;
KisCoordinatesConverter *viewConverter() const;
void resetImageSizeAndScroll(bool changeCentering,
const QPointF &oldImageStillPoint = QPointF(),
const QPointF &newImageStillPoint = QPointF());
void setCurrentNode(KisNodeSP node);
KisNodeSP currentNode() const;
KisLayerSP currentLayer() const;
KisMaskSP currentMask() const;
/**
* @brief softProofing
* @return whether or not we're softproofing in this view.
*/
bool softProofing();
/**
* @brief gamutCheck
* @return whether or not we're using gamut warnings in this view.
*/
bool gamutCheck();
/// Convenience method to get at the active selection (the
/// selection of the current layer, or, if that does not exist,
/// the global selection.
KisSelectionSP selection();
void notifyCurrentStateChanged(bool isCurrent);
bool isCurrent() const;
void setShowFloatingMessage(bool show);
void showFloatingMessage(const QString &message, const QIcon& icon, int timeout = 4500,
KisFloatingMessage::Priority priority = KisFloatingMessage::Medium,
int alignment = Qt::AlignCenter | Qt::TextWordWrap);
bool canvasIsMirrored() const;
void syncLastActiveNodeToDocument();
void saveViewState(KisPropertiesConfiguration &config) const;
void restoreViewState(const KisPropertiesConfiguration &config);
public Q_SLOTS:
/**
* Display a message in the status bar (calls QStatusBar::message())
* @todo rename to something more generic
* @param value determines autosaving
*/
void slotSavingStatusMessage(const QString &text, int timeout, bool isAutoSaving = false);
/**
* End of the message in the status bar (calls QStatusBar::clear())
* @todo rename to something more generic
*/
void slotClearStatusText();
/**
* @brief slotSoftProofing set whether or not we're softproofing in this view.
* Will be setting the same in the canvas belonging to the view.
*/
void slotSoftProofing(bool softProofing);
/**
* @brief slotGamutCheck set whether or not we're gamutchecking in this view.
* Will be setting the same in the vans belonging to the view.
*/
void slotGamutCheck(bool gamutCheck);
bool queryClose();
void slotScreenChanged();
void slotThemeChanged(QPalette pal);
private Q_SLOTS:
void slotImageNodeAdded(KisNodeSP node);
void slotContinueAddNode(KisNodeSP newActiveNode);
void slotImageNodeRemoved(KisNodeSP node);
void slotContinueRemoveNode(KisNodeSP newActiveNode);
Q_SIGNALS:
// From KisImage
void sigSizeChanged(const QPointF &oldStillPoint, const QPointF &newStillPoint);
void sigProfileChanged(const KoColorProfile * profile);
void sigColorSpaceChanged(const KoColorSpace* cs);
void titleModified(QString,bool);
void sigContinueAddNode(KisNodeSP newActiveNode);
void sigContinueRemoveNode(KisNodeSP newActiveNode);
protected:
// QWidget overrides
void dragEnterEvent(QDragEnterEvent *event) override;
void dropEvent(QDropEvent *event) override;
void dragMoveEvent(QDragMoveEvent *event) override;
void closeEvent(QCloseEvent *event) override;
/**
* Generate a name for this view.
*/
QString newObjectName();
public Q_SLOTS:
void slotLoadingFinished();
void slotSavingFinished();
void slotImageResolutionChanged();
void slotImageSizeChanged(const QPointF &oldStillPoint, const QPointF &newStillPoint);
private:
class Private;
Private * const d;
static bool s_firstView;
};
#endif
diff --git a/libs/ui/widgets/kis_scratch_pad.cpp b/libs/ui/widgets/kis_scratch_pad.cpp
index d6258e008b..652ea7b12c 100644
--- a/libs/ui/widgets/kis_scratch_pad.cpp
+++ b/libs/ui/widgets/kis_scratch_pad.cpp
@@ -1,644 +1,662 @@
/* This file is part of the KDE project
* Copyright 2010 (C) Boudewijn Rempt
* Copyright 2011 (C) Dmitry Kazakov
*
* 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 "kis_scratch_pad.h"
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include "kis_config.h"
#include "kis_image.h"
#include "kis_undo_stores.h"
#include "kis_update_scheduler.h"
#include "kis_post_execution_undo_adapter.h"
#include "kis_scratch_pad_event_filter.h"
#include "kis_painting_information_builder.h"
#include "kis_tool_freehand_helper.h"
#include "kis_image_patch.h"
#include "kis_canvas_widget_base.h"
#include "kis_layer_projection_plane.h"
#include "kis_node_graph_listener.h"
#include "kis_transaction.h"
class KisScratchPadNodeListener : public KisNodeGraphListener
{
public:
KisScratchPadNodeListener(KisScratchPad *scratchPad)
: m_scratchPad(scratchPad)
{
}
void requestProjectionUpdate(KisNode *node, const QVector &rects, bool resetAnimationCache) override {
KisNodeGraphListener::requestProjectionUpdate(node, rects, resetAnimationCache);
QMutexLocker locker(&m_lock);
Q_FOREACH (const QRect &rc, rects) {
m_scratchPad->imageUpdated(rc);
}
}
private:
KisScratchPad *m_scratchPad;
QMutex m_lock;
};
class KisScratchPadDefaultBounds : public KisDefaultBounds
{
public:
KisScratchPadDefaultBounds(KisScratchPad *scratchPad)
: m_scratchPad(scratchPad)
{
}
~KisScratchPadDefaultBounds() override {}
QRect bounds() const override {
return m_scratchPad->imageBounds();
}
void * sourceCookie() const override {
return m_scratchPad;
}
private:
Q_DISABLE_COPY(KisScratchPadDefaultBounds)
KisScratchPad *m_scratchPad;
};
KisScratchPad::KisScratchPad(QWidget *parent)
: QWidget(parent)
, m_toolMode(HOVERING)
+ , isModeManuallySet(false)
+ , isMouseDown(false)
+ , linkCanvasZoomLevel(false)
, m_paintLayer(0)
, m_displayProfile(0)
, m_resourceProvider(0)
{
setAutoFillBackground(false);
setMouseTracking(true);
m_cursor = KisCursor::load("tool_freehand_cursor.png", 5, 5);
m_colorPickerCursor = KisCursor::load("tool_color_picker_cursor.png", 5, 5);
setCursor(m_cursor);
KisConfig cfg(true);
QImage checkImage = KisCanvasWidgetBase::createCheckersImage(cfg.checkSize());
m_checkBrush = QBrush(checkImage);
// We are not supposed to use updates here,
// so just set the listener to null
m_updateScheduler = new KisUpdateScheduler(0);
m_undoStore = new KisSurrogateUndoStore();
m_undoAdapter = new KisPostExecutionUndoAdapter(m_undoStore, m_updateScheduler);
m_nodeListener = new KisScratchPadNodeListener(this);
connect(this, SIGNAL(sigUpdateCanvas(QRect)), SLOT(slotUpdateCanvas(QRect)), Qt::QueuedConnection);
// filter will be deleted by the QObject hierarchy
m_eventFilter = new KisScratchPadEventFilter(this);
m_infoBuilder = new KisPaintingInformationBuilder();
m_scaleBorderWidth = 1;
}
KisScratchPad::~KisScratchPad()
{
delete m_infoBuilder;
delete m_undoAdapter;
delete m_undoStore;
delete m_updateScheduler;
delete m_nodeListener;
}
KisScratchPad::Mode KisScratchPad::modeFromButton(Qt::MouseButton button) const
{
return
button == Qt::NoButton ? HOVERING :
button == Qt::MidButton ? PANNING :
button == Qt::RightButton ? PICKING :
PAINTING;
}
void KisScratchPad::pointerPress(KoPointerEvent *event)
{
- if (isModeManuallySet == false) {
-
- if (m_toolMode != HOVERING) return;
+ if(!isEnabled()) return;
+ if (isModeManuallySet == false) {
m_toolMode = modeFromButton(event->button());
-
}
// see if we are pressing down with a button
if (event->button() == Qt::LeftButton ||
event->button() == Qt::MidButton ||
event->button() == Qt::RightButton) {
isMouseDown = true;
} else {
isMouseDown = false;
}
// if mouse is down, we are doing one of three things
if(isMouseDown) {
if (m_toolMode == PAINTING) {
beginStroke(event);
event->accept();
}
else if (m_toolMode == PANNING) {
beginPan(event);
event->accept();
}
else if (m_toolMode == PICKING) {
pick(event);
event->accept();
}
}
}
void KisScratchPad::pointerRelease(KoPointerEvent *event)
{
+ if(!isEnabled()) return;
isMouseDown = false;
if (isModeManuallySet == false) {
if (modeFromButton(event->button()) != m_toolMode) return;
if (m_toolMode == PAINTING) {
endStroke(event);
m_toolMode = HOVERING;
event->accept();
}
else if (m_toolMode == PANNING) {
endPan(event);
m_toolMode = HOVERING;
event->accept();
}
else if (m_toolMode == PICKING) {
event->accept();
m_toolMode = HOVERING;
}
} else {
if (m_toolMode == PAINTING) {
endStroke(event);
}
else if (m_toolMode == PANNING) {
endPan(event);
}
event->accept();
}
}
void KisScratchPad::pointerMove(KoPointerEvent *event)
{
+ if(!isEnabled()) return;
+
if(event && event->point.isNull() == false) {
m_helper->cursorMoved(documentToWidget().map(event->point));
}
if (isMouseDown) {
if (m_toolMode == PAINTING) {
doStroke(event);
event->accept();
}
else if (m_toolMode == PANNING) {
doPan(event);
event->accept();
}
else if (m_toolMode == PICKING) {
pick(event);
event->accept();
}
}
}
void KisScratchPad::beginStroke(KoPointerEvent *event)
{
m_helper->initPaint(event,
documentToWidget().map(event->point),
0,
0,
m_updateScheduler,
m_paintLayer,
m_paintLayer->paintDevice()->defaultBounds());
}
void KisScratchPad::doStroke(KoPointerEvent *event)
{
m_helper->paintEvent(event);
}
void KisScratchPad::endStroke(KoPointerEvent *event)
{
Q_UNUSED(event);
m_helper->endPaint();
}
void KisScratchPad::beginPan(KoPointerEvent *event)
{
setCursor(QCursor(Qt::ClosedHandCursor));
m_panDocPoint = event->point;
}
void KisScratchPad::doPan(KoPointerEvent *event)
{
QPointF docOffset = event->point - m_panDocPoint;
m_translateTransform.translate(-docOffset.x(), -docOffset.y());
updateTransformations();
update();
}
void KisScratchPad::endPan(KoPointerEvent *event)
{
Q_UNUSED(event);
// the normal brush editor scratchpad reverts back to paint mode when done
if(isModeManuallySet) {
setCursor(QCursor(Qt::OpenHandCursor));
} else {
setCursor(m_cursor);
}
}
void KisScratchPad::pick(KoPointerEvent *event)
{
KoColor color;
if (KisToolUtils::pickColor(color, m_paintLayer->projection(), event->point.toPoint())) {
emit colorSelected(color);
}
}
void KisScratchPad::setOnScreenResolution(qreal scaleX, qreal scaleY)
{
m_scaleBorderWidth = BORDER_SIZE(qMax(scaleX, scaleY));
- m_scaleTransform = QTransform::fromScale(scaleX, scaleY);
+ // the scratchpad will use the canvas zoom level...or not
+ if(linkCanvasZoomLevel) {
+ m_scaleTransform = QTransform::fromScale(scaleX, scaleY);
+ } else {
+ m_scaleTransform = QTransform::fromScale(1, 1);
+ }
+
updateTransformations();
update();
}
QTransform KisScratchPad::documentToWidget() const
{
return m_translateTransform.inverted() * m_scaleTransform;
}
QTransform KisScratchPad::widgetToDocument() const
{
return m_scaleTransform.inverted() * m_translateTransform;
}
void KisScratchPad::updateTransformations()
{
m_eventFilter->setWidgetToDocumentTransform(widgetToDocument());
}
QRect KisScratchPad::imageBounds() const
{
return widgetToDocument().mapRect(rect());
}
void KisScratchPad::imageUpdated(const QRect &rect)
{
emit sigUpdateCanvas(documentToWidget().mapRect(QRectF(rect)).toAlignedRect());
}
void KisScratchPad::slotUpdateCanvas(const QRect &rect)
{
update(rect);
}
void KisScratchPad::paintEvent ( QPaintEvent * event ) {
if(!m_paintLayer) return;
QRectF imageRect = widgetToDocument().mapRect(QRectF(event->rect()));
QRect alignedImageRect =
imageRect.adjusted(-m_scaleBorderWidth, -m_scaleBorderWidth,
m_scaleBorderWidth, m_scaleBorderWidth).toAlignedRect();
QPointF offset = alignedImageRect.topLeft();
m_paintLayer->projectionPlane()->recalculate(alignedImageRect, m_paintLayer);
KisPaintDeviceSP projection = m_paintLayer->projection();
+
+
QImage image = projection->convertToQImage(m_displayProfile,
alignedImageRect.x(),
alignedImageRect.y(),
alignedImageRect.width(),
alignedImageRect.height(),
KoColorConversionTransformation::internalRenderingIntent(),
KoColorConversionTransformation::internalConversionFlags());
+
QPainter gc(this);
gc.fillRect(event->rect(), m_checkBrush);
gc.setRenderHints(QPainter::SmoothPixmapTransform);
gc.drawImage(QRectF(event->rect()), image, imageRect.translated(-offset));
QBrush brush(Qt::lightGray);
QPen pen(brush, 1, Qt::DotLine);
gc.setPen(pen);
if (m_cutoutOverlay.isValid()) {
gc.drawRect(m_cutoutOverlay);
}
if(!isEnabled()) {
QColor color(Qt::lightGray);
color.setAlphaF(0.5);
QBrush disabledBrush(color);
gc.fillRect(event->rect(), disabledBrush);
}
gc.end();
}
void KisScratchPad::setupScratchPad(KisCanvasResourceProvider* resourceProvider,
const QColor &defaultColor)
{
m_resourceProvider = resourceProvider;
KisConfig cfg(true);
setDisplayProfile(cfg.displayProfile(QApplication::desktop()->screenNumber(this)));
connect(m_resourceProvider, SIGNAL(sigDisplayProfileChanged(const KoColorProfile*)),
SLOT(setDisplayProfile(const KoColorProfile*)));
connect(m_resourceProvider, SIGNAL(sigOnScreenResolutionChanged(qreal,qreal)),
SLOT(setOnScreenResolution(qreal,qreal)));
connect(this, SIGNAL(colorSelected(KoColor)),
m_resourceProvider, SLOT(slotSetFGColor(KoColor)));
m_helper.reset(new KisToolFreehandHelper(m_infoBuilder, m_resourceProvider->resourceManager()));
setFillColor(defaultColor);
KisPaintDeviceSP paintDevice =
new KisPaintDevice(m_defaultColor.colorSpace(), "scratchpad");
m_paintLayer = new KisPaintLayer(0, "ScratchPad", OPACITY_OPAQUE_U8, paintDevice);
m_paintLayer->setGraphListener(m_nodeListener);
m_paintLayer->paintDevice()->setDefaultBounds(new KisScratchPadDefaultBounds(this));
fillDefault();
}
void KisScratchPad::setCutoutOverlayRect(const QRect& rc)
{
m_cutoutOverlay = rc;
}
void KisScratchPad::setModeManually(bool value)
{
isModeManuallySet = value;
}
void KisScratchPad::setModeType(QString mode)
{
if (mode.toLower() == "painting") {
m_toolMode = PAINTING;
setCursor(m_cursor);
}
else if (mode.toLower() == "panning") {
m_toolMode = PANNING;
setCursor(Qt::OpenHandCursor);
}
else if (mode.toLower() == "colorpicking") {
m_toolMode = PICKING;
setCursor(m_colorPickerCursor);
}
}
+void KisScratchPad::linkCanvavsToZoomLevel(bool value)
+{
+ linkCanvasZoomLevel = value;
+}
+
QImage KisScratchPad::cutoutOverlay() const
{
if(!m_paintLayer) return QImage();
KisPaintDeviceSP paintDevice = m_paintLayer->paintDevice();
QRect rc = widgetToDocument().mapRect(m_cutoutOverlay);
QImage rawImage = paintDevice->convertToQImage(0, rc.x(), rc.y(), rc.width(), rc.height(), KoColorConversionTransformation::internalRenderingIntent(), KoColorConversionTransformation::internalConversionFlags());
QImage scaledImage = rawImage.scaled(m_cutoutOverlay.size(),
Qt::IgnoreAspectRatio,
Qt::SmoothTransformation);
return scaledImage;
}
void KisScratchPad::setPresetImage(const QImage& image)
{
m_presetImage = image;
}
void KisScratchPad::paintCustomImage(const QImage& loadedImage)
{
// this is 99% copied from the normal paintPresetImage()
// we don't want to save over the preset image, so we don't
// want to store it in the m_presetImage
if(!m_paintLayer) return;
KisPaintDeviceSP paintDevice = m_paintLayer->paintDevice();
QRect overlayRect = widgetToDocument().mapRect(m_cutoutOverlay);
QRect imageRect(QPoint(), overlayRect.size());
QImage scaledImage = loadedImage.scaled(overlayRect.size(),
Qt::IgnoreAspectRatio,
Qt::SmoothTransformation);
KisPaintDeviceSP device = new KisPaintDevice(paintDevice->colorSpace());
device->convertFromQImage(scaledImage, 0);
KisPainter painter(paintDevice);
painter.beginTransaction();
painter.bitBlt(overlayRect.topLeft(), device, imageRect);
painter.deleteTransaction();
update();
}
void KisScratchPad::loadScratchpadImage(QImage image)
{
if(!m_paintLayer) return;
m_translateTransform.reset(); // image will be loaded at 0,0, so reset panning location
updateTransformations();
fillDefault(); // wipes out whatever was there before
QRect imageSize = image.rect();
KisPaintDeviceSP paintDevice = m_paintLayer->paintDevice();
KisPaintDeviceSP device = new KisPaintDevice(paintDevice->colorSpace());
device->convertFromQImage(image, 0);
KisPainter painter(paintDevice);
painter.beginTransaction();
painter.bitBlt(imageSize.topLeft(), device, imageSize);
painter.deleteTransaction();
update();
}
QImage KisScratchPad::copyScratchpadImageData()
{
const QRect paintingBounds = m_paintLayer.data()->exactBounds();
QImage imageData = m_paintLayer->paintDevice()->convertToQImage(0, paintingBounds.x(), paintingBounds.y(), paintingBounds.width(), paintingBounds.height(),
KoColorConversionTransformation::internalRenderingIntent(),
KoColorConversionTransformation::internalConversionFlags());
return imageData;
}
void KisScratchPad::paintPresetImage()
{
if(!m_paintLayer) return;
KisPaintDeviceSP paintDevice = m_paintLayer->paintDevice();
QRect overlayRect = widgetToDocument().mapRect(m_cutoutOverlay);
QRect imageRect(QPoint(), overlayRect.size());
QImage scaledImage = m_presetImage.scaled(overlayRect.size(),
Qt::IgnoreAspectRatio,
Qt::SmoothTransformation);
KisPaintDeviceSP device = new KisPaintDevice(paintDevice->colorSpace());
device->convertFromQImage(scaledImage, 0);
KisPainter painter(paintDevice);
painter.beginTransaction();
painter.bitBlt(overlayRect.topLeft(), device, imageRect);
painter.deleteTransaction();
update();
}
void KisScratchPad::setDisplayProfile(const KoColorProfile *colorProfile)
{
if (colorProfile) {
m_displayProfile = colorProfile;
QWidget::update();
}
}
void KisScratchPad::fillDefault()
{
if(!m_paintLayer) return;
KisPaintDeviceSP paintDevice = m_paintLayer->paintDevice();
KisTransaction t(paintDevice);
paintDevice->setDefaultPixel(m_defaultColor);
paintDevice->clear();
t.end();
update();
}
void KisScratchPad::fillTransparent() {
if(!m_paintLayer) return;
KisPaintDeviceSP paintDevice = m_paintLayer->paintDevice();
QColor transQColor(0,0,0,0);
KoColor transparentColor(transQColor, KoColorSpaceRegistry::instance()->rgb8());
transparentColor.setOpacity(0.0);
KisTransaction t(paintDevice);
paintDevice->setDefaultPixel(transparentColor);
paintDevice->clear();
t.end();
update();
}
void KisScratchPad::setFillColor(QColor newColor)
{
m_defaultColor = KoColor(newColor, KoColorSpaceRegistry::instance()->rgb8());
}
void KisScratchPad::fillGradient()
{
if(!m_paintLayer) return;
KisPaintDeviceSP paintDevice = m_paintLayer->paintDevice();
KoAbstractGradientSP gradient = m_resourceProvider->currentGradient();
QRect gradientRect = widgetToDocument().mapRect(rect());
KisTransaction t(paintDevice);
paintDevice->clear();
KisGradientPainter painter(paintDevice);
painter.setGradient(gradient);
painter.setGradientShape(KisGradientPainter::GradientShapeLinear);
painter.paintGradient(gradientRect.topLeft(),
gradientRect.bottomRight(),
KisGradientPainter::GradientRepeatNone,
0.2, false,
gradientRect.left(), gradientRect.top(),
gradientRect.width(), gradientRect.height());
t.end();
update();
}
void KisScratchPad::fillBackground()
{
if(!m_paintLayer) return;
KisPaintDeviceSP paintDevice = m_paintLayer->paintDevice();
KisTransaction t(paintDevice);
paintDevice->setDefaultPixel(m_resourceProvider->bgColor());
paintDevice->clear();
t.end();
update();
}
void KisScratchPad::fillLayer()
{
if(!m_paintLayer) return;
KisPaintDeviceSP paintDevice = m_paintLayer->paintDevice();
QRect sourceRect(0, 0, paintDevice->exactBounds().width(), paintDevice->exactBounds().height());
KisPainter painter(paintDevice);
painter.beginTransaction();
painter.bitBlt(QPoint(0, 0), m_resourceProvider->currentImage()->projection(), sourceRect);
painter.deleteTransaction();
update();
}
diff --git a/libs/ui/widgets/kis_scratch_pad.h b/libs/ui/widgets/kis_scratch_pad.h
index 34c1168919..b963906e42 100644
--- a/libs/ui/widgets/kis_scratch_pad.h
+++ b/libs/ui/widgets/kis_scratch_pad.h
@@ -1,204 +1,212 @@
/* This file is part of the KDE project
* Copyright 2010 (C) Boudewijn Rempt
* Copyright 2011 (C) Dmitry Kazakov
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public License
* along with this library; see the file COPYING.LIB. If not, write to
* the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#ifndef KIS_SCRATCH_PAD_H
#define KIS_SCRATCH_PAD_H
#include
#include
#include
#include
#include
#include
#include
class QColor;
class KoColorProfile;
class KoPointerEvent;
class KisCanvasResourceProvider;
class KisUpdateScheduler;
class KisUndoStore;
class KisPostExecutionUndoAdapter;
class KisScratchPadEventFilter;
class KisPaintingInformationBuilder;
class KisToolFreehandHelper;
class KisNodeGraphListener;
/**
* A scratchpad is a painting canvas with only one zoomlevel and based on
* a paint layer, not on a KisImage. It can have a blank, tiled background or
* a gradient background.
*/
class KRITAUI_EXPORT KisScratchPad : public QWidget
{
Q_OBJECT
public:
void setupScratchPad(KisCanvasResourceProvider* resourceProvider,
const QColor &defaultColor);
KisScratchPad(QWidget *parent = 0);
~KisScratchPad() override;
/// set the specified rect as the area taken for @see cutoutOverlay
void setCutoutOverlayRect(const QRect&rc);
/**
* keep track of if our scratchpad is in paint, pan, or color pick mode
* Set to true if there is a GUI controlling current mode
* If this is false, the modes are only changed with various mouse click shortcuts
*/
void setModeManually(bool value);
/**
* @brief change the mode explicitly to paint, mix, or pan
* @param what mode to change it to
*/
void setModeType(QString modeName);
+ /**
+ * @brief should the scratchpad zoom level stay in sync with canvas
+ * @param should we link zoom level
+ */
+ void linkCanvavsToZoomLevel(bool value);
+
+
/// return the contents of the area under the cutoutOverlay rect
QImage cutoutOverlay() const;
// A callback for our own node graph listener
void imageUpdated(const QRect &rect);
// A callback for scratch pad default bounds
QRect imageBounds() const;
// Called by the event filter
void pointerPress(KoPointerEvent *event);
void pointerRelease(KoPointerEvent *event);
void pointerMove(KoPointerEvent *event);
public Q_SLOTS:
void fillDefault();
void fillGradient();
void fillBackground();
void fillTransparent();
void setFillColor(QColor newColor);
/// Fill the area with what is on your current canvas
void fillLayer();
/**
* Set the icon of the current preset
*/
void setPresetImage(const QImage& image);
/**
* Paint the icon of the current preset inside the
* cutout overlay
*
* \see setPresetImage
*/
void paintPresetImage();
/**
* Paint the icon of a custom image that is being loaded
*
*/
void paintCustomImage(const QImage & loadedImage);
void loadScratchpadImage(QImage image);
QImage copyScratchpadImageData();
private Q_SLOTS:
void setOnScreenResolution(qreal scaleX, qreal scaleY);
void setDisplayProfile(const KoColorProfile* colorProfile);
void slotUpdateCanvas(const QRect &rect);
Q_SIGNALS:
void colorSelected(const KoColor& color);
void sigUpdateCanvas(const QRect &rect);
protected:
void paintEvent ( QPaintEvent * event ) override;
private:
void beginStroke(KoPointerEvent *event);
void doStroke(KoPointerEvent *event);
void endStroke(KoPointerEvent *event);
void beginPan(KoPointerEvent *event);
void doPan(KoPointerEvent *event);
void endPan(KoPointerEvent *event);
void pick(KoPointerEvent *event);
void updateTransformations();
QTransform documentToWidget() const;
QTransform widgetToDocument() const;
private:
enum Mode {
PAINTING,
HOVERING,
PANNING,
PICKING
};
Mode modeFromButton(Qt::MouseButton button) const;
private:
KoColor m_defaultColor;
Mode m_toolMode;
- bool isModeManuallySet = false;
- bool isMouseDown = false;
+ bool isModeManuallySet;
+ bool isMouseDown;
+ bool linkCanvasZoomLevel;
KisPaintLayerSP m_paintLayer;
const KoColorProfile* m_displayProfile;
QCursor m_cursor;
QCursor m_colorPickerCursor;
QRect m_cutoutOverlay;
QBrush m_checkBrush;
KisCanvasResourceProvider* m_resourceProvider;
KisUpdateScheduler *m_updateScheduler;
KisUndoStore *m_undoStore;
KisPostExecutionUndoAdapter *m_undoAdapter;
KisNodeGraphListener *m_nodeListener;
KisScratchPadEventFilter *m_eventFilter;
QScopedPointer m_helper;
KisPaintingInformationBuilder *m_infoBuilder;
QTransform m_scaleTransform;
QTransform m_translateTransform;
QPointF m_panDocPoint;
int m_scaleBorderWidth;
QImage m_presetImage;
};
#endif // KIS_SCRATCH_PAD_H
diff --git a/plugins/extensions/pykrita/sip/krita/Document.sip b/plugins/extensions/pykrita/sip/krita/Document.sip
index fdad92d1be..29c06485f6 100644
--- a/plugins/extensions/pykrita/sip/krita/Document.sip
+++ b/plugins/extensions/pykrita/sip/krita/Document.sip
@@ -1,103 +1,103 @@
class Document : QObject /NoDefaultCtors/
{
%TypeHeaderCode
#include "Document.h"
%End
Document(const Document & __0);
public:
bool operator==(const Document &other) const;
bool operator!=(const Document &other) const;
QList horizontalGuides() const;
QList verticalGuides() const;
bool guidesVisible() const;
bool guidesLocked() const;
public Q_SLOTS:
Document *clone() const /Factory/;
Node * activeNode() const /Factory/;
void setActiveNode(Node* value);
QList topLevelNodes() const /Factory/;
Node *nodeByName(const QString &node) const /Factory/;
bool batchmode() const;
void setBatchmode(bool value);
QString colorDepth() const;
QString colorModel() const;
QString colorProfile() const;
bool setColorProfile(const QString &colorProfile);
bool setColorSpace(const QString &value, const QString &colorDepth, const QString &colorProfile);
QColor backgroundColor();
bool setBackgroundColor(const QColor &color);
QString documentInfo() const;
void setDocumentInfo(const QString &document);
QString fileName() const;
void setFileName(QString value);
int height() const;
void setHeight(int value);
QString name() const;
void setName(QString value);
int resolution() const;
void setResolution(int value);
Node * rootNode() const /Factory/;
Selection * selection() const /Factory/;
void setSelection(Selection* value);
int width() const;
void setWidth(int value);
int xOffset() const;
void setXOffset(int x);
int yOffset() const;
void setYOffset(int y);
double xRes() const;
void setXRes(double xRes) const;
double yRes() const;
void setYRes(double yRes) const;
QByteArray pixelData(int x, int y, int w, int h) const;
bool close();
void crop(int x, int y, int w, int h);
bool exportImage(const QString &filename, const InfoObject & exportConfiguration);
void flatten();
void resizeImage(int x, int y, int w, int h);
void scaleImage(int w, int h, int xres, int yres, QString strategy);
void rotateImage(double radians);
void shearImage(double angleX, double angleY);
bool save();
bool saveAs(const QString & filename);
Node *createNode(const QString & name, const QString & nodeType) /Factory/;
GroupLayer *createGroupLayer(const QString &name) /Factory/;
CloneLayer *createCloneLayer(const QString &name, const Node *source) /Factory/;
FilterLayer *createFilterLayer(const QString &name, Filter &filter, Selection &selection) /Factory/;
FillLayer *createFillLayer(const QString &name, const QString filterName, InfoObject &configuration, Selection &selection) /Factory/;
VectorLayer *createVectorLayer(const QString &name) /Factory/;
FilterMask *createFilterMask(const QString &name, Filter &filter, Selection &selection) /Factory/;
FilterMask *createFilterMask(const QString &name, Filter &filter, const Node *selection_source) /Factory/;
SelectionMask *createSelectionMask(const QString &name) /Factory/;
QImage projection(int x = 0, int y = 0, int w = 0, int h = 0) const;
QImage thumbnail(int w, int h) const;
void lock();
void unlock();
void waitForDone();
bool tryBarrierLock();
void refreshProjection();
void setHorizontalGuides(const QList &lines);
void setVerticalGuides(const QList &lines);
void setGuidesVisible(bool visible);
void setGuidesLocked(bool locked);
bool modified() const;
QRect bounds() const;
bool importAnimation(const QList &files, int firstFrame, int step);
int framesPerSecond();
void setFramesPerSecond(int fps);
void setFullClipRangeStartTime(int startTime);
int fullClipRangeStartTime();
void setFullClipRangeEndTime(int endTime);
int fullClipRangeEndTime();
int animationLength();
void setPlayBackRange(int start, int stop);
int playBackStartTime();
int playBackEndTime();
int currentTime();
void setCurrentTime(int time);
-
+
private:
};
diff --git a/plugins/extensions/pykrita/sip/krita/Scratchpad.sip b/plugins/extensions/pykrita/sip/krita/Scratchpad.sip
index 9f056193d9..51a7110f03 100644
--- a/plugins/extensions/pykrita/sip/krita/Scratchpad.sip
+++ b/plugins/extensions/pykrita/sip/krita/Scratchpad.sip
@@ -1,19 +1,21 @@
class Scratchpad : public QWidget /NoDefaultCtors/
{
%TypeHeaderCode
#include "Scratchpad.h"
%End
+
public:
- Scratchpad(View* view , const QString & defaultColor, QWidget* parent /TransferThis/ = 0);
+ Scratchpad(View* view , const QColor & defaultColor, QWidget* parent /TransferThis/ = 0);
virtual ~Scratchpad();
public Q_SLOTS:
void clear();
void setModeManually(bool value);
+ void linkCanvasZoom(bool value);
void setMode(QString modeName);
void setFillColor(QColor color);
void loadScratchpadImage(QImage image);
QImage copyScratchpadImageData();
};