diff --git a/libs/image/lazybrush/kis_colorize_mask.cpp b/libs/image/lazybrush/kis_colorize_mask.cpp
index 2572e4ea1e..ffe20d7d1c 100644
--- a/libs/image/lazybrush/kis_colorize_mask.cpp
+++ b/libs/image/lazybrush/kis_colorize_mask.cpp
@@ -1,1166 +1,1166 @@
/*
* Copyright (c) 2016 Dmitry Kazakov
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include "kis_colorize_mask.h"
#include
#include
#include
#include "kis_pixel_selection.h"
#include "kis_icon_utils.h"
#include "kis_node_visitor.h"
#include "kis_processing_visitor.h"
#include "kis_painter.h"
#include "kis_fill_painter.h"
#include "kis_lazy_fill_tools.h"
#include "kis_cached_paint_device.h"
#include "kis_paint_device_debug_utils.h"
#include "kis_layer_properties_icons.h"
-#include "kis_signal_compressor.h"
+#include "kis_thread_safe_signal_compressor.h"
#include "kis_colorize_stroke_strategy.h"
#include "kis_multiway_cut.h"
#include "kis_image.h"
#include "kis_layer.h"
#include "kis_macro_based_undo_store.h"
#include "kis_post_execution_undo_adapter.h"
#include "kis_command_utils.h"
#include "kis_processing_applicator.h"
#include "krita_utils.h"
using namespace KisLazyFillTools;
struct KisColorizeMask::Private
{
Private(KisColorizeMask *_q)
: q(_q),
coloringProjection(new KisPaintDevice(KoColorSpaceRegistry::instance()->rgb8())),
fakePaintDevice(new KisPaintDevice(KoColorSpaceRegistry::instance()->rgb8())),
filteredSource(new KisPaintDevice(KoColorSpaceRegistry::instance()->rgb8())),
needAddCurrentKeyStroke(false),
showKeyStrokes(true),
showColoring(true),
needsUpdate(true),
originalSequenceNumber(-1),
- updateCompressor(1000, KisSignalCompressor::FIRST_ACTIVE_POSTPONE_NEXT, _q),
- dirtyParentUpdateCompressor(200, KisSignalCompressor::FIRST_ACTIVE_POSTPONE_NEXT, _q),
- prefilterRecalculationCompressor(1000, KisSignalCompressor::POSTPONE, _q),
+ updateCompressor(1000, KisSignalCompressor::FIRST_ACTIVE_POSTPONE_NEXT),
+ dirtyParentUpdateCompressor(200, KisSignalCompressor::FIRST_ACTIVE_POSTPONE_NEXT),
+ prefilterRecalculationCompressor(1000, KisSignalCompressor::POSTPONE),
updateIsRunning(false),
filteringOptions(false, 4.0, 15, 0.7),
limitToDeviceBounds(false)
{
}
Private(const Private &rhs, KisColorizeMask *_q)
: q(_q),
coloringProjection(new KisPaintDevice(*rhs.coloringProjection)),
fakePaintDevice(new KisPaintDevice(*rhs.fakePaintDevice)),
filteredSource(new KisPaintDevice(*rhs.filteredSource)),
filteredDeviceBounds(rhs.filteredDeviceBounds),
needAddCurrentKeyStroke(rhs.needAddCurrentKeyStroke),
showKeyStrokes(rhs.showKeyStrokes),
showColoring(rhs.showColoring),
needsUpdate(false),
originalSequenceNumber(-1),
- updateCompressor(1000, KisSignalCompressor::FIRST_ACTIVE_POSTPONE_NEXT, _q),
- dirtyParentUpdateCompressor(200, KisSignalCompressor::FIRST_ACTIVE_POSTPONE_NEXT, _q),
- prefilterRecalculationCompressor(1000, KisSignalCompressor::POSTPONE, _q),
+ updateCompressor(1000, KisSignalCompressor::FIRST_ACTIVE_POSTPONE_NEXT),
+ dirtyParentUpdateCompressor(200, KisSignalCompressor::FIRST_ACTIVE_POSTPONE_NEXT),
+ prefilterRecalculationCompressor(1000, KisSignalCompressor::POSTPONE),
offset(rhs.offset),
updateIsRunning(false),
filteringOptions(rhs.filteringOptions),
limitToDeviceBounds(rhs.limitToDeviceBounds)
{
Q_FOREACH (const KeyStroke &stroke, rhs.keyStrokes) {
keyStrokes << KeyStroke(KisPaintDeviceSP(new KisPaintDevice(*stroke.dev)), stroke.color, stroke.isTransparent);
}
}
KisColorizeMask *q = 0;
QList keyStrokes;
KisPaintDeviceSP coloringProjection;
KisPaintDeviceSP fakePaintDevice;
KisPaintDeviceSP filteredSource;
QRect filteredDeviceBounds;
KoColor currentColor;
KisPaintDeviceSP currentKeyStrokeDevice;
bool needAddCurrentKeyStroke;
bool showKeyStrokes;
bool showColoring;
KisCachedSelection cachedSelection;
KisCachedSelection cachedConversionSelection;
bool needsUpdate;
int originalSequenceNumber;
- KisSignalCompressor updateCompressor;
- KisSignalCompressor dirtyParentUpdateCompressor;
- KisSignalCompressor prefilterRecalculationCompressor;
+ KisThreadSafeSignalCompressor updateCompressor;
+ KisThreadSafeSignalCompressor dirtyParentUpdateCompressor;
+ KisThreadSafeSignalCompressor prefilterRecalculationCompressor;
QPoint offset;
bool updateIsRunning;
QStack extentBeforeUpdateStart;
FilteringOptions filteringOptions;
bool filteringDirty = true;
bool limitToDeviceBounds = false;
bool filteredSourceValid(KisPaintDeviceSP parentDevice) {
return !filteringDirty && originalSequenceNumber == parentDevice->sequenceNumber();
}
void setNeedsUpdateImpl(bool value, bool requestedByUser);
bool shouldShowFilteredSource() const;
bool shouldShowColoring() const;
};
KisColorizeMask::KisColorizeMask()
: m_d(new Private(this))
{
connect(&m_d->updateCompressor,
SIGNAL(timeout()),
SLOT(slotUpdateRegenerateFilling()));
connect(this, SIGNAL(sigUpdateOnDirtyParent()),
&m_d->dirtyParentUpdateCompressor, SLOT(start()));
connect(&m_d->dirtyParentUpdateCompressor,
SIGNAL(timeout()),
SLOT(slotUpdateOnDirtyParent()));
connect(&m_d->prefilterRecalculationCompressor,
SIGNAL(timeout()),
SLOT(slotRecalculatePrefilteredImage()));
m_d->updateCompressor.moveToThread(qApp->thread());
}
KisColorizeMask::~KisColorizeMask()
{
}
KisColorizeMask::KisColorizeMask(const KisColorizeMask& rhs)
: KisEffectMask(rhs),
m_d(new Private(*rhs.m_d, this))
{
connect(&m_d->updateCompressor,
SIGNAL(timeout()),
SLOT(slotUpdateRegenerateFilling()));
connect(this, SIGNAL(sigUpdateOnDirtyParent()),
&m_d->dirtyParentUpdateCompressor, SLOT(start()));
connect(&m_d->dirtyParentUpdateCompressor,
SIGNAL(timeout()),
SLOT(slotUpdateOnDirtyParent()));
m_d->updateCompressor.moveToThread(qApp->thread());
}
void KisColorizeMask::initializeCompositeOp()
{
KisLayerSP parentLayer(qobject_cast(parent().data()));
if (!parentLayer || !parentLayer->original()) return;
KisImageSP image = parentLayer->image();
if (!image) return;
const qreal samplePortion = 0.1;
const qreal alphaPortion =
KritaUtils::estimatePortionOfTransparentPixels(parentLayer->original(),
image->bounds(),
samplePortion);
setCompositeOpId(alphaPortion > 0.3 ? COMPOSITE_BEHIND : COMPOSITE_MULT);
}
const KoColorSpace* KisColorizeMask::colorSpace() const
{
return m_d->fakePaintDevice->colorSpace();
}
struct SetKeyStrokesColorSpaceCommand : public KUndo2Command {
SetKeyStrokesColorSpaceCommand(const KoColorSpace *dstCS,
KoColorConversionTransformation::Intent renderingIntent,
KoColorConversionTransformation::ConversionFlags conversionFlags,
QList *list,
KisColorizeMaskSP node)
: m_dstCS(dstCS),
m_renderingIntent(renderingIntent),
m_conversionFlags(conversionFlags),
m_list(list),
m_node(node) {}
void undo() override {
KIS_ASSERT_RECOVER_RETURN(m_list->size() == m_oldColors.size());
for (int i = 0; i < m_list->size(); i++) {
(*m_list)[i].color = m_oldColors[i];
}
m_node->setNeedsUpdate(true);
}
void redo() override {
if (m_oldColors.isEmpty()) {
Q_FOREACH(const KeyStroke &stroke, *m_list) {
m_oldColors << stroke.color;
m_newColors << stroke.color;
m_newColors.last().convertTo(m_dstCS, m_renderingIntent, m_conversionFlags);
}
}
KIS_ASSERT_RECOVER_RETURN(m_list->size() == m_newColors.size());
for (int i = 0; i < m_list->size(); i++) {
(*m_list)[i].color = m_newColors[i];
}
m_node->setNeedsUpdate(true);
}
private:
QVector m_oldColors;
QVector m_newColors;
const KoColorSpace *m_dstCS;
KoColorConversionTransformation::Intent m_renderingIntent;
KoColorConversionTransformation::ConversionFlags m_conversionFlags;
QList *m_list;
KisColorizeMaskSP m_node;
};
void KisColorizeMask::setProfile(const KoColorProfile *profile)
{
// WARNING: there is no undo information, used only while loading!
m_d->fakePaintDevice->setProfile(profile);
m_d->coloringProjection->setProfile(profile);
for (auto stroke : m_d->keyStrokes) {
stroke.color.setProfile(profile);
}
}
KUndo2Command* KisColorizeMask::setColorSpace(const KoColorSpace * dstColorSpace,
KoColorConversionTransformation::Intent renderingIntent,
KoColorConversionTransformation::ConversionFlags conversionFlags)
{
using namespace KisCommandUtils;
CompositeCommand *composite = new CompositeCommand();
composite->addCommand(m_d->fakePaintDevice->convertTo(dstColorSpace, renderingIntent, conversionFlags));
composite->addCommand(m_d->coloringProjection->convertTo(dstColorSpace, renderingIntent, conversionFlags));
KUndo2Command *strokesConversionCommand =
new SetKeyStrokesColorSpaceCommand(
dstColorSpace, renderingIntent, conversionFlags,
&m_d->keyStrokes, KisColorizeMaskSP(this));
strokesConversionCommand->redo();
composite->addCommand(new SkipFirstRedoWrapper(strokesConversionCommand));
return composite;
}
bool KisColorizeMask::needsUpdate() const
{
return m_d->needsUpdate;
}
void KisColorizeMask::setNeedsUpdate(bool value)
{
m_d->setNeedsUpdateImpl(value, true);
}
void KisColorizeMask::Private::setNeedsUpdateImpl(bool value, bool requestedByUser)
{
if (value != needsUpdate) {
needsUpdate = value;
q->baseNodeChangedCallback();
if (!value && requestedByUser) {
updateCompressor.start();
}
}
}
void KisColorizeMask::slotUpdateRegenerateFilling(bool prefilterOnly)
{
KisPaintDeviceSP src = parent()->original();
KIS_ASSERT_RECOVER_RETURN(src);
const bool filteredSourceValid = m_d->filteredSourceValid(src);
m_d->originalSequenceNumber = src->sequenceNumber();
m_d->filteringDirty = false;
if (!prefilterOnly) {
m_d->coloringProjection->clear();
}
KisLayerSP parentLayer(qobject_cast(parent().data()));
if (!parentLayer) return;
KisImageSP image = parentLayer->image();
if (image) {
m_d->updateIsRunning = true;
QRect fillBounds;
if (m_d->limitToDeviceBounds) {
fillBounds |= src->exactBounds();
Q_FOREACH (const KeyStroke &stroke, m_d->keyStrokes) {
fillBounds |= stroke.dev->exactBounds();
}
fillBounds &= image->bounds();
} else {
fillBounds = image->bounds();
}
m_d->filteredDeviceBounds = fillBounds;
KisColorizeStrokeStrategy *strategy =
new KisColorizeStrokeStrategy(src,
m_d->coloringProjection,
m_d->filteredSource,
filteredSourceValid,
fillBounds,
this,
prefilterOnly);
strategy->setFilteringOptions(m_d->filteringOptions);
Q_FOREACH (const KeyStroke &stroke, m_d->keyStrokes) {
const KoColor color =
!stroke.isTransparent ?
stroke.color :
KoColor(Qt::transparent, stroke.color.colorSpace());
strategy->addKeyStroke(stroke.dev, color);
}
m_d->extentBeforeUpdateStart.push(extent());
connect(strategy, SIGNAL(sigFinished(bool)), SLOT(slotRegenerationFinished(bool)));
connect(strategy, SIGNAL(sigCancelled()), SLOT(slotRegenerationCancelled()));
KisStrokeId id = image->startStroke(strategy);
image->endStroke(id);
}
}
void KisColorizeMask::slotUpdateOnDirtyParent()
{
KisPaintDeviceSP src = parent()->original();
KIS_ASSERT_RECOVER_RETURN(src);
if (!m_d->filteredSourceValid(src)) {
const QRect &oldExtent = extent();
m_d->setNeedsUpdateImpl(true, false);
m_d->filteringDirty = true;
setDirty(oldExtent | extent());
}
}
void KisColorizeMask::slotRecalculatePrefilteredImage()
{
slotUpdateRegenerateFilling(true);
}
void KisColorizeMask::slotRegenerationFinished(bool prefilterOnly)
{
m_d->updateIsRunning = false;
if (!prefilterOnly) {
m_d->setNeedsUpdateImpl(false, false);
}
QRect oldExtent;
if (!m_d->extentBeforeUpdateStart.isEmpty()) {
oldExtent = m_d->extentBeforeUpdateStart.pop();
} else {
KIS_SAFE_ASSERT_RECOVER_NOOP(!m_d->extentBeforeUpdateStart.isEmpty()); // always fail!
}
setDirty(oldExtent | extent());
}
void KisColorizeMask::slotRegenerationCancelled()
{
slotRegenerationFinished(true);
}
KisBaseNode::PropertyList KisColorizeMask::sectionModelProperties() const
{
KisBaseNode::PropertyList l = KisMask::sectionModelProperties();
l << KisLayerPropertiesIcons::getProperty(KisLayerPropertiesIcons::colorizeNeedsUpdate, needsUpdate());
l << KisLayerPropertiesIcons::getProperty(KisLayerPropertiesIcons::colorizeEditKeyStrokes, showKeyStrokes());
l << KisLayerPropertiesIcons::getProperty(KisLayerPropertiesIcons::colorizeShowColoring, showColoring());
return l;
}
void KisColorizeMask::setSectionModelProperties(const KisBaseNode::PropertyList &properties)
{
KisMask::setSectionModelProperties(properties);
Q_FOREACH (const KisBaseNode::Property &property, properties) {
if (property.id == KisLayerPropertiesIcons::colorizeNeedsUpdate.id()) {
if (m_d->needsUpdate && m_d->needsUpdate != property.state.toBool()) {
setNeedsUpdate(property.state.toBool());
}
}
if (property.id == KisLayerPropertiesIcons::colorizeEditKeyStrokes.id()) {
if (m_d->showKeyStrokes != property.state.toBool()) {
setShowKeyStrokes(property.state.toBool());
}
}
if (property.id == KisLayerPropertiesIcons::colorizeShowColoring.id()) {
if (m_d->showColoring != property.state.toBool()) {
setShowColoring(property.state.toBool());
}
}
}
}
KisPaintDeviceSP KisColorizeMask::paintDevice() const
{
return m_d->showKeyStrokes && !m_d->updateIsRunning ? m_d->fakePaintDevice : KisPaintDeviceSP();
}
KisPaintDeviceSP KisColorizeMask::coloringProjection() const
{
return m_d->coloringProjection;
}
KisPaintDeviceSP KisColorizeMask::colorPickSourceDevice() const
{
return
m_d->shouldShowColoring() && !m_d->coloringProjection->extent().isEmpty() ?
m_d->coloringProjection : projection();
}
QIcon KisColorizeMask::icon() const
{
return KisIconUtils::loadIcon("colorizeMask");
}
bool KisColorizeMask::accept(KisNodeVisitor &v)
{
return v.visit(this);
}
void KisColorizeMask::accept(KisProcessingVisitor &visitor, KisUndoAdapter *undoAdapter)
{
return visitor.visit(this, undoAdapter);
}
bool KisColorizeMask::Private::shouldShowFilteredSource() const
{
return !updateIsRunning &&
showKeyStrokes &&
!filteringDirty &&
filteredSource &&
!filteredSource->extent().isEmpty();
}
bool KisColorizeMask::Private::shouldShowColoring() const
{
return !updateIsRunning &&
showColoring &&
coloringProjection;
}
QRect KisColorizeMask::decorateRect(KisPaintDeviceSP &src,
KisPaintDeviceSP &dst,
const QRect &rect,
PositionToFilthy maskPos) const
{
Q_UNUSED(maskPos);
if (maskPos == N_ABOVE_FILTHY) {
// the source layer has changed, we should update the filtered cache!
if (!m_d->filteringDirty) {
emit sigUpdateOnDirtyParent();
}
}
KIS_ASSERT(dst != src);
// Draw the filling and the original layer
{
KisPainter gc(dst);
if (m_d->shouldShowFilteredSource()) {
const QRect drawRect = m_d->limitToDeviceBounds ? rect & m_d->filteredDeviceBounds : rect;
gc.setOpacity(128);
gc.bitBlt(drawRect.topLeft(), m_d->filteredSource, drawRect);
} else {
gc.setOpacity(255);
gc.bitBlt(rect.topLeft(), src, rect);
}
if (m_d->shouldShowColoring()) {
gc.setOpacity(opacity());
gc.setCompositeOp(compositeOpId());
gc.bitBlt(rect.topLeft(), m_d->coloringProjection, rect);
}
}
// Draw the key strokes
if (m_d->showKeyStrokes) {
KisIndirectPaintingSupport::ReadLocker locker(this);
KisSelectionSP selection = m_d->cachedSelection.getSelection();
KisSelectionSP conversionSelection = m_d->cachedConversionSelection.getSelection();
KisPixelSelectionSP tempSelection = conversionSelection->pixelSelection();
KisPaintDeviceSP temporaryTarget = this->temporaryTarget();
const bool isTemporaryTargetErasing = temporaryCompositeOp() == COMPOSITE_ERASE;
const QRect temporaryExtent = temporaryTarget ? temporaryTarget->extent() : QRect();
KisFillPainter gc(dst);
QList extendedStrokes = m_d->keyStrokes;
if (m_d->currentKeyStrokeDevice &&
m_d->needAddCurrentKeyStroke &&
!isTemporaryTargetErasing) {
extendedStrokes << KeyStroke(m_d->currentKeyStrokeDevice, m_d->currentColor);
}
Q_FOREACH (const KeyStroke &stroke, extendedStrokes) {
selection->pixelSelection()->makeCloneFromRough(stroke.dev, rect);
gc.setSelection(selection);
if (stroke.color == m_d->currentColor ||
(isTemporaryTargetErasing &&
temporaryExtent.intersects(selection->pixelSelection()->selectedRect()))) {
if (temporaryTarget) {
tempSelection->copyAlphaFrom(temporaryTarget, rect);
KisPainter selectionPainter(selection->pixelSelection());
setupTemporaryPainter(&selectionPainter);
selectionPainter.bitBlt(rect.topLeft(), tempSelection, rect);
}
}
gc.fillSelection(rect, stroke.color);
}
m_d->cachedSelection.putSelection(selection);
m_d->cachedSelection.putSelection(conversionSelection);
}
return rect;
}
struct DeviceExtentPolicy
{
inline QRect operator() (const KisPaintDevice *dev) {
return dev->extent();
}
};
struct DeviceExactBoundsPolicy
{
inline QRect operator() (const KisPaintDevice *dev) {
return dev->exactBounds();
}
};
template
QRect KisColorizeMask::calculateMaskBounds(DeviceMetricPolicy boundsPolicy) const
{
QRect rc;
if (m_d->shouldShowFilteredSource()) {
rc |= boundsPolicy(m_d->filteredSource);
}
if (m_d->shouldShowColoring()) {
rc |= boundsPolicy(m_d->coloringProjection);
}
if (m_d->showKeyStrokes) {
Q_FOREACH (const KeyStroke &stroke, m_d->keyStrokes) {
rc |= boundsPolicy(stroke.dev);
}
KisIndirectPaintingSupport::ReadLocker locker(this);
KisPaintDeviceSP temporaryTarget = this->temporaryTarget();
if (temporaryTarget) {
rc |= boundsPolicy(temporaryTarget);
}
}
return rc;
}
QRect KisColorizeMask::extent() const
{
return calculateMaskBounds(DeviceExtentPolicy());
}
QRect KisColorizeMask::exactBounds() const
{
return calculateMaskBounds(DeviceExactBoundsPolicy());
}
QRect KisColorizeMask::nonDependentExtent() const
{
return extent();
}
KisImageSP KisColorizeMask::fetchImage() const
{
KisLayerSP parentLayer(qobject_cast(parent().data()));
if (!parentLayer) return KisImageSP();
return parentLayer->image();
}
void KisColorizeMask::setImage(KisImageWSP image)
{
KisDefaultBoundsSP bounds(new KisDefaultBounds(image));
auto it = m_d->keyStrokes.begin();
for(; it != m_d->keyStrokes.end(); ++it) {
it->dev->setDefaultBounds(bounds);
}
m_d->coloringProjection->setDefaultBounds(bounds);
m_d->fakePaintDevice->setDefaultBounds(bounds);
m_d->filteredSource->setDefaultBounds(bounds);
}
void KisColorizeMask::setCurrentColor(const KoColor &_color)
{
KoColor color = _color;
color.convertTo(colorSpace());
WriteLocker locker(this);
m_d->setNeedsUpdateImpl(true, false);
QList::const_iterator it =
std::find_if(m_d->keyStrokes.constBegin(),
m_d->keyStrokes.constEnd(),
[color] (const KeyStroke &s) {
return s.color == color;
});
KisPaintDeviceSP activeDevice;
bool newKeyStroke = false;
if (it == m_d->keyStrokes.constEnd()) {
activeDevice = new KisPaintDevice(KoColorSpaceRegistry::instance()->alpha8());
activeDevice->setParentNode(this);
activeDevice->setDefaultBounds(KisDefaultBoundsBaseSP(new KisDefaultBounds(fetchImage())));
newKeyStroke = true;
} else {
activeDevice = it->dev;
}
m_d->currentColor = color;
m_d->currentKeyStrokeDevice = activeDevice;
m_d->needAddCurrentKeyStroke = newKeyStroke;
}
struct KeyStrokeAddRemoveCommand : public KisCommandUtils::FlipFlopCommand {
KeyStrokeAddRemoveCommand(bool add, int index, KeyStroke stroke, QList *list, KisColorizeMaskSP node)
: FlipFlopCommand(!add),
m_index(index), m_stroke(stroke),
m_list(list), m_node(node) {}
void init() override {
m_list->insert(m_index, m_stroke);
m_node->setNeedsUpdate(true);
emit m_node->sigKeyStrokesListChanged();
}
void end() override {
KIS_ASSERT_RECOVER_RETURN((*m_list)[m_index] == m_stroke);
m_list->removeAt(m_index);
m_node->setNeedsUpdate(true);
emit m_node->sigKeyStrokesListChanged();
}
private:
int m_index;
KeyStroke m_stroke;
QList *m_list;
KisColorizeMaskSP m_node;
};
void KisColorizeMask::mergeToLayer(KisNodeSP layer, KisPostExecutionUndoAdapter *undoAdapter, const KUndo2MagicString &transactionText,int timedID)
{
Q_UNUSED(layer);
WriteLocker locker(this);
KisPaintDeviceSP temporaryTarget = this->temporaryTarget();
const bool isTemporaryTargetErasing = temporaryCompositeOp() == COMPOSITE_ERASE;
const QRect temporaryExtent = temporaryTarget ? temporaryTarget->extent() : QRect();
KisSavedMacroCommand *macro = undoAdapter->createMacro(transactionText);
KisMacroBasedUndoStore store(macro);
KisPostExecutionUndoAdapter fakeUndoAdapter(&store, undoAdapter->strokesFacade());
/**
* Add a new key stroke plane
*/
if (m_d->needAddCurrentKeyStroke && !isTemporaryTargetErasing) {
KeyStroke key(m_d->currentKeyStrokeDevice, m_d->currentColor);
KUndo2Command *cmd =
new KeyStrokeAddRemoveCommand(
true, m_d->keyStrokes.size(), key, &m_d->keyStrokes, KisColorizeMaskSP(this));
cmd->redo();
fakeUndoAdapter.addCommand(toQShared(cmd));
}
/**
* When erasing, the brush affects all the key strokes, not only
* the current one.
*/
if (!isTemporaryTargetErasing) {
mergeToLayerImpl(m_d->currentKeyStrokeDevice, &fakeUndoAdapter, transactionText, timedID, false);
} else {
Q_FOREACH (const KeyStroke &stroke, m_d->keyStrokes) {
if (temporaryExtent.intersects(stroke.dev->extent())) {
mergeToLayerImpl(stroke.dev, &fakeUndoAdapter, transactionText, timedID, false);
}
}
}
mergeToLayerImpl(m_d->fakePaintDevice, &fakeUndoAdapter, transactionText, timedID, false);
m_d->currentKeyStrokeDevice = 0;
m_d->currentColor = KoColor();
releaseResources();
/**
* Try removing the key strokes that has been completely erased
*/
if (isTemporaryTargetErasing) {
for (int index = 0; index < m_d->keyStrokes.size(); /*noop*/) {
const KeyStroke &stroke = m_d->keyStrokes[index];
if (stroke.dev->exactBounds().isEmpty()) {
KUndo2Command *cmd =
new KeyStrokeAddRemoveCommand(
false, index, stroke, &m_d->keyStrokes, KisColorizeMaskSP(this));
cmd->redo();
fakeUndoAdapter.addCommand(toQShared(cmd));
} else {
index++;
}
}
}
undoAdapter->addMacro(macro);
}
void KisColorizeMask::writeMergeData(KisPainter *painter, KisPaintDeviceSP src)
{
const KoColorSpace *alpha8 = KoColorSpaceRegistry::instance()->alpha8();
const bool nonAlphaDst = !(*painter->device()->colorSpace() == *alpha8);
if (nonAlphaDst) {
Q_FOREACH (const QRect &rc, src->region().rects()) {
painter->bitBlt(rc.topLeft(), src, rc);
}
} else {
KisSelectionSP conversionSelection = m_d->cachedConversionSelection.getSelection();
KisPixelSelectionSP tempSelection = conversionSelection->pixelSelection();
Q_FOREACH (const QRect &rc, src->region().rects()) {
tempSelection->copyAlphaFrom(src, rc);
painter->bitBlt(rc.topLeft(), tempSelection, rc);
}
m_d->cachedSelection.putSelection(conversionSelection);
}
}
bool KisColorizeMask::supportsNonIndirectPainting() const
{
return false;
}
bool KisColorizeMask::showColoring() const
{
return m_d->showColoring;
}
void KisColorizeMask::setShowColoring(bool value)
{
QRect savedExtent;
if (m_d->showColoring && !value) {
savedExtent = extent();
}
m_d->showColoring = value;
baseNodeChangedCallback();
if (!savedExtent.isEmpty()) {
setDirty(savedExtent);
}
}
bool KisColorizeMask::showKeyStrokes() const
{
return m_d->showKeyStrokes;
}
void KisColorizeMask::setShowKeyStrokes(bool value)
{
QRect savedExtent;
if (m_d->showKeyStrokes && !value) {
savedExtent = extent();
}
m_d->showKeyStrokes = value;
baseNodeChangedCallback();
if (!savedExtent.isEmpty()) {
setDirty(savedExtent);
}
regeneratePrefilteredDeviceIfNeeded();
}
KisColorizeMask::KeyStrokeColors KisColorizeMask::keyStrokesColors() const
{
KeyStrokeColors colors;
// TODO: thread safety!
for (int i = 0; i < m_d->keyStrokes.size(); i++) {
colors.colors << m_d->keyStrokes[i].color;
if (m_d->keyStrokes[i].isTransparent) {
colors.transparentIndex = i;
}
}
return colors;
}
struct SetKeyStrokeColorsCommand : public KUndo2Command {
SetKeyStrokeColorsCommand(const QList newList, QList *list, KisColorizeMaskSP node)
: m_newList(newList),
m_oldList(*list),
m_list(list),
m_node(node) {}
void redo() override {
*m_list = m_newList;
m_node->setNeedsUpdate(true);
emit m_node->sigKeyStrokesListChanged();
m_node->setDirty();
}
void undo() override {
*m_list = m_oldList;
m_node->setNeedsUpdate(true);
emit m_node->sigKeyStrokesListChanged();
m_node->setDirty();
}
private:
QList m_newList;
QList m_oldList;
QList *m_list;
KisColorizeMaskSP m_node;
};
void KisColorizeMask::setKeyStrokesColors(KeyStrokeColors colors)
{
KIS_ASSERT_RECOVER_RETURN(colors.colors.size() == m_d->keyStrokes.size());
QList newList = m_d->keyStrokes;
for (int i = 0; i < newList.size(); i++) {
newList[i].color = colors.colors[i];
newList[i].color.convertTo(colorSpace());
newList[i].isTransparent = colors.transparentIndex == i;
}
KisProcessingApplicator applicator(fetchImage(), KisNodeSP(this),
KisProcessingApplicator::NONE,
KisImageSignalVector(),
kundo2_i18n("Change Key Stroke Color"));
applicator.applyCommand(
new SetKeyStrokeColorsCommand(
newList, &m_d->keyStrokes, KisColorizeMaskSP(this)));
applicator.end();
}
void KisColorizeMask::removeKeyStroke(const KoColor &_color)
{
KoColor color = _color;
color.convertTo(colorSpace());
QList::iterator it =
std::find_if(m_d->keyStrokes.begin(),
m_d->keyStrokes.end(),
[color] (const KeyStroke &s) {
return s.color == color;
});
KIS_SAFE_ASSERT_RECOVER_RETURN(it != m_d->keyStrokes.end());
const int index = it - m_d->keyStrokes.begin();
KisProcessingApplicator applicator(KisImageWSP(fetchImage()), KisNodeSP(this),
KisProcessingApplicator::NONE,
KisImageSignalVector(),
kundo2_i18n("Remove Key Stroke"));
applicator.applyCommand(
new KeyStrokeAddRemoveCommand(
false, index, *it, &m_d->keyStrokes, KisColorizeMaskSP(this)));
applicator.end();
}
QVector KisColorizeMask::allPaintDevices() const
{
QVector devices;
Q_FOREACH (const KeyStroke &stroke, m_d->keyStrokes) {
devices << stroke.dev;
}
devices << m_d->coloringProjection;
devices << m_d->fakePaintDevice;
return devices;
}
void KisColorizeMask::resetCache()
{
m_d->filteredSource->clear();
m_d->originalSequenceNumber = -1;
m_d->filteringDirty = true;
rerenderFakePaintDevice();
slotUpdateRegenerateFilling(true);
}
void KisColorizeMask::setUseEdgeDetection(bool value)
{
m_d->filteringOptions.useEdgeDetection = value;
m_d->filteringDirty = true;
setNeedsUpdate(true);
}
bool KisColorizeMask::useEdgeDetection() const
{
return m_d->filteringOptions.useEdgeDetection;
}
void KisColorizeMask::setEdgeDetectionSize(qreal value)
{
m_d->filteringOptions.edgeDetectionSize = value;
m_d->filteringDirty = true;
setNeedsUpdate(true);
}
qreal KisColorizeMask::edgeDetectionSize() const
{
return m_d->filteringOptions.edgeDetectionSize;
}
void KisColorizeMask::setFuzzyRadius(qreal value)
{
m_d->filteringOptions.fuzzyRadius = value;
m_d->filteringDirty = true;
setNeedsUpdate(true);
}
qreal KisColorizeMask::fuzzyRadius() const
{
return m_d->filteringOptions.fuzzyRadius;
}
void KisColorizeMask::setCleanUpAmount(qreal value)
{
m_d->filteringOptions.cleanUpAmount = value;
setNeedsUpdate(true);
}
qreal KisColorizeMask::cleanUpAmount() const
{
return m_d->filteringOptions.cleanUpAmount;
}
void KisColorizeMask::setLimitToDeviceBounds(bool value)
{
m_d->limitToDeviceBounds = value;
m_d->filteringDirty = true;
setNeedsUpdate(true);
}
bool KisColorizeMask::limitToDeviceBounds() const
{
return m_d->limitToDeviceBounds;
}
void KisColorizeMask::rerenderFakePaintDevice()
{
m_d->fakePaintDevice->clear();
KisFillPainter gc(m_d->fakePaintDevice);
KisSelectionSP selection = m_d->cachedSelection.getSelection();
Q_FOREACH (const KeyStroke &stroke, m_d->keyStrokes) {
const QRect rect = stroke.dev->extent();
selection->pixelSelection()->makeCloneFromRough(stroke.dev, rect);
gc.setSelection(selection);
gc.fillSelection(rect, stroke.color);
}
m_d->cachedSelection.putSelection(selection);
}
void KisColorizeMask::testingAddKeyStroke(KisPaintDeviceSP dev, const KoColor &color, bool isTransparent)
{
m_d->keyStrokes << KeyStroke(dev, color, isTransparent);
}
void KisColorizeMask::testingRegenerateMask()
{
slotUpdateRegenerateFilling();
}
KisPaintDeviceSP KisColorizeMask::testingFilteredSource() const
{
return m_d->filteredSource;
}
QList KisColorizeMask::fetchKeyStrokesDirect() const
{
return m_d->keyStrokes;
}
void KisColorizeMask::setKeyStrokesDirect(const QList &strokes)
{
m_d->keyStrokes = strokes;
for (auto it = m_d->keyStrokes.begin(); it != m_d->keyStrokes.end(); ++it) {
it->dev->setParentNode(this);
}
KisImageSP image = fetchImage();
KIS_SAFE_ASSERT_RECOVER_RETURN(image);
setImage(image);
}
qint32 KisColorizeMask::x() const
{
return m_d->offset.x();
}
qint32 KisColorizeMask::y() const
{
return m_d->offset.y();
}
void KisColorizeMask::setX(qint32 x)
{
const QPoint oldOffset = m_d->offset;
m_d->offset.rx() = x;
moveAllInternalDevices(m_d->offset - oldOffset);
}
void KisColorizeMask::setY(qint32 y)
{
const QPoint oldOffset = m_d->offset;
m_d->offset.ry() = y;
moveAllInternalDevices(m_d->offset - oldOffset);
}
KisPaintDeviceList KisColorizeMask::getLodCapableDevices() const
{
KisPaintDeviceList list;
auto it = m_d->keyStrokes.begin();
for(; it != m_d->keyStrokes.end(); ++it) {
list << it->dev;
}
list << m_d->coloringProjection;
list << m_d->fakePaintDevice;
list << m_d->filteredSource;
return list;
}
void KisColorizeMask::regeneratePrefilteredDeviceIfNeeded()
{
if (!parent()) return;
KisPaintDeviceSP src = parent()->original();
KIS_ASSERT_RECOVER_RETURN(src);
if (!m_d->filteredSourceValid(src)) {
// update the prefiltered source if needed
slotUpdateRegenerateFilling(true);
}
}
void KisColorizeMask::moveAllInternalDevices(const QPoint &diff)
{
QVector devices = allPaintDevices();
Q_FOREACH (KisPaintDeviceSP dev, devices) {
dev->moveTo(dev->offset() + diff);
}
}
diff --git a/libs/ui/CMakeLists.txt b/libs/ui/CMakeLists.txt
index b8f3777096..4e4daeccd8 100644
--- a/libs/ui/CMakeLists.txt
+++ b/libs/ui/CMakeLists.txt
@@ -1,594 +1,595 @@
include_directories(
${CMAKE_CURRENT_SOURCE_DIR}/qtlockedfile
${EXIV2_INCLUDE_DIR}
)
include_directories(SYSTEM
${EIGEN3_INCLUDE_DIR}
${OCIO_INCLUDE_DIR}
)
add_subdirectory( tests )
if (APPLE)
find_library(FOUNDATION_LIBRARY Foundation)
find_library(APPKIT_LIBRARY AppKit)
endif ()
set(kritaui_LIB_SRCS
canvas/kis_canvas_widget_base.cpp
canvas/kis_canvas2.cpp
canvas/kis_canvas_updates_compressor.cpp
canvas/kis_canvas_controller.cpp
canvas/kis_paintop_transformation_connector.cpp
canvas/kis_display_color_converter.cpp
canvas/kis_display_filter.cpp
canvas/kis_exposure_gamma_correction_interface.cpp
canvas/kis_tool_proxy.cpp
canvas/kis_canvas_decoration.cc
canvas/kis_coordinates_converter.cpp
canvas/kis_grid_manager.cpp
canvas/kis_grid_decoration.cpp
canvas/kis_grid_config.cpp
canvas/kis_prescaled_projection.cpp
canvas/kis_qpainter_canvas.cpp
canvas/kis_projection_backend.cpp
canvas/kis_update_info.cpp
canvas/kis_image_patch.cpp
canvas/kis_image_pyramid.cpp
canvas/kis_infinity_manager.cpp
canvas/kis_change_guides_command.cpp
canvas/kis_guides_decoration.cpp
canvas/kis_guides_manager.cpp
canvas/kis_guides_config.cpp
canvas/kis_snap_config.cpp
canvas/kis_snap_line_strategy.cpp
canvas/KisSnapPointStrategy.cpp
dialogs/kis_about_application.cpp
dialogs/kis_dlg_adj_layer_props.cc
dialogs/kis_dlg_adjustment_layer.cc
dialogs/kis_dlg_filter.cpp
dialogs/kis_dlg_generator_layer.cpp
dialogs/kis_dlg_file_layer.cpp
dialogs/kis_dlg_filter.cpp
dialogs/kis_dlg_stroke_selection_properties.cpp
dialogs/kis_dlg_image_properties.cc
dialogs/kis_dlg_layer_properties.cc
dialogs/kis_dlg_preferences.cc
dialogs/slider_and_spin_box_sync.cpp
dialogs/kis_dlg_blacklist_cleanup.cpp
dialogs/kis_dlg_layer_style.cpp
dialogs/kis_dlg_png_import.cpp
dialogs/kis_dlg_import_image_sequence.cpp
dialogs/kis_delayed_save_dialog.cpp
dialogs/kis_dlg_internal_color_selector.cpp
dialogs/KisSessionManagerDialog.cpp
dialogs/KisNewWindowLayoutDialog.cpp
flake/kis_node_dummies_graph.cpp
flake/kis_dummies_facade_base.cpp
flake/kis_dummies_facade.cpp
flake/kis_node_shapes_graph.cpp
flake/kis_node_shape.cpp
flake/kis_shape_controller.cpp
flake/kis_shape_layer.cc
flake/kis_shape_layer_canvas.cpp
flake/kis_shape_selection.cpp
flake/kis_shape_selection_canvas.cpp
flake/kis_shape_selection_model.cpp
flake/kis_take_all_shapes_command.cpp
brushhud/kis_uniform_paintop_property_widget.cpp
brushhud/kis_brush_hud.cpp
brushhud/kis_round_hud_button.cpp
brushhud/kis_dlg_brush_hud_config.cpp
brushhud/kis_brush_hud_properties_list.cpp
brushhud/kis_brush_hud_properties_config.cpp
kis_aspect_ratio_locker.cpp
kis_autogradient.cc
kis_bookmarked_configurations_editor.cc
kis_bookmarked_configurations_model.cc
kis_bookmarked_filter_configurations_model.cc
kis_base_option.cpp
kis_canvas_resource_provider.cpp
kis_derived_resources.cpp
kis_categories_mapper.cpp
kis_categorized_list_model.cpp
kis_categorized_item_delegate.cpp
kis_clipboard.cc
kis_config.cc
kis_config_notifier.cpp
kis_control_frame.cpp
kis_composite_ops_model.cc
kis_paint_ops_model.cpp
kis_cursor.cc
kis_cursor_cache.cpp
kis_custom_pattern.cc
kis_file_layer.cpp
kis_change_file_layer_command.h
kis_safe_document_loader.cpp
kis_splash_screen.cpp
kis_filter_manager.cc
kis_filters_model.cc
kis_histogram_view.cc
KisImageBarrierLockerWithFeedback.cpp
kis_image_manager.cc
kis_image_view_converter.cpp
kis_import_catcher.cc
kis_layer_manager.cc
kis_mask_manager.cc
kis_mimedata.cpp
kis_node_commands_adapter.cpp
kis_node_manager.cpp
kis_node_juggler_compressed.cpp
kis_node_selection_adapter.cpp
kis_node_insertion_adapter.cpp
kis_node_model.cpp
kis_node_filter_proxy_model.cpp
kis_model_index_converter_base.cpp
kis_model_index_converter.cpp
kis_model_index_converter_show_all.cpp
kis_painting_assistant.cc
kis_painting_assistants_decoration.cpp
KisDecorationsManager.cpp
kis_paintop_box.cc
kis_paintop_option.cpp
kis_paintop_options_model.cpp
kis_paintop_settings_widget.cpp
kis_popup_palette.cpp
kis_png_converter.cpp
kis_preference_set_registry.cpp
KisResourceServerProvider.cpp
KisResourceBundleServerProvider.cpp
KisSelectedShapesProxy.cpp
kis_selection_decoration.cc
kis_selection_manager.cc
kis_statusbar.cc
kis_zoom_manager.cc
kis_favorite_resource_manager.cpp
kis_workspace_resource.cpp
kis_action.cpp
kis_action_manager.cpp
KisActionPlugin.cpp
kis_canvas_controls_manager.cpp
kis_tooltip_manager.cpp
kis_multinode_property.cpp
kis_stopgradient_editor.cpp
kisexiv2/kis_exif_io.cpp
kisexiv2/kis_exiv2.cpp
kisexiv2/kis_iptc_io.cpp
kisexiv2/kis_xmp_io.cpp
opengl/kis_opengl.cpp
opengl/kis_opengl_canvas2.cpp
opengl/kis_opengl_canvas_debugger.cpp
opengl/kis_opengl_image_textures.cpp
opengl/kis_texture_tile.cpp
opengl/kis_opengl_shader_loader.cpp
opengl/kis_texture_tile_info_pool.cpp
opengl/KisOpenGLUpdateInfoBuilder.cpp
kis_fps_decoration.cpp
recorder/kis_node_query_path_editor.cc
recorder/kis_recorded_action_creator.cc
recorder/kis_recorded_action_creator_factory.cc
recorder/kis_recorded_action_creator_factory_registry.cc
recorder/kis_recorded_action_editor_factory.cc
recorder/kis_recorded_action_editor_factory_registry.cc
recorder/kis_recorded_filter_action_editor.cc
recorder/kis_recorded_filter_action_creator.cpp
recorder/kis_recorded_paint_action_editor.cc
tool/kis_selection_tool_helper.cpp
tool/kis_selection_tool_config_widget_helper.cpp
tool/kis_rectangle_constraint_widget.cpp
tool/kis_shape_tool_helper.cpp
tool/kis_tool.cc
tool/kis_delegated_tool_policies.cpp
tool/kis_tool_freehand.cc
tool/kis_speed_smoother.cpp
tool/kis_painting_information_builder.cpp
tool/kis_stabilized_events_sampler.cpp
tool/kis_tool_freehand_helper.cpp
tool/kis_tool_multihand_helper.cpp
tool/kis_figure_painting_tool_helper.cpp
tool/kis_recording_adapter.cpp
tool/kis_tool_paint.cc
tool/kis_tool_shape.cc
tool/kis_tool_ellipse_base.cpp
tool/kis_tool_rectangle_base.cpp
tool/kis_tool_polyline_base.cpp
tool/kis_tool_utils.cpp
tool/kis_resources_snapshot.cpp
tool/kis_smoothing_options.cpp
tool/KisStabilizerDelayedPaintHelper.cpp
tool/KisStrokeSpeedMonitor.cpp
tool/strokes/freehand_stroke.cpp
tool/strokes/KisStrokeEfficiencyMeasurer.cpp
tool/strokes/kis_painter_based_stroke_strategy.cpp
tool/strokes/kis_filter_stroke_strategy.cpp
tool/strokes/kis_color_picker_stroke_strategy.cpp
tool/strokes/KisFreehandStrokeInfo.cpp
tool/strokes/KisMaskedFreehandStrokePainter.cpp
tool/strokes/KisMaskingBrushRenderer.cpp
tool/strokes/KisMaskingBrushCompositeOpFactory.cpp
widgets/kis_cmb_composite.cc
widgets/kis_cmb_contour.cpp
widgets/kis_cmb_gradient.cpp
widgets/kis_paintop_list_widget.cpp
widgets/kis_cmb_idlist.cc
widgets/kis_color_space_selector.cc
widgets/kis_advanced_color_space_selector.cc
widgets/kis_cie_tongue_widget.cpp
widgets/kis_tone_curve_widget.cpp
widgets/kis_curve_widget.cpp
widgets/kis_custom_image_widget.cc
widgets/kis_image_from_clipboard_widget.cpp
widgets/kis_double_widget.cc
widgets/kis_filter_selector_widget.cc
widgets/kis_gradient_chooser.cc
widgets/kis_iconwidget.cc
widgets/kis_mask_widgets.cpp
widgets/kis_meta_data_merge_strategy_chooser_widget.cc
widgets/kis_multi_bool_filter_widget.cc
widgets/kis_multi_double_filter_widget.cc
widgets/kis_multi_integer_filter_widget.cc
widgets/kis_multipliers_double_slider_spinbox.cpp
widgets/kis_paintop_presets_popup.cpp
widgets/kis_tool_options_popup.cpp
widgets/kis_paintop_presets_chooser_popup.cpp
widgets/kis_paintop_presets_save.cpp
widgets/kis_paintop_preset_icon_library.cpp
widgets/kis_pattern_chooser.cc
widgets/kis_popup_button.cc
widgets/kis_preset_chooser.cpp
widgets/kis_progress_widget.cpp
widgets/kis_selection_options.cc
widgets/kis_scratch_pad.cpp
widgets/kis_scratch_pad_event_filter.cpp
widgets/kis_preset_selector_strip.cpp
widgets/kis_slider_spin_box.cpp
widgets/KisSelectionPropertySlider.cpp
widgets/kis_size_group.cpp
widgets/kis_size_group_p.cpp
widgets/kis_wdg_generator.cpp
widgets/kis_workspace_chooser.cpp
widgets/kis_categorized_list_view.cpp
widgets/kis_widget_chooser.cpp
widgets/kis_tool_button.cpp
widgets/kis_floating_message.cpp
widgets/kis_lod_availability_widget.cpp
widgets/kis_color_label_selector_widget.cpp
widgets/kis_color_filter_combo.cpp
widgets/kis_elided_label.cpp
widgets/kis_stopgradient_slider_widget.cpp
widgets/kis_spinbox_color_selector.cpp
widgets/kis_screen_color_picker.cpp
widgets/kis_preset_live_preview_view.cpp
widgets/KoDualColorButton.cpp
widgets/kis_color_input.cpp
widgets/kis_color_button.cpp
widgets/KisVisualColorSelector.cpp
widgets/KisVisualColorSelectorShape.cpp
widgets/KisVisualEllipticalSelectorShape.cpp
widgets/KisVisualRectangleSelectorShape.cpp
widgets/KisVisualTriangleSelectorShape.cpp
widgets/KoStrokeConfigWidget.cpp
widgets/KoFillConfigWidget.cpp
widgets/KisLayoutSelector.cpp
utils/kis_document_aware_spin_box_unit_manager.cpp
input/kis_input_manager.cpp
input/kis_input_manager_p.cpp
input/kis_extended_modifiers_mapper.cpp
input/kis_abstract_input_action.cpp
input/kis_tool_invocation_action.cpp
input/kis_pan_action.cpp
input/kis_alternate_invocation_action.cpp
input/kis_rotate_canvas_action.cpp
input/kis_zoom_action.cpp
input/kis_change_frame_action.cpp
input/kis_gamma_exposure_action.cpp
input/kis_show_palette_action.cpp
input/kis_change_primary_setting_action.cpp
input/kis_abstract_shortcut.cpp
input/kis_native_gesture_shortcut.cpp
input/kis_single_action_shortcut.cpp
input/kis_stroke_shortcut.cpp
input/kis_shortcut_matcher.cpp
input/kis_select_layer_action.cpp
input/KisQtWidgetsTweaker.cpp
input/KisInputActionGroup.cpp
operations/kis_operation.cpp
operations/kis_operation_configuration.cpp
operations/kis_operation_registry.cpp
operations/kis_operation_ui_factory.cpp
operations/kis_operation_ui_widget.cpp
operations/kis_filter_selection_operation.cpp
actions/kis_selection_action_factories.cpp
actions/KisPasteActionFactory.cpp
input/kis_touch_shortcut.cpp
kis_document_undo_store.cpp
kis_transaction_based_command.cpp
kis_gui_context_command.cpp
kis_gui_context_command_p.cpp
input/kis_tablet_debugger.cpp
input/kis_input_profile_manager.cpp
input/kis_input_profile.cpp
input/kis_shortcut_configuration.cpp
input/config/kis_input_configuration_page.cpp
input/config/kis_edit_profiles_dialog.cpp
input/config/kis_input_profile_model.cpp
input/config/kis_input_configuration_page_item.cpp
input/config/kis_action_shortcuts_model.cpp
input/config/kis_input_type_delegate.cpp
input/config/kis_input_mode_delegate.cpp
input/config/kis_input_button.cpp
input/config/kis_input_editor_delegate.cpp
input/config/kis_mouse_input_editor.cpp
input/config/kis_wheel_input_editor.cpp
input/config/kis_key_input_editor.cpp
processing/fill_processing_visitor.cpp
kis_asl_layer_style_serializer.cpp
kis_psd_layer_style_resource.cpp
canvas/kis_mirror_axis.cpp
kis_abstract_perspective_grid.cpp
KisApplication.cpp
KisAutoSaveRecoveryDialog.cpp
KisDetailsPane.cpp
KisDocument.cpp
+ KisCloneDocumentStroke.cpp
KisNodeDelegate.cpp
kis_node_view_visibility_delegate.cpp
KisNodeToolTip.cpp
KisNodeView.cpp
kis_node_view_color_scheme.cpp
KisImportExportFilter.cpp
KisFilterEntry.cpp
KisImportExportManager.cpp
KisImportExportUtils.cpp
kis_async_action_feedback.cpp
KisMainWindow.cpp
KisOpenPane.cpp
KisPart.cpp
KisPrintJob.cpp
KisTemplate.cpp
KisTemplateCreateDia.cpp
KisTemplateGroup.cpp
KisTemplates.cpp
KisTemplatesPane.cpp
KisTemplateTree.cpp
KisUndoActionsUpdateManager.cpp
KisView.cpp
thememanager.cpp
kis_mainwindow_observer.cpp
KisViewManager.cpp
kis_mirror_manager.cpp
qtlockedfile/qtlockedfile.cpp
qtsingleapplication/qtlocalpeer.cpp
qtsingleapplication/qtsingleapplication.cpp
KisResourceBundle.cpp
KisResourceBundleManifest.cpp
kis_md5_generator.cpp
KisApplicationArguments.cpp
KisNetworkAccessManager.cpp
KisMultiFeedRSSModel.cpp
KisRemoteFileFetcher.cpp
KisPaletteModel.cpp
kis_palette_delegate.cpp
kis_palette_view.cpp
KisColorsetChooser.cpp
KisSaveGroupVisitor.cpp
KisWindowLayoutResource.cpp
KisWindowLayoutManager.cpp
KisSessionResource.cpp
KisReferenceImagesDecoration.cpp
KisReferenceImage.cpp
flake/KisReferenceImagesLayer.cpp
flake/KisReferenceImagesLayer.h
)
if(WIN32)
set(kritaui_LIB_SRCS
${kritaui_LIB_SRCS}
input/kis_tablet_event.cpp
input/wintab/kis_tablet_support_win.cpp
input/wintab/kis_screen_size_choice_dialog.cpp
qtlockedfile/qtlockedfile_win.cpp
input/wintab/kis_tablet_support_win8.cpp
opengl/kis_opengl_win.cpp
)
endif()
set(kritaui_LIB_SRCS
${kritaui_LIB_SRCS}
kis_animation_frame_cache.cpp
kis_animation_cache_populator.cpp
KisAsyncAnimationRendererBase.cpp
KisAsyncAnimationCacheRenderer.cpp
KisAsyncAnimationFramesSavingRenderer.cpp
dialogs/KisAsyncAnimationRenderDialogBase.cpp
dialogs/KisAsyncAnimationCacheRenderDialog.cpp
dialogs/KisAsyncAnimationFramesSaveDialog.cpp
canvas/kis_animation_player.cpp
kis_animation_importer.cpp
KisSyncedAudioPlayback.cpp
KisFrameDataSerializer.cpp
KisFrameCacheStore.cpp
KisFrameCacheSwapper.cpp
)
if(UNIX)
set(kritaui_LIB_SRCS
${kritaui_LIB_SRCS}
input/kis_tablet_event.cpp
input/wintab/kis_tablet_support.cpp
qtlockedfile/qtlockedfile_unix.cpp
)
if(NOT APPLE)
set(kritaui_LIB_SRCS
${kritaui_LIB_SRCS}
input/wintab/qxcbconnection_xi2.cpp
input/wintab/qxcbconnection.cpp
input/wintab/kis_xi2_event_filter.cpp
)
endif()
endif()
if(APPLE)
set(kritaui_LIB_SRCS
${kritaui_LIB_SRCS}
osx.mm
)
endif()
ki18n_wrap_ui(kritaui_LIB_SRCS
widgets/KoFillConfigWidget.ui
widgets/KoStrokeConfigWidget.ui
forms/wdgdlgpngimport.ui
forms/wdgfullscreensettings.ui
forms/wdgautogradient.ui
forms/wdggeneralsettings.ui
forms/wdgperformancesettings.ui
forms/wdggenerators.ui
forms/wdgbookmarkedconfigurationseditor.ui
forms/wdgapplyprofile.ui
forms/wdgcustompattern.ui
forms/wdglayerproperties.ui
forms/wdgcolorsettings.ui
forms/wdgtabletsettings.ui
forms/wdgcolorspaceselector.ui
forms/wdgcolorspaceselectoradvanced.ui
forms/wdgdisplaysettings.ui
forms/kis_previewwidgetbase.ui
forms/kis_matrix_widget.ui
forms/wdgselectionoptions.ui
forms/wdggeometryoptions.ui
forms/wdgnewimage.ui
forms/wdgimageproperties.ui
forms/wdgmaskfromselection.ui
forms/wdgmasksource.ui
forms/wdgfilterdialog.ui
forms/wdgmetadatamergestrategychooser.ui
forms/wdgpaintoppresets.ui
forms/wdgpaintopsettings.ui
forms/wdgdlggeneratorlayer.ui
forms/wdgdlgfilelayer.ui
forms/wdgfilterselector.ui
forms/wdgfilternodecreation.ui
forms/wdgpaintactioneditor.ui
forms/wdgmultipliersdoublesliderspinbox.ui
forms/wdgnodequerypatheditor.ui
forms/wdgpresetselectorstrip.ui
forms/wdgsavebrushpreset.ui
forms/wdgpreseticonlibrary.ui
forms/wdgdlgblacklistcleanup.ui
forms/wdgrectangleconstraints.ui
forms/wdgimportimagesequence.ui
forms/wdgstrokeselectionproperties.ui
forms/KisDetailsPaneBase.ui
forms/KisOpenPaneBase.ui
forms/wdgstopgradienteditor.ui
forms/wdgsessionmanager.ui
forms/wdgnewwindowlayout.ui
brushhud/kis_dlg_brush_hud_config.ui
forms/wdgdlginternalcolorselector.ui
dialogs/kis_delayed_save_dialog.ui
input/config/kis_input_configuration_page.ui
input/config/kis_edit_profiles_dialog.ui
input/config/kis_input_configuration_page_item.ui
input/config/kis_mouse_input_editor.ui
input/config/kis_wheel_input_editor.ui
input/config/kis_key_input_editor.ui
layerstyles/wdgBevelAndEmboss.ui
layerstyles/wdgblendingoptions.ui
layerstyles/WdgColorOverlay.ui
layerstyles/wdgContour.ui
layerstyles/wdgdropshadow.ui
layerstyles/WdgGradientOverlay.ui
layerstyles/wdgInnerGlow.ui
layerstyles/wdglayerstyles.ui
layerstyles/WdgPatternOverlay.ui
layerstyles/WdgSatin.ui
layerstyles/WdgStroke.ui
layerstyles/wdgstylesselector.ui
layerstyles/wdgTexture.ui
wdgsplash.ui
input/wintab/kis_screen_size_choice_dialog.ui
)
QT5_WRAP_CPP(kritaui_HEADERS_MOC KisNodePropertyAction_p.h)
add_library(kritaui SHARED ${kritaui_HEADERS_MOC} ${kritaui_LIB_SRCS} )
generate_export_header(kritaui BASE_NAME kritaui)
target_link_libraries(kritaui KF5::CoreAddons KF5::Completion KF5::I18n KF5::ItemViews Qt5::Network
kritaimpex kritacolor kritaimage kritalibbrush kritawidgets kritawidgetutils ${PNG_LIBRARIES} ${EXIV2_LIBRARIES}
)
if (HAVE_QT_MULTIMEDIA)
target_link_libraries(kritaui Qt5::Multimedia)
endif()
if (HAVE_KIO)
target_link_libraries(kritaui KF5::KIOCore)
endif()
if (NOT WIN32 AND NOT APPLE)
target_link_libraries(kritaui ${X11_X11_LIB}
${X11_Xinput_LIB}
${XCB_LIBRARIES})
endif()
if(APPLE)
target_link_libraries(kritaui ${FOUNDATION_LIBRARY})
target_link_libraries(kritaui ${APPKIT_LIBRARY})
endif ()
target_link_libraries(kritaui ${OPENEXR_LIBRARIES})
# Add VSync disable workaround
if(NOT WIN32 AND NOT APPLE)
target_link_libraries(kritaui ${CMAKE_DL_LIBS} Qt5::X11Extras)
endif()
if(X11_FOUND)
target_link_libraries(kritaui Qt5::X11Extras ${X11_LIBRARIES})
endif()
target_include_directories(kritaui
PUBLIC
$
$
$
$
$
$
$
)
set_target_properties(kritaui
PROPERTIES VERSION ${GENERIC_KRITA_LIB_VERSION} SOVERSION ${GENERIC_KRITA_LIB_SOVERSION}
)
install(TARGETS kritaui ${INSTALL_TARGETS_DEFAULT_ARGS})
if (APPLE)
install(FILES osx.stylesheet DESTINATION ${DATA_INSTALL_DIR}/krita)
endif ()
diff --git a/libs/ui/KisAsyncAnimationFramesSavingRenderer.cpp b/libs/ui/KisAsyncAnimationFramesSavingRenderer.cpp
index 5478ba858e..aae47bbed0 100644
--- a/libs/ui/KisAsyncAnimationFramesSavingRenderer.cpp
+++ b/libs/ui/KisAsyncAnimationFramesSavingRenderer.cpp
@@ -1,127 +1,127 @@
/*
* Copyright (c) 2017 Dmitry Kazakov
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include "KisAsyncAnimationFramesSavingRenderer.h"
#include "kis_image.h"
#include "kis_paint_device.h"
#include "KisImportExportFilter.h"
#include "KisPart.h"
#include "KisDocument.h"
#include "kis_time_range.h"
#include "kis_paint_layer.h"
struct KisAsyncAnimationFramesSavingRenderer::Private
{
Private(KisImageSP image, const KisTimeRange &_range, int _sequenceNumberingOffset, KisPropertiesConfigurationSP _exportConfiguration)
: savingDoc(KisPart::instance()->createDocument()),
range(_range),
sequenceNumberingOffset(_sequenceNumberingOffset),
exportConfiguration(_exportConfiguration)
{
- savingDoc->setAutoSaveDelay(0);
+ savingDoc->setInfiniteAutoSaveInterval();
savingDoc->setFileBatchMode(true);
KisImageSP savingImage = new KisImage(savingDoc->createUndoStore(),
image->bounds().width(),
image->bounds().height(),
image->colorSpace(),
QString());
savingImage->setResolution(image->xRes(), image->yRes());
savingDoc->setCurrentImage(savingImage);
KisPaintLayer* paintLayer = new KisPaintLayer(savingImage, "paint device", 255);
savingImage->addNode(paintLayer, savingImage->root(), KisLayerSP(0));
savingDevice = paintLayer->paintDevice();
}
QScopedPointer savingDoc;
KisPaintDeviceSP savingDevice;
KisTimeRange range;
int sequenceNumberingOffset = 0;
QString filenamePrefix;
QString filenameSuffix;
QByteArray outputMimeType;
KisPropertiesConfigurationSP exportConfiguration;
};
KisAsyncAnimationFramesSavingRenderer::KisAsyncAnimationFramesSavingRenderer(KisImageSP image,
const QString &fileNamePrefix,
const QString &fileNameSuffix,
const QByteArray &outputMimeType,
const KisTimeRange &range,
const int sequenceNumberingOffset,
KisPropertiesConfigurationSP exportConfiguration)
: m_d(new Private(image, range, sequenceNumberingOffset, exportConfiguration))
{
m_d->filenamePrefix = fileNamePrefix;
m_d->filenameSuffix = fileNameSuffix;
m_d->outputMimeType = outputMimeType;
connect(this, SIGNAL(sigCompleteRegenerationInternal(int)), SLOT(notifyFrameCompleted(int)));
connect(this, SIGNAL(sigCancelRegenerationInternal(int)), SLOT(notifyFrameCancelled(int)));
}
KisAsyncAnimationFramesSavingRenderer::~KisAsyncAnimationFramesSavingRenderer()
{
}
void KisAsyncAnimationFramesSavingRenderer::frameCompletedCallback(int frame)
{
KisImageSP image = requestedImage();
if (!image) return;
m_d->savingDevice->makeCloneFromRough(image->projection(), image->bounds());
KisTimeRange range(frame, 1);
KisImportExportFilter::ConversionStatus status = KisImportExportFilter::OK;
for (int i = range.start(); i <= range.end(); i++) {
QString frameNumber = QString("%1").arg(i - m_d->range.start() + m_d->sequenceNumberingOffset, 4, 10, QChar('0'));
QString filename = m_d->filenamePrefix + frameNumber + m_d->filenameSuffix;
if (!m_d->savingDoc->exportDocumentSync(QUrl::fromLocalFile(filename), m_d->outputMimeType, m_d->exportConfiguration)) {
status = KisImportExportFilter::InternalError;
break;
}
}
if (status == KisImportExportFilter::OK) {
emit sigCompleteRegenerationInternal(frame);
} else {
emit sigCancelRegenerationInternal(frame);
}
}
void KisAsyncAnimationFramesSavingRenderer::frameCancelledCallback(int frame)
{
notifyFrameCancelled(frame);
}
diff --git a/libs/ui/KisCloneDocumentStroke.cpp b/libs/ui/KisCloneDocumentStroke.cpp
new file mode 100644
index 0000000000..a6290c91c9
--- /dev/null
+++ b/libs/ui/KisCloneDocumentStroke.cpp
@@ -0,0 +1,59 @@
+/*
+ * Copyright (c) 2018 Dmitry Kazakov
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+#include "KisCloneDocumentStroke.h"
+
+#include "KisDocument.h"
+#include "kis_layer_utils.h"
+
+struct KRITAIMAGE_NO_EXPORT KisCloneDocumentStroke::Private
+{
+ Private(KisDocument *_document)
+ : document(_document)
+ {
+ }
+
+ KisDocument *document = 0;
+};
+
+KisCloneDocumentStroke::KisCloneDocumentStroke(KisDocument *document)
+ : KisSimpleStrokeStrategy("clone-document-stroke", kundo2_i18n("Clone Document")),
+ m_d(new Private(document))
+{
+ setClearsRedoOnStart(false);
+ setRequestsOtherStrokesToEnd(false); // TODO: ???
+ enableJob(JOB_INIT, true, KisStrokeJobData::BARRIER, KisStrokeJobData::EXCLUSIVE);
+ enableJob(JOB_FINISH, true, KisStrokeJobData::BARRIER, KisStrokeJobData::EXCLUSIVE);
+}
+
+KisCloneDocumentStroke::~KisCloneDocumentStroke()
+{
+}
+
+void KisCloneDocumentStroke::initStrokeCallback()
+{
+ KisLayerUtils::forceAllDelayedNodesUpdate(m_d->document->image()->root());
+}
+
+#include
+
+void KisCloneDocumentStroke::finishStrokeCallback()
+{
+ KisDocument *doc = m_d->document->clone();
+ doc->moveToThread(qApp->thread());
+ emit sigDocumentCloned(doc);
+}
diff --git a/libs/ui/KisCloneDocumentStroke.h b/libs/ui/KisCloneDocumentStroke.h
new file mode 100644
index 0000000000..d3d8d3ae25
--- /dev/null
+++ b/libs/ui/KisCloneDocumentStroke.h
@@ -0,0 +1,45 @@
+/*
+ * Copyright (c) 2018 Dmitry Kazakov
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+#ifndef KISCLONEDOCUMENTSTROKE_H
+#define KISCLONEDOCUMENTSTROKE_H
+
+#include "kritaimage_export.h"
+#include
+#include "kis_simple_stroke_strategy.h"
+
+class KisDocument;
+
+class KisCloneDocumentStroke : public QObject, public KisSimpleStrokeStrategy
+{
+ Q_OBJECT
+public:
+ KisCloneDocumentStroke(KisDocument *document);
+ ~KisCloneDocumentStroke();
+
+ void initStrokeCallback() override;
+ void finishStrokeCallback() override;
+
+Q_SIGNALS:
+ void sigDocumentCloned(KisDocument *image);
+
+private:
+ struct Private;
+ const QScopedPointer m_d;
+};
+
+#endif // KISCLONEDOCUMENTSTROKE_H
diff --git a/libs/ui/KisDocument.cpp b/libs/ui/KisDocument.cpp
index 2b1a597315..77b243f4c2 100644
--- a/libs/ui/KisDocument.cpp
+++ b/libs/ui/KisDocument.cpp
@@ -1,1732 +1,1790 @@
/* This file is part of the Krita project
*
* Copyright (C) 2014 Boudewijn Rempt
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public License
* along with this library; see the file COPYING.LIB. If not, write to
* the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#include "KisMainWindow.h" // XXX: remove
#include // XXX: remove
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
// Krita Image
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include "kis_layer_utils.h"
// Local
#include "KisViewManager.h"
#include "kis_clipboard.h"
#include "widgets/kis_custom_image_widget.h"
#include "canvas/kis_canvas2.h"
#include "flake/kis_shape_controller.h"
#include "kis_statusbar.h"
#include "widgets/kis_progress_widget.h"
#include "kis_canvas_resource_provider.h"
#include "KisResourceServerProvider.h"
#include "kis_node_manager.h"
#include "KisPart.h"
#include "KisApplication.h"
#include "KisDocument.h"
#include "KisImportExportManager.h"
#include "KisPart.h"
#include "KisView.h"
#include "kis_grid_config.h"
#include "kis_guides_config.h"
#include "kis_image_barrier_lock_adapter.h"
#include "KisReferenceImagesLayer.h"
#include
#include "kis_config_notifier.h"
#include "kis_async_action_feedback.h"
+#include "KisCloneDocumentStroke.h"
// Define the protocol used here for embedded documents' URL
// This used to "store" but QUrl didn't like it,
// so let's simply make it "tar" !
#define STORE_PROTOCOL "tar"
// The internal path is a hack to make QUrl happy and for document children
#define INTERNAL_PROTOCOL "intern"
#define INTERNAL_PREFIX "intern:/"
// Warning, keep it sync in koStore.cc
#include
using namespace std;
namespace {
constexpr int errorMessageTimeout = 5000;
constexpr int successMessageTimeout = 1000;
}
/**********************************************************
*
* KisDocument
*
**********************************************************/
//static
QString KisDocument::newObjectName()
{
static int s_docIFNumber = 0;
QString name; name.setNum(s_docIFNumber++); name.prepend("document_");
return name;
}
class UndoStack : public KUndo2Stack
{
public:
UndoStack(KisDocument *doc)
: KUndo2Stack(doc),
m_doc(doc)
{
}
void setIndex(int idx) override {
KisImageWSP image = this->image();
image->requestStrokeCancellation();
if(image->tryBarrierLock()) {
KUndo2Stack::setIndex(idx);
image->unlock();
}
}
void notifySetIndexChangedOneCommand() override {
KisImageWSP image = this->image();
image->unlock();
/**
* Some very weird commands may emit blocking signals to
* the GUI (e.g. KisGuiContextCommand). Here is the best thing
* we can do to avoid the deadlock
*/
while(!image->tryBarrierLock()) {
QApplication::processEvents();
}
}
void undo() override {
KisImageWSP image = this->image();
image->requestUndoDuringStroke();
if (image->tryUndoUnfinishedLod0Stroke() == UNDO_OK) {
return;
}
if(image->tryBarrierLock()) {
KUndo2Stack::undo();
image->unlock();
}
}
void redo() override {
KisImageWSP image = this->image();
if(image->tryBarrierLock()) {
KUndo2Stack::redo();
image->unlock();
}
}
private:
KisImageWSP image() {
KisImageWSP currentImage = m_doc->image();
Q_ASSERT(currentImage);
return currentImage;
}
private:
KisDocument *m_doc;
};
class Q_DECL_HIDDEN KisDocument::Private
{
public:
Private(KisDocument *q)
: docInfo(new KoDocumentInfo(q)) // deleted by QObject
, importExportManager(new KisImportExportManager(q)) // deleted manually
+ , autoSaveTimer(new QTimer(q))
, undoStack(new UndoStack(q)) // deleted by QObject
, m_bAutoDetectedMime(false)
, modified(false)
, readwrite(true)
, firstMod(QDateTime::currentDateTime())
, lastMod(firstMod)
, nserver(new KisNameServer(1))
, imageIdleWatcher(2000 /*ms*/)
, savingLock(&savingMutex)
, batchMode(false)
{
if (QLocale().measurementSystem() == QLocale::ImperialSystem) {
unit = KoUnit::Inch;
} else {
unit = KoUnit::Centimeter;
}
}
Private(const Private &rhs, KisDocument *q)
: docInfo(new KoDocumentInfo(*rhs.docInfo, q))
, unit(rhs.unit)
, importExportManager(new KisImportExportManager(q))
, mimeType(rhs.mimeType)
, outputMimeType(rhs.outputMimeType)
+ , autoSaveTimer(new QTimer(q))
, undoStack(new UndoStack(q))
, guidesConfig(rhs.guidesConfig)
, m_bAutoDetectedMime(rhs.m_bAutoDetectedMime)
, m_url(rhs.m_url)
, m_file(rhs.m_file)
, modified(rhs.modified)
, readwrite(rhs.readwrite)
, firstMod(rhs.firstMod)
, lastMod(rhs.lastMod)
, nserver(new KisNameServer(*rhs.nserver))
, preActivatedNode(0) // the node is from another hierarchy!
, imageIdleWatcher(2000 /*ms*/)
, assistants(rhs.assistants) // WARNING: assistants should not store pointers to the document!
, gridConfig(rhs.gridConfig)
, savingLock(&savingMutex)
, batchMode(rhs.batchMode)
{
}
~Private() {
// Don't delete m_d->shapeController because it's in a QObject hierarchy.
delete nserver;
}
KoDocumentInfo *docInfo = 0;
KoUnit unit;
KisImportExportManager *importExportManager = 0; // The filter-manager to use when loading/saving [for the options]
QByteArray mimeType; // The actual mimetype of the document
QByteArray outputMimeType; // The mimetype to use when saving
- QTimer autoSaveTimer;
+ QTimer *autoSaveTimer;
QString lastErrorMessage; // see openFile()
QString lastWarningMessage;
int autoSaveDelay = 300; // in seconds, 0 to disable.
bool modifiedAfterAutosave = false;
bool isAutosaving = false;
bool disregardAutosaveFailure = false;
+ int autoSaveFailureCount = 0;
KUndo2Stack *undoStack = 0;
KisGuidesConfig guidesConfig;
bool m_bAutoDetectedMime = false; // whether the mimetype in the arguments was detected by the part itself
QUrl m_url; // local url - the one displayed to the user.
QString m_file; // Local file - the only one the part implementation should deal with.
QMutex savingMutex;
bool modified = false;
bool readwrite = false;
QDateTime firstMod;
QDateTime lastMod;
KisNameServer *nserver;
KisImageSP image;
KisImageSP savingImage;
KisNodeWSP preActivatedNode;
KisShapeController* shapeController = 0;
KoShapeController* koShapeController = 0;
KisIdleWatcher imageIdleWatcher;
QScopedPointer imageIdleConnection;
QList assistants;
KisSharedPtr referenceImagesLayer;
KisGridConfig gridConfig;
StdLockableWrapper savingLock;
bool modifiedWhileSaving = false;
QScopedPointer backgroundSaveDocument;
QPointer savingUpdater;
QFuture childSavingFuture;
KritaUtils::ExportFileJob backgroundSaveJob;
bool isRecovered = false;
bool batchMode { false };
void setImageAndInitIdleWatcher(KisImageSP _image) {
image = _image;
imageIdleWatcher.setTrackedImage(image);
if (image) {
imageIdleConnection.reset(
new KisSignalAutoConnection(
&imageIdleWatcher, SIGNAL(startedIdleMode()),
image.data(), SLOT(explicitRegenerateLevelOfDetail())));
}
}
class StrippedSafeSavingLocker;
};
class KisDocument::Private::StrippedSafeSavingLocker {
public:
StrippedSafeSavingLocker(QMutex *savingMutex, KisImageSP image)
: m_locked(false)
, m_image(image)
, m_savingLock(savingMutex)
, m_imageLock(image, true)
{
/**
* Initial try to lock both objects. Locking the image guards
* us from any image composition threads running in the
* background, while savingMutex guards us from entering the
* saving code twice by autosave and main threads.
*
* Since we are trying to lock multiple objects, so we should
* do it in a safe manner.
*/
m_locked = std::try_lock(m_imageLock, m_savingLock) < 0;
if (!m_locked) {
m_image->requestStrokeEnd();
QApplication::processEvents();
// one more try...
m_locked = std::try_lock(m_imageLock, m_savingLock) < 0;
}
}
~StrippedSafeSavingLocker() {
if (m_locked) {
m_imageLock.unlock();
m_savingLock.unlock();
}
}
bool successfullyLocked() const {
return m_locked;
}
private:
bool m_locked;
KisImageSP m_image;
StdLockableWrapper m_savingLock;
KisImageBarrierLockAdapter m_imageLock;
};
KisDocument::KisDocument()
: d(new Private(this))
{
connect(KisConfigNotifier::instance(), SIGNAL(configChanged()), SLOT(slotConfigChanged()));
connect(d->undoStack, SIGNAL(cleanChanged(bool)), this, SLOT(slotUndoStackCleanChanged(bool)));
- connect(&d->autoSaveTimer, SIGNAL(timeout()), this, SLOT(slotAutoSave()));
+ connect(d->autoSaveTimer, SIGNAL(timeout()), this, SLOT(slotAutoSave()));
setObjectName(newObjectName());
// preload the krita resources
KisResourceServerProvider::instance();
d->shapeController = new KisShapeController(this, d->nserver),
d->koShapeController = new KoShapeController(0, d->shapeController),
slotConfigChanged();
}
KisDocument::KisDocument(const KisDocument &rhs)
: QObject(),
d(new Private(*rhs.d, this))
{
connect(KisConfigNotifier::instance(), SIGNAL(configChanged()), SLOT(slotConfigChanged()));
connect(d->undoStack, SIGNAL(cleanChanged(bool)), this, SLOT(slotUndoStackCleanChanged(bool)));
- connect(&d->autoSaveTimer, SIGNAL(timeout()), this, SLOT(slotAutoSave()));
+ connect(d->autoSaveTimer, SIGNAL(timeout()), this, SLOT(slotAutoSave()));
setObjectName(rhs.objectName());
d->shapeController = new KisShapeController(this, d->nserver),
d->koShapeController = new KoShapeController(0, d->shapeController),
slotConfigChanged();
// clone the image with keeping the GUIDs of the layers intact
// NOTE: we expect the image to be locked!
setCurrentImage(rhs.image()->clone(true), false);
if (rhs.d->preActivatedNode) {
// since we clone uuid's, we can use them for lacating new
// nodes. Otherwise we would need to use findSymmetricClone()
d->preActivatedNode =
KisLayerUtils::findNodeByUuid(d->image->root(), rhs.d->preActivatedNode->uuid());
}
}
KisDocument::~KisDocument()
{
// wait until all the pending operations are in progress
waitForSavingToComplete();
/**
* Push a timebomb, which will try to release the memory after
* the document has been deleted
*/
KisPaintDevice::createMemoryReleaseObject()->deleteLater();
- d->autoSaveTimer.disconnect(this);
- d->autoSaveTimer.stop();
+ d->autoSaveTimer->disconnect(this);
+ d->autoSaveTimer->stop();
delete d->importExportManager;
// Despite being QObject they needs to be deleted before the image
delete d->shapeController;
delete d->koShapeController;
if (d->image) {
d->image->notifyAboutToBeDeleted();
/**
* WARNING: We should wait for all the internal image jobs to
* finish before entering KisImage's destructor. The problem is,
* while execution of KisImage::~KisImage, all the weak shared
* pointers pointing to the image enter an inconsistent
* state(!). The shared counter is already zero and destruction
* has started, but the weak reference doesn't know about it,
* because KisShared::~KisShared hasn't been executed yet. So all
* the threads running in background and having weak pointers will
* enter the KisImage's destructor as well.
*/
d->image->requestStrokeCancellation();
d->image->waitForDone();
// clear undo commands that can still point to the image
d->undoStack->clear();
d->image->waitForDone();
KisImageWSP sanityCheckPointer = d->image;
Q_UNUSED(sanityCheckPointer);
// The following line trigger the deletion of the image
d->image.clear();
// check if the image has actually been deleted
KIS_SAFE_ASSERT_RECOVER_NOOP(!sanityCheckPointer.isValid());
}
delete d;
}
bool KisDocument::reload()
{
// XXX: reimplement!
return false;
}
KisDocument *KisDocument::clone()
{
return new KisDocument(*this);
}
bool KisDocument::exportDocumentImpl(const KritaUtils::ExportFileJob &job, KisPropertiesConfigurationSP exportConfiguration)
{
QFileInfo filePathInfo(job.filePath);
if (filePathInfo.exists() && !filePathInfo.isWritable()) {
slotCompleteSavingDocument(job,
KisImportExportFilter::CreationError,
i18n("%1 cannot be written to. Please save under a different name.", job.filePath));
return false;
}
KisConfig cfg;
if (cfg.backupFile() && filePathInfo.exists()) {
KBackup::backupFile(job.filePath);
}
KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(!job.mimeType.isEmpty(), false);
const QString actionName =
job.flags & KritaUtils::SaveIsExporting ?
i18n("Exporting Document...") :
i18n("Saving Document...");
bool started =
initiateSavingInBackground(actionName,
this, SLOT(slotCompleteSavingDocument(KritaUtils::ExportFileJob, KisImportExportFilter::ConversionStatus,QString)),
job, exportConfiguration);
if (!started) {
emit canceled(QString());
}
return started;
}
bool KisDocument::exportDocument(const QUrl &url, const QByteArray &mimeType, bool showWarnings, KisPropertiesConfigurationSP exportConfiguration)
{
using namespace KritaUtils;
SaveFlags flags = SaveIsExporting;
if (showWarnings) {
flags |= SaveShowWarnings;
}
return exportDocumentImpl(KritaUtils::ExportFileJob(url.toLocalFile(),
mimeType,
flags),
exportConfiguration);
}
bool KisDocument::saveAs(const QUrl &url, const QByteArray &mimeType, bool showWarnings, KisPropertiesConfigurationSP exportConfiguration)
{
using namespace KritaUtils;
return exportDocumentImpl(ExportFileJob(url.toLocalFile(),
mimeType,
showWarnings ? SaveShowWarnings : SaveNone),
exportConfiguration);
}
bool KisDocument::save(bool showWarnings, KisPropertiesConfigurationSP exportConfiguration)
{
return saveAs(url(), mimeType(), showWarnings, exportConfiguration);
}
QByteArray KisDocument::serializeToNativeByteArray()
{
QByteArray byteArray;
QBuffer buffer(&byteArray);
QScopedPointer filter(KisImportExportManager::filterForMimeType(nativeFormatMimeType(), KisImportExportManager::Export));
filter->setBatchMode(true);
filter->setMimeType(nativeFormatMimeType());
Private::StrippedSafeSavingLocker locker(&d->savingMutex, d->image);
if (!locker.successfullyLocked()) {
return byteArray;
}
d->savingImage = d->image;
if (filter->convert(this, &buffer) != KisImportExportFilter::OK) {
qWarning() << "serializeToByteArray():: Could not export to our native format";
}
return byteArray;
}
void KisDocument::slotCompleteSavingDocument(const KritaUtils::ExportFileJob &job, KisImportExportFilter::ConversionStatus status, const QString &errorMessage)
{
if (status == KisImportExportFilter::UserCancelled)
return;
const QString fileName = QFileInfo(job.filePath).fileName();
if (status != KisImportExportFilter::OK) {
emit statusBarMessage(i18nc("%1 --- failing file name, %2 --- error message",
"Error during saving %1: %2",
fileName,
exportErrorToUserMessage(status, errorMessage)), errorMessageTimeout);
if (!fileBatchMode()) {
const QString filePath = job.filePath;
QMessageBox::critical(0, i18nc("@title:window", "Krita"), i18n("Could not save %1\nReason: %2", filePath, errorMessage));
}
} else {
if (!(job.flags & KritaUtils::SaveIsExporting)) {
setUrl(QUrl::fromLocalFile(job.filePath));
setLocalFilePath(job.filePath);
setMimeType(job.mimeType);
updateEditingTime(true);
if (!d->modifiedWhileSaving) {
d->undoStack->setClean();
}
setRecovered(false);
removeAutoSaveFiles();
}
emit completed();
emit sigSavingFinished();
emit statusBarMessage(i18n("Finished saving %1", fileName), successMessageTimeout);
}
}
QByteArray KisDocument::mimeType() const
{
return d->mimeType;
}
void KisDocument::setMimeType(const QByteArray & mimeType)
{
d->mimeType = mimeType;
}
bool KisDocument::fileBatchMode() const
{
return d->batchMode;
}
void KisDocument::setFileBatchMode(const bool batchMode)
{
d->batchMode = batchMode;
}
KisDocument* KisDocument::lockAndCloneForSaving()
{
// force update of all the asynchronous nodes before cloning
QApplication::processEvents();
KisLayerUtils::forceAllDelayedNodesUpdate(d->image->root());
KisMainWindow *window = KisPart::instance()->currentMainwindow();
if (window) {
if (window->viewManager()) {
if (!window->viewManager()->blockUntilOperationsFinished(d->image)) {
return 0;
}
}
}
Private::StrippedSafeSavingLocker locker(&d->savingMutex, d->image);
if (!locker.successfullyLocked()) {
return 0;
}
return new KisDocument(*this);
}
bool KisDocument::exportDocumentSync(const QUrl &url, const QByteArray &mimeType, KisPropertiesConfigurationSP exportConfiguration)
{
Private::StrippedSafeSavingLocker locker(&d->savingMutex, d->image);
if (!locker.successfullyLocked()) {
return false;
}
d->savingImage = d->image;
const QString fileName = url.toLocalFile();
KisImportExportFilter::ConversionStatus status =
d->importExportManager->
exportDocument(fileName, fileName, mimeType, false, exportConfiguration);
d->savingImage = 0;
return status == KisImportExportFilter::OK;
}
bool KisDocument::initiateSavingInBackground(const QString actionName,
const QObject *receiverObject, const char *receiverMethod,
const KritaUtils::ExportFileJob &job,
KisPropertiesConfigurationSP exportConfiguration)
+{
+ return initiateSavingInBackground(actionName, receiverObject, receiverMethod,
+ job, exportConfiguration, std::unique_ptr());
+}
+
+bool KisDocument::initiateSavingInBackground(const QString actionName,
+ const QObject *receiverObject, const char *receiverMethod,
+ const KritaUtils::ExportFileJob &job,
+ KisPropertiesConfigurationSP exportConfiguration,
+ std::unique_ptr &&optionalClonedDocument)
{
KIS_ASSERT_RECOVER_RETURN_VALUE(job.isValid(), false);
- QScopedPointer clonedDocument(lockAndCloneForSaving());
+ QScopedPointer clonedDocument;
+
+ if (!optionalClonedDocument) {
+ clonedDocument.reset(lockAndCloneForSaving());
+ } else {
+ clonedDocument.reset(optionalClonedDocument.release());
+ }
// we block saving until the current saving is finished!
if (!clonedDocument || !d->savingMutex.tryLock()) {
return false;
}
KIS_ASSERT_RECOVER_RETURN_VALUE(!d->backgroundSaveDocument, false);
KIS_ASSERT_RECOVER_RETURN_VALUE(!d->backgroundSaveJob.isValid(), false);
d->backgroundSaveDocument.reset(clonedDocument.take());
d->backgroundSaveJob = job;
d->modifiedWhileSaving = false;
if (d->backgroundSaveJob.flags & KritaUtils::SaveInAutosaveMode) {
d->backgroundSaveDocument->d->isAutosaving = true;
}
connect(d->backgroundSaveDocument.data(),
SIGNAL(sigBackgroundSavingFinished(KisImportExportFilter::ConversionStatus, const QString&)),
this,
SLOT(slotChildCompletedSavingInBackground(KisImportExportFilter::ConversionStatus, const QString&)));
connect(this, SIGNAL(sigCompleteBackgroundSaving(KritaUtils::ExportFileJob,KisImportExportFilter::ConversionStatus,QString)),
receiverObject, receiverMethod, Qt::UniqueConnection);
bool started =
d->backgroundSaveDocument->startExportInBackground(actionName,
job.filePath,
job.filePath,
job.mimeType,
job.flags & KritaUtils::SaveShowWarnings,
exportConfiguration);
if (!started) {
// the state should have been deinitialized in slotChildCompletedSavingInBackground()
KIS_SAFE_ASSERT_RECOVER (!d->backgroundSaveDocument && !d->backgroundSaveJob.isValid()) {
d->backgroundSaveDocument.take()->deleteLater();
d->savingMutex.unlock();
d->backgroundSaveJob = KritaUtils::ExportFileJob();
}
}
return started;
}
void KisDocument::slotChildCompletedSavingInBackground(KisImportExportFilter::ConversionStatus status, const QString &errorMessage)
{
KIS_SAFE_ASSERT_RECOVER(!d->savingMutex.tryLock()) {
d->savingMutex.unlock();
return;
}
KIS_SAFE_ASSERT_RECOVER_RETURN(d->backgroundSaveDocument);
if (d->backgroundSaveJob.flags & KritaUtils::SaveInAutosaveMode) {
d->backgroundSaveDocument->d->isAutosaving = false;
}
d->backgroundSaveDocument.take()->deleteLater();
d->savingMutex.unlock();
KIS_SAFE_ASSERT_RECOVER_RETURN(d->backgroundSaveJob.isValid());
const KritaUtils::ExportFileJob job = d->backgroundSaveJob;
d->backgroundSaveJob = KritaUtils::ExportFileJob();
emit sigCompleteBackgroundSaving(job, status, errorMessage);
}
-void KisDocument::slotAutoSave()
+void KisDocument::slotAutoSaveImpl(std::unique_ptr &&optionalClonedDocument)
{
if (!d->modified || !d->modifiedAfterAutosave) return;
const QString autoSaveFileName = generateAutoSaveFileName(localFilePath());
emit statusBarMessage(i18n("Autosaving... %1", autoSaveFileName), successMessageTimeout);
+ const bool hadClonedDocument = bool(optionalClonedDocument);
bool started = false;
- if (d->image->isIdle()) {
+ if (d->image->isIdle() || hadClonedDocument) {
started = initiateSavingInBackground(i18n("Autosaving..."),
this, SLOT(slotCompleteAutoSaving(KritaUtils::ExportFileJob, KisImportExportFilter::ConversionStatus, const QString&)),
KritaUtils::ExportFileJob(autoSaveFileName, nativeFormatMimeType(), KritaUtils::SaveIsExporting | KritaUtils::SaveInAutosaveMode),
- 0);
+ 0,
+ std::move(optionalClonedDocument));
} else {
emit statusBarMessage(i18n("Autosaving postponed: document is busy..."), errorMessageTimeout);
}
- if (!started) {
- const int emergencyAutoSaveInterval = 10; // sec
- setAutoSaveDelay(emergencyAutoSaveInterval);
+ if (!started && !hadClonedDocument && d->autoSaveFailureCount >= 3) {
+ KisCloneDocumentStroke *stroke = new KisCloneDocumentStroke(this);
+ connect(stroke, SIGNAL(sigDocumentCloned(KisDocument*)),
+ this, SLOT(slotInitiateAsyncAutosaving(KisDocument*)),
+ Qt::BlockingQueuedConnection);
+
+ KisStrokeId strokeId = d->image->startStroke(stroke);
+ d->image->endStroke(strokeId);
+
+ setInfiniteAutoSaveInterval();
+
+ } else if (!started) {
+ setEmergencyAutoSaveInterval();
} else {
d->modifiedAfterAutosave = false;
}
}
+void KisDocument::slotAutoSave()
+{
+ slotAutoSaveImpl(std::unique_ptr());
+}
+
+void KisDocument::slotInitiateAsyncAutosaving(KisDocument *clonedDocument)
+{
+ slotAutoSaveImpl(std::unique_ptr(clonedDocument));
+}
+
void KisDocument::slotCompleteAutoSaving(const KritaUtils::ExportFileJob &job, KisImportExportFilter::ConversionStatus status, const QString &errorMessage)
{
Q_UNUSED(job);
const QString fileName = QFileInfo(job.filePath).fileName();
if (status != KisImportExportFilter::OK) {
- const int emergencyAutoSaveInterval = 10; // sec
- setAutoSaveDelay(emergencyAutoSaveInterval);
+ setEmergencyAutoSaveInterval();
emit statusBarMessage(i18nc("%1 --- failing file name, %2 --- error message",
"Error during autosaving %1: %2",
fileName,
exportErrorToUserMessage(status, errorMessage)), errorMessageTimeout);
} else {
KisConfig cfg;
d->autoSaveDelay = cfg.autoSaveInterval();
if (!d->modifiedWhileSaving) {
- d->autoSaveTimer.stop(); // until the next change
+ d->autoSaveTimer->stop(); // until the next change
+ d->autoSaveFailureCount = 0;
} else {
- setAutoSaveDelay(d->autoSaveDelay); // restart the timer
+ setNormalAutoSaveInterval();
}
emit statusBarMessage(i18n("Finished autosaving %1", fileName), successMessageTimeout);
}
}
bool KisDocument::startExportInBackground(const QString &actionName,
const QString &location,
const QString &realLocation,
const QByteArray &mimeType,
bool showWarnings,
KisPropertiesConfigurationSP exportConfiguration)
{
d->savingImage = d->image;
KisMainWindow *window = KisPart::instance()->currentMainwindow();
if (window) {
if (window->viewManager()) {
d->savingUpdater = window->viewManager()->createThreadedUpdater(actionName);
d->importExportManager->setUpdater(d->savingUpdater);
}
}
KisImportExportFilter::ConversionStatus initializationStatus;
d->childSavingFuture =
d->importExportManager->exportDocumentAsyc(location,
realLocation,
mimeType,
initializationStatus,
showWarnings,
exportConfiguration);
if (initializationStatus != KisImportExportFilter::ConversionStatus::OK) {
if (d->savingUpdater) {
d->savingUpdater->cancel();
}
d->savingImage.clear();
emit sigBackgroundSavingFinished(initializationStatus, this->errorMessage());
return false;
}
typedef QFutureWatcher StatusWatcher;
StatusWatcher *watcher = new StatusWatcher();
watcher->setFuture(d->childSavingFuture);
connect(watcher, SIGNAL(finished()), SLOT(finishExportInBackground()));
connect(watcher, SIGNAL(finished()), watcher, SLOT(deleteLater()));
return true;
}
void KisDocument::finishExportInBackground()
{
KIS_SAFE_ASSERT_RECOVER(d->childSavingFuture.isFinished()) {
emit sigBackgroundSavingFinished(KisImportExportFilter::InternalError, "");
return;
}
KisImportExportFilter::ConversionStatus status =
d->childSavingFuture.result();
const QString errorMessage = this->errorMessage();
d->savingImage.clear();
d->childSavingFuture = QFuture();
d->lastErrorMessage.clear();
if (d->savingUpdater) {
d->savingUpdater->setProgress(100);
}
emit sigBackgroundSavingFinished(status, errorMessage);
}
void KisDocument::setReadWrite(bool readwrite)
{
d->readwrite = readwrite;
- setAutoSaveDelay(d->autoSaveDelay);
+ setNormalAutoSaveInterval();
Q_FOREACH (KisMainWindow *mainWindow, KisPart::instance()->mainWindows()) {
mainWindow->setReadWrite(readwrite);
}
}
void KisDocument::setAutoSaveDelay(int delay)
{
- //qDebug() << "setting autosave delay from" << d->autoSaveDelay << "to" << delay;
- d->autoSaveDelay = delay;
- if (isReadWrite() && d->autoSaveDelay > 0) {
- d->autoSaveTimer.start(d->autoSaveDelay * 1000);
- }
- else {
- d->autoSaveTimer.stop();
+ if (isReadWrite() && delay > 0) {
+ d->autoSaveTimer->start(delay * 1000);
+ } else {
+ d->autoSaveTimer->stop();
}
}
+void KisDocument::setNormalAutoSaveInterval()
+{
+ setAutoSaveDelay(d->autoSaveDelay);
+ d->autoSaveFailureCount = 0;
+}
+
+void KisDocument::setEmergencyAutoSaveInterval()
+{
+ const int emergencyAutoSaveInterval = 10; /* sec */
+ setAutoSaveDelay(emergencyAutoSaveInterval);
+ d->autoSaveFailureCount++;
+}
+
+void KisDocument::setInfiniteAutoSaveInterval()
+{
+ setAutoSaveDelay(-1);
+}
+
KoDocumentInfo *KisDocument::documentInfo() const
{
return d->docInfo;
}
bool KisDocument::isModified() const
{
return d->modified;
}
QPixmap KisDocument::generatePreview(const QSize& size)
{
KisImageSP image = d->image;
if (d->savingImage) image = d->savingImage;
if (image) {
QRect bounds = image->bounds();
QSize newSize = bounds.size();
newSize.scale(size, Qt::KeepAspectRatio);
QPixmap px = QPixmap::fromImage(image->convertToQImage(newSize, 0));
if (px.size() == QSize(0,0)) {
px = QPixmap(newSize);
QPainter gc(&px);
QBrush checkBrush = QBrush(KisCanvasWidgetBase::createCheckersImage(newSize.width() / 5));
gc.fillRect(px.rect(), checkBrush);
gc.end();
}
return px;
}
return QPixmap(size);
}
QString KisDocument::generateAutoSaveFileName(const QString & path) const
{
QString retval;
// Using the extension allows to avoid relying on the mime magic when opening
const QString extension (".kra");
QRegularExpression autosavePattern("^\\..+-autosave.kra$");
QFileInfo fi(path);
QString dir = fi.absolutePath();
QString filename = fi.fileName();
if (path.isEmpty() || autosavePattern.match(filename).hasMatch()) {
// Never saved?
#ifdef Q_OS_WIN
// On Windows, use the temp location (https://bugs.kde.org/show_bug.cgi?id=314921)
retval = QString("%1%2.%3-%4-%5-autosave%6").arg(QDir::tempPath()).arg(QDir::separator()).arg("krita").arg(qApp->applicationPid()).arg(objectName()).arg(extension);
#else
// On Linux, use a temp file in $HOME then. Mark it with the pid so two instances don't overwrite each other's autosave file
retval = QString("%1%2.%3-%4-%5-autosave%6").arg(QDir::homePath()).arg(QDir::separator()).arg("krita").arg(qApp->applicationPid()).arg(objectName()).arg(extension);
#endif
} else {
retval = QString("%1%2.%3-autosave%4").arg(dir).arg(QDir::separator()).arg(filename).arg(extension);
}
//qDebug() << "generateAutoSaveFileName() for path" << path << ":" << retval;
return retval;
}
bool KisDocument::importDocument(const QUrl &_url)
{
bool ret;
dbgUI << "url=" << _url.url();
// open...
ret = openUrl(_url);
// reset url & m_file (kindly? set by KisParts::openUrl()) to simulate a
// File --> Import
if (ret) {
dbgUI << "success, resetting url";
resetURL();
setTitleModified();
}
return ret;
}
bool KisDocument::openUrl(const QUrl &_url, OpenFlags flags)
{
if (!_url.isLocalFile()) {
return false;
}
dbgUI << "url=" << _url.url();
d->lastErrorMessage.clear();
// Reimplemented, to add a check for autosave files and to improve error reporting
if (!_url.isValid()) {
d->lastErrorMessage = i18n("Malformed URL\n%1", _url.url()); // ## used anywhere ?
return false;
}
QUrl url(_url);
bool autosaveOpened = false;
if (url.isLocalFile() && !fileBatchMode()) {
QString file = url.toLocalFile();
QString asf = generateAutoSaveFileName(file);
if (QFile::exists(asf)) {
KisApplication *kisApp = static_cast(qApp);
kisApp->hideSplashScreen();
//dbgUI <<"asf=" << asf;
// ## TODO compare timestamps ?
int res = QMessageBox::warning(0,
i18nc("@title:window", "Krita"),
i18n("An autosaved file exists for this document.\nDo you want to open the autosaved file instead?"),
QMessageBox::Yes | QMessageBox::No | QMessageBox::Cancel, QMessageBox::Yes);
switch (res) {
case QMessageBox::Yes :
url.setPath(asf);
autosaveOpened = true;
break;
case QMessageBox::No :
QFile::remove(asf);
break;
default: // Cancel
return false;
}
}
}
bool ret = openUrlInternal(url);
if (autosaveOpened || flags & RecoveryFile) {
setReadWrite(true); // enable save button
setModified(true);
setRecovered(true);
}
else {
if (!(flags & DontAddToRecent)) {
KisPart::instance()->addRecentURLToAllMainWindows(_url);
}
if (ret) {
// Detect readonly local-files; remote files are assumed to be writable
QFileInfo fi(url.toLocalFile());
setReadWrite(fi.isWritable());
}
setRecovered(false);
}
return ret;
}
class DlgLoadMessages : public KoDialog {
public:
DlgLoadMessages(const QString &title, const QString &message, const QStringList &warnings) {
setWindowTitle(title);
setWindowIcon(KisIconUtils::loadIcon("warning"));
QWidget *page = new QWidget(this);
QVBoxLayout *layout = new QVBoxLayout(page);
QHBoxLayout *hlayout = new QHBoxLayout();
QLabel *labelWarning= new QLabel();
labelWarning->setPixmap(KisIconUtils::loadIcon("warning").pixmap(32, 32));
hlayout->addWidget(labelWarning);
hlayout->addWidget(new QLabel(message));
layout->addLayout(hlayout);
QTextBrowser *browser = new QTextBrowser();
QString warning = "";
if (warnings.size() == 1) {
warning += " Reason:
";
}
else {
warning += " Reasons:
";
}
warning += "";
Q_FOREACH(const QString &w, warnings) {
warning += "\n- " + w + "
";
}
warning += "
";
browser->setHtml(warning);
browser->setMinimumHeight(200);
browser->setMinimumWidth(400);
layout->addWidget(browser);
setMainWidget(page);
setButtons(KoDialog::Ok);
resize(minimumSize());
}
};
bool KisDocument::openFile()
{
//dbgUI <<"for" << localFilePath();
if (!QFile::exists(localFilePath())) {
QMessageBox::critical(0, i18nc("@title:window", "Krita"), i18n("File %1 does not exist.", localFilePath()));
return false;
}
QString filename = localFilePath();
QString typeName = mimeType();
if (typeName.isEmpty()) {
typeName = KisMimeDatabase::mimeTypeForFile(filename);
}
//qDebug() << "mimetypes 4:" << typeName;
// Allow to open backup files, don't keep the mimetype application/x-trash.
if (typeName == "application/x-trash") {
QString path = filename;
while (path.length() > 0) {
path.chop(1);
typeName = KisMimeDatabase::mimeTypeForFile(path);
//qDebug() << "\t" << path << typeName;
if (!typeName.isEmpty()) {
break;
}
}
//qDebug() << "chopped" << filename << "to" << path << "Was trash, is" << typeName;
}
dbgUI << localFilePath() << "type:" << typeName;
KisMainWindow *window = KisPart::instance()->currentMainwindow();
if (window && window->viewManager()) {
KoUpdaterPtr updater = window->viewManager()->createUnthreadedUpdater(i18n("Opening document"));
d->importExportManager->setUpdater(updater);
}
KisImportExportFilter::ConversionStatus status;
status = d->importExportManager->importDocument(localFilePath(), typeName);
if (status != KisImportExportFilter::OK) {
QString msg = KisImportExportFilter::conversionStatusString(status);
if (!msg.isEmpty()) {
DlgLoadMessages dlg(i18nc("@title:window", "Krita"),
i18n("Could not open %2.\nReason: %1.", msg, prettyPathOrUrl()),
errorMessage().split("\n") + warningMessage().split("\n"));
dlg.exec();
}
return false;
}
else if (!warningMessage().isEmpty()) {
DlgLoadMessages dlg(i18nc("@title:window", "Krita"),
i18n("There were problems opening %1.", prettyPathOrUrl()),
warningMessage().split("\n"));
dlg.exec();
setUrl(QUrl());
}
setMimeTypeAfterLoading(typeName);
emit sigLoadingFinished();
undoStack()->clear();
return true;
}
// shared between openFile and koMainWindow's "create new empty document" code
void KisDocument::setMimeTypeAfterLoading(const QString& mimeType)
{
d->mimeType = mimeType.toLatin1();
d->outputMimeType = d->mimeType;
}
bool KisDocument::loadNativeFormat(const QString & file_)
{
return openUrl(QUrl::fromLocalFile(file_));
}
void KisDocument::setModified()
{
d->modified = true;
}
void KisDocument::setModified(bool mod)
{
if (mod) {
updateEditingTime(false);
}
if (d->isAutosaving) // ignore setModified calls due to autosaving
return;
if ( !d->readwrite && d->modified ) {
errKrita << "Can't set a read-only document to 'modified' !" << endl;
return;
}
//dbgUI<<" url:" << url.path();
//dbgUI<<" mod="<docInfo->aboutInfo("editing-time").toInt() + d->firstMod.secsTo(d->lastMod)));
d->firstMod = now;
} else if (firstModDelta > 60 || forceStoreElapsed) {
d->docInfo->setAboutInfo("editing-time", QString::number(d->docInfo->aboutInfo("editing-time").toInt() + firstModDelta));
d->firstMod = now;
}
d->lastMod = now;
}
QString KisDocument::prettyPathOrUrl() const
{
QString _url(url().toDisplayString());
#ifdef Q_OS_WIN
if (url().isLocalFile()) {
_url = QDir::toNativeSeparators(_url);
}
#endif
return _url;
}
// Get caption from document info (title(), in about page)
QString KisDocument::caption() const
{
QString c;
const QString _url(url().fileName());
// if URL is empty...it is probably an unsaved file
if (_url.isEmpty()) {
c = " [" + i18n("Not Saved") + "] ";
} else {
c = _url; // Fall back to document URL
}
return c;
}
void KisDocument::setTitleModified()
{
emit titleModified(caption(), isModified());
}
QDomDocument KisDocument::createDomDocument(const QString& tagName, const QString& version) const
{
return createDomDocument("krita", tagName, version);
}
//static
QDomDocument KisDocument::createDomDocument(const QString& appName, const QString& tagName, const QString& version)
{
QDomImplementation impl;
QString url = QString("http://www.calligra.org/DTD/%1-%2.dtd").arg(appName).arg(version);
QDomDocumentType dtype = impl.createDocumentType(tagName,
QString("-//KDE//DTD %1 %2//EN").arg(appName).arg(version),
url);
// The namespace URN doesn't need to include the version number.
QString namespaceURN = QString("http://www.calligra.org/DTD/%1").arg(appName);
QDomDocument doc = impl.createDocument(namespaceURN, tagName, dtype);
doc.insertBefore(doc.createProcessingInstruction("xml", "version=\"1.0\" encoding=\"UTF-8\""), doc.documentElement());
return doc;
}
bool KisDocument::isNativeFormat(const QByteArray& mimetype) const
{
if (mimetype == nativeFormatMimeType())
return true;
return extraNativeMimeTypes().contains(mimetype);
}
void KisDocument::setErrorMessage(const QString& errMsg)
{
d->lastErrorMessage = errMsg;
}
QString KisDocument::errorMessage() const
{
return d->lastErrorMessage;
}
void KisDocument::setWarningMessage(const QString& warningMsg)
{
d->lastWarningMessage = warningMsg;
}
QString KisDocument::warningMessage() const
{
return d->lastWarningMessage;
}
void KisDocument::removeAutoSaveFiles()
{
//qDebug() << "removeAutoSaveFiles";
// Eliminate any auto-save file
QString asf = generateAutoSaveFileName(localFilePath()); // the one in the current dir
//qDebug() << "\tfilename:" << asf << "exists:" << QFile::exists(asf);
if (QFile::exists(asf)) {
//qDebug() << "\tremoving autosavefile" << asf;
QFile::remove(asf);
}
asf = generateAutoSaveFileName(QString()); // and the one in $HOME
//qDebug() << "Autsavefile in $home" << asf;
if (QFile::exists(asf)) {
//qDebug() << "\tremoving autsavefile 2" << asf;
QFile::remove(asf);
}
}
KoUnit KisDocument::unit() const
{
return d->unit;
}
void KisDocument::setUnit(const KoUnit &unit)
{
if (d->unit != unit) {
d->unit = unit;
emit unitChanged(unit);
}
}
KUndo2Stack *KisDocument::undoStack()
{
return d->undoStack;
}
KisImportExportManager *KisDocument::importExportManager() const
{
return d->importExportManager;
}
void KisDocument::addCommand(KUndo2Command *command)
{
if (command)
d->undoStack->push(command);
}
void KisDocument::beginMacro(const KUndo2MagicString & text)
{
d->undoStack->beginMacro(text);
}
void KisDocument::endMacro()
{
d->undoStack->endMacro();
}
void KisDocument::slotUndoStackCleanChanged(bool value)
{
setModified(!value);
}
void KisDocument::slotConfigChanged()
{
KisConfig cfg;
d->undoStack->setUndoLimit(cfg.undoStackLimit());
- setAutoSaveDelay(cfg.autoSaveInterval());
+ d->autoSaveDelay = cfg.autoSaveInterval();
+ setNormalAutoSaveInterval();
}
void KisDocument::clearUndoHistory()
{
d->undoStack->clear();
}
KisGridConfig KisDocument::gridConfig() const
{
return d->gridConfig;
}
void KisDocument::setGridConfig(const KisGridConfig &config)
{
d->gridConfig = config;
}
const KisGuidesConfig& KisDocument::guidesConfig() const
{
return d->guidesConfig;
}
void KisDocument::setGuidesConfig(const KisGuidesConfig &data)
{
if (d->guidesConfig == data) return;
d->guidesConfig = data;
emit sigGuidesConfigChanged(d->guidesConfig);
}
void KisDocument::resetURL() {
setUrl(QUrl());
setLocalFilePath(QString());
}
KoDocumentInfoDlg *KisDocument::createDocumentInfoDialog(QWidget *parent, KoDocumentInfo *docInfo) const
{
return new KoDocumentInfoDlg(parent, docInfo);
}
bool KisDocument::isReadWrite() const
{
return d->readwrite;
}
QUrl KisDocument::url() const
{
return d->m_url;
}
bool KisDocument::closeUrl(bool promptToSave)
{
if (promptToSave) {
if ( isReadWrite() && isModified()) {
Q_FOREACH (KisView *view, KisPart::instance()->views()) {
if (view && view->document() == this) {
if (!view->queryClose()) {
return false;
}
}
}
}
}
// Not modified => ok and delete temp file.
d->mimeType = QByteArray();
// It always succeeds for a read-only part,
// but the return value exists for reimplementations
// (e.g. pressing cancel for a modified read-write part)
return true;
}
void KisDocument::setUrl(const QUrl &url)
{
d->m_url = url;
}
QString KisDocument::localFilePath() const
{
return d->m_file;
}
void KisDocument::setLocalFilePath( const QString &localFilePath )
{
d->m_file = localFilePath;
}
bool KisDocument::openUrlInternal(const QUrl &url)
{
if ( !url.isValid() )
return false;
if (d->m_bAutoDetectedMime) {
d->mimeType = QByteArray();
d->m_bAutoDetectedMime = false;
}
QByteArray mimetype = d->mimeType;
if ( !closeUrl() )
return false;
d->mimeType = mimetype;
setUrl(url);
d->m_file.clear();
if (d->m_url.isLocalFile()) {
d->m_file = d->m_url.toLocalFile();
bool ret;
// set the mimetype only if it was not already set (for example, by the host application)
if (d->mimeType.isEmpty()) {
// get the mimetype of the file
// using findByUrl() to avoid another string -> url conversion
QString mime = KisMimeDatabase::mimeTypeForFile(d->m_url.toLocalFile());
d->mimeType = mime.toLocal8Bit();
d->m_bAutoDetectedMime = true;
}
setUrl(d->m_url);
ret = openFile();
if (ret) {
emit completed();
} else {
emit canceled(QString());
}
return ret;
}
return false;
}
bool KisDocument::newImage(const QString& name,
qint32 width, qint32 height,
const KoColorSpace* cs,
const KoColor &bgColor, bool backgroundAsLayer,
int numberOfLayers,
const QString &description, const double imageResolution)
{
Q_ASSERT(cs);
KisConfig cfg;
KisImageSP image;
KisPaintLayerSP layer;
if (!cs) return false;
QApplication::setOverrideCursor(Qt::BusyCursor);
image = new KisImage(createUndoStore(), width, height, cs, name);
Q_CHECK_PTR(image);
connect(image, SIGNAL(sigImageModified()), this, SLOT(setImageModified()), Qt::UniqueConnection);
image->setResolution(imageResolution, imageResolution);
image->assignImageProfile(cs->profile());
documentInfo()->setAboutInfo("title", name);
documentInfo()->setAboutInfo("abstract", description);
layer = new KisPaintLayer(image.data(), image->nextLayerName(), OPACITY_OPAQUE_U8, cs);
Q_CHECK_PTR(layer);
if (backgroundAsLayer) {
image->setDefaultProjectionColor(KoColor(cs));
if (bgColor.opacityU8() == OPACITY_OPAQUE_U8) {
layer->paintDevice()->setDefaultPixel(bgColor);
} else {
// Hack: with a semi-transparent background color, the projection isn't composited right if we just set the default pixel
KisFillPainter painter;
painter.begin(layer->paintDevice());
painter.fillRect(0, 0, width, height, bgColor, bgColor.opacityU8());
}
} else {
image->setDefaultProjectionColor(bgColor);
}
layer->setDirty(QRect(0, 0, width, height));
image->addNode(layer.data(), image->rootLayer().data());
setCurrentImage(image);
for(int i = 1; i < numberOfLayers; ++i) {
KisPaintLayerSP layer = new KisPaintLayer(image, image->nextLayerName(), OPACITY_OPAQUE_U8, cs);
image->addNode(layer, image->root(), i);
layer->setDirty(QRect(0, 0, width, height));
}
cfg.defImageWidth(width);
cfg.defImageHeight(height);
cfg.defImageResolution(imageResolution);
cfg.defColorModel(image->colorSpace()->colorModelId().id());
cfg.setDefaultColorDepth(image->colorSpace()->colorDepthId().id());
cfg.defColorProfile(image->colorSpace()->profile()->name());
QApplication::restoreOverrideCursor();
return true;
}
bool KisDocument::isSaving() const
{
const bool result = d->savingMutex.tryLock();
if (result) {
d->savingMutex.unlock();
}
return !result;
}
void KisDocument::waitForSavingToComplete()
{
if (isSaving()) {
KisAsyncActionFeedback f(i18nc("progress dialog message when the user closes the document that is being saved", "Waiting for saving to complete..."), 0);
f.waitForMutex(&d->savingMutex);
}
}
KoShapeBasedDocumentBase *KisDocument::shapeController() const
{
return d->shapeController;
}
KoShapeLayer* KisDocument::shapeForNode(KisNodeSP layer) const
{
return d->shapeController->shapeForNode(layer);
}
QList KisDocument::assistants() const
{
return d->assistants;
}
void KisDocument::setAssistants(const QList &value)
{
d->assistants = value;
}
KisSharedPtr KisDocument::createReferenceImagesLayer(KisImageSP targetImage)
{
if (!d->referenceImagesLayer) {
if (targetImage.isNull()) targetImage = d->image;
d->referenceImagesLayer = new KisReferenceImagesLayer(shapeController(), targetImage);
targetImage->addNode(d->referenceImagesLayer, targetImage->root());
}
return d->referenceImagesLayer;
}
KisReferenceImagesLayer *KisDocument::referenceImagesLayer() const
{
return d->referenceImagesLayer.data();
}
void KisDocument::setPreActivatedNode(KisNodeSP activatedNode)
{
d->preActivatedNode = activatedNode;
}
KisNodeSP KisDocument::preActivatedNode() const
{
return d->preActivatedNode;
}
KisImageWSP KisDocument::image() const
{
return d->image;
}
KisImageSP KisDocument::savingImage() const
{
return d->savingImage;
}
void KisDocument::setCurrentImage(KisImageSP image, bool forceInitialUpdate)
{
if (d->image) {
// Disconnect existing sig/slot connections
d->image->disconnect(this);
d->shapeController->setImage(0);
d->image = 0;
}
if (!image) return;
d->setImageAndInitIdleWatcher(image);
d->shapeController->setImage(image);
setModified(false);
connect(d->image, SIGNAL(sigImageModified()), this, SLOT(setImageModified()), Qt::UniqueConnection);
if (forceInitialUpdate) {
d->image->initialRefreshGraph();
}
}
void KisDocument::hackPreliminarySetImage(KisImageSP image)
{
KIS_SAFE_ASSERT_RECOVER_RETURN(!d->image);
d->setImageAndInitIdleWatcher(image);
d->shapeController->setImage(image);
}
void KisDocument::setImageModified()
{
setModified(true);
}
KisUndoStore* KisDocument::createUndoStore()
{
return new KisDocumentUndoStore(this);
}
bool KisDocument::isAutosaving() const
{
return d->isAutosaving;
}
QString KisDocument::exportErrorToUserMessage(KisImportExportFilter::ConversionStatus status, const QString &errorMessage)
{
return errorMessage.isEmpty() ? KisImportExportFilter::conversionStatusString(status) : errorMessage;
}
diff --git a/libs/ui/KisDocument.h b/libs/ui/KisDocument.h
index 3f30864b87..22130bfb27 100644
--- a/libs/ui/KisDocument.h
+++ b/libs/ui/KisDocument.h
@@ -1,615 +1,644 @@
/* This file is part of the Krita project
*
* Copyright (C) 2014 Boudewijn Rempt
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public License
* along with this library; see the file COPYING.LIB. If not, write to
* the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#ifndef KISDOCUMENT_H
#define KISDOCUMENT_H
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include "kritaui_export.h"
+#include
+
class QString;
class KUndo2Command;
class KoUnit;
class KoColor;
class KoColorSpace;
class KoShapeBasedDocumentBase;
class KoShapeLayer;
class KoStore;
class KoOdfReadStore;
class KoDocumentInfo;
class KoDocumentInfoDlg;
class KisImportExportManager;
class KisUndoStore;
class KisPart;
class KisGridConfig;
class KisGuidesConfig;
class QDomDocument;
class KisReferenceImagesLayer;
#define KIS_MIME_TYPE "application/x-krita"
/**
* The %Calligra document class
*
* This class provides some functionality each %Calligra document should have.
*
* @short The %Calligra document class
*/
class KRITAUI_EXPORT KisDocument : public QObject, public KoDocumentBase
{
Q_OBJECT
protected:
explicit KisDocument();
/**
* @brief KisDocument makes a deep copy of the document \p rhs.
* The caller *must* ensure that the image is properly
* locked and is in consistent state before asking for
* cloning.
* @param rhs the source document to copy from
*/
explicit KisDocument(const KisDocument &rhs);
public:
enum OpenFlag {
None = 0,
DontAddToRecent = 0x1,
RecoveryFile = 0x2
};
Q_DECLARE_FLAGS(OpenFlags, OpenFlag)
/**
* Destructor.
*
* The destructor does not delete any attached KisView objects and it does not
* delete the attached widget as returned by widget().
*/
~KisDocument() override;
/**
* @brief reload Reloads the document from the original url
* @return the result of loading the document
*/
bool reload();
/**
* @brief creates a clone of the document and returns it. Please make sure that you
* hold all the necessary locks on the image before asking for a clone!
*/
KisDocument* clone();
/**
* @brief openUrl Open an URL
* @param url The URL to open
* @param flags Control specific behavior
* @return success status
*/
bool openUrl(const QUrl &url, OpenFlags flags = None);
/**
* Opens the document given by @p url, without storing the URL
* in the KisDocument.
* Call this instead of openUrl() to implement KisMainWindow's
* File --> Import feature.
*
* @note This will call openUrl(). To differentiate this from an ordinary
* Open operation (in any reimplementation of openUrl() or openFile())
* call isImporting().
*/
bool importDocument(const QUrl &url);
/**
* Saves the document as @p url without changing the state of the
* KisDocument (URL, modified flag etc.). Call this instead of
* KisParts::ReadWritePart::saveAs() to implement KisMainWindow's
* File --> Export feature.
*/
bool exportDocument(const QUrl &url, const QByteArray &mimeType, bool showWarnings = false, KisPropertiesConfigurationSP exportConfiguration = 0);
bool exportDocumentSync(const QUrl &url, const QByteArray &mimeType, KisPropertiesConfigurationSP exportConfiguration = 0);
private:
bool exportDocumentImpl(const KritaUtils::ExportFileJob &job, KisPropertiesConfigurationSP exportConfiguration);
public:
/**
* @brief Sets whether the document can be edited or is read only.
*
* This recursively applied to all child documents and
* KisView::updateReadWrite is called for every attached
* view.
*/
void setReadWrite(bool readwrite = true);
/**
* To be preferred when a document exists. It is fast when calling
* it multiple times since it caches the result that readNativeFormatMimeType()
* delivers.
* This comes from the X-KDE-NativeMimeType key in the .desktop file.
*/
static QByteArray nativeFormatMimeType() { return KIS_MIME_TYPE; }
/// Checks whether a given mimetype can be handled natively.
bool isNativeFormat(const QByteArray& mimetype) const;
/// Returns a list of the mimetypes considered "native", i.e. which can
/// be saved by KisDocument without a filter, in *addition* to the main one
static QStringList extraNativeMimeTypes() { return QStringList() << KIS_MIME_TYPE; }
/**
* Returns the actual mimetype of the document
*/
QByteArray mimeType() const override;
/**
* @brief Sets the mime type for the document.
*
* When choosing "save as" this is also the mime type
* selected by default.
*/
void setMimeType(const QByteArray & mimeType) override;
/**
* @return true if file operations should inhibit the option dialog
*/
bool fileBatchMode() const;
/**
* @param batchMode if true, do not show the option dialog for file operations.
*/
void setFileBatchMode(const bool batchMode);
/**
* Sets the error message to be shown to the user (use i18n()!)
* when loading or saving fails.
* If you asked the user about something and they chose "Cancel",
*/
void setErrorMessage(const QString& errMsg);
/**
* Return the last error message. Usually KisDocument takes care of
* showing it; this method is mostly provided for non-interactive use.
*/
QString errorMessage() const;
/**
* Sets the warning message to be shown to the user (use i18n()!)
* when loading or saving fails.
*/
void setWarningMessage(const QString& warningMsg);
/**
* Return the last warning message set by loading or saving. Warnings
* mean that the document could not be completely loaded, but the errors
* were not absolutely fatal.
*/
QString warningMessage() const;
/**
* @brief Generates a preview picture of the document
* @note The preview is used in the File Dialog and also to create the Thumbnail
*/
QPixmap generatePreview(const QSize& size);
/**
* Tells the document that its title has been modified, either because
* the modified status changes (this is done by setModified() ) or
* because the URL or the document-info's title changed.
*/
void setTitleModified();
/**
* @brief Sets the document to empty.
*
* Used after loading a template
* (which is not empty, but not the user's input).
*
* @see isEmpty()
*/
void setEmpty(bool empty = true);
/**
* Return a correctly created QDomDocument for this KisDocument,
* including processing instruction, complete DOCTYPE tag (with systemId and publicId), and root element.
* @param tagName the name of the tag for the root element
* @param version the DTD version (usually the application's version).
*/
QDomDocument createDomDocument(const QString& tagName, const QString& version) const;
/**
* Return a correctly created QDomDocument for an old (1.3-style) %Calligra document,
* including processing instruction, complete DOCTYPE tag (with systemId and publicId), and root element.
* This static method can be used e.g. by filters.
* @param appName the app's instance name, e.g. words, kspread, kpresenter etc.
* @param tagName the name of the tag for the root element, e.g. DOC for words/kpresenter.
* @param version the DTD version (usually the application's version).
*/
static QDomDocument createDomDocument(const QString& appName, const QString& tagName, const QString& version);
/**
* Loads a document in the native format from a given URL.
* Reimplement if your native format isn't XML.
*
* @param file the file to load - usually KReadOnlyPart::m_file or the result of a filter
*/
bool loadNativeFormat(const QString & file);
/**
- * Activate/deactivate/configure the autosave feature.
- * @param delay in seconds, 0 to disable
+ * Set standard autosave interval that is set by a config file
*/
- void setAutoSaveDelay(int delay);
+ void setNormalAutoSaveInterval();
+
+ /**
+ * Set emergency interval that autosave uses when the image is busy,
+ * by default it is 10 sec
+ */
+ void setEmergencyAutoSaveInterval();
+
+ /**
+ * Disable autosave
+ */
+ void setInfiniteAutoSaveInterval();
/**
* @return the information concerning this document.
* @see KoDocumentInfo
*/
KoDocumentInfo *documentInfo() const;
/**
* Performs a cleanup of unneeded backup files
*/
void removeAutoSaveFiles();
/**
* Returns true if this document or any of its internal child documents are modified.
*/
bool isModified() const override;
/**
* @return caption of the document
*
* Caption is of the form "[title] - [url]",
* built out of the document info (title) and pretty-printed
* document URL.
* If the title is not present, only the URL it returned.
*/
QString caption() const;
/**
* Sets the document URL to empty URL
* KParts doesn't allow this, but %Calligra apps have e.g. templates
* After using loadNativeFormat on a template, one wants
* to set the url to QUrl()
*/
void resetURL();
/**
* @internal (public for KisMainWindow)
*/
void setMimeTypeAfterLoading(const QString& mimeType);
/**
* Returns the unit used to display all measures/distances.
*/
KoUnit unit() const;
/**
* Sets the unit used to display all measures/distances.
*/
void setUnit(const KoUnit &unit);
KisGridConfig gridConfig() const;
void setGridConfig(const KisGridConfig &config);
/// returns the guides data for this document.
const KisGuidesConfig& guidesConfig() const;
void setGuidesConfig(const KisGuidesConfig &data);
void clearUndoHistory();
/**
* Sets the modified flag on the document. This means that it has
* to be saved or not before deleting it.
*/
void setModified(bool _mod);
void setRecovered(bool value);
bool isRecovered() const;
void updateEditingTime(bool forceStoreElapsed);
/**
* Returns the global undo stack
*/
KUndo2Stack *undoStack();
/**
* @brief importExportManager gives access to the internal import/export manager
* @return the document's import/export manager
*/
KisImportExportManager *importExportManager() const;
/**
* @brief serializeToNativeByteArray daves the document into a .kra file wtitten
* to a memory-based byte-array
* @return a byte array containing the .kra file
*/
QByteArray serializeToNativeByteArray();
/**
* @brief isInSaving shown if the document has any (background) saving process or not
* @return true if there is some saving in action
*/
bool isInSaving() const;
public Q_SLOTS:
/**
* Adds a command to the undo stack and executes it by calling the redo() function.
* @param command command to add to the undo stack
*/
void addCommand(KUndo2Command *command);
/**
* Begins recording of a macro command. At the end endMacro needs to be called.
* @param text command description
*/
void beginMacro(const KUndo2MagicString &text);
/**
* Ends the recording of a macro command.
*/
void endMacro();
Q_SIGNALS:
/**
* This signal is emitted when the unit is changed by setUnit().
* It is common to connect views to it, in order to change the displayed units
* (e.g. in the rulers)
*/
void unitChanged(const KoUnit &unit);
/**
* Emitted e.g. at the beginning of a save operation
* This is emitted by KisDocument and used by KisView to display a statusbar message
*/
void statusBarMessage(const QString& text, int timeout = 0);
/**
* Emitted e.g. at the end of a save operation
* This is emitted by KisDocument and used by KisView to clear the statusbar message
*/
void clearStatusBarMessage();
/**
* Emitted when the document is modified
*/
void modified(bool);
void titleModified(const QString &caption, bool isModified);
void sigLoadingFinished();
void sigSavingFinished();
void sigGuidesConfigChanged(const KisGuidesConfig &config);
void sigBackgroundSavingFinished(KisImportExportFilter::ConversionStatus status, const QString &errorMessage);
void sigCompleteBackgroundSaving(const KritaUtils::ExportFileJob &job, KisImportExportFilter::ConversionStatus status, const QString &errorMessage);
private Q_SLOTS:
void finishExportInBackground();
void slotChildCompletedSavingInBackground(KisImportExportFilter::ConversionStatus status, const QString &errorMessage);
void slotCompleteAutoSaving(const KritaUtils::ExportFileJob &job, KisImportExportFilter::ConversionStatus status, const QString &errorMessage);
void slotCompleteSavingDocument(const KritaUtils::ExportFileJob &job, KisImportExportFilter::ConversionStatus status, const QString &errorMessage);
+
+ void slotInitiateAsyncAutosaving(KisDocument *clonedDocument);
+
private:
friend class KisPart;
friend class SafeSavingLocker;
+ bool initiateSavingInBackground(const QString actionName,
+ const QObject *receiverObject, const char *receiverMethod,
+ const KritaUtils::ExportFileJob &job,
+ KisPropertiesConfigurationSP exportConfiguration,
+ std::unique_ptr &&optionalClonedDocument);
+
bool initiateSavingInBackground(const QString actionName,
const QObject *receiverObject, const char *receiverMethod,
const KritaUtils::ExportFileJob &job,
KisPropertiesConfigurationSP exportConfiguration);
bool startExportInBackground(const QString &actionName, const QString &location,
const QString &realLocation,
const QByteArray &mimeType,
bool showWarnings,
KisPropertiesConfigurationSP exportConfiguration);
+ /**
+ * Activate/deactivate/configure the autosave feature.
+ * @param delay in seconds, 0 to disable
+ */
+ void setAutoSaveDelay(int delay);
+
/**
* Generate a name for the document.
*/
QString newObjectName();
QString generateAutoSaveFileName(const QString & path) const;
/**
* Loads a document
*
* Applies a filter if necessary, and calls loadNativeFormat in any case
* You should not have to reimplement, except for very special cases.
*
* NOTE: this method also creates a new KisView instance!
*
* This method is called from the KReadOnlyPart::openUrl method.
*/
bool openFile();
/** @internal */
void setModified();
public:
bool isAutosaving() const override;
public:
QString localFilePath() const override;
void setLocalFilePath( const QString &localFilePath );
KoDocumentInfoDlg* createDocumentInfoDialog(QWidget *parent, KoDocumentInfo *docInfo) const;
bool isReadWrite() const;
QUrl url() const override;
void setUrl(const QUrl &url) override;
bool closeUrl(bool promptToSave = true);
bool saveAs(const QUrl &url, const QByteArray &mimeType, bool showWarnings, KisPropertiesConfigurationSP exportConfigration = 0);
/**
* Create a new image that has this document as a parent and
* replace the current image with this image.
*/
bool newImage(const QString& name, qint32 width, qint32 height, const KoColorSpace * cs, const KoColor &bgColor, bool backgroundAsLayer,
int numberOfLayers, const QString &imageDescription, const double imageResolution);
bool isSaving() const;
void waitForSavingToComplete();
KisImageWSP image() const;
/**
* @brief savingImage provides a detached, shallow copy of the original image that must be used when saving.
* Any strokes in progress will not be applied to this image, so the result might be missing some data. On
* the other hand, it won't block.
*
* @return a shallow copy of the original image, or 0 is saving is not in progress
*/
KisImageSP savingImage() const;
/**
* Set the current image to the specified image and turn undo on.
*/
void setCurrentImage(KisImageSP image, bool forceInitialUpdate = true);
/**
* Set the image of the document preliminary, before the document
* has completed loading. Some of the document items (shapes) may want
* to access image properties (bounds and resolution), so we should provide
* it to them even before the entire image is loaded.
*
* Right now, the only use by KoShapeRegistry::createShapeFromOdf(), remove
* after it is deprecated.
*/
void hackPreliminarySetImage(KisImageSP image);
KisUndoStore* createUndoStore();
/**
* The shape controller matches internal krita image layers with
* the flake shape hierarchy.
*/
KoShapeBasedDocumentBase * shapeController() const;
KoShapeLayer* shapeForNode(KisNodeSP layer) const;
/**
* Set the list of nodes that was marked as currently active. Used *only*
* for saving loading. Never use it for tools or processing.
*/
void setPreActivatedNode(KisNodeSP activatedNode);
/**
* @return the node that was set as active during loading. Used *only*
* for saving loading. Never use it for tools or processing.
*/
KisNodeSP preActivatedNode() const;
/// @return the list of assistants associated with this document
QList assistants() const;
/// @replace the current list of assistants with @param value
void setAssistants(const QList &value);
/**
* Get existing reference images layer or create new if none exists.
*/
KisSharedPtr createReferenceImagesLayer(KisImageSP targetImage = KisImageSP());
/**
* Get existing reference images layer or null if none exists.
*/
KisReferenceImagesLayer *referenceImagesLayer() const;
bool save(bool showWarnings, KisPropertiesConfigurationSP exportConfiguration);
Q_SIGNALS:
void completed();
void canceled(const QString &);
private Q_SLOTS:
void setImageModified();
void slotAutoSave();
void slotUndoStackCleanChanged(bool value);
void slotConfigChanged();
private:
/**
* @brief try to clone the image. This method handles all the locking for you. If locking
* has failed, no cloning happens
* @return cloned document on success, null otherwise
*/
KisDocument *lockAndCloneForSaving();
QString exportErrorToUserMessage(KisImportExportFilter::ConversionStatus status, const QString &errorMessage);
QString prettyPathOrUrl() const;
bool openUrlInternal(const QUrl &url);
+ void slotAutoSaveImpl(std::unique_ptr &&optionalClonedDocument);
+
class Private;
Private *const d;
};
Q_DECLARE_OPERATORS_FOR_FLAGS(KisDocument::OpenFlags)
Q_DECLARE_METATYPE(KisDocument*)
#endif
diff --git a/plugins/impex/csv/csv_loader.cpp b/plugins/impex/csv/csv_loader.cpp
index bbf387b6de..c47e7026e3 100644
--- a/plugins/impex/csv/csv_loader.cpp
+++ b/plugins/impex/csv/csv_loader.cpp
@@ -1,489 +1,489 @@
/*
* Copyright (c) 2016 Laszlo Fazekas
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#include "csv_loader.h"
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include "csv_read_line.h"
#include "csv_layer_record.h"
CSVLoader::CSVLoader(KisDocument *doc, bool batchMode)
: m_image(0)
, m_doc(doc)
, m_batchMode(batchMode)
, m_stop(false)
{
}
CSVLoader::~CSVLoader()
{
}
KisImageBuilder_Result CSVLoader::decode(QIODevice *io, const QString &filename)
{
QString field;
int idx;
int frame = 0;
QString projName;
int width = 0;
int height = 0;
int frameCount = 1;
float framerate = 24.0;
float pixelRatio = 1.0;
int projNameIdx = -1;
int widthIdx = -1;
int heightIdx = -1;
int frameCountIdx = -1;
int framerateIdx = -1;
int pixelRatioIdx = -1;
QVector layers;
QApplication::setOverrideCursor(QCursor(Qt::WaitCursor));
idx = filename.lastIndexOf(QRegExp("[\\/]"));
QString base = (idx == -1) ? QString() : filename.left(idx + 1); //include separator
QString path = filename;
if (path.right(4).toUpper() == ".CSV")
path = path.left(path.size() - 4);
//according to the QT docs, the slash is a universal directory separator
path.append(".frames/");
KisImageBuilder_Result retval = KisImageBuilder_RESULT_OK;
dbgFile << "pos:" << io->pos();
CSVReadLine readLine;
QScopedPointer importDoc(KisPart::instance()->createDocument());
- importDoc->setAutoSaveDelay(0);
+ importDoc->setInfiniteAutoSaveInterval();
importDoc->setFileBatchMode(true);
KisView *setView(0);
if (!m_batchMode) {
// TODO: use other systems of progress reporting (KisViewManager::createUnthreadedUpdater()
// //show the statusbar message even if no view
// Q_FOREACH (KisView* view, KisPart::instance()->views()) {
// if (view && view->document() == m_doc) {
// setView = view;
// break;
// }
// }
// if (!setView) {
// QStatusBar *sb = KisPart::instance()->currentMainwindow()->statusBar();
// if (sb) {
// sb->showMessage(i18n("Loading CSV file..."));
// }
// } else {
// emit m_doc->statusBarMessage(i18n("Loading CSV file..."));
// }
// emit m_doc->sigProgress(0);
// connect(m_doc, SIGNAL(sigProgressCanceled()), this, SLOT(cancel()));
}
int step = 0;
do {
qApp->processEvents();
if (m_stop) {
retval = KisImageBuilder_RESULT_CANCEL;
break;
}
if ((idx = readLine.nextLine(io)) <= 0) {
if ((idx < 0) ||(step < 5))
retval = KisImageBuilder_RESULT_FAILURE;
break;
}
field = readLine.nextField(); //first field of the line
if (field.isNull()) continue; //empty row
switch (step) {
case 0 : //skip first row
step = 1;
break;
case 1 : //scene header names
step = 2;
for (idx = 0; !field.isNull(); idx++) {
if (field == "Project Name") {
projNameIdx = idx;
} else if (field == "Width") {
widthIdx = idx;
} else if (field == "Height") {
heightIdx = idx;
} else if (field == "Frame Count") {
frameCountIdx = idx;
} else if (field == "Frame Rate") {
framerateIdx = idx;
} else if (field == "Pixel Aspect Ratio") {
pixelRatioIdx = idx;
}
field= readLine.nextField();
}
break;
case 2 : //scene header values
step= 3;
for (idx= 0; !field.isNull(); idx++) {
if (idx == projNameIdx) {
projName = field;
} else if (idx == widthIdx) {
width = field.toInt();
} else if (idx == heightIdx) {
height = field.toInt();
} else if (idx == frameCountIdx) {
frameCount = field.toInt();
if (frameCount < 1) frameCount= 1;
} else if (idx == framerateIdx) {
framerate = field.toFloat();
} else if (idx == pixelRatioIdx) {
pixelRatio = field.toFloat();
}
field= readLine.nextField();
}
if ((width < 1) || (height < 1)) {
retval = KisImageBuilder_RESULT_FAILURE;
break;
}
retval = createNewImage(width, height, pixelRatio, projName.isNull() ? filename : projName);
break;
case 3 : //create level headers
if (field[0] != '#') break;
for (; !(field = readLine.nextField()).isNull(); ) {
CSVLayerRecord* layerRecord = new CSVLayerRecord();
layers.append(layerRecord);
}
readLine.rewind();
field = readLine.nextField();
step = 4;
/* Falls through */
case 4 : //level header
if (field == "#Layers") {
//layer name
for (idx = 0; !(field = readLine.nextField()).isNull() && (idx < layers.size()); idx++)
layers.at(idx)->name = field;
break;
}
if (field == "#Density") {
//layer opacity
for (idx = 0; !(field = readLine.nextField()).isNull() && (idx < layers.size()); idx++)
layers.at(idx)->density = field.toFloat();
break;
}
if (field == "#Blending") {
//layer blending mode
for (idx = 0; !(field = readLine.nextField()).isNull() && (idx < layers.size()); idx++)
layers.at(idx)->blending = field;
break;
}
if (field == "#Visible") {
//layer visibility
for (idx = 0; !(field = readLine.nextField()).isNull() && (idx < layers.size()); idx++)
layers.at(idx)->visible = field.toInt();
break;
}
if (field == "#Folder") {
//CSV 1.1 folder location
for (idx = 0; !(field = readLine.nextField()).isNull() && (idx < layers.size()); idx++)
layers.at(idx)->path = validPath(field, base);
break;
}
if ((field.size() < 2) || (field[0] != '#') || !field[1].isDigit()) break;
step = 5;
/* Falls through */
case 5 : //frames
if ((field.size() < 2) || (field[0] != '#') || !field[1].isDigit()) break;
for (idx = 0; !(field = readLine.nextField()).isNull() && (idx < layers.size()); idx++) {
CSVLayerRecord* layer = layers.at(idx);
if (layer->last != field) {
if (!m_batchMode) {
//emit m_doc->sigProgress((frame * layers.size() + idx) * 100 /
// (frameCount * layers.size()));
}
retval = setLayer(layer, importDoc.data(), path);
layer->last = field;
layer->frame = frame;
}
}
frame++;
break;
}
} while (retval == KisImageBuilder_RESULT_OK);
//finish the layers
if (retval == KisImageBuilder_RESULT_OK) {
if (m_image) {
KisImageAnimationInterface *animation = m_image->animationInterface();
if (frame > frameCount)
frameCount = frame;
animation->setFullClipRange(KisTimeRange::fromTime(0,frameCount - 1));
animation->setFramerate((int)framerate);
}
for (idx = 0; idx < layers.size(); idx++) {
CSVLayerRecord* layer = layers.at(idx);
//empty layers without any pictures are dropped
if ((layer->frame > 0) || !layer->last.isEmpty()) {
retval = setLayer(layer, importDoc.data(), path);
if (retval != KisImageBuilder_RESULT_OK)
break;
}
}
}
if (m_image) {
//insert the existing layers by the right order
for (idx = layers.size() - 1; idx >= 0; idx--) {
CSVLayerRecord* layer = layers.at(idx);
if (layer->layer) {
m_image->addNode(layer->layer, m_image->root());
}
}
m_image->unlock();
}
qDeleteAll(layers);
io->close();
if (!m_batchMode) {
// disconnect(m_doc, SIGNAL(sigProgressCanceled()), this, SLOT(cancel()));
// emit m_doc->sigProgress(100);
if (!setView) {
QStatusBar *sb = KisPart::instance()->currentMainwindow()->statusBar();
if (sb) {
sb->clearMessage();
}
} else {
emit m_doc->clearStatusBarMessage();
}
}
QApplication::restoreOverrideCursor();
return retval;
}
QString CSVLoader::convertBlending(const QString &blending)
{
if (blending == "Color") return COMPOSITE_OVER;
if (blending == "Behind") return COMPOSITE_BEHIND;
if (blending == "Erase") return COMPOSITE_ERASE;
// "Shade"
if (blending == "Light") return COMPOSITE_LINEAR_LIGHT;
if (blending == "Colorize") return COMPOSITE_COLORIZE;
if (blending == "Hue") return COMPOSITE_HUE;
if (blending == "Add") return COMPOSITE_ADD;
if (blending == "Sub") return COMPOSITE_INVERSE_SUBTRACT;
if (blending == "Multiply") return COMPOSITE_MULT;
if (blending == "Screen") return COMPOSITE_SCREEN;
// "Replace"
// "Substitute"
if (blending == "Difference") return COMPOSITE_DIFF;
if (blending == "Divide") return COMPOSITE_DIVIDE;
if (blending == "Overlay") return COMPOSITE_OVERLAY;
if (blending == "Light2") return COMPOSITE_DODGE;
if (blending == "Shade2") return COMPOSITE_BURN;
if (blending == "HardLight") return COMPOSITE_HARD_LIGHT;
if (blending == "SoftLight") return COMPOSITE_SOFT_LIGHT_PHOTOSHOP;
if (blending == "GrainExtract") return COMPOSITE_GRAIN_EXTRACT;
if (blending == "GrainMerge") return COMPOSITE_GRAIN_MERGE;
if (blending == "Sub2") return COMPOSITE_SUBTRACT;
if (blending == "Darken") return COMPOSITE_DARKEN;
if (blending == "Lighten") return COMPOSITE_LIGHTEN;
if (blending == "Saturation") return COMPOSITE_SATURATION;
return COMPOSITE_OVER;
}
QString CSVLoader::validPath(const QString &path,const QString &base)
{
//replace Windows directory separators with the universal /
QString tryPath= QString(path).replace(QString("\\"), QString("/"));
int i = tryPath.lastIndexOf("/");
if (i == (tryPath.size() - 1))
tryPath= tryPath.left(i); //remove the ending separator if exists
if (QFileInfo(tryPath).isDir())
return tryPath.append("/");
QString scan(tryPath);
i = -1;
while ((i= (scan.lastIndexOf("/",i) - 1)) > 0) {
//avoid testing if the next level will be the default xxxx.layers folder
if ((i >= 6) && (scan.mid(i - 6, 7) == ".layers")) continue;
tryPath= QString(base).append(scan.mid(i + 2)); //base already ending with a /
if (QFileInfo(tryPath).isDir())
return tryPath.append("/");
}
return QString(); //NULL string
}
KisImageBuilder_Result CSVLoader::setLayer(CSVLayerRecord* layer, KisDocument *importDoc, const QString &path)
{
bool result = true;
if (layer->channel == 0) {
//create a new document layer
float opacity = layer->density;
if (opacity > 1.0)
opacity = 1.0;
else if (opacity < 0.0)
opacity = 0.0;
const KoColorSpace* cs = m_image->colorSpace();
const QString layerName = (layer->name).isEmpty() ? m_image->nextLayerName() : layer->name;
KisPaintLayer* paintLayer = new KisPaintLayer(m_image, layerName,
(quint8)(opacity * OPACITY_OPAQUE_U8), cs);
paintLayer->setCompositeOpId(convertBlending(layer->blending));
paintLayer->setVisible(layer->visible);
paintLayer->enableAnimation();
layer->layer = paintLayer;
layer->channel = qobject_cast
(paintLayer->getKeyframeChannel(KisKeyframeChannel::Content.id(), true));
}
if (!layer->last.isEmpty()) {
//png image
QString filename = layer->path.isNull() ? path : layer->path;
filename.append(layer->last);
result = importDoc->openUrl(QUrl::fromLocalFile(filename),
KisDocument::DontAddToRecent);
if (result)
layer->channel->importFrame(layer->frame, importDoc->image()->projection(), 0);
} else {
//blank
layer->channel->addKeyframe(layer->frame);
}
return (result) ? KisImageBuilder_RESULT_OK : KisImageBuilder_RESULT_FAILURE;
}
KisImageBuilder_Result CSVLoader::createNewImage(int width, int height, float ratio, const QString &name)
{
//the CSV is RGBA 8bits, sRGB
if (!m_image) {
const KoColorSpace* cs = KoColorSpaceRegistry::instance()->colorSpace(
RGBAColorModelID.id(), Integer8BitsColorDepthID.id(), 0);
if (cs) m_image = new KisImage(m_doc->createUndoStore(), width, height, cs, name);
if (!m_image) return KisImageBuilder_RESULT_FAILURE;
m_image->setResolution(ratio, 1.0);
m_image->lock();
}
return KisImageBuilder_RESULT_OK;
}
KisImageBuilder_Result CSVLoader::buildAnimation(QIODevice *io, const QString &filename)
{
return decode(io, filename);
}
KisImageSP CSVLoader::image()
{
return m_image;
}
void CSVLoader::cancel()
{
m_stop = true;
}
diff --git a/plugins/impex/csv/csv_saver.cpp b/plugins/impex/csv/csv_saver.cpp
index 073c5ef121..40925974ab 100644
--- a/plugins/impex/csv/csv_saver.cpp
+++ b/plugins/impex/csv/csv_saver.cpp
@@ -1,479 +1,479 @@
/*
* Copyright (c) 2016 Laszlo Fazekas
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#include "csv_saver.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 "csv_layer_record.h"
CSVSaver::CSVSaver(KisDocument *doc, bool batchMode)
: m_image(doc->savingImage())
, m_doc(doc)
, m_batchMode(batchMode)
, m_stop(false)
{
}
CSVSaver::~CSVSaver()
{
}
KisImageSP CSVSaver::image()
{
return m_image;
}
KisImageBuilder_Result CSVSaver::encode(QIODevice *io)
{
int idx;
int start, end;
KisNodeSP node;
QByteArray ba;
KisKeyframeSP keyframe;
QVector layers;
KisImageAnimationInterface *animation = m_image->animationInterface();
QApplication::setOverrideCursor(QCursor(Qt::WaitCursor));
// XXX: Stream was unused?
// //DataStream instead of TextStream for correct line endings
// QDataStream stream(&f);
//Using the original local path
QString path = m_doc->localFilePath();
if (path.right(4).toUpper() == ".CSV")
path = path.left(path.size() - 4);
else {
// something is wrong: the local file name is not .csv!
// trying the given (probably temporary) filename as well
KIS_SAFE_ASSERT_RECOVER(0 && "Wrong extension of the saved file!") {
path = path.left(path.size() - 4);
}
}
path.append(".frames");
//create directory
QDir dir(path);
if (!dir.exists()) {
dir.mkpath(".");
}
//according to the QT docs, the slash is a universal directory separator
path.append("/");
node = m_image->rootLayer()->firstChild();
//TODO: correct handling of the layer tree.
//for now, only top level paint layers are saved
idx = 0;
while (node) {
if (node->inherits("KisLayer")) {
KisLayer* paintLayer = qobject_cast(node.data());
CSVLayerRecord* layerRecord = new CSVLayerRecord();
layers.prepend(layerRecord); //reverse order!
layerRecord->name = paintLayer->name();
layerRecord->name.replace(QRegExp("[\"\\r\\n]"), "_");
if (layerRecord->name.isEmpty())
layerRecord->name= QString("Unnamed-%1").arg(idx);
layerRecord->visible = (paintLayer->visible()) ? 1 : 0;
layerRecord->density = (float)(paintLayer->opacity()) / OPACITY_OPAQUE_U8;
layerRecord->blending = convertToBlending(paintLayer->compositeOpId());
layerRecord->layer = paintLayer;
layerRecord->channel = paintLayer->original()->keyframeChannel();
layerRecord->last = "";
layerRecord->frame = 0;
idx++;
}
node = node->nextSibling();
}
KisTimeRange range = animation->fullClipRange();
start = (range.isValid()) ? range.start() : 0;
if (!range.isInfinite()) {
end = range.end();
if (end < start) end = start;
} else {
//undefined length, searching for the last keyframe
end = start;
for (idx = 0; idx < layers.size(); idx++) {
KisRasterKeyframeChannel *channel = layers.at(idx)->channel;
if (channel) {
keyframe = channel->lastKeyframe();
if ( (!keyframe.isNull()) && (keyframe->time() > end) )
end = keyframe->time();
}
}
}
//create temporary doc for exporting
QScopedPointer exportDoc(KisPart::instance()->createDocument());
createTempImage(exportDoc.data());
KisImageBuilder_Result retval= KisImageBuilder_RESULT_OK;
if (!m_batchMode) {
// TODO: use other systems of progress reporting (KisViewManager::createUnthreadedUpdater()
//emit m_doc->statusBarMessage(i18n("Saving CSV file..."));
//emit m_doc->sigProgress(0);
//connect(m_doc, SIGNAL(sigProgressCanceled()), this, SLOT(cancel()));
}
int frame = start;
int step = 0;
do {
qApp->processEvents();
if (m_stop) {
retval = KisImageBuilder_RESULT_CANCEL;
break;
}
switch(step) {
case 0 : //first row
if (io->write("UTF-8, TVPaint, \"CSV 1.0\"\r\n") < 0) {
retval = KisImageBuilder_RESULT_FAILURE;
}
break;
case 1 : //scene header names
if (io->write("Project Name, Width, Height, Frame Count, Layer Count, Frame Rate, Pixel Aspect Ratio, Field Mode\r\n") < 0) {
retval = KisImageBuilder_RESULT_FAILURE;
}
break;
case 2 : //scene header values
ba = QString("\"%1\", ").arg(m_image->objectName()).toUtf8();
if (io->write(ba.data()) < 0) {
retval = KisImageBuilder_RESULT_FAILURE;
break;
}
ba = QString("%1, %2, ").arg(m_image->width()).arg(m_image->height()).toUtf8();
if (io->write(ba.data()) < 0) {
retval = KisImageBuilder_RESULT_FAILURE;
break;
}
ba = QString("%1, %2, ").arg(end - start + 1).arg(layers.size()).toUtf8();
if (io->write(ba.data()) < 0) {
retval = KisImageBuilder_RESULT_FAILURE;
break;
}
//the framerate is an integer here
ba = QString("%1, ").arg((double)(animation->framerate()),0,'f',6).toUtf8();
if (io->write(ba.data()) < 0) {
retval = KisImageBuilder_RESULT_FAILURE;
break;
}
ba = QString("%1, Progressive\r\n").arg((double)(m_image->xRes() / m_image->yRes()),0,'f',6).toUtf8();
if (io->write(ba.data()) < 0) {
retval = KisImageBuilder_RESULT_FAILURE;
break;
}
break;
case 3 : //layer header values
if (io->write("#Layers") < 0) { //Layers
retval = KisImageBuilder_RESULT_FAILURE;
break;
}
for (idx = 0; idx < layers.size(); idx++) {
ba = QString(", \"%1\"").arg(layers.at(idx)->name).toUtf8();
if (io->write(ba.data()) < 0)
break;
}
break;
case 4 :
if (io->write("\r\n#Density") < 0) { //Density
retval = KisImageBuilder_RESULT_FAILURE;
break;
}
for (idx = 0; idx < layers.size(); idx++) {
ba = QString(", %1").arg((double)(layers.at(idx)->density), 0, 'f', 6).toUtf8();
if (io->write(ba.data()) < 0)
break;
}
break;
case 5 :
if (io->write("\r\n#Blending") < 0) { //Blending
retval = KisImageBuilder_RESULT_FAILURE;
break;
}
for (idx = 0; idx < layers.size(); idx++) {
ba = QString(", \"%1\"").arg(layers.at(idx)->blending).toUtf8();
if (io->write(ba.data()) < 0)
break;
}
break;
case 6 :
if (io->write("\r\n#Visible") < 0) { //Visible
retval = KisImageBuilder_RESULT_FAILURE;
break;
}
for (idx = 0; idx < layers.size(); idx++) {
ba = QString(", %1").arg(layers.at(idx)->visible).toUtf8();
if (io->write(ba.data()) < 0)
break;
}
if (idx < layers.size()) {
retval = KisImageBuilder_RESULT_FAILURE;
}
break;
default : //frames
if (frame > end) {
if (io->write("\r\n") < 0)
retval = KisImageBuilder_RESULT_FAILURE;
step = 8;
break;
}
ba = QString("\r\n#%1").arg(frame, 5, 10, QChar('0')).toUtf8();
if (io->write(ba.data()) < 0) {
retval = KisImageBuilder_RESULT_FAILURE;
break;
}
for (idx = 0; idx < layers.size(); idx++) {
CSVLayerRecord *layer = layers.at(idx);
KisRasterKeyframeChannel *channel = layer->channel;
if (channel) {
if (frame == start) {
keyframe = channel->activeKeyframeAt(frame);
} else {
keyframe = channel->keyframeAt(frame);
}
} else {
keyframe.clear(); // without animation
}
if ( !keyframe.isNull() || (frame == start) ) {
if (!m_batchMode) {
//emit m_doc->sigProgress(((frame - start) * layers.size() + idx) * 100 /
// ((end - start) * layers.size()));
}
retval = getLayer(layer, exportDoc.data(), keyframe, path, frame, idx);
if (retval != KisImageBuilder_RESULT_OK)
break;
}
ba = QString(", \"%1\"").arg(layer->last).toUtf8();
if (io->write(ba.data()) < 0)
break;
}
if (idx < layers.size())
retval = KisImageBuilder_RESULT_FAILURE;
frame++;
step = 6; //keep step here
break;
}
step++;
} while((retval == KisImageBuilder_RESULT_OK) && (step < 8));
qDeleteAll(layers);
// io->close(); it seems this is not required anymore
if (!m_batchMode) {
//disconnect(m_doc, SIGNAL(sigProgressCanceled()), this, SLOT(cancel()));
//emit m_doc->sigProgress(100);
//emit m_doc->clearStatusBarMessage();
}
QApplication::restoreOverrideCursor();
return retval;
}
QString CSVSaver::convertToBlending(const QString &opid)
{
if (opid == COMPOSITE_OVER) return "Color";
if (opid == COMPOSITE_BEHIND) return "Behind";
if (opid == COMPOSITE_ERASE) return "Erase";
// "Shade"
if (opid == COMPOSITE_LINEAR_LIGHT) return "Light";
if (opid == COMPOSITE_COLORIZE) return "Colorize";
if (opid == COMPOSITE_HUE) return "Hue";
if ((opid == COMPOSITE_ADD) ||
(opid == COMPOSITE_LINEAR_DODGE)) return "Add";
if (opid == COMPOSITE_INVERSE_SUBTRACT) return "Sub";
if (opid == COMPOSITE_MULT) return "Multiply";
if (opid == COMPOSITE_SCREEN) return "Screen";
// "Replace"
// "Substitute"
if (opid == COMPOSITE_DIFF) return "Difference";
if (opid == COMPOSITE_DIVIDE) return "Divide";
if (opid == COMPOSITE_OVERLAY) return "Overlay";
if (opid == COMPOSITE_DODGE) return "Light2";
if (opid == COMPOSITE_BURN) return "Shade2";
if (opid == COMPOSITE_HARD_LIGHT) return "HardLight";
if ((opid == COMPOSITE_SOFT_LIGHT_PHOTOSHOP) ||
(opid == COMPOSITE_SOFT_LIGHT_SVG)) return "SoftLight";
if (opid == COMPOSITE_GRAIN_EXTRACT) return "GrainExtract";
if (opid == COMPOSITE_GRAIN_MERGE) return "GrainMerge";
if (opid == COMPOSITE_SUBTRACT) return "Sub2";
if (opid == COMPOSITE_DARKEN) return "Darken";
if (opid == COMPOSITE_LIGHTEN) return "Lighten";
if (opid == COMPOSITE_SATURATION) return "Saturation";
return "Color";
}
KisImageBuilder_Result CSVSaver::getLayer(CSVLayerRecord* layer, KisDocument* exportDoc, KisKeyframeSP keyframe, const QString &path, int frame, int idx)
{
//render to the temp layer
KisImageSP image = exportDoc->savingImage();
if (!image) image= exportDoc->image();
KisPaintDeviceSP device = image->rootLayer()->firstChild()->projection();
if (!keyframe.isNull()) {
layer->channel->fetchFrame(keyframe, device);
} else {
device->makeCloneFrom(layer->layer->projection(),image->bounds()); // without animation
}
QRect bounds = device->exactBounds();
if (bounds.isEmpty()) {
layer->last = ""; //empty frame
return KisImageBuilder_RESULT_OK;
}
layer->last = QString("frame%1-%2.png").arg(idx + 1,5,10,QChar('0')).arg(frame,5,10,QChar('0'));
QString filename = path;
filename.append(layer->last);
//save to PNG
KisSequentialConstIterator it(device, image->bounds());
const KoColorSpace* cs = device->colorSpace();
bool isThereAlpha = false;
while (it.nextPixel()) {
if (cs->opacityU8(it.oldRawData()) != OPACITY_OPAQUE_U8) {
isThereAlpha = true;
break;
}
}
if (!KisPNGConverter::isColorSpaceSupported(cs)) {
device = new KisPaintDevice(*device.data());
KUndo2Command *cmd= device->convertTo(KoColorSpaceRegistry::instance()->rgb8());
delete cmd;
}
KisPNGOptions options;
options.alpha = isThereAlpha;
options.interlace = false;
options.compression = 8;
options.tryToSaveAsIndexed = false;
options.transparencyFillColor = QColor(0,0,0);
options.saveSRGBProfile = true; //TVPaint can use only sRGB
options.forceSRGB = false;
KisPNGConverter kpc(exportDoc);
KisImageBuilder_Result result = kpc.buildFile(filename, image->bounds(),
image->xRes(), image->yRes(), device,
image->beginAnnotations(), image->endAnnotations(),
options, (KisMetaData::Store* )0 );
return result;
}
void CSVSaver::createTempImage(KisDocument* exportDoc)
{
- exportDoc->setAutoSaveDelay(0);
+ exportDoc->setInfiniteAutoSaveInterval();
exportDoc->setFileBatchMode(true);
KisImageSP exportImage = new KisImage(exportDoc->createUndoStore(),
m_image->width(), m_image->height(), m_image->colorSpace(),
QString());
exportImage->setResolution(m_image->xRes(), m_image->yRes());
exportDoc->setCurrentImage(exportImage);
KisPaintLayer* paintLayer = new KisPaintLayer(exportImage, "paint device", OPACITY_OPAQUE_U8);
exportImage->addNode(paintLayer, exportImage->rootLayer(), KisLayerSP(0));
}
KisImageBuilder_Result CSVSaver::buildAnimation(QIODevice *io)
{
if (!m_image) {
return KisImageBuilder_RESULT_EMPTY;
}
return encode(io);
}
void CSVSaver::cancel()
{
m_stop = true;
}