diff --git a/libs/flake/text/KoSvgTextShape.cpp b/libs/flake/text/KoSvgTextShape.cpp index 321a00ed38..942caad8e2 100644 --- a/libs/flake/text/KoSvgTextShape.cpp +++ b/libs/flake/text/KoSvgTextShape.cpp @@ -1,628 +1,627 @@ /* * Copyright (c) 2017 Dmitry Kazakov * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "KoSvgTextShape.h" #include #include #include "KoSvgText.h" #include "KoSvgTextProperties.h" #include #include #include #include #include #include "kis_debug.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include class KoSvgTextShapePrivate : public KoSvgTextChunkShapePrivate { KoSvgTextShapePrivate(KoSvgTextShape *_q) : KoSvgTextChunkShapePrivate(_q) { } KoSvgTextShapePrivate(const KoSvgTextShapePrivate &rhs, KoSvgTextShape *q) : KoSvgTextChunkShapePrivate(rhs, q) { } std::vector> cachedLayouts; std::vector cachedLayoutsOffsets; QThread *cachedLayoutsWorkingThread = 0; void clearAssociatedOutlines(KoShape *rootShape); Q_DECLARE_PUBLIC(KoSvgTextShape) }; KoSvgTextShape::KoSvgTextShape() : KoSvgTextChunkShape(new KoSvgTextShapePrivate(this)) { setShapeId(KoSvgTextShape_SHAPEID); } KoSvgTextShape::KoSvgTextShape(const KoSvgTextShape &rhs) : KoSvgTextChunkShape(new KoSvgTextShapePrivate(*rhs.d_func(), this)) { setShapeId(KoSvgTextShape_SHAPEID); // QTextLayout has no copy-ctor, so just relayout everything! relayout(); } KoSvgTextShape::~KoSvgTextShape() { } KoShape *KoSvgTextShape::cloneShape() const { return new KoSvgTextShape(*this); } void KoSvgTextShape::shapeChanged(ChangeType type, KoShape *shape) { KoSvgTextChunkShape::shapeChanged(type, shape); if (type == StrokeChanged || type == BackgroundChanged || type == ContentChanged) { relayout(); } } void KoSvgTextShape::paintComponent(QPainter &painter, const KoViewConverter &converter, KoShapePaintingContext &paintContext) { Q_D(KoSvgTextShape); Q_UNUSED(paintContext); /** * HACK ALERT: * QTextLayout should only be accessed from the thread it has been created in. * If the cached layout has been created in a different thread, we should just * recreate the layouts in the current thread to be able to render them. */ if (QThread::currentThread() != d->cachedLayoutsWorkingThread) { relayout(); } applyConversion(painter, converter); for (int i = 0; i < (int)d->cachedLayouts.size(); i++) { d->cachedLayouts[i]->draw(&painter, d->cachedLayoutsOffsets[i]); } /** * HACK ALERT: * The layouts of non-gui threads must be destroyed in the same thread * they have been created. Because the thread might be restarted in the * meantime or just destroyed, meaning that the per-thread freetype data * will not be available. */ if (QThread::currentThread() != qApp->thread()) { d->cachedLayouts.clear(); d->cachedLayoutsOffsets.clear(); d->cachedLayoutsWorkingThread = 0; } } void KoSvgTextShape::paintStroke(QPainter &painter, const KoViewConverter &converter, KoShapePaintingContext &paintContext) { Q_UNUSED(painter); Q_UNUSED(converter); Q_UNUSED(paintContext); // do nothing! everything is painted in paintComponent() } QPainterPath KoSvgTextShape::textOutline() { Q_D(KoSvgTextShape); QPainterPath result; result.setFillRule(Qt::WindingFill); for (int i = 0; i < (int)d->cachedLayouts.size(); i++) { const QPointF layoutOffset = d->cachedLayoutsOffsets[i]; const QTextLayout *layout = d->cachedLayouts[i].get(); for (int j = 0; j < layout->lineCount(); j++) { QTextLine line = layout->lineAt(j); Q_FOREACH (const QGlyphRun &run, line.glyphRuns()) { const QVector indexes = run.glyphIndexes(); const QVector positions = run.positions(); const QRawFont font = run.rawFont(); KIS_SAFE_ASSERT_RECOVER(indexes.size() == positions.size()) { continue; } for (int k = 0; k < indexes.size(); k++) { QPainterPath glyph = font.pathForGlyph(indexes[k]); glyph.translate(positions[k] + layoutOffset); result += glyph; } const qreal thickness = font.lineThickness(); const QRectF runBounds = run.boundingRect(); if (run.overline()) { // the offset is calculated to be consistent with the way how Qt renders the text const qreal y = line.y(); QRectF overlineBlob(runBounds.x(), y, runBounds.width(), thickness); overlineBlob.translate(layoutOffset); QPainterPath path; path.addRect(overlineBlob); // don't use direct addRect, because it doesn't care about Qt::WindingFill result += path; } if (run.strikeOut()) { // the offset is calculated to be consistent with the way how Qt renders the text const qreal y = line.y() + 0.5 * line.height(); QRectF strikeThroughBlob(runBounds.x(), y, runBounds.width(), thickness); strikeThroughBlob.translate(layoutOffset); QPainterPath path; path.addRect(strikeThroughBlob); // don't use direct addRect, because it doesn't care about Qt::WindingFill result += path; } if (run.underline()) { const qreal y = line.y() + line.ascent() + font.underlinePosition(); QRectF underlineBlob(runBounds.x(), y, runBounds.width(), thickness); underlineBlob.translate(layoutOffset); QPainterPath path; path.addRect(underlineBlob); // don't use direct addRect, because it doesn't care about Qt::WindingFill result += path; } } } } return result; } void KoSvgTextShape::resetTextShape() { KoSvgTextChunkShape::resetTextShape(); relayout(); } struct TextChunk { QString text; QVector formats; Qt::LayoutDirection direction = Qt::LeftToRight; Qt::Alignment alignment = Qt::AlignLeading; struct SubChunkOffset { QPointF offset; int start = 0; }; QVector offsets; boost::optional xStartPos; boost::optional yStartPos; QPointF applyStartPosOverride(const QPointF &pos) const { QPointF result = pos; if (xStartPos) { result.rx() = *xStartPos; } if (yStartPos) { result.ry() = *yStartPos; } return result; } }; QVector mergeIntoChunks(const QVector &subChunks) { QVector chunks; for (auto it = subChunks.begin(); it != subChunks.end(); ++it) { if (it->transformation.startsNewChunk() || it == subChunks.begin()) { TextChunk newChunk = TextChunk(); newChunk.direction = it->format.layoutDirection(); newChunk.alignment = it->format.calculateAlignment(); newChunk.xStartPos = it->transformation.xPos; newChunk.yStartPos = it->transformation.yPos; chunks.append(newChunk); } TextChunk ¤tChunk = chunks.last(); if (it->transformation.hasRelativeOffset()) { TextChunk::SubChunkOffset o; o.start = currentChunk.text.size(); o.offset = it->transformation.relativeOffset(); KIS_SAFE_ASSERT_RECOVER_NOOP(!o.offset.isNull()); currentChunk.offsets.append(o); } QTextLayout::FormatRange formatRange; formatRange.start = currentChunk.text.size(); formatRange.length = it->text.size(); formatRange.format = it->format; currentChunk.formats.append(formatRange); currentChunk.text += it->text; } return chunks; } /** * Qt's QTextLayout has a weird trait, it doesn't count space characters as * distinct characters in QTextLayout::setNumColumns(), that is, if we want to * position a block of text that starts with a space character in a specific * position, QTextLayout will drop this space and will move the text to the left. * * That is why we have a special wrapper object that ensures that no spaces are * dropped and their horizontal advance parameter is taken into account. */ struct LayoutChunkWrapper { LayoutChunkWrapper(QTextLayout *layout) : m_layout(layout) { } QPointF addTextChunk(int startPos, int length, const QPointF &textChunkStartPos) { QPointF currentTextPos = textChunkStartPos; const int lastPos = startPos + length - 1; KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(startPos == m_addedChars, currentTextPos); KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(lastPos < m_layout->text().size(), currentTextPos); QTextLine line; std::swap(line, m_danglingLine); if (!line.isValid()) { line = m_layout->createLine(); } // skip all the space characters that were not included into the Qt's text line const int currentLineStart = line.isValid() ? line.textStart() : startPos + length; while (startPos < currentLineStart && startPos <= lastPos) { currentTextPos.rx() += skipSpaceCharacter(startPos); startPos++; } if (startPos <= lastPos) { const int numChars = lastPos - startPos + 1; line.setNumColumns(numChars); line.setPosition(currentTextPos - QPointF(0, line.ascent())); currentTextPos.rx() += line.horizontalAdvance(); // skip all the space characters that were not included into the Qt's text line for (int i = line.textStart() + line.textLength(); i < lastPos; i++) { currentTextPos.rx() += skipSpaceCharacter(i); } } else { // keep the created but unused line for future use std::swap(line, m_danglingLine); } m_addedChars += length; return currentTextPos; } private: qreal skipSpaceCharacter(int pos) { const QTextCharFormat format = formatForPos(pos, m_layout->formats()); const QChar skippedChar = m_layout->text()[pos]; KIS_SAFE_ASSERT_RECOVER_NOOP(skippedChar.isSpace() || !skippedChar.isPrint()); QFontMetrics metrics(format.font()); return metrics.width(skippedChar); } static QTextCharFormat formatForPos(int pos, const QVector &formats) { Q_FOREACH (const QTextLayout::FormatRange &range, formats) { if (pos >= range.start && pos < range.start + range.length) { return range.format; } } KIS_SAFE_ASSERT_RECOVER_NOOP(0 && "pos should be within the bounds of the layouted text"); return QTextCharFormat(); } private: int m_addedChars = 0; QTextLayout *m_layout; QTextLine m_danglingLine; }; void KoSvgTextShape::relayout() { Q_D(KoSvgTextShape); d->cachedLayouts.clear(); d->cachedLayoutsOffsets.clear(); d->cachedLayoutsWorkingThread = QThread::currentThread(); QPointF currentTextPos; QVector textChunks = mergeIntoChunks(layoutInterface()->collectSubChunks()); Q_FOREACH (const TextChunk &chunk, textChunks) { std::unique_ptr layout(new QTextLayout()); QTextOption option; // WARNING: never activate this option! It breaks the RTL text layout! //option.setFlags(QTextOption::ShowTabsAndSpaces); option.setWrapMode(QTextOption::WrapAnywhere); option.setUseDesignMetrics(true); // TODO: investigate if it is needed? option.setTextDirection(chunk.direction); layout->setText(chunk.text); layout->setTextOption(option); layout->setFormats(chunk.formats); layout->setCacheEnabled(true); layout->beginLayout(); currentTextPos = chunk.applyStartPosOverride(currentTextPos); const QPointF anchorPointPos = currentTextPos; int lastSubChunkStart = 0; QPointF lastSubChunkOffset; LayoutChunkWrapper wrapper(layout.get()); for (int i = 0; i <= chunk.offsets.size(); i++) { const bool isFinalPass = i == chunk.offsets.size(); const int length = !isFinalPass ? chunk.offsets[i].start - lastSubChunkStart : chunk.text.size() - lastSubChunkStart; if (length > 0) { currentTextPos += lastSubChunkOffset; currentTextPos = wrapper.addTextChunk(lastSubChunkStart, length, currentTextPos); } if (!isFinalPass) { lastSubChunkOffset = chunk.offsets[i].offset; lastSubChunkStart = chunk.offsets[i].start; } } layout->endLayout(); QPointF diff; if (chunk.alignment & Qt::AlignTrailing || chunk.alignment & Qt::AlignHCenter) { if (chunk.alignment & Qt::AlignTrailing) { diff = currentTextPos - anchorPointPos; } else if (chunk.alignment & Qt::AlignHCenter) { diff = 0.5 * (currentTextPos - anchorPointPos); } // TODO: fix after t2b text implemented diff.ry() = 0; } d->cachedLayouts.push_back(std::move(layout)); d->cachedLayoutsOffsets.push_back(-diff); } d->clearAssociatedOutlines(this); for (int i = 0; i < int(d->cachedLayouts.size()); i++) { const QTextLayout &layout = *d->cachedLayouts[i]; const QPointF layoutOffset = d->cachedLayoutsOffsets[i]; using namespace KoSvgText; Q_FOREACH (const QTextLayout::FormatRange &range, layout.formats()) { const KoSvgCharChunkFormat &format = static_cast(range.format); AssociatedShapeWrapper wrapper = format.associatedShapeWrapper(); const int rangeStart = range.start; const int safeRangeLength = range.length > 0 ? range.length : layout.text().size() - rangeStart; if (safeRangeLength <= 0) continue; const int rangeEnd = range.start + safeRangeLength - 1; const int firstLineIndex = layout.lineForTextPosition(rangeStart).lineNumber(); const int lastLineIndex = layout.lineForTextPosition(rangeEnd).lineNumber(); for (int i = firstLineIndex; i <= lastLineIndex; i++) { const QTextLine line = layout.lineAt(i); // It might happen that the range contains only one (or two) // symbol that is a whitespace symbol. In such a case we should // just skip this (invalid) line. if (!line.isValid()) continue; const int posStart = qMax(line.textStart(), rangeStart); const int posEnd = qMin(line.textStart() + line.textLength() - 1, rangeEnd); const QList glyphRuns = line.glyphRuns(posStart, posEnd - posStart + 1); Q_FOREACH (const QGlyphRun &run, glyphRuns) { const QPointF firstPosition = run.positions().first(); const quint32 firstGlyphIndex = run.glyphIndexes().first(); const QPointF lastPosition = run.positions().last(); const quint32 lastGlyphIndex = run.glyphIndexes().last(); const QRawFont rawFont = run.rawFont(); const QRectF firstGlyphRect = rawFont.boundingRect(firstGlyphIndex).translated(firstPosition); const QRectF lastGlyphRect = rawFont.boundingRect(lastGlyphIndex).translated(lastPosition); QRectF rect = run.boundingRect(); /** * HACK ALERT: there is a bug in a way how Qt calculates boundingRect() * of the glyph run. It doesn't care about left and right bearings * of the border chars in the run, therefore it becomes cropped. * * Here we just add a half-char width margin to both sides * of the glyph run to make sure the glyphs are fully painted. * * BUG: 389528 * BUG: 392068 */ rect.setLeft(qMin(rect.left(), lastGlyphRect.left()) - 0.5 * firstGlyphRect.width()); rect.setRight(qMax(rect.right(), lastGlyphRect.right()) + 0.5 * lastGlyphRect.width()); wrapper.addCharacterRect(rect.translated(layoutOffset)); } } } } } void KoSvgTextShapePrivate::clearAssociatedOutlines(KoShape *rootShape) { KoSvgTextChunkShape *chunkShape = dynamic_cast(rootShape); KIS_SAFE_ASSERT_RECOVER_RETURN(chunkShape); chunkShape->layoutInterface()->clearAssociatedOutline(); Q_FOREACH (KoShape *child, chunkShape->shapes()) { clearAssociatedOutlines(child); } } bool KoSvgTextShape::isRootTextNode() const { return true; } KoSvgTextShapeFactory::KoSvgTextShapeFactory() : KoShapeFactoryBase(KoSvgTextShape_SHAPEID, i18n("Text")) { setToolTip(i18n("SVG Text Shape")); setIconName(koIconNameCStr("x-shape-text")); setLoadingPriority(5); setXmlElementNames(KoXmlNS::svg, QStringList("text")); KoShapeTemplate t; t.name = i18n("SVG Text"); t.iconName = koIconName("x-shape-text"); t.toolTip = i18n("SVG Text Shape"); addTemplate(t); } KoShape *KoSvgTextShapeFactory::createDefaultShape(KoDocumentResourceManager *documentResources) const { debugFlake << "Create default svg text shape"; KoSvgTextShape *shape = new KoSvgTextShape(); shape->setShapeId(KoSvgTextShape_SHAPEID); KoSvgTextShapeMarkupConverter converter(shape); converter.convertFromSvg("Lorem ipsum dolor sit amet, consectetur adipiscing elit.", "", QRectF(0, 0, 200, 60), documentResources->shapeController()->pixelsPerInch()); debugFlake << converter.errors() << converter.warnings(); return shape; } KoShape *KoSvgTextShapeFactory::createShape(const KoProperties *params, KoDocumentResourceManager *documentResources) const { KoSvgTextShape *shape = new KoSvgTextShape(); shape->setShapeId(KoSvgTextShape_SHAPEID); QString svgText = params->stringProperty("svgText", "Lorem ipsum dolor sit amet, consectetur adipiscing elit."); QString defs = params->stringProperty("defs" , ""); QRectF shapeRect = QRectF(0, 0, 200, 60); QVariant rect = params->property("shapeRect"); if (rect.type()==QVariant::RectF) { shapeRect = rect.toRectF(); } KoSvgTextShapeMarkupConverter converter(shape); converter.convertFromSvg(svgText, defs, shapeRect, documentResources->shapeController()->pixelsPerInch()); - shape->setBackground(QSharedPointer(new KoColorBackground(QColor(Qt::black)))); shape->setPosition(shapeRect.topLeft()); return shape; } bool KoSvgTextShapeFactory::supports(const KoXmlElement &/*e*/, KoShapeLoadingContext &/*context*/) const { return false; } diff --git a/plugins/tools/svgtexttool/SvgTextEditor.cpp b/plugins/tools/svgtexttool/SvgTextEditor.cpp index 1f723f6a10..6032a35486 100644 --- a/plugins/tools/svgtexttool/SvgTextEditor.cpp +++ b/plugins/tools/svgtexttool/SvgTextEditor.cpp @@ -1,1107 +1,1108 @@ /* 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 "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")); QByteArray state; if (cg.hasKey("WindowState")) { state = cg.readEntry("State", state); state = QByteArray::fromBase64(state); // One day will need to load the version number, but for now, assume 0 restoreState(state); } setAcceptDrops(true); //setStandardToolBarMenuEnabled(true); #ifdef Q_OS_OSX 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()); } 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); 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')); } } } 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())); 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); } 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()); + qDebug() << "fg" << fg.toQString(fg); 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())); } } } 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; 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); } 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; format.setFontItalic(!m_textEditorWidget.richTextEdit->textCursor().charFormat().fontItalic()); m_textEditorWidget.richTextEdit->mergeCurrentCharFormat(format); } else { if (cursor.hasSelection()) { QString selectionModified = "" + cursor.selectedText() + ""; cursor.removeSelectedText(); cursor.insertText(selectionModified); } } } void SvgTextEditor::setTextDecoration(KoSvgText::TextDecoration decor) { QTextCursor cursor = m_textEditorWidget.svgTextEdit->textCursor(); 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); } } } 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() { 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); } void SvgTextEditor::decreaseTextSize() { 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); } void SvgTextEditor::setLineHeight(double lineHeightPercentage) { QTextBlockFormat format = m_textEditorWidget.richTextEdit->textCursor().blockFormat(); format.setLineHeight(lineHeightPercentage, QTextBlockFormat::ProportionalHeight); m_textEditorWidget.richTextEdit->textCursor().mergeBlockFormat(format); } void SvgTextEditor::alignLeft() { QTextBlockFormat format = m_textEditorWidget.richTextEdit->textCursor().blockFormat(); format.setAlignment(Qt::AlignLeft); m_textEditorWidget.richTextEdit->textCursor().mergeBlockFormat(format); } void SvgTextEditor::alignRight() { QTextBlockFormat format = m_textEditorWidget.richTextEdit->textCursor().blockFormat(); format.setAlignment(Qt::AlignRight); m_textEditorWidget.richTextEdit->textCursor().mergeBlockFormat(format); } void SvgTextEditor::alignCenter() { QTextBlockFormat format = m_textEditorWidget.richTextEdit->textCursor().blockFormat(); format.setAlignment(Qt::AlignCenter); m_textEditorWidget.richTextEdit->textCursor().mergeBlockFormat(format); } void SvgTextEditor::alignJustified() { QTextBlockFormat format = m_textEditorWidget.richTextEdit->textCursor().blockFormat(); format.setAlignment(Qt::AlignJustify); m_textEditorWidget.richTextEdit->textCursor().mergeBlockFormat(format); } 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().background().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())); } } if (!writingSystems.isEmpty()) { 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) { QTextCharFormat format; format.setForeground(QBrush(color)); m_textEditorWidget.richTextEdit->mergeCurrentCharFormat(format); } 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) { m_textEditorWidget.richTextEdit->mergeCurrentCharFormat(format); } 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) { QTextCharFormat format; format.setFontPointSize(fontSize); m_textEditorWidget.richTextEdit->mergeCurrentCharFormat(format); } 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(); } } 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().background().color()); palette.setBrush(QPalette::Active, QPalette::Background, QBrush(background)); 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(qobject_cast(actionCollection()->action("svg_font"))->defaultWidget())->refillComboBox(writingSystems); 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 *lineHeight = new QWidgetAction(this); lineHeight->setToolTip(i18n("Line height")); QDoubleSpinBox *spnLineHeight = new QDoubleSpinBox(); spnLineHeight->setRange(0.0, 1000.0); spnLineHeight->setSingleStep(10.0); spnLineHeight->setSuffix("%"); 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); } 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(); } diff --git a/plugins/tools/svgtexttool/SvgTextTool.cpp b/plugins/tools/svgtexttool/SvgTextTool.cpp index 8d7d974a2f..5bff31d86e 100644 --- a/plugins/tools/svgtexttool/SvgTextTool.cpp +++ b/plugins/tools/svgtexttool/SvgTextTool.cpp @@ -1,415 +1,419 @@ /* 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 "SvgTextTool.h" #include "KoSvgTextShape.h" #include "SvgTextChangeCommand.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include "kis_assert.h" #include #include #include #include #include #include #include #include #include #include #include #include #include "KoToolManager.h" +#include "KoCanvasResourceProvider.h" #include "SvgTextEditor.h" #include "KisHandlePainterHelper.h" #include SvgTextTool::SvgTextTool(KoCanvasBase *canvas) : KoToolBase(canvas) , m_editor(0) , m_dragStart( 0, 0) , m_dragEnd( 0, 0) , m_dragging(false) { } SvgTextTool::~SvgTextTool() { } void SvgTextTool::activate(ToolActivation activation, const QSet &shapes) { KoToolBase::activate(activation, shapes); useCursor(Qt::ArrowCursor); if (shapes.size() == 1) { KoSvgTextShape *textShape = dynamic_cast(*shapes.constBegin()); if (!textShape) { koSelection()->deselectAll(); } else { // if we are a text shape...and the proxy tells us we want to edit the shape. open the text editor if (canvas()->selectedShapesProxy()->isRequestingToBeEdited()) { showEditor(); } } } else if (shapes.size() > 1) { KoSvgTextShape *foundTextShape = 0; Q_FOREACH (KoShape *shape, shapes) { KoSvgTextShape *textShape = dynamic_cast(shape); if (textShape) { foundTextShape = textShape; break; } } koSelection()->deselectAll(); if (foundTextShape) { koSelection()->select(foundTextShape); } } } void SvgTextTool::deactivate() { KoToolBase::deactivate(); QRectF updateRect = m_hoveredShapeHighlightRect; KoSvgTextShape *shape = selectedShape(); if (shape) { updateRect |= shape->boundingRect(); } m_hoveredShapeHighlightRect = QRectF(); canvas()->updateCanvas(updateRect); } QWidget *SvgTextTool::createOptionWidget() { QWidget *optionWidget = new QWidget(); QGridLayout *layout = new QGridLayout(optionWidget); m_configGroup = KSharedConfig::openConfig()->group(toolId()); QGroupBox *defsOptions = new QGroupBox(i18n("Create new texts with...")); QVBoxLayout *defOptionsLayout = new QVBoxLayout(); defsOptions->setLayout(defOptionsLayout); m_defFont = new QFontComboBox(); QString storedFont = m_configGroup.readEntry("defaultFont", QApplication::font().family()); m_defFont->setCurrentFont(QFont(storedFont)); defsOptions->layout()->addWidget(m_defFont); m_defPointSize = new QComboBox(); Q_FOREACH (int size, QFontDatabase::standardSizes()) { m_defPointSize->addItem(QString::number(size)+" pt"); } int storedSize = m_configGroup.readEntry("defaultSize", QApplication::font().pointSize()); m_defPointSize->setCurrentIndex(QFontDatabase::standardSizes().indexOf(storedSize)); int checkedAlignment = m_configGroup.readEntry("defaultAlignment", 0); m_defAlignment = new QButtonGroup(); QHBoxLayout *alignButtons = new QHBoxLayout(); alignButtons->addWidget(m_defPointSize); QToolButton *alignLeft = new QToolButton(); alignLeft->setIcon(KisIconUtils::loadIcon("format-justify-left")); alignLeft->setCheckable(true); alignLeft->setToolTip(i18n("Anchor text to the left.")); m_defAlignment->addButton(alignLeft, 0); alignButtons->addWidget(alignLeft); QToolButton *alignCenter = new QToolButton(); alignCenter->setIcon(KisIconUtils::loadIcon("format-justify-center")); alignCenter->setCheckable(true); m_defAlignment->addButton(alignCenter, 1); alignCenter->setToolTip(i18n("Anchor text to the middle.")); alignButtons->addWidget(alignCenter); QToolButton *alignRight = new QToolButton(); alignRight->setIcon(KisIconUtils::loadIcon("format-justify-right")); alignRight->setCheckable(true); m_defAlignment->addButton(alignRight, 2); alignRight->setToolTip(i18n("Anchor text to the right.")); alignButtons->addWidget(alignRight); m_defAlignment->setExclusive(true); if (checkedAlignment<1) { alignLeft->setChecked(true); } else if (checkedAlignment==1) { alignCenter->setChecked(true); } else if (checkedAlignment==2) { alignRight->setChecked(true); } else { alignLeft->setChecked(true); } defOptionsLayout->addLayout(alignButtons); layout->addWidget(defsOptions); connect(m_defAlignment, SIGNAL(buttonClicked(int)), this, SLOT(storeDefaults())); connect(m_defFont, SIGNAL(currentFontChanged(QFont)), this, SLOT(storeDefaults())); connect(m_defPointSize, SIGNAL(currentIndexChanged(int)), this, SLOT(storeDefaults())); m_edit = new QPushButton(optionWidget); m_edit->setText(i18n("Edit Text")); connect(m_edit, SIGNAL(clicked(bool)), SLOT(showEditor())); layout->addWidget(m_edit); return optionWidget; } KoSelection *SvgTextTool::koSelection() const { KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(canvas(), 0); KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(canvas()->selectedShapesProxy(), 0); return canvas()->selectedShapesProxy()->selection(); } KoSvgTextShape *SvgTextTool::selectedShape() const { KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(canvas(), 0); KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(canvas()->selectedShapesProxy(), 0); QList shapes = koSelection()->selectedEditableShapes(); if (shapes.isEmpty()) return 0; KIS_SAFE_ASSERT_RECOVER_NOOP(shapes.size() == 1); KoSvgTextShape *textShape = dynamic_cast(shapes.first()); return textShape; } void SvgTextTool::showEditor() { KoSvgTextShape *shape = selectedShape(); if (!shape) return; if (!m_editor) { m_editor = new SvgTextEditor(); m_editor->setWindowModality(Qt::ApplicationModal); + connect(m_editor, SIGNAL(textUpdated(KoSvgTextShape*,QString,QString,bool)), SLOT(textUpdated(KoSvgTextShape*,QString,QString,bool))); connect(m_editor, SIGNAL(textEditorClosed()), SLOT(slotTextEditorClosed())); } m_editor->setShape(shape); m_editor->show(); m_editor->activateWindow(); } void SvgTextTool::textUpdated(KoSvgTextShape *shape, const QString &svg, const QString &defs, bool richTextUpdated) { SvgTextChangeCommand *cmd = new SvgTextChangeCommand(shape, svg, defs, richTextUpdated); canvas()->addCommand(cmd); } void SvgTextTool::slotTextEditorClosed() { // change tools to the shape selection tool when we close the text editor to allow moving and further editing of the object. // most of the time when we edit text, the shape selection tool is where we left off anyway KoToolManager::instance()->switchToolRequested("InteractionTool"); } QString SvgTextTool::generateDefs() { QString font = m_defFont->currentFont().family(); QString size = QString::number(QFontDatabase::standardSizes().at(m_defPointSize->currentIndex())); QString textAnchor = "middle"; if (m_defAlignment->button(0)->isChecked()) { textAnchor = "start"; } if (m_defAlignment->button(2)->isChecked()) { textAnchor = "end"; } - return QString("\n \n").arg(font, size, textAnchor); + QString fontColor = canvas()->resourceManager()->foregroundColor().toQColor().name(); + + return QString("\n \n").arg(font, size, fontColor, textAnchor); } void SvgTextTool::storeDefaults() { m_configGroup = KSharedConfig::openConfig()->group(toolId()); m_configGroup.writeEntry("defaultFont", m_defFont->currentFont().family()); m_configGroup.writeEntry("defaultSize", QFontDatabase::standardSizes().at(m_defPointSize->currentIndex())); m_configGroup.writeEntry("defaultAlignment", m_defAlignment->checkedId()); } void SvgTextTool::paint(QPainter &gc, const KoViewConverter &converter) { if (!isActivated()) return; KoShape::applyConversion(gc, converter); KisHandlePainterHelper handlePainter(&gc); if (m_dragging) { QPolygonF poly(QRectF(m_dragStart, m_dragEnd)); handlePainter.setHandleStyle(KisHandleStyle::primarySelection()); handlePainter.drawRubberLine(poly); } KoSvgTextShape *shape = selectedShape(); if (shape) { handlePainter.setHandleStyle(KisHandleStyle::primarySelection()); QPainterPath path; path.addRect(shape->boundingRect()); handlePainter.drawPath(path); } if (!m_hoveredShapeHighlightRect.isEmpty()) { handlePainter.setHandleStyle(KisHandleStyle::highlightedPrimaryHandlesWithSolidOutline()); QPainterPath path; path.addRect(m_hoveredShapeHighlightRect); handlePainter.drawPath(path); } } void SvgTextTool::mousePressEvent(KoPointerEvent *event) { KoSvgTextShape *selectedShape = this->selectedShape(); KoSvgTextShape *hoveredShape = dynamic_cast(canvas()->shapeManager()->shapeAt(event->point)); if (!selectedShape || hoveredShape != selectedShape) { canvas()->shapeManager()->selection()->deselectAll(); if (hoveredShape) { canvas()->shapeManager()->selection()->select(hoveredShape); } else { m_dragStart = m_dragEnd = event->point; m_dragging = true; } event->accept(); } else { event->ignore(); } } void SvgTextTool::mouseMoveEvent(KoPointerEvent *event) { QRectF updateRect = m_hoveredShapeHighlightRect; if (m_dragging) { m_dragEnd = event->point; m_hoveredShapeHighlightRect = QRectF(); updateRect |= QRectF(m_dragStart, m_dragEnd).normalized().toAlignedRect(); event->accept(); } else { KoSvgTextShape *hoveredShape = dynamic_cast(canvas()->shapeManager()->shapeAt(event->point)); if (hoveredShape) { m_hoveredShapeHighlightRect = hoveredShape->boundingRect(); updateRect |= m_hoveredShapeHighlightRect; } else { m_hoveredShapeHighlightRect = QRect(); } event->ignore(); } if (!updateRect.isEmpty()) { canvas()->updateCanvas(kisGrowRect(updateRect, 100)); } } void SvgTextTool::mouseReleaseEvent(KoPointerEvent *event) { if (m_dragging) { QRectF rectangle = QRectF(m_dragStart, m_dragEnd).normalized(); if (rectangle.width() < 4 && rectangle.height() < 4) { m_dragging = false; event->accept(); return; } KoShapeFactoryBase *factory = KoShapeRegistry::instance()->value("KoSvgTextShapeID"); KoProperties *params = new KoProperties();//Fill these with "svgText", "defs" and "shapeRect" params->setProperty("defs", QVariant(generateDefs())); if (m_dragging) { m_dragEnd = event->point; m_dragging = false; //The following show only happen when we're creating preformatted text. If we're making //Word-wrapped text, it should take the rectangle unmodified. int size = QFontDatabase::standardSizes().at(m_defPointSize->currentIndex()); QFont font = m_defFont->currentFont(); font.setPointSize(size); rectangle.setTop(rectangle.top()+QFontMetrics(font).lineSpacing()); if (m_defAlignment->button(1)->isChecked()) { rectangle.setLeft(rectangle.center().x()); } else if (m_defAlignment->button(2)->isChecked()) { qreal right = rectangle.right(); rectangle.setRight(right+10); rectangle.setLeft(right); } params->setProperty("shapeRect", QVariant(rectangle)); } KoShape *textShape = factory->createShape( params, canvas()->shapeController()->resourceManager()); KUndo2Command *parentCommand = new KUndo2Command(); new KoKeepShapesSelectedCommand(koSelection()->selectedShapes(), {}, canvas()->selectedShapesProxy(), false, parentCommand); KUndo2Command *cmd = canvas()->shapeController()->addShape(textShape, 0, parentCommand); parentCommand->setText(cmd->text()); new KoKeepShapesSelectedCommand({}, {textShape}, canvas()->selectedShapesProxy(), true, parentCommand); canvas()->addCommand(parentCommand); showEditor(); } event->accept(); } void SvgTextTool::keyPressEvent(QKeyEvent *event) { if (event->key()==Qt::Key_Enter || event->key()==Qt::Key_Return) { showEditor(); event->accept(); } else { event->ignore(); } } void SvgTextTool::mouseDoubleClickEvent(KoPointerEvent *event) { if (canvas()->shapeManager()->shapeAt(event->point) != selectedShape()) { event->ignore(); // allow the event to be used by another return; } showEditor(); }