diff --git a/libs/flake/text/KoSvgTextChunkShape.cpp b/libs/flake/text/KoSvgTextChunkShape.cpp index fde66bb73c..8b760d2b2c 100644 --- a/libs/flake/text/KoSvgTextChunkShape.cpp +++ b/libs/flake/text/KoSvgTextChunkShape.cpp @@ -1,985 +1,983 @@ /* * 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 v * 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 "KoSvgTextChunkShape.h" #include "KoSvgTextChunkShape_p.h" #include "KoSvgText.h" #include "KoSvgTextProperties.h" #include "kis_debug.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace { void appendLazy(QVector *list, boost::optional value, int iteration, bool hasDefault = true, qreal defaultValue = 0.0) { if (!value) return; if (value && *value == defaultValue && hasDefault == true && list->isEmpty()) return; while (list->size() < iteration) { list->append(defaultValue); } list->append(*value); } void fillTransforms(QVector *xPos, QVector *yPos, QVector *dxPos, QVector *dyPos, QVector *rotate, QVector localTransformations) { for (int i = 0; i < localTransformations.size(); i++) { const KoSvgText::CharTransformation &t = localTransformations[i]; appendLazy(xPos, t.xPos, i, false); appendLazy(yPos, t.yPos, i, false); appendLazy(dxPos, t.dxPos, i); appendLazy(dyPos, t.dyPos, i); appendLazy(rotate, t.rotate, i); } } QVector parseListAttributeX(const QString &value, SvgLoadingContext &context) { QVector result; QStringList list = SvgUtil::simplifyList(value); Q_FOREACH (const QString &str, list) { result << SvgUtil::parseUnitX(context.currentGC(), str); } return result; } QVector parseListAttributeY(const QString &value, SvgLoadingContext &context) { QVector result; QStringList list = SvgUtil::simplifyList(value); Q_FOREACH (const QString &str, list) { result << SvgUtil::parseUnitY(context.currentGC(), str); } return result; } QVector parseListAttributeAngular(const QString &value, SvgLoadingContext &context) { QVector result; QStringList list = SvgUtil::simplifyList(value); Q_FOREACH (const QString &str, list) { result << SvgUtil::parseUnitAngular(context.currentGC(), str); } return result; } QString convertListAttribute(const QVector &values) { QStringList stringValues; Q_FOREACH (qreal value, values) { stringValues.append(KisDomUtils::toString(value)); } return stringValues.join(','); } } struct KoSvgTextChunkShape::Private::LayoutInterface : public KoSvgTextChunkShapeLayoutInterface { LayoutInterface(KoSvgTextChunkShape *_q) : q(_q) {} KoSvgText::AutoValue textLength() const override { return q->s->textLength; } KoSvgText::LengthAdjust lengthAdjust() const override { return q->s->lengthAdjust; } int numChars() const override { KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(!q->shapeCount() || q->s->text.isEmpty(), 0); int result = 0; if (!q->shapeCount()) { result = q->s->text.size(); } else { Q_FOREACH (KoShape *shape, q->shapes()) { KoSvgTextChunkShape *chunkShape = dynamic_cast(shape); KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(chunkShape, 0); result += chunkShape->layoutInterface()->numChars(); } } return result; } int relativeCharPos(KoSvgTextChunkShape *child, int pos) const override { QList childShapes = q->shapes(); int result = -1; int numCharsPassed = 0; Q_FOREACH (KoShape *shape, q->shapes()) { KoSvgTextChunkShape *chunkShape = dynamic_cast(shape); KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(chunkShape, 0); if (chunkShape == child) { result = pos + numCharsPassed; break; } else { numCharsPassed += chunkShape->layoutInterface()->numChars(); } } return result; } bool isTextNode() const override { KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(!q->shapeCount() || q->s->text.isEmpty(), false); return !q->shapeCount(); } QString nodeText() const override { KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(!q->shapeCount() || q->s->text.isEmpty(), 0); return !q->shapeCount() ? q->s->text : QString(); } QVector localCharTransformations() const override { KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(isTextNode(), QVector()); const QVector t = q->s->localTransformations; return t.mid(0, qMin(t.size(), q->s->text.size())); } static QString getBidiOpening(KoSvgText::Direction direction, KoSvgText::UnicodeBidi bidi) { using namespace KoSvgText; QString result; if (bidi == BidiEmbed) { result = direction == DirectionLeftToRight ? "\u202a" : "\u202b"; } else if (bidi == BidiOverride) { result = direction == DirectionLeftToRight ? "\u202d" : "\u202e"; } return result; } QVector collectSubChunks() const override { QVector result; if (isTextNode()) { const QString text = q->s->text; const KoSvgText::KoSvgCharChunkFormat format = q->fetchCharFormat(); QVector transforms = q->s->localTransformations; /** * Sometimes SVG can contain the X,Y offsets for the pieces of text that * do not exist, just skip them. */ if (text.size() <= transforms.size()) { transforms.resize(text.size()); } KoSvgText::UnicodeBidi bidi = KoSvgText::UnicodeBidi(q->s->properties.propertyOrDefault(KoSvgTextProperties::UnicodeBidiId).toInt()); KoSvgText::Direction direction = KoSvgText::Direction(q->s->properties.propertyOrDefault(KoSvgTextProperties::DirectionId).toInt()); const QString bidiOpening = getBidiOpening(direction, bidi); if (!bidiOpening.isEmpty()) { result << SubChunk(bidiOpening, format); } if (transforms.isEmpty()) { result << SubChunk(text, format); } else { for (int i = 0; i < transforms.size(); i++) { const KoSvgText::CharTransformation baseTransform = transforms[i]; int subChunkLength = 1; for (int j = i + 1; j < transforms.size(); j++) { if (transforms[j].isNull()) { subChunkLength++; } else { break; } } if (i + subChunkLength >= transforms.size()) { subChunkLength = text.size() - i; } result << SubChunk(text.mid(i, subChunkLength), format, baseTransform); i += subChunkLength - 1; } } if (!bidiOpening.isEmpty()) { result << SubChunk("\u202c", format); } } else { Q_FOREACH (KoShape *shape, q->shapes()) { KoSvgTextChunkShape *chunkShape = dynamic_cast(shape); KIS_SAFE_ASSERT_RECOVER_BREAK(chunkShape); result += chunkShape->layoutInterface()->collectSubChunks(); } } return result; } void addAssociatedOutline(const QRectF &rect) override { KIS_SAFE_ASSERT_RECOVER_RETURN(isTextNode()); QPainterPath path; path.addRect(rect); path |= q->s->associatedOutline; path.setFillRule(Qt::WindingFill); path = path.simplified(); q->s->associatedOutline = path; q->setSize(path.boundingRect().size()); q->notifyChanged(); q->shapeChangedPriv(KoShape::SizeChanged); } void clearAssociatedOutline() override { q->s->associatedOutline = QPainterPath(); q->setSize(QSizeF()); q->notifyChanged(); q->shapeChangedPriv(KoShape::SizeChanged); } private: KoSvgTextChunkShape *q; }; KoSvgTextChunkShape::KoSvgTextChunkShape() : KoShapeContainer() , d(new Private) , s(new SharedData) { d->layoutInterface.reset(new KoSvgTextChunkShape::Private::LayoutInterface(this)); } KoSvgTextChunkShape::KoSvgTextChunkShape(const KoSvgTextChunkShape &rhs) : KoShapeContainer(rhs) , d(new Private) , s(rhs.s) { if (rhs.model()) { SimpleShapeContainerModel *otherModel = dynamic_cast(rhs.model()); KIS_ASSERT_RECOVER_RETURN(otherModel); setModelInit(new SimpleShapeContainerModel(*otherModel)); } d->layoutInterface.reset(new KoSvgTextChunkShape::Private::LayoutInterface(this)); } KoSvgTextChunkShape::~KoSvgTextChunkShape() { } KoShape *KoSvgTextChunkShape::cloneShape() const { return new KoSvgTextChunkShape(*this); } QSizeF KoSvgTextChunkShape::size() const { return outlineRect().size(); } void KoSvgTextChunkShape::setSize(const QSizeF &size) { Q_UNUSED(size); // we do not support resizing! } QRectF KoSvgTextChunkShape::outlineRect() const { return outline().boundingRect(); } QPainterPath KoSvgTextChunkShape::outline() const { QPainterPath result; result.setFillRule(Qt::WindingFill); if (d->layoutInterface->isTextNode()) { result = s->associatedOutline; } else { Q_FOREACH (KoShape *shape, shapes()) { KoSvgTextChunkShape *chunkShape = dynamic_cast(shape); KIS_SAFE_ASSERT_RECOVER_BREAK(chunkShape); result |= chunkShape->outline(); } } return result.simplified(); } void KoSvgTextChunkShape::paintComponent(QPainter &painter, KoShapePaintingContext &paintContext) const { Q_UNUSED(painter); Q_UNUSED(paintContext); } void KoSvgTextChunkShape::saveOdf(KoShapeSavingContext &context) const { Q_UNUSED(context); } bool KoSvgTextChunkShape::loadOdf(const KoXmlElement &element, KoShapeLoadingContext &context) { Q_UNUSED(element); Q_UNUSED(context); return false; } bool KoSvgTextChunkShape::saveHtml(HtmlSavingContext &context) { // Should we add a newline? Check for vertical movement if we're using rtl or ltr text // XXX: if vertical text, check horizontal movement. QVector xPos; QVector yPos; QVector dxPos; QVector dyPos; QVector rotate; fillTransforms(&xPos, &yPos, &dxPos, &dyPos, &rotate, s->localTransformations); for (int i = 0; i < s->localTransformations.size(); i++) { const KoSvgText::CharTransformation &t = s->localTransformations[i]; appendLazy(&xPos, t.xPos, i, false); appendLazy(&yPos, t.yPos, i, false); appendLazy(&dxPos, t.dxPos, i); appendLazy(&dyPos, t.dyPos, i); } KoSvgTextChunkShape *parent = !isRootTextNode() ? dynamic_cast(this->parent()) : 0; KoSvgTextProperties parentProperties = parent ? parent->textProperties() : KoSvgTextProperties::defaultProperties(); // XXX: we don't save fill, stroke, text length, length adjust or spacing and glyphs. KoSvgTextProperties ownProperties = textProperties().ownProperties(parentProperties); if (isRootTextNode()) { context.shapeWriter().startElement("body", false); if (layoutInterface()->isTextNode()) { context.shapeWriter().startElement("p", false); } // XXX: Save the style? } else if (parent && parent->isRootTextNode()) { context.shapeWriter().startElement("p", false); } else { context.shapeWriter().startElement("span", false); // XXX: Save the style? } QMap attributes = ownProperties.convertToSvgTextAttributes(); if (attributes.size() > 0) { QString styleString; for (auto it = attributes.constBegin(); it != attributes.constEnd(); ++it) { if (QString(it.key().toLatin1().data()).contains("text-anchor")) { QString val = it.value(); if (it.value()=="middle") { val = "center"; } else if (it.value()=="end") { val = "right"; } else { val = "left"; } styleString.append("text-align") .append(": ") .append(val) .append(";" ); } else if (QString(it.key().toLatin1().data()).contains("fill")){ styleString.append("color") .append(": ") .append(it.value()) .append(";" ); } else if (QString(it.key().toLatin1().data()).contains("font-size")){ QString val = it.value(); if (QRegExp ("\\d*").exactMatch(val)) { val.append("pt"); } styleString.append(it.key().toLatin1().data()) .append(": ") .append(val) .append(";" ); } else { styleString.append(it.key().toLatin1().data()) .append(": ") .append(it.value()) .append(";" ); } } context.shapeWriter().addAttribute("style", styleString); } if (layoutInterface()->isTextNode()) { debugFlake << "saveHTML" << this << s->text << xPos << yPos << dxPos << dyPos; // After adding all the styling to the

element, add the text context.shapeWriter().addTextNode(s->text); } else { Q_FOREACH (KoShape *child, this->shapes()) { KoSvgTextChunkShape *childText = dynamic_cast(child); KIS_SAFE_ASSERT_RECOVER(childText) { continue; } childText->saveHtml(context); } } if (isRootTextNode() && layoutInterface()->isTextNode()) { context.shapeWriter().endElement(); // body } context.shapeWriter().endElement(); // p or span return true; } void writeTextListAttribute(const QString &attribute, const QVector &values, KoXmlWriter &writer) { const QString value = convertListAttribute(values); if (!value.isEmpty()) { writer.addAttribute(attribute.toLatin1().data(), value); } } bool KoSvgTextChunkShape::saveSvg(SvgSavingContext &context) { if (isRootTextNode()) { context.shapeWriter().startElement("text", false); if (!context.strippedTextMode()) { context.shapeWriter().addAttribute("id", context.getID(this)); context.shapeWriter().addAttribute("krita:useRichText", s->isRichTextPreferred ? "true" : "false"); SvgUtil::writeTransformAttributeLazy("transform", transformation(), context.shapeWriter()); SvgStyleWriter::saveSvgStyle(this, context); } else { SvgStyleWriter::saveSvgFill(this, context); SvgStyleWriter::saveSvgStroke(this, context); } } else { context.shapeWriter().startElement("tspan", false); if (!context.strippedTextMode()) { SvgStyleWriter::saveSvgBasicStyle(this, context); } } if (layoutInterface()->isTextNode()) { QVector xPos; QVector yPos; QVector dxPos; QVector dyPos; QVector rotate; fillTransforms(&xPos, &yPos, &dxPos, &dyPos, &rotate, s->localTransformations); writeTextListAttribute("x", xPos, context.shapeWriter()); writeTextListAttribute("y", yPos, context.shapeWriter()); writeTextListAttribute("dx", dxPos, context.shapeWriter()); writeTextListAttribute("dy", dyPos, context.shapeWriter()); writeTextListAttribute("rotate", rotate, context.shapeWriter()); } if (!s->textLength.isAuto) { context.shapeWriter().addAttribute("textLength", KisDomUtils::toString(s->textLength.customValue)); if (s->lengthAdjust == KoSvgText::LengthAdjustSpacingAndGlyphs) { context.shapeWriter().addAttribute("lengthAdjust", "spacingAndGlyphs"); } } KoSvgTextChunkShape *parent = !isRootTextNode() ? dynamic_cast(this->parent()) : 0; KoSvgTextProperties parentProperties = parent ? parent->textProperties() : KoSvgTextProperties::defaultProperties(); KoSvgTextProperties ownProperties = textProperties().ownProperties(parentProperties); // we write down stroke/fill iff they are different from the parent's value if (!isRootTextNode()) { if (ownProperties.hasProperty(KoSvgTextProperties::FillId)) { SvgStyleWriter::saveSvgFill(this, context); } if (ownProperties.hasProperty(KoSvgTextProperties::StrokeId)) { SvgStyleWriter::saveSvgStroke(this, context); } } QMap attributes = ownProperties.convertToSvgTextAttributes(); for (auto it = attributes.constBegin(); it != attributes.constEnd(); ++it) { context.shapeWriter().addAttribute(it.key().toLatin1().data(), it.value()); } if (layoutInterface()->isTextNode()) { context.shapeWriter().addTextNode(s->text); } else { Q_FOREACH (KoShape *child, this->shapes()) { KoSvgTextChunkShape *childText = dynamic_cast(child); KIS_SAFE_ASSERT_RECOVER(childText) { continue; } childText->saveSvg(context); } } context.shapeWriter().endElement(); return true; } void KoSvgTextChunkShape::SharedData::loadContextBasedProperties(SvgGraphicsContext *gc) { properties = gc->textProperties; font = gc->font; fontFamiliesList = gc->fontFamiliesList; } void KoSvgTextChunkShape::resetTextShape() { using namespace KoSvgText; s->properties = KoSvgTextProperties(); s->font = QFont(); s->fontFamiliesList = QStringList(); s->textLength = AutoValue(); s->lengthAdjust = LengthAdjustSpacing; s->localTransformations.clear(); s->text.clear(); // all the subchunks are destroyed! // (first detach, then destroy) QList shapesToReset = shapes(); Q_FOREACH (KoShape *shape, shapesToReset) { shape->setParent(0); delete shape; } } bool KoSvgTextChunkShape::loadSvg(const KoXmlElement &e, SvgLoadingContext &context) { SvgGraphicsContext *gc = context.currentGC(); KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(gc, false); s->loadContextBasedProperties(gc); s->textLength = KoSvgText::parseAutoValueXY(e.attribute("textLength", ""), context, ""); s->lengthAdjust = KoSvgText::parseLengthAdjust(e.attribute("lengthAdjust", "spacing")); QVector xPos = parseListAttributeX(e.attribute("x", ""), context); QVector yPos = parseListAttributeY(e.attribute("y", ""), context); QVector dxPos = parseListAttributeX(e.attribute("dx", ""), context); QVector dyPos = parseListAttributeY(e.attribute("dy", ""), context); QVector rotate = parseListAttributeAngular(e.attribute("rotate", ""), context); const int numLocalTransformations = std::max({xPos.size(), yPos.size(), dxPos.size(), dyPos.size(), rotate.size()}); s->localTransformations.resize(numLocalTransformations); for (int i = 0; i < numLocalTransformations; i++) { if (i < xPos.size()) { s->localTransformations[i].xPos = xPos[i]; } if (i < yPos.size()) { s->localTransformations[i].yPos = yPos[i]; } if (i < dxPos.size() && dxPos[i] != 0.0) { s->localTransformations[i].dxPos = dxPos[i]; } if (i < dyPos.size() && dyPos[i] != 0.0) { s->localTransformations[i].dyPos = dyPos[i]; } if (i < rotate.size()) { s->localTransformations[i].rotate = rotate[i]; } } return true; } namespace { QString cleanUpString(QString text) { text.replace(QRegExp("[\\r\\n\u2028]"), ""); text.replace(QRegExp(" {2,}"), " "); return text; } enum Result { FoundNothing, FoundText, FoundSpace }; Result hasPreviousSibling(KoXmlNode node) { while (!node.isNull()) { if (node.isElement()) { KoXmlElement element = node.toElement(); if (element.tagName() == "text") break; } while (!node.previousSibling().isNull()) { node = node.previousSibling(); while (!node.lastChild().isNull()) { node = node.lastChild(); } if (node.isText()) { KoXmlText textNode = node.toText(); const QString text = cleanUpString(textNode.data()); if (!text.isEmpty()) { // if we are the leading whitespace, we should report that // we are the last if (text == " ") { return hasPreviousSibling(node) == FoundNothing ? FoundNothing : FoundSpace; } return text[text.size() - 1] != ' ' ? FoundText : FoundSpace; } } } node = node.parentNode(); } return FoundNothing; } Result hasNextSibling(KoXmlNode node) { while (!node.isNull()) { while (!node.nextSibling().isNull()) { node = node.nextSibling(); while (!node.firstChild().isNull()) { node = node.firstChild(); } if (node.isText()) { KoXmlText textNode = node.toText(); const QString text = cleanUpString(textNode.data()); // if we are the trailing whitespace, we should report that // we are the last if (text == " ") { return hasNextSibling(node) == FoundNothing ? FoundNothing : FoundSpace; } if (!text.isEmpty()) { return text[0] != ' ' ? FoundText : FoundSpace; } } } node = node.parentNode(); } return FoundNothing; } } bool KoSvgTextChunkShape::loadSvgTextNode(const KoXmlText &text, SvgLoadingContext &context) { SvgGraphicsContext *gc = context.currentGC(); KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(gc, false); s->loadContextBasedProperties(gc); QString data = cleanUpString(text.data()); const Result leftBorder = hasPreviousSibling(text); const Result rightBorder = hasNextSibling(text); if (data.startsWith(' ') && leftBorder == FoundNothing) { data.remove(0, 1); } if (data.endsWith(' ') && rightBorder != FoundText) { data.remove(data.size() - 1, 1); } if (data == " " && (leftBorder == FoundNothing || rightBorder == FoundNothing)) { data = ""; } //ENTER_FUNCTION() << text.data() << "-->" << data; s->text = data; return !data.isEmpty(); } void KoSvgTextChunkShape::normalizeCharTransformations() { applyParentCharTransformations(s->localTransformations); } void KoSvgTextChunkShape::simplifyFillStrokeInheritance() { if (!isRootTextNode()) { KoShape *parentShape = parent(); KIS_SAFE_ASSERT_RECOVER_RETURN(parentShape); QSharedPointer bg = background(); QSharedPointer parentBg = parentShape->background(); if (!inheritBackground() && ((!bg && !parentBg) || (bg && parentBg && bg->compareTo(parentShape->background().data())))) { setInheritBackground(true); } KoShapeStrokeModelSP stroke = this->stroke(); KoShapeStrokeModelSP parentStroke= parentShape->stroke(); if (!inheritStroke() && ((!stroke && !parentStroke) || (stroke && parentStroke && stroke->compareFillTo(parentShape->stroke().data()) && stroke->compareStyleTo(parentShape->stroke().data())))) { setInheritStroke(true); } } Q_FOREACH (KoShape *shape, shapes()) { KoSvgTextChunkShape *chunkShape = dynamic_cast(shape); KIS_SAFE_ASSERT_RECOVER_RETURN(chunkShape); chunkShape->simplifyFillStrokeInheritance(); } } KoSvgTextProperties KoSvgTextChunkShape::textProperties() const { KoSvgTextProperties properties = s->properties; properties.setProperty(KoSvgTextProperties::FillId, QVariant::fromValue(KoSvgText::BackgroundProperty(background()))); properties.setProperty(KoSvgTextProperties::StrokeId, QVariant::fromValue(KoSvgText::StrokeProperty(stroke()))); return properties; } bool KoSvgTextChunkShape::isTextNode() const { return d->layoutInterface->isTextNode(); } KoSvgTextChunkShapeLayoutInterface *KoSvgTextChunkShape::layoutInterface() const { return d->layoutInterface.data(); } bool KoSvgTextChunkShape::isRichTextPreferred() const { return isRootTextNode() && s->isRichTextPreferred; } void KoSvgTextChunkShape::setRichTextPreferred(bool value) { KIS_SAFE_ASSERT_RECOVER_RETURN(isRootTextNode()); s->isRichTextPreferred = value; } bool KoSvgTextChunkShape::isRootTextNode() const { return false; } /**************************************************************************************************/ /* KoSvgTextChunkShape::Private */ /**************************************************************************************************/ -#include "SimpleShapeContainerModel.h" - KoSvgTextChunkShape::SharedData::SharedData() : QSharedData() { } KoSvgTextChunkShape::SharedData::SharedData(const SharedData &rhs) : QSharedData() , properties(rhs.properties) , font(rhs.font) , fontFamiliesList(rhs.fontFamiliesList) , localTransformations(rhs.localTransformations) , textLength(rhs.textLength) , lengthAdjust(rhs.lengthAdjust) , text(rhs.text) , isRichTextPreferred(rhs.isRichTextPreferred) { } KoSvgTextChunkShape::SharedData::~SharedData() { } #include #include #include KoSvgText::KoSvgCharChunkFormat KoSvgTextChunkShape::fetchCharFormat() const { KoSvgText::KoSvgCharChunkFormat format; format.setFont(s->font); format.setTextAnchor(KoSvgText::TextAnchor(s->properties.propertyOrDefault(KoSvgTextProperties::TextAnchorId).toInt())); KoSvgText::Direction direction = KoSvgText::Direction(s->properties.propertyOrDefault(KoSvgTextProperties::DirectionId).toInt()); format.setLayoutDirection(direction == KoSvgText::DirectionLeftToRight ? Qt::LeftToRight : Qt::RightToLeft); KoSvgText::BaselineShiftMode shiftMode = KoSvgText::BaselineShiftMode(s->properties.propertyOrDefault(KoSvgTextProperties::BaselineShiftModeId).toInt()); // FIXME: we support only 'none', 'sub' and 'super' shifts at the moment. // Please implement 'percentage' as well! // WARNING!!! Qt's setVerticalAlignment() also changes the size of the font! And SVG does not(!) imply it! if (shiftMode == KoSvgText::ShiftSub) { format.setVerticalAlignment(QTextCharFormat::AlignSubScript); } else if (shiftMode == KoSvgText::ShiftSuper) { format.setVerticalAlignment(QTextCharFormat::AlignSuperScript); } KoSvgText::AutoValue letterSpacing = s->properties.propertyOrDefault(KoSvgTextProperties::LetterSpacingId).value(); if (!letterSpacing.isAuto) { format.setFontLetterSpacingType(QFont::AbsoluteSpacing); format.setFontLetterSpacing(letterSpacing.customValue); } KoSvgText::AutoValue wordSpacing = s->properties.propertyOrDefault(KoSvgTextProperties::WordSpacingId).value(); if (!wordSpacing.isAuto) { format.setFontWordSpacing(wordSpacing.customValue); } KoSvgText::AutoValue kerning = s->properties.propertyOrDefault(KoSvgTextProperties::KerningId).value(); if (!kerning.isAuto) { format.setFontKerning(false); format.setFontLetterSpacingType(QFont::AbsoluteSpacing); format.setFontLetterSpacing(format.fontLetterSpacing() + kerning.customValue); } QBrush textBrush = Qt::NoBrush; if (background()) { KoColorBackground *colorBackground = dynamic_cast(background().data()); if (!colorBackground) { qWarning() << "TODO: support gradient and pattern backgrounds for text"; textBrush = Qt::red; } if (colorBackground) { textBrush = colorBackground->brush(); } } format.setForeground(textBrush); QPen textPen = Qt::NoPen; if (stroke()) { KoShapeStroke *stroke = dynamic_cast(this->stroke().data()); if (stroke) { textPen = stroke->resultLinePen(); } } format.setTextOutline(textPen); // TODO: avoid const_cast somehow... format.setAssociatedShape(const_cast(this)); return format; } void KoSvgTextChunkShape::applyParentCharTransformations(const QVector transformations) { if (shapeCount()) { int numCharsPassed = 0; Q_FOREACH (KoShape *shape, shapes()) { KoSvgTextChunkShape *chunkShape = dynamic_cast(shape); KIS_SAFE_ASSERT_RECOVER_RETURN(chunkShape); const int numCharsInSubtree = chunkShape->layoutInterface()->numChars(); QVector t = transformations.mid(numCharsPassed, numCharsInSubtree); if (t.isEmpty()) break; chunkShape->applyParentCharTransformations(t); numCharsPassed += numCharsInSubtree; if (numCharsPassed >= transformations.size()) break; } } else { for (int i = 0; i < qMin(transformations.size(), s->text.size()); i++) { KIS_SAFE_ASSERT_RECOVER_RETURN(s->localTransformations.size() >= i); if (s->localTransformations.size() == i) { s->localTransformations.append(transformations[i]); } else { s->localTransformations[i].mergeInParentTransformation(transformations[i]); } } } } diff --git a/plugins/dockers/artisticcolorselector/kis_color_selector.cpp b/plugins/dockers/artisticcolorselector/kis_color_selector.cpp index 689353da82..e3290fe6a4 100644 --- a/plugins/dockers/artisticcolorselector/kis_color_selector.cpp +++ b/plugins/dockers/artisticcolorselector/kis_color_selector.cpp @@ -1,1186 +1,1185 @@ /* Copyright (C) 2011 Silvio Heinrich This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include //#include -#include #include "kis_color_selector.h" //#define DEBUG_ARC_SELECTOR KisColorSelector::KisColorSelector(QWidget* parent, KisColor::Type type) : QWidget(parent) , m_colorConverter(KisDisplayColorConverter::dumbConverterInstance()) , m_colorSpace(type) , m_inverseSaturation(false) , m_selectedColor(m_colorConverter) , m_fgColor(m_colorConverter) , m_bgColor(m_colorConverter) , m_clickedRing(-1) , m_gamutMaskOn(false) , m_currentGamutMask(nullptr) , m_maskPreviewActive(true) , m_gamutMaskViewTransform(QTransform()) , m_widgetUpdatesSelf(false) , m_isDirtyWheel(false) , m_isDirtyLightStrip(false) , m_isDirtyGamutMask(false) , m_isDirtyColorPreview(false) { // m_viewConverter = new KisGamutMaskViewConverter(); setLumaCoefficients(DEFAULT_LUMA_R, DEFAULT_LUMA_G, DEFAULT_LUMA_B,DEFAULT_LUMA_GAMMA); recalculateRings(DEFAULT_SATURATION_STEPS, DEFAULT_HUE_STEPS); recalculateAreas(DEFAULT_VALUE_SCALE_STEPS); selectColor(KisColor(Qt::red, m_colorConverter, KisColor::HSY, m_lumaR, m_lumaG, m_lumaB, m_lumaGamma)); using namespace std::placeholders; // For _1 placeholder auto function = std::bind(&KisColorSelector::slotUpdateColorAndPreview, this, _1); m_updateColorCompressor.reset(new ColorCompressorType(20 /* ms */, function)); } void KisColorSelector::setColorSpace(KisColor::Type type) { m_colorSpace = type; m_selectedColor = KisColor(m_selectedColor, m_colorConverter, m_colorSpace, m_lumaR, m_lumaG, m_lumaB, m_lumaGamma); m_isDirtyLightStrip = true; m_isDirtyWheel = true; #ifdef DEBUG_ARC_SELECTOR dbgPlugins << "KisColorSelector::setColorSpace: set to:" << m_colorSpace; #endif update(); } void KisColorSelector::setColorConverter(KisDisplayColorConverter *colorConverter) { m_colorConverter = colorConverter; m_selectedColor = KisColor(m_selectedColor, m_colorConverter, m_colorSpace, m_lumaR, m_lumaG, m_lumaB, m_lumaGamma); m_fgColor = KisColor(m_fgColor, m_colorConverter, m_colorSpace, m_lumaR, m_lumaG, m_lumaB, m_lumaGamma); m_bgColor = KisColor(m_bgColor, m_colorConverter, m_colorSpace, m_lumaR, m_lumaG, m_lumaB, m_lumaGamma); update(); } void KisColorSelector::setNumLightPieces(int num) { num = qBound(MIN_NUM_LIGHT_PIECES, num, MAX_NUM_LIGHT_PIECES); recalculateAreas(quint8(num)); if (m_selectedLightPiece >= 0) m_selectedLightPiece = getLightIndex(m_selectedColor.getX()); update(); } void KisColorSelector::setNumPieces(int num) { num = qBound(MIN_NUM_HUE_PIECES, num, MAX_NUM_HUE_PIECES); recalculateRings(quint8(getNumRings()), quint8(num)); if (m_selectedPiece >= 0) m_selectedPiece = getHueIndex(m_selectedColor.getH() * PI2); update(); } void KisColorSelector::setNumRings(int num) { num = qBound(MIN_NUM_SATURATION_RINGS, num, MAX_NUM_SATURATION_RINGS); recalculateRings(quint8(num), quint8(getNumPieces())); if (m_selectedRing >= 0) m_selectedRing = getSaturationIndex(m_selectedColor.getS()); update(); } void KisColorSelector::selectColor(const KisColor& color) { m_selectedColor = KisColor(color, m_colorConverter, m_colorSpace, m_lumaR, m_lumaG, m_lumaB, m_lumaGamma); m_selectedPiece = getHueIndex(m_selectedColor.getH() * PI2); m_selectedRing = getSaturationIndex(m_selectedColor.getS()); m_selectedLightPiece = getLightIndex(m_selectedColor.getX()); update(); } void KisColorSelector::setFgColor(const KoColor& fgColor) { if (!m_widgetUpdatesSelf) { m_fgColor = KisColor(fgColor, m_colorConverter, m_colorSpace, m_lumaR, m_lumaG, m_lumaB, m_lumaGamma); m_selectedColor = KisColor(fgColor, m_colorConverter, m_colorSpace, m_lumaR, m_lumaG, m_lumaB, m_lumaGamma); m_isDirtyWheel = true; m_isDirtyLightStrip = true; m_isDirtyColorPreview = true; #ifdef DEBUG_ARC_SELECTOR dbgPlugins << "KisColorSelector::setFgColor: m_fgColor set to:" << "H:" << m_fgColor.getH() << "S:" << m_fgColor.getS() << "X:" << m_fgColor.getX(); dbgPlugins << "KisColorSelector::setFgColor: m_selectedColor set to:" << "H:" << m_selectedColor.getH() << "S:" << m_selectedColor.getS() << "X:" << m_selectedColor.getX(); #endif update(); } } void KisColorSelector::setBgColor(const KoColor& bgColor) { if (!m_widgetUpdatesSelf) { m_bgColor = KisColor(bgColor, m_colorConverter, m_colorSpace, m_lumaR, m_lumaG, m_lumaB, m_lumaGamma); m_isDirtyColorPreview = true; #ifdef DEBUG_ARC_SELECTOR dbgPlugins << "KisColorSelector::setBgColor: m_bgColor set to:" << "H:" << m_bgColor.getH() << "S:" << m_bgColor.getS() << "X:" << m_bgColor.getX(); #endif update(); } } void KisColorSelector::setLight(qreal light) { m_selectedColor.setX(qBound(0.0, light, 1.0)); m_selectedLightPiece = getLightIndex(m_selectedColor.getX()); m_isDirtyLightStrip = true; update(); } void KisColorSelector::setLumaCoefficients(qreal lR, qreal lG, qreal lB, qreal lGamma) { m_lumaR = lR; m_lumaG = lG; m_lumaB = lB; m_lumaGamma = lGamma; m_selectedColor = KisColor(m_selectedColor, m_colorConverter, m_colorSpace, m_lumaR, m_lumaG, m_lumaB, m_lumaGamma); m_isDirtyLightStrip = true; m_isDirtyWheel = true; #ifdef DEBUG_ARC_SELECTOR dbgPlugins << "KisColorSelector::setLumaCoefficients: " << m_lumaR << " " << m_lumaG << " " << m_lumaB << " " << m_lumaGamma; #endif update(); } void KisColorSelector::setInverseSaturation(bool inverse) { if (m_inverseSaturation != inverse) { m_selectedRing = (getNumRings()-1) - m_selectedRing; m_inverseSaturation = inverse; recalculateRings(quint8(getNumRings()), quint8(getNumPieces())); update(); } } void KisColorSelector::setGamutMask(KoGamutMask* gamutMask) { if (!gamutMask) { return; } m_currentGamutMask = gamutMask; m_gamutMaskViewTransform = m_currentGamutMask->maskToViewTransform(m_renderArea.width()); if (m_enforceGamutMask) { m_isDirtyWheel = true; } else { m_isDirtyGamutMask = true; } update(); } void KisColorSelector::setDirty() { m_isDirtyWheel = true; m_isDirtyLightStrip = true; m_isDirtyGamutMask = true; m_isDirtyColorPreview = true; update(); } KoGamutMask* KisColorSelector::gamutMask() { return m_currentGamutMask; } bool KisColorSelector::gamutMaskOn() { return m_gamutMaskOn; } void KisColorSelector::setGamutMaskOn(bool gamutMaskOn) { if (m_currentGamutMask) { m_gamutMaskOn = gamutMaskOn; if (m_enforceGamutMask) { m_isDirtyWheel = true; } else { m_isDirtyGamutMask = true; } update(); } } void KisColorSelector::setEnforceGamutMask(bool enforce) { m_enforceGamutMask = enforce; m_isDirtyGamutMask = true; m_isDirtyWheel = true; update(); } QPointF KisColorSelector::mapCoordToView(const QPointF& pt, const QRectF& viewRect) const { qreal w = viewRect.width() / 2.0; qreal h = viewRect.height() / 2.0; qreal x = pt.x() + 1.0; qreal y = (pt.y()) + 1.0; return QPointF(x*w, y*h); } QPointF KisColorSelector::mapCoordToUnit(const QPointF& pt, const QRectF& viewRect) const { qreal w = viewRect.width() / 2.0; qreal h = viewRect.height() / 2.0; qreal x = pt.x() - (viewRect.x() + w); qreal y = pt.y() - (viewRect.y() + h); return QPointF(x/w, y/h); } QPointF KisColorSelector::mapColorToUnit(const KisColor& color, bool invertSaturation) const { qreal radius; if (invertSaturation && m_inverseSaturation) { radius = 1.0 - color.getS(); } else { radius = color.getS(); } QPointF hueCoord = mapHueToAngle(color.getH()); qreal x = hueCoord.x()*radius; qreal y = hueCoord.y()*radius; return QPointF(x,y); } KisColorSelector::Radian KisColorSelector::mapCoordToAngle(qreal x, qreal y) const { qreal angle = std::atan2(-y, -x); #ifdef DEBUG_ARC_SELECTOR dbgPlugins << "KisColorSelector::mapCoordToAngle: " << "X:" << x << "Y:" << y << "angle:" << angle; #endif return angle; } QPointF KisColorSelector::mapHueToAngle(qreal hue) const { qreal angle = hue * 2.0 * M_PI - M_PI; qreal x = std::cos(angle); qreal y = std::sin(angle); return QPointF(x,y); } qint8 KisColorSelector::getLightIndex(const QPointF& pt) const { if (m_lightStripArea.contains(pt.toPoint(), true)) { qreal t = (pt.x() - m_lightStripArea.x()) / qreal(m_lightStripArea.width()); t = (pt.y() - m_lightStripArea.y()) / qreal(m_lightStripArea.height()); return qint8(t * getNumLightPieces()); } return -1; } qint8 KisColorSelector::getLightIndex(qreal light) const { light = qreal(1) - qBound(qreal(0), light, qreal(1)); return qint8(qRound(light * (getNumLightPieces()-1))); } qreal KisColorSelector::getLight(const QPointF& pt) const { qint8 clickedLightPiece = getLightIndex(pt); if (clickedLightPiece >= 0) { if (getNumLightPieces() > 1) { return 1.0 - (qreal(clickedLightPiece) / qreal(getNumLightPieces()-1)); } return 1.0 - (qreal(pt.y()) / qreal(m_lightStripArea.height())); } return qreal(0); } qint8 KisColorSelector::getHueIndex(Radian hue) const { qreal partSize = 1.0 / qreal(getNumPieces()); return qint8(qRound(hue.scaled(0.0, 1.0) / partSize) % getNumPieces()); } qreal KisColorSelector::getHue(int hueIdx, Radian shift) const { Radian hue = (qreal(hueIdx) / qreal(getNumPieces())) * PI2; hue += shift; return hue.scaled(0.0, 1.0); } qint8 KisColorSelector::getSaturationIndex(qreal saturation) const { saturation = qBound(qreal(0), saturation, qreal(1)); saturation = m_inverseSaturation ? (qreal(1) - saturation) : saturation; return qint8(saturation * qreal(getNumRings() - 1)); } qint8 KisColorSelector::getSaturationIndex(const QPointF& pt) const { qreal length = std::sqrt(pt.x()*pt.x() + pt.y()*pt.y()); for(int i=0; i= m_colorRings[i].innerRadius && length < m_colorRings[i].outerRadius) return qint8(i); } return -1; } qreal KisColorSelector::getSaturation(int saturationIdx) const { qreal sat = qreal(saturationIdx) / qreal(getNumRings()-1); return m_inverseSaturation ? (1.0 - sat) : sat; } void KisColorSelector::recalculateAreas(quint8 numLightPieces) { qreal LIGHT_STRIP_RATIO = 0.075; if (m_showValueScaleNumbers) { LIGHT_STRIP_RATIO = 0.25; } int width = QWidget::width(); int height = QWidget::height(); int size = qMin(width, height); int stripThick = int(size * LIGHT_STRIP_RATIO); width -= stripThick; size = qMin(width, height); int x = (width - size) / 2; int y = (height - size) / 2; m_widgetArea = QRect(0, 0, QWidget::width(), QWidget::height()); m_renderArea = QRect(x+stripThick, y, size, size); m_lightStripArea = QRect(0, 0, stripThick, QWidget::height()); m_renderBuffer = QImage(size, size, QImage::Format_ARGB32_Premultiplied); m_colorPreviewBuffer = QImage(QWidget::width(), QWidget::height(), QImage::Format_ARGB32_Premultiplied); m_maskBuffer = QImage(size, size, QImage::Format_ARGB32_Premultiplied); m_lightStripBuffer = QImage(stripThick, QWidget::height(), QImage::Format_ARGB32_Premultiplied); m_numLightPieces = numLightPieces; m_isDirtyGamutMask = true; m_isDirtyLightStrip = true; m_isDirtyWheel = true; m_isDirtyColorPreview = true; } void KisColorSelector::recalculateRings(quint8 numRings, quint8 numPieces) { m_colorRings.resize(numRings); m_numPieces = numPieces; for(int i=0; i(numPieces, 1); ring.innerRadius = innerRadius; ring.outerRadius = outerRadius; ring.pieced.resize(numParts); qreal partSize = 360.0 / qreal(numParts); QRectF outerRect(-outerRadius, -outerRadius, outerRadius*2.0, outerRadius*2.0); QRectF innerRect(-innerRadius, -innerRadius, innerRadius*2.0, innerRadius*2.0); for(int i=0; iviewToMaskTransform(m_renderArea.width()).map(colorCoord); bool isClear = m_currentGamutMask->coordIsClear(translatedPoint, m_maskPreviewActive); if (isClear) { return true; } else { return false; } } else { return true; } return false; } void KisColorSelector::requestUpdateColorAndPreview(const KisColor &color, Acs::ColorRole role) { #ifdef DEBUG_ARC_SELECTOR dbgPlugins << "KisColorSelector::requestUpdateColorAndPreview: requesting update to: " << "H:" << color.getH() << "S:" << color.getS() << "X:" << color.getX(); #endif m_updateColorCompressor->start(qMakePair(color, role)); } void KisColorSelector::slotUpdateColorAndPreview(QPair color) { const bool selectAsFgColor = color.second == Acs::Foreground; if (selectAsFgColor) { m_fgColor = KisColor(color.first, m_colorConverter, m_colorSpace, m_lumaR, m_lumaG, m_lumaB, m_lumaGamma); } else { m_bgColor = KisColor(color.first, m_colorConverter, m_colorSpace, m_lumaR, m_lumaG, m_lumaB, m_lumaGamma); } m_selectedColor = KisColor(color.first, m_colorConverter, m_colorSpace, m_lumaR, m_lumaG, m_lumaB, m_lumaGamma); m_isDirtyLightStrip = true; m_isDirtyColorPreview = true; m_isDirtyWheel = true; #ifdef DEBUG_ARC_SELECTOR dbgPlugins << "KisColorSelector::slotUpdateColorAndPreview: m_selectedColor set to:" << "H:" << m_selectedColor.getH() << "S:" << m_selectedColor.getS() << "X:" << m_selectedColor.getX(); #endif if (selectAsFgColor) { emit sigFgColorChanged(m_selectedColor); } else { emit sigBgColorChanged(m_selectedColor); } } void KisColorSelector::drawRing(QPainter& painter, KisColorSelector::ColorRing& ring, const QRect& rect) { painter.save(); painter.setRenderHint(QPainter::Antialiasing, true); painter.resetTransform(); painter.translate(rect.width()/2, rect.height()/2); if (ring.pieced.size() > 1) { QTransform mirror; mirror.rotate(180, Qt::YAxis); painter.setTransform(mirror, true); painter.scale(rect.width()/2, rect.height()/2); QPen normalPen = QPen(QBrush(COLOR_NORMAL_OUTLINE), 0.005); QPen clearMaskPen = QPen(QBrush(COLOR_MASK_CLEAR), 0.005); QBrush brush(Qt::SolidPattern); for(int i=0; i= 1.0) ? (hue - 1.0) : hue; hue = (hue < 0.0) ? (hue + 1.0) : hue; KisColor color(hue, m_colorConverter, m_colorSpace); color.setS(ring.saturation); color.setX(m_selectedColor.getX()); if(m_gamutMaskOn && m_enforceGamutMask && colorIsClear(color)) { painter.setPen(clearMaskPen); } else { painter.setPen(normalPen); } if ((m_enforceGamutMask) && (!colorIsClear(color))) { brush.setColor(COLOR_MASK_FILL); } else { brush.setColor(color.toQColor()); } painter.setBrush(brush); painter.drawPath(ring.pieced[i]); } } else { KisColor colors[7] = { KisColor(Qt::cyan , m_colorConverter, m_colorSpace, m_lumaR, m_lumaG, m_lumaB, m_lumaGamma), KisColor(Qt::green , m_colorConverter, m_colorSpace, m_lumaR, m_lumaG, m_lumaB, m_lumaGamma), KisColor(Qt::yellow , m_colorConverter, m_colorSpace, m_lumaR, m_lumaG, m_lumaB, m_lumaGamma), KisColor(Qt::red , m_colorConverter, m_colorSpace, m_lumaR, m_lumaG, m_lumaB, m_lumaGamma), KisColor(Qt::magenta, m_colorConverter, m_colorSpace, m_lumaR, m_lumaG, m_lumaB, m_lumaGamma), KisColor(Qt::blue , m_colorConverter, m_colorSpace, m_lumaR, m_lumaG, m_lumaB, m_lumaGamma), KisColor(Qt::cyan , m_colorConverter, m_colorSpace, m_lumaR, m_lumaG, m_lumaB, m_lumaGamma) }; QConicalGradient gradient(0, 0, 0); for(int i=0; i<=6; ++i) { qreal hue = qreal(i) / 6.0; colors[i].setS(ring.saturation); colors[i].setX(m_selectedColor.getX()); gradient.setColorAt(hue, colors[i].toQColor()); } painter.scale(rect.width()/2, rect.height()/2); painter.fillPath(ring.pieced[0], QBrush(gradient)); } painter.restore(); } void KisColorSelector::drawOutline(QPainter& painter, const QRect& rect) { painter.save(); painter.setRenderHint(QPainter::Antialiasing, true); painter.resetTransform(); painter.translate(rect.x() + rect.width()/2, rect.y() + rect.height()/2); painter.scale(rect.width()/2, rect.height()/2); QPen normalPen = QPen(QBrush(COLOR_NORMAL_OUTLINE), 0.005); QPen selectedPen; painter.setPen(normalPen); if (getNumPieces() > 1) { if (m_selectedRing >= 0 && m_selectedPiece >= 0) { painter.resetTransform(); painter.translate(rect.x() + rect.width()/2, rect.y() + rect.height()/2); QTransform mirror; mirror.rotate(180, Qt::YAxis); painter.setTransform(mirror, true); painter.scale(rect.width()/2, rect.height()/2); if (m_selectedColor.getX() < 0.55) { selectedPen = QPen(QBrush(COLOR_SELECTED_LIGHT), 0.007); } else { selectedPen = QPen(QBrush(COLOR_SELECTED_DARK), 0.007); } painter.setPen(selectedPen); painter.drawPath(m_colorRings[m_selectedRing].pieced[m_selectedPiece]); } } else { for(int i=0; i= 0) { qreal iRad = m_colorRings[m_selectedRing].innerRadius; qreal oRad = m_colorRings[m_selectedRing].outerRadius; if (m_selectedColor.getX() < 0.55) { selectedPen = QPen(QBrush(COLOR_SELECTED_LIGHT), 0.005); } else { selectedPen = QPen(QBrush(COLOR_SELECTED_DARK), 0.005); } painter.setPen(selectedPen); painter.drawEllipse(QRectF(-iRad, -iRad, iRad*2.0, iRad*2.0)); painter.drawEllipse(QRectF(-oRad, -oRad, oRad*2.0, oRad*2.0)); QPointF lineCoords = mapHueToAngle(m_selectedColor.getH()); painter.drawLine(QPointF(lineCoords.x()*iRad, lineCoords.y()*iRad), QPointF(lineCoords.x()*oRad, lineCoords.y()*oRad)); } } painter.restore(); } void KisColorSelector::drawLightStrip(QPainter& painter, const QRect& rect) { qreal penSize = qreal(qMin(QWidget::width(), QWidget::height())) / 200.0; qreal penSizeSmall = penSize / 1.2; QPen selectedPen; KisColor valueScaleColor(m_selectedColor, m_colorConverter, m_colorSpace, m_lumaR, m_lumaG, m_lumaB, m_lumaGamma); KisColor grayScaleColor(Qt::gray, m_colorConverter, m_colorSpace, m_lumaR, m_lumaG, m_lumaB, m_lumaGamma); int rectSize = rect.height(); painter.save(); painter.resetTransform(); painter.setRenderHint(QPainter::Antialiasing, true); QTransform matrix; matrix.translate(rect.x(), rect.y()); matrix.scale(rect.width(), rect.height()); qreal rectColorLeftX; qreal rectColorWidth; if (m_showValueScaleNumbers) { rectColorLeftX = 0.6; rectColorWidth = 0.4; } else { rectColorLeftX = 0.0; rectColorWidth = 1.0; } if (getNumLightPieces() > 1) { for(int i=0; i 0) && (fm.boundingRect("100%").width() > rect.width()*rectColorLeftX)) { font.setPointSize(font.pointSize() - 1); painter.setFont(font); fm = painter.fontMetrics(); retries--; } for(int i=0; i 0) { int valueNumber = 0; if (m_colorSpace == KisColor::HSY) { valueNumber = 100 - round(pow(pow(grayScaleColor.getX(), m_lumaGamma), 1.0/2.2)*100); } else { valueNumber = 100 - grayScaleColor.getX()*100; } if (valueNumber < 55) { painter.setPen(QPen(QBrush(COLOR_DARK), penSize)); } else { painter.setPen(QPen(QBrush(COLOR_LIGHT), penSize)); } painter.drawText(rectValue, Qt::AlignRight|Qt::AlignBottom, QString("%1%").arg(QString::number(valueNumber))); } } } painter.restore(); } void KisColorSelector::drawBlip(QPainter& painter, const QRect& rect) { painter.save(); painter.setRenderHint(QPainter::Antialiasing, true); painter.resetTransform(); painter.translate(rect.x() + rect.width()/2, rect.y() + rect.height()/2); painter.scale(rect.width()/2, rect.height()/2); QPointF fgColorPos = mapColorToUnit(m_fgColor); #ifdef DEBUG_ARC_SELECTOR dbgPlugins << "KisColorSelector::drawBlip: " << "colorPoint H:" << m_fgColor.getH() << " S:" << m_fgColor.getS() << "-> coord X:" << fgColorPos.x() << " Y:" << fgColorPos.y(); #endif painter.setPen(QPen(QBrush(COLOR_SELECTED_DARK), 0.01)); painter.drawEllipse(fgColorPos, 0.05, 0.05); painter.setPen(QPen(QBrush(COLOR_SELECTED_LIGHT), 0.01)); painter.drawEllipse(fgColorPos, 0.04, 0.04); painter.restore(); } void KisColorSelector::drawGamutMaskShape(QPainter &painter, const QRect &rect) { painter.save(); painter.setRenderHint(QPainter::Antialiasing, true); painter.resetTransform(); painter.translate(rect.width()/2, rect.height()/2); painter.scale(rect.width()/2, rect.height()/2); painter.setPen(Qt::NoPen); painter.setBrush(COLOR_MASK_FILL); painter.drawEllipse(QPointF(0,0), 1.0, 1.0); painter.setWorldTransform(m_gamutMaskViewTransform); painter.setCompositionMode(QPainter::CompositionMode_DestinationIn); m_currentGamutMask->paint(painter, m_maskPreviewActive); painter.setCompositionMode(QPainter::CompositionMode_SourceOver); m_currentGamutMask->paintStroke(painter, m_maskPreviewActive); painter.restore(); } void KisColorSelector::drawColorPreview(QPainter &painter, const QRect &rect) { painter.save(); painter.setRenderHint(QPainter::Antialiasing, true); painter.fillRect(rect, m_fgColor.toQColor()); int bgSide = qMin(rect.width()*0.15,rect.height()*0.15); if (m_showBgColor) { QPointF bgPolyPoints[3] = { QPointF(rect.width(), rect.height()), QPointF(rect.width()-bgSide, rect.height()), QPointF(rect.width(), rect.height()-bgSide) }; painter.setBrush(m_bgColor.toQColor()); painter.setPen(m_bgColor.toQColor()); painter.drawPolygon(bgPolyPoints, 3); } painter.restore(); } void KisColorSelector::paintEvent(QPaintEvent* /*event*/) { QPainter wdgPainter(this); // draw the fg and bg color previews if (m_isDirtyColorPreview) { m_colorPreviewBuffer.fill(Qt::transparent); QPainter colorPreviewPainter(&m_colorPreviewBuffer); drawColorPreview(colorPreviewPainter, m_widgetArea); m_isDirtyColorPreview = false; } wdgPainter.drawImage(m_widgetArea, m_colorPreviewBuffer); // draw the fg and bg color previews // draw the wheel if (m_isDirtyWheel) { m_renderBuffer.fill(Qt::transparent); QPainter wheelPainter(&m_renderBuffer); for(int i=0; ilocalPos(), m_renderArea); m_mouseMoved = false; m_pressedButtons = event->buttons(); m_clickedRing = getSaturationIndex(m_clickPos); Acs::ColorRole colorRole = Acs::buttonsToRole(Qt::NoButton, m_pressedButtons); qint8 clickedLightPiece = getLightIndex(event->localPos()); if (clickedLightPiece >= 0) { setLight(getLight(event->localPos())); m_selectedLightPiece = clickedLightPiece; requestUpdateColorAndPreview(m_selectedColor, colorRole); m_mouseMoved = true; } else if (m_clickedRing >= 0) { if (getNumPieces() == 1) { Radian angle = mapCoordToAngle(m_clickPos.x(), m_clickPos.y()); KisColor color(m_colorConverter, m_colorSpace); color.setHSX(angle.scaled(0.0, 1.0) , getSaturation(m_clickedRing) , m_selectedColor.getX() ); #ifdef DEBUG_ARC_SELECTOR dbgPlugins << "KisColorSelector::mousePressEvent: picked color: " << "H:" << color.getH() << "S:" << color.getS() << "X:" << color.getX(); #endif if ((!m_enforceGamutMask) || colorIsClear(color)) { m_selectedColor.setHSX(color.getH(), color.getS(), color.getX()); requestUpdateColorAndPreview(m_selectedColor, colorRole); m_selectedRing = m_clickedRing; m_mouseMoved = true; update(); } } } } void KisColorSelector::mouseMoveEvent(QMouseEvent* event) { QPointF dragPos = mapCoordToUnit(event->localPos(), m_renderArea); qint8 clickedLightPiece = getLightIndex(event->localPos()); Acs::ColorRole colorRole = Acs::buttonsToRole(Qt::NoButton, m_pressedButtons); if (clickedLightPiece >= 0) { setLight(getLight(event->localPos())); m_selectedLightPiece = clickedLightPiece; requestUpdateColorAndPreview(m_selectedColor, colorRole); } if (m_clickedRing < 0) return; if (getNumPieces() == 1) { Radian angle = mapCoordToAngle(dragPos.x(), dragPos.y()); KisColor color(m_colorConverter, m_colorSpace); color.setHSX(angle.scaled(0.0, 1.0) , getSaturation(m_clickedRing) , m_selectedColor.getX() ); if ((!m_enforceGamutMask) || colorIsClear(color)) { m_selectedColor.setHSX(color.getH(), color.getS(), color.getX()); requestUpdateColorAndPreview(m_selectedColor, colorRole); } } update(); } void KisColorSelector::mouseReleaseEvent(QMouseEvent* /*event*/) { Acs::ColorRole colorRole = Acs::buttonsToRole(Qt::NoButton, m_pressedButtons); if (!m_mouseMoved && m_clickedRing >= 0) { Radian angle = mapCoordToAngle(m_clickPos.x(), m_clickPos.y()); KisColor color(m_colorConverter, m_colorSpace); qint8 hueIndex = getHueIndex(angle); if (getNumPieces() > 1) { color.setH(getHue(hueIndex)); } else { color.setH(angle.scaled(0.0, 1.0)); } color.setS(getSaturation(m_clickedRing)); color.setX(m_selectedColor.getX()); if ((!m_enforceGamutMask) || colorIsClear(color)) { m_selectedColor.setHSX(color.getH(), color.getS(), color.getX()); m_selectedPiece = hueIndex; m_selectedRing = m_clickedRing; requestUpdateColorAndPreview(m_selectedColor, colorRole); } } else if (m_mouseMoved) requestUpdateColorAndPreview(m_selectedColor, colorRole); m_clickedRing = -1; m_widgetUpdatesSelf = false; #ifdef DEBUG_ARC_SELECTOR dbgPlugins << "KisColorSelector::ReleasePressEvent: m_widgetUpdatesSelf = false"; #endif update(); } void KisColorSelector::resizeEvent(QResizeEvent* /*event*/) { recalculateAreas(quint8(getNumLightPieces())); } void KisColorSelector::leaveEvent(QEvent* /*e*/) { m_widgetUpdatesSelf = false; #ifdef DEBUG_ARC_SELECTOR dbgPlugins << "KisColorSelector::leaveEvent: m_widgetUpdatesSelf = false"; #endif } void KisColorSelector::saveSettings() { KisConfig cfg(false); cfg.writeEntry("ArtColorSel.ColorSpace" , qint32(m_colorSpace)); cfg.writeEntry("ArtColorSel.lumaR", qreal(m_lumaR)); cfg.writeEntry("ArtColorSel.lumaG", qreal(m_lumaG)); cfg.writeEntry("ArtColorSel.lumaB", qreal(m_lumaB)); cfg.writeEntry("ArtColorSel.lumaGamma", qreal(m_lumaGamma)); cfg.writeEntry("ArtColorSel.NumRings" , m_colorRings.size()); cfg.writeEntry("ArtColorSel.RingPieces" , qint32(m_numPieces)); cfg.writeEntry("ArtColorSel.LightPieces", qint32(m_numLightPieces)); cfg.writeEntry("ArtColorSel.InversedSaturation", m_inverseSaturation); cfg.writeEntry("ArtColorSel.Light" , m_selectedColor.getX()); cfg.writeEntry("ArtColorSel.SelColorH", m_selectedColor.getH()); cfg.writeEntry("ArtColorSel.SelColorS", m_selectedColor.getS()); cfg.writeEntry("ArtColorSel.SelColorX", m_selectedColor.getX()); cfg.writeEntry("ArtColorSel.defaultHueSteps", quint32(m_defaultHueSteps)); cfg.writeEntry("ArtColorSel.defaultSaturationSteps", quint32(m_defaultSaturationSteps)); cfg.writeEntry("ArtColorSel.defaultValueScaleSteps", quint32(m_defaultValueScaleSteps)); cfg.writeEntry("ArtColorSel.showBgColor", m_showBgColor); cfg.writeEntry("ArtColorSel.showValueScale", m_showValueScaleNumbers); cfg.writeEntry("ArtColorSel.enforceGamutMask", m_enforceGamutMask); } void KisColorSelector::loadSettings() { KisConfig cfg(true); m_defaultHueSteps = cfg.readEntry("ArtColorSel.defaultHueSteps", DEFAULT_HUE_STEPS); m_defaultSaturationSteps = cfg.readEntry("ArtColorSel.defaultSaturationSteps", DEFAULT_SATURATION_STEPS); m_defaultValueScaleSteps = cfg.readEntry("ArtColorSel.defaultValueScaleSteps", DEFAULT_VALUE_SCALE_STEPS); setNumLightPieces(cfg.readEntry("ArtColorSel.LightPieces", DEFAULT_VALUE_SCALE_STEPS)); KisColor::Type colorSpace = KisColor::Type(cfg.readEntry("ArtColorSel.ColorSpace" , KisColor::HSY)); setColorSpace(colorSpace); setLumaCoefficients(cfg.readEntry("ArtColorSel.lumaR", DEFAULT_LUMA_R), cfg.readEntry("ArtColorSel.lumaG", DEFAULT_LUMA_G), cfg.readEntry("ArtColorSel.lumaB", DEFAULT_LUMA_B), cfg.readEntry("ArtColorSel.lumaGamma", DEFAULT_LUMA_GAMMA)); m_selectedColor.setH(cfg.readEntry("ArtColorSel.SelColorH", 0.0)); m_selectedColor.setS(cfg.readEntry("ArtColorSel.SelColorS", 0.0)); m_selectedColor.setX(cfg.readEntry("ArtColorSel.SelColorX", 0.0)); setInverseSaturation(cfg.readEntry("ArtColorSel.InversedSaturation", false)); setLight(cfg.readEntry("ArtColorSel.Light", 0.5f)); setNumRings(cfg.readEntry("ArtColorSel.NumRings", DEFAULT_SATURATION_STEPS)); setNumPieces(cfg.readEntry("ArtColorSel.RingPieces", DEFAULT_HUE_STEPS)); m_showBgColor = cfg.readEntry("ArtColorSel.showBgColor", true); m_showValueScaleNumbers = cfg.readEntry("ArtColorSel.showValueScale", false); m_enforceGamutMask = cfg.readEntry("ArtColorSel.enforceGamutMask", false); selectColor(m_selectedColor); update(); } void KisColorSelector::setDefaultHueSteps(int num) { num = qBound(MIN_NUM_HUE_PIECES, num, MAX_NUM_HUE_PIECES); m_defaultHueSteps = num; } void KisColorSelector::setDefaultSaturationSteps(int num) { num = qBound(MIN_NUM_SATURATION_RINGS, num, MAX_NUM_SATURATION_RINGS); m_defaultSaturationSteps = num; } void KisColorSelector::setDefaultValueScaleSteps(int num) { num = qBound(MIN_NUM_LIGHT_PIECES, num, MAX_NUM_LIGHT_PIECES); m_defaultValueScaleSteps = num; } void KisColorSelector::setShowBgColor(bool value) { m_showBgColor = value; m_isDirtyColorPreview = true; update(); } void KisColorSelector::setShowValueScaleNumbers(bool value) { m_showValueScaleNumbers = value; recalculateAreas(quint8(getNumLightPieces())); update(); } diff --git a/plugins/impex/jp2/jp2_converter.cc b/plugins/impex/jp2/jp2_converter.cc index db133db1fe..664d152242 100644 --- a/plugins/impex/jp2/jp2_converter.cc +++ b/plugins/impex/jp2/jp2_converter.cc @@ -1,423 +1,421 @@ /* * Copyright (c) 2019 Aaron Boxer * * This library is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation; version 2.1 of the License. * * 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 Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "jp2_converter.h" #include #include #include #include -#include - #include #include #include #include #include #include #include #include #include #include #include #include #include "kis_iterator_ng.h" #include #include #include #define J2K_CFMT 0 #define JP2_CFMT 1 JP2Converter::JP2Converter(KisDocument *doc) { m_doc = doc; m_stop = false; } JP2Converter::~JP2Converter() { } /** * sample error callback expecting a FILE* client object * */ static void error_callback(const char *msg, void *client_data) { JP2Converter *converter = (JP2Converter*) client_data; converter->addErrorString(msg); } /** * sample warning callback expecting a FILE* client object * */ static void warning_callback(const char *msg, void *client_data) { JP2Converter *converter = (JP2Converter*) client_data; converter->addWarningString(msg); } /** * sample debug callback expecting no client object * */ static void info_callback(const char *msg, void *client_data) { JP2Converter *converter = (JP2Converter*) client_data; converter->addInfoString(msg); } static int getFileFormat(const char *filename) { unsigned int i; static const char *extension[] = { "j2k", "jp2", "j2c", "jpc" }; static const int format[] = { J2K_CFMT, JP2_CFMT, J2K_CFMT, J2K_CFMT }; const char *ext = strrchr(filename, '.'); if (ext == NULL) { return -1; } ext++; if (*ext) { for (i = 0; i < sizeof(format) / sizeof(*format); i++) { if (strcasecmp(ext, extension[i]) == 0) { return format[i]; } } } return -1; } #define JP2_RFC3745_MAGIC "\x00\x00\x00\x0c\x6a\x50\x20\x20\x0d\x0a\x87\x0a" #define JP2_MAGIC "\x0d\x0a\x87\x0a" #define J2K_CODESTREAM_MAGIC "\xff\x4f\xff\x51" int JP2Converter::infile_format(const char *fname) { FILE *reader; const char *s, *magic_s; int ext_format, magic_format; unsigned char buf[12]; OPJ_SIZE_T l_nb_read; reader = fopen(fname, "rb"); if (reader == NULL) { return -2; } memset(buf, 0, 12); l_nb_read = fread(buf, 1, 12, reader); fclose(reader); if (l_nb_read != 12) { return -1; } ext_format = getFileFormat(fname); if (memcmp(buf, JP2_RFC3745_MAGIC, 12) == 0 || memcmp(buf, JP2_MAGIC, 4) == 0) { magic_format = JP2_CFMT; magic_s = ".jp2"; } else if (memcmp(buf, J2K_CODESTREAM_MAGIC, 4) == 0) { magic_format = J2K_CFMT; magic_s = ".j2k or .jpc or .j2c"; } else { return -1; } if (magic_format == ext_format) { return ext_format; } if (strlen(fname) >= 4) { s = fname + strlen(fname) - 4; std::ostringstream buffer; buffer << "The extension of this file is incorrect.\n" << "Found " << s << " while it should be " << magic_s << "."; addErrorString(buffer.str()); } return magic_format; } KisImportExportErrorCode JP2Converter::buildImage(const QString &filename) { KisImportExportErrorCode res = ImportExportCodes::OK; const char *file_str = filename.toUtf8().data(); opj_codec_t *l_codec = 0; opj_dparameters_t parameters; bool hasColorSpaceInfo = false; opj_stream_t *l_stream = NULL; opj_image_t *image = NULL; int pos = 0; KisHLineIteratorSP it = NULL; unsigned int numComponents = 0; unsigned int precision = 0; const KoColorSpace *colorSpace = 0; QVector channelorder; KisPaintLayerSP layer; bool isSigned; int32_t signedCorrection = 0; uint32_t w=0, h=0; // decompression parameters opj_set_default_decoder_parameters(¶meters); // Determine the type parameters.decod_format = infile_format(file_str); if (parameters.decod_format == -1) { addErrorString("Not a JPEG 2000 file."); res = ImportExportCodes::FileFormatIncorrect; goto beach; } // Decode the file /* get a decoder handle */ switch (parameters.decod_format) { case J2K_CFMT: { l_codec = opj_create_decompress(OPJ_CODEC_J2K); break; } case JP2_CFMT: { l_codec = opj_create_decompress(OPJ_CODEC_JP2); hasColorSpaceInfo = true; break; } } Q_ASSERT(l_codec); opj_codec_set_threads( l_codec,QThread::idealThreadCount() ); /* setup the decoder decoding parameters using user parameters */ opj_setup_decoder(l_codec, ¶meters); l_stream = opj_stream_create_default_file_stream(file_str, 1); if (!l_stream) { addErrorString("Failed to create the stream"); res = ImportExportCodes::ErrorWhileReading; goto beach; } // Setup an event handling opj_set_info_handler(l_codec, info_callback, this); opj_set_error_handler(l_codec, error_callback, this); opj_set_warning_handler(l_codec, warning_callback, this); if (!opj_read_header(l_stream, l_codec, &image)) { addErrorString("Failed to read the header"); res = ImportExportCodes::ErrorWhileReading; goto beach; } /* Get the decoded image */ if (!(opj_decode(l_codec, l_stream, image) && opj_end_decompress(l_codec, l_stream))) { addErrorString("Failed to decode image"); res = ImportExportCodes::ErrorWhileReading; goto beach; } // Look for the colorspace numComponents = image->numcomps; if (image->numcomps == 0) { addErrorString("Image must have at least one component"); res = ImportExportCodes::Failure; goto beach; } precision = image->comps[0].prec; for (uint32_t i = 1; i < numComponents; ++i) { if (image->comps[i].prec != precision) { std::ostringstream buffer; buffer << "All components must have the same bit depth " << precision; addErrorString(buffer.str()); res = ImportExportCodes::FormatFeaturesUnsupported; goto beach; } } isSigned = false; for (uint32_t i = 0; i < numComponents; ++i) { if ((image->comps[i].dx != 1) || (image->comps[i].dy != 1)) { addErrorString("Sub-sampling not supported"); res = ImportExportCodes::FormatFeaturesUnsupported; goto beach; } isSigned = isSigned || (image->comps[0].sgnd); } if (isSigned) signedCorrection = 1 << (precision - 1); dbgFile << "Image has " << numComponents << " numComponents and a bit depth of " << precision << " for color space " << image->color_space; channelorder = QVector(numComponents); if (!hasColorSpaceInfo) { if (numComponents == 3) { image->color_space = OPJ_CLRSPC_SRGB; } else if (numComponents == 1) { image->color_space = OPJ_CLRSPC_GRAY; } } switch (image->color_space) { case OPJ_CLRSPC_UNKNOWN: case OPJ_CLRSPC_UNSPECIFIED: break; case OPJ_CLRSPC_SRGB: { if (precision == 16 || precision == 12) { colorSpace = KoColorSpaceRegistry::instance()->rgb16(); } else if (precision == 8) { colorSpace = KoColorSpaceRegistry::instance()->rgb8(); } if (numComponents != 3) { std::ostringstream buffer; buffer << "sRGB: number of numComponents " << numComponents << " does not equal 3"; addErrorString(buffer.str()); res = ImportExportCodes::FormatFeaturesUnsupported; goto beach; } channelorder[0] = KoBgrU16Traits::red_pos; channelorder[1] = KoBgrU16Traits::green_pos; channelorder[2] = KoBgrU16Traits::blue_pos; break; } case OPJ_CLRSPC_GRAY: { if (precision == 16 || precision == 12) { colorSpace = KoColorSpaceRegistry::instance()->colorSpace( GrayAColorModelID.id(), Integer16BitsColorDepthID.id(), ""); } else if (precision == 8) { colorSpace = KoColorSpaceRegistry::instance()->colorSpace( GrayAColorModelID.id(), Integer8BitsColorDepthID.id(), ""); } if (numComponents != 1) { std::ostringstream buffer; buffer << "Grayscale: number of numComponents " << numComponents << " greater than 1"; addErrorString(buffer.str()); res = ImportExportCodes::FormatFeaturesUnsupported; goto beach; } channelorder[0] = 0; break; } case OPJ_CLRSPC_SYCC: addErrorString("YUV color space not supported"); res = ImportExportCodes::FormatColorSpaceUnsupported; goto beach; break; case OPJ_CLRSPC_EYCC: addErrorString("eYCC color space not supported"); res = ImportExportCodes::FormatColorSpaceUnsupported; goto beach; break; case OPJ_CLRSPC_CMYK: addErrorString("CMYK color space not supported"); res = ImportExportCodes::FormatColorSpaceUnsupported; goto beach; break; default: break; } if (!colorSpace) { addErrorString("No color space found for image"); res = ImportExportCodes::FormatColorSpaceUnsupported; goto beach; } // Create the image w = (uint32_t)(image->x1 - image->x0); h = (uint32_t)(image->y1 - image->y0); if (m_image == 0) { m_image = new KisImage(m_doc->createUndoStore(), w, h, colorSpace, "built image"); } // Create the layer layer = new KisPaintLayer(m_image, m_image->nextLayerName(), OPACITY_OPAQUE_U8); m_image->addNode(layer); // Set the data it = layer->paintDevice()->createHLineIteratorNG(0, 0, w); for (OPJ_UINT32 v = 0; v < image->y1; ++v) { if (precision == 16 || precision == 12) { do { quint16 *px = reinterpret_cast(it->rawData()); for (uint32_t i = 0; i < numComponents; ++i) { px[channelorder[i]] = image->comps[i].data[pos] + signedCorrection; } colorSpace->setOpacity(it->rawData(), OPACITY_OPAQUE_U8, 1); ++pos; } while (it->nextPixel()); } else if (precision == 8) { do { quint8 *px = it->rawData(); for (uint32_t i = 0; i < numComponents; ++i) { px[channelorder[i]] = image->comps[i].data[pos] + signedCorrection; } colorSpace->setOpacity(px, OPACITY_OPAQUE_U8, 1); ++pos; } while (it->nextPixel()); } it->nextRow(); } beach: if (l_stream) opj_stream_destroy(l_stream); if (l_codec) opj_destroy_codec(l_codec); if (image) opj_image_destroy(image); if (!err.empty()) m_doc->setErrorMessage(i18n(err.c_str())); if (!warn.empty()) m_doc->setWarningMessage(i18n(warn.c_str())); return res; } KisImageWSP JP2Converter::image() { return m_image; } KisImportExportErrorCode JP2Converter::buildFile(const QString &filename, KisPaintLayerSP layer, const JP2ConvertOptions &options) { (void) layer; (void) filename; (void) options; return ImportExportCodes::Failure; } void JP2Converter::cancel() { m_stop = true; } void JP2Converter::addWarningString(const std::string &str) { if (!warn.empty()) warn += "\n"; warn += str; } void JP2Converter::addInfoString(const std::string &str) { dbgFile << str.c_str(); } void JP2Converter::addErrorString(const std::string &str) { if (!err.empty()) err += "\n"; err += str; } diff --git a/plugins/tools/svgtexttool/SvgTextEditor.cpp b/plugins/tools/svgtexttool/SvgTextEditor.cpp index 790ec33544..7d36a40bd1 100644 --- a/plugins/tools/svgtexttool/SvgTextEditor.cpp +++ b/plugins/tools/svgtexttool/SvgTextEditor.cpp @@ -1,1251 +1,1250 @@ /* This file is part of the KDE project * * Copyright 2017 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 "SvgTextEditor.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include -#include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "kis_font_family_combo_box.h" #include "FontSizeAction.h" #include "kis_signals_blocker.h" SvgTextEditor::SvgTextEditor(QWidget *parent, Qt::WindowFlags flags) : KXmlGuiWindow(parent, flags) , m_page(new QWidget(this)) #ifndef Q_OS_WIN , m_charSelectDialog(new KoDialog(this)) #endif { m_textEditorWidget.setupUi(m_page); setCentralWidget(m_page); m_textEditorWidget.chkVertical->setVisible(false); #ifndef Q_OS_WIN KCharSelect *charSelector = new KCharSelect(m_charSelectDialog, 0, KCharSelect::AllGuiElements); m_charSelectDialog->setMainWidget(charSelector); connect(charSelector, SIGNAL(currentCharChanged(QChar)), SLOT(insertCharacter(QChar))); m_charSelectDialog->hide(); m_charSelectDialog->setButtons(KoDialog::Close); #endif connect(m_textEditorWidget.buttons, SIGNAL(accepted()), this, SLOT(save())); connect(m_textEditorWidget.buttons, SIGNAL(rejected()), this, SLOT(slotCloseEditor())); connect(m_textEditorWidget.buttons, SIGNAL(clicked(QAbstractButton*)), this, SLOT(dialogButtonClicked(QAbstractButton*))); KConfigGroup cg(KSharedConfig::openConfig(), "SvgTextTool"); actionCollection()->setConfigGroup("SvgTextTool"); actionCollection()->setComponentName("svgtexttool"); actionCollection()->setComponentDisplayName(i18n("Text Tool")); if (cg.hasKey("WindowState")) { QByteArray state = cg.readEntry("State", state); // One day will need to load the version number, but for now, assume 0 restoreState(QByteArray::fromBase64(state)); } if (cg.hasKey("Geometry")) { QByteArray ba = cg.readEntry("Geometry", QByteArray()); restoreGeometry(QByteArray::fromBase64(ba)); } else { const int scnum = QApplication::desktop()->screenNumber(parentWidget()); QRect desk = QGuiApplication::screens().at(scnum)->availableVirtualGeometry(); quint32 x = desk.x(); quint32 y = desk.y(); quint32 w = 0; quint32 h = 0; const int deskWidth = desk.width(); w = (deskWidth / 3) * 2; h = (desk.height() / 3) * 2; x += (desk.width() - w) / 2; y += (desk.height() - h) / 2; move(x,y); setGeometry(geometry().x(), geometry().y(), w, h); } setAcceptDrops(true); //setStandardToolBarMenuEnabled(true); #ifdef Q_OS_MACOS setUnifiedTitleAndToolBarOnMac(true); #endif setTabPosition(Qt::AllDockWidgetAreas, QTabWidget::North); m_syntaxHighlighter = new BasicXMLSyntaxHighlighter(m_textEditorWidget.svgTextEdit); m_textEditorWidget.svgTextEdit->setFont(QFontDatabase().systemFont(QFontDatabase::FixedFont)); createActions(); // If we have customized the toolbars, load that first setLocalXMLFile(KoResourcePaths::locateLocal("data", "svgtexttool.xmlgui")); setXMLFile(":/kxmlgui5/svgtexttool.xmlgui"); guiFactory()->addClient(this); // Create and plug toolbar list for Settings menu QList toolbarList; Q_FOREACH (QWidget* it, guiFactory()->containers("ToolBar")) { KToolBar * toolBar = ::qobject_cast(it); if (toolBar) { toolBar->setToolButtonStyle(Qt::ToolButtonIconOnly); KToggleAction* act = new KToggleAction(i18n("Show %1 Toolbar", toolBar->windowTitle()), this); actionCollection()->addAction(toolBar->objectName().toUtf8(), act); act->setCheckedState(KGuiItem(i18n("Hide %1 Toolbar", toolBar->windowTitle()))); connect(act, SIGNAL(toggled(bool)), this, SLOT(slotToolbarToggled(bool))); act->setChecked(!toolBar->isHidden()); toolbarList.append(act); } } plugActionList("toolbarlist", toolbarList); connect(m_textEditorWidget.textTab, SIGNAL(currentChanged(int)), this, SLOT(switchTextEditorTab())); switchTextEditorTab(); m_textEditorWidget.richTextEdit->document()->setDefaultStyleSheet("p {margin:0px;}"); applySettings(); } SvgTextEditor::~SvgTextEditor() { KConfigGroup g(KSharedConfig::openConfig(), "SvgTextTool"); QByteArray ba = saveState(); g.writeEntry("windowState", ba.toBase64()); ba = saveGeometry(); g.writeEntry("Geometry", ba.toBase64()); } void SvgTextEditor::setShape(KoSvgTextShape *shape) { m_shape = shape; if (m_shape) { KoSvgTextShapeMarkupConverter converter(m_shape); QString svg; QString styles; QTextDocument *doc = m_textEditorWidget.richTextEdit->document(); if (converter.convertToSvg(&svg, &styles)) { m_textEditorWidget.svgTextEdit->setPlainText(svg); m_textEditorWidget.svgStylesEdit->setPlainText(styles); m_textEditorWidget.svgTextEdit->document()->setModified(false); if (shape->isRichTextPreferred() && converter.convertSvgToDocument(svg, doc)) { m_textEditorWidget.richTextEdit->setDocument(doc); KisSignalsBlocker b(m_textEditorWidget.textTab); m_textEditorWidget.textTab->setCurrentIndex(Richtext); doc->clearUndoRedoStacks(); switchTextEditorTab(false); } else { KisSignalsBlocker b(m_textEditorWidget.textTab); m_textEditorWidget.textTab->setCurrentIndex(SvgSource); switchTextEditorTab(false); } } else { QMessageBox::warning(this, i18n("Conversion failed"), "Could not get svg text from the shape:\n" + converter.errors().join('\n') + "\n" + converter.warnings().join('\n')); } } KisFontComboBoxes* fontComboBox = qobject_cast(qobject_cast(actionCollection()->action("svg_font"))->defaultWidget()); fontComboBox->setInitialized(); } void SvgTextEditor::save() { if (m_shape) { if (m_textEditorWidget.textTab->currentIndex() == Richtext) { QString svg; QString styles = m_textEditorWidget.svgStylesEdit->document()->toPlainText(); KoSvgTextShapeMarkupConverter converter(m_shape); if (!converter.convertDocumentToSvg(m_textEditorWidget.richTextEdit->document(), &svg)) { qWarning()<<"new converter doesn't work!"; } m_textEditorWidget.richTextEdit->document()->setModified(false); emit textUpdated(m_shape, svg, styles, true); } else { emit textUpdated(m_shape, m_textEditorWidget.svgTextEdit->document()->toPlainText(), m_textEditorWidget.svgStylesEdit->document()->toPlainText(), false); m_textEditorWidget.svgTextEdit->document()->setModified(false); } } } void SvgTextEditor::switchTextEditorTab(bool convertData) { KoSvgTextShape shape; KoSvgTextShapeMarkupConverter converter(&shape); if (m_currentEditor) { disconnect(m_currentEditor->document(), SIGNAL(modificationChanged(bool)), this, SLOT(setModified(bool))); } if (m_textEditorWidget.textTab->currentIndex() == Richtext) { //first, make buttons checkable enableRichTextActions(true); enableSvgTextActions(false); //then connect the cursor change to the checkformat(); connect(m_textEditorWidget.richTextEdit, SIGNAL(cursorPositionChanged()), this, SLOT(checkFormat())); connect(m_textEditorWidget.richTextEdit, SIGNAL(textChanged()), this, SLOT(slotFixUpEmptyTextBlock())); checkFormat(); if (m_shape && convertData) { QTextDocument *doc = m_textEditorWidget.richTextEdit->document(); if (!converter.convertSvgToDocument(m_textEditorWidget.svgTextEdit->document()->toPlainText(), doc)) { qWarning()<<"new converter svgToDoc doesn't work!"; } m_textEditorWidget.richTextEdit->setDocument(doc); doc->clearUndoRedoStacks(); } m_currentEditor = m_textEditorWidget.richTextEdit; } else { //first, make buttons uncheckable enableRichTextActions(false); enableSvgTextActions(true); disconnect(m_textEditorWidget.richTextEdit, SIGNAL(cursorPositionChanged()), this, SLOT(checkFormat())); // Convert the rich text to svg and styles strings if (m_shape && convertData) { QString svg; QString styles; if (!converter.convertDocumentToSvg(m_textEditorWidget.richTextEdit->document(), &svg)) { qWarning()<<"new converter docToSVG doesn't work!"; } m_textEditorWidget.svgTextEdit->setPlainText(svg); } m_currentEditor = m_textEditorWidget.svgTextEdit; } connect(m_currentEditor->document(), SIGNAL(modificationChanged(bool)), SLOT(setModified(bool))); } void SvgTextEditor::checkFormat() { QTextCharFormat format = m_textEditorWidget.richTextEdit->textCursor().charFormat(); QTextBlockFormat blockFormat = m_textEditorWidget.richTextEdit->textCursor().blockFormat(); // checkboxes do not emit signals on manual switching, so we // can avoid blocking them if (format.fontWeight() > QFont::Normal) { actionCollection()->action("svg_weight_bold")->setChecked(true); } else { actionCollection()->action("svg_weight_bold")->setChecked(false); } actionCollection()->action("svg_format_italic")->setChecked(format.fontItalic()); actionCollection()->action("svg_format_underline")->setChecked(format.fontUnderline()); actionCollection()->action("svg_format_strike_through")->setChecked(format.fontStrikeOut()); { FontSizeAction *fontSizeAction = qobject_cast(actionCollection()->action("svg_font_size")); KisSignalsBlocker b(fontSizeAction); fontSizeAction->setFontSize(format.font().pointSize()); } { KoColor fg(format.foreground().color(), KoColorSpaceRegistry::instance()->rgb8()); KoColorPopupAction *fgColorPopup = qobject_cast(actionCollection()->action("svg_format_textcolor")); KisSignalsBlocker b(fgColorPopup); fgColorPopup->setCurrentColor(fg); } { KoColor bg(format.foreground().color(), KoColorSpaceRegistry::instance()->rgb8()); KoColorPopupAction *bgColorPopup = qobject_cast(actionCollection()->action("svg_background_color")); KisSignalsBlocker b(bgColorPopup); bgColorPopup->setCurrentColor(bg); } { KisFontComboBoxes* fontComboBox = qobject_cast(qobject_cast(actionCollection()->action("svg_font"))->defaultWidget()); KisSignalsBlocker b(fontComboBox); fontComboBox->setCurrentFont(format.font()); } { QDoubleSpinBox *spnLineHeight = qobject_cast(qobject_cast(actionCollection()->action("svg_line_height"))->defaultWidget()); KisSignalsBlocker b(spnLineHeight); if (blockFormat.lineHeightType() == QTextBlockFormat::SingleHeight) { spnLineHeight->setValue(100.0); } else if(blockFormat.lineHeightType() == QTextBlockFormat::ProportionalHeight) { spnLineHeight->setValue(double(blockFormat.lineHeight())); } } { QDoubleSpinBox* spnLetterSpacing = qobject_cast(qobject_cast(actionCollection()->action("svg_letter_spacing"))->defaultWidget()); KisSignalsBlocker b(spnLetterSpacing); spnLetterSpacing->setValue(format.fontLetterSpacing()); } } void SvgTextEditor::slotFixUpEmptyTextBlock() { if (m_textEditorWidget.richTextEdit->document()->isEmpty()) { QTextCursor cursor = m_textEditorWidget.richTextEdit->textCursor(); QTextCharFormat format = cursor.blockCharFormat(); { FontSizeAction *fontSizeAction = qobject_cast(actionCollection()->action("svg_font_size")); KisFontComboBoxes* fontComboBox = qobject_cast(qobject_cast(actionCollection()->action("svg_font"))->defaultWidget()); format.setFont(fontComboBox->currentFont(fontSizeAction->fontSize())); } { KoColorPopupAction *fgColorPopup = qobject_cast(actionCollection()->action("svg_format_textcolor")); format.setForeground(fgColorPopup->currentColor()); } { //KoColorPopupAction *bgColorPopup = qobject_cast(actionCollection()->action("svg_background_color")); //format.setBackground(bgColorPopup->currentColor()); } KisSignalsBlocker b(m_textEditorWidget.richTextEdit); cursor.setBlockCharFormat(format); } } void SvgTextEditor::undo() { m_currentEditor->undo(); } void SvgTextEditor::redo() { m_currentEditor->redo(); } void SvgTextEditor::cut() { m_currentEditor->cut(); } void SvgTextEditor::copy() { m_currentEditor->copy(); } void SvgTextEditor::paste() { m_currentEditor->paste(); } void SvgTextEditor::selectAll() { m_currentEditor->selectAll(); } void SvgTextEditor::deselect() { QTextCursor cursor(m_currentEditor->textCursor()); cursor.clearSelection(); m_currentEditor->setTextCursor(cursor); } void SvgTextEditor::find() { QDialog *findDialog = new QDialog(this); findDialog->setWindowTitle(i18n("Find Text")); QFormLayout *layout = new QFormLayout(); findDialog->setLayout(layout); QLineEdit *lnSearchKey = new QLineEdit(); layout->addRow(i18n("Find:"), lnSearchKey); QDialogButtonBox *buttons = new QDialogButtonBox(QDialogButtonBox::Ok|QDialogButtonBox::Cancel); findDialog->layout()->addWidget(buttons); connect(buttons, SIGNAL(accepted()), findDialog, SLOT(accept())); connect(buttons, SIGNAL(rejected()), findDialog, SLOT(reject())); if (findDialog->exec()==QDialog::Accepted) { m_searchKey = lnSearchKey->text(); m_currentEditor->find(m_searchKey); } } void SvgTextEditor::findNext() { if (!m_currentEditor->find(m_searchKey)) { QTextCursor cursor(m_currentEditor->textCursor()); cursor.movePosition(QTextCursor::Start); m_currentEditor->setTextCursor(cursor); m_currentEditor->find(m_searchKey); } } void SvgTextEditor::findPrev() { if (!m_currentEditor->find(m_searchKey,QTextDocument::FindBackward)) { QTextCursor cursor(m_currentEditor->textCursor()); cursor.movePosition(QTextCursor::End); m_currentEditor->setTextCursor(cursor); m_currentEditor->find(m_searchKey,QTextDocument::FindBackward); } } void SvgTextEditor::replace() { QDialog *findDialog = new QDialog(this); findDialog->setWindowTitle(i18n("Find and Replace all")); QFormLayout *layout = new QFormLayout(); findDialog->setLayout(layout); QLineEdit *lnSearchKey = new QLineEdit(); QLineEdit *lnReplaceKey = new QLineEdit(); layout->addRow(i18n("Find:"), lnSearchKey); QDialogButtonBox *buttons = new QDialogButtonBox(QDialogButtonBox::Ok|QDialogButtonBox::Cancel); layout->addRow(i18n("Replace:"), lnReplaceKey); findDialog->layout()->addWidget(buttons); connect(buttons, SIGNAL(accepted()), findDialog, SLOT(accept())); connect(buttons, SIGNAL(rejected()), findDialog, SLOT(reject())); if (findDialog->exec()==QDialog::Accepted) { QString search = lnSearchKey->text(); QString replace = lnReplaceKey->text(); QTextCursor cursor(m_currentEditor->textCursor()); cursor.movePosition(QTextCursor::Start); m_currentEditor->setTextCursor(cursor); while(m_currentEditor->find(search)) { m_currentEditor->textCursor().removeSelectedText(); m_currentEditor->textCursor().insertText(replace); } } } void SvgTextEditor::zoomOut() { m_currentEditor->zoomOut(); } void SvgTextEditor::zoomIn() { m_currentEditor->zoomIn(); } #ifndef Q_OS_WIN void SvgTextEditor::showInsertSpecialCharacterDialog() { m_charSelectDialog->setVisible(!m_charSelectDialog->isVisible()); } void SvgTextEditor::insertCharacter(const QChar &c) { m_currentEditor->textCursor().insertText(QString(c)); } #endif void SvgTextEditor::setTextBold(QFont::Weight weight) { if (m_textEditorWidget.textTab->currentIndex() == Richtext) { QTextCharFormat format; QTextCursor oldCursor = setTextSelection(); if (m_textEditorWidget.richTextEdit->textCursor().charFormat().fontWeight() > QFont::Normal && weight==QFont::Bold) { format.setFontWeight(QFont::Normal); } else { format.setFontWeight(weight); } m_textEditorWidget.richTextEdit->mergeCurrentCharFormat(format); m_textEditorWidget.richTextEdit->setTextCursor(oldCursor); } else { QTextCursor cursor = m_textEditorWidget.svgTextEdit->textCursor(); if (cursor.hasSelection()) { QString selectionModified = "" + cursor.selectedText() + ""; cursor.removeSelectedText(); cursor.insertText(selectionModified); } } } void SvgTextEditor::setTextWeightLight() { if (m_textEditorWidget.richTextEdit->textCursor().charFormat().fontWeight() < QFont::Normal) { setTextBold(QFont::Normal); } else { setTextBold(QFont::Light); } } void SvgTextEditor::setTextWeightNormal() { setTextBold(QFont::Normal); } void SvgTextEditor::setTextWeightDemi() { if (m_textEditorWidget.richTextEdit->textCursor().charFormat().fontWeight() != QFont::Normal) { setTextBold(QFont::Normal); } else { setTextBold(QFont::DemiBold); } } void SvgTextEditor::setTextWeightBlack() { if (m_textEditorWidget.richTextEdit->textCursor().charFormat().fontWeight()>QFont::Normal) { setTextBold(QFont::Normal); } else { setTextBold(QFont::Black); } } void SvgTextEditor::setTextItalic(QFont::Style style) { QTextCursor cursor = m_textEditorWidget.svgTextEdit->textCursor(); QString fontStyle = "inherit"; if (style == QFont::StyleItalic) { fontStyle = "italic"; } else if(style == QFont::StyleOblique) { fontStyle = "oblique"; } if (m_textEditorWidget.textTab->currentIndex() == Richtext) { QTextCharFormat format; QTextCursor origCursor = setTextSelection(); format.setFontItalic(!m_textEditorWidget.richTextEdit->textCursor().charFormat().fontItalic()); m_textEditorWidget.richTextEdit->mergeCurrentCharFormat(format); m_textEditorWidget.richTextEdit->setTextCursor(origCursor); } else { if (cursor.hasSelection()) { QString selectionModified = "" + cursor.selectedText() + ""; cursor.removeSelectedText(); cursor.insertText(selectionModified); } } } void SvgTextEditor::setTextDecoration(KoSvgText::TextDecoration decor) { QTextCursor cursor = setTextSelection(); QTextCharFormat currentFormat = m_textEditorWidget.richTextEdit->textCursor().charFormat(); QTextCharFormat format; QString textDecoration = "inherit"; if (decor == KoSvgText::DecorationUnderline) { textDecoration = "underline"; if (currentFormat.fontUnderline()) { format.setFontUnderline(false); } else { format.setFontUnderline(true); } format.setFontOverline(false); format.setFontStrikeOut(false); } else if (decor == KoSvgText::DecorationLineThrough) { textDecoration = "line-through"; format.setFontUnderline(false); format.setFontOverline(false); if (currentFormat.fontStrikeOut()) { format.setFontStrikeOut(false); } else { format.setFontStrikeOut(true); } } else if (decor == KoSvgText::DecorationOverline) { textDecoration = "overline"; format.setFontUnderline(false); if (currentFormat.fontOverline()) { format.setFontOverline(false); } else { format.setFontOverline(true); } format.setFontStrikeOut(false); } if (m_textEditorWidget.textTab->currentIndex() == Richtext) { m_textEditorWidget.richTextEdit->mergeCurrentCharFormat(format); } else { if (cursor.hasSelection()) { QString selectionModified = "" + cursor.selectedText() + ""; cursor.removeSelectedText(); cursor.insertText(selectionModified); } } m_textEditorWidget.richTextEdit->setTextCursor(cursor); } void SvgTextEditor::setTextUnderline() { setTextDecoration(KoSvgText::DecorationUnderline); } void SvgTextEditor::setTextOverline() { setTextDecoration(KoSvgText::DecorationOverline); } void SvgTextEditor::setTextStrikethrough() { setTextDecoration(KoSvgText::DecorationLineThrough); } void SvgTextEditor::setTextSubscript() { QTextCharFormat format = m_textEditorWidget.richTextEdit->textCursor().charFormat(); if (format.verticalAlignment()==QTextCharFormat::AlignSubScript) { format.setVerticalAlignment(QTextCharFormat::AlignNormal); } else { format.setVerticalAlignment(QTextCharFormat::AlignSubScript); } m_textEditorWidget.richTextEdit->mergeCurrentCharFormat(format); } void SvgTextEditor::setTextSuperScript() { QTextCharFormat format = m_textEditorWidget.richTextEdit->textCursor().charFormat(); if (format.verticalAlignment()==QTextCharFormat::AlignSuperScript) { format.setVerticalAlignment(QTextCharFormat::AlignNormal); } else { format.setVerticalAlignment(QTextCharFormat::AlignSuperScript); } m_textEditorWidget.richTextEdit->mergeCurrentCharFormat(format); } void SvgTextEditor::increaseTextSize() { QTextCursor oldCursor = setTextSelection(); QTextCharFormat format; int pointSize = m_textEditorWidget.richTextEdit->textCursor().charFormat().font().pointSize(); if (pointSize<0) { pointSize = m_textEditorWidget.richTextEdit->textCursor().charFormat().font().pixelSize(); } format.setFontPointSize(pointSize+1.0); m_textEditorWidget.richTextEdit->mergeCurrentCharFormat(format); m_textEditorWidget.richTextEdit->setTextCursor(oldCursor); } void SvgTextEditor::decreaseTextSize() { QTextCursor oldCursor = setTextSelection(); QTextCharFormat format; int pointSize = m_textEditorWidget.richTextEdit->textCursor().charFormat().font().pointSize(); if (pointSize<1) { pointSize = m_textEditorWidget.richTextEdit->textCursor().charFormat().font().pixelSize(); } format.setFontPointSize(qMax(pointSize-1.0, 1.0)); m_textEditorWidget.richTextEdit->mergeCurrentCharFormat(format); m_textEditorWidget.richTextEdit->setTextCursor(oldCursor); } void SvgTextEditor::setLineHeight(double lineHeightPercentage) { QTextCursor oldCursor = setTextSelection(); QTextBlockFormat format = m_textEditorWidget.richTextEdit->textCursor().blockFormat(); format.setLineHeight(lineHeightPercentage, QTextBlockFormat::ProportionalHeight); m_textEditorWidget.richTextEdit->textCursor().mergeBlockFormat(format); m_textEditorWidget.richTextEdit->setTextCursor(oldCursor); } void SvgTextEditor::setLetterSpacing(double letterSpacing) { QTextCursor cursor = setTextSelection(); if (m_textEditorWidget.textTab->currentIndex() == Richtext) { QTextCharFormat format; format.setFontLetterSpacingType(QFont::AbsoluteSpacing); format.setFontLetterSpacing(letterSpacing); m_textEditorWidget.richTextEdit->mergeCurrentCharFormat(format); m_textEditorWidget.richTextEdit->setTextCursor(cursor); } else { if (cursor.hasSelection()) { QString selectionModified = "" + cursor.selectedText() + ""; cursor.removeSelectedText(); cursor.insertText(selectionModified); } } } void SvgTextEditor::alignLeft() { QTextCursor oldCursor = setTextSelection(); QTextBlockFormat format = m_textEditorWidget.richTextEdit->textCursor().blockFormat(); format.setAlignment(Qt::AlignLeft); m_textEditorWidget.richTextEdit->textCursor().mergeBlockFormat(format); m_textEditorWidget.richTextEdit->setTextCursor(oldCursor); } void SvgTextEditor::alignRight() { QTextCursor oldCursor = setTextSelection(); QTextBlockFormat format = m_textEditorWidget.richTextEdit->textCursor().blockFormat(); format.setAlignment(Qt::AlignRight); m_textEditorWidget.richTextEdit->textCursor().mergeBlockFormat(format); m_textEditorWidget.richTextEdit->setTextCursor(oldCursor); } void SvgTextEditor::alignCenter() { QTextCursor oldCursor = setTextSelection(); QTextBlockFormat format = m_textEditorWidget.richTextEdit->textCursor().blockFormat(); format.setAlignment(Qt::AlignCenter); m_textEditorWidget.richTextEdit->textCursor().mergeBlockFormat(format); m_textEditorWidget.richTextEdit->setTextCursor(oldCursor); } void SvgTextEditor::alignJustified() { QTextCursor oldCursor = setTextSelection(); QTextBlockFormat format = m_textEditorWidget.richTextEdit->textCursor().blockFormat(); format.setAlignment(Qt::AlignJustify); m_textEditorWidget.richTextEdit->textCursor().mergeBlockFormat(format); m_textEditorWidget.richTextEdit->setTextCursor(oldCursor); } void SvgTextEditor::setSettings() { KoDialog settingsDialog(this); Ui_WdgSvgTextSettings textSettings; QWidget *settingsPage = new QWidget(&settingsDialog, 0); settingsDialog.setMainWidget(settingsPage); textSettings.setupUi(settingsPage); // get the settings and initialize the dialog KConfigGroup cfg(KSharedConfig::openConfig(), "SvgTextTool"); QStringList selectedWritingSystems = cfg.readEntry("selectedWritingSystems", "").split(","); QList scripts = QFontDatabase().writingSystems(); QStandardItemModel *writingSystemsModel = new QStandardItemModel(&settingsDialog); for (int s = 0; s < scripts.size(); s ++) { QString writingSystem = QFontDatabase().writingSystemName(scripts.at(s)); QStandardItem *script = new QStandardItem(writingSystem); script->setCheckable(true); script->setCheckState(selectedWritingSystems.contains(QString::number(scripts.at(s))) ? Qt::Checked : Qt::Unchecked); script->setData((int)scripts.at(s)); writingSystemsModel->appendRow(script); } textSettings.lwScripts->setModel(writingSystemsModel); EditorMode mode = (EditorMode)cfg.readEntry("EditorMode", (int)Both); switch(mode) { case(RichText): textSettings.radioRichText->setChecked(true); break; case(SvgSource): textSettings.radioSvgSource->setChecked(true); break; case(Both): textSettings.radioBoth->setChecked(true); } QColor background = cfg.readEntry("colorEditorBackground", qApp->palette().window().color()); textSettings.colorEditorBackground->setColor(background); textSettings.colorEditorForeground->setColor(cfg.readEntry("colorEditorForeground", qApp->palette().text().color())); textSettings.colorKeyword->setColor(cfg.readEntry("colorKeyword", QColor(background.value() < 100 ? Qt::cyan : Qt::blue))); textSettings.chkBoldKeyword->setChecked(cfg.readEntry("BoldKeyword", true)); textSettings.chkItalicKeyword->setChecked(cfg.readEntry("ItalicKeyword", false)); textSettings.colorElement->setColor(cfg.readEntry("colorElement", QColor(background.value() < 100 ? Qt::magenta : Qt::darkMagenta))); textSettings.chkBoldElement->setChecked(cfg.readEntry("BoldElement", true)); textSettings.chkItalicElement->setChecked(cfg.readEntry("ItalicElement", false)); textSettings.colorAttribute->setColor(cfg.readEntry("colorAttribute", QColor(background.value() < 100 ? Qt::green : Qt::darkGreen))); textSettings.chkBoldAttribute->setChecked(cfg.readEntry("BoldAttribute", true)); textSettings.chkItalicAttribute->setChecked(cfg.readEntry("ItalicAttribute", true)); textSettings.colorValue->setColor(cfg.readEntry("colorValue", QColor(background.value() < 100 ? Qt::red: Qt::darkRed))); textSettings.chkBoldValue->setChecked(cfg.readEntry("BoldValue", true)); textSettings.chkItalicValue->setChecked(cfg.readEntry("ItalicValue", false)); textSettings.colorComment->setColor(cfg.readEntry("colorComment", QColor(background.value() < 100 ? Qt::lightGray : Qt::gray))); textSettings.chkBoldComment->setChecked(cfg.readEntry("BoldComment", false)); textSettings.chkItalicComment->setChecked(cfg.readEntry("ItalicComment", false)); settingsDialog.setButtons(KoDialog::Ok | KoDialog::Cancel); if (settingsDialog.exec() == QDialog::Accepted) { // save and set the settings QStringList writingSystems; for (int i = 0; i < writingSystemsModel->rowCount(); i++) { QStandardItem *item = writingSystemsModel->item(i); if (item->checkState() == Qt::Checked) { writingSystems.append(QString::number(item->data().toInt())); } } cfg.writeEntry("selectedWritingSystems", writingSystems.join(',')); if (textSettings.radioRichText->isChecked()) { cfg.writeEntry("EditorMode", (int)Richtext); } else if (textSettings.radioSvgSource->isChecked()) { cfg.writeEntry("EditorMode", (int)SvgSource); } else if (textSettings.radioBoth->isChecked()) { cfg.writeEntry("EditorMode", (int)Both); } cfg.writeEntry("colorEditorBackground", textSettings.colorEditorBackground->color()); cfg.writeEntry("colorEditorForeground", textSettings.colorEditorForeground->color()); cfg.writeEntry("colorKeyword", textSettings.colorKeyword->color()); cfg.writeEntry("BoldKeyword", textSettings.chkBoldKeyword->isChecked()); cfg.writeEntry("ItalicKeyWord", textSettings.chkItalicKeyword->isChecked()); cfg.writeEntry("colorElement", textSettings.colorElement->color()); cfg.writeEntry("BoldElement", textSettings.chkBoldElement->isChecked()); cfg.writeEntry("ItalicElement", textSettings.chkItalicElement->isChecked()); cfg.writeEntry("colorAttribute", textSettings.colorAttribute->color()); cfg.writeEntry("BoldAttribute", textSettings.chkBoldAttribute->isChecked()); cfg.writeEntry("ItalicAttribute", textSettings.chkItalicAttribute->isChecked()); cfg.writeEntry("colorValue", textSettings.colorValue->color()); cfg.writeEntry("BoldValue", textSettings.chkBoldValue->isChecked()); cfg.writeEntry("ItalicValue", textSettings.chkItalicValue->isChecked()); cfg.writeEntry("colorComment", textSettings.colorComment->color()); cfg.writeEntry("BoldComment", textSettings.chkBoldComment->isChecked()); cfg.writeEntry("ItalicComment", textSettings.chkItalicComment->isChecked()); applySettings(); } } void SvgTextEditor::slotToolbarToggled(bool) { } void SvgTextEditor::setFontColor(const KoColor &c) { QColor color = c.toQColor(); if (m_textEditorWidget.textTab->currentIndex() == Richtext) { QTextCursor oldCursor = setTextSelection(); QTextCharFormat format; format.setForeground(QBrush(color)); m_textEditorWidget.richTextEdit->mergeCurrentCharFormat(format); m_textEditorWidget.richTextEdit->setTextCursor(oldCursor); } else { QTextCursor cursor = m_textEditorWidget.svgTextEdit->textCursor(); if (cursor.hasSelection()) { QString selectionModified = "" + cursor.selectedText() + ""; cursor.removeSelectedText(); cursor.insertText(selectionModified); } } } void SvgTextEditor::setBackgroundColor(const KoColor &c) { QColor color = c.toQColor(); QTextCursor cursor = m_textEditorWidget.svgTextEdit->textCursor(); if (cursor.hasSelection()) { QString selectionModified = "" + cursor.selectedText() + ""; cursor.removeSelectedText(); cursor.insertText(selectionModified); } } void SvgTextEditor::setModified(bool modified) { if (modified) { m_textEditorWidget.buttons->setStandardButtons(QDialogButtonBox::Save | QDialogButtonBox::Discard); } else { m_textEditorWidget.buttons->setStandardButtons(QDialogButtonBox::Save | QDialogButtonBox::Close); } } void SvgTextEditor::dialogButtonClicked(QAbstractButton *button) { if (m_textEditorWidget.buttons->standardButton(button) == QDialogButtonBox::Discard) { if (QMessageBox::warning(this, i18nc("@title:window", "Krita"), i18n("You have modified the text. Discard changes?"), QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes) { close(); } } } void SvgTextEditor::setFont(const QString &fontName) { QFont font; font.fromString(fontName); QTextCharFormat curFormat = m_textEditorWidget.richTextEdit->textCursor().charFormat(); font.setPointSize(curFormat.font().pointSize()); QTextCharFormat format; //This disables the style being set from the font-comboboxes too, so we need to rethink how we use that. format.setFontFamily(font.family()); if (m_textEditorWidget.textTab->currentIndex() == Richtext) { QTextCursor oldCursor = setTextSelection(); m_textEditorWidget.richTextEdit->mergeCurrentCharFormat(format); m_textEditorWidget.richTextEdit->setTextCursor(oldCursor); } else { QTextCursor cursor = m_textEditorWidget.svgTextEdit->textCursor(); if (cursor.hasSelection()) { QString selectionModified = "" + cursor.selectedText() + ""; cursor.removeSelectedText(); cursor.insertText(selectionModified); } } } void SvgTextEditor::setFontSize(qreal fontSize) { if (m_textEditorWidget.textTab->currentIndex() == Richtext) { QTextCursor oldCursor = setTextSelection(); QTextCharFormat format; format.setFontPointSize(fontSize); m_textEditorWidget.richTextEdit->mergeCurrentCharFormat(format); m_textEditorWidget.richTextEdit->setTextCursor(oldCursor); } else { QTextCursor cursor = m_textEditorWidget.svgTextEdit->textCursor(); if (cursor.hasSelection()) { QString selectionModified = "" + cursor.selectedText() + ""; cursor.removeSelectedText(); cursor.insertText(selectionModified); } } } void SvgTextEditor::setBaseline(KoSvgText::BaselineShiftMode) { QTextCursor cursor = m_textEditorWidget.svgTextEdit->textCursor(); if (cursor.hasSelection()) { QString selectionModified = "" + cursor.selectedText() + ""; cursor.removeSelectedText(); cursor.insertText(selectionModified); } } void SvgTextEditor::wheelEvent(QWheelEvent *event) { if (event->modifiers() & Qt::ControlModifier) { int numDegrees = event->delta() / 8; int numSteps = numDegrees / 7; m_textEditorWidget.svgTextEdit->zoomOut(numSteps); event->accept(); } } QTextCursor SvgTextEditor::setTextSelection() { QTextCursor orignalCursor(m_textEditorWidget.richTextEdit->textCursor()); if (!orignalCursor.hasSelection()){ m_textEditorWidget.richTextEdit->selectAll(); } return orignalCursor; } void SvgTextEditor::applySettings() { KConfigGroup cfg(KSharedConfig::openConfig(), "SvgTextTool"); EditorMode mode = (EditorMode)cfg.readEntry("EditorMode", (int)Both); QWidget *richTab = m_textEditorWidget.richTab; QWidget *svgTab = m_textEditorWidget.svgTab; m_page->setUpdatesEnabled(false); m_textEditorWidget.textTab->clear(); switch(mode) { case(RichText): m_textEditorWidget.textTab->addTab(richTab, i18n("Rich text")); break; case(SvgSource): m_textEditorWidget.textTab->addTab(svgTab, i18n("SVG Source")); break; case(Both): m_textEditorWidget.textTab->addTab(richTab, i18n("Rich text")); m_textEditorWidget.textTab->addTab(svgTab, i18n("SVG Source")); } m_syntaxHighlighter->setFormats(); QPalette palette = m_textEditorWidget.svgTextEdit->palette(); QColor background = cfg.readEntry("colorEditorBackground", qApp->palette().window().color()); palette.setBrush(QPalette::Active, QPalette::Background, QBrush(background)); m_textEditorWidget.richTextEdit->setStyleSheet(QString("background-color:%1").arg(background.name())); m_textEditorWidget.svgStylesEdit->setStyleSheet(QString("background-color:%1").arg(background.name())); m_textEditorWidget.svgTextEdit->setStyleSheet(QString("background-color:%1").arg(background.name())); QColor foreground = cfg.readEntry("colorEditorForeground", qApp->palette().text().color()); palette.setBrush(QPalette::Active, QPalette::Text, QBrush(foreground)); QStringList selectedWritingSystems = cfg.readEntry("selectedWritingSystems", "").split(","); QVector writingSystems; for (int i=0; i(actionCollection()->action("svg_font_size")); KisFontComboBoxes* fontComboBox = qobject_cast(qobject_cast(actionCollection()->action("svg_font"))->defaultWidget()); const QFont oldFont = fontComboBox->currentFont(fontSizeAction->fontSize()); fontComboBox->refillComboBox(writingSystems); fontComboBox->setCurrentFont(oldFont); } m_page->setUpdatesEnabled(true); } QAction *SvgTextEditor::createAction(const QString &name, const char *member) { QAction *action = new QAction(this); KisActionRegistry *actionRegistry = KisActionRegistry::instance(); actionRegistry->propertizeAction(name, action); actionCollection()->addAction(name, action); QObject::connect(action, SIGNAL(triggered(bool)), this, member); return action; } void SvgTextEditor::createActions() { KisActionRegistry *actionRegistry = KisActionRegistry::instance(); // File: new, open, save, save as, close KStandardAction::save(this, SLOT(save()), actionCollection()); KStandardAction::close(this, SLOT(slotCloseEditor()), actionCollection()); // Edit KStandardAction::undo(this, SLOT(undo()), actionCollection()); KStandardAction::redo(this, SLOT(redo()), actionCollection()); KStandardAction::cut(this, SLOT(cut()), actionCollection()); KStandardAction::copy(this, SLOT(copy()), actionCollection()); KStandardAction::paste(this, SLOT(paste()), actionCollection()); KStandardAction::selectAll(this, SLOT(selectAll()), actionCollection()); KStandardAction::deselect(this, SLOT(deselect()), actionCollection()); KStandardAction::find(this, SLOT(find()), actionCollection()); KStandardAction::findNext(this, SLOT(findNext()), actionCollection()); KStandardAction::findPrev(this, SLOT(findPrev()), actionCollection()); KStandardAction::replace(this, SLOT(replace()), actionCollection()); // View // WISH: we cannot zoom-in/out in rech-text mode m_svgTextActions << KStandardAction::zoomOut(this, SLOT(zoomOut()), actionCollection()); m_svgTextActions << KStandardAction::zoomIn(this, SLOT(zoomIn()), actionCollection()); #ifndef Q_OS_WIN // Insert: QAction * insertAction = createAction("svg_insert_special_character", SLOT(showInsertSpecialCharacterDialog())); insertAction->setCheckable(true); insertAction->setChecked(false); #endif // Format: m_richTextActions << createAction("svg_weight_bold", SLOT(setTextBold())); m_richTextActions << createAction("svg_format_italic", SLOT(setTextItalic())); m_richTextActions << createAction("svg_format_underline", SLOT(setTextUnderline())); m_richTextActions << createAction("svg_format_strike_through", SLOT(setTextStrikethrough())); m_richTextActions << createAction("svg_format_superscript", SLOT(setTextSuperScript())); m_richTextActions << createAction("svg_format_subscript", SLOT(setTextSubscript())); m_richTextActions << createAction("svg_weight_light", SLOT(setTextWeightLight())); m_richTextActions << createAction("svg_weight_normal", SLOT(setTextWeightNormal())); m_richTextActions << createAction("svg_weight_demi", SLOT(setTextWeightDemi())); m_richTextActions << createAction("svg_weight_black", SLOT(setTextWeightBlack())); m_richTextActions << createAction("svg_increase_font_size", SLOT(increaseTextSize())); m_richTextActions << createAction("svg_decrease_font_size", SLOT(decreaseTextSize())); m_richTextActions << createAction("svg_align_left", SLOT(alignLeft())); m_richTextActions << createAction("svg_align_right", SLOT(alignRight())); m_richTextActions << createAction("svg_align_center", SLOT(alignCenter())); // m_richTextActions << createAction("svg_align_justified", // SLOT(alignJustified())); // Settings m_richTextActions << createAction("svg_settings", SLOT(setSettings())); QWidgetAction *fontComboAction = new QWidgetAction(this); fontComboAction->setToolTip(i18n("Font")); KisFontComboBoxes *fontCombo = new KisFontComboBoxes(); connect(fontCombo, SIGNAL(fontChanged(QString)), SLOT(setFont(QString))); fontComboAction->setDefaultWidget(fontCombo); actionCollection()->addAction("svg_font", fontComboAction); m_richTextActions << fontComboAction; actionRegistry->propertizeAction("svg_font", fontComboAction); QWidgetAction *fontSizeAction = new FontSizeAction(this); fontSizeAction->setToolTip(i18n("Size")); connect(fontSizeAction, SIGNAL(fontSizeChanged(qreal)), this, SLOT(setFontSize(qreal))); actionCollection()->addAction("svg_font_size", fontSizeAction); m_richTextActions << fontSizeAction; actionRegistry->propertizeAction("svg_font_size", fontSizeAction); KoColorPopupAction *fgColor = new KoColorPopupAction(this); fgColor->setCurrentColor(QColor(Qt::black)); fgColor->setToolTip(i18n("Text Color")); connect(fgColor, SIGNAL(colorChanged(KoColor)), SLOT(setFontColor(KoColor))); actionCollection()->addAction("svg_format_textcolor", fgColor); m_richTextActions << fgColor; actionRegistry->propertizeAction("svg_format_textcolor", fgColor); KoColorPopupAction *bgColor = new KoColorPopupAction(this); bgColor->setCurrentColor(QColor(Qt::white)); bgColor->setToolTip(i18n("Background Color")); connect(bgColor, SIGNAL(colorChanged(KoColor)), SLOT(setBackgroundColor(KoColor))); actionCollection()->addAction("svg_background_color", bgColor); actionRegistry->propertizeAction("svg_background_color", bgColor); m_richTextActions << bgColor; QWidgetAction *colorPickerAction = new QWidgetAction(this); colorPickerAction->setToolTip(i18n("Pick a Color")); KisScreenColorPicker *colorPicker = new KisScreenColorPicker(false); connect(colorPicker, SIGNAL(sigNewColorPicked(KoColor)), fgColor, SLOT(setCurrentColor(KoColor))); connect(colorPicker, SIGNAL(sigNewColorPicked(KoColor)), SLOT(setFontColor(KoColor))); colorPickerAction->setDefaultWidget(colorPicker); actionCollection()->addAction("svg_pick_color", colorPickerAction); m_richTextActions << colorPickerAction; actionRegistry->propertizeAction("svg_pick_color", colorPickerAction); QWidgetAction *lineHeight = new QWidgetAction(this); QDoubleSpinBox *spnLineHeight = new QDoubleSpinBox(); spnLineHeight->setToolTip(i18n("Line height")); spnLineHeight->setRange(0.0, 1000.0); spnLineHeight->setSingleStep(10.0); spnLineHeight->setSuffix(i18n("%")); connect(spnLineHeight, SIGNAL(valueChanged(double)), SLOT(setLineHeight(double))); lineHeight->setDefaultWidget(spnLineHeight); actionCollection()->addAction("svg_line_height", lineHeight); m_richTextActions << lineHeight; actionRegistry->propertizeAction("svg_line_height", lineHeight); QWidgetAction *letterSpacing = new QWidgetAction(this); QDoubleSpinBox *spnletterSpacing = new QDoubleSpinBox(); spnletterSpacing->setToolTip(i18n("Letter Spacing")); spnletterSpacing->setRange(-20.0, 20.0); spnletterSpacing->setSingleStep(0.5); connect(spnletterSpacing, SIGNAL(valueChanged(double)), SLOT(setLetterSpacing(double))); letterSpacing->setDefaultWidget(spnletterSpacing); actionCollection()->addAction("svg_letter_spacing", letterSpacing); m_richTextActions << letterSpacing; actionRegistry->propertizeAction("svg_letter_spacing", letterSpacing); } void SvgTextEditor::enableRichTextActions(bool enable) { Q_FOREACH(QAction *action, m_richTextActions) { action->setEnabled(enable); } } void SvgTextEditor::enableSvgTextActions(bool enable) { Q_FOREACH(QAction *action, m_svgTextActions) { action->setEnabled(enable); } } void SvgTextEditor::slotCloseEditor() { close(); emit textEditorClosed(); }