diff --git a/libs/libkis/Document.cpp b/libs/libkis/Document.cpp index 3257d9cc78..c6fd3a4344 100644 --- a/libs/libkis/Document.cpp +++ b/libs/libkis/Document.cpp @@ -1,1026 +1,1036 @@ /* * 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 "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 #include #include #include #include #include #include #include +#include "Scratchpad.h" #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); } +Scratchpad *Document::createScratchpad() +{ + if (!d->document) return 0; + + return static_cast(d->document->addScratchPad()); +} + + 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/Document.h b/libs/libkis/Document.h index 449b1e5665..910cbfaf8e 100644 --- a/libs/libkis/Document.h +++ b/libs/libkis/Document.h @@ -1,880 +1,887 @@ /* * 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_DOCUMENT_H #define LIBKIS_DOCUMENT_H #include #include "kritalibkis_export.h" #include "libkis.h" #include "GroupLayer.h" #include "CloneLayer.h" #include "FileLayer.h" #include "FilterLayer.h" #include "FillLayer.h" #include "VectorLayer.h" #include "FilterMask.h" #include "SelectionMask.h" class KisDocument; +class Scratchpad; /** * The Document class encapsulates a Krita Document/Image. A Krita document is an Image with * a filename. Libkis does not differentiate between a document and an image, like Krita does * internally. */ class KRITALIBKIS_EXPORT Document : public QObject { Q_OBJECT Q_DISABLE_COPY(Document) public: explicit Document(KisDocument *document, bool ownsDocument, QObject *parent = 0); ~Document() override; bool operator==(const Document &other) const; bool operator!=(const Document &other) const; /** * @brief horizontalGuides * The horizontal guides. * @return a list of the horizontal positions of guides. */ QList horizontalGuides() const; /** * @brief verticalGuides * The vertical guide lines. * @return a list of vertical guides. */ QList verticalGuides() const; /** * @brief guidesVisible * Returns guide visibility. * @return whether the guides are visible. */ bool guidesVisible() const; /** * @brief guidesLocked * Returns guide lockedness. * @return whether the guides are locked. */ bool guidesLocked() const; public Q_SLOTS: /** * @brief clone create a shallow clone of this document. * @return a new Document that should be identical to this one in every respect. */ Document *clone() const; /** * Batchmode means that no actions on the document should show dialogs or popups. * @return true if the document is in batchmode. */ bool batchmode() const; /** * Set batchmode to @p value. If batchmode is true, then there should be no popups * or dialogs shown to the user. */ void setBatchmode(bool value); /** * @brief activeNode retrieve the node that is currently active in the currently active window * @return the active node. If there is no active window, the first child node is returned. */ Node* activeNode() const; /** * @brief setActiveNode make the given node active in the currently active view and window * @param value the node to make active. */ void setActiveNode(Node* value); /** * @brief toplevelNodes return a list with all top level nodes in the image graph */ QList topLevelNodes() const; /** * @brief nodeByName searches the node tree for a node with the given name and returns it * @param name the name of the node * @return the first node with the given name or 0 if no node is found */ Node *nodeByName(const QString &name) const; /** * colorDepth A string describing the color depth of the image: *
    *
  • U8: unsigned 8 bits integer, the most common type
  • *
  • U16: unsigned 16 bits integer
  • *
  • F16: half, 16 bits floating point. Only available if Krita was built with OpenEXR
  • *
  • F32: 32 bits floating point
  • *
* @return the color depth. */ QString colorDepth() const; /** * @brief colorModel retrieve the current color model of this document: *
    *
  • A: Alpha mask
  • *
  • RGBA: RGB with alpha channel (The actual order of channels is most often BGR!)
  • *
  • XYZA: XYZ with alpha channel
  • *
  • LABA: LAB with alpha channel
  • *
  • CMYKA: CMYK with alpha channel
  • *
  • GRAYA: Gray with alpha channel
  • *
  • YCbCrA: YCbCr with alpha channel
  • *
* @return the internal color model string. */ QString colorModel() const; /** * @return the name of the current color profile */ QString colorProfile() const; /** * @brief setColorProfile set the color profile of the image to the given profile. The profile has to * be registered with krita and be compatible with the current color model and depth; the image data * is not converted. * @param colorProfile * @return false if the colorProfile name does not correspond to to a registered profile or if assigning * the profile failed. */ bool setColorProfile(const QString &colorProfile); /** * @brief setColorSpace convert the nodes and the image to the given colorspace. The conversion is * done with Perceptual as intent, High Quality and No LCMS Optimizations as flags and no blackpoint * compensation. * * @param colorModel A string describing the color model of the image: *
    *
  • A: Alpha mask
  • *
  • RGBA: RGB with alpha channel (The actual order of channels is most often BGR!)
  • *
  • XYZA: XYZ with alpha channel
  • *
  • LABA: LAB with alpha channel
  • *
  • CMYKA: CMYK with alpha channel
  • *
  • GRAYA: Gray with alpha channel
  • *
  • YCbCrA: YCbCr with alpha channel
  • *
* @param colorDepth A string describing the color depth of the image: *
    *
  • U8: unsigned 8 bits integer, the most common type
  • *
  • U16: unsigned 16 bits integer
  • *
  • F16: half, 16 bits floating point. Only available if Krita was built with OpenEXR
  • *
  • F32: 32 bits floating point
  • *
* @param colorProfile a valid color profile for this color model and color depth combination. * @return false the combination of these arguments does not correspond to a colorspace. */ bool setColorSpace(const QString &colorModel, const QString &colorDepth, const QString &colorProfile); /** * @brief backgroundColor returns the current background color of the document. The color will * also include the opacity. * * @return QColor */ QColor backgroundColor(); /** * @brief setBackgroundColor sets the background color of the document. It will trigger a projection * update. * * @param color A QColor. The color will be converted from sRGB. * @return bool */ bool setBackgroundColor(const QColor &color); /** * @brief documentInfo creates and XML document representing document and author information. * @return a string containing a valid XML document with the right information about the document * and author. The DTD can be found here: * * https://phabricator.kde.org/source/krita/browse/master/krita/dtd/ * * @code * * * * * My Document * * * * * Unknown * 1 * 35 * 2017-02-27T20:15:09 * 2017-02-27T20:14:33 * * * * Boudewijn Rempt * * * * * * * * * * * * * * * @endcode * */ QString documentInfo() const; /** * @brief setDocumentInfo set the Document information to the information contained in document * @param document A string containing a valid XML document that conforms to the document-info DTD * that can be found here: * * https://phabricator.kde.org/source/krita/browse/master/krita/dtd/ */ void setDocumentInfo(const QString &document); /** * @return the full path to the document, if it has been set. */ QString fileName() const; /** * @brief setFileName set the full path of the document to @param value */ void setFileName(QString value); /** * @return the height of the image in pixels */ int height() const; /** * @brief setHeight resize the document to @param value height. This is a canvas resize, not a scale. */ void setHeight(int value); /** * @return the name of the document. This is the title field in the @ref documentInfo */ QString name() const; /** * @brief setName sets the name of the document to @p value. This is the title field in the @ref documentInfo */ void setName(QString value); /** * @return the resolution in pixels per inch */ int resolution() const; /** * @brief setResolution set the resolution of the image; this does not scale the image * @param value the resolution in pixels per inch */ void setResolution(int value); /** * @brief rootNode the root node is the invisible group layer that contains the entire node * hierarchy. * @return the root of the image */ Node* rootNode() const; /** * @brief selection Create a Selection object around the global selection, if there is one. * @return the global selection or None if there is no global selection. */ Selection* selection() const; /** * @brief setSelection set or replace the global selection * @param value a valid selection object. */ void setSelection(Selection* value); /** * @return the width of the image in pixels. */ int width() const; /** * @brief setWidth resize the document to @param value width. This is a canvas resize, not a scale. */ void setWidth(int value); /** * @return the left edge of the canvas in pixels. */ int xOffset() const; /** * @brief setXOffset sets the left edge of the canvas to @p x. */ void setXOffset(int x); /** * @return the top edge of the canvas in pixels. */ int yOffset() const; /** * @brief setYOffset sets the top edge of the canvas to @p y. */ void setYOffset(int y); /** * @return xRes the horizontal resolution of the image in pixels * per inch */ double xRes() const; /** * @brief setXRes set the horizontal resolution of the image to * xRes in pixels per inch */ void setXRes(double xRes) const; /** * @return yRes the vertical resolution of the image in pixels per * inch */ double yRes() const; /** * @brief setYRes set the vertical resolution of the image to yRes * in pixels per inch */ void setYRes(double yRes) const; /** * @brief pixelData reads the given rectangle from the image projection and returns it as a byte * array. The pixel data starts top-left, and is ordered row-first. * * The byte array can be interpreted as follows: 8 bits images have one byte per channel, * and as many bytes as there are channels. 16 bits integer images have two bytes per channel, * representing an unsigned short. 16 bits float images have two bytes per channel, representing * a half, or 16 bits float. 32 bits float images have four bytes per channel, representing a * float. * * You can read outside the image boundaries; those pixels will be transparent black. * * The order of channels is: * *
    *
  • Integer RGBA: Blue, Green, Red, Alpha *
  • Float RGBA: Red, Green, Blue, Alpha *
  • LabA: L, a, b, Alpha *
  • CMYKA: Cyan, Magenta, Yellow, Key, Alpha *
  • XYZA: X, Y, Z, A *
  • YCbCrA: Y, Cb, Cr, Alpha *
* * The byte array is a copy of the original image data. In Python, you can use bytes, bytearray * and the struct module to interpret the data and construct, for instance, a Pillow Image object. * * @param x x position from where to start reading * @param y y position from where to start reading * @param w row length to read * @param h number of rows to read * @return a QByteArray with the pixel data. The byte array may be empty. */ QByteArray pixelData(int x, int y, int w, int h) const; /** * @brief close Close the document: remove it from Krita's internal list of documents and * close all views. If the document is modified, you should save it first. There will be * no prompt for saving. * * After closing the document it becomes invalid. * * @return true if the document is closed. */ bool close(); /** * @brief crop the image to rectangle described by @p x, @p y, * @p w and @p h * @param x x coordinate of the top left corner * @param y y coordinate of the top left corner * @param w width * @param h height */ void crop(int x, int y, int w, int h); /** * @brief exportImage export the image, without changing its URL to the given path. * @param filename the full path to which the image is to be saved * @param exportConfiguration a configuration object appropriate to the file format. * An InfoObject will used to that configuration. * * The supported formats have specific configurations that must be used when in * batchmode. They are described below: * *\b png *
    *
  • alpha: bool (True or False) *
  • compression: int (1 to 9) *
  • forceSRGB: bool (True or False) *
  • indexed: bool (True or False) *
  • interlaced: bool (True or False) *
  • saveSRGBProfile: bool (True or False) *
  • transparencyFillcolor: rgb (Ex:[255,255,255]) *
* *\b jpeg *
    *
  • baseline: bool (True or False) *
  • exif: bool (True or False) *
  • filters: bool (['ToolInfo', 'Anonymizer']) *
  • forceSRGB: bool (True or False) *
  • iptc: bool (True or False) *
  • is_sRGB: bool (True or False) *
  • optimize: bool (True or False) *
  • progressive: bool (True or False) *
  • quality: int (0 to 100) *
  • saveProfile: bool (True or False) *
  • smoothing: int (0 to 100) *
  • subsampling: int (0 to 3) *
  • transparencyFillcolor: rgb (Ex:[255,255,255]) *
  • xmp: bool (True or False) *
* @return true if the export succeeded, false if it failed. */ bool exportImage(const QString &filename, const InfoObject &exportConfiguration); /** * @brief flatten all layers in the image */ void flatten(); /** * @brief resizeImage resizes the canvas to the given left edge, top edge, width and height. * Note: This doesn't scale, use scale image for that. * @param x the new left edge * @param y the new top edge * @param w the new width * @param h the new height */ void resizeImage(int x, int y, int w, int h); /** * @brief scaleImage * @param w the new width * @param h the new height * @param xres the new xres * @param yres the new yres * @param strategy the scaling strategy. There's several ones amongst these that aren't available in the regular UI. * The list of filters is extensible and can be retrieved with Krita::filter *
    *
  • Hermite
  • *
  • Bicubic - Adds pixels using the color of surrounding pixels. Produces smoother tonal gradations than Bilinear.
  • *
  • Box - Replicate pixels in the image. Preserves all the original detail, but can produce jagged effects.
  • *
  • Bilinear - Adds pixels averaging the color values of surrounding pixels. Produces medium quality results when the image is scaled from half to two times the original size.
  • *
  • Bell
  • *
  • BSpline
  • *
  • Kanczos3 - Offers similar results than Bicubic, but maybe a little bit sharper. Can produce light and dark halos along strong edges.
  • *
  • Mitchell
  • *
*/ void scaleImage(int w, int h, int xres, int yres, QString strategy); /** * @brief rotateImage * Rotate the image by the given radians. * @param radians the amount you wish to rotate the image in radians */ void rotateImage(double radians); /** * @brief shearImage shear the whole image. * @param angleX the X-angle in degrees to shear by * @param angleY the Y-angle in degrees to shear by */ void shearImage(double angleX, double angleY); /** * @brief save the image to its currently set path. The modified flag of the * document will be reset * @return true if saving succeeded, false otherwise. */ bool save(); /** * @brief saveAs save the document under the @p filename. The document's * filename will be reset to @p filename. * @param filename the new filename (full path) for the document * @return true if saving succeeded, false otherwise. */ bool saveAs(const QString &filename); /** * @brief createNode create a new node of the given type. The node is not added * to the node hierarchy; you need to do that by finding the right parent node, * getting its list of child nodes and adding the node in the right place, then * calling Node::SetChildNodes * * @param name The name of the node * * @param nodeType The type of the node. Valid types are: *
    *
  • paintlayer *
  • grouplayer *
  • filelayer *
  • filterlayer *
  • filllayer *
  • clonelayer *
  • vectorlayer *
  • transparencymask *
  • filtermask *
  • transformmask *
  • selectionmask *
* * When relevant, the new Node will have the colorspace of the image by default; * that can be changed with Node::setColorSpace. * * The settings and selections for relevant layer and mask types can also be set * after the Node has been created. * @code d = Application.createDocument(1000, 1000, "Test", "RGBA", "U8", "", 120.0) root = d.rootNode(); print(root.childNodes()) l2 = d.createNode("layer2", "paintLayer") print(l2) root.addChildNode(l2, None) print(root.childNodes()) @endcode * * * @return the new Node. */ Node* createNode(const QString &name, const QString &nodeType); /** * @brief createGroupLayer * Returns a grouplayer object. Grouplayers are nodes that can have * other layers as children and have the passthrough mode. * @param name the name of the layer. * @return a GroupLayer object. */ GroupLayer* createGroupLayer(const QString &name); /** * @brief createFileLayer returns a layer that shows an external image. * @param name name of the file layer. * @param fileName the absolute filename of the file referenced. Symlinks will be resolved. * @param scalingMethod how the dimensions of the file are interpreted * can be either "None", "ImageToSize" or "ImageToPPI" * @return a FileLayer */ FileLayer* createFileLayer(const QString &name, const QString fileName, const QString scalingMethod); /** * @brief createFilterLayer creates a filter layer, which is a layer that represents a filter * applied non-destructively. * @param name name of the filterLayer * @param filter the filter that this filter layer will us. * @param selection the selection. * @return a filter layer object. */ FilterLayer* createFilterLayer(const QString &name, Filter &filter, Selection &selection); /** * @brief createFillLayer creates a fill layer object, which is a layer * @param name * @param generatorName - name of the generation filter. * @param configuration - the configuration for the generation filter. * @param selection - the selection. * @return a filllayer object. * * @code * from krita import * * d = Krita.instance().activeDocument() * i = InfoObject(); * i.setProperty("pattern", "Cross01.pat") * s = Selection(); * s.select(0, 0, d.width(), d.height(), 255) * n = d.createFillLayer("test", "pattern", i, s) * r = d.rootNode(); * c = r.childNodes(); * r.addChildNode(n, c[0]) * d.refreshProjection() * @endcode */ FillLayer* createFillLayer(const QString &name, const QString generatorName, InfoObject &configuration, Selection &selection); /** * @brief createCloneLayer * @param name * @param source * @return */ CloneLayer* createCloneLayer(const QString &name, const Node* source); /** * @brief createVectorLayer * Creates a vector layer that can contain vector shapes. * @param name the name of this layer. * @return a VectorLayer. */ VectorLayer* createVectorLayer(const QString &name); /** * @brief createFilterMask * Creates a filter mask object that much like a filterlayer can apply a filter non-destructively. * @param name the name of the layer. * @param filter the filter assigned. * @param selection the selection to be used by the filter mask * @return a FilterMask */ FilterMask* createFilterMask(const QString &name, Filter &filter, Selection &selection); /** * @brief createFilterMask * Creates a filter mask object that much like a filterlayer can apply a filter non-destructively. * @param name the name of the layer. * @param filter the filter assigned. * @param selection_source a node from which the selection should be initialized * @return a FilterMask */ FilterMask* createFilterMask(const QString &name, Filter &filter, const Node* selection_source); /** * @brief createSelectionMask * Creates a selection mask, which can be used to store selections. * @param name - the name of the layer. * @return a SelectionMask */ SelectionMask* createSelectionMask(const QString &name); + /** + * @brief creates a scratchpad widget to draw on. + * @return a scratchpad + */ + Scratchpad *createScratchpad(); /** * @brief projection creates a QImage from the rendered image or * a cutout rectangle. */ QImage projection(int x = 0, int y = 0, int w = 0, int h = 0) const; /** * @brief thumbnail create a thumbnail of the given dimensions. * * If the requested size is too big a null QImage is created. * * @return a QImage representing the layer contents. */ QImage thumbnail(int w, int h) const; /** * [low-level] Lock the image without waiting for all the internal job queues are processed * * WARNING: Don't use it unless you really know what you are doing! Use barrierLock() instead! * * Waits for all the **currently running** internal jobs to complete and locks the image * for writing. Please note that this function does **not** wait for all the internal * queues to process, so there might be some non-finished actions pending. It means that * you just postpone these actions until you unlock() the image back. Until then, then image * might easily be frozen in some inconsistent state. * * The only sane usage for this function is to lock the image for **emergency** * processing, when some internal action or scheduler got hung up, and you just want * to fetch some data from the image without races. * * In all other cases, please use barrierLock() instead! */ void lock(); /** * Unlocks the image and starts/resumes all the pending internal jobs. If the image * has been locked for a non-readOnly access, then all the internal caches of the image * (e.g. lod-planes) are reset and regeneration jobs are scheduled. */ void unlock(); /** * Wait for all the internal image jobs to complete and return without locking * the image. This function is handly for tests or other synchronous actions, * when one needs to wait for the result of his actions. */ void waitForDone(); /** * @brief Tries to lock the image without waiting for the jobs to finish * * Same as barrierLock(), but doesn't block execution of the calling thread * until all the background jobs are finished. Instead, in case of presence of * unfinished jobs in the queue, it just returns false * * @return whether the lock has been acquired */ bool tryBarrierLock(); /** * Starts a synchronous recomposition of the projection: everything will * wait until the image is fully recomputed. */ void refreshProjection(); /** * @brief setHorizontalGuides * replace all existing horizontal guides with the entries in the list. * @param lines a list of floats containing the new guides. */ void setHorizontalGuides(const QList &lines); /** * @brief setVerticalGuides * replace all existing horizontal guides with the entries in the list. * @param lines a list of floats containing the new guides. */ void setVerticalGuides(const QList &lines); /** * @brief setGuidesVisible * set guides visible on this document. * @param visible whether or not the guides are visible. */ void setGuidesVisible(bool visible); /** * @brief setGuidesLocked * set guides locked on this document * @param locked whether or not to lock the guides on this document. */ void setGuidesLocked(bool locked); /** * @brief modified returns true if the document has unsaved modifications. */ bool modified() const; /** * @brief bounds return the bounds of the image * @return the bounds */ QRect bounds() const; /**** * Animation Related API *****/ /** * @brief Import an image sequence of files from a directory. This will grab all * images from the directory and import them with a potential offset (firstFrame) * and step (images on 2s, 3s, etc) * @returns whether the animation import was successful */ bool importAnimation(const QList &files, int firstFrame, int step); /** * @brief frames per second of document * @return the fps of the document */ int framesPerSecond(); /** * @brief set frames per second of document */ void setFramesPerSecond(int fps); /** * @brief set start time of animation */ void setFullClipRangeStartTime(int startTime); /** * @brief get the full clip range start time * @return full clip range start time */ int fullClipRangeStartTime(); /** * @brief set full clip range end time */ void setFullClipRangeEndTime(int endTime); /** * @brief get the full clip range end time * @return full clip range end time */ int fullClipRangeEndTime(); /** * @brief get total frame range for animation * @return total frame range for animation */ int animationLength(); /** * @brief set temporary playback range of document */ void setPlayBackRange(int start, int stop); /** * @brief get start time of current playback * @return start time of current playback */ int playBackStartTime(); /** * @brief get end time of current playback * @return end time of current playback */ int playBackEndTime(); /** * @brief get current frame selected of animation * @return current frame selected of animation */ int currentTime(); /** * @brief set current time of document's animation */ void setCurrentTime(int time); private: friend class Krita; friend class Window; friend class Filter; friend class View; + friend class Scratchpad; QPointer document() const; void setOwnsDocument(bool ownsDocument); private: struct Private; Private *const d; }; #endif // LIBKIS_DOCUMENT_H diff --git a/libs/libkis/Scratchpad.cpp b/libs/libkis/Scratchpad.cpp index a900d2b718..638b15f73e 100644 --- a/libs/libkis/Scratchpad.cpp +++ b/libs/libkis/Scratchpad.cpp @@ -1,70 +1,75 @@ /* * 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 Scratchpad::Scratchpad(View *view, const QColor & defaultColor, QWidget *parent) : KisScratchPad(parent) { KisScratchPad::setupScratchPad(view->view()->resourceProvider(), defaultColor); KisScratchPad::setMinimumSize(50, 50); } Scratchpad::~Scratchpad() { } void Scratchpad::setModeManually(bool value) { KisScratchPad::setModeManually(value); } void Scratchpad::setMode(QString modeType) { KisScratchPad::setModeType(modeType); } +void Scratchpad::linkCanvasZoom(bool value) +{ + KisScratchPad::linkCanvavsToZoomLevel(value); +} + void Scratchpad::loadScratchpad(QImage image) { KisScratchPad::loadScratchpadImage(image); } QImage Scratchpad::copyScratchPadImage() { return KisScratchPad::copyScratchpadImageData(); } void Scratchpad::clear() { // need ability to set color KisScratchPad::fillDefault(); } void Scratchpad::setFillColor(QColor color) { KisScratchPad::setFillColor(color); } diff --git a/libs/libkis/Scratchpad.h b/libs/libkis/Scratchpad.h index 96c79e3fb2..6f35dffab0 100644 --- a/libs/libkis/Scratchpad.h +++ b/libs/libkis/Scratchpad.h @@ -1,74 +1,77 @@ /* * 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 { 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); /// take what is on scratchpad area and grab image QImage copyScratchPadImage(); }; #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..161f5fad04 100644 --- a/libs/ui/KisDocument.cpp +++ b/libs/ui/KisDocument.cpp @@ -1,2343 +1,2361 @@ /* 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(); } + +KisScratchPad *KisDocument::addScratchPad() +{ + + + KisMainWindow *window = KisPart::instance()->currentMainwindow(); + if(!window) return 0; + + KisScratchPad *newScratchPad = new KisScratchPad(window->activeView()); + + + newScratchPad->setupScratchPad(window->viewManager()->canvasResourceProvider(), Qt::white); + return newScratchPad; +} + + 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..13f3efd14e 100644 --- a/libs/ui/KisDocument.h +++ b/libs/ui/KisDocument.h @@ -1,707 +1,713 @@ /* 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); + /** + * @brief Creates a scratchpad and return the new object + */ + KisScratchPad *addScratchPad(); + 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..c419540f07 100644 --- a/plugins/extensions/pykrita/sip/krita/Document.sip +++ b/plugins/extensions/pykrita/sip/krita/Document.sip @@ -1,103 +1,104 @@ 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/; + Scratchpad * createScratchpad(); 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..883578010a 100644 --- a/plugins/extensions/pykrita/sip/krita/Scratchpad.sip +++ b/plugins/extensions/pykrita/sip/krita/Scratchpad.sip @@ -1,19 +1,20 @@ class Scratchpad : public QWidget /NoDefaultCtors/ { %TypeHeaderCode #include "Scratchpad.h" %End -public: Scratchpad(View* view , const QString & defaultColor, QWidget* parent /TransferThis/ = 0); +public: 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(); };