diff --git a/libs/flake/KoShapeLoadingContext.cpp b/libs/flake/KoShapeLoadingContext.cpp index 79b7995bdd..d2a34835a7 100644 --- a/libs/flake/KoShapeLoadingContext.cpp +++ b/libs/flake/KoShapeLoadingContext.cpp @@ -1,221 +1,221 @@ /* This file is part of the KDE project Copyright (C) 2007-2009, 2011 Thorsten Zachmann Copyright (C) 2007 Jan Hambrecht - Copyright (C) 2014 Denis Kuplyakov + Copyright (C) 2014-2015 Denis Kuplyakov 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 "KoShapeLoadingContext.h" #include "KoShape.h" #include "KoShapeContainer.h" #include "KoSharedLoadingData.h" #include "KoShapeBasedDocumentBase.h" #include "KoImageCollection.h" #include "KoMarkerCollection.h" #include "KoDocumentResourceManager.h" #include "KoLoadingShapeUpdater.h" #include uint qHash(const KoShapeLoadingContext::AdditionalAttributeData & attributeData) { return qHash(attributeData.name); } static QSet s_additionlAttributes; class Q_DECL_HIDDEN KoShapeLoadingContext::Private { public: Private(KoOdfLoadingContext &c, KoDocumentResourceManager *resourceManager) : context(c) , zIndex(0) , documentResources(resourceManager) , documentRdf(0) - , sectionManager(0) + , sectionModel(0) { } ~Private() { foreach(KoSharedLoadingData * data, sharedData) { delete data; } } KoOdfLoadingContext &context; QMap layers; QMap drawIds; QMap > subIds; QMap sharedData; //FIXME: use QScopedPointer here to auto delete in destructor int zIndex; QMap updaterById; QMap updaterByShape; KoDocumentResourceManager *documentResources; QObject *documentRdf; - KoSectionManager *sectionManager; + KoSectionModel *sectionModel; }; KoShapeLoadingContext::KoShapeLoadingContext(KoOdfLoadingContext & context, KoDocumentResourceManager *documentResources) : d(new Private(context, documentResources)) { if (d->documentResources) { KoMarkerCollection *markerCollection = d->documentResources->resource(KoDocumentResourceManager::MarkerCollection).value(); if (markerCollection) { markerCollection->loadOdf(*this); } } } KoShapeLoadingContext::~KoShapeLoadingContext() { delete d; } KoOdfLoadingContext & KoShapeLoadingContext::odfLoadingContext() { return d->context; } KoShapeLayer * KoShapeLoadingContext::layer(const QString & layerName) { return d->layers.value(layerName, 0); } void KoShapeLoadingContext::addLayer(KoShapeLayer * layer, const QString & layerName) { d->layers[ layerName ] = layer; } void KoShapeLoadingContext::clearLayers() { d->layers.clear(); } void KoShapeLoadingContext::addShapeId(KoShape * shape, const QString & id) { d->drawIds.insert(id, shape); QMap::iterator it(d->updaterById.find(id)); while (it != d->updaterById.end() && it.key() == id) { d->updaterByShape.insertMulti(shape, it.value()); it = d->updaterById.erase(it); } } KoShape * KoShapeLoadingContext::shapeById(const QString &id) { return d->drawIds.value(id, 0); } void KoShapeLoadingContext::addShapeSubItemId(KoShape *shape, const QVariant &subItem, const QString &id) { d->subIds.insert(id, QPair(shape, subItem)); } QPair KoShapeLoadingContext::shapeSubItemById(const QString &id) { return d->subIds.value(id); } // TODO make sure to remove the shape from the loading context when loading for it failed and it was deleted. This can also happen when the parent is deleted void KoShapeLoadingContext::updateShape(const QString & id, KoLoadingShapeUpdater * shapeUpdater) { d->updaterById.insertMulti(id, shapeUpdater); } void KoShapeLoadingContext::shapeLoaded(KoShape * shape) { QMap::iterator it(d->updaterByShape.find(shape)); while (it != d->updaterByShape.end() && it.key() == shape) { it.value()->update(shape); delete it.value(); it = d->updaterByShape.erase(it); } } KoImageCollection * KoShapeLoadingContext::imageCollection() { return d->documentResources ? d->documentResources->imageCollection() : 0; } int KoShapeLoadingContext::zIndex() { return d->zIndex++; } void KoShapeLoadingContext::setZIndex(int index) { d->zIndex = index; } void KoShapeLoadingContext::addSharedData(const QString & id, KoSharedLoadingData * data) { QMap::iterator it(d->sharedData.find(id)); // data will not be overwritten if (it == d->sharedData.end()) { d->sharedData.insert(id, data); } else { kWarning(30006) << "The id" << id << "is already registered. Data not inserted"; Q_ASSERT(it == d->sharedData.end()); } } KoSharedLoadingData * KoShapeLoadingContext::sharedData(const QString & id) const { KoSharedLoadingData * data = 0; QMap::const_iterator it(d->sharedData.find(id)); if (it != d->sharedData.constEnd()) { data = it.value(); } return data; } void KoShapeLoadingContext::addAdditionalAttributeData(const AdditionalAttributeData & attributeData) { s_additionlAttributes.insert(attributeData); } QSet KoShapeLoadingContext::additionalAttributeData() { return s_additionlAttributes; } KoDocumentResourceManager *KoShapeLoadingContext::documentResourceManager() const { return d->documentResources; } QObject *KoShapeLoadingContext::documentRdf() const { return d->documentRdf; } void KoShapeLoadingContext::setDocumentRdf(QObject *documentRdf) { d->documentRdf = documentRdf; } -KoSectionManager* KoShapeLoadingContext::sectionManager() +KoSectionModel *KoShapeLoadingContext::sectionModel() { - return d->sectionManager; + return d->sectionModel; } -void KoShapeLoadingContext::setSectionManager(KoSectionManager *sectionManager) +void KoShapeLoadingContext::setSectionModel(KoSectionModel *sectionModel) { - d->sectionManager = sectionManager; + d->sectionModel = sectionModel; } diff --git a/libs/flake/KoShapeLoadingContext.h b/libs/flake/KoShapeLoadingContext.h index b5c8659b97..5b804a12a3 100644 --- a/libs/flake/KoShapeLoadingContext.h +++ b/libs/flake/KoShapeLoadingContext.h @@ -1,211 +1,211 @@ /* This file is part of the KDE project Copyright (C) 2007 Thorsten Zachmann Copyright (C) 2007 Jan Hambrecht - Copyright (C) 2014 Denis Kuplyakov + Copyright (C) 2014-2015 Denis Kuplyakov This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef KOSHAPELOADINGCONTEXT_H #define KOSHAPELOADINGCONTEXT_H #include #include #include #include #include "flake_export.h" class KoOdfLoadingContext; class KoShapeLayer; class KoShape; class KoShapeBasedDocumentBase; class KoLoadingShapeUpdater; class KoImageCollection; class KoSharedLoadingData; class KoDocumentResourceManager; -class KoSectionManager; +class KoSectionModel; /** * Context passed to shapes during loading. * This class holds various variables as well as a context full of variables which all together * form the context of a loading operation. */ class FLAKE_EXPORT KoShapeLoadingContext { public: /** * Struct to store data about additional attributes that should be loaded during * the shape loading. * * Make sure all parameters point to const char * that stay around. e.g. The a KoXmlNS or * a "tag" defined string e.g. * AdditionalAttributeData( KoXmlNS::presentation, "placeholder", presentation:placeholder" ) */ struct AdditionalAttributeData { AdditionalAttributeData(const QString &ns, const QString &tag, const QString &name) : ns(ns) , tag(tag) , name(name) { } const QString ns; const QString tag; const QString name; bool operator==(const AdditionalAttributeData &other) const { return name == other.name; } }; /** * constructor * @param context the context created for generic ODF loading. * @param documentResources the data of the shape controller. */ KoShapeLoadingContext(KoOdfLoadingContext &context, KoDocumentResourceManager *documentResources); /// destructor ~KoShapeLoadingContext(); /// return the embedded loading context KoOdfLoadingContext &odfLoadingContext(); /// Returns layer referenced by given name KoShapeLayer *layer(const QString &layerName); /// Adds a new layer to be referenced by the given name later void addLayer(KoShapeLayer *layer, const QString &layerName); /** * remove all layers * * This can be used for loading different layer sets per page. */ void clearLayers(); /// register the id for a specific shape void addShapeId(KoShape *shape, const QString &id); /// return the shape formerly registered using addShapeId() KoShape *shapeById(const QString &id); /// register the id for a specific shape sub item void addShapeSubItemId(KoShape *shape, const QVariant &subItem, const QString &id); /// return the shape and subitem formerly registered using addShapeSubItemId() QPair shapeSubItemById(const QString &id); /** * call function on the shapeUpdater when the shape with the id shapeid is inserted * After that destroy the updater. */ void updateShape(const QString &id, KoLoadingShapeUpdater *shapeUpdater); /** * this checks if there is an updater for this shape if yes it calls it * this needs to be done via the shape id and */ void shapeLoaded(KoShape *shape); /// Returns the image collection for loading images KoImageCollection *imageCollection(); /// Get current z-index int zIndex(); /// Set z-index void setZIndex(int index); /** * Add shared data * * This can be use to pass data between shapes on loading. E.g. The decoded text styles * of the TextShape. With that the styles only have to be read once and can be used in * all shapes that also need them. * * The ownership of the added data is passed to the context. The KoShapeLoadingContext will * delete the added data when it is destroyed. * * Data inserted for a specific id will not be overwritten by calling addSharedData with * the same id again. * * You get an assertion when the id is already existing. * * @see KoSharedLoadingData */ void addSharedData(const QString &id, KoSharedLoadingData *data); /** * Get the shared data. * * @see KoSharedLoadingData * * @param id The id used to identify the shared data. * @return The shared data for the id or 0 if there is no shared data for the id. */ KoSharedLoadingData *sharedData(const QString &id) const; /** * @brief Add an additional attribute that should be loaded during shape loading * * An application can use that to set the data for additional attributes that should be * loaded during shape loading. * If attribute is set it will not change if set again. The tag is used to differentiate * the attributes * * @param attributeData The data describing the additional attribute data */ static void addAdditionalAttributeData(const AdditionalAttributeData &attributeData); /** * @brief Get the additional attribute data for loading of a shape * * This is used by KoShape::loadOdfAttributes to load all additional attributes defined * in the returned set. */ static QSet additionalAttributeData(); KoDocumentResourceManager *documentResourceManager() const; /** * @brief get the rdf document * @return the rdf document, or 0 if there is none set/ */ QObject *documentRdf() const; /** * @brief setDocumentRdf sets the rdf document for the loading context * @param documentRdf the rdf document -- it needs to have been loaded already */ void setDocumentRdf(QObject *documentRdf); /** - * @brief returns the current section manager - * @return the pointer to KoSectionManager + * @brief returns the current section model + * @return the pointer to KoSectionModel */ - KoSectionManager *sectionManager(); + KoSectionModel *sectionModel(); /** - * @brief sets the section manager for the loading context - * @param sectionManager the section manager to set + * @brief sets the section model for the loading context + * @param sectionModel the section model to set */ - void setSectionManager(KoSectionManager *sectionManager); + void setSectionModel(KoSectionModel *sectionModel); private: // to allow only the KoShapeRegistry access to the KoShapeBasedDocumentBase class Private; Private * const d; }; #endif /* KOSHAPELOADINGCONTEXT_H */ diff --git a/libs/kotext/CMakeLists.txt b/libs/kotext/CMakeLists.txt index d5c3b2d895..e714763e47 100644 --- a/libs/kotext/CMakeLists.txt +++ b/libs/kotext/CMakeLists.txt @@ -1,231 +1,232 @@ include_directories(${KOMAIN_INCLUDES} ${FONTCONFIG_INCLUDE_DIR}/fontconfig ${FREETYPE_INCLUDE_DIRS}) add_subdirectory( tests ) add_subdirectory( styles/tests ) ########### next target ############### set(kotext_LIB_SRCS KoDocumentRdfBase.cpp KoText.cpp KoTextBlockData.cpp KoTextBlockBorderData.cpp KoTextBlockPaintStrategyBase.cpp KoTextOdfSaveHelper.cpp KoTextPaste.cpp KoTextDocument.cpp KoTextEditor.cpp KoTextEditor_undo.cpp KoTextEditor_format.cpp KoList.cpp KoTextEditingRegistry.cpp KoTextEditingFactory.cpp KoTextEditingPlugin.cpp KoTextRangeManager.cpp KoInlineTextObjectManager.cpp KoInlineObjectFactoryBase.cpp KoInlineObjectRegistry.cpp InsertInlineObjectActionBase_p.cpp InsertVariableAction.cpp InsertNamedVariableAction.cpp InsertTextReferenceAction.cpp InsertTextLocator.cpp KoInlineObject.cpp KoTextRange.cpp KoVariable.cpp KoVariableManager.cpp KoNamedVariable.cpp KoSection.cpp KoSectionEnd.cpp KoSectionUtils.cpp - KoSectionManager.cpp + KoSectionModel.cpp KoTextLocator.cpp KoTextReference.cpp KoAnchorInlineObject.cpp KoAnchorTextRange.cpp KoTextShapeSavingContext.cpp KoAnnotation.cpp KoAnnotationManager.cpp KoBookmark.cpp KoBookmarkManager.cpp KoInlineNote.cpp KoInlineCite.cpp KoTextSoftPageBreak.cpp FindDirection_p.cpp KoFindStrategy.cpp KoReplaceStrategy.cpp KoFind_p.cpp KoFind.cpp KoTextDebug.cpp KoTextPage.cpp KoPageProvider.cpp KoTableColumnAndRowStyleManager.cpp KoTextInlineRdf.cpp KoTextMeta.cpp KoTextTableTemplate.cpp OdfTextTrackStyles.cpp ToCBibGeneratorInfo.cpp KoTableOfContentsGeneratorInfo.cpp KoBibliographyInfo.cpp BibliographyGenerator.cpp styles/Styles_p.cpp styles/KoCharacterStyle.cpp styles/KoParagraphStyle.cpp styles/KoStyleManager.cpp styles/KoListStyle.cpp styles/KoListLevelProperties.cpp styles/KoTableStyle.cpp styles/KoTableColumnStyle.cpp styles/KoTableRowStyle.cpp styles/KoTableCellStyle.cpp styles/KoSectionStyle.cpp opendocument/KoTextSharedLoadingData.cpp opendocument/KoTextSharedSavingData.cpp opendocument/KoTextLoader.cpp opendocument/KoTextWriter_p.cpp opendocument/KoTextWriter.cpp changetracker/KoChangeTracker.cpp changetracker/KoChangeTrackerElement.cpp changetracker/KoFormatChangeInformation.cpp changetracker/KoDeletedRowColumnDataStore.cpp changetracker/KoDeletedRowData.cpp changetracker/KoDeletedColumnData.cpp changetracker/KoDeletedCellData.cpp commands/ChangeAnchorPropertiesCommand.cpp commands/ChangeListCommand.cpp commands/ChangeStylesCommand.cpp commands/ChangeStylesMacroCommand.cpp commands/DeleteAnchorsCommand.cpp commands/DeleteAnnotationsCommand.cpp commands/DeleteCommand.cpp commands/DeleteTableColumnCommand.cpp commands/DeleteTableRowCommand.cpp commands/InsertNoteCommand.cpp commands/InsertTableColumnCommand.cpp commands/InsertTableRowCommand.cpp commands/ResizeTableCommand.cpp commands/InsertInlineObjectCommand.cpp commands/ListItemNumberingCommand.cpp commands/TextPasteCommand.cpp commands/AddTextRangeCommand.cpp commands/AddAnnotationCommand.cpp commands/ParagraphFormattingCommand.cpp commands/RenameSectionCommand.cpp commands/NewSectionCommand.cpp + commands/SplitSectionsCommand.cpp KoTextDrag.cpp KoTextCommandBase.cpp ) if( SHOULD_BUILD_FEATURE_RDF ) set(kotext_LIB_SRCS ${kotext_LIB_SRCS} KoTextRdfCore.cpp ) endif() add_library(kotext SHARED ${kotext_LIB_SRCS}) # generate_export_header(kotext BASE_NAME kotext) target_link_libraries(kotext flake) if( SHOULD_BUILD_FEATURE_RDF ) target_link_libraries(kotext ${SOPRANO_LIBRARIES}) endif() target_link_libraries(kotext LINK_INTERFACE_LIBRARIES flake) if( FONTCONFIG_FOUND ) target_link_libraries(kotext ${FONTCONFIG_LIBRARIES}) endif() if( FREETYPE_FOUND ) target_link_libraries(kotext ${FREETYPE_LIBRARIES}) endif() set_target_properties(kotext PROPERTIES VERSION ${GENERIC_CALLIGRA_LIB_VERSION} SOVERSION ${GENERIC_CALLIGRA_LIB_SOVERSION} ) install(TARGETS kotext ${INSTALL_TARGETS_DEFAULT_ARGS}) ########### install files ############### install( FILES texteditingplugin.desktop inlinetextobject.desktop DESTINATION ${SERVICETYPES_INSTALL_DIR}) install( FILES kotext_export.h KoDocumentRdfBase.h KoInlineObject.h KoTextEditor.h KoTextEditingFactory.h KoTextEditingPlugin.h KoTextEditingRegistry.h KoInlineObjectRegistry.h KoInlineObjectFactoryBase.h KoBookmark.h KoBookmarkManager.h KoAnnotationManager.h KoInlineTextObjectManager.h KoAnchorInlineObject.h KoAnchorTextRange.h KoTextBlockBorderData.h KoTextBlockData.h KoTextDocument.h KoText.h KoTextRange.h KoTextRangeManager.h KoList.h KoTextLocator.h KoTextPage.h KoTextPaste.h KoVariable.h KoVariableManager.h KoTextRdfCore.h KoTextInlineRdf.h KoTextMeta.h KoTextSoftPageBreak.cpp KoSection.h KoSectionEnd.h KoSectionUtils.h - KoSectionManager.h + KoSectionModel.h KoTextCommandBase.h KoTextTableTemplate.h DESTINATION ${INCLUDE_INSTALL_DIR}/calligra COMPONENT Devel ) install( FILES styles/KoCharacterStyle.h styles/KoListLevelProperties.h styles/KoListStyle.h styles/KoParagraphStyle.h styles/KoTableColumnStyle.h styles/KoTableRowStyle.h styles/KoTableCellStyle.h styles/KoSectionStyle.h styles/KoStyleManager.h styles/KoTableStyle.h DESTINATION ${INCLUDE_INSTALL_DIR}/calligra/styles COMPONENT Devel ) install( FILES changetracker/KoChangeTracker.h changetracker/KoChangeTrackerElement.h changetracker/KoDeletedRowColumnDataStore.h changetracker/KoDeletedRowData.cpp changetracker/KoDeletedColumnData.cpp changetracker/KoDeletedCellData.cpp DESTINATION ${INCLUDE_INSTALL_DIR}/calligra/changetracker COMPONENT Devel ) diff --git a/libs/kotext/KoInlineNote.cpp b/libs/kotext/KoInlineNote.cpp index 4fe33d68a5..26ab73dafd 100644 --- a/libs/kotext/KoInlineNote.cpp +++ b/libs/kotext/KoInlineNote.cpp @@ -1,302 +1,301 @@ /* This file is part of the KDE project * Copyright (C) 2007 Thomas Zander * Copyright (C) 2010 KO Gmbh * * 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 "KoInlineNote.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include using namespace writeodf; class Q_DECL_HIDDEN KoInlineNote::Private { public: Private(KoInlineNote::Type t) : textFrame(0) , autoNumbering(false) , type(t) { } QTextDocument *document; QTextFrame *textFrame; QString label; QString author; QDateTime date; bool autoNumbering; KoInlineNote::Type type; int posInDocument; }; KoInlineNote::KoInlineNote(Type type) : KoInlineObject(true) , d(new Private(type)) { } KoInlineNote::~KoInlineNote() { delete d; } void KoInlineNote::setMotherFrame(QTextFrame *motherFrame) { d->document = motherFrame->document(); // We create our own subframe QTextCursor cursor(motherFrame->lastCursorPosition()); QTextFrameFormat format; format.setProperty(KoText::SubFrameType, KoText::NoteFrameType); d->textFrame = cursor.insertFrame(format); // Now let's make sure it has the right paragraph KoOdfNotesConfiguration *notesConfig = 0; if (d->type == KoInlineNote::Footnote) { notesConfig = KoTextDocument(d->document).styleManager()->notesConfiguration(KoOdfNotesConfiguration::Footnote); } else if (d->type == KoInlineNote::Endnote) { notesConfig = KoTextDocument(d->document).styleManager()->notesConfiguration(KoOdfNotesConfiguration::Endnote); } KoParagraphStyle *style = static_cast(notesConfig->defaultNoteParagraphStyle()); if (style) { - QTextBlock block = cursor.block(); QTextBlockFormat bf; QTextCharFormat cf; style->applyStyle(bf); style->KoCharacterStyle::applyStyle(cf); cursor.setBlockFormat(bf); cursor.setBlockCharFormat(cf); } } void KoInlineNote::setLabel(const QString &text) { d->label = text; } void KoInlineNote::setAutoNumber(int autoNumber) { if (d->autoNumbering) { KoOdfNotesConfiguration *notesConfig = 0; if (d->type == KoInlineNote::Footnote) { notesConfig = KoTextDocument(d->document).styleManager()->notesConfiguration(KoOdfNotesConfiguration::Footnote); } else if (d->type == KoInlineNote::Endnote) { notesConfig = KoTextDocument(d->document).styleManager()->notesConfiguration(KoOdfNotesConfiguration::Endnote); } d->label = notesConfig->numberFormat().formattedNumber(autoNumber + notesConfig->startValue()); } } QTextFrame *KoInlineNote::textFrame() const { return d->textFrame; } void KoInlineNote::setTextFrame(QTextFrame *textFrame) { d->textFrame = textFrame; } QString KoInlineNote::label() const { return d->label; } bool KoInlineNote::autoNumbering() const { return d->autoNumbering; } void KoInlineNote::setAutoNumbering(bool on) { d->autoNumbering = on; } KoInlineNote::Type KoInlineNote::type() const { return d->type; } void KoInlineNote::updatePosition(const QTextDocument *document, int posInDocument, const QTextCharFormat &format) { Q_UNUSED(document); Q_UNUSED(format); d->posInDocument = posInDocument; } void KoInlineNote::resize(const QTextDocument *document, QTextInlineObject &object, int posInDocument, const QTextCharFormat &format, QPaintDevice *pd) { Q_UNUSED(document); Q_UNUSED(posInDocument); if (d->label.isEmpty()) { object.setWidth(0); object.setAscent(0); object.setDescent(0); } else { Q_ASSERT(format.isCharFormat()); QFontMetricsF fm(format.font(), pd); object.setWidth(fm.width(d->label)); object.setAscent(fm.ascent()); object.setDescent(fm.descent()); } } void KoInlineNote::paint(QPainter &painter, QPaintDevice *pd, const QTextDocument *document, const QRectF &rect, const QTextInlineObject &object, int posInDocument, const QTextCharFormat &originalFormat) { Q_UNUSED(document); Q_UNUSED(posInDocument); if (d->label.isEmpty()) return; QTextCharFormat format = originalFormat; KoOdfNotesConfiguration *notesConfig = 0; if (d->type == KoInlineNote::Footnote) { notesConfig = KoTextDocument(d->document).styleManager()->notesConfiguration(KoOdfNotesConfiguration::Footnote); } else if (d->type == KoInlineNote::Endnote) { notesConfig = KoTextDocument(d->document).styleManager()->notesConfiguration(KoOdfNotesConfiguration::Endnote); } KoCharacterStyle *style = static_cast(notesConfig->citationBodyTextStyle()); if (style) { style->applyStyle(format); } QFont font(format.font(), pd); QTextLayout layout(d->label, font, pd); layout.setCacheEnabled(true); QList layouts; QTextLayout::FormatRange range; range.start = 0; range.length = d->label.length(); range.format = format; layouts.append(range); layout.setAdditionalFormats(layouts); QTextOption option(Qt::AlignLeft | Qt::AlignAbsolute); option.setTextDirection(object.textDirection()); layout.setTextOption(option); layout.beginLayout(); layout.createLine(); layout.endLayout(); layout.draw(&painter, rect.topLeft()); } bool KoInlineNote::loadOdf(const KoXmlElement & element, KoShapeLoadingContext &context) { KoTextLoader loader(context); QTextCursor cursor(d->textFrame); if (element.namespaceURI() == KoXmlNS::text && element.localName() == "note") { QString className = element.attributeNS(KoXmlNS::text, "note-class"); if (className == "footnote") { d->type = Footnote; } else if (className == "endnote") { d->type = Endnote; } else { return false; } for (KoXmlNode node = element.firstChild(); !node.isNull(); node = node.nextSibling()) { KoXmlElement ts = node.toElement(); if (ts.namespaceURI() != KoXmlNS::text) continue; if (ts.localName() == "note-body") { loader.loadBody(ts, cursor); } else if (ts.localName() == "note-citation") { d->label = ts.attributeNS(KoXmlNS::text, "label"); if (d->label.isEmpty()) { setAutoNumbering(true); d->label = ts.text(); } } } } else if (element.namespaceURI() == KoXmlNS::office && element.localName() == "annotation") { d->author = element.attributeNS(KoXmlNS::text, "dc-creator"); d->date = QDateTime::fromString(element.attributeNS(KoXmlNS::text, "dc-date"), Qt::ISODate); loader.loadBody(element, cursor); // would skip author and date, and do just the and elements } else { return false; } return true; } void KoInlineNote::saveOdf(KoShapeSavingContext & context) { KoXmlWriter *writer = &context.xmlWriter(); if (d->type == Footnote || d->type == Endnote) { text_note note(writer, (d->type == Footnote) ?"footnote" :"endnote"); text_note_citation cite(note.add_text_note_citation()); if (!autoNumbering()) { cite.set_text_label(d->label); } cite.addTextNode(d->label); text_note_body body(note.add_text_note_body()); KoTextWriter textWriter(context); textWriter.write(d->document, d->textFrame->firstPosition(), d->textFrame->lastPosition()); } else if (d->type == Annotation) { office_annotation annotation(writer); if (!d->author.isEmpty()) { dc_creator creator(annotation.add_dc_creator()); creator.addTextNode(d->author); } if (d->date.isValid()) { dc_date date(annotation.add_dc_date()); date.addTextNode(d->date.toString(Qt::ISODate)); } KoTextWriter textWriter(context); textWriter.write(d->document, d->textFrame->firstPosition(),d->textFrame->lastPosition()); } } int KoInlineNote::getPosInDocument() const { return d->posInDocument; } diff --git a/libs/kotext/KoSection.cpp b/libs/kotext/KoSection.cpp index 384b393e0a..8e112a15d5 100644 --- a/libs/kotext/KoSection.cpp +++ b/libs/kotext/KoSection.cpp @@ -1,216 +1,245 @@ /* * Copyright (c) 2011 Boudewijn Rempt - * Copyright (c) 2014 Denis Kuplyakov + * Copyright (c) 2014-2015 Denis Kuplyakov * * 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; either * version 2.1 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser 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 "KoSection.h" #include #include #include #include #include #include -#include +#include #include #include #include #include #include class KoSectionPrivate { public: - explicit KoSectionPrivate(const QTextDocument *_document) - : document(_document) - , manager(KoTextDocument(_document).sectionManager()) + explicit KoSectionPrivate(const QTextCursor &cursor, const QString &_name, KoSection *_parent) + : document(cursor.block().document()) + , name(_name) , sectionStyle(0) + , boundingCursorStart(cursor) + , boundingCursorEnd(cursor) + , parent(_parent) , inlineRdf(0) { - Q_ASSERT(manager); - name = manager->possibleNewName(); } const QTextDocument *document; - KoSectionManager *manager; QString condition; QString display; QString name; QString text_protected; QString protection_key; QString protection_key_digest_algorithm; QString style_name; KoSectionStyle *sectionStyle; - QScopedPointer sectionEnd; //< pointer to the corresponding section end - int level; //< level of the section in document, root sections have 0 level - QPair bounds; //< start and end position of section in QDocument + QScopedPointer sectionEnd; ///< pointer to the corresponding section end + int level; ///< level of the section in document, root sections have 0 level + QTextCursor boundingCursorStart; ///< This cursor points to the start of the section + QTextCursor boundingCursorEnd; ///< This cursor points to the end of the section (excluding paragraph symbol) - KoTextInlineRdf *inlineRdf; + // Boundings explanation: + // + // |S|e|c|t|i|o|n|...|t|e|x|t|P| + // ^ ^ + // |--- Start |-- End + + QVector children; ///< List of the section's childrens + KoSection *parent; ///< Parent of the section + + KoTextInlineRdf *inlineRdf; ///< Handling associated RDF }; -KoSection::KoSection(const QTextCursor &cursor) - : d_ptr(new KoSectionPrivate(cursor.block().document())) +KoSection::KoSection(const QTextCursor &cursor, const QString &name, KoSection *parent) + : d_ptr(new KoSectionPrivate(cursor, name, parent)) { Q_D(KoSection); - d->manager->registerSection(this); + + d->boundingCursorStart.setKeepPositionOnInsert(true); // Start cursor should stay on place + d->boundingCursorEnd.setKeepPositionOnInsert(false); // and end one should move forward + + if (parent) { + d->level = parent->level() + 1; + } else { + d->level = 0; + } } KoSection::~KoSection() { - Q_D(KoSection); - d->manager->unregisterSection(this); + // Here scoped pointer will delete sectionEnd } QString KoSection::name() const { Q_D(const KoSection); return d->name; } QPair KoSection::bounds() const { Q_D(const KoSection); - d->manager->update(); - return d->bounds; + return QPair( + d->boundingCursorStart.position(), + d->boundingCursorEnd.position() + ); } int KoSection::level() const { Q_D(const KoSection); - d->manager->update(); return d->level; } -bool KoSection::setName(const QString &name) -{ - Q_D(KoSection); - - if (name == d->name) { - return true; - } - - if (d->manager->isValidNewName(name)) { - d->name = name; - d->manager->invalidate(); - return true; - } - return false; -} - bool KoSection::loadOdf(const KoXmlElement &element, KoTextSharedLoadingData *sharedData, bool stylesDotXml) { Q_D(KoSection); // check whether we really are a section if (element.namespaceURI() == KoXmlNS::text && element.localName() == "section") { // get all the attributes d->condition = element.attributeNS(KoXmlNS::text, "condition"); d->display = element.attributeNS(KoXmlNS::text, "display"); if (d->display == "condition" && d->condition.isEmpty()) { kWarning(32500) << "Section display is set to \"condition\", but condition is empty."; } - if (!setName(element.attributeNS(KoXmlNS::text, "name"))) { - kWarning(32500) << "Sections name \"" << element.attributeNS(KoXmlNS::text, "name") - << "\" repeated, but must be unique."; + QString newName = element.attributeNS(KoXmlNS::text, "name"); + if (!KoTextDocument(d->document).sectionModel()->setName(this, newName)) { + kWarning(32500) << "Section name \"" << newName + << "\" must be unique or is invalid. Resetting it to " << name(); } d->text_protected = element.attributeNS(KoXmlNS::text, "text-protected"); d->protection_key = element.attributeNS(KoXmlNS::text, "protection-key"); d->protection_key_digest_algorithm = element.attributeNS(KoXmlNS::text, "protection-key-algorithm"); d->style_name = element.attributeNS(KoXmlNS::text, "style-name", ""); if (!d->style_name.isEmpty()) { d->sectionStyle = sharedData->sectionStyle(d->style_name, stylesDotXml); } // lets handle associated xml:id if (element.hasAttribute("id")) { KoTextInlineRdf* inlineRdf = new KoTextInlineRdf(const_cast(d->document), this); if (inlineRdf->loadOdf(element)) { d->inlineRdf = inlineRdf; } else { delete inlineRdf; inlineRdf = 0; } } return true; } return false; } void KoSection::saveOdf(KoShapeSavingContext &context) const { Q_D(const KoSection); KoXmlWriter *writer = &context.xmlWriter(); Q_ASSERT(writer); writer->startElement("text:section", false); if (!d->condition.isEmpty()) writer->addAttribute("text:condition", d->condition); if (!d->display.isEmpty()) writer->addAttribute("text:display", d->condition); if (!d->name.isEmpty()) writer->addAttribute("text:name", d->name); if (!d->text_protected.isEmpty()) writer->addAttribute("text:text-protected", d->text_protected); if (!d->protection_key.isEmpty()) writer->addAttribute("text:protection-key", d->protection_key); - if (!d->protection_key_digest_algorithm.isEmpty()) writer->addAttribute("text:protection-key-digest-algorihtm", d->protection_key_digest_algorithm); + if (!d->protection_key_digest_algorithm.isEmpty()) { + writer->addAttribute("text:protection-key-digest-algorihtm", d->protection_key_digest_algorithm); + } if (!d->style_name.isEmpty()) writer->addAttribute("text:style-name", d->style_name); if (d->inlineRdf) { d->inlineRdf->saveOdf(context, writer); } } void KoSection::setSectionEnd(KoSectionEnd* sectionEnd) { Q_D(KoSection); d->sectionEnd.reset(sectionEnd); } -void KoSection::setBeginPos(int pos) +void KoSection::setName(const QString &name) +{ + Q_D(KoSection); + d->name = name; +} + +void KoSection::setLevel(int level) { Q_D(KoSection); - d->bounds.first = pos; + d->level = level; } -void KoSection::setEndPos(int pos) +void KoSection::setKeepEndBound(bool state) { Q_D(KoSection); - d->bounds.second = pos; + d->boundingCursorEnd.setKeepPositionOnInsert(state); } -void KoSection::setLevel(int level) +KoSection *KoSection::parent() const +{ + Q_D(const KoSection); + return d->parent; +} + +QVector KoSection::children() const +{ + Q_D(const KoSection); + return d->children; +} + +void KoSection::insertChild(int childIdx, KoSection *section) { Q_D(KoSection); - d->level = level; + d->children.insert(childIdx, section); +} + +void KoSection::removeChild(int childIdx) +{ + Q_D(KoSection); + d->children.remove(childIdx); } KoTextInlineRdf *KoSection::inlineRdf() const { Q_D(const KoSection); return d->inlineRdf; } void KoSection::setInlineRdf(KoTextInlineRdf *inlineRdf) { Q_D(KoSection); d->inlineRdf = inlineRdf; } diff --git a/libs/kotext/KoSection.h b/libs/kotext/KoSection.h index 86e2b256a4..440ea67f5f 100644 --- a/libs/kotext/KoSection.h +++ b/libs/kotext/KoSection.h @@ -1,107 +1,135 @@ /* * Copyright (c) 2011 Boudewijn Rempt - * Copyright (c) 2014 Denis Kuplyakov + * Copyright (c) 2014-2015 Denis Kuplyakov * * 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; either * version 2.1 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef KOSECTION_H #define KOSECTION_H #include "kotext_export.h" #include #include #include #include #include #include class KoXmlElement; class KoShapeSavingContext; class KoTextSharedLoadingData; class KoSectionEnd; class KoElementReference; class KoTextInlineRdf; class KoSectionPrivate; /** * Contains the information about the current text:section. * * The element has the following attributes: * *
    *
  • text:condition *
  • text:display *
  • text:name *
  • text:protected *
  • text:protection-key *
  • text:protection-key-digest-algorithm *
  • text:style-name *
  • xml:id *
* (odf spec v.12) */ class KOTEXT_EXPORT KoSection { public: - explicit KoSection(const QTextCursor &cursor); ~KoSection(); /// Returns section name QString name() const; /// Returns starting and ending position of section in QTextDocument QPair bounds() const; /// Returns section level. Root section has @c 0 level. int level() const; - /** Tries to set section's name to @p name - * @return @c false if there is a section with such name - * and new name isn't accepted - */ - bool setName(const QString &name); /** Returns inlineRdf associated with section * @return pointer to the KoTextInlineRdf for this section */ KoTextInlineRdf *inlineRdf() const; /** Sets KoTextInlineRdf for this section * @param inlineRdf pointer to KoTextInlineRdf to set */ void setInlineRdf(KoTextInlineRdf *inlineRdf); bool loadOdf(const KoXmlElement &element, KoTextSharedLoadingData *sharedData, bool stylesDotXml); void saveOdf(KoShapeSavingContext &context) const; protected: const QScopedPointer d_ptr; private: Q_DISABLE_COPY(KoSection) Q_DECLARE_PRIVATE(KoSection) + explicit KoSection(const QTextCursor &cursor, const QString &name, KoSection *parent); + + /// Changes section's name to @param name + void setName(const QString &name); + + /// Sets paired KoSectionsEnd for this section. void setSectionEnd(KoSectionEnd *sectionEnd); - void setBeginPos(int pos); - void setEndPos(int pos); + + /** + * Sets level of section in section tree. + * Root sections have @c 0 level. + */ void setLevel(int level); - friend class KoSectionManager; + /// Returns a pointer to the parent of the section in tree. + KoSection *parent() const; + + /// Returns a vector of pointers to the children of the section. + QVector children() const; + + /** + * Specifies if end bound of section should stay on place when inserting text. + * Used by KoTextLoader on document loading. + * @see QTextCursor::setKeepPositionOnInsert(bool) + */ + void setKeepEndBound(bool state); + + /** + * Inserts @param section to position @param childIdx of children + */ + void insertChild(int childIdx, KoSection *section); + + /** + * Removes child on position @param childIdx + */ + void removeChild(int childIdx); + + friend class KoSectionModel; + friend class KoTextLoader; // accesses setKeepEndBound() function friend class KoSectionEnd; + friend class TestKoTextEditor; // accesses setKeepEndBound() function }; Q_DECLARE_METATYPE(KoSection *) Q_DECLARE_METATYPE(QList) #endif // KOSECTION_H diff --git a/libs/kotext/KoSectionEnd.h b/libs/kotext/KoSectionEnd.h index 1750426b87..bebac82331 100644 --- a/libs/kotext/KoSectionEnd.h +++ b/libs/kotext/KoSectionEnd.h @@ -1,58 +1,62 @@ /* * Copyright (c) 2011 Boudewijn Rempt - * Copyright (c) 2014 Denis Kuplyakov + * Copyright (c) 2014-2015 Denis Kuplyakov * * 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; either * version 2.1 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef KOSECTIONEND_H #define KOSECTIONEND_H #include "kotext_export.h" #include #include #include #include class KoShapeSavingContext; class KoSection; class KoSectionEndPrivate; /** * Marks the end of the section */ class KOTEXT_EXPORT KoSectionEnd { public: - explicit KoSectionEnd(KoSection* section); - ~KoSectionEnd(); // this is needed to QScopedPointer to work + ~KoSectionEnd(); // this is needed for QScopedPointer void saveOdf(KoShapeSavingContext &context) const; QString name() const; KoSection *correspondingSection() const; protected: const QScopedPointer d_ptr; private: Q_DISABLE_COPY(KoSectionEnd) Q_DECLARE_PRIVATE(KoSectionEnd) + + explicit KoSectionEnd(KoSection *section); + + friend class KoSectionModel; + friend class TestKoTextEditor; }; Q_DECLARE_METATYPE(KoSectionEnd *) Q_DECLARE_METATYPE(QList) #endif // KOSECTIONEND_H diff --git a/libs/kotext/KoSectionManager.cpp b/libs/kotext/KoSectionManager.cpp deleted file mode 100644 index 623aa0a37a..0000000000 --- a/libs/kotext/KoSectionManager.cpp +++ /dev/null @@ -1,207 +0,0 @@ -/* - * Copyright (c) 2014 Denis Kuplyakov - * - * 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; either - * version 2.1 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 - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser 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 "KoSectionManager.h" - -#include -#include -#include "KoSectionEnd.h" -#include -#include -#include "KoSectionUtils.h" - -#include -#include -#include -#include - -#include - -class KoSectionManagerPrivate -{ -public: - KoSectionManagerPrivate(KoSectionManager *parent, QTextDocument *_doc) - : doc(_doc) - , valid(false) - , q_ptr(parent) - { - Q_ASSERT(_doc); - } - - ~KoSectionManagerPrivate() - { - QSet::iterator it = registeredSections.begin(); - for (; it != registeredSections.end(); it++) { - delete *it; // KoSectionEnd will be deleted in KoSection - } - } - - QTextDocument *doc; - bool valid; //< is current section info is valid - QSet registeredSections; //< stores pointer to sections that sometime was registered - - // used to prevent using sectionNames without update - QHash §ionNames() - { - Q_Q(KoSectionManager); - q->update(); - return m_sectionNames; - } - -protected: - KoSectionManager *q_ptr; - -private: - Q_DISABLE_COPY(KoSectionManagerPrivate) - Q_DECLARE_PUBLIC(KoSectionManager); - - QHash m_sectionNames; //< stores name -> pointer reference, for sections that are visible in document now -}; - -KoSectionManager::KoSectionManager(QTextDocument* doc) - : d_ptr(new KoSectionManagerPrivate(this, doc)) -{ - KoTextDocument(doc).setSectionManager(this); //FIXME: setting it back from here looks bad -} - -KoSectionManager::~KoSectionManager() -{ - delete d_ptr; -} - -KoSection *KoSectionManager::sectionAtPosition(int pos) -{ - Q_D(KoSectionManager); - update(); - - KoSection *result = 0; - int smallest = INT_MAX; //smallest in size section will be the deepest - QHash::iterator it = d->sectionNames().begin(); - for (; it != d->sectionNames().end(); it++) { - if (it.value()->bounds().first > pos || it.value()->bounds().second <= pos) { - continue; - } - - if (it.value()->bounds().second - it.value()->bounds().first < smallest) { - result = it.value(); - smallest = result->bounds().second - result->bounds().first; - } - } - - return result; -} - -void KoSectionManager::invalidate() -{ - Q_D(KoSectionManager); - d->valid = false; -} - -bool KoSectionManager::isValidNewName(const QString &name) -{ - Q_D(KoSectionManager); - return (d->sectionNames().constFind(name) == d->sectionNames().constEnd()); -} - -QString KoSectionManager::possibleNewName() -{ - Q_D(KoSectionManager); - - QString newName; - int i = d->registeredSections.count(); - do { - i++; - newName = i18nc("new numbered section name", "New section %1", i); - } while (!isValidNewName(newName)); - - return newName; -} - -void KoSectionManager::registerSection(KoSection* section) -{ - Q_D(KoSectionManager); - d->registeredSections.insert(section); - invalidate(); -} - -void KoSectionManager::unregisterSection(KoSection* section) -{ - Q_D(KoSectionManager); - d->registeredSections.remove(section); - invalidate(); -} - -QStandardItemModel *KoSectionManager::update(bool needModel) -{ - Q_D(KoSectionManager); - if (d->valid && !needModel) { - return 0; - } - d->valid = true; - d->sectionNames().clear(); - - QSet::iterator it = d->registeredSections.begin(); - for (; it != d->registeredSections.end(); it++) { - (*it)->setBeginPos(-1); - (*it)->setEndPos(-1); - (*it)->setLevel(-1); - } - - QTextBlock block = d->doc->begin(); - - QStandardItemModel *model = 0; - QStack curChain; - - if (needModel) { - model = new QStandardItemModel(); - curChain.push(model->invisibleRootItem()); - } - - int curLevel = -1; - do { - QTextBlockFormat fmt = block.blockFormat(); - - foreach (KoSection *sec, KoSectionUtils::sectionStartings(fmt)) { - curLevel++; - sec->setBeginPos(block.position()); - sec->setLevel(curLevel); - - d->sectionNames()[sec->name()] = sec; - - if (needModel) { - QStandardItem *item = new QStandardItem(sec->name()); - item->setData(QVariant::fromValue(sec), Qt::UserRole + 1); - - curChain.top()->appendRow(item); - - curChain.push(item); - } - } - - foreach (const KoSectionEnd *sec, KoSectionUtils::sectionEndings(fmt)) { - curLevel--; - sec->correspondingSection()->setEndPos(block.position() + block.length()); - - if (needModel) { - curChain.pop(); - } - } - } while ((block = block.next()).isValid()); - - return model; -} diff --git a/libs/kotext/KoSectionManager.h b/libs/kotext/KoSectionManager.h deleted file mode 100644 index cfd957e431..0000000000 --- a/libs/kotext/KoSectionManager.h +++ /dev/null @@ -1,102 +0,0 @@ -/* - * Copyright (c) 2014 Denis Kuplyakov - * - * 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; either - * version 2.1 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 - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this library; see the file COPYING.LIB. If not, write to - * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, - * Boston, MA 02110-1301, USA. - */ -#ifndef KOSECTIONMANAGER_H -#define KOSECTIONMANAGER_H - -#include -#include -#include -#include - -#include - -class KoSection; -class KoSectionManagerPrivate; - -/** - * Used to handle all the sections in the document - * - * Sections are registered in KoSection constructor and unregistered - * in KoSection destructor. - * - * Info is invalidated in DeleteCommand, because we can't check it from here. - * Registering and unregistering section also calls invalidate(). - * - * This object is created for QTextDocument on the first query of it. - */ -class KOTEXT_EXPORT KoSectionManager -{ -public: - explicit KoSectionManager(QTextDocument* doc); - ~KoSectionManager(); - - /** - * Returns pointer to the deepest KoSection that covers @p pos - * or NULL if there is no such section - */ - KoSection *sectionAtPosition(int pos); - - /** - * Returns name for the new section - */ - QString possibleNewName(); - - /** - * Returns if this name is possible. - */ - bool isValidNewName(const QString &name); - -public Q_SLOTS: - /** - * Call this to recalc all sections information - * @param needModel place @c true to it if you need model to use in GUI and @c false otherwise - * @return pointer to QStandardItemModel, build according to section tree - * with a pointers to KoSection at Qt::UserRole + 1 and section name - * for display role. - * NOTE: it is not updated further by KoSectionManager - */ - QStandardItemModel *update(bool needModel = false); - - /** - * Call this to notify manager that info in it has invalidated. - */ - void invalidate(); - - /** - * - * Call this to register new section in manager - */ - void registerSection(KoSection *section); - - /** - * Call this to unregister section from manager - */ - void unregisterSection(KoSection *section); - -protected: - KoSectionManagerPrivate * const d_ptr; - -private: - Q_DISABLE_COPY(KoSectionManager) - Q_DECLARE_PRIVATE(KoSectionManager) -}; - -Q_DECLARE_METATYPE(KoSectionManager *) - -#endif //KOSECTIONMANAGER_H diff --git a/libs/kotext/KoSectionModel.cpp b/libs/kotext/KoSectionModel.cpp new file mode 100644 index 0000000000..dd8f68abd5 --- /dev/null +++ b/libs/kotext/KoSectionModel.cpp @@ -0,0 +1,217 @@ +#include "KoSectionModel.h" + +#include +#include +#include + +KoSectionModel::KoSectionModel(QTextDocument *doc) + : QAbstractItemModel() + , m_doc(doc) +{ + KoTextDocument(m_doc).setSectionModel(this); +} + +KoSectionModel::~KoSectionModel() +{ + foreach(KoSection *sec, m_registeredSections) { + delete sec; // This will delete associated KoSectionEnd in KoSection destructor + } +} + +KoSection *KoSectionModel::createSection(const QTextCursor &cursor, KoSection *parent, const QString &name) +{ + if (!isValidNewName(name)) { + return 0; + } + + KoSection *result = new KoSection(cursor, name, parent); + + // Lets find our number in parent's children by cursor position + QVector children = (parent ? parent->children() : m_rootSections); + int childrenId = children.size(); + for (int i = 0; i < children.size(); i++) { + if (cursor.position() < children[i]->bounds().first) { + childrenId = i; + break; + } + } + // We need to place link from parent to children in childId place + // Also need to find corresponding index and declare operations in terms of model + insertToModel(result, childrenId); + + return result; +} + +KoSection *KoSectionModel::createSection(const QTextCursor &cursor, KoSection *parent) +{ + return createSection(cursor, parent, possibleNewName()); +} + +KoSectionEnd *KoSectionModel::createSectionEnd(KoSection *section) +{ + return new KoSectionEnd(section); +} + +KoSection *KoSectionModel::sectionAtPosition(int pos) +{ + // TODO: Rewrite it by traversing Model as tree + KoSection *result = 0; + int level = -1; // Seeking the section with maximum level + QHash::iterator it = m_sectionNames.begin(); + for (; it != m_sectionNames.end(); it++) { + QPair bounds = it.value()->bounds(); + if (bounds.first > pos || bounds.second < pos) { + continue; + } + + if (it.value()->level() > level) { + result = it.value(); + level = it.value()->level(); + } + } + + return result; +} + +bool KoSectionModel::isValidNewName(const QString &name) const +{ + return (m_sectionNames.constFind(name) == m_sectionNames.constEnd()); +} + +QString KoSectionModel::possibleNewName() +{ + QString newName; + int i = m_registeredSections.count(); + do { + i++; + newName = i18nc("new numbered section name", "New section %1", i); + } while (!isValidNewName(newName)); + + return newName; +} + +bool KoSectionModel::setName(KoSection *section, const QString &name) +{ + if (section->name() == name || isValidNewName(name)) { + section->setName(name); + //TODO: we don't have name in columns, but need something to notify views about change + emit dataChanged(m_modelIndex[section], m_modelIndex[section]); + return true; + } + return false; +} + +void KoSectionModel::allowMovingEndBound() +{ + QSet::iterator it = m_registeredSections.begin(); + for (; it != m_registeredSections.end(); it++) { + (*it)->setKeepEndBound(false); + } +} + +int KoSectionModel::findRowOfChild(KoSection *section) const +{ + QVector lookOn; + if (!section->parent()) { + lookOn = m_rootSections; + } else { + lookOn = section->parent()->children(); + } + + int result = lookOn.indexOf(section); + Q_ASSERT(result != -1); + return result; +} + +QModelIndex KoSectionModel::index(int row, int column, const QModelIndex &parentIdx) const +{ + if (!hasIndex(row, column, parentIdx)) { + return QModelIndex(); + } + + if (!parentIdx.isValid()) { + return createIndex(row, column, m_rootSections[row]); + } + + KoSection *parent = static_cast(parentIdx.internalPointer()); + return createIndex(row, column, parent->children()[row]); +} + +QModelIndex KoSectionModel::parent(const QModelIndex &child) const +{ + if (!child.isValid()) { + return QModelIndex(); + } + + KoSection *section = static_cast(child.internalPointer()); + KoSection *parent = section->parent(); + if (parent) { + return createIndex(findRowOfChild(parent), 0, parent); + } + return QModelIndex(); +} + +int KoSectionModel::rowCount(const QModelIndex &parent) const +{ + if (!parent.isValid()) { + return m_rootSections.size(); + } + return static_cast(parent.internalPointer())->children().size(); +} + +int KoSectionModel::columnCount(const QModelIndex &/*parent*/) const +{ + return 1; +} + +QVariant KoSectionModel::data(const QModelIndex &index, int role) const +{ + if (!index.isValid()) { + return QVariant(); + } + + if (index.column() == 0 && role == PointerRole) { + QVariant v; + v.setValue(static_cast(index.internalPointer())); + return v; + } + return QVariant(); +} + +void KoSectionModel::insertToModel(KoSection *section, int childIdx) +{ + Q_ASSERT(isValidNewName(section->name())); + + KoSection *parent = section->parent(); + if (parent) { // Inserting to some section + beginInsertRows(m_modelIndex[parent], childIdx, childIdx); + parent->insertChild(childIdx, section); + endInsertRows(); + m_modelIndex[section] = QPersistentModelIndex(index(childIdx, 0, m_modelIndex[parent])); + } else { // It will be root section + beginInsertRows(QModelIndex(), childIdx, childIdx); + m_rootSections.insert(childIdx, section); + endInsertRows(); + m_modelIndex[section] = QPersistentModelIndex(index(childIdx, 0, QModelIndex())); + } + + m_registeredSections.insert(section); + m_sectionNames[section->name()] = section; +} + +void KoSectionModel::deleteFromModel(KoSection *section) +{ + KoSection *parent = section->parent(); + int childIdx = findRowOfChild(section); + if (parent) { // Deleting non root section + beginRemoveRows(m_modelIndex[parent], childIdx, childIdx); + parent->removeChild(childIdx); + endRemoveRows(); + } else { // Deleting root section + beginRemoveRows(QModelIndex(), childIdx, childIdx); + m_rootSections.remove(childIdx); + endRemoveRows(); + } + m_modelIndex.remove(section); + m_sectionNames.remove(section->name()); +} diff --git a/libs/kotext/KoSectionModel.h b/libs/kotext/KoSectionModel.h new file mode 100644 index 0000000000..9c5a86d91d --- /dev/null +++ b/libs/kotext/KoSectionModel.h @@ -0,0 +1,142 @@ +#ifndef KOSECTIONMODEL_H +#define KOSECTIONMODEL_H + +#include +#include +#include +#include + +#include +#include + +/** + * Used to handle all the sections in the document + * + * Now there actually two levels of section handling: + * 1) Formatting Level: on this level we should be sure, that + * pointers to KoSection and KoSectionEnd in the KoParagraphStyles + * properties SectionEndings and SectionStartings are consistent. + * Handling on this level is provided on the level of text editing + * commands: DeleteCommand, NewSectionCommand + * We can't move it to another place, because we should know the + * semantics of operation to handle it right way. + * 2) Model(Tree) Level: on this level we should update KoSectionModel + * right way, so it in any moment represents the actual tree + * of sections. Tree is builded easily: + * One section is son of another, if it is directly nested in it. + * As text editing commands have access to change Formatting Level, + * they are declared as friend classes of KoSectionModel to be able + * affect Model structure without changing something on Formatting + * Level. Also affected by RenameSectionCommand. + * + * Also we need to look at the consistency of some section properties: + * + * 1) Bounds. Those now are handled with QTextCursors that are placed + * on start and end of the section. In default state start cursor + * isn't moving if text inserted in its position, and end cursor + * moves. But in the case of initial document loading, it is necessary + * to make some end cursors stop moving, so we have: + * KoTextLoader -> calling -> KoSection::setKeepEndBound() + * KoTextLoader -> calling -> KoSectionModel::allowMovingEndBound() + * ^-- this needed to restore defaul behaviour after load + * + * 2) Level. Level means the depth of the section in tree. Root + * sections has 0 (zero) level. Now if you look at the possible + * text editing command affecting sections you may notice that + * level of section doesn't change in any case. Initial level + * is set in KoSection constructor as parent's level plus one. + * TODO: write about drag-n-drop here, when its implemented + * + * 3) Name. Each KoSection has a name that must be unique. We have + * two groups of KoSections in each moment of time: the first group + * consists of the sections that are present in document now, + * the second group consists of the sections that were deleted, but + * we still need them as they may be restored with "undo". + * This groups are stored in m_registeredSections and m_sectionNames. + * + * Sections are created through this newSection() and newSectionEnd() + * functions. + * + * This object is created for QTextDocument on the first query of it. + */ +class KOTEXT_EXPORT KoSectionModel : public QAbstractItemModel +{ + Q_OBJECT +public: + static const int PointerRole = Qt::UserRole; + + explicit KoSectionModel(QTextDocument *doc); + virtual ~KoSectionModel(); + + virtual QModelIndex index(int row, int column, const QModelIndex &parent = QModelIndex()) const; + virtual QModelIndex parent(const QModelIndex &child) const; + + virtual int rowCount(const QModelIndex &parent = QModelIndex()) const; + virtual int columnCount(const QModelIndex &parent = QModelIndex()) const; + + virtual QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const; + + /// Creates KoSection in position of @param cursor with some allowed name + KoSection *createSection(const QTextCursor &cursor, KoSection *parent); + + /// Creates KoSection in position of @param cursor with specified @param name + KoSection *createSection(const QTextCursor &cursor, KoSection *parent, const QString &name); + + /// Creates KoSectionEnd in pair for a @param section + KoSectionEnd *createSectionEnd(KoSection *section); + + /** Tries to set @param section name to @param name + * @return @c false if there is a section with such name + * and new name isn't accepted and @c true otherwise. + */ + bool setName(KoSection *section, const QString &name); + + /** + * Returns pointer to the deepest KoSection that covers @p pos + * or NULL if there is no such section + */ + KoSection *sectionAtPosition(int pos); + + /// Returns name for the new section. + QString possibleNewName(); + + /// Returns if this name is possible. + bool isValidNewName(const QString &name) const; + + /// Setting all sections end bound cursor to move with text inserting. + void allowMovingEndBound(); + + /// Finds index of @param child inside his parent. + int findRowOfChild(KoSection *child) const; + +private: + Q_DISABLE_COPY(KoSectionModel) + + friend class DeleteCommand; + friend class NewSectionCommand; + + /** + * Inserts @param section to it's parent (should be + * stored in @param section already) in position childIdx. + * Affects only Model Level(@see KoSectionModel). + */ + void insertToModel(KoSection* section, int childIdx); + /** + * Deletes @param section from it's parent (should be + * stored in @param section already). + * Affects only Model Level(@see KoSectionModel). + */ + void deleteFromModel(KoSection *section); + + QTextDocument *m_doc; + QSet m_registeredSections; ///< stores pointer to sections that sometime was registered + QHash m_sectionNames; ///< stores name -> pointer reference, for sections that are visible in document now + QHash m_modelIndex; + + QVector m_rootSections; + +}; + +Q_DECLARE_METATYPE(KoSectionModel *) + +#endif //KOSECTIONMODEL_H diff --git a/libs/kotext/KoSectionUtils.cpp b/libs/kotext/KoSectionUtils.cpp index acb6be1c27..cfbc402ed3 100644 --- a/libs/kotext/KoSectionUtils.cpp +++ b/libs/kotext/KoSectionUtils.cpp @@ -1,76 +1,76 @@ /* * Copyright (c) 2014 Denis Kuplyakov * * 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; either * version 2.1 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser 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 #include bool KoSectionUtils::getNextBlock(QTextCursor &cur) { QTextCursor next = cur; bool ok = next.movePosition(QTextCursor::NextBlock); while (ok && next.currentFrame() != cur.currentFrame()) { ok = next.movePosition(QTextCursor::NextBlock); } if (!ok || next.currentFrame() != cur.currentFrame()) { // There is no previous block. return false; } cur = next; return true; } -void KoSectionUtils::setSectionStartings(QTextBlockFormat &fmt, QList &list) +void KoSectionUtils::setSectionStartings(QTextBlockFormat &fmt, const QList &list) { if (list.empty()) { fmt.clearProperty(KoParagraphStyle::SectionStartings); } else { fmt.setProperty(KoParagraphStyle::SectionStartings, QVariant::fromValue< QList >(list)); } } -void KoSectionUtils::setSectionEndings(QTextBlockFormat &fmt, QList &list) +void KoSectionUtils::setSectionEndings(QTextBlockFormat &fmt, const QList &list) { if (list.empty()) { fmt.clearProperty(KoParagraphStyle::SectionEndings); } else { fmt.setProperty(KoParagraphStyle::SectionEndings, QVariant::fromValue< QList >(list)); } } QList KoSectionUtils::sectionStartings(const QTextBlockFormat &fmt) { if (!fmt.hasProperty(KoParagraphStyle::SectionStartings)) { return QList(); } else { return fmt.property(KoParagraphStyle::SectionStartings).value< QList >(); } } QList KoSectionUtils::sectionEndings(const QTextBlockFormat &fmt) { if (!fmt.hasProperty(KoParagraphStyle::SectionEndings)) { return QList(); } else { return fmt.property(KoParagraphStyle::SectionEndings).value< QList >(); } } diff --git a/libs/kotext/KoSectionUtils.h b/libs/kotext/KoSectionUtils.h index 13fa15a13b..6096e6d106 100644 --- a/libs/kotext/KoSectionUtils.h +++ b/libs/kotext/KoSectionUtils.h @@ -1,73 +1,73 @@ /* * Copyright (c) 2014 Denis Kuplyakov * * 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; either * version 2.1 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef KOSECTIONUTILS_H #define KOSECTIONUTILS_H #include #include #include #include #include namespace KoSectionUtils { /** * Moves the cursors to the next block within the same QTextFrame. * @param cur cursor to move, modified during call * @return @c false if there is no next block, @c true otherwise */ bool getNextBlock(QTextCursor &cur); /** * Convinient function to set a list of startings to QTextBlockFormat. * This checks that list is empty. * * @param fmt QTextBlockFormat reference to set startings. * @param list QList is a list to set. */ - KOTEXT_EXPORT void setSectionStartings(QTextBlockFormat &fmt, QList &list); + KOTEXT_EXPORT void setSectionStartings(QTextBlockFormat &fmt, const QList &list); /** * Convinient function to set a list of endings to QTextBlockFormat. * This checks that list is empty. * * @param fmt QTextBlockFormat reference to set endings. * @param list QList is a list to set. */ - KOTEXT_EXPORT void setSectionEndings(QTextBlockFormat& fmt, QList &list); + KOTEXT_EXPORT void setSectionEndings(QTextBlockFormat& fmt, const QList &list); /** * Convinient function to get section startings from QTextBlockFormat. * @param fmt QTextBlockFormat format to retrieve section startings from. * @return QList that contains pointers to sections that start * according to QTextBlockFormat. */ KOTEXT_EXPORT QList sectionStartings(const QTextBlockFormat &fmt); /** * Convinient function to get section endings from QTextBlockFormat. * @param fmt QTextBlockFormat format to retrieve section startings from. * @return QList that contains pointers to sections that end * according to QTextBlockFormat. */ KOTEXT_EXPORT QList sectionEndings(const QTextBlockFormat& fmt); } #endif //KOSECTIONUTILS_H diff --git a/libs/kotext/KoTextDocument.cpp b/libs/kotext/KoTextDocument.cpp index aa320d48ca..85848dbc72 100644 --- a/libs/kotext/KoTextDocument.cpp +++ b/libs/kotext/KoTextDocument.cpp @@ -1,418 +1,416 @@ /* This file is part of the KDE project * Copyright (C) 2008 Girish Ramakrishnan * Copyright (C) 2009 Thomas Zander * Copyright (C) 2008 Pierre Stirnweiss \pierre.stirnweiss_calligra@gadz.org> * Copyright (C) 2011-2012 C. Boemann - * Copyright (C) 2014 Denis Kuplyakov + * Copyright (C) 2014-2015 Denis Kuplyakov * * 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 "KoTextDocument.h" #include #include #include #include #include #include #include "KoText.h" #include "KoTextEditor.h" #include "styles/KoStyleManager.h" #include "OdfTextTrackStyles.h" #include "KoTextRangeManager.h" #include "KoInlineTextObjectManager.h" #include "KoList.h" #include "KoOdfLineNumberingConfiguration.h" #include "changetracker/KoChangeTracker.h" #include -#include +#include Q_DECLARE_METATYPE(QAbstractTextDocumentLayout::Selection) Q_DECLARE_METATYPE(QTextFrame*) Q_DECLARE_METATYPE(QTextCharFormat) Q_DECLARE_METATYPE(QTextBlockFormat) const QUrl KoTextDocument::StyleManagerURL = QUrl("kotext://stylemanager"); const QUrl KoTextDocument::ListsURL = QUrl("kotext://lists"); const QUrl KoTextDocument::InlineObjectTextManagerURL = QUrl("kotext://inlineObjectTextManager"); const QUrl KoTextDocument::TextRangeManagerURL = QUrl("kotext://textRangeManager"); const QUrl KoTextDocument::UndoStackURL = QUrl("kotext://undoStack"); const QUrl KoTextDocument::ChangeTrackerURL = QUrl("kotext://changetracker"); const QUrl KoTextDocument::TextEditorURL = QUrl("kotext://textEditor"); const QUrl KoTextDocument::LineNumberingConfigurationURL = QUrl("kotext://linenumberingconfiguration"); const QUrl KoTextDocument::RelativeTabsURL = QUrl("kotext://relativetabs"); const QUrl KoTextDocument::HeadingListURL = QUrl("kotext://headingList"); const QUrl KoTextDocument::SelectionsURL = QUrl("kotext://selections"); const QUrl KoTextDocument::LayoutTextPageUrl = QUrl("kotext://layoutTextPage"); const QUrl KoTextDocument::ParaTableSpacingAtStartUrl = QUrl("kotext://spacingAtStart"); const QUrl KoTextDocument::IndexGeneratorManagerUrl = QUrl("kotext://indexGeneratorManager"); const QUrl KoTextDocument::FrameCharFormatUrl = QUrl("kotext://frameCharFormat"); const QUrl KoTextDocument::FrameBlockFormatUrl = QUrl("kotext://frameBlockFormat"); const QUrl KoTextDocument::ShapeControllerUrl = QUrl("kotext://shapeController"); -const QUrl KoTextDocument::SectionManagerUrl = QUrl("ktext://sectionManager"); +const QUrl KoTextDocument::SectionModelUrl = QUrl("ktext://sectionModel"); KoTextDocument::KoTextDocument(QTextDocument *document) : m_document(document) { Q_ASSERT(m_document); } KoTextDocument::KoTextDocument(const QTextDocument *document) : m_document(const_cast(document)) { Q_ASSERT(m_document); } KoTextDocument::KoTextDocument(QWeakPointer document) : m_document(document.data()) { Q_ASSERT(m_document); } KoTextDocument::~KoTextDocument() { } QTextDocument *KoTextDocument::document() const { return m_document; } void KoTextDocument::setTextEditor (KoTextEditor* textEditor) { Q_ASSERT(textEditor->document() == m_document); QVariant v; v.setValue(textEditor); m_document->addResource(KoTextDocument::TextEditor, TextEditorURL, v); } KoTextEditor* KoTextDocument::textEditor() const { QVariant resource = m_document->resource(KoTextDocument::TextEditor, TextEditorURL); return resource.value(); } void KoTextDocument::setStyleManager(KoStyleManager *sm) { QVariant v; v.setValue(sm); m_document->addResource(KoTextDocument::StyleManager, StyleManagerURL, v); if (sm) { OdfTextTrackStyles *cf = OdfTextTrackStyles::instance(sm); cf->registerDocument(m_document); } } void KoTextDocument::setInlineTextObjectManager(KoInlineTextObjectManager *manager) { QVariant v; v.setValue(manager); m_document->addResource(KoTextDocument::InlineTextManager, InlineObjectTextManagerURL, v); } void KoTextDocument::setTextRangeManager(KoTextRangeManager *manager) { QVariant v; v.setValue(manager); m_document->addResource(KoTextDocument::TextRangeManager, TextRangeManagerURL, v); } KoStyleManager *KoTextDocument::styleManager() const { QVariant resource = m_document->resource(KoTextDocument::StyleManager, StyleManagerURL); return resource.value(); } void KoTextDocument::setChangeTracker(KoChangeTracker *changeTracker) { QVariant v; v.setValue(changeTracker); m_document->addResource(KoTextDocument::ChangeTrackerResource, ChangeTrackerURL, v); } KoChangeTracker *KoTextDocument::changeTracker() const { QVariant resource = m_document->resource(KoTextDocument::ChangeTrackerResource, ChangeTrackerURL); if (resource.isValid()) { return resource.value(); } else { return 0; } } void KoTextDocument::setShapeController(KoShapeController *controller) { QVariant v; v.setValue(controller); m_document->addResource(KoTextDocument::ShapeController, ShapeControllerUrl, v); } KoShapeController *KoTextDocument::shapeController() const { QVariant resource = m_document->resource(KoTextDocument::ShapeController, ShapeControllerUrl); if (resource.isValid()) { return resource.value(); } else { return 0; } } void KoTextDocument::setLineNumberingConfiguration(KoOdfLineNumberingConfiguration *lineNumberingConfiguration) { lineNumberingConfiguration->setParent(m_document); QVariant v; v.setValue(lineNumberingConfiguration); m_document->addResource(KoTextDocument::LineNumberingConfiguration, LineNumberingConfigurationURL, v); } KoOdfLineNumberingConfiguration *KoTextDocument::lineNumberingConfiguration() const { return m_document->resource(KoTextDocument::LineNumberingConfiguration, LineNumberingConfigurationURL) .value(); } void KoTextDocument::setHeadingList(KoList *headingList) { QVariant v; v.setValue(headingList); m_document->addResource(KoTextDocument::HeadingList, HeadingListURL, v); } KoList *KoTextDocument::headingList() const { QVariant resource = m_document->resource(KoTextDocument::HeadingList, HeadingListURL); return resource.value(); } void KoTextDocument::setUndoStack(KUndo2Stack *undoStack) { QVariant v; v.setValue(undoStack); m_document->addResource(KoTextDocument::UndoStack, UndoStackURL, v); } KUndo2Stack *KoTextDocument::undoStack() const { QVariant resource = m_document->resource(KoTextDocument::UndoStack, UndoStackURL); return static_cast(resource.value()); } void KoTextDocument::setLists(const QList &lists) { QVariant v; v.setValue(lists); m_document->addResource(KoTextDocument::Lists, ListsURL, v); } QList KoTextDocument::lists() const { QVariant resource = m_document->resource(KoTextDocument::Lists, ListsURL); return resource.value >(); } void KoTextDocument::addList(KoList *list) { Q_ASSERT(list); list->setParent(m_document); QList l = lists(); if (l.contains(list)) return; l.append(list); setLists(l); } void KoTextDocument::removeList(KoList *list) { QList l = lists(); if (l.contains(list)) { l.removeAll(list); setLists(l); } } KoList *KoTextDocument::list(const QTextBlock &block) const { QTextList *textList = block.textList(); if (!textList) return 0; return list(textList); } KoList *KoTextDocument::list(QTextList *textList) const { if (!textList) { return 0; } // FIXME: this is horrible. foreach(KoList *l, lists()) { if (l->textLists().contains(textList)) return l; } return 0; } KoList *KoTextDocument::list(KoListStyle::ListIdType listId) const { foreach(KoList *l, lists()) { if (l->textListIds().contains(listId)) return l; } return 0; } void KoTextDocument::clearText() { QTextCursor cursor(m_document); cursor.select(QTextCursor::Document); cursor.removeSelectedText(); } QVector< QAbstractTextDocumentLayout::Selection > KoTextDocument::selections() const { QVariant resource = m_document->resource(KoTextDocument::Selections, SelectionsURL); QVariantList variants = resource.toList(); QVector selections; foreach(const QVariant &variant, variants) { selections.append(variant.value()); } return selections; } void KoTextDocument::setSelections(const QVector< QAbstractTextDocumentLayout::Selection >& selections) { QVariantList variants; foreach(const QAbstractTextDocumentLayout::Selection &selection, selections) { variants.append(QVariant::fromValue(selection)); } m_document->addResource(KoTextDocument::Selections, SelectionsURL, variants); } KoInlineTextObjectManager *KoTextDocument::inlineTextObjectManager() const { QVariant resource = m_document->resource(KoTextDocument::InlineTextManager, InlineObjectTextManagerURL); return resource.value(); } KoTextRangeManager *KoTextDocument::textRangeManager() const { QVariant resource = m_document->resource(KoTextDocument::TextRangeManager, TextRangeManagerURL); return resource.value(); } QTextFrame *KoTextDocument::auxillaryFrame() { QTextCursor cursor(m_document->rootFrame()->lastCursorPosition()); cursor.movePosition(QTextCursor::PreviousCharacter); QTextFrame *frame = cursor.currentFrame(); if (frame->format().intProperty(KoText::SubFrameType) != KoText::AuxillaryFrameType) { cursor = m_document->rootFrame()->lastCursorPosition(); QTextFrameFormat format; format.setProperty(KoText::SubFrameType, KoText::AuxillaryFrameType); frame = cursor.insertFrame(format); } return frame; } void KoTextDocument::setRelativeTabs(bool relative) { QVariant v(relative); m_document->addResource(KoTextDocument::RelativeTabs, RelativeTabsURL, v); } bool KoTextDocument::relativeTabs() const { QVariant resource = m_document->resource(KoTextDocument::RelativeTabs, RelativeTabsURL); if (resource.isValid()) return resource.toBool(); else return true; } void KoTextDocument::setParaTableSpacingAtStart(bool spacingAtStart) { QVariant v(spacingAtStart); m_document->addResource(KoTextDocument::ParaTableSpacingAtStart, ParaTableSpacingAtStartUrl, v); } bool KoTextDocument::paraTableSpacingAtStart() const { QVariant resource = m_document->resource(KoTextDocument::ParaTableSpacingAtStart, ParaTableSpacingAtStartUrl); if (resource.isValid()) return resource.toBool(); else return false; } QTextCharFormat KoTextDocument::frameCharFormat() const { QVariant resource = m_document->resource(KoTextDocument::FrameCharFormat, FrameCharFormatUrl); if (resource.isValid()) return resource.value(); else return QTextCharFormat(); } void KoTextDocument::setFrameCharFormat(const QTextCharFormat &format) { m_document->addResource(KoTextDocument::FrameCharFormat, FrameCharFormatUrl, QVariant::fromValue(format)); } QTextBlockFormat KoTextDocument::frameBlockFormat() const { QVariant resource = m_document->resource(KoTextDocument::FrameBlockFormat, FrameBlockFormatUrl); if (resource.isValid()) return resource.value(); else return QTextBlockFormat(); } void KoTextDocument::setFrameBlockFormat(const QTextBlockFormat &format) { m_document->addResource(KoTextDocument::FrameBlockFormat, FrameBlockFormatUrl, QVariant::fromValue(format)); } -KoSectionManager* KoTextDocument::sectionManager() +KoSectionModel* KoTextDocument::sectionModel() { - QVariant resource = m_document->resource(KoTextDocument::SectionManager, SectionManagerUrl); + QVariant resource = m_document->resource(KoTextDocument::SectionModel, SectionModelUrl); if (!resource.isValid()) { - setSectionManager(new KoSectionManager(document())); //using create on demand strategy + setSectionModel(new KoSectionModel(document())); // Using create on demand strategy } - - return m_document->resource(KoTextDocument::SectionManager, SectionManagerUrl).value(); + return m_document->resource(KoTextDocument::SectionModel, SectionModelUrl).value(); } -void KoTextDocument::setSectionManager(KoSectionManager *manager) +void KoTextDocument::setSectionModel(KoSectionModel *model) { QVariant v; - v.setValue(manager); - m_document->addResource(KoTextDocument::SectionManager, SectionManagerUrl, v); + v.setValue(model); + m_document->addResource(KoTextDocument::SectionModel, SectionModelUrl, v); } - diff --git a/libs/kotext/KoTextDocument.h b/libs/kotext/KoTextDocument.h index 4ad8ec1ce8..09e54df1c3 100644 --- a/libs/kotext/KoTextDocument.h +++ b/libs/kotext/KoTextDocument.h @@ -1,271 +1,267 @@ /* This file is part of the KDE project * Copyright (C) 2008 Girish Ramakrishnan * Copyright (C) 2009 Thomas Zander * Copyright (C) 2008 Pierre Stirnweiss - * Copyright (C) 2014 Denis Kuplyakov + * Copyright (C) 2014-2015 Denis Kuplyakov * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef KOTEXTDOCUMENT_H #define KOTEXTDOCUMENT_H #include #include #include #include #include "KoListStyle.h" class KoList; class KoStyleManager; class KoInlineTextObjectManager; class KoTextRangeManager; class KUndo2Stack; class KoTextEditor; class KoOdfLineNumberingConfiguration; class KoChangeTracker; class KoShapeController; -class KoSectionManager; +class KoSectionModel; class QTextCharFormat; /** * KoTextDocument provides an easy mechanism to set and access the * editing members of a QTextDocument. The meta data are stored as resources * in the QTextDocument using QTextDocument::addResource() and fetched * using QTextDocument::resource(). * */ class KOTEXT_EXPORT KoTextDocument { public: /// Constructor KoTextDocument(QTextDocument *document); // krazy:exclude=explicit /// Constructor KoTextDocument(const QTextDocument *document); // krazy:exclude=explicit /// Constructor KoTextDocument(QWeakPointer document); // krazy:exclude=explicit /// Destructor ~KoTextDocument(); /// Returns the document that was passed in the constructor QTextDocument *document() const; ///Returns the text editor for that document KoTextEditor *textEditor() const; ///Sets the text editor for the document void setTextEditor(KoTextEditor *textEditor); /// Sets the style manager that defines the named styles in the document void setStyleManager(KoStyleManager *styleManager); /// Returns the style manager KoStyleManager *styleManager() const; /// Sets the change tracker of the document void setChangeTracker(KoChangeTracker *changeTracker); ///Returns the change tracker of the document KoChangeTracker *changeTracker() const; void setLineNumberingConfiguration(KoOdfLineNumberingConfiguration *lineNumberingConfiguration); /// @return the notes configuration KoOdfLineNumberingConfiguration *lineNumberingConfiguration() const; ///Sets the global undo stack void setUndoStack(KUndo2Stack *undoStack); ///Returns the global undo stack KUndo2Stack *undoStack() const; ///Sets the global heading list void setHeadingList(KoList *list); ///Returns the global heading list KoList *headingList() const; /// Sets the lists of the document void setLists(const QList &lists); /// Returns the lists in the document QList lists() const; /// Adds a list to the document void addList(KoList *list); /// Removes a list from the document void removeList(KoList *list); /// Returns the KoList that holds \a block; 0 if block is not part of any list KoList *list(const QTextBlock &block) const; /// Returns the KoList that holds \a list KoList *list(QTextList *textList) const; /// Return the KoList that holds \a listId KoList *list(KoListStyle::ListIdType listId) const; /// Return the selections used during painting. QVector selections() const; /** * Set the selections to use for painting. * * The selections are used to apply temporary styling to * parts of a document. * * \param selections The new selections to use. */ void setSelections(const QVector &selections); /// Returns the KoInlineTextObjectManager KoInlineTextObjectManager *inlineTextObjectManager() const; /// Set the KoInlineTextObjectManager void setInlineTextObjectManager(KoInlineTextObjectManager *manager); - /** - * @return section manager for the document - */ - KoSectionManager *sectionManager(); + /// @return section model for the document + KoSectionModel *sectionModel(); - /** - * sets the section manager for the document - */ - void setSectionManager(KoSectionManager *manager); + /// Sets the section model for the document + void setSectionModel(KoSectionModel *model); /// Returns the KoTextRangeManager KoTextRangeManager *textRangeManager() const; /// Set the KoTextRangeManager void setTextRangeManager(KoTextRangeManager *manager); /// Set the KoDocument's shapeController. This controller exists as long as KoDocument exists. It should only be used for deleting shapes. void setShapeController(KoShapeController *controller); /// Returns the shapeController KoShapeController *shapeController() const; QTextFrame* auxillaryFrame(); /** * Specifies if tabs are relative to paragraph indent. * * By default it's false. */ void setRelativeTabs(bool relative); /** * Returns if tabs are placed relative to paragraph indent. * * By default, this is false. * * @see setRelativeTabs */ bool relativeTabs() const; void setParaTableSpacingAtStart(bool spacingAtStart); bool paraTableSpacingAtStart() const; /** * Returns the character format for the frame of this document. * * @return the character format for the frame of this document. * @see setFrameCharFormat */ QTextCharFormat frameCharFormat() const; /** * Sets the character format for the frame of this document. * * @param format the character format for the frame of this document. * @see frameCharFormat */ void setFrameCharFormat(const QTextCharFormat &format); /** * Returns the block format for the frame of this document. * * @return the block format for the frame of this document. * @see setFrameBlockFormat */ QTextBlockFormat frameBlockFormat() const; /** * Sets the block format for the frame of this document. * * @param format the block format for the frame of this document. * @see frameBlockFormat */ void setFrameBlockFormat(const QTextBlockFormat &format); /** * Clears the text in the document. Unlike QTextDocument::clear(), this * function does not clear the resources of the QTextDocument. */ void clearText(); /// Enum (type) used to add resources using QTextDocument::addResource() enum ResourceType { StyleManager = QTextDocument::UserResource, Lists, TextRangeManager, InlineTextManager, ChangeTrackerResource, UndoStack, TextEditor, LineNumberingConfiguration, RelativeTabs, HeadingList, Selections, LayoutTextPage, /// this is used for setting the correct page variable on the first resize and should not be used for other purposes ParaTableSpacingAtStart, /// this is used during layouting to specify if at the first paragraph margin-top should be applied. IndexGeneratorManager, FrameCharFormat, FrameBlockFormat, ShapeController, - SectionManager + SectionModel }; static const QUrl StyleManagerURL; static const QUrl ListsURL; static const QUrl TextRangeManagerURL; static const QUrl InlineObjectTextManagerURL; static const QUrl ChangeTrackerURL; static const QUrl UndoStackURL; static const QUrl TextEditorURL; static const QUrl LineNumberingConfigurationURL; static const QUrl BibliographyConfigurationURL; static const QUrl RelativeTabsURL; static const QUrl HeadingListURL; static const QUrl SelectionsURL; static const QUrl LayoutTextPageUrl; static const QUrl ParaTableSpacingAtStartUrl; static const QUrl IndexGeneratorManagerUrl; static const QUrl FrameCharFormatUrl; static const QUrl FrameBlockFormatUrl; static const QUrl ShapeControllerUrl; - static const QUrl SectionManagerUrl; + static const QUrl SectionModelUrl; private: QTextDocument *m_document; }; #endif // KOTEXTDOCUMENT_H diff --git a/libs/kotext/KoTextEditor.cpp b/libs/kotext/KoTextEditor.cpp index 1b81e3abed..b0d9160d67 100644 --- a/libs/kotext/KoTextEditor.cpp +++ b/libs/kotext/KoTextEditor.cpp @@ -1,1684 +1,1707 @@ /* This file is part of the KDE project * Copyright (C) 2009-2012 Pierre Stirnweiss * Copyright (C) 2006-2010 Thomas Zander * Copyright (c) 2011 Boudewijn Rempt * Copyright (C) 2011-2015 C. Boemann - * Copyright (C) 2014 Denis Kuplyakov + * Copyright (C) 2014-2015 Denis Kuplyakov * Copyright (C) 2015 Soma Schliszka * * 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 "KoTextEditor.h" #include "KoTextEditor_p.h" #include "KoList.h" #include "KoBookmark.h" #include "KoAnnotation.h" #include "KoTextRangeManager.h" #include "KoInlineTextObjectManager.h" #include "KoInlineNote.h" #include "KoInlineCite.h" #include "BibliographyGenerator.h" #include #include #include #include #include #include "KoShapeAnchor.h" #include "KoTextDocument.h" #include "KoTextLocator.h" #include "KoTableOfContentsGeneratorInfo.h" #include "KoBibliographyInfo.h" #include "changetracker/KoChangeTracker.h" #include "changetracker/KoChangeTrackerElement.h" #include "styles/KoCharacterStyle.h" #include "styles/KoParagraphStyle.h" #include "styles/KoStyleManager.h" #include "styles/KoTableCellStyle.h" #include "styles/KoTableStyle.h" #include "KoTableColumnAndRowStyleManager.h" #include "commands/DeleteTableRowCommand.h" #include "commands/DeleteTableColumnCommand.h" #include "commands/InsertTableRowCommand.h" #include "commands/InsertTableColumnCommand.h" #include "commands/ResizeTableCommand.h" #include "commands/TextPasteCommand.h" #include "commands/ListItemNumberingCommand.h" #include "commands/ChangeListCommand.h" #include "commands/InsertInlineObjectCommand.h" #include "commands/DeleteCommand.h" #include "commands/DeleteAnchorsCommand.h" #include "commands/DeleteAnnotationsCommand.h" #include "commands/InsertNoteCommand.h" #include "commands/AddTextRangeCommand.h" #include "commands/AddAnnotationCommand.h" #include "commands/RenameSectionCommand.h" #include "commands/NewSectionCommand.h" +#include "commands/SplitSectionsCommand.h" #include #include #include #include #include #include #include #include #include #include #include #include #include "KoTextDebug.h" Q_DECLARE_METATYPE(QTextFrame*) /*Private*/ KoTextEditor::Private::Private(KoTextEditor *qq, QTextDocument *document) : q(qq) , document (document) , addNewCommand(true) , dummyMacroAdded(false) , customCommandCount(0) , editProtectionCached(false) { caret = QTextCursor(document); editorState = NoOp; } void KoTextEditor::Private::emitTextFormatChanged() { emit q->textFormatChanged(); } void KoTextEditor::Private::newLine(KUndo2Command *parent) { // Handle if this is the special block before a table bool hiddenTableHandling = caret.blockFormat().hasProperty(KoParagraphStyle::HiddenByTable); if (hiddenTableHandling) { // Easy solution is to go back to the end of previous block and do the insertion from there. // However if there is no block before we have a problem. This may be the case if there is // a table before or we are at the beginning of a cell or a document. // So here is a better approach // 1) create block // 2) select the previous block so it get's deleted and replaced // 3) remove HiddenByTable from both new and previous block // 4) actually make new line replacing the block we just inserted // 5) set HiddenByTable on the block just before the table again caret.insertText("oops you should never see this"); caret.insertBlock(); caret.movePosition(QTextCursor::PreviousCharacter, QTextCursor::KeepAnchor); caret.movePosition(QTextCursor::StartOfBlock, QTextCursor::KeepAnchor); QTextBlockFormat bf = caret.blockFormat(); bf.clearProperty(KoParagraphStyle::HiddenByTable); caret.setBlockFormat(bf); } if (caret.hasSelection()) { q->deleteChar(false, parent); } KoTextDocument textDocument(document); KoStyleManager *styleManager = textDocument.styleManager(); KoParagraphStyle *nextStyle = 0; KoParagraphStyle *currentStyle = 0; if (styleManager) { int id = caret.blockFormat().intProperty(KoParagraphStyle::StyleId); currentStyle = styleManager->paragraphStyle(id); if (currentStyle == 0) // not a style based parag. Lets make the next one correct. nextStyle = styleManager->defaultParagraphStyle(); else nextStyle = styleManager->paragraphStyle(currentStyle->nextStyle()); Q_ASSERT(nextStyle); if (currentStyle == nextStyle) nextStyle = 0; } QTextCharFormat format = caret.charFormat(); if (format.hasProperty(KoCharacterStyle::ChangeTrackerId)) { format.clearProperty(KoCharacterStyle::ChangeTrackerId); } // Build the block format and subtract the properties that are not inherited QTextBlockFormat bf = caret.blockFormat(); bf.clearProperty(KoParagraphStyle::BreakBefore); bf.clearProperty(KoParagraphStyle::ListStartValue); bf.clearProperty(KoParagraphStyle::UnnumberedListItem); bf.clearProperty(KoParagraphStyle::IsListHeader); bf.clearProperty(KoParagraphStyle::MasterPageName); bf.clearProperty(KoParagraphStyle::OutlineLevel); bf.clearProperty(KoParagraphStyle::HiddenByTable); // We should stay in the same section so we can't start new one. bf.clearProperty(KoParagraphStyle::SectionStartings); // But we move all the current endings to the next paragraph. QTextBlockFormat origin = caret.blockFormat(); origin.clearProperty(KoParagraphStyle::SectionEndings); caret.setBlockFormat(origin); // Build the block char format which is just a copy QTextCharFormat bcf = caret.blockCharFormat(); // Actually insert the new paragraph char int startPosition = caret.position(); caret.insertBlock(bf, bcf); int endPosition = caret.position(); // Mark the CR as a tracked change QTextCursor changeCursor(document); changeCursor.beginEditBlock(); changeCursor.setPosition(startPosition); changeCursor.setPosition(endPosition, QTextCursor::KeepAnchor); changeCursor.endEditBlock(); q->registerTrackedChange(changeCursor, KoGenChange::InsertChange, kundo2_i18n("New Paragraph"), format, format, false); // possibly change the style if requested if (nextStyle) { QTextBlock block = caret.block(); if (currentStyle) currentStyle->unapplyStyle(block); nextStyle->applyStyle(block); format = block.charFormat(); } caret.setCharFormat(format); if (hiddenTableHandling) { // see code and comment above QTextBlockFormat bf = caret.blockFormat(); bf.setProperty(KoParagraphStyle::HiddenByTable, true); caret.setBlockFormat(bf); caret.movePosition(QTextCursor::PreviousCharacter); } } /*KoTextEditor*/ //TODO factor out the changeTracking charFormat setting from all individual slots to a public slot, which will be available for external commands (TextShape) //The BlockFormatVisitor and CharFormatVisitor are used when a property needs to be modified relative to its current value (which could be different over the selection). For example: increase indentation by 10pt. //The BlockFormatVisitor is also used for the change tracking of a blockFormat. The changeTracker stores the information about the changeId in the charFormat. The BlockFormatVisitor ensures that thd changeId is set on the whole block (even if only a part of the block is actually selected). //Should such mechanisms be later provided directly by Qt, we could dispose of these classes. KoTextEditor::KoTextEditor(QTextDocument *document) : QObject(document), d (new Private(this, document)) { connect (d->document, SIGNAL (undoCommandAdded()), this, SLOT (documentCommandAdded())); } KoTextEditor::~KoTextEditor() { delete d; } KoTextEditor *KoTextEditor::getTextEditorFromCanvas(KoCanvasBase *canvas) { KoSelection *selection = canvas->shapeManager()->selection(); if (selection) { foreach(KoShape *shape, selection->selectedShapes()) { if (KoTextShapeDataBase *textData = qobject_cast(shape->userData())) { KoTextDocument doc(textData->document()); return doc.textEditor(); } } } return 0; } QTextCursor* KoTextEditor::cursor() { return &(d->caret); } const QTextCursor KoTextEditor::constCursor() const { return QTextCursor(d->caret); } void KoTextEditor::registerTrackedChange(QTextCursor &selection, KoGenChange::Type changeType, const KUndo2MagicString &title, QTextFormat& format, QTextFormat& prevFormat, bool applyToWholeBlock) { KoChangeTracker *changeTracker = KoTextDocument(d->document).changeTracker(); if (!changeTracker || !changeTracker->recordChanges()) { // clear the ChangeTrackerId from the passed in selection, without recursively registring // change tracking again ;) int start = qMin(selection.position(), selection.anchor()); int end = qMax(selection.position(), selection.anchor()); QTextBlock block = selection.block(); if (block.position() > start) block = block.document()->findBlock(start); while (block.isValid() && block.position() < end) { QTextBlock::iterator iter = block.begin(); while (!iter.atEnd()) { QTextFragment fragment = iter.fragment(); if (fragment.position() > end) { break; } if (fragment.position() + fragment.length() <= start) { ++iter; continue; } QTextCursor cursor(block); cursor.setPosition(fragment.position()); QTextCharFormat fm = fragment.charFormat(); if (fm.hasProperty(KoCharacterStyle::ChangeTrackerId)) { fm.clearProperty(KoCharacterStyle::ChangeTrackerId); int to = qMin(end, fragment.position() + fragment.length()); cursor.setPosition(to, QTextCursor::KeepAnchor); cursor.setCharFormat(fm); iter = block.begin(); } else { ++iter; } } block = block.next(); } } else { if (changeType != KoGenChange::DeleteChange) { //first check if there already is an identical change registered just before or just after the selection. If so, merge appropriatly. //TODO implement for format change. handle the prevFormat/newFormat check. QTextCursor checker = QTextCursor(selection); int idBefore = 0; int idAfter = 0; int changeId = 0; int selectionBegin = qMin(checker.anchor(), checker.position()); int selectionEnd = qMax(checker.anchor(), checker.position()); checker.setPosition(selectionBegin); if (!checker.atBlockStart()) { int changeId = checker.charFormat().property(KoCharacterStyle::ChangeTrackerId).toInt(); if (changeId && changeTracker->elementById(changeId)->getChangeType() == changeType) idBefore = changeId; } else { if (!checker.currentTable()) { int changeId = checker.blockFormat().intProperty(KoCharacterStyle::ChangeTrackerId); if (changeId && changeTracker->elementById(changeId)->getChangeType() == changeType) idBefore = changeId; } else { idBefore = checker.currentTable()->format().intProperty(KoCharacterStyle::ChangeTrackerId); if (!idBefore) { idBefore = checker.currentTable()->cellAt(checker).format().intProperty(KoCharacterStyle::ChangeTrackerId); } } } checker.setPosition(selectionEnd); if (!checker.atEnd()) { checker.movePosition(QTextCursor::NextCharacter); idAfter = changeTracker->mergeableId(changeType, title, checker.charFormat().property( KoCharacterStyle::ChangeTrackerId ).toInt()); } changeId = (idBefore)?idBefore:idAfter; switch (changeType) {//TODO: this whole thing actually needs to be done like a visitor. If the selection contains several change regions, the parenting needs to be individualised. case KoGenChange::InsertChange: if (!changeId) changeId = changeTracker->getInsertChangeId(title, 0); break; case KoGenChange::FormatChange: if (!changeId) changeId = changeTracker->getFormatChangeId(title, format, prevFormat, 0); break; case KoGenChange::DeleteChange: //this should never be the case break; default: ;// do nothing } if (applyToWholeBlock) { selection.movePosition(QTextCursor::StartOfBlock); selection.movePosition(QTextCursor::EndOfBlock, QTextCursor::KeepAnchor); } QTextCharFormat f; f.setProperty(KoCharacterStyle::ChangeTrackerId, changeId); selection.mergeCharFormat(f); QTextBlock startBlock = selection.document()->findBlock(selection.anchor()); QTextBlock endBlock = selection.document()->findBlock(selection.position()); while (startBlock.isValid() && startBlock != endBlock) { startBlock = startBlock.next(); QTextCursor cursor(startBlock); QTextBlockFormat blockFormat; blockFormat.setProperty(KoCharacterStyle::ChangeTrackerId, changeId); cursor.mergeBlockFormat(blockFormat); QTextCharFormat blockCharFormat = cursor.blockCharFormat(); if (blockCharFormat.hasProperty(KoCharacterStyle::ChangeTrackerId)) { blockCharFormat.clearProperty(KoCharacterStyle::ChangeTrackerId); cursor.setBlockCharFormat(blockCharFormat); } } } } } // To figure out if a the blocks of the selection are write protected we need to // traverse the entire document as sections build up the protectiveness recursively. void KoTextEditor::recursivelyVisitSelection(QTextFrame::iterator it, KoTextVisitor &visitor) const { do { if (visitor.abortVisiting()) return; QTextBlock block = it.currentBlock(); QTextTable *table = qobject_cast(it.currentFrame()); QTextFrame *subFrame = it.currentFrame(); if (table) { // There are 4 ways this table can be selected: // - "before to mid" // - "mid to after" // - "complex mid to mid" // - "simple mid to mid" // The 3 first are entire cells, the fourth is within a cell if (d->caret.selectionStart() <= table->lastPosition() && d->caret.selectionEnd() >= table->firstPosition()) { // We have a selection somewhere QTextTableCell cell1 = table->cellAt(d->caret.selectionStart()); QTextTableCell cell2 = table->cellAt(d->caret.selectionEnd()); if (cell1 != cell2 || !cell1.isValid() || !cell2.isValid()) { // And the selection is complex or entire table int selectionRow; int selectionColumn; int selectionRowSpan; int selectionColumnSpan; if (!cell1.isValid() || !cell2.isValid()) { // entire table visitor.visitTable(table, KoTextVisitor::Entirely); selectionRow = selectionColumn = 0; selectionRowSpan = table->rows(); selectionColumnSpan = table->columns(); } else { visitor.visitTable(table, KoTextVisitor::Partly); d->caret.selectedTableCells(&selectionRow, &selectionRowSpan, &selectionColumn, &selectionColumnSpan); } for (int r = selectionRow; r < selectionRow + selectionRowSpan; r++) { for (int c = selectionColumn; c < selectionColumn + selectionColumnSpan; c++) { QTextTableCell cell = table->cellAt(r,c); if (!cell.format().boolProperty(KoTableCellStyle::CellIsProtected)) { visitor.visitTableCell(&cell, KoTextVisitor::Partly); recursivelyVisitSelection(cell.begin(), visitor); } else { visitor.nonVisit(); } if (visitor.abortVisiting()) return; } } } else { visitor.visitTable(table, KoTextVisitor::Partly); // And the selection is simple if (!cell1.format().boolProperty(KoTableCellStyle::CellIsProtected)) { visitor.visitTableCell(&cell1, KoTextVisitor::Entirely); recursivelyVisitSelection(cell1.begin(), visitor); } else { visitor.nonVisit(); } return; } } if (d->caret.selectionEnd() <= table->lastPosition()) { return; } } else if (subFrame) { recursivelyVisitSelection(subFrame->begin(), visitor); } else { // TODO build up the section stack if (d->caret.selectionStart() < block.position() + block.length() && d->caret.selectionEnd() >= block.position()) { // We have a selection somewhere if (true) { // TODO don't change if block is protected by section visitor.visitBlock(block, d->caret); } else { visitor.nonVisit(); } } // TODO tear down the section stack if (d->caret.selectionEnd() < block.position() + block.length()) { return; } } if (!it.atEnd()) { ++it; } } while (!it.atEnd()); } KoBookmark *KoTextEditor::addBookmark(const QString &name) {//TODO changeTracking KUndo2Command *topCommand = beginEditBlock(kundo2_i18n("Add Bookmark")); KoBookmark *bookmark = new KoBookmark(d->caret); bookmark->setName(name); bookmark->setManager(KoTextDocument(d->document).textRangeManager()); addCommand(new AddTextRangeCommand(bookmark, topCommand)); endEditBlock(); return bookmark; } KoTextRangeManager *KoTextEditor::textRangeManager() const { return KoTextDocument(d->document).textRangeManager(); } KoAnnotation *KoTextEditor::addAnnotation(KoShape *annotationShape) { KUndo2Command *topCommand = beginEditBlock(kundo2_i18n("Add Annotation")); KoAnnotation *annotation = new KoAnnotation(d->caret); KoTextRangeManager *textRangeManager = KoTextDocument(d->document).textRangeManager(); annotation->setManager(textRangeManager); //FIXME: I need the name, a unique name, we can set selected text as annotation name or use createUniqueAnnotationName function // to do it for us. QString name = annotation->createUniqueAnnotationName(textRangeManager->annotationManager(), "", false); annotation->setName(name); annotation->setAnnotationShape(annotationShape); addCommand(new AddAnnotationCommand(annotation, topCommand)); endEditBlock(); return annotation; } KoInlineObject *KoTextEditor::insertIndexMarker() {//TODO changeTracking if (isEditProtected()) { return 0; } d->updateState(KoTextEditor::Private::Custom, kundo2_i18n("Insert Index")); if (d->caret.blockFormat().hasProperty(KoParagraphStyle::HiddenByTable)) { d->newLine(0); } QTextBlock block = d->caret.block(); if (d->caret.position() >= block.position() + block.length() - 1) return 0; // can't insert one at end of text if (block.text()[ d->caret.position() - block.position()].isSpace()) return 0; // can't insert one on a whitespace as that does not indicate a word. KoTextLocator *tl = new KoTextLocator(); KoTextDocument(d->document).inlineTextObjectManager()->insertInlineObject(d->caret, tl); d->updateState(KoTextEditor::Private::NoOp); return tl; } void KoTextEditor::insertInlineObject(KoInlineObject *inliner, KUndo2Command *cmd) { if (isEditProtected()) { return; } KUndo2Command *topCommand = cmd; if (!cmd) { topCommand = beginEditBlock(kundo2_i18n("Insert Variable")); } if (d->caret.hasSelection()) { deleteChar(false, topCommand); } d->caret.beginEditBlock(); if (d->caret.blockFormat().hasProperty(KoParagraphStyle::HiddenByTable)) { d->newLine(0); } QTextCharFormat format = d->caret.charFormat(); if (format.hasProperty(KoCharacterStyle::ChangeTrackerId)) { format.clearProperty(KoCharacterStyle::ChangeTrackerId); } InsertInlineObjectCommand *insertInlineObjectCommand = new InsertInlineObjectCommand(inliner, d->document, topCommand); d->caret.endEditBlock(); if (!cmd) { addCommand(topCommand); endEditBlock(); } emit cursorPositionChanged(); } void KoTextEditor::updateInlineObjectPosition(int start, int end) { KoInlineTextObjectManager *inlineObjectManager = KoTextDocument(d->document).inlineTextObjectManager(); // and, of course, every inline object after the current position has the wrong position QTextCursor cursor = d->document->find(QString(QChar::ObjectReplacementCharacter), start); while (!cursor.isNull() && (end > -1 && cursor.position() < end )) { QTextCharFormat fmt = cursor.charFormat(); KoInlineObject *obj = inlineObjectManager->inlineTextObject(fmt); obj->updatePosition(d->document, cursor.position(), fmt); cursor = d->document->find(QString(QChar::ObjectReplacementCharacter), cursor.position()); } } void KoTextEditor::removeAnchors(const QList &anchors, KUndo2Command *parent) { Q_ASSERT(parent); addCommand(new DeleteAnchorsCommand(anchors, d->document, parent)); } void KoTextEditor::removeAnnotations(const QList &annotations, KUndo2Command *parent) { Q_ASSERT(parent); addCommand(new DeleteAnnotationsCommand(annotations, d->document, parent)); } void KoTextEditor::insertFrameBreak() { if (isEditProtected()) { return; } QTextCursor curr(d->caret.block()); if (dynamic_cast (curr.currentFrame())) { return; } d->updateState(KoTextEditor::Private::KeyPress, kundo2_i18n("Insert Break")); QTextBlock block = d->caret.block(); if (d->caret.position() == block.position() && block.length() > 0) { // start of parag QTextBlockFormat bf = d->caret.blockFormat(); bf.setProperty(KoParagraphStyle::BreakBefore, KoText::PageBreak); d->caret.insertBlock(bf); if (block.textList()) block.textList()->remove(block); } else { QTextBlockFormat bf = d->caret.blockFormat(); if (!d->caret.blockFormat().hasProperty(KoParagraphStyle::HiddenByTable)) { newLine(); } bf = d->caret.blockFormat(); bf.setProperty(KoParagraphStyle::BreakBefore, KoText::PageBreak); d->caret.setBlockFormat(bf); } d->updateState(KoTextEditor::Private::NoOp); emit cursorPositionChanged(); } void KoTextEditor::paste(KoCanvasBase *canvas, const QMimeData *mimeData, bool pasteAsText) { if (isEditProtected()) { return; } KoShapeController *shapeController = KoTextDocument(d->document).shapeController(); addCommand(new TextPasteCommand(mimeData, d->document, shapeController, canvas, 0, pasteAsText)); } void KoTextEditor::deleteChar(bool previous, KUndo2Command *parent) { if (isEditProtected()) { return; } KoShapeController *shapeController = KoTextDocument(d->document).shapeController(); // Find out if we should track changes or not // KoChangeTracker *changeTracker = KoTextDocument(d->document).changeTracker(); // bool trackChanges = false; // if (changeTracker && changeTracker->recordChanges()) { // trackChanges = true; // } if (previous) { if (!d->caret.hasSelection() && d->caret.block().blockFormat().hasProperty(KoParagraphStyle::HiddenByTable)) { movePosition(QTextCursor::PreviousCharacter); if (d->caret.block().length() <= 1) { movePosition(QTextCursor::NextCharacter); } else return; // it becomes just a cursor movement; } } else { if (!d->caret.hasSelection() && d->caret.block().length() > 1) { QTextCursor tmpCursor = d->caret; tmpCursor.movePosition(QTextCursor::NextCharacter); if (tmpCursor.block().blockFormat().hasProperty(KoParagraphStyle::HiddenByTable)) { movePosition(QTextCursor::NextCharacter); return; // it becomes just a cursor movement; } } } if (previous) { addCommand(new DeleteCommand(DeleteCommand::PreviousChar, d->document, shapeController, parent)); } else { addCommand(new DeleteCommand(DeleteCommand::NextChar, d->document, shapeController, parent)); } } void KoTextEditor::toggleListNumbering(bool numberingEnabled) { if (isEditProtected()) { return; } addCommand(new ListItemNumberingCommand(block(), numberingEnabled)); emit textFormatChanged(); } void KoTextEditor::setListProperties(const KoListLevelProperties &llp, ChangeListFlags flags, KUndo2Command *parent) { if (isEditProtected()) { return; } if (flags & AutoListStyle && d->caret.block().textList() == 0) { flags = MergeWithAdjacentList; } if (KoList *list = KoTextDocument(d->document).list(d->caret.block().textList())) { KoListStyle *listStyle = list->style(); if (KoStyleManager *styleManager = KoTextDocument(d->document).styleManager()) { QList paragraphStyles = styleManager->paragraphStyles(); foreach (KoParagraphStyle *paragraphStyle, paragraphStyles) { if (paragraphStyle->listStyle() == listStyle || (paragraphStyle->list() && paragraphStyle->list()->style() == listStyle)) { flags = NoFlags; break; } } } } addCommand(new ChangeListCommand(d->caret, llp, flags, parent)); emit textFormatChanged(); } int KoTextEditor::anchor() const { return d->caret.anchor(); } bool KoTextEditor::atBlockEnd() const { return d->caret.atBlockEnd(); } bool KoTextEditor::atBlockStart() const { return d->caret.atBlockStart(); } bool KoTextEditor::atEnd() const { QTextCursor cursor(d->caret.document()->rootFrame()->lastCursorPosition()); cursor.movePosition(QTextCursor::PreviousCharacter); QTextFrame *auxFrame = cursor.currentFrame(); if (auxFrame->format().intProperty(KoText::SubFrameType) == KoText::AuxillaryFrameType) { //auxFrame really is the auxillary frame if (d->caret.position() == auxFrame->firstPosition() - 1) { return true; } return false; } return d->caret.atEnd(); } bool KoTextEditor::atStart() const { return d->caret.atStart(); } QTextBlock KoTextEditor::block() const { return d->caret.block(); } int KoTextEditor::blockNumber() const { return d->caret.blockNumber(); } void KoTextEditor::clearSelection() { d->caret.clearSelection(); } int KoTextEditor::columnNumber() const { return d->caret.columnNumber(); } void KoTextEditor::deleteChar() { if (isEditProtected()) { return; } if (!d->caret.hasSelection()) { if (d->caret.atEnd()) return; // We alson need to refuse delete if we are at final pos in table cell if (QTextTable *table = d->caret.currentTable()) { QTextTableCell cell = table->cellAt(d->caret.position()); if (d->caret.position() == cell.lastCursorPosition().position()) { return; } } // We also need to refuse delete if it will delete a note frame QTextCursor after(d->caret); after.movePosition(QTextCursor::NextCharacter); QTextFrame *beforeFrame = d->caret.currentFrame(); while (qobject_cast(beforeFrame)) { beforeFrame = beforeFrame->parentFrame(); } QTextFrame *afterFrame = after.currentFrame(); while (qobject_cast(afterFrame)) { afterFrame = afterFrame->parentFrame(); } if (beforeFrame != afterFrame) { return; } } deleteChar(false); emit cursorPositionChanged(); } void KoTextEditor::deletePreviousChar() { if (isEditProtected()) { return; } if (!d->caret.hasSelection()) { if (d->caret.atStart()) return; // We also need to refuse delete if we are at first pos in table cell if (QTextTable *table = d->caret.currentTable()) { QTextTableCell cell = table->cellAt(d->caret.position()); if (d->caret.position() == cell.firstCursorPosition().position()) { return; } } // We also need to refuse delete if it will delete a note frame QTextCursor after(d->caret); after.movePosition(QTextCursor::PreviousCharacter); QTextFrame *beforeFrame = d->caret.currentFrame(); while (qobject_cast(beforeFrame)) { beforeFrame = beforeFrame->parentFrame(); } QTextFrame *afterFrame = after.currentFrame(); while (qobject_cast(afterFrame)) { afterFrame = afterFrame->parentFrame(); } if (beforeFrame != afterFrame) { return; } } deleteChar(true); emit cursorPositionChanged(); } QTextDocument *KoTextEditor::document() const { return d->caret.document(); } bool KoTextEditor::hasComplexSelection() const { return d->caret.hasComplexSelection(); } bool KoTextEditor::hasSelection() const { return d->caret.hasSelection(); } class ProtectionCheckVisitor : public KoTextVisitor { public: ProtectionCheckVisitor(const KoTextEditor *editor) : KoTextVisitor(const_cast(editor)) { } // override super's implementation to not waste cpu cycles virtual void visitBlock(QTextBlock&, const QTextCursor &) { } virtual void nonVisit() { setAbortVisiting(true); } }; bool KoTextEditor::isEditProtected(bool useCached) const { ProtectionCheckVisitor visitor(this); if (useCached) { if (! d->editProtectionCached) { recursivelyVisitSelection(d->document->rootFrame()->begin(), visitor); d->editProtected = visitor.abortVisiting(); d->editProtectionCached = true; } return d->editProtected; } d->editProtectionCached = false; recursivelyVisitSelection(d->document->rootFrame()->begin(), visitor); return visitor.abortVisiting(); } void KoTextEditor::insertTable(int rows, int columns) { if (isEditProtected() || rows <= 0 || columns <= 0) { return; } bool hasSelection = d->caret.hasSelection(); if (!hasSelection) { d->updateState(KoTextEditor::Private::Custom, kundo2_i18n("Insert Table")); } else { KUndo2Command *topCommand = beginEditBlock(kundo2_i18n("Insert Table")); deleteChar(false, topCommand); d->caret.beginEditBlock(); } QTextTableFormat tableFormat; tableFormat.setWidth(QTextLength(QTextLength::PercentageLength, 100)); tableFormat.setProperty(KoTableStyle::CollapsingBorders, true); tableFormat.setMargin(5); KoChangeTracker *changeTracker = KoTextDocument(d->document).changeTracker(); if (changeTracker && changeTracker->recordChanges()) { QTextCharFormat charFormat = d->caret.charFormat(); QTextBlockFormat blockFormat = d->caret.blockFormat(); KUndo2MagicString title = kundo2_i18n("Insert Table"); int changeId; if (!d->caret.atBlockStart()) { changeId = changeTracker->mergeableId(KoGenChange::InsertChange, title, charFormat.intProperty(KoCharacterStyle::ChangeTrackerId)); } else { changeId = changeTracker->mergeableId(KoGenChange::InsertChange, title, blockFormat.intProperty(KoCharacterStyle::ChangeTrackerId)); } if (!changeId) { changeId = changeTracker->getInsertChangeId(title, 0); } tableFormat.setProperty(KoCharacterStyle::ChangeTrackerId, changeId); } QTextBlock currentBlock = d->caret.block(); if (d->caret.position() != currentBlock.position()) { d->caret.insertBlock(); currentBlock = d->caret.block(); } QTextTable *table = d->caret.insertTable(rows, columns, tableFormat); // Get (and thus create) columnandrowstyle manager so it becomes part of undo // and not something that happens uncontrollably during layout KoTableColumnAndRowStyleManager::getManager(table); // 'Hide' the block before the table QTextBlockFormat blockFormat = currentBlock.blockFormat(); QTextCursor cursor(currentBlock); blockFormat.setProperty(KoParagraphStyle::HiddenByTable, true); cursor.setBlockFormat(blockFormat); // Define the initial cell format QTextTableCellFormat format; KoTableCellStyle cellStyle; cellStyle.setEdge(KoBorder::TopBorder, KoBorder::BorderSolid, 2, QColor(Qt::black)); cellStyle.setEdge(KoBorder::LeftBorder, KoBorder::BorderSolid, 2, QColor(Qt::black)); cellStyle.setEdge(KoBorder::BottomBorder, KoBorder::BorderSolid, 2, QColor(Qt::black)); cellStyle.setEdge(KoBorder::RightBorder, KoBorder::BorderSolid, 2, QColor(Qt::black)); cellStyle.setPadding(5); cellStyle.applyStyle(format); // Apply formatting to all cells for (int row = 0; row < table->rows(); ++row) { for (int col = 0; col < table->columns(); ++col) { QTextTableCell cell = table->cellAt(row, col); cell.setFormat(format); } } if (hasSelection) { d->caret.endEditBlock(); endEditBlock(); } else { d->updateState(KoTextEditor::Private::NoOp); } emit cursorPositionChanged(); } void KoTextEditor::insertTableRowAbove() { if (isEditProtected()) { return; } QTextTable *table = d->caret.currentTable(); if (table) { addCommand(new InsertTableRowCommand(this, table, false)); } } void KoTextEditor::insertTableRowBelow() { if (isEditProtected()) { return; } QTextTable *table = d->caret.currentTable(); if (table) { addCommand(new InsertTableRowCommand(this, table, true)); } } void KoTextEditor::insertTableColumnLeft() { if (isEditProtected()) { return; } QTextTable *table = d->caret.currentTable(); if (table) { addCommand(new InsertTableColumnCommand(this, table, false)); } } void KoTextEditor::insertTableColumnRight() { if (isEditProtected()) { return; } QTextTable *table = d->caret.currentTable(); if (table) { addCommand(new InsertTableColumnCommand(this, table, true)); } } void KoTextEditor::deleteTableColumn() { if (isEditProtected()) { return; } QTextTable *table = d->caret.currentTable(); if (table) { addCommand(new DeleteTableColumnCommand(this, table)); } } void KoTextEditor::deleteTableRow() { if (isEditProtected()) { return; } QTextTable *table = d->caret.currentTable(); if (table) { addCommand(new DeleteTableRowCommand(this, table)); } } void KoTextEditor::mergeTableCells() { if (isEditProtected()) { return; } d->updateState(KoTextEditor::Private::Custom, kundo2_i18n("Merge Cells")); QTextTable *table = d->caret.currentTable(); if (table) { table->mergeCells(d->caret); } d->updateState(KoTextEditor::Private::NoOp); } void KoTextEditor::splitTableCells() { if (isEditProtected()) { return; } d->updateState(KoTextEditor::Private::Custom, kundo2_i18n("Split Cells")); QTextTable *table = d->caret.currentTable(); if (table) { QTextTableCell cell = table->cellAt(d->caret); table->splitCell(cell.row(), cell.column(), 1, 1); } d->updateState(KoTextEditor::Private::NoOp); } void KoTextEditor::adjustTableColumnWidth(QTextTable *table, int column, qreal width, KUndo2Command *parentCommand) { ResizeTableCommand *cmd = new ResizeTableCommand(table, true, column, width, parentCommand); addCommand(cmd); } void KoTextEditor::adjustTableRowHeight(QTextTable *table, int column, qreal height, KUndo2Command *parentCommand) { ResizeTableCommand *cmd = new ResizeTableCommand(table, false, column, height, parentCommand); addCommand(cmd); } void KoTextEditor::adjustTableWidth(QTextTable *table, qreal dLeft, qreal dRight) { d->updateState(KoTextEditor::Private::Custom, kundo2_i18n("Adjust Table Width")); d->caret.beginEditBlock(); QTextTableFormat fmt = table->format(); if (dLeft) { fmt.setLeftMargin(fmt.leftMargin() + dLeft); } if (dRight) { fmt.setRightMargin(fmt.rightMargin() + dRight); } table->setFormat(fmt); d->caret.endEditBlock(); d->updateState(KoTextEditor::Private::NoOp); } void KoTextEditor::setTableBorderData(QTextTable *table, int row, int column, KoBorder::BorderSide cellSide, const KoBorder::BorderData &data) { d->updateState(KoTextEditor::Private::Custom, kundo2_i18n("Change Border Formatting")); d->caret.beginEditBlock(); QTextTableCell cell = table->cellAt(row, column); QTextCharFormat fmt = cell.format(); KoBorder border = fmt.property(KoTableCellStyle::Borders).value(); border.setBorderData(cellSide, data); fmt.setProperty(KoTableCellStyle::Borders, QVariant::fromValue(border)); cell.setFormat(fmt); d->caret.endEditBlock(); d->updateState(KoTextEditor::Private::NoOp); } KoInlineNote *KoTextEditor::insertFootNote() { if (isEditProtected()) { return 0; } InsertNoteCommand *cmd = new InsertNoteCommand(KoInlineNote::Footnote, d->document); addCommand(cmd); emit cursorPositionChanged(); return cmd->m_inlineNote; } KoInlineNote *KoTextEditor::insertEndNote() { if (isEditProtected()) { return 0; } InsertNoteCommand *cmd = new InsertNoteCommand(KoInlineNote::Endnote, d->document); addCommand(cmd); emit cursorPositionChanged(); return cmd->m_inlineNote; } void KoTextEditor::insertTableOfContents(KoTableOfContentsGeneratorInfo *info) { if (isEditProtected()) { return; } bool hasSelection = d->caret.hasSelection(); if (!hasSelection) { d->updateState(KoTextEditor::Private::Custom, kundo2_i18n("Insert Table Of Contents")); } else { KUndo2Command *topCommand = beginEditBlock(kundo2_i18n("Insert Table Of Contents")); deleteChar(false, topCommand); d->caret.beginEditBlock(); } QTextBlockFormat tocFormat; KoTableOfContentsGeneratorInfo *newToCInfo = info->clone(); QTextDocument *tocDocument = new QTextDocument(); tocFormat.setProperty(KoParagraphStyle::TableOfContentsData, QVariant::fromValue(newToCInfo) ); tocFormat.setProperty(KoParagraphStyle::GeneratedDocument, QVariant::fromValue(tocDocument)); //make sure we set up the textrangemanager on the subdocument as well KoTextDocument(tocDocument).setTextRangeManager(new KoTextRangeManager); KoChangeTracker *changeTracker = KoTextDocument(d->document).changeTracker(); if (changeTracker && changeTracker->recordChanges()) { QTextCharFormat charFormat = d->caret.charFormat(); QTextBlockFormat blockFormat = d->caret.blockFormat(); KUndo2MagicString title = kundo2_i18n("Insert Table Of Contents"); int changeId; if (!d->caret.atBlockStart()) { changeId = changeTracker->mergeableId(KoGenChange::InsertChange, title, charFormat.intProperty(KoCharacterStyle::ChangeTrackerId)); } else { changeId = changeTracker->mergeableId(KoGenChange::InsertChange, title, blockFormat.intProperty(KoCharacterStyle::ChangeTrackerId)); } if (!changeId) { changeId = changeTracker->getInsertChangeId(title, 0); } tocFormat.setProperty(KoCharacterStyle::ChangeTrackerId, changeId); } d->caret.insertBlock(); d->caret.movePosition(QTextCursor::Left); d->caret.insertBlock(tocFormat); d->caret.movePosition(QTextCursor::Right); if (hasSelection) { d->caret.endEditBlock(); endEditBlock(); } else { d->updateState(KoTextEditor::Private::NoOp); } emit cursorPositionChanged(); } void KoTextEditor::setTableOfContentsConfig(KoTableOfContentsGeneratorInfo *info, const QTextBlock &block) { if (isEditProtected()) { return; } KoTableOfContentsGeneratorInfo *newToCInfo=info->clone(); d->updateState(KoTextEditor::Private::Custom, kundo2_i18n("Modify Table Of Contents")); QTextCursor cursor(block); QTextBlockFormat tocBlockFormat=block.blockFormat(); tocBlockFormat.setProperty(KoParagraphStyle::TableOfContentsData, QVariant::fromValue(newToCInfo) ); cursor.setBlockFormat(tocBlockFormat); d->updateState(KoTextEditor::Private::NoOp); emit cursorPositionChanged(); const_cast(document())->markContentsDirty(document()->firstBlock().position(), 0); } void KoTextEditor::insertBibliography(KoBibliographyInfo *info) { bool hasSelection = d->caret.hasSelection(); if (!hasSelection) { d->updateState(KoTextEditor::Private::Custom, kundo2_i18n("Insert Bibliography")); } else { KUndo2Command *topCommand = beginEditBlock(kundo2_i18n("Insert Bibliography")); deleteChar(false, topCommand); d->caret.beginEditBlock(); } QTextBlockFormat bibFormat; KoBibliographyInfo *newBibInfo = info->clone(); QTextDocument *bibDocument = new QTextDocument(); bibFormat.setProperty( KoParagraphStyle::BibliographyData, QVariant::fromValue(newBibInfo)); bibFormat.setProperty( KoParagraphStyle::GeneratedDocument, QVariant::fromValue(bibDocument)); //make sure we set up the textrangemanager on the subdocument as well KoTextDocument(bibDocument).setTextRangeManager(new KoTextRangeManager); KoChangeTracker *changeTracker = KoTextDocument(d->document).changeTracker(); if (changeTracker && changeTracker->recordChanges()) { QTextCharFormat charFormat = d->caret.charFormat(); QTextBlockFormat blockFormat = d->caret.blockFormat(); KUndo2MagicString title = kundo2_i18n("Insert Bibliography"); int changeId; if (!d->caret.atBlockStart()) { changeId = changeTracker->mergeableId(KoGenChange::InsertChange, title, charFormat.intProperty(KoCharacterStyle::ChangeTrackerId)); } else { changeId = changeTracker->mergeableId(KoGenChange::InsertChange, title, blockFormat.intProperty(KoCharacterStyle::ChangeTrackerId)); } if (!changeId) { changeId = changeTracker->getInsertChangeId(title, 0); } bibFormat.setProperty(KoCharacterStyle::ChangeTrackerId, changeId); } d->caret.insertBlock(); d->caret.movePosition(QTextCursor::Left); d->caret.insertBlock(bibFormat); d->caret.movePosition(QTextCursor::Right); new BibliographyGenerator(bibDocument, block(), newBibInfo); if (hasSelection) { d->caret.endEditBlock(); endEditBlock(); } else { d->updateState(KoTextEditor::Private::NoOp); } emit cursorPositionChanged(); } KoInlineCite *KoTextEditor::insertCitation() { bool hasSelection = d->caret.hasSelection(); if (!hasSelection) { d->updateState(KoTextEditor::Private::KeyPress, kundo2_i18n("Add Citation")); } else { KUndo2Command *topCommand = beginEditBlock(kundo2_i18n("Add Citation")); deleteChar(false, topCommand); d->caret.beginEditBlock(); } KoInlineCite *cite = new KoInlineCite(KoInlineCite::Citation); KoInlineTextObjectManager *manager = KoTextDocument(d->document).inlineTextObjectManager(); manager->insertInlineObject(d->caret,cite); if (hasSelection) { d->caret.endEditBlock(); endEditBlock(); } else { d->updateState(KoTextEditor::Private::NoOp); } return cite; } void KoTextEditor::insertText(const QString &text, const QString &hRef) { if (isEditProtected()) { return; } bool hasSelection = d->caret.hasSelection(); if (!hasSelection) { d->updateState(KoTextEditor::Private::KeyPress, kundo2_i18n("Typing")); } else { KUndo2Command *topCommand = beginEditBlock(kundo2_i18n("Typing")); deleteChar(false, topCommand); d->caret.beginEditBlock(); } //first we make sure that we clear the inlineObject charProperty, if we have no selection if (!hasSelection && d->caret.charFormat().hasProperty(KoCharacterStyle::InlineInstanceId)) d->clearCharFormatProperty(KoCharacterStyle::InlineInstanceId); int startPosition = d->caret.position(); if (d->caret.blockFormat().hasProperty(KoParagraphStyle::HiddenByTable)) { d->newLine(0); startPosition = d->caret.position(); } QTextCharFormat format = d->caret.charFormat(); if (format.hasProperty(KoCharacterStyle::ChangeTrackerId)) { format.clearProperty(KoCharacterStyle::ChangeTrackerId); } static QRegExp urlScanner("\\S+://\\S+"); if (!hRef.isEmpty()) { format.setAnchor(true); format.setProperty(KoCharacterStyle::AnchorType, KoCharacterStyle::Anchor); if ((urlScanner.indexIn(hRef)) == 0) {//web url format.setAnchorHref(hRef); } else { format.setAnchorHref("#"+hRef); } } d->caret.insertText(text, format); int endPosition = d->caret.position(); //Mark the inserted text d->caret.setPosition(startPosition); d->caret.setPosition(endPosition, QTextCursor::KeepAnchor); registerTrackedChange(d->caret, KoGenChange::InsertChange, kundo2_i18n("Typing"), format, format, false); d->caret.clearSelection(); if (hasSelection) { d->caret.endEditBlock(); endEditBlock(); } if (!hRef.isEmpty()) { format.setAnchor(false); format.clearProperty(KoCharacterStyle::Anchor); format.clearProperty(KoCharacterStyle::AnchorType); d->caret.setCharFormat(format); } emit cursorPositionChanged(); } void KoTextEditor::insertHtml(const QString &html) { if (isEditProtected()) { return; } // XXX: do the changetracking and everything! QTextBlock currentBlock = d->caret.block(); d->caret.insertHtml(html); QList pastedLists; KoList *currentPastedList = 0; while (currentBlock != d->caret.block()) { currentBlock = currentBlock.next(); QTextList *currentTextList = currentBlock.textList(); if(currentTextList && !pastedLists.contains(currentBlock.textList())) { KoListStyle *listStyle = KoTextDocument(d->document).styleManager()->defaultListStyle()->clone(); listStyle->setName(""); listStyle->setStyleId(0); currentPastedList = new KoList(d->document, listStyle); QTextListFormat currentTextListFormat = currentTextList->format(); KoListLevelProperties levelProperty = listStyle->levelProperties(currentTextListFormat.indent()); levelProperty.setStyle(static_cast(currentTextListFormat.style())); levelProperty.setLevel(currentTextListFormat.indent()); levelProperty.setListItemPrefix(""); levelProperty.setListItemSuffix(""); levelProperty.setListId((KoListStyle::ListIdType)currentTextList); listStyle->setLevelProperties(levelProperty); currentTextListFormat.setProperty(KoListStyle::Level, currentTextListFormat.indent()); currentBlock.textList()->setFormat(currentTextListFormat); currentPastedList->updateStoredList(currentBlock); currentPastedList->setStyle(listStyle); pastedLists.append(currentBlock.textList()); } } } bool KoTextEditor::movePosition(QTextCursor::MoveOperation operation, QTextCursor::MoveMode mode, int n) { d->editProtectionCached = false; // We need protection against moving in and out of note areas QTextCursor after(d->caret); bool b = after.movePosition (operation, mode, n); QTextFrame *beforeFrame = d->caret.currentFrame(); while (qobject_cast(beforeFrame)) { beforeFrame = beforeFrame->parentFrame(); } QTextFrame *afterFrame = after.currentFrame(); while (qobject_cast(afterFrame)) { afterFrame = afterFrame->parentFrame(); } if (beforeFrame == afterFrame) { if (after.selectionEnd() == after.document()->characterCount() -1) { QTextCursor cursor(d->caret.document()->rootFrame()->lastCursorPosition()); cursor.movePosition(QTextCursor::PreviousCharacter); QTextFrame *auxFrame = cursor.currentFrame(); if (auxFrame->format().intProperty(KoText::SubFrameType) == KoText::AuxillaryFrameType) { if (operation == QTextCursor::End) { d->caret.setPosition(auxFrame->firstPosition() - 1, mode); emit cursorPositionChanged(); return true; } return false; } } d->caret = after; emit cursorPositionChanged(); return b; } return false; } void KoTextEditor::newSection() { if (isEditProtected()) { return; } NewSectionCommand *cmd = new NewSectionCommand(d->document); addCommand(cmd); emit cursorPositionChanged(); } -void KoTextEditor::renameSection(KoSection* section, const QString &newName) +void KoTextEditor::splitSectionsStartings(int sectionIdToInsertBefore) { if (isEditProtected()) { return; } + addCommand(new SplitSectionsCommand( + d->document, + SplitSectionsCommand::Startings, + sectionIdToInsertBefore)); + emit cursorPositionChanged(); +} - RenameSectionCommand *cmd = new RenameSectionCommand(section, newName); - addCommand(cmd); +void KoTextEditor::splitSectionsEndings(int sectionIdToInsertAfter) +{ + if (isEditProtected()) { + return; + } + addCommand(new SplitSectionsCommand( + d->document, + SplitSectionsCommand::Endings, + sectionIdToInsertAfter)); + emit cursorPositionChanged(); +} + +void KoTextEditor::renameSection(KoSection* section, const QString &newName) +{ + if (isEditProtected()) { + return; + } + addCommand(new RenameSectionCommand(section, newName, document())); } void KoTextEditor::newLine() { if (isEditProtected()) { return; } bool hasSelection = d->caret.hasSelection(); if (!hasSelection) { d->updateState(KoTextEditor::Private::Custom, kundo2_i18n("New Paragraph")); } else { KUndo2Command *topCommand = beginEditBlock(kundo2_i18n("New Paragraph")); deleteChar(false, topCommand); } d->caret.beginEditBlock(); d->newLine(0); d->caret.endEditBlock(); if (hasSelection) { endEditBlock(); } else { d->updateState(KoTextEditor::Private::NoOp); } emit cursorPositionChanged(); } class WithinSelectionVisitor : public KoTextVisitor { public: WithinSelectionVisitor(KoTextEditor *editor, int position) : KoTextVisitor(editor) , m_position(position) , m_returnValue(false) { } virtual void visitBlock(QTextBlock &block, const QTextCursor &caret) { if (m_position >= qMax(block.position(), caret.selectionStart()) && m_position <= qMin(block.position() + block.length(), caret.selectionEnd())) { m_returnValue = true; setAbortVisiting(true); } } int m_position; //the position we are searching for bool m_returnValue; //if position is within the selection }; bool KoTextEditor::isWithinSelection(int position) const { // we know the visitor doesn't do anything with the texteditor so let's const cast // to have a more beautiful outer api WithinSelectionVisitor visitor(const_cast(this), position); recursivelyVisitSelection(d->document->rootFrame()->begin(), visitor); return visitor.m_returnValue; } int KoTextEditor::position() const { return d->caret.position(); } void KoTextEditor::select(QTextCursor::SelectionType selection) { //TODO add selection of previous/next char, and option about hasSelection d->caret.select(selection); } QString KoTextEditor::selectedText() const { return d->caret.selectedText(); } QTextDocumentFragment KoTextEditor::selection() const { return d->caret.selection(); } int KoTextEditor::selectionEnd() const { return d->caret.selectionEnd(); } int KoTextEditor::selectionStart() const { return d->caret.selectionStart(); } void KoTextEditor::setPosition(int pos, QTextCursor::MoveMode mode) { d->editProtectionCached = false; if (pos == d->caret.document()->characterCount() -1) { QTextCursor cursor(d->caret.document()->rootFrame()->lastCursorPosition()); cursor.movePosition(QTextCursor::PreviousCharacter); QTextFrame *auxFrame = cursor.currentFrame(); if (auxFrame->format().intProperty(KoText::SubFrameType) == KoText::AuxillaryFrameType) { return; } } if (mode == QTextCursor::MoveAnchor) { d->caret.setPosition (pos, mode); emit cursorPositionChanged(); } // We need protection against moving in and out of note areas QTextCursor after(d->caret); after.setPosition (pos, mode); QTextFrame *beforeFrame = d->caret.currentFrame(); while (qobject_cast(beforeFrame)) { beforeFrame = beforeFrame->parentFrame(); } QTextFrame *afterFrame = after.currentFrame(); while (qobject_cast(afterFrame)) { afterFrame = afterFrame->parentFrame(); } if (beforeFrame == afterFrame) { d->caret = after; emit cursorPositionChanged(); } } void KoTextEditor::setVisualNavigation(bool b) { d->caret.setVisualNavigation (b); } bool KoTextEditor::visualNavigation() const { return d->caret.visualNavigation(); } const QTextFrame *KoTextEditor::currentFrame () const { return d->caret.currentFrame(); } const QTextList *KoTextEditor::currentList () const { return d->caret.currentList(); } const QTextTable *KoTextEditor::currentTable () const { return d->caret.currentTable(); } //have to include this because of Q_PRIVATE_SLOT #include "moc_KoTextEditor.cpp" diff --git a/libs/kotext/KoTextEditor.h b/libs/kotext/KoTextEditor.h index e802da8c09..5af40d2dc9 100644 --- a/libs/kotext/KoTextEditor.h +++ b/libs/kotext/KoTextEditor.h @@ -1,518 +1,561 @@ /* This file is part of the KDE project * Copyright (C) 2009 Pierre Stirnweiss * Copyright (C) 2009 Thomas Zander * Copyright (C) 2011 Boudewijn Rempt * Copyright (C) 2011-2012 C. Boemann * Copyright (C) 2014 Denis Kuplyakov * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef KOTEXTEDITOR_H #define KOTEXTEDITOR_H #include "kotext_export.h" #include #include #include #include #include #include #include class KoListLevelProperties; class KoCharacterStyle; class KoInlineObject; class KoParagraphStyle; class KoInlineNote; class KoInlineCite; class KoBibliographyInfo; class KoCanvasBase; class KoTableOfContentsGeneratorInfo; class KoShapeAnchor; class KoShape; class KoBookmark; class KoAnnotation; class KoTextRangeManager; class KoTextVisitor; class KUndo2Command; class QTextBlock; class QTextCharFormat; class QTextBlockFormat; class QTextDocument; class QTextDocumentFragment; class QString; class QMimeData; /** * KoTextEditor is a wrapper around QTextCursor. It handles undo/redo and change * tracking for all editing commands. */ class KOTEXT_EXPORT KoTextEditor: public QObject { Q_OBJECT public: enum ChangeListFlag { NoFlags = 0, ModifyExistingList = 1, MergeWithAdjacentList = 2, MergeExactly = 4, CreateNumberedParagraph = 8, AutoListStyle = 16, DontUnsetIfSame = 32 /// do not unset the current list style if it is already been set the same }; Q_DECLARE_FLAGS(ChangeListFlags, ChangeListFlag) explicit KoTextEditor(QTextDocument *document); virtual ~KoTextEditor(); /** * Retrieves the texteditor for the document of the first text shape in the current * set of selected shapes on the given canvas. * * @param canvas the canvas we will check for a suitable selected shape. * @returns a texteditor, or 0 if there is no shape active that has a QTextDocument as * userdata */ static KoTextEditor *getTextEditorFromCanvas(KoCanvasBase *canvas); public: // KoToolSelection overloads /// returns true if the wrapped QTextCursor has a selection. bool hasSelection() const; /** returns true if the current cursor position is protected from editing * @param cached use cached value if available. */ bool isEditProtected(bool useCached = false) const; public: bool operator!=(const QTextCursor &other) const; bool operator<(const QTextCursor &other) const; bool operator<=(const QTextCursor &other) const; bool operator==(const QTextCursor &other) const; bool operator>(const QTextCursor &other) const; bool operator>=(const QTextCursor &other) const; const QTextCursor constCursor() const; private: // for the call to KoTextLoader::loadBody, which has a QTextCursor friend class KoTextPaste; // from KoTextEditor_p.h friend class CharFormatVisitor; // our commands can have access to us friend class DeleteTableRowCommand; friend class DeleteTableColumnCommand; friend class InsertTableRowCommand; friend class InsertTableColumnCommand; friend class ChangeTrackedDeleteCommand; friend class DeleteCommand; friend class InsertInlineObjectCommand; friend class InsertNoteCommand; friend class ParagraphFormattingCommand; friend class RenameSectionCommand; friend class NewSectionCommand; + friend class SplitSectionsCommand; // for unittests friend class TestKoInlineTextObjectManager; // temporary... friend class TextShape; friend class TextTool; /** * This should be used only as read-only cursor or within a KUndo2Command sub-class which * will be added to the textEditor with addCommand. For examples of proper implementation of * such undoCommands, see the TextShape commands. */ QTextCursor* cursor(); public Q_SLOTS: /// This adds the \ref command to the calligra undo stack. /// /// From this point forward all text manipulation is placed in the qt text systems internal /// undostack while also adding representative subcommands to \ref command. /// /// The \ref command is not redone as part of this process. /// /// Note: Be aware that many KoTextEditor methods start their own commands thus terminating /// the recording of this \ref command. Only use QTextCursor manipulation (with all the issues /// that brings) or only use KoTextEditor methods that don't start their own command. /// /// The recording is automatically terminated when another command is added, which as mentioned /// can happen by executing some of the KoTextEditor methods. void addCommand(KUndo2Command *command); /// This instantly "redo" the command thus placing all the text manipulation the "redo" does /// (should be implemented with a "first redo" pattern) in the qt text systems internal /// undostack while also adding representative subcommands to \ref command. /// /// When \ref command is done "redoing" no further text manipulation is added as subcommands. /// /// \ref command is not put on the calligra undo stack. That is the responsibility of the /// caller, or the caller can choose to quickly undo and then delete the \ref command. void instantlyExecuteCommand(KUndo2Command *command); void registerTrackedChange(QTextCursor &selection, KoGenChange::Type changeType, const KUndo2MagicString &title, QTextFormat &format, QTextFormat &prevFormat, bool applyToWholeBlock = false); void bold(bool bold); void italic(bool italic); void underline(bool underline); void strikeOut(bool strikeOut); void setHorizontalTextAlignment(Qt::Alignment align); void setVerticalTextAlignment(Qt::Alignment align); void increaseIndent(); void decreaseIndent(); void increaseFontSize(); void decreaseFontSize(); void setFontFamily(const QString &font); void setFontSize(qreal size); void setTextColor(const QColor &color); void setTextBackgroundColor(const QColor &color); void setStyle(KoParagraphStyle *style); void setStyle(KoCharacterStyle *style); void mergeAutoStyle(const QTextCharFormat &deltaCharFormat); void applyDirectFormatting(const QTextCharFormat &deltaCharFormat, const QTextBlockFormat &deltaBlockFormat, const KoListLevelProperties &llp); /** * Insert an inlineObject (such as a variable) at the current cursor position. Possibly replacing the selection. * @param inliner the object to insert. * @param cmd a parent command for the commands created by this methods. If present, the commands * will not be added to the document's undo stack automatically. */ void insertInlineObject(KoInlineObject *inliner, KUndo2Command *parent = 0); /** * update the position of all inline objects from the given start point to the given end point. * @param start start position for updating. If 0, we update from the start of the document * @param end end position for updating. If -1, we update to the end of the document */ void updateInlineObjectPosition(int start = 0, int end = -1); /** * Remove the KoShapeAnchor objects from the document. * * NOTE: Call this method only when the shapes belonging to the anchors have been deleted. */ void removeAnchors(const QList &anchors, KUndo2Command *parent); /** * Remove the KoAnnotation objects from the document. * * NOTE: Call this method only when the shapes belonging to the annotations have been deleted. * This is not the way to delete annotations directly - instead delete the shape or * delete the text containing the annotation */ void removeAnnotations(const QList &annotations, KUndo2Command *parent); /** * At the current cursor position, insert a marker that marks the next word as being part of the index. * @returns returns the index marker when successful, or 0 if failed. Failure can be because there is no word * at the cursor position or there already is an index marker available. */ KoInlineObject *insertIndexMarker(); /// add a bookmark on current cursor location or current selection KoBookmark *addBookmark(const QString &name); /// Add an annotation at the current cursor location or the current selection. KoAnnotation *addAnnotation(KoShape *annotationShape); KoTextRangeManager *textRangeManager() const; /** * Insert a frame break at the cursor position, moving the rest of the text to the next frame. */ void insertFrameBreak(); /** * paste the given mimedata object at the current position * @param canvas the canvas we used when placing the shape. * @param mimeData: the mimedata containing text, html or odf * @param pasteAsText: if true, paste without formatting */ void paste(KoCanvasBase *canvas, const QMimeData *mimeData, bool pasteAsText=false); /** * @param numberingEnabled when true, we will enable numbering for the current paragraph (block). */ void toggleListNumbering(bool numberingEnabled); /** * change the current block's list properties */ void setListProperties(const KoListLevelProperties &llp, ChangeListFlags flags = ChangeListFlags(ModifyExistingList | MergeWithAdjacentList), KUndo2Command *parent = 0); // ------------------------------------------------------------- // Wrapped QTextCursor methods // ------------------------------------------------------------- int anchor() const; bool atBlockEnd() const; bool atBlockStart() const; bool atEnd() const; bool atStart() const; QTextBlock block() const; QTextCharFormat blockCharFormat() const; QTextBlockFormat blockFormat() const; int blockNumber() const; QTextCharFormat charFormat() const; void clearSelection(); int columnNumber() const; void deleteChar(); void deletePreviousChar(); QTextDocument *document() const; /// Same as Qt, only to be used inside KUndo2Commands KUndo2Command *beginEditBlock(const KUndo2MagicString &title = KUndo2MagicString()); void endEditBlock(); /** * Delete one character in the specified direction or a selection. * Warning: From the outside this method should only be used with a parent command * and only if there is a selection * @param previous should be true if act like backspace */ void deleteChar(bool previous, KUndo2Command *parent = 0); bool hasComplexSelection() const; /** * Insert a table at the current cursor position. * @param rows the number of rows in the created table. * @param columns the number of columns in the created table. */ void insertTable(int rows, int columns); /** * Insert a table row above the current cursor position (if in a table). */ void insertTableRowAbove(); /** * Insert a table row below the current cursor position (if in a table). */ void insertTableRowBelow(); /** * Insert a table column to the left of the current cursor position (if in a table). */ void insertTableColumnLeft(); /** * Insert a table column to the right of the current cursor position (if in a table). */ void insertTableColumnRight(); /** * Delete a table column where the cursor is (if in a table). */ void deleteTableColumn(); /** * Delete a table row where the cursor is (if in a table). */ void deleteTableRow(); /** * Merge table cells (selected by the cursor). */ void mergeTableCells(); /** * Split table cells (selected by the cursor) that were previously merged. */ void splitTableCells(); /** * Sets the width of a table column. * @param table is the table to be adjusted. * @param column the column that is to be adjusted. */ void adjustTableColumnWidth(QTextTable *table, int column, qreal width, KUndo2Command *parentCommand = 0); /** * Sets the height of a table row. * @param table is the table to be adjusted. * @param row the row that is to be adjusted. */ void adjustTableRowHeight(QTextTable *table, int row, qreal height, KUndo2Command *parentCommand = 0); /** * Changes the width of a table by adjusting the margins. * @param table is the table to be adjusted. * @param dLeft delta value for the left margin. * @param dRight delta value for the right margin. */ void adjustTableWidth(QTextTable *table, qreal dLeft, qreal dRight); /** * Sets the border formatting of a side in a table cell. * @param table is the table to be adjusted. * @param column the column coordinate of the cell that is to be adjusted. * @param row the row coordinate of the cell that is to be adjusted. */ void setTableBorderData(QTextTable *table, int row, int column, KoBorder::BorderSide cellSide, const KoBorder::BorderData &data); /** * Insert a footnote at the current cursor position * @return a pointer to the inserted footnote */ KoInlineNote *insertFootNote(); /** * Insert an endnote at the current cursor position * @return a pointer to the inserted endnote */ KoInlineNote *insertEndNote(); /** * Insert a table of Contents at the current cursor position. */ void insertTableOfContents(KoTableOfContentsGeneratorInfo *info); /** * Configures various values of a ToC to the one passed in info */ void setTableOfContentsConfig(KoTableOfContentsGeneratorInfo *info, const QTextBlock &block); void insertBibliography(KoBibliographyInfo *info); KoInlineCite *insertCitation(); /** * Inserts the supplied text at the current cursor position. If the second argument is * supplied, a link is inserted at the current cursor position with the hRef as given * by the user. To test whether the supplied link destination is a web url or a bookmark, * a regular expression ( \\S+://\\S+ ) is used. * @param text is the text to be inserted * @param hRef if supplied is the Hypertext reference */ void insertText(const QString &text, const QString &hRef = QString()); void insertHtml(const QString &html); void mergeBlockFormat( const QTextBlockFormat &modifier); bool movePosition(QTextCursor::MoveOperation operation, QTextCursor::MoveMode mode = QTextCursor::MoveAnchor, int n = 1); + /** + * Inserts a new paragraph and warps it to new section + * Source: + * some|textP + * Result: + * someP + * [|textP] + * + * [] -- section bounds + * | -- cursor зщышешщт + * P -- paragraph sign + */ void newSection(); + + /** + * Splits sections startings and inserts paragraph between them. + * Source: {sectionIdToInsertBefore == 1} + * [[[sometext... + * ^ + * 012 + * Result: + * [P + * [[sometext... + * + * [] -- section bounds + * P -- paragraph sign + */ + void splitSectionsStartings(int sectionIdToInsertBefore); + + /** + * Splits section endings and insert paragraph between them. + * Source: {sectionIdToInsertAfter == 1} + * sometext]]] + * ^ + * 012 + * Result: + * sometext]]P + * P] + * + * [] -- section bounds + * P -- paragraph sign + */ + void splitSectionsEndings(int sectionIdToInsertAfter); void renameSection(KoSection *section, const QString &newName); void newLine(); bool isWithinSelection(int position) const; int position() const; void select(QTextCursor::SelectionType selection); QString selectedText() const; QTextDocumentFragment selection() const; int selectionEnd() const; int selectionStart() const; void setBlockFormat(const QTextBlockFormat &format); void setCharFormat(const QTextCharFormat &format); void setPosition(int pos, QTextCursor::MoveMode mode = QTextCursor::MoveAnchor); void setVisualNavigation(bool on); bool visualNavigation() const; const QTextFrame *currentFrame () const; const QTextList *currentList () const; const QTextTable *currentTable () const; Q_SIGNALS: void cursorPositionChanged(); void textFormatChanged(); void characterStyleApplied(KoCharacterStyle *style); void paragraphStyleApplied(KoParagraphStyle *style); protected: void recursivelyVisitSelection(QTextFrame::iterator it, KoTextVisitor &visitor) const; private: Q_PRIVATE_SLOT(d, void documentCommandAdded()) class Private; friend class Private; Private* const d; }; Q_DECLARE_METATYPE(KoTextEditor*) Q_DECLARE_METATYPE(bool *) Q_DECLARE_OPERATORS_FOR_FLAGS(KoTextEditor::ChangeListFlags) #endif // KOTEXTEDITOR_H diff --git a/libs/kotext/KoTextPaste.cpp b/libs/kotext/KoTextPaste.cpp index 18b1daf2d8..f0da086dc0 100644 --- a/libs/kotext/KoTextPaste.cpp +++ b/libs/kotext/KoTextPaste.cpp @@ -1,129 +1,129 @@ /* This file is part of the KDE project * Copyright (C) 2008 Thorsten Zachmann * Copyright (C) 2011 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 "KoTextPaste.h" #include #include #include #include #include #include #include #include #include #include -#include +#include #include #ifdef SHOULD_BUILD_RDF #include "KoTextRdfCore.h" -#include "KoSectionManager.h" +#include "KoSectionModel.h" #include #endif class Q_DECL_HIDDEN KoTextPaste::Private { public: Private(KoTextEditor *editor, KoShapeController *shapeCont, QSharedPointer _rdfModel, KoCanvasBase *c, KUndo2Command *cmd ) : editor(editor) , resourceManager(shapeCont->resourceManager()) , rdfModel(_rdfModel) , shapeController(shapeCont) , command(cmd) , canvas(c) { } KoTextEditor *editor; KoDocumentResourceManager *resourceManager; QSharedPointer rdfModel; KoShapeController *shapeController; KUndo2Command *command; KoCanvasBase *canvas; }; KoTextPaste::KoTextPaste(KoTextEditor *editor, KoShapeController *shapeController, QSharedPointer rdfModel, KoCanvasBase *c, KUndo2Command *cmd) : d(new Private(editor, shapeController, rdfModel, c, cmd)) { } KoTextPaste::~KoTextPaste() { delete d; } bool KoTextPaste::process(const KoXmlElement &body, KoOdfReadStore &odfStore) { bool ok = true; KoOdfLoadingContext loadingContext(odfStore.styles(), odfStore.store()); KoShapeLoadingContext context(loadingContext, d->resourceManager); - context.setSectionManager(KoTextDocument(d->editor->document()).sectionManager()); + context.setSectionModel(KoTextDocument(d->editor->document()).sectionModel()); KoTextLoader loader(context); kDebug(30015) << "text paste"; // load the paste directly into the editor's cursor -- which breaks encapsulation loader.loadBody(body, *d->editor->cursor(), KoTextLoader::PasteMode); // now let's load the body from the ODF KoXmlElement. - context.sectionManager()->invalidate(); +// context.sectionModel()->invalidate(); FIXME!! #ifdef SHOULD_BUILD_RDF kDebug(30015) << "text paste, rdf handling" << d->rdfModel; // RDF: Grab RDF metadata from ODF file if present & load it into rdfModel if (d->rdfModel) { QSharedPointer tmpmodel(Soprano::createModel()); ok = KoTextRdfCore::loadManifest(odfStore.store(), tmpmodel); kDebug(30015) << "ok:" << ok << " tmpmodel.sz:" << tmpmodel->statementCount(); kDebug(30015) << "existing rdf model.sz:" << d->rdfModel->statementCount(); #ifndef NDEBUG KoTextRdfCore::dumpModel("RDF from C+P", tmpmodel); #endif d->rdfModel->addStatements(tmpmodel->listStatements().allElements()); kDebug(30015) << "done... existing rdf model.sz:" << d->rdfModel->statementCount(); #ifndef NDEBUG KoTextRdfCore::dumpModel("Imported RDF after C+P", d->rdfModel); #endif } #endif KoTextSharedLoadingData *sharedData = static_cast(context.sharedData(KOTEXT_SHARED_LOADING_ID)); // add shapes to the document foreach (KoShape *shape, sharedData->insertedShapes()) { QPointF move; d->canvas->clipToDocument(shape, move); if (move.x() != 0 || move.y() != 0) { shape->setPosition(shape->position() + move); } // During load we make page anchored shapes invisible, because otherwise // they leave empty rects in the text if there is run-around // now is the time to make them visible again shape->setVisible(true); d->editor->addCommand(d->shapeController->addShapeDirect(shape, d->command)); } return ok; } diff --git a/libs/kotext/commands/DeleteCommand.cpp b/libs/kotext/commands/DeleteCommand.cpp index a9683d643f..c3e9b7025a 100644 --- a/libs/kotext/commands/DeleteCommand.cpp +++ b/libs/kotext/commands/DeleteCommand.cpp @@ -1,512 +1,614 @@ /* This file is part of the KDE project * Copyright (C) 2009 Ganesh Paramasivam * Copyright (C) 2009 Pierre Stirnweiss * Copyright (C) 2010 Thomas Zander * Copyright (C) 2012 C. Boemann - * Copyright (C) 2014 Denis Kuplyakov + * Copyright (C) 2014-2015 Denis Kuplyakov * * 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 "DeleteCommand.h" #include #include #include #include #include #include #include #include #include #include #include #include -#include +#include #include #include #include +bool DeleteCommand::SectionDeleteInfo::operator<(const DeleteCommand::SectionDeleteInfo &other) const +{ + // At first we remove sections that lays deeper in tree + // On one level we delete sections by descending order of their childIdx + // That is needed on undo, cuz we want it to be simply done by inserting + // sections back in reverse order of their deletion. + // Without childIdx compare it is possible that we will want to insert + // section on position 2 while the number of children is less than 2. + + if (section->level() != other.section->level()) { + return section->level() > other.section->level(); + } + return childIdx > other.childIdx; +} + DeleteCommand::DeleteCommand(DeleteMode mode, QTextDocument *document, KoShapeController *shapeController, KUndo2Command *parent) : KoTextCommandBase (parent) , m_document(document) , m_shapeController(shapeController) , m_first(true) - , m_undone(false) , m_mode(mode) + , m_mergePossible(true) { setText(kundo2_i18n("Delete")); } void DeleteCommand::undo() { KoTextCommandBase::undo(); - UndoRedoFinalizer finalizer(this); + UndoRedoFinalizer finalizer(this); // Look at KoTextCommandBase documentation + + // KoList updateListChanges(); - m_undone = true; - KoTextDocument(m_document).sectionManager()->invalidate(); + + // KoTextRange KoTextRangeManager *rangeManager = KoTextDocument(m_document).textRangeManager(); foreach (KoTextRange *range, m_rangesToRemove) { rangeManager->insert(range); } + + // KoInlineObject foreach (KoInlineObject *object, m_invalidInlineObjects) { object->manager()->addInlineObject(object); } + + // KoSectionModel + insertSectionsToModel(); } void DeleteCommand::redo() { - m_undone = false; if (!m_first) { KoTextCommandBase::redo(); - UndoRedoFinalizer finalizer(this); + UndoRedoFinalizer finalizer(this); // Look at KoTextCommandBase documentation + + // KoTextRange KoTextRangeManager *rangeManager = KoTextDocument(m_document).textRangeManager(); foreach (KoTextRange *range, m_rangesToRemove) { rangeManager->remove(range); } + + // KoSectionModel + deleteSectionsFromModel(); + + // TODO: there is nothing for InlineObjects and Lists. Is it OK? } else { m_first = false; if (m_document) { KoTextEditor *textEditor = KoTextDocument(m_document).textEditor(); if (textEditor) { textEditor->beginEditBlock(); doDelete(); textEditor->endEditBlock(); } } } } +// Section handling algorithm: +// At first, we go though the all section starts and ends +// that are in selection, and delete all pairs, because +// they will be deleted. +// Then we have multiple cases: selection start split some block +// or don't split any block. +// In the first case all formatting info will be stored in the +// split block(it has startBlockNum number). +// In the second case it will be stored in the block pointed by the +// selection end(it has endBlockNum number). +// Also there is a trivial case, when whole selection is inside +// one block, in this case hasEntirelyInsideBlock will be false +// and we will do nothing. + class DeleteVisitor : public KoTextVisitor { public: DeleteVisitor(KoTextEditor *editor, DeleteCommand *command) : KoTextVisitor(editor) , m_first(true) - , m_mergePossible(true) , m_command(command) , m_startBlockNum(-1) , m_endBlockNum(-1) , m_hasEntirelyInsideBlock(false) { } - // Section handling algorithm: - // At first, we go though the all section starts and ends - // that are in selection, and delete all pairs, because - // they will be deleted. - // Then we have multiple cases: selection start split some block - // or don't split any block. - // In the first case all formatting info will be stored in the - // split block(it has startBlockNum number). - // In the second case it will be stored in the block pointed by the - // selection end(it has endBlockNum number). - // Also there is a trivial case, when whole selection is inside - // one block, in this case hasEntirelyInsideBlock will be false - // and we will do nothing. - virtual void visitBlock(QTextBlock &block, const QTextCursor &caret) { for (QTextBlock::iterator it = block.begin(); it != block.end(); ++it) { QTextCursor fragmentSelection(caret); fragmentSelection.setPosition(qMax(caret.selectionStart(), it.fragment().position())); - fragmentSelection.setPosition(qMin(caret.selectionEnd(), it.fragment().position() + it.fragment().length()), QTextCursor::KeepAnchor); + fragmentSelection.setPosition( + qMin(caret.selectionEnd(), it.fragment().position() + it.fragment().length()), + QTextCursor::KeepAnchor + ); if (fragmentSelection.anchor() >= fragmentSelection.position()) { continue; } visitFragmentSelection(fragmentSelection); } + // Section handling below bool doesBeginInside = false; bool doesEndInside = false; if (block.position() >= caret.selectionStart()) { // Begin of the block is inside selection. doesBeginInside = true; QList openList = KoSectionUtils::sectionStartings(block.blockFormat()); foreach (KoSection *sec, openList) { m_curSectionDelimiters.push_back(SectionHandle(sec->name(), sec)); } } if (block.position() + block.length() <= caret.selectionEnd()) { // End of the block is inside selection. doesEndInside = true; QList closeList = KoSectionUtils::sectionEndings(block.blockFormat()); foreach (KoSectionEnd *se, closeList) { if (!m_curSectionDelimiters.empty() && m_curSectionDelimiters.last().name == se->name()) { - m_curSectionDelimiters.pop_back(); + KoSection *section = se->correspondingSection(); + int childIdx = KoTextDocument(m_command->m_document).sectionModel() + ->findRowOfChild(section); + + m_command->m_sectionsToRemove.push_back( + DeleteCommand::SectionDeleteInfo( + section, + childIdx + ) + ); + m_curSectionDelimiters.pop_back(); // This section will die } else { m_curSectionDelimiters.push_back(SectionHandle(se->name(), se)); } } } if (!doesBeginInside && doesEndInside) { m_startBlockNum = block.blockNumber(); } else if (doesBeginInside && !doesEndInside) { m_endBlockNum = block.blockNumber(); } else if (doesBeginInside && doesEndInside) { m_hasEntirelyInsideBlock = true; } } virtual void visitFragmentSelection(QTextCursor &fragmentSelection) { if (m_first) { m_firstFormat = fragmentSelection.charFormat(); m_first = false; } - if (m_mergePossible && fragmentSelection.charFormat() != m_firstFormat) { - m_mergePossible = false; + if (m_command->m_mergePossible && fragmentSelection.charFormat() != m_firstFormat) { + m_command->m_mergePossible = false; } + // Handling InlineObjects below KoTextDocument textDocument(fragmentSelection.document()); KoInlineTextObjectManager *manager = textDocument.inlineTextObjectManager(); QString selected = fragmentSelection.selectedText(); fragmentSelection.setPosition(fragmentSelection.selectionStart() + 1); int position = fragmentSelection.position(); const QChar *data = selected.constData(); for (int i = 0; i < selected.length(); i++) { if (data->unicode() == QChar::ObjectReplacementCharacter) { fragmentSelection.setPosition(position + i); KoInlineObject *object = manager->inlineTextObject(fragmentSelection); m_command->m_invalidInlineObjects.insert(object); } data++; } } - void finalize(QTextCursor *cur) - { - KoTextDocument(cur->document()).sectionManager()->invalidate(); - // It means that selection isn't within one block. - if (m_hasEntirelyInsideBlock || m_startBlockNum != -1 || m_endBlockNum != -1) { - QList openList; - QList closeList; - foreach (const SectionHandle &handle, m_curSectionDelimiters) { - if (handle.type == SectionOpen) { // Start of the section. - openList << handle.dataSec; - } else { // End of the section. - closeList << handle.dataSecEnd; - } - } - - // We're expanding ends in affected blocks to the end of the start block, - // delete all sections, that are entirely in affected blocks, - // and move ends, we have, to the begin of the next after the end block. - if (m_startBlockNum != -1) { - QTextBlockFormat fmt = cur->document()->findBlockByNumber(m_startBlockNum).blockFormat(); - QTextBlockFormat fmt2 = cur->document()->findBlockByNumber(m_endBlockNum + 1).blockFormat(); - fmt.clearProperty(KoParagraphStyle::SectionEndings); - - //m_endBlockNum != -1 in this case. - QList closeListEndBlock = KoSectionUtils::sectionEndings( - cur->document()->findBlockByNumber(m_endBlockNum).blockFormat()); - - while (!openList.empty() && !closeListEndBlock.empty() - && openList.last()->name() == closeListEndBlock.first()->name()) { - openList.pop_back(); - closeListEndBlock.pop_front(); - } - openList << KoSectionUtils::sectionStartings(fmt2); - closeList << closeListEndBlock; - - // We leave open section of start block untouched. - KoSectionUtils::setSectionStartings(fmt2, openList); - KoSectionUtils::setSectionEndings(fmt, closeList); - - QTextCursor changer = *cur; - changer.setPosition(cur->document()->findBlockByNumber(m_startBlockNum).position()); - changer.setBlockFormat(fmt); - if (m_endBlockNum + 1 < cur->document()->blockCount()) { - changer.setPosition(cur->document()->findBlockByNumber(m_endBlockNum + 1).position()); - changer.setBlockFormat(fmt2); - } - } else { // m_endBlockNum != -1 in this case. We're pushing all new section info to the end block. - QTextBlockFormat fmt = cur->document()->findBlockByNumber(m_endBlockNum).blockFormat(); - QList allStartings = KoSectionUtils::sectionStartings(fmt); - fmt.clearProperty(KoParagraphStyle::SectionStartings); - - QList pairedEndings; - QList unpairedEndings; - - foreach (KoSectionEnd *se, KoSectionUtils::sectionEndings(fmt)) { - KoSection *sec = se->correspondingSection(); - - if (allStartings.contains(sec)) { - pairedEndings << se; - } else { - unpairedEndings << se; - } - } - - closeList = pairedEndings + closeList + unpairedEndings; - - KoSectionUtils::setSectionStartings(fmt, openList); - KoSectionUtils::setSectionEndings(fmt, closeList); - - QTextCursor changer = *cur; - changer.setPosition(cur->document()->findBlockByNumber(m_endBlockNum).position()); - changer.setBlockFormat(fmt); - } - } - } - enum SectionHandleAction { - SectionClose, // Denotes close of the section. - SectionOpen // Denotes start or beginning of the section. + SectionClose, ///< Denotes close of the section. + SectionOpen ///< Denotes start or beginning of the section. }; - //Helper struct for handling sections. + /// Helper struct for handling sections. struct SectionHandle { - QString name; // Name of the section. - SectionHandleAction type; // Action of a SectionHandle. + QString name; ///< Name of the section. + SectionHandleAction type; ///< Action of a SectionHandle. - KoSection *dataSec; // Pointer to KoSection. - KoSectionEnd *dataSecEnd; // Pointer to KoSectionEnd. + KoSection *dataSec; ///< Pointer to KoSection. + KoSectionEnd *dataSecEnd; ///< Pointer to KoSectionEnd. SectionHandle(QString _name, KoSection *_data) : name(_name) , type(SectionOpen) , dataSec(_data) , dataSecEnd(0) { } SectionHandle(QString _name, KoSectionEnd *_data) : name(_name) , type(SectionClose) , dataSec(0) , dataSecEnd(_data) { } }; bool m_first; - bool m_mergePossible; DeleteCommand *m_command; QTextCharFormat m_firstFormat; int m_startBlockNum; int m_endBlockNum; bool m_hasEntirelyInsideBlock; QList m_curSectionDelimiters; }; +void DeleteCommand::finalizeSectionHandling(QTextCursor *cur, DeleteVisitor &v) +{ + // Lets handle pointers from block formats first + // It means that selection isn't within one block. + if (v.m_hasEntirelyInsideBlock || v.m_startBlockNum != -1 || v.m_endBlockNum != -1) { + QList openList; + QList closeList; + foreach (const DeleteVisitor::SectionHandle &handle, v.m_curSectionDelimiters) { + if (handle.type == v.SectionOpen) { // Start of the section. + openList << handle.dataSec; + } else { // End of the section. + closeList << handle.dataSecEnd; + } + } + + // We're expanding ends in affected blocks to the end of the start block, + // delete all sections, that are entirely in affected blocks, + // and move ends, we have, to the begin of the next after the end block. + if (v.m_startBlockNum != -1) { + QTextBlockFormat fmt = cur->document()->findBlockByNumber(v.m_startBlockNum).blockFormat(); + QTextBlockFormat fmt2 = cur->document()->findBlockByNumber(v.m_endBlockNum + 1).blockFormat(); + fmt.clearProperty(KoParagraphStyle::SectionEndings); + + // m_endBlockNum != -1 in this case. + QList closeListEndBlock = KoSectionUtils::sectionEndings( + cur->document()->findBlockByNumber(v.m_endBlockNum).blockFormat()); + + while (!openList.empty() && !closeListEndBlock.empty() + && openList.last()->name() == closeListEndBlock.first()->name()) { + + int childIdx = KoTextDocument(m_document) + .sectionModel()->findRowOfChild(openList.back()); + m_sectionsToRemove.push_back( + DeleteCommand::SectionDeleteInfo( + openList.back(), + childIdx + ) + ); + + openList.pop_back(); + closeListEndBlock.pop_front(); + } + openList << KoSectionUtils::sectionStartings(fmt2); + closeList << closeListEndBlock; + + // We leave open section of start block untouched. + KoSectionUtils::setSectionStartings(fmt2, openList); + KoSectionUtils::setSectionEndings(fmt, closeList); + + QTextCursor changer = *cur; + changer.setPosition(cur->document()->findBlockByNumber(v.m_startBlockNum).position()); + changer.setBlockFormat(fmt); + if (v.m_endBlockNum + 1 < cur->document()->blockCount()) { + changer.setPosition(cur->document()->findBlockByNumber(v.m_endBlockNum + 1).position()); + changer.setBlockFormat(fmt2); + } + } else { // v.m_startBlockNum == -1 + // v.m_endBlockNum != -1 in this case. + // We're pushing all new section info to the end block. + QTextBlockFormat fmt = cur->document()->findBlockByNumber(v.m_endBlockNum).blockFormat(); + QList allStartings = KoSectionUtils::sectionStartings(fmt); + fmt.clearProperty(KoParagraphStyle::SectionStartings); + + QList pairedEndings; + QList unpairedEndings; + + foreach (KoSectionEnd *se, KoSectionUtils::sectionEndings(fmt)) { + KoSection *sec = se->correspondingSection(); + + if (allStartings.contains(sec)) { + pairedEndings << se; + } else { + unpairedEndings << se; + } + } + + if (cur->selectionStart()) { + QTextCursor changer = *cur; + changer.setPosition(cur->selectionStart() - 1); + + QTextBlockFormat prevFmt = changer.blockFormat(); + QList prevEndings = KoSectionUtils::sectionEndings(prevFmt); + + prevEndings = prevEndings + closeList; + + KoSectionUtils::setSectionEndings(prevFmt, prevEndings); + changer.setBlockFormat(prevFmt); + } + + KoSectionUtils::setSectionStartings(fmt, openList); + KoSectionUtils::setSectionEndings(fmt, pairedEndings + unpairedEndings); + + QTextCursor changer = *cur; + changer.setPosition(cur->document()->findBlockByNumber(v.m_endBlockNum).position()); + changer.setBlockFormat(fmt); + } + } + + // Now lets deal with KoSectionModel + qSort(m_sectionsToRemove.begin(), m_sectionsToRemove.end()); + deleteSectionsFromModel(); +} + +void DeleteCommand::deleteSectionsFromModel() +{ + KoSectionModel *model = KoTextDocument(m_document).sectionModel(); + foreach (const SectionDeleteInfo &info, m_sectionsToRemove) { + model->deleteFromModel(info.section); + } +} + +void DeleteCommand::insertSectionsToModel() +{ + KoSectionModel *model = KoTextDocument(m_document).sectionModel(); + QList::iterator it = m_sectionsToRemove.end(); + while (it != m_sectionsToRemove.begin()) { + it--; + model->insertToModel(it->section, it->childIdx); + } +} + void DeleteCommand::doDelete() { KoTextEditor *textEditor = KoTextDocument(m_document).textEditor(); Q_ASSERT(textEditor); QTextCursor *caret = textEditor->cursor(); QTextCharFormat charFormat = caret->charFormat(); bool caretAtBeginOfBlock = (caret->position() == caret->block().position()); if (!textEditor->hasSelection()) { if (m_mode == PreviousChar) { caret->movePosition(QTextCursor::PreviousCharacter, QTextCursor::KeepAnchor); } else { caret->movePosition(QTextCursor::NextCharacter, QTextCursor::KeepAnchor); } } DeleteVisitor visitor(textEditor, this); textEditor->recursivelyVisitSelection(m_document.data()->rootFrame()->begin(), visitor); - visitor.finalize(caret); // Finalize section handling routine. - m_mergePossible = visitor.m_mergePossible; + // Sections Model + finalizeSectionHandling(caret, visitor); // Finalize section handling routine. + + // InlineObjects foreach (KoInlineObject *object, m_invalidInlineObjects) { deleteInlineObject(object); } + // Ranges KoTextRangeManager *rangeManager = KoTextDocument(m_document).textRangeManager(); - m_rangesToRemove = rangeManager->textRangesChangingWithin(textEditor->document(), textEditor->selectionStart(), textEditor->selectionEnd(), textEditor->selectionStart(), textEditor->selectionEnd()); + m_rangesToRemove = rangeManager->textRangesChangingWithin( + textEditor->document(), + textEditor->selectionStart(), + textEditor->selectionEnd(), + textEditor->selectionStart(), + textEditor->selectionEnd() + ); foreach (KoTextRange *range, m_rangesToRemove) { KoAnchorTextRange *anchorRange = dynamic_cast(range); KoAnnotation *annotation = dynamic_cast(range); if (anchorRange) { // we should only delete the anchor if the selection is covering it... not if the selection is // just adjecent to the anchor. This is more in line with what other wordprocessors do if (anchorRange->position() != textEditor->selectionStart() - && anchorRange->position() != textEditor->selectionEnd()) { + && anchorRange->position() != textEditor->selectionEnd()) { KoShape *shape = anchorRange->anchor()->shape(); if (m_shapeController) { KUndo2Command *shapeDeleteCommand = m_shapeController->removeShape(shape, this); shapeDeleteCommand->redo(); } // via m_shapeController->removeShape a DeleteAnchorsCommand should be created that // also calls rangeManager->remove(range), so we shouldn't do that here aswell } } else if (annotation) { KoShape *shape = annotation->annotationShape(); if (m_shapeController) { KUndo2Command *shapeDeleteCommand = m_shapeController->removeShape(shape, this); shapeDeleteCommand->redo(); } // via m_shapeController->removeShape a DeleteAnnotationsCommand should be created that // also calls rangeManager->remove(range), so we shouldn't do that here aswell } else { rangeManager->remove(range); } } + // Check: is merge possible? if (textEditor->hasComplexSelection()) { m_mergePossible = false; } + //FIXME: lets forbid merging of "section affecting" deletions by now + if (!m_sectionsToRemove.empty()) { + m_mergePossible = false; + } + if (m_mergePossible) { // Store various info needed for checkMerge m_format = textEditor->charFormat(); m_position = textEditor->selectionStart(); m_length = textEditor->selectionEnd() - textEditor->selectionStart(); } + // Actual deletion of text caret->deleteChar(); if (m_mode != PreviousChar || !caretAtBeginOfBlock) { caret->setCharFormat(charFormat); } } void DeleteCommand::deleteInlineObject(KoInlineObject *object) { if (object) { KoAnchorInlineObject *anchorObject = dynamic_cast(object); if (anchorObject) { KoShape *shape = anchorObject->anchor()->shape(); KUndo2Command *shapeDeleteCommand = m_shapeController->removeShape(shape, this); shapeDeleteCommand->redo(); } else { object->manager()->removeInlineObject(object); } } } int DeleteCommand::id() const { // Should be an enum declared somewhere. KoTextCommandBase.h ??? return 56789; } bool DeleteCommand::mergeWith(const KUndo2Command *command) { class UndoTextCommand : public KUndo2Command { public: UndoTextCommand(QTextDocument *document, KUndo2Command *parent = 0) : KUndo2Command(kundo2_i18n("Text"), parent), m_document(document) {} void undo() { QTextDocument *doc = m_document.data(); if (doc) doc->undo(KoTextDocument(doc).textEditor()->cursor()); } void redo() { QTextDocument *doc = m_document.data(); if (doc) doc->redo(KoTextDocument(doc).textEditor()->cursor()); } QWeakPointer m_document; }; KoTextEditor *textEditor = KoTextDocument(m_document).textEditor(); if (textEditor == 0) return false; if (command->id() != id()) return false; if (!checkMerge(command)) return false; DeleteCommand *other = const_cast(static_cast(command)); m_invalidInlineObjects += other->m_invalidInlineObjects; other->m_invalidInlineObjects.clear(); for (int i=0; i < command->childCount(); i++) new UndoTextCommand(const_cast(textEditor->document()), this); return true; } bool DeleteCommand::checkMerge(const KUndo2Command *command) { DeleteCommand *other = const_cast(static_cast(command)); if (!(m_mergePossible && other->m_mergePossible)) return false; if (m_position == other->m_position && m_format == other->m_format) { m_length += other->m_length; return true; } if ((other->m_position + other->m_length == m_position) && (m_format == other->m_format)) { m_position = other->m_position; m_length += other->m_length; return true; } return false; } void DeleteCommand::updateListChanges() { KoTextEditor *textEditor = KoTextDocument(m_document).textEditor(); if (textEditor == 0) return; QTextDocument *document = const_cast(textEditor->document()); QTextCursor tempCursor(document); QTextBlock startBlock = document->findBlock(m_position); QTextBlock endBlock = document->findBlock(m_position + m_length); if (endBlock != document->end()) endBlock = endBlock.next(); QTextList *currentList; for (QTextBlock currentBlock = startBlock; currentBlock != endBlock; currentBlock = currentBlock.next()) { tempCursor.setPosition(currentBlock.position()); currentList = tempCursor.currentList(); if (currentList) { KoListStyle::ListIdType listId; if (sizeof(KoListStyle::ListIdType) == sizeof(uint)) listId = currentList->format().property(KoListStyle::ListId).toUInt(); else listId = currentList->format().property(KoListStyle::ListId).toULongLong(); if (!KoTextDocument(document).list(currentBlock)) { KoList *list = KoTextDocument(document).list(listId); if (list) { list->updateStoredList(currentBlock); } } } } } DeleteCommand::~DeleteCommand() { } diff --git a/libs/kotext/commands/DeleteCommand.h b/libs/kotext/commands/DeleteCommand.h index 9b67fe1e2f..90ce10cf0d 100644 --- a/libs/kotext/commands/DeleteCommand.h +++ b/libs/kotext/commands/DeleteCommand.h @@ -1,79 +1,98 @@ /* This file is part of the KDE project * Copyright (C) 2009 Ganesh Paramasivam * Copyright (C) 2012 C. Boemann - * Copyright (C) 2014 Denis Kuplyakov + * Copyright (C) 2014-2015 Denis Kuplyakov * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA.*/ #ifndef DELETECOMMAND_H #define DELETECOMMAND_H #include "KoTextCommandBase.h" #include #include #include #include class QTextDocument; + class KoShapeController; class KoInlineObject; +class KoTextRange; +class KoSection; class DeleteVisitor; -class KoTextRange; class DeleteCommand : public KoTextCommandBase { public: enum DeleteMode { PreviousChar, NextChar }; DeleteCommand(DeleteMode mode, QTextDocument *document, KoShapeController *shapeController, KUndo2Command* parent = 0); virtual ~DeleteCommand(); virtual void undo(); virtual void redo(); virtual int id() const; virtual bool mergeWith(const KUndo2Command *command); private: friend class DeleteVisitor; + struct SectionDeleteInfo { + SectionDeleteInfo(KoSection *_section, int _childIdx) + : section(_section) + , childIdx(_childIdx) + { + } + + bool operator<(const SectionDeleteInfo &other) const; + + KoSection *section; ///< Section to remove + int childIdx; ///< Position of section in parent's children() list + }; + QWeakPointer m_document; KoShapeController *m_shapeController; QSet m_invalidInlineObjects; QList m_cursorsToWholeDeleteBlocks; QHash m_rangesToRemove; + QList m_sectionsToRemove; + bool m_first; - bool m_undone; DeleteMode m_mode; int m_position; int m_length; QTextCharFormat m_format; bool m_mergePossible; void doDelete(); void deleteInlineObject(KoInlineObject *object); bool checkMerge(const KUndo2Command *command); void updateListChanges(); + void finalizeSectionHandling(QTextCursor *caret, DeleteVisitor &visitor); + void deleteSectionsFromModel(); + void insertSectionsToModel(); }; #endif // DELETECOMMAND_H diff --git a/libs/kotext/commands/NewSectionCommand.cpp b/libs/kotext/commands/NewSectionCommand.cpp index cadaa9541f..412d102d65 100644 --- a/libs/kotext/commands/NewSectionCommand.cpp +++ b/libs/kotext/commands/NewSectionCommand.cpp @@ -1,78 +1,92 @@ /* * This file is part of the KDE project - * Copyright (C) 2014 Denis Kuplyakov + * Copyright (C) 2014-2015 Denis Kuplyakov * * 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 "NewSectionCommand.h" #include #include #include #include -#include #include #include +#include #include #include NewSectionCommand::NewSectionCommand(QTextDocument *document) : KUndo2Command () , m_first(true) , m_document(document) { setText(kundo2_i18n("New Section")); } NewSectionCommand::~NewSectionCommand() { } void NewSectionCommand::undo() { KUndo2Command::undo(); - KoTextDocument(m_document).sectionManager()->invalidate(); + //FIXME: if it will go to KoTextCommandBase, place UndoRedoFinalizer here + + // All formatting changes will be undone automatically. + // Lets handle Model Level (see KoSectionModel). + KoTextDocument(m_document).sectionModel()->deleteFromModel(m_section); } void NewSectionCommand::redo() { - KoTextDocument(m_document).sectionManager()->invalidate(); + KoTextDocument koDocument(m_document); + KoSectionModel *sectionModel = koDocument.sectionModel(); if (!m_first) { KUndo2Command::redo(); + //FIXME: if it will go to KoTextCommandBase, place UndoRedoFinalizer here + + // All formatting changes will be redone automatically. + // Lets handle Model Level (see KoSectionModel). + sectionModel->insertToModel(m_section, m_childIdx); } else { m_first = false; - KoTextEditor *editor = KoTextDocument(m_document).textEditor(); - + KoTextEditor *editor = koDocument.textEditor(); editor->newLine(); - KoSection *start = new KoSection(editor->constCursor()); - KoSectionEnd *end = new KoSectionEnd(start); + m_section = sectionModel->createSection( + editor->constCursor(), + sectionModel->sectionAtPosition(editor->constCursor().position()) + ); + m_childIdx = sectionModel->findRowOfChild(m_section); + + KoSectionEnd *sectionEnd = sectionModel->createSectionEnd(m_section); QTextBlockFormat fmt = editor->blockFormat(); QList sectionStartings = KoSectionUtils::sectionStartings(fmt); QList sectionEndings = KoSectionUtils::sectionEndings(fmt); - sectionStartings.append(start); - sectionEndings.prepend(end); + sectionStartings.append(m_section); + sectionEndings.prepend(sectionEnd); KoSectionUtils::setSectionStartings(fmt, sectionStartings); KoSectionUtils::setSectionEndings(fmt, sectionEndings); editor->setBlockFormat(fmt); } } diff --git a/libs/kotext/commands/NewSectionCommand.h b/libs/kotext/commands/NewSectionCommand.h index a0b02b8670..6ef5986e60 100644 --- a/libs/kotext/commands/NewSectionCommand.h +++ b/libs/kotext/commands/NewSectionCommand.h @@ -1,43 +1,48 @@ /* * This file is part of the KDE project * Copyright (C) 2014 Denis Kuplaykov * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA.*/ #ifndef NEWSECTIONCOMMAND_H #define NEWSECTIONCOMMAND_H -#include -#include - #include +class KoSection; +class QTextDocument; + +//FIXME: why it is not going from KoTextCommandBase? +// If it will be changed to KoTextCommandBase, +// don't forget to add UndoRedoFinalizer. class NewSectionCommand : public KUndo2Command { public: explicit NewSectionCommand(QTextDocument *document); virtual ~NewSectionCommand(); virtual void undo(); virtual void redo(); private: - bool m_first; // checks first call of redo - QTextDocument *m_document; // section manager of the document + bool m_first; ///< Checks first call of redo + QTextDocument *m_document; ///< Pointer to document + KoSection *m_section; ///< Inserted section + int m_childIdx; ///< Position of inserted section in parent, after inserting }; #endif // NEWSECTIONCOMMAND_H diff --git a/libs/kotext/commands/RenameSectionCommand.cpp b/libs/kotext/commands/RenameSectionCommand.cpp index 3067b85134..650f343c59 100644 --- a/libs/kotext/commands/RenameSectionCommand.cpp +++ b/libs/kotext/commands/RenameSectionCommand.cpp @@ -1,78 +1,76 @@ /* * This file is part of the KDE project - * Copyright (C) 2014 Denis Kuplyakov + * Copyright (C) 2014-2015 Denis Kuplyakov * * 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 "RenameSectionCommand.h" #include +#include +#include #include #include -RenameSectionCommand::RenameSectionCommand(KoSection *section, const QString &newName) +RenameSectionCommand::RenameSectionCommand(KoSection *section, const QString &newName, QTextDocument *document) : KUndo2Command() + , m_sectionModel(KoTextDocument(document).sectionModel()) , m_section(section) , m_newName(newName) , m_first(true) { setText(kundo2_i18n("Rename Section")); } RenameSectionCommand::~RenameSectionCommand() { } void RenameSectionCommand::undo() { KUndo2Command::undo(); - m_section->setName(m_oldName); + m_sectionModel->setName(m_section, m_oldName); } void RenameSectionCommand::redo() { if (!m_first) { KUndo2Command::redo(); } m_oldName = m_section->name(); - m_section->setName(m_newName); + m_sectionModel->setName(m_section, m_newName); m_first = false; } int RenameSectionCommand::id() const { + //FIXME: extract this to some enum shared accross all commands return 34537684; } bool RenameSectionCommand::mergeWith(const KUndo2Command *other) { if (other->id() != id()) { return false; } const RenameSectionCommand *command = static_cast(other); - - if (command->m_section != m_section) { + if (command->m_section != m_section || m_newName != command->m_oldName) { return false; } - - if (m_newName != command->m_oldName) { - return false; - } - m_newName = command->m_oldName; return true; } diff --git a/libs/kotext/commands/RenameSectionCommand.h b/libs/kotext/commands/RenameSectionCommand.h index 4efb0759ee..cb2af79184 100644 --- a/libs/kotext/commands/RenameSectionCommand.h +++ b/libs/kotext/commands/RenameSectionCommand.h @@ -1,49 +1,53 @@ /* * This file is part of the KDE project - * Copyright (C) 2014 Denis Kuplaykov + * Copyright (C) 2014-2015 Denis Kuplaykov * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA.*/ #ifndef RENAMESECTIONCOMMAND_H #define RENAMESECTIONCOMMAND_H -#include - #include #include +class QTextDocument; + +class KoSection; +class KoSectionModel; + class RenameSectionCommand : public KUndo2Command { public: - RenameSectionCommand(KoSection *section, const QString &newName); + RenameSectionCommand(KoSection *section, const QString &newName, QTextDocument *document); virtual ~RenameSectionCommand(); virtual void undo(); virtual void redo(); virtual bool mergeWith(const KUndo2Command *other); virtual int id() const; private: - KoSection *m_section; // section to rename - QString m_newName; // new section name - QString m_oldName; // old section name (needed to undo) - bool m_first; // checks first call of redo + KoSectionModel *m_sectionModel; ///< Pointer to document's KoSectionModel + KoSection *m_section; ///< Section to rename + QString m_newName; ///< New section name + QString m_oldName; ///< Old section name (needed to undo) + bool m_first; ///< Checks first call of redo }; #endif // RENAMESECTIONCOMMAND_H diff --git a/libs/kotext/commands/SplitSectionsCommand.cpp b/libs/kotext/commands/SplitSectionsCommand.cpp new file mode 100644 index 0000000000..021a19264e --- /dev/null +++ b/libs/kotext/commands/SplitSectionsCommand.cpp @@ -0,0 +1,105 @@ +/* + * This file is part of the KDE project + * Copyright (C) 2014-2015 Denis Kuplyakov + * + * 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 "SplitSectionsCommand.h" +#include +#include +#include +#include +#include +#include + +#include +#include + +SplitSectionsCommand::SplitSectionsCommand(QTextDocument *document, SplitType type, int splitPosition) + : KUndo2Command () + , m_first(true) + , m_document(document) + , m_type(type) + , m_splitPosition(splitPosition) +{ + if (m_type == Startings) { + setText(kundo2_i18n("Split sections startings")); + } else { // Endings + setText(kundo2_i18n("Split sections endings")); + } +} + +SplitSectionsCommand::~SplitSectionsCommand() +{ +} + +void SplitSectionsCommand::undo() +{ + KUndo2Command::undo(); + //FIXME: if it will go to KoTextCommandBase, place UndoRedoFinalizer here + + // All formatting changes will be undone automatically. + // Model Level is untouched. +} + +void SplitSectionsCommand::redo() +{ + KoTextDocument koDocument(m_document); + + if (!m_first) { + KUndo2Command::redo(); + //FIXME: if it will go to KoTextCommandBase, place UndoRedoFinalizer here + + // All formatting changes will be redone automatically. + // Model level is untouched. + } else { + m_first = false; + + KoTextEditor *editor = koDocument.textEditor(); + + if (m_type == Startings) { + editor->movePosition(QTextCursor::StartOfBlock); + editor->newLine(); + editor->movePosition(QTextCursor::PreviousBlock); + + QTextBlockFormat fmt = editor->blockFormat(); + KoSectionUtils::setSectionEndings(fmt, QList()); + QList firstBlockStartings = KoSectionUtils::sectionStartings(fmt).mid(0, m_splitPosition); + QList moveForward = KoSectionUtils::sectionStartings(fmt).mid(m_splitPosition); + KoSectionUtils::setSectionStartings(fmt, firstBlockStartings); + editor->setBlockFormat(fmt); + editor->movePosition(QTextCursor::NextBlock); + fmt = editor->blockFormat(); + KoSectionUtils::setSectionStartings(fmt, moveForward); + editor->setBlockFormat(fmt); + editor->movePosition(QTextCursor::PreviousBlock); + } else { // Endings + editor->movePosition(QTextCursor::EndOfBlock); + editor->newLine(); + + QTextBlockFormat fmt = editor->blockFormat(); + QList secondBlockEndings = KoSectionUtils::sectionEndings(fmt).mid(m_splitPosition + 1); + QList moveBackward = KoSectionUtils::sectionEndings(fmt).mid(0, m_splitPosition + 1); + KoSectionUtils::setSectionEndings(fmt, secondBlockEndings); + editor->setBlockFormat(fmt); + editor->movePosition(QTextCursor::PreviousBlock); + fmt = editor->blockFormat(); + KoSectionUtils::setSectionEndings(fmt, moveBackward); + editor->setBlockFormat(fmt); + editor->movePosition(QTextCursor::NextBlock); + } + } +} diff --git a/libs/kotext/commands/NewSectionCommand.h b/libs/kotext/commands/SplitSectionsCommand.h similarity index 54% copy from libs/kotext/commands/NewSectionCommand.h copy to libs/kotext/commands/SplitSectionsCommand.h index a0b02b8670..473b404eeb 100644 --- a/libs/kotext/commands/NewSectionCommand.h +++ b/libs/kotext/commands/SplitSectionsCommand.h @@ -1,43 +1,53 @@ /* * This file is part of the KDE project - * Copyright (C) 2014 Denis Kuplaykov + * Copyright (C) 2015 Denis Kuplaykov * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA.*/ -#ifndef NEWSECTIONCOMMAND_H -#define NEWSECTIONCOMMAND_H - -#include -#include +#ifndef SPLITSECTIONSCOMMAND_H +#define SPLITSECTIONSCOMMAND_H #include -class NewSectionCommand : public KUndo2Command +class KoSection; +class QTextDocument; + +//FIXME: why it is not going from KoTextCommandBase? +// If it will be changed to KoTextCommandBase, +// don't forget to add UndoRedoFinalizer. +class SplitSectionsCommand : public KUndo2Command { public: + enum SplitType + { + Startings, + Endings + }; - explicit NewSectionCommand(QTextDocument *document); - virtual ~NewSectionCommand(); + explicit SplitSectionsCommand(QTextDocument *document, SplitType type, int splitPosition); + virtual ~SplitSectionsCommand(); virtual void undo(); virtual void redo(); private: - bool m_first; // checks first call of redo - QTextDocument *m_document; // section manager of the document + bool m_first; ///< Checks first call of redo + QTextDocument *m_document; ///< Pointer to document + SplitType m_type; ///< Split type + int m_splitPosition; ///< Split position }; -#endif // NEWSECTIONCOMMAND_H +#endif // SPLITSECTIONSCOMMAND_H diff --git a/libs/kotext/opendocument/KoTextLoader.cpp b/libs/kotext/opendocument/KoTextLoader.cpp index 68e7f6846b..2cd487b792 100644 --- a/libs/kotext/opendocument/KoTextLoader.cpp +++ b/libs/kotext/opendocument/KoTextLoader.cpp @@ -1,1647 +1,1657 @@ /* This file is part of the KDE project * Copyright (C) 2001-2006 David Faure * Copyright (C) 2007,2009,2011 Thomas Zander * Copyright (C) 2007 Sebastian Sauer * Copyright (C) 2007,2011 Pierre Ducroquet * Copyright (C) 2007-2011 Thorsten Zachmann * Copyright (C) 2008 Girish Ramakrishnan * Copyright (C) 2009-2012 KO GmbH * Copyright (C) 2009 Pierre Stirnweiss * Copyright (C) 2010 KO GmbH * Copyright (C) 2011 Pavol Korinek * Copyright (C) 2011 Lukáš Tvrdý * Copyright (C) 2011 Boudewijn Rempt * Copyright (C) 2011-2012 Gopalakrishna Bhat A * Copyright (C) 2012 Inge Wallin * Copyright (C) 2009-2012 C. Boemann - * Copyright (C) 2014 Denis Kuplyakov + * Copyright (C) 2014-2015 Denis Kuplyakov * * 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 "KoTextLoader.h" #include #include #include #include #include #include #include #include #include #include "KoList.h" #include #include #include #include #include #include #include #include #include #include #include #include "KoTextDebug.h" #include "KoTextDocument.h" #include "KoTextSharedLoadingData.h" #include #include #include #include #include #include #include "KoTextInlineRdf.h" #include "KoTableOfContentsGeneratorInfo.h" #include "KoBibliographyInfo.h" #include "KoSection.h" #include "KoSectionEnd.h" #include "KoTextSoftPageBreak.h" #include "KoDocumentRdfBase.h" #include "KoElementReference.h" #include "KoTextTableTemplate.h" #include "styles/KoStyleManager.h" #include "styles/KoParagraphStyle.h" #include "styles/KoCharacterStyle.h" #include "styles/KoListStyle.h" #include "styles/KoListLevelProperties.h" #include "styles/KoTableStyle.h" #include "styles/KoTableColumnStyle.h" #include "styles/KoTableCellStyle.h" #include "styles/KoSectionStyle.h" #include +#include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include // if defined then debugging is enabled // #define KOOPENDOCUMENTLOADER_DEBUG /// \internal d-pointer class. class Q_DECL_HIDDEN KoTextLoader::Private { public: KoShapeLoadingContext &context; KoTextSharedLoadingData *textSharedData; // store it here so that you don't need to get it all the time from // the KoOdfLoadingContext. bool stylesDotXml; QTextBlockFormat defaultBlockFormat; QTextCharFormat defaultCharFormat; int bodyProgressTotal; int bodyProgressValue; int nextProgressReportMs; QTime progressTime; QVector currentLists; KoListStyle *currentListStyle; int currentListLevel; // Two lists that follow the same style are considered as one for numbering purposes // This hash keeps all the lists that have the same style in one KoList. QHash lists; KoCharacterStyle *endCharStyle; // charstyle from empty span used at end of paragraph KoStyleManager *styleManager; KoShape *shape; int loadSpanLevel; int loadSpanInitialPos; QVector nameSpacesList; QList openingSections; + QStack sectionStack; // Used to track the parent of current section QMap xmlIdToListMap; QVector m_previousList; QMap numberedParagraphListId; QStringList rdfIdList; /// level is between 1 and 10 void setCurrentList(KoList *currentList, int level); /// level is between 1 and 10 KoList *previousList(int level); explicit Private(KoShapeLoadingContext &context, KoShape *s) : context(context), textSharedData(0), // stylesDotXml says from where the office:automatic-styles are to be picked from: // the content.xml or the styles.xml (in a multidocument scenario). It does not // decide from where the office:styles are to be picked (always picked from styles.xml). // For our use here, stylesDotXml is always false (see ODF1.1 spec §2.1). stylesDotXml(context.odfLoadingContext().useStylesAutoStyles()), bodyProgressTotal(0), bodyProgressValue(0), nextProgressReportMs(0), currentLists(10), currentListStyle(0), currentListLevel(1), endCharStyle(0), styleManager(0), shape(s), loadSpanLevel(0), loadSpanInitialPos(0) , m_previousList(10) { progressTime.start(); } ~Private() { kDebug(32500) << "Loading took" << (float)(progressTime.elapsed()) / 1000 << " seconds"; } KoList *list(const QTextDocument *document, KoListStyle *listStyle, bool mergeSimilarStyledList); }; KoList *KoTextLoader::Private::list(const QTextDocument *document, KoListStyle *listStyle, bool mergeSimilarStyledList) { //TODO: Remove mergeSimilarStyledList parameter by finding a way to put the numbered-paragraphs of same level // to a single QTextList while loading rather than maintaining a hash list if (mergeSimilarStyledList) { if (lists.contains(listStyle)) { return lists[listStyle]; } } KoList *newList = new KoList(document, listStyle); lists[listStyle] = newList; return newList; } void KoTextLoader::Private::setCurrentList(KoList *currentList, int level) { Q_ASSERT(level > 0 && level <= 10); currentLists[level - 1] = currentList; m_previousList[level - 1] = currentList; } KoList *KoTextLoader::Private::previousList(int level) { Q_ASSERT(level > 0 && level <= 10); if (m_previousList.size() < level) { return 0; } return m_previousList.at(level - 1); } inline static bool isspace(ushort ch) { // options are ordered by likelyhood return ch == ' ' || ch== '\n' || ch == '\r' || ch == '\t'; } QString KoTextLoader::normalizeWhitespace(const QString &in, bool leadingSpace) { QString textstring = in; ushort *text = (ushort*)textstring.data(); // this detaches from the string 'in' int r, w = 0; int len = textstring.length(); for (r = 0; r < len; ++r) { const ushort ch = text[r]; // check for space, tab, line feed, carriage return if (isspace(ch)) { // if we were lead by whitespace in some parent or previous sibling element, // we completely collapse this space if (r != 0 || !leadingSpace) text[w++] = ' '; // find the end of the whitespace run while (r < len && isspace(text[r])) ++r; // and then record the next non-whitespace character if (r < len) text[w++] = text[r]; } else { text[w++] = ch; } } // and now trim off the unused part of the string textstring.truncate(w); return textstring; } /////////////KoTextLoader KoTextLoader::KoTextLoader(KoShapeLoadingContext &context, KoShape *shape) : QObject() , d(new Private(context, shape)) { KoSharedLoadingData *sharedData = context.sharedData(KOTEXT_SHARED_LOADING_ID); if (sharedData) { d->textSharedData = dynamic_cast(sharedData); } //kDebug(32500) << "sharedData" << sharedData << "textSharedData" << d->textSharedData; if (!d->textSharedData) { d->textSharedData = new KoTextSharedLoadingData(); KoDocumentResourceManager *rm = context.documentResourceManager(); KoStyleManager *styleManager = rm->resource(KoText::StyleManager).value(); d->textSharedData->loadOdfStyles(context, styleManager); if (!sharedData) { context.addSharedData(KOTEXT_SHARED_LOADING_ID, d->textSharedData); } else { kWarning(32500) << "A different type of sharedData was found under the" << KOTEXT_SHARED_LOADING_ID; Q_ASSERT(false); } } if (context.documentRdf()) { d->rdfIdList = qobject_cast(context.documentRdf())->idrefList(); } } KoTextLoader::~KoTextLoader() { delete d; } void KoTextLoader::loadBody(const KoXmlElement &bodyElem, QTextCursor &cursor, LoadBodyMode mode) { const QTextDocument *document = cursor.block().document(); static int rootCallChecker = 0; if (rootCallChecker == 0) { if (document->resource(KoTextDocument::FrameCharFormat, KoTextDocument::FrameCharFormatUrl).isValid()) { d->defaultBlockFormat = KoTextDocument(document).frameBlockFormat(); d->defaultCharFormat = KoTextDocument(document).frameCharFormat(); } else { // This is the first call of loadBody on the document. // Store the default block and char formats // Will be used whenever a new block is inserted d->defaultCharFormat = cursor.charFormat(); KoTextDocument(document).setFrameCharFormat(cursor.blockCharFormat()); d->defaultBlockFormat = cursor.blockFormat(); KoTextDocument(document).setFrameBlockFormat(cursor.blockFormat()); } } rootCallChecker++; cursor.beginEditBlock(); // If we are pasting text, we should handle sections correctly // we are saving which sections end in current block // and put their ends after the inserted text. QList oldSectionEndings; if (mode == PasteMode) { QTextBlockFormat fmt = cursor.blockFormat(); oldSectionEndings = KoSectionUtils::sectionEndings(fmt); fmt.clearProperty(KoParagraphStyle::SectionEndings); cursor.setBlockFormat(fmt); } if (!d->openingSections.isEmpty()) { QTextBlockFormat format = cursor.block().blockFormat(); d->openingSections << KoSectionUtils::sectionStartings(format); // if we had some already we need to append the new ones KoSectionUtils::setSectionStartings(format, d->openingSections); cursor.setBlockFormat(format); d->openingSections.clear(); } KoOdfLineNumberingConfiguration *lineNumberingConfiguration = new KoOdfLineNumberingConfiguration(d->context.odfLoadingContext() .stylesReader() .lineNumberingConfiguration()); KoTextDocument(document).setLineNumberingConfiguration(lineNumberingConfiguration); KoOdfBibliographyConfiguration *bibConfiguration = new KoOdfBibliographyConfiguration(d->context.odfLoadingContext() .stylesReader() .globalBibliographyConfiguration()); KoTextDocument(document).styleManager()->setBibliographyConfiguration(bibConfiguration); d->styleManager = KoTextDocument(document).styleManager(); #ifdef KOOPENDOCUMENTLOADER_DEBUG kDebug(32500) << "text-style:" << KoTextDebug::textAttributes(cursor.blockCharFormat()); #endif bool usedParagraph = false; // set to true if we found a tag that used the paragraph, indicating that the next round needs to start a new one. if (bodyElem.namespaceURI() == KoXmlNS::table && bodyElem.localName() == "table") { loadTable(bodyElem, cursor); } else { startBody(KoXml::childNodesCount(bodyElem)); KoXmlElement tag; for (KoXmlNode _node = bodyElem.firstChild(); !_node.isNull(); _node = _node.nextSibling() ) { if (!(tag = _node.toElement()).isNull()) { const QString localName = tag.localName(); if (tag.namespaceURI() == KoXmlNS::text) { if ((usedParagraph) && (tag.localName() != "table")) cursor.insertBlock(d->defaultBlockFormat, d->defaultCharFormat); usedParagraph = true; if (localName == "p") { // text paragraph loadParagraph(tag, cursor); } else if (localName == "h") { // heading loadHeading(tag, cursor); } else if (localName == "unordered-list" || localName == "ordered-list" // OOo-1.1 || localName == "list" || localName == "numbered-paragraph") { // OASIS loadList(tag, cursor); } else if (localName == "section") { loadSection(tag, cursor); } else if (localName == "table-of-content") { loadTableOfContents(tag, cursor); } else if (localName == "bibliography") { loadBibliography(tag, cursor); } else { KoInlineObject *obj = KoInlineObjectRegistry::instance()->createFromOdf(tag, d->context); if (obj) { KoInlineTextObjectManager *textObjectManager = KoTextDocument(cursor.block().document()).inlineTextObjectManager(); if (textObjectManager) { KoVariableManager *varManager = textObjectManager->variableManager(); if (varManager) { textObjectManager->insertInlineObject(cursor, obj); } } } else { usedParagraph = false; kWarning(32500) << "unhandled text:" << localName; } } } else if (tag.namespaceURI() == KoXmlNS::draw || tag.namespaceURI() == KoXmlNS::dr3d) { loadShape(tag, cursor); } else if (tag.namespaceURI() == KoXmlNS::table) { if (localName == "table") { loadTable(tag, cursor); usedParagraph = false; } else { kWarning(32500) << "KoTextLoader::loadBody unhandled table::" << localName; } } processBody(); } } endBody(); } rootCallChecker--; // Here we put old endings after text insertion. if (mode == PasteMode) { QTextBlockFormat fmt = cursor.blockFormat(); oldSectionEndings = KoSectionUtils::sectionEndings(fmt); KoSectionUtils::setSectionEndings(fmt, oldSectionEndings); cursor.setBlockFormat(fmt); } cursor.endEditBlock(); KoTextRangeManager *textRangeManager = KoTextDocument(cursor.block().document()).textRangeManager(); Q_UNUSED(textRangeManager); //kDebug(32500) << "text ranges::"; //foreach(KoTextRange *range, textRangeManager->textRanges()) { //kDebug(32500) << range->id(); //} + if (!rootCallChecker) { + // Allow to move end bounds of sections with inserting text + KoTextDocument(cursor.block().document()).sectionModel()->allowMovingEndBound(); + } } void KoTextLoader::loadParagraph(const KoXmlElement &element, QTextCursor &cursor) { // TODO use the default style name a default value? const QString styleName = element.attributeNS(KoXmlNS::text, "style-name", QString()); KoParagraphStyle *paragraphStyle = d->textSharedData->paragraphStyle(styleName, d->stylesDotXml); Q_ASSERT(d->styleManager); if (!paragraphStyle) { // Either the paragraph has no style or the style-name could not be found. // Fix up the paragraphStyle to be our default paragraph style in either case. if (!styleName.isEmpty()) kWarning(32500) << "paragraph style " << styleName << "not found - using default style"; paragraphStyle = d->styleManager->defaultParagraphStyle(); } QTextCharFormat cf = cursor.charFormat(); // store the current cursor char format if (paragraphStyle && (cursor.position() == cursor.block().position())) { QTextBlock block = cursor.block(); // Apply list style when loading a list but we don't have a list style paragraphStyle->applyStyle(block, d->currentLists[d->currentListLevel - 1] && !d->currentListStyle); // Clear the outline level property. If a default-outline-level was set, it should not // be applied when loading a document, only on user action. block.blockFormat().clearProperty(KoParagraphStyle::OutlineLevel); } // Some paragraph have id's defined which we need to store so that we can eg // attach text animations to this specific paragraph later on KoElementReference id; id.loadOdf(element); if (id.isValid() && d->shape) { QTextBlock block = cursor.block(); KoTextBlockData data(block); // this sets the user data, so don't remove d->context.addShapeSubItemId(d->shape, QVariant::fromValue(block.userData()), id.toString()); } // attach Rdf to cursor.block() // remember inline Rdf metadata -- if the xml-id is actually // about rdf. if (element.hasAttributeNS(KoXmlNS::xhtml, "property") || d->rdfIdList.contains(id.toString())) { QTextBlock block = cursor.block(); KoTextInlineRdf* inlineRdf = new KoTextInlineRdf((QTextDocument*)block.document(), block); if (inlineRdf->loadOdf(element)) { KoTextInlineRdf::attach(inlineRdf, cursor); } else { delete inlineRdf; inlineRdf = 0; } } #ifdef KOOPENDOCUMENTLOADER_DEBUG kDebug(32500) << "text-style:" << KoTextDebug::textAttributes(cursor.blockCharFormat()) << d->currentLists[d->currentListLevel - 1] << d->currentListStyle; #endif bool stripLeadingSpace = true; loadSpan(element, cursor, &stripLeadingSpace); QTextBlock block = cursor.block(); QString text = block.text(); if (text.length() == 0 || text.at(text.length()-1) == QChar(0x2028)) { if (d->endCharStyle) { QTextBlockFormat blockFormat = block.blockFormat(); blockFormat.setProperty(KoParagraphStyle::EndCharStyle, QVariant::fromValue< QSharedPointer >(QSharedPointer(d->endCharStyle->clone()))); cursor.setBlockFormat(blockFormat); } } d->endCharStyle = 0; cursor.setCharFormat(cf); // restore the cursor char format } void KoTextLoader::loadHeading(const KoXmlElement &element, QTextCursor &cursor) { Q_ASSERT(d->styleManager); int level = qMax(-1, element.attributeNS(KoXmlNS::text, "outline-level", "-1").toInt()); // This will fallback to the default-outline-level applied by KoParagraphStyle QString styleName = element.attributeNS(KoXmlNS::text, "style-name", QString()); QTextBlock block = cursor.block(); // Set the paragraph-style on the block KoParagraphStyle *paragraphStyle = d->textSharedData->paragraphStyle(styleName, d->stylesDotXml); if (!paragraphStyle) { paragraphStyle = d->styleManager->defaultParagraphStyle(); } if (paragraphStyle) { // Apply list style when loading a list but we don't have a list style paragraphStyle->applyStyle(block, (d->currentListLevel > 1) && d->currentLists[d->currentListLevel - 2] && !d->currentListStyle); } QTextCharFormat cf = cursor.charFormat(); // store the current cursor char format bool stripLeadingSpace = true; loadSpan(element, cursor, &stripLeadingSpace); cursor.setCharFormat(cf); // restore the cursor char format if ((block.blockFormat().hasProperty(KoParagraphStyle::OutlineLevel)) && (level == -1)) { level = block.blockFormat().property(KoParagraphStyle::OutlineLevel).toInt(); } else { if (level == -1) level = 1; QTextBlockFormat blockFormat; blockFormat.setProperty(KoParagraphStyle::OutlineLevel, level); cursor.mergeBlockFormat(blockFormat); } if (element.hasAttributeNS(KoXmlNS::text, "is-list-header")) { QTextBlockFormat blockFormat; blockFormat.setProperty(KoParagraphStyle::IsListHeader, element.attributeNS(KoXmlNS::text, "is-list-header") == "true"); cursor.mergeBlockFormat(blockFormat); } //we are defining our default behaviour here //Case 1: If text:outline-style is specified then we use the outline style to determine the numbering style //Case 2: If text:outline-style is not specified then if the element is inside a then it is numbered // otherwise it is not KoListStyle *outlineStyle = d->styleManager->outlineStyle(); if (!outlineStyle) { outlineStyle = d->styleManager->defaultOutlineStyle()->clone(); d->styleManager->setOutlineStyle(outlineStyle); } //if outline style is not specified and this is not inside a list then we do not number it if (outlineStyle->styleId() == d->styleManager->defaultOutlineStyle()->styleId()) { if (d->currentListLevel <= 1) { QTextBlockFormat blockFormat; blockFormat.setProperty(KoParagraphStyle::UnnumberedListItem, true); cursor.mergeBlockFormat(blockFormat); } else { //inside a list then take the numbering from the list style int level = d->currentListLevel - 1; KoListLevelProperties llp; if (!d->currentListStyle->hasLevelProperties(level)) { // Look if one of the lower levels are defined to we can copy over that level. for(int i = level - 1; i >= 0; --i) { if(d->currentLists[level - 1]->style()->hasLevelProperties(i)) { llp = d->currentLists[level - 1]->style()->levelProperties(i); break; } } } else { llp = d->currentListStyle->levelProperties(level); } llp.setLevel(level); outlineStyle->setLevelProperties(llp); } } KoList *list = KoTextDocument(block.document()).headingList(); if (!list) { list = d->list(block.document(), outlineStyle, false); KoTextDocument(block.document()).setHeadingList(list); } list->setStyle(outlineStyle); list->add(block, level); // attach Rdf to cursor.block() // remember inline Rdf metadata KoElementReference id; id.loadOdf(element); if (element.hasAttributeNS(KoXmlNS::xhtml, "property") || d->rdfIdList.contains(id.toString())) { QTextBlock block = cursor.block(); KoTextInlineRdf* inlineRdf = new KoTextInlineRdf((QTextDocument*)block.document(), block); if (inlineRdf->loadOdf(element)) { KoTextInlineRdf::attach(inlineRdf, cursor); } else { delete inlineRdf; inlineRdf = 0; } } #ifdef KOOPENDOCUMENTLOADER_DEBUG kDebug(32500) << "text-style:" << KoTextDebug::textAttributes(cursor.blockCharFormat()); #endif } void KoTextLoader::loadList(const KoXmlElement &element, QTextCursor &cursor) { const bool numberedParagraph = element.localName() == "numbered-paragraph"; QString styleName = element.attributeNS(KoXmlNS::text, "style-name", QString()); KoListStyle *listStyle = d->textSharedData->listStyle(styleName, d->stylesDotXml); KoList *continuedList = 0; int level; if (d->currentLists[d->currentListLevel - 1] || d->currentListLevel == 1) { d->currentLists[d->currentListLevel - 1] = 0; } else { d->currentLists[d->currentListLevel - 1] = d->currentLists[d->currentListLevel - 2]; } if (element.hasAttributeNS(KoXmlNS::text, "continue-list")) { if (d->xmlIdToListMap.contains(element.attributeNS(KoXmlNS::text, "continue-list", QString()))) { continuedList = d->xmlIdToListMap.value(element.attributeNS(KoXmlNS::text, "continue-list", QString())); } } else { //the ODF spec says that continue-numbering is considered only if continue-list is not specified if (element.hasAttributeNS(KoXmlNS::text, "continue-numbering")) { const QString continueNumbering = element.attributeNS(KoXmlNS::text, "continue-numbering", QString()); if (continueNumbering == "true") { //since ODF spec says "and the numbering style of the preceding list is the same as the current list" KoList *prevList = d->previousList(d->currentListLevel); if (prevList && listStyle && prevList->style()->hasLevelProperties(d->currentListLevel) && listStyle->hasLevelProperties(d->currentListLevel) && (prevList->style()->levelProperties(d->currentListLevel).style() == listStyle->levelProperties(d->currentListLevel).style())) { continuedList = prevList; } } } } // TODO: get level from the style, if it has a style:list-level attribute (new in ODF-1.2) if (numberedParagraph) { if (element.hasAttributeNS(KoXmlNS::text, "list-id")) { QString listId = element.attributeNS(KoXmlNS::text, "list-id"); if (d->numberedParagraphListId.contains(listId)) { d->currentLists.fill(d->numberedParagraphListId.value(listId)); } else { KoList *currentList = d->list(cursor.block().document(), listStyle, false); d->currentLists.fill(currentList); d->numberedParagraphListId.insert(listId, currentList); } } else { d->currentLists.fill(d->list(cursor.block().document(), listStyle, true)); } level = element.attributeNS(KoXmlNS::text, "level", "1").toInt(); d->currentListStyle = listStyle; } else { if (!listStyle) listStyle = d->currentListStyle; level = d->currentListLevel++; KoList *currentList = d->currentLists[d->currentListLevel - 2]; if (!currentList) { currentList = d->list(cursor.block().document(), listStyle, false); currentList->setListContinuedFrom(continuedList); d->currentLists[d->currentListLevel - 2] = currentList; } d->currentListStyle = listStyle; } if (element.hasAttributeNS(KoXmlNS::xml, "id")) { d->xmlIdToListMap.insert(element.attributeNS(KoXmlNS::xml, "id", QString()), d->currentLists[d->currentListLevel - 2]); } if (level < 0 || level > 10) { // should not happen but if it does then we should not crash/assert kWarning() << "Out of bounds list-level=" << level; level = qBound(0, level, 10); } if (! numberedParagraph) { d->setCurrentList(d->currentLists[d->currentListLevel - 2], level); } #ifdef KOOPENDOCUMENTLOADER_DEBUG if (d->currentListStyle) kDebug(32500) << "styleName =" << styleName << "listStyle =" << d->currentListStyle->name() << "level =" << level << "hasLevelProperties =" << d->currentListStyle->hasLevelProperties(level) //<<" style="< childElementsList; QString generatedXmlString; KoXmlDocument doc; QXmlStreamReader reader; for (KoXmlNode _node = element.firstChild(); !_node.isNull(); _node = _node.nextSibling()) { if (!(e = _node.toElement()).isNull()) { childElementsList.append(e); } } // Iterate over list items and add them to the textlist bool firstTime = true; foreach (e, childElementsList) { if (e.localName() != "removed-content") { if (!firstTime && !numberedParagraph) cursor.insertBlock(d->defaultBlockFormat, d->defaultCharFormat); firstTime = false; loadListItem(e, cursor, level); } } if (numberedParagraph || --d->currentListLevel == 1) { d->currentListStyle = 0; d->currentLists.fill(0); } } -void KoTextLoader::loadListItem(KoXmlElement &e, QTextCursor &cursor, int level) +void KoTextLoader::loadListItem(const KoXmlElement &e, QTextCursor &cursor, int level) { bool numberedParagraph = e.parentNode().toElement().localName() == "numbered-paragraph"; if (e.isNull() || e.namespaceURI() != KoXmlNS::text) return; const bool listHeader = e.tagName() == "list-header"; if (!numberedParagraph && e.tagName() != "list-item" && !listHeader) return; QTextBlock current = cursor.block(); QTextBlockFormat blockFormat; if (numberedParagraph) { if (e.localName() == "p") { loadParagraph(e, cursor); } else if (e.localName() == "h") { loadHeading(e, cursor); } blockFormat.setProperty(KoParagraphStyle::ListLevel, level); } else { loadBody(e, cursor); } if (!cursor.blockFormat().boolProperty(KoParagraphStyle::ForceDisablingList)) { if (!current.textList()) { if (!d->currentLists[level - 1]->style()->hasLevelProperties(level)) { KoListLevelProperties llp; // Look if one of the lower levels are defined to we can copy over that level. for(int i = level - 1; i >= 0; --i) { if(d->currentLists[level - 1]->style()->hasLevelProperties(i)) { llp = d->currentLists[level - 1]->style()->levelProperties(i); break; } } llp.setLevel(level); // TODO make the 10 configurable llp.setIndent(level * 10.0); d->currentLists[level - 1]->style()->setLevelProperties(llp); } d->currentLists[level - 1]->add(current, level); } if (listHeader) blockFormat.setProperty(KoParagraphStyle::IsListHeader, true); if (e.hasAttributeNS(KoXmlNS::text, "start-value")) { int startValue = e.attributeNS(KoXmlNS::text, "start-value", QString()).toInt(); blockFormat.setProperty(KoParagraphStyle::ListStartValue, startValue); } // mark intermediate paragraphs as unnumbered items QTextCursor c(current); c.mergeBlockFormat(blockFormat); while (c.block() != cursor.block()) { c.movePosition(QTextCursor::NextBlock); if (c.block().textList()) // a sublist break; blockFormat = c.blockFormat(); blockFormat.setProperty(listHeader ? KoParagraphStyle::IsListHeader : KoParagraphStyle::UnnumberedListItem, true); c.setBlockFormat(blockFormat); d->currentLists[level - 1]->add(c.block(), level); } } kDebug(32500) << "text-style:" << KoTextDebug::textAttributes(cursor.blockCharFormat()); } void KoTextLoader::loadSection(const KoXmlElement §ionElem, QTextCursor &cursor) { - KoSection *section = new KoSection(cursor); + KoSection *parent = d->sectionStack.empty() ? 0 : d->sectionStack.top(); + KoSection *section = d->context.sectionModel()->createSection(cursor, parent); if (!section->loadOdf(sectionElem, d->textSharedData, d->stylesDotXml)) { delete section; kWarning(32500) << "Could not load section"; return; } + d->sectionStack << section; d->openingSections << section; loadBody(sectionElem, cursor); // Close the section on the last block of text we have loaded just now. QTextBlockFormat format = cursor.block().blockFormat(); KoSectionUtils::setSectionEndings(format, - KoSectionUtils::sectionEndings(format) << new KoSectionEnd(section)); + KoSectionUtils::sectionEndings(format) << d->context.sectionModel()->createSectionEnd(section)); + d->sectionStack.pop(); cursor.setBlockFormat(format); + + section->setKeepEndBound(true); // This bound should stop moving with new text } void KoTextLoader::loadNote(const KoXmlElement ¬eElem, QTextCursor &cursor) { KoInlineTextObjectManager *textObjectManager = KoTextDocument(cursor.block().document()).inlineTextObjectManager(); if (textObjectManager) { QString className = noteElem.attributeNS(KoXmlNS::text, "note-class"); KoInlineNote *note = 0; int position = cursor.position(); // need to store this as the following might move is if (className == "footnote") { note = new KoInlineNote(KoInlineNote::Footnote); note->setMotherFrame(KoTextDocument(cursor.block().document()).auxillaryFrame()); } else { note = new KoInlineNote(KoInlineNote::Endnote); note->setMotherFrame(KoTextDocument(cursor.block().document()).auxillaryFrame()); } if (note->loadOdf(noteElem, d->context)) { cursor.setPosition(position); // restore the position before inserting the note textObjectManager->insertInlineObject(cursor, note); } else { cursor.setPosition(position); // restore the position delete note; } } } - void KoTextLoader::loadCite(const KoXmlElement ¬eElem, QTextCursor &cursor) { KoInlineTextObjectManager *textObjectManager = KoTextDocument(cursor.block().document()).inlineTextObjectManager(); if (textObjectManager) { //Now creating citation with default type KoInlineCite::Citation. KoInlineCite *cite = new KoInlineCite(KoInlineCite::Citation); // the manager is needed during loading so set it now cite->setManager(textObjectManager); if (cite->loadOdf(noteElem, d->context)) { textObjectManager->insertInlineObject(cursor, cite); } else { delete cite; } } } void KoTextLoader::loadText(const QString &fulltext, QTextCursor &cursor, bool *stripLeadingSpace, bool isLastNode) { QString text = normalizeWhitespace(fulltext, *stripLeadingSpace); #ifdef KOOPENDOCUMENTLOADER_DEBUG kDebug(32500) << " text=" << text << text.length() << *stripLeadingSpace; #endif if (!text.isEmpty()) { // if present text ends with a space, // we can remove the leading space in the next text *stripLeadingSpace = text[text.length() - 1].isSpace(); cursor.insertText(text); if (d->loadSpanLevel == 1 && isLastNode && cursor.position() > d->loadSpanInitialPos) { QTextCursor tempCursor(cursor); tempCursor.movePosition(QTextCursor::Left, QTextCursor::KeepAnchor, 1); // select last char loaded if (tempCursor.selectedText() == " " && *stripLeadingSpace) { // if it's a collapsed blankspace tempCursor.removeSelectedText(); // remove it } } } } void KoTextLoader::loadSpan(const KoXmlElement &element, QTextCursor &cursor, bool *stripLeadingSpace) { #ifdef KOOPENDOCUMENTLOADER_DEBUG kDebug(32500) << "text-style:" << KoTextDebug::textAttributes(cursor.blockCharFormat()); #endif Q_ASSERT(stripLeadingSpace); if (d->loadSpanLevel++ == 0) d->loadSpanInitialPos = cursor.position(); for (KoXmlNode node = element.firstChild(); !node.isNull(); node = node.nextSibling()) { KoXmlElement ts = node.toElement(); const QString localName(ts.localName()); const bool isTextNS = ts.namespaceURI() == KoXmlNS::text; const bool isDrawNS = ts.namespaceURI() == KoXmlNS::draw; const bool isDr3dNS = ts.namespaceURI() == KoXmlNS::dr3d; const bool isOfficeNS = ts.namespaceURI() == KoXmlNS::office; //if (isOfficeNS) kDebug(32500) << "office:"<endCharStyle = 0; } if (node.isText()) { bool isLastNode = node.nextSibling().isNull(); loadText(node.toText().data(), cursor, stripLeadingSpace, isLastNode); } else if (isTextNS && localName == "span") { // text:span #ifdef KOOPENDOCUMENTLOADER_DEBUG kDebug(32500) << " localName=" << localName; #endif QString styleName = ts.attributeNS(KoXmlNS::text, "style-name", QString()); QTextCharFormat cf = cursor.charFormat(); // store the current cursor char format KoCharacterStyle *characterStyle = d->textSharedData->characterStyle(styleName, d->stylesDotXml); if (characterStyle) { characterStyle->applyStyle(&cursor); if (ts.firstChild().isNull()) { // empty span so let's save the characterStyle for possible use at end of par d->endCharStyle = characterStyle; } } else if (!styleName.isEmpty()) { kWarning(32500) << "character style " << styleName << " not found"; } loadSpan(ts, cursor, stripLeadingSpace); // recurse cursor.setCharFormat(cf); // restore the cursor char format } else if (isTextNS && localName == "s") { // text:s int howmany = 1; if (ts.hasAttributeNS(KoXmlNS::text, "c")) { howmany = ts.attributeNS(KoXmlNS::text, "c", QString()).toInt(); } cursor.insertText(QString().fill(32, howmany)); *stripLeadingSpace = false; } else if ( (isTextNS && localName == "note")) { // text:note loadNote(ts, cursor); } else if (isTextNS && localName == "bibliography-mark") { // text:bibliography-mark loadCite(ts,cursor); } else if (isTextNS && localName == "tab") { // text:tab cursor.insertText("\t"); *stripLeadingSpace = false; } else if (isTextNS && localName == "a") { // text:a QString target = ts.attributeNS(KoXmlNS::xlink, "href"); QString styleName = ts.attributeNS(KoXmlNS::text, "style-name", QString()); QTextCharFormat cf = cursor.charFormat(); // store the current cursor char format if (!styleName.isEmpty()) { KoCharacterStyle *characterStyle = d->textSharedData->characterStyle(styleName, d->stylesDotXml); if (characterStyle) { characterStyle->applyStyle(&cursor); } else { kWarning(32500) << "character style " << styleName << " not found"; } } QTextCharFormat newCharFormat = cursor.charFormat(); newCharFormat.setAnchor(true); newCharFormat.setProperty(KoCharacterStyle::AnchorType, KoCharacterStyle::Anchor); newCharFormat.setAnchorHref(target); cursor.setCharFormat(newCharFormat); loadSpan(ts, cursor, stripLeadingSpace); // recurse cursor.setCharFormat(cf); // restore the cursor char format } else if (isTextNS && localName == "line-break") { // text:line-break #ifdef KOOPENDOCUMENTLOADER_DEBUG kDebug(32500) << " Node localName=" << localName; #endif cursor.insertText(QChar(0x2028)); *stripLeadingSpace = false; } else if (isTextNS && localName == "soft-page-break") { // text:soft-page-break KoInlineTextObjectManager *textObjectManager = KoTextDocument(cursor.block().document()).inlineTextObjectManager(); if (textObjectManager) { textObjectManager->insertInlineObject(cursor, new KoTextSoftPageBreak()); } } else if (isTextNS && localName == "meta") { #ifdef KOOPENDOCUMENTLOADER_DEBUG kDebug(30015) << "loading a text:meta"; #endif KoInlineTextObjectManager *textObjectManager = KoTextDocument(cursor.block().document()).inlineTextObjectManager(); if (textObjectManager) { const QTextDocument *document = cursor.block().document(); KoTextMeta* startmark = new KoTextMeta(document); textObjectManager->insertInlineObject(cursor, startmark); // Add inline Rdf here. KoElementReference id; id.loadOdf(ts); if (ts.hasAttributeNS(KoXmlNS::xhtml, "property") || (id.isValid() && d->rdfIdList.contains(id.toString()))) { KoTextInlineRdf* inlineRdf = new KoTextInlineRdf((QTextDocument*)document, startmark); if (inlineRdf->loadOdf(ts)) { startmark->setInlineRdf(inlineRdf); } else { delete inlineRdf; inlineRdf = 0; } } loadSpan(ts, cursor, stripLeadingSpace); // recurse KoTextMeta* endmark = new KoTextMeta(document); textObjectManager->insertInlineObject(cursor, endmark); startmark->setEndBookmark(endmark); } } // text:bookmark, text:bookmark-start and text:bookmark-end else if (isTextNS && (localName == "bookmark" || localName == "bookmark-start" || localName == "bookmark-end")) { KoTextRangeManager *textRangeManager = KoTextDocument(cursor.block().document()).textRangeManager(); if (localName == "bookmark-end") { KoBookmark *bookmark = textRangeManager->bookmarkManager()->bookmark(KoBookmark::createUniqueBookmarkName(textRangeManager->bookmarkManager(), ts.attribute("name"), true)); if (bookmark) { bookmark->setRangeEnd(cursor.position()); } } else { KoBookmark *bookmark = new KoBookmark(cursor); bookmark->setManager(textRangeManager); if (textRangeManager && bookmark->loadOdf(ts, d->context)) { textRangeManager->insert(bookmark); } else { kWarning(32500) << "Could not load bookmark"; delete bookmark; } } } else if (isTextNS && localName == "bookmark-ref") { QString bookmarkName = ts.attribute("ref-name"); QTextCharFormat cf = cursor.charFormat(); // store the current cursor char format if (!bookmarkName.isEmpty()) { QTextCharFormat linkCf(cf); // and copy it to alter it linkCf.setAnchor(true); linkCf.setProperty(KoCharacterStyle::AnchorType, KoCharacterStyle::Bookmark); QStringList anchorName; anchorName << bookmarkName; linkCf.setAnchorHref('#'+ bookmarkName); cursor.setCharFormat(linkCf); } // TODO add support for loading text:reference-format loadSpan(ts, cursor, stripLeadingSpace); // recurse cursor.setCharFormat(cf); // restore the cursor char format } // text:annotation and text:annotation-end else if (isOfficeNS && (localName == "annotation" || localName == "annotation-end")) { kDebug(32500) << "------> annotation found" << localName; KoTextRangeManager *textRangeManager = KoTextDocument(cursor.block().document()).textRangeManager(); if (localName == "annotation-end") { // If the tag is annotation-end, there should already be a KoAnnotation // with the same name as this one. If so, just find it and set the end // of the range to the current position. KoAnnotation *annotation = textRangeManager->annotationManager()->annotation(KoAnnotation::createUniqueAnnotationName(textRangeManager->annotationManager(), ts.attribute("name"), true)); if (annotation) { annotation->setRangeEnd(cursor.position()); } } else { // if the tag is "annotation" then create a KoAnnotation // and an annotation shape and call loadOdf() in them. KoAnnotation *annotation = new KoAnnotation(cursor); annotation->setManager(textRangeManager); if (textRangeManager && annotation->loadOdf(ts, d->context)) { textRangeManager->insert(annotation); KoShape *shape = KoShapeRegistry::instance()->createShapeFromOdf(ts, d->context); // Don't show it before it's being laid out. // // Also this hides it in all applications but // those that have an annotation layout manager // (currently: words, author). shape->setVisible(false); d->textSharedData->shapeInserted(shape, element, d->context); annotation->setAnnotationShape(shape); } else { kWarning(32500) << "Could not load annotation"; delete annotation; } } } else if (isTextNS && localName == "number") { // text:number /* ODF Spec, §4.1.1, Formatted Heading Numbering If a heading has a numbering applied, the text of the formatted number can be included in a element. This text can be used by applications that do not support numbering of headings, but it will be ignored by applications that support numbering. */ } else if (isTextNS && localName == "dde-connection") { // TODO: load actual connection (or at least preserve it) // For now: just load the text for (KoXmlNode n = ts.firstChild(); !n.isNull(); n = n.nextSibling()) { if (n.isText()) { loadText(n.toText().data(), cursor, stripLeadingSpace, false); } } } else if ((isDrawNS) && localName == "a") { // draw:a loadShapeWithHyperLink(ts, cursor); } else if (isDrawNS || isDr3dNS) { loadShape(ts, cursor); } else { KoInlineObject *obj = KoInlineObjectRegistry::instance()->createFromOdf(ts, d->context); KoInlineTextObjectManager *textObjectManager = KoTextDocument(cursor.block().document()).inlineTextObjectManager(); if (obj && textObjectManager) { KoVariableManager *varManager = textObjectManager->variableManager(); if (varManager) { textObjectManager->insertInlineObject(cursor, obj); // we need to update whitespace stripping here so we don't remove to many whitespaces. // this is simplified as it assumes the first child it the text item but that should be the case // most of the time with variables so it should be fine. KoXmlNode child = ts.firstChild(); if (child.isText()) { QString text = normalizeWhitespace(child.toText().data(), *stripLeadingSpace); if (!text.isEmpty()) { // if present text ends with a space, // we can remove the leading space in the next text *stripLeadingSpace = text[text.length() - 1].isSpace(); } } } } else { #if 0 //1.6: bool handled = false; // Check if it's a variable KoVariable *var = context.variableCollection().loadOasisField(textDocument(), ts, context); if (var) { textData = "#"; // field placeholder customItem = var; handled = true; } if (!handled) { handled = textDocument()->loadSpanTag(ts, context, this, pos, textData, customItem); if (!handled) { kWarning(32500) << "Ignoring tag " << ts.tagName(); context.styleStack().restore(); continue; } } #else #ifdef KOOPENDOCUMENTLOADER_DEBUG kDebug(32500) << "Node '" << localName << "' unhandled"; #endif } #endif } } --d->loadSpanLevel; } void KoTextLoader::loadTable(const KoXmlElement &tableElem, QTextCursor &cursor) { QTextTableFormat tableFormat; QString tableStyleName = tableElem.attributeNS(KoXmlNS::table, "style-name", ""); if (!tableStyleName.isEmpty()) { KoTableStyle *tblStyle = d->textSharedData->tableStyle(tableStyleName, d->stylesDotXml); if (tblStyle) tblStyle->applyStyle(tableFormat); } QString tableTemplateName = tableElem.attributeNS(KoXmlNS::table, "template-name", ""); if (! tableTemplateName.isEmpty()) { if(KoTextTableTemplate *tableTemplate = d->styleManager->tableTemplate(tableTemplateName)) { tableFormat.setProperty(KoTableStyle::TableTemplate, tableTemplate->styleId()); } } if (tableElem.attributeNS(KoXmlNS::table, "use-banding-columns-styles", "false") == "true") { tableFormat.setProperty(KoTableStyle::UseBandingColumnStyles, true); } if (tableElem.attributeNS(KoXmlNS::table, "use-banding-rows-styles", "false") == "true") { tableFormat.setProperty(KoTableStyle::UseBandingRowStyles, true); } if (tableElem.attributeNS(KoXmlNS::table, "use-first-column-styles", "false") == "true") { tableFormat.setProperty(KoTableStyle::UseFirstColumnStyles, true); } if (tableElem.attributeNS(KoXmlNS::table, "use-first-row-styles", "false") == "true") { tableFormat.setProperty(KoTableStyle::UseFirstRowStyles, true); } if (tableElem.attributeNS(KoXmlNS::table, "use-last-column-styles", "false") == "true") { tableFormat.setProperty(KoTableStyle::UseLastColumnStyles, true); } if (tableElem.attributeNS(KoXmlNS::table, "use-last-row-styles", "false") == "true") { tableFormat.setProperty(KoTableStyle::UseLastRowStyles, true); } // Let's try to figure out when to hide the current block QTextBlock currentBlock = cursor.block(); QTextTable *outerTable = cursor.currentTable(); bool hide = cursor.position() == 0; if (outerTable) { QTextTableCell cell = outerTable->cellAt(cursor.position()); if (cursor.position() == cell.firstCursorPosition().position()) { hide = true; } } if (!hide) { // Let's insert an extra block so that will be the one we hide instead cursor.insertBlock(cursor.blockFormat(), cursor.blockCharFormat()); currentBlock = cursor.block(); } if (tableElem.attributeNS(KoXmlNS::table, "protected", "false") == "true") { tableFormat.setProperty(KoTableStyle::TableIsProtected, true); } QTextTable *tbl = cursor.insertTable(1, 1, tableFormat); // 'Hide' the block before the table (possibly the extra block we just inserted) QTextBlockFormat blockFormat; //blockFormat.setFont(currentBlock.blockFormat().font()); QTextCursor tmpCursor(currentBlock); blockFormat.setProperty(KoParagraphStyle::HiddenByTable, true); QVariant masterStyle = tableFormat.property(KoTableStyle::MasterPageName); if (!masterStyle.isNull()) { // if table has a master page style property, copy it to block before table, because // this block is a hidden block so let's make it belong to the same masterpage // so we don't end up with a page break at the wrong place blockFormat.setProperty(KoParagraphStyle::MasterPageName, masterStyle); } tmpCursor.setBlockFormat(blockFormat); KoTableColumnAndRowStyleManager tcarManager = KoTableColumnAndRowStyleManager::getManager(tbl); int rows = 0; int columns = 0; QList spanStore; //temporary list to store spans until the entire table have been created KoXmlElement tblTag; int headingRowCounter = 0; QList rowTags; forEachElement(tblTag, tableElem) { if (! tblTag.isNull()) { const QString tblLocalName = tblTag.localName(); if (tblTag.namespaceURI() == KoXmlNS::table) { if (tblLocalName == "table-column") { loadTableColumn(tblTag, tbl, columns); } else if (tblLocalName == "table-columns") { KoXmlElement e; forEachElement(e, tblTag) { if (e.localName() == "table-column") { loadTableColumn(e, tbl, columns); } } } else if (tblLocalName == "table-row") { loadTableRow(tblTag, tbl, spanStore, cursor, rows); } else if (tblLocalName == "table-rows") { KoXmlElement subTag; forEachElement(subTag, tblTag) { if (!subTag.isNull()) { if ((subTag.namespaceURI() == KoXmlNS::table) && (subTag.localName() == "table-row")) { loadTableRow(subTag, tbl, spanStore, cursor, rows); } } } } else if (tblLocalName == "table-header-rows") { KoXmlElement subTag; forEachElement(subTag, tblTag) { if (!subTag.isNull()) { if ((subTag.namespaceURI() == KoXmlNS::table) && (subTag.localName() == "table-row")) { headingRowCounter++; loadTableRow(subTag, tbl, spanStore, cursor, rows); } } } } } } } if (headingRowCounter > 0) { QTextTableFormat fmt = tbl->format(); fmt.setProperty(KoTableStyle::NumberHeadingRows, headingRowCounter); tbl->setFormat(fmt); } // Finally create spans foreach(const QRect &span, spanStore) { tbl->mergeCells(span.y(), span.x(), span.height(), span.width()); // for some reason Qt takes row, column } cursor = tbl->lastCursorPosition(); cursor.movePosition(QTextCursor::Right, QTextCursor::MoveAnchor, 1); } -void KoTextLoader::loadTableColumn(KoXmlElement &tblTag, QTextTable *tbl, int &columns) +void KoTextLoader::loadTableColumn(const KoXmlElement &tblTag, QTextTable *tbl, int &columns) { KoTableColumnAndRowStyleManager tcarManager = KoTableColumnAndRowStyleManager::getManager(tbl); int rows = tbl->rows(); int repeatColumn = tblTag.attributeNS(KoXmlNS::table, "number-columns-repeated", "1").toInt(); QString columnStyleName = tblTag.attributeNS(KoXmlNS::table, "style-name", ""); if (!columnStyleName.isEmpty()) { KoTableColumnStyle *columnStyle = d->textSharedData->tableColumnStyle(columnStyleName, d->stylesDotXml); if (columnStyle) { for (int c = columns; c < columns + repeatColumn; c++) { tcarManager.setColumnStyle(c, *columnStyle); } } } QString defaultCellStyleName = tblTag.attributeNS(KoXmlNS::table, "default-cell-style-name", ""); if (!defaultCellStyleName.isEmpty()) { KoTableCellStyle *cellStyle = d->textSharedData->tableCellStyle(defaultCellStyleName, d->stylesDotXml); for (int c = columns; c < columns + repeatColumn; c++) { tcarManager.setDefaultColumnCellStyle(c, cellStyle); } } columns = columns + repeatColumn; if (rows > 0) tbl->resize(rows, columns); else tbl->resize(1, columns); } -void KoTextLoader::loadTableRow(KoXmlElement &tblTag, QTextTable *tbl, QList &spanStore, QTextCursor &cursor, int &rows) +void KoTextLoader::loadTableRow(const KoXmlElement &tblTag, QTextTable *tbl, QList &spanStore, QTextCursor &cursor, int &rows) { KoTableColumnAndRowStyleManager tcarManager = KoTableColumnAndRowStyleManager::getManager(tbl); int columns = tbl->columns(); QString rowStyleName = tblTag.attributeNS(KoXmlNS::table, "style-name", ""); if (!rowStyleName.isEmpty()) { KoTableRowStyle *rowStyle = d->textSharedData->tableRowStyle(rowStyleName, d->stylesDotXml); if (rowStyle) { tcarManager.setRowStyle(rows, *rowStyle); } } QString defaultCellStyleName = tblTag.attributeNS(KoXmlNS::table, "default-cell-style-name", ""); if (!defaultCellStyleName.isEmpty()) { KoTableCellStyle *cellStyle = d->textSharedData->tableCellStyle(defaultCellStyleName, d->stylesDotXml); tcarManager.setDefaultRowCellStyle(rows, cellStyle); } rows++; if (columns > 0) tbl->resize(rows, columns); else tbl->resize(rows, 1); // Added a row int currentCell = 0; KoXmlElement rowTag; forEachElement(rowTag, tblTag) { if (!rowTag.isNull()) { const QString rowLocalName = rowTag.localName(); if (rowTag.namespaceURI() == KoXmlNS::table) { if (rowLocalName == "table-cell") { loadTableCell(rowTag, tbl, spanStore, cursor, currentCell); currentCell++; } else if (rowLocalName == "covered-table-cell") { currentCell++; } } } } } -void KoTextLoader::loadTableCell(KoXmlElement &rowTag, QTextTable *tbl, QList &spanStore, QTextCursor &cursor, int ¤tCell) +void KoTextLoader::loadTableCell(const KoXmlElement &rowTag, QTextTable *tbl, QList &spanStore, QTextCursor &cursor, int ¤tCell) { KoTableColumnAndRowStyleManager tcarManager = KoTableColumnAndRowStyleManager::getManager(tbl); const int currentRow = tbl->rows() - 1; QTextTableCell cell = tbl->cellAt(currentRow, currentCell); // store spans until entire table have been loaded int rowsSpanned = rowTag.attributeNS(KoXmlNS::table, "number-rows-spanned", "1").toInt(); int columnsSpanned = rowTag.attributeNS(KoXmlNS::table, "number-columns-spanned", "1").toInt(); spanStore.append(QRect(currentCell, currentRow, columnsSpanned, rowsSpanned)); if (cell.isValid()) { QString cellStyleName = rowTag.attributeNS(KoXmlNS::table, "style-name", ""); KoTableCellStyle *cellStyle = 0; if (!cellStyleName.isEmpty()) { cellStyle = d->textSharedData->tableCellStyle(cellStyleName, d->stylesDotXml); } else if (tcarManager.defaultRowCellStyle(currentRow)) { cellStyle = tcarManager.defaultRowCellStyle(currentRow); } else if (tcarManager.defaultColumnCellStyle(currentCell)) { cellStyle = tcarManager.defaultColumnCellStyle(currentCell); } if (cellStyle) cellStyle->applyStyle(cell); QTextTableCellFormat cellFormat = cell.format().toTableCellFormat(); if (rowTag.attributeNS(KoXmlNS::table, "protected", "false") == "true") { cellFormat.setProperty(KoTableCellStyle::CellIsProtected, true); } cell.setFormat(cellFormat); // handle inline Rdf // rowTag is the current table cell. KoElementReference id; id.loadOdf(rowTag); if (rowTag.hasAttributeNS(KoXmlNS::xhtml, "property") || d->rdfIdList.contains(id.toString())) { KoTextInlineRdf* inlineRdf = new KoTextInlineRdf((QTextDocument*)cursor.block().document(),cell); if (inlineRdf->loadOdf(rowTag)) { QTextTableCellFormat cellFormat = cell.format().toTableCellFormat(); cellFormat.setProperty(KoTableCellStyle::InlineRdf,QVariant::fromValue(inlineRdf)); cell.setFormat(cellFormat); } else { delete inlineRdf; inlineRdf = 0; } } cursor = cell.firstCursorPosition(); loadBody(rowTag, cursor); } } void KoTextLoader::loadShapeWithHyperLink(const KoXmlElement &element, QTextCursor& cursor) { // get the hyperlink QString hyperLink = element.attributeNS(KoXmlNS::xlink, "href"); KoShape *shape = 0; //load the shape for hyperlink KoXmlNode node = element.firstChild(); if (!node.isNull()) { KoXmlElement ts = node.toElement(); shape = loadShape(ts, cursor); if (shape) { shape->setHyperLink(hyperLink); } } } KoShape *KoTextLoader::loadShape(const KoXmlElement &element, QTextCursor &cursor) { KoShape *shape = KoShapeRegistry::instance()->createShapeFromOdf(element, d->context); if (!shape) { kDebug(32500) << "shape '" << element.localName() << "' unhandled"; return 0; } KoShapeAnchor *anchor = new KoShapeAnchor(shape); anchor->loadOdf(element, d->context); shape->setAnchor(anchor); d->textSharedData->shapeInserted(shape, element, d->context); // page anchored shapes are handled differently if (anchor->anchorType() == KoShapeAnchor::AnchorPage) { // nothing else to do } else if (anchor->anchorType() == KoShapeAnchor::AnchorAsCharacter) { KoAnchorInlineObject *anchorObject = new KoAnchorInlineObject(anchor); KoInlineTextObjectManager *textObjectManager = KoTextDocument(cursor.block().document()).inlineTextObjectManager(); if (textObjectManager) { textObjectManager->insertInlineObject(cursor, anchorObject); } } else { // KoShapeAnchor::AnchorToCharacter or KoShapeAnchor::AnchorParagraph KoAnchorTextRange *anchorRange = new KoAnchorTextRange(anchor, cursor); KoTextRangeManager *textRangeManager = KoTextDocument(cursor.block().document()).textRangeManager(); anchorRange->setManager(textRangeManager); textRangeManager->insert(anchorRange); } return shape; } void KoTextLoader::loadTableOfContents(const KoXmlElement &element, QTextCursor &cursor) { // make sure that the tag is table-of-content Q_ASSERT(element.tagName() == "table-of-content"); QTextBlockFormat tocFormat; // for "meta-information" about the TOC we use this class KoTableOfContentsGeneratorInfo *info = new KoTableOfContentsGeneratorInfo(); // to store the contents we use an extrafor "meta-information" about the TOC we use this class QTextDocument *tocDocument = new QTextDocument(); KoTextDocument(tocDocument).setStyleManager(d->styleManager); KoTextDocument(tocDocument).setTextRangeManager(new KoTextRangeManager); info->m_name = element.attribute("name"); info->m_styleName = element.attribute("style-name"); KoXmlElement e; forEachElement(e, element) { if (e.isNull() || e.namespaceURI() != KoXmlNS::text) { continue; } if (e.localName() == "table-of-content-source" && e.namespaceURI() == KoXmlNS::text) { info->loadOdf(d->textSharedData, e); // uncomment to see what has been loaded //info.tableOfContentData()->dump(); tocFormat.setProperty(KoParagraphStyle::TableOfContentsData, QVariant::fromValue(info) ); tocFormat.setProperty(KoParagraphStyle::GeneratedDocument, QVariant::fromValue(tocDocument) ); cursor.insertBlock(tocFormat); // We'll just try to find displayable elements and add them as paragraphs } else if (e.localName() == "index-body") { QTextCursor cursorFrame = tocDocument->rootFrame()->lastCursorPosition(); bool firstTime = true; KoXmlElement p; forEachElement(p, e) { // All elem will be "p" instead of the title, which is particular if (p.isNull() || p.namespaceURI() != KoXmlNS::text) continue; if (!firstTime) { // use empty formats to not inherit from the prev parag QTextBlockFormat bf; QTextCharFormat cf; cursorFrame.insertBlock(bf, cf); } firstTime = false; QTextBlock current = cursorFrame.block(); QTextBlockFormat blockFormat; if (p.localName() == "p") { loadParagraph(p, cursorFrame); } else if (p.localName() == "index-title") { loadBody(p, cursorFrame); } QTextCursor c(current); c.mergeBlockFormat(blockFormat); } }// index-body } } void KoTextLoader::loadBibliography(const KoXmlElement &element, QTextCursor &cursor) { // make sure that the tag is bibliography Q_ASSERT(element.tagName() == "bibliography"); QTextBlockFormat bibFormat; // for "meta-information" about the bibliography we use this class KoBibliographyInfo *info = new KoBibliographyInfo(); QTextDocument *bibDocument = new QTextDocument(); KoTextDocument(bibDocument).setStyleManager(d->styleManager); KoTextDocument(bibDocument).setTextRangeManager(new KoTextRangeManager); info->m_name = element.attribute("name"); info->m_styleName = element.attribute("style-name"); KoXmlElement e; forEachElement(e, element) { if (e.isNull() || e.namespaceURI() != KoXmlNS::text) { continue; } if (e.localName() == "bibliography-source" && e.namespaceURI() == KoXmlNS::text) { info->loadOdf(d->textSharedData, e); bibFormat.setProperty(KoParagraphStyle::BibliographyData, QVariant::fromValue(info)); bibFormat.setProperty(KoParagraphStyle::GeneratedDocument, QVariant::fromValue(bibDocument)); cursor.insertBlock(bibFormat); // We'll just try to find displayable elements and add them as paragraphs } else if (e.localName() == "index-body") { QTextCursor cursorFrame = bibDocument->rootFrame()->lastCursorPosition(); bool firstTime = true; KoXmlElement p; forEachElement(p, e) { // All elem will be "p" instead of the title, which is particular if (p.isNull() || p.namespaceURI() != KoXmlNS::text) continue; if (!firstTime) { // use empty formats to not inherit from the prev parag QTextBlockFormat bf; QTextCharFormat cf; cursorFrame.insertBlock(bf, cf); } firstTime = false; QTextBlock current = cursorFrame.block(); QTextBlockFormat blockFormat; if (p.localName() == "p") { loadParagraph(p, cursorFrame); } else if (p.localName() == "index-title") { loadBody(p, cursorFrame); } QTextCursor c(current); c.mergeBlockFormat(blockFormat); } }// index-body } } void KoTextLoader::startBody(int total) { d->bodyProgressTotal += total; } void KoTextLoader::processBody() { d->bodyProgressValue++; if (d->progressTime.elapsed() >= d->nextProgressReportMs) { // update based on elapsed time, don't saturate the queue d->nextProgressReportMs = d->progressTime.elapsed() + 333; // report 3 times per second Q_ASSERT(d->bodyProgressTotal > 0); const int percent = d->bodyProgressValue * 100 / d->bodyProgressTotal; emit sigProgress(percent); } } void KoTextLoader::endBody() { } diff --git a/libs/kotext/opendocument/KoTextLoader.h b/libs/kotext/opendocument/KoTextLoader.h index 88179610a7..b6a737a7a8 100644 --- a/libs/kotext/opendocument/KoTextLoader.h +++ b/libs/kotext/opendocument/KoTextLoader.h @@ -1,223 +1,223 @@ /* This file is part of the KDE project * Copyright (C) 2010 Thomas Zander * Copyright (C) 2001-2006 David Faure * Copyright (C) 2007 Sebastian Sauer * Copyright (C) 2007 Pierre Ducroquet * Copyright (C) 2007-2009 Thorsten Zachmann * Copyright (C) 2008 Girish Ramakrishnan * Copyright (C) 2009 KO GmbH * Copyright (C) 2009 Pierre Stirnweiss * Copyright (C) 2010 KO GmbH * Copyright (C) 2011 Pavol Korinek * Copyright (C) 2011 Lukáš Tvrdý * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef KOTEXTLOADER_H #define KOTEXTLOADER_H #include #include "kotext_export.h" #include class QTextCursor; class QTextTable; class QRect; class KoShapeLoadingContext; class KoShape; /** * The KoTextLoader loads is use to load text for one and only one textdocument or shape * to the scribe text-engine using QTextCursor objects. So if you have two shapes 2 different * KoTextLoader are used for that. Also if you have a frame with text inside a text a new * KoTextLoader is used. * * If you want to use the KoTextLoader for text that needs styles from styles.xml. e.g. * test shapes on master pages, you need to set KoOdfLoadingContext::setUseStylesAutoStyles( true ). * * Don't forget to unset it if you later want to load text that needs content.xml. */ class KOTEXT_EXPORT KoTextLoader : public QObject { Q_OBJECT public: /** * Normalizes the whitespaces in the string \p in according to the ODF ruleset * for stripping whitespaces and returns the result. If \p leadingSpace is * true a leading space is stripped too. * * This is different from QString::simplifyWhiteSpace() because that one removes * leading and trailing whitespace, but such whitespace is significant in ODF. * So we use this function to compress sequences of space characters into single * spaces. */ static QString normalizeWhitespace(const QString &in, bool leadingSpace); /** * Constructor. * Notice that despite this being a QObject there is no 'parent' available for * memory management here. * * @param context The context the KoTextLoader is called in */ explicit KoTextLoader(KoShapeLoadingContext &context, KoShape *shape = 0); /** * Destructor. */ ~KoTextLoader(); enum LoadBodyMode { LoadMode, PasteMode }; /** * Load the body from the \p element into the \p cursor . * * This method got called e.g. at the \a KoTextShapeData::loadOdf() method if a TextShape * instance likes to load an ODF element. * * @param element the element to start loading at * @param cursor the text cursor to insert the body after * @param pasteMode does special handling of section needed, in case we are pasting text */ void loadBody(const KoXmlElement &element, QTextCursor &cursor, LoadBodyMode pasteMode = LoadMode); Q_SIGNALS: /** * This signal is emitted during loading with a percentage within 1-100 range * \param percent the progress as a percentage */ void sigProgress(int percent); private: /** * Load the paragraph from the \p element into the \p cursor . */ void loadParagraph(const KoXmlElement &element, QTextCursor &cursor); /** * Load the heading from the \p element into the \p cursor . */ void loadHeading(const KoXmlElement &element, QTextCursor &cursor); /** * Load the list from the \p element into the \p cursor . */ void loadList(const KoXmlElement &element, QTextCursor &cursor); /** * Load a list-item into the cursor */ - void loadListItem(KoXmlElement &e, QTextCursor &cursor, int level); + void loadListItem(const KoXmlElement &e, QTextCursor &cursor, int level); /** * Load the section from the \p element into the \p cursor . */ void loadSection(const KoXmlElement &element, QTextCursor &cursor); /** * Load the text into the \p cursor . */ void loadText(const QString &text, QTextCursor &cursor, bool *stripLeadingSpace, bool isLastNode); /** * Load the span from the \p element into the \p cursor . */ void loadSpan(const KoXmlElement &element, QTextCursor &cursor, bool *leadingSpace); /** * Load the table from the \p element into the \p cursor. * * The table and its contents are placed in a new shape. */ void loadTable(const KoXmlElement &element, QTextCursor& cursor); /** * Loads a table column */ - void loadTableColumn(KoXmlElement &element, QTextTable *table, int &columns); + void loadTableColumn(const KoXmlElement &element, QTextTable *table, int &columns); /** * Loads a table-row into the cursor */ - void loadTableRow(KoXmlElement &element, QTextTable *table, QList &spanStore, QTextCursor &cursor, int &rows); + void loadTableRow(const KoXmlElement &element, QTextTable *table, QList &spanStore, QTextCursor &cursor, int &rows); /** * Loads a table-cell into the cursor */ - void loadTableCell(KoXmlElement &element, QTextTable *table, QList &spanStore, QTextCursor &cursor, int ¤tCell); + void loadTableCell(const KoXmlElement &element, QTextTable *table, QList &spanStore, QTextCursor &cursor, int ¤tCell); /** * Load a note \p element into the \p cursor. */ void loadNote(const KoXmlElement &element, QTextCursor& cursor); /** * Load a citation \p element into the \p cursor. */ void loadCite(const KoXmlElement &element, QTextCursor& cursor); /** * Load the shape element and assign hyperlink to it \p element into the \p cursor . */ void loadShapeWithHyperLink(const KoXmlElement &element, QTextCursor& cursor); /** * Load the shape element \p element into the \p cursor . */ KoShape *loadShape(const KoXmlElement &element, QTextCursor& cursor); /** * Load the table of content element \p element into the \p cursor . */ void loadTableOfContents(const KoXmlElement &element, QTextCursor& cursor); /** * Load the bibliography element \p element into the \p cursor . */ void loadBibliography(const KoXmlElement &element, QTextCursor& cursor); /** * This is called in loadBody before reading the body starts. */ void startBody(int total); /** * This is called in loadBody on each item that is read within the body. */ void processBody(); /** * This is called in loadBody once the body was read. */ void endBody(); /// \internal d-pointer class. class Private; /// \internal d-pointer instance. Private* const d; }; #endif diff --git a/libs/kotext/opendocument/KoTextWriter_p.cpp b/libs/kotext/opendocument/KoTextWriter_p.cpp index def4d68405..8bae9d0545 100644 --- a/libs/kotext/opendocument/KoTextWriter_p.cpp +++ b/libs/kotext/opendocument/KoTextWriter_p.cpp @@ -1,1141 +1,1141 @@ /* * Copyright (c) 2010 Boudewijn Rempt * Copyright (c) 2014 Denis Kuplyakov * * 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; either * version 2.1 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser 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 "KoTextWriter_p.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include // A convenience function to get a listId from a list-format static KoListStyle::ListIdType ListId(const QTextListFormat &format) { KoListStyle::ListIdType listId; if (sizeof(KoListStyle::ListIdType) == sizeof(uint)) listId = format.property(KoListStyle::ListId).toUInt(); else listId = format.property(KoListStyle::ListId).toULongLong(); return listId; } typedef QPair Attribute; KoTextWriter::Private::Private(KoShapeSavingContext &context) : rdfData(0) , sharedData(0) , styleManager(0) , document(0) , writer(0) , context(context) { currentPairedInlineObjectsStack = new QStack(); writer = &context.xmlWriter(); } void KoTextWriter::Private::writeBlocks(QTextDocument *document, int from, int to, QHash &listStyles, QTextTable *currentTable, QTextList *currentList) { pairedInlineObjectsStackStack.push(currentPairedInlineObjectsStack); currentPairedInlineObjectsStack = new QStack(); QTextBlock block = document->findBlock(from); // Here we are going to detect all sections that // are positioned entirely inside selection. // They will stay untouched, and others will be omitted. - // So we are using stack to detect them, by going though + // So we are using stack to detect them, by going through // the selection and finding open/close pairs. QSet entireWithinSectionNames; QStack sectionNamesStack; QTextCursor cur(document); cur.setPosition(from); while (to == -1 || cur.position() <= to) { if (cur.block().position() >= from) { // Begin of the block is inside selection. foreach (const KoSection *sec, KoSectionUtils::sectionStartings(cur.blockFormat())) { sectionNamesStack.push_back(sec->name()); } } if (to == -1 || cur.block().position() + cur.block().length() - 1 <= to) { // End of the block is inside selection. foreach (const KoSectionEnd *sec, KoSectionUtils::sectionEndings(cur.blockFormat())) { if (!sectionNamesStack.empty() && sectionNamesStack.top() == sec->name()) { sectionNamesStack.pop(); entireWithinSectionNames.insert(sec->name()); } } } if (!KoSectionUtils::getNextBlock(cur)) { break; } } while (block.isValid() && ((to == -1) || (block.position() <= to))) { QTextCursor cursor(block); int frameType = cursor.currentFrame()->format().intProperty(KoText::SubFrameType); if (frameType == KoText::AuxillaryFrameType) { break; // we've reached the "end" (end/footnotes saved by themselves) // note how NoteFrameType passes through here so the notes can // call writeBlocks to save their contents. } QTextBlockFormat format = block.blockFormat(); foreach (const KoSection *section, KoSectionUtils::sectionStartings(format)) { // We are writing in only sections, that are completely inside selection. if (entireWithinSectionNames.contains(section->name())) { section->saveOdf(context); } } if (format.hasProperty(KoParagraphStyle::HiddenByTable)) { block = block.next(); continue; } if (format.hasProperty(KoParagraphStyle::TableOfContentsData)) { saveTableOfContents(document, listStyles, block); block = block.next(); continue; } if (format.hasProperty(KoParagraphStyle::BibliographyData)) { saveBibliography(document, listStyles, block); block = block.next(); continue; } if (cursor.currentTable() && cursor.currentTable() != currentTable) { // Call the code to save the table.... saveTable(cursor.currentTable(), listStyles, from, to); // We skip to the end of the table. block = cursor.currentTable()->lastCursorPosition().block(); block = block.next(); continue; } if (cursor.currentList() && cursor.currentList() != currentList) { int previousBlockNumber = block.blockNumber(); block = saveList(block, listStyles, 1, currentTable); int blockNumberToProcess = block.blockNumber(); if (blockNumberToProcess != previousBlockNumber) continue; } saveParagraph(block, from, to); foreach (const KoSectionEnd *sectionEnd, KoSectionUtils::sectionEndings(format)) { // We are writing in only sections, that are completely inside selection. if (entireWithinSectionNames.contains(sectionEnd->name())) { sectionEnd->saveOdf(context); } } block = block.next(); } // while Q_ASSERT(!pairedInlineObjectsStackStack.isEmpty()); delete currentPairedInlineObjectsStack; currentPairedInlineObjectsStack = pairedInlineObjectsStackStack.pop(); } QHash KoTextWriter::Private::saveListStyles(QTextBlock block, int to) { QHash generatedLists; QHash listStyles; for (;block.isValid() && ((to == -1) || (block.position() < to)); block = block.next()) { QTextList *textList = block.textList(); if (!textList) continue; KoListStyle::ListIdType listId = ListId(textList->format()); if (KoList *list = KoTextDocument(document).list(listId)) { if (generatedLists.contains(list)) { if (!listStyles.contains(textList)) listStyles.insert(textList, generatedLists.value(list)); continue; } KoListStyle *listStyle = list->style(); if (listStyle && listStyle->isOulineStyle()) { continue; } bool automatic = listStyle->styleId() == 0; KoGenStyle style(automatic ? KoGenStyle::ListAutoStyle : KoGenStyle::ListStyle); if (automatic && context.isSet(KoShapeSavingContext::AutoStyleInStyleXml)) style.setAutoStyleInStylesDotXml(true); listStyle->saveOdf(style, context); QString generatedName = context.mainStyles().insert(style, listStyle->name(), listStyle->isNumberingStyle() ? KoGenStyles::AllowDuplicates : KoGenStyles::DontAddNumberToName); listStyles[textList] = generatedName; generatedLists.insert(list, generatedName); } else { if (listStyles.contains(textList)) continue; KoListLevelProperties llp = KoListLevelProperties::fromTextList(textList); KoGenStyle style(KoGenStyle::ListAutoStyle); if (context.isSet(KoShapeSavingContext::AutoStyleInStyleXml)) style.setAutoStyleInStylesDotXml(true); KoListStyle listStyle; listStyle.setLevelProperties(llp); if (listStyle.isOulineStyle()) { continue; } listStyle.saveOdf(style, context); QString generatedName = context.mainStyles().insert(style, listStyle.name()); listStyles[textList] = generatedName; } } return listStyles; } //---------------------------- PRIVATE ----------------------------------------------------------- void KoTextWriter::Private::openTagRegion(ElementType elementType, TagInformation& tagInformation) { //kDebug(30015) << "tag:" << tagInformation.name() << openedTagStack.size(); if (tagInformation.name()) { writer->startElement(tagInformation.name(), elementType != ParagraphOrHeader); foreach (const Attribute &attribute, tagInformation.attributes()) { writer->addAttribute(attribute.first.toLocal8Bit(), attribute.second); } } openedTagStack.push(tagInformation.name()); //kDebug(30015) << "stack" << openedTagStack.size(); } void KoTextWriter::Private::closeTagRegion() { // the tag needs to be closed even if there is no change tracking //kDebug(30015) << "stack" << openedTagStack.size(); const char *tagName = openedTagStack.pop(); //kDebug(30015) << "tag:" << tagName << openedTagStack.size(); if (tagName) { writer->endElement(); // close the tag } } QString KoTextWriter::Private::saveParagraphStyle(const QTextBlock &block) { return KoTextWriter::saveParagraphStyle(block, styleManager, context); } QString KoTextWriter::Private::saveParagraphStyle(const QTextBlockFormat &blockFormat, const QTextCharFormat &charFormat) { return KoTextWriter::saveParagraphStyle(blockFormat, charFormat, styleManager, context); } QString KoTextWriter::Private::saveCharacterStyle(const QTextCharFormat &charFormat, const QTextCharFormat &blockCharFormat) { KoCharacterStyle *defaultCharStyle = styleManager->defaultCharacterStyle(); KoCharacterStyle *originalCharStyle = styleManager->characterStyle(charFormat.intProperty(KoCharacterStyle::StyleId)); if (!originalCharStyle) originalCharStyle = defaultCharStyle; QString generatedName; QString displayName = originalCharStyle->name(); QString internalName = QString(QUrl::toPercentEncoding(displayName, "", " ")).replace('%', '_'); KoCharacterStyle *autoStyle = originalCharStyle->autoStyle(charFormat, blockCharFormat); if (autoStyle->isEmpty()) { // This is the real, unmodified character style. if (originalCharStyle != defaultCharStyle) { KoGenStyle style(KoGenStyle::TextStyle, "text"); originalCharStyle->saveOdf(style); generatedName = context.mainStyles().insert(style, internalName, KoGenStyles::DontAddNumberToName); } } else { // There are manual changes... We'll have to store them then KoGenStyle style(KoGenStyle::TextAutoStyle, "text", originalCharStyle != defaultCharStyle ? internalName : "" /*parent*/); if (context.isSet(KoShapeSavingContext::AutoStyleInStyleXml)) style.setAutoStyleInStylesDotXml(true); autoStyle->saveOdf(style); generatedName = context.mainStyles().insert(style, "T"); } delete autoStyle; return generatedName; } QString KoTextWriter::Private::saveTableStyle(const QTextTable& table) { KoTableStyle *originalTableStyle = styleManager->tableStyle(table.format().intProperty(KoTableStyle::StyleId)); QString generatedName; QString internalName; if (originalTableStyle) { internalName = QString(QUrl::toPercentEncoding(originalTableStyle->name(), "", " ")).replace('%', '_'); } KoTableStyle tableStyle(table.format()); if ((originalTableStyle) && (*originalTableStyle == tableStyle)) { // This is the real unmodified table style KoGenStyle style(KoGenStyle::TableStyle, "table"); originalTableStyle->saveOdf(style); generatedName = context.mainStyles().insert(style, internalName, KoGenStyles::DontAddNumberToName); } else { // There are manual changes... We'll have to store them then KoGenStyle style(KoGenStyle::TableAutoStyle, "table", internalName); if (context.isSet(KoShapeSavingContext::AutoStyleInStyleXml)) style.setAutoStyleInStylesDotXml(true); if (originalTableStyle) tableStyle.removeDuplicates(*originalTableStyle); if (!tableStyle.isEmpty()) { tableStyle.saveOdf(style); generatedName = context.mainStyles().insert(style, "Table"); } } return generatedName; } QString KoTextWriter::Private::saveTableColumnStyle(const KoTableColumnStyle& tableColumnStyle, int columnNumber, const QString& tableStyleName) { // 26*26 columns should be enough for everyone QString columnName = QChar('A' + int(columnNumber % 26)); if (columnNumber > 25) columnName.prepend(QChar('A' + int(columnNumber/26))); QString generatedName = tableStyleName + '.' + columnName; KoGenStyle style(KoGenStyle::TableColumnAutoStyle, "table-column"); if (context.isSet(KoShapeSavingContext::AutoStyleInStyleXml)) style.setAutoStyleInStylesDotXml(true); tableColumnStyle.saveOdf(style); generatedName = context.mainStyles().insert(style, generatedName, KoGenStyles::DontAddNumberToName); return generatedName; } QString KoTextWriter::Private::saveTableRowStyle(const KoTableRowStyle& tableRowStyle, int rowNumber, const QString& tableStyleName) { QString generatedName = tableStyleName + '.' + QString::number(rowNumber + 1); KoGenStyle style(KoGenStyle::TableRowAutoStyle, "table-row"); if (context.isSet(KoShapeSavingContext::AutoStyleInStyleXml)) style.setAutoStyleInStylesDotXml(true); tableRowStyle.saveOdf(style); generatedName = context.mainStyles().insert(style, generatedName, KoGenStyles::DontAddNumberToName); return generatedName; } QString KoTextWriter::Private::saveTableCellStyle(const QTextTableCellFormat& cellFormat, int columnNumber, const QString& tableStyleName) { // 26*26 columns should be enough for everyone QString columnName = QChar('A' + int(columnNumber % 26)); if (columnNumber > 25) columnName.prepend(QChar('A' + int(columnNumber/26))); QString generatedName = tableStyleName + '.' + columnName; KoGenStyle style(KoGenStyle::TableCellAutoStyle, "table-cell"); if (context.isSet(KoShapeSavingContext::AutoStyleInStyleXml)) style.setAutoStyleInStylesDotXml(true); KoTableCellStyle cellStyle(cellFormat); cellStyle.saveOdf(style, context); generatedName = context.mainStyles().insert(style, generatedName); return generatedName; } void KoTextWriter::Private::saveInlineRdf(KoTextInlineRdf* rdf, TagInformation* tagInfos) { QBuffer rdfXmlData; KoXmlWriter rdfXmlWriter(&rdfXmlData); rdfXmlWriter.startDocument("rdf"); rdfXmlWriter.startElement("rdf"); rdf->saveOdf(context, &rdfXmlWriter); rdfXmlWriter.endElement(); rdfXmlWriter.endDocument(); KoXmlDocument xmlReader; xmlReader.setContent(rdfXmlData.data(), true); KoXmlElement mainElement = xmlReader.documentElement(); foreach (const Attribute &attributeNameNS, mainElement.attributeFullNames()) { QString attributeName = QString("%1:%2").arg(KoXmlNS::nsURI2NS(attributeNameNS.first)) .arg(attributeNameNS.second); if (attributeName.startsWith(':')) attributeName.prepend("xml"); tagInfos->addAttribute(attributeName, mainElement.attribute(attributeNameNS.second)); } } /* Note on saving textranges: Start and end tags of textranges can appear on cursor positions in a text block. in front of the first text element, between the elements, or behind the last. A textblock is composed of no, one or many text fragments. If there is no fragment at all, the only possible cursor position is 0 (relative to the begin of the block). Example: ([] marks a block, {} a fragment) Three blocks, first with text fragments {AB} {C}, second empty, last with {DEF}. Possible positions are: [|{A|B}|{C}|] [|] [|{D|E|F}|] Start tags are ideally written in front of the content they are tagging, not behind the previous content. That way tags which are at the very begin of the complete document do not need special handling. End tags are ideally written directly behind the content, and not in front of the next content. That way end tags which are at the end of the complete document do not need special handling. Next there is the case of start tags which are at the final position of a text block: the content they belong to includes the block end/border, so they need to be written at the place of the last position. Then there is the case of end tags at the first position of a text block: the content they belong to includes the block start/border, so they need to be written at the place of the first position. Example: (< marks a start tag, > marks an end tag) [|>{}|{}|<] [|><] [|>{||}|<] */ void KoTextWriter::Private::saveParagraph(const QTextBlock &block, int from, int to) { QTextCursor cursor(block); QTextBlockFormat blockFormat = block.blockFormat(); const int outlineLevel = blockFormat.intProperty(KoParagraphStyle::OutlineLevel); TagInformation blockTagInformation; if (outlineLevel > 0) { blockTagInformation.setTagName("text:h"); blockTagInformation.addAttribute("text:outline-level", outlineLevel); if (blockFormat.boolProperty(KoParagraphStyle::IsListHeader) || blockFormat.boolProperty(KoParagraphStyle::UnnumberedListItem)) { blockTagInformation.addAttribute("text:is-list-header", "true"); } } else { blockTagInformation.setTagName("text:p"); } openTagRegion(KoTextWriter::Private::ParagraphOrHeader, blockTagInformation); QString styleName = saveParagraphStyle(block); if (!styleName.isEmpty()) writer->addAttribute("text:style-name", styleName); KoElementReference xmlid; xmlid.invalidate(); QTextBlock currentBlock = block; KoTextBlockData blockData(currentBlock); if (blockData.saveXmlID()) { xmlid = context.xmlid(&blockData); xmlid.saveOdf(writer, KoElementReference::TextId); } // Write the fragments and their formats QTextCharFormat blockCharFormat = cursor.blockCharFormat(); QTextCharFormat previousCharFormat; QTextBlock::iterator it; if (KoTextInlineRdf* inlineRdf = KoTextInlineRdf::tryToGetInlineRdf(blockCharFormat)) { // Write xml:id here for Rdf kDebug(30015) << "have inline rdf xmlid:" << inlineRdf->xmlId() << "active xml id" << xmlid.toString(); inlineRdf->saveOdf(context, writer, xmlid); } const KoTextRangeManager *textRangeManager = KoTextDocument(block.document()).textRangeManager(); if (textRangeManager) { // write tags for ranges which end at the first position of the block const QHash endingTextRangesAtStart = textRangeManager->textRangesChangingWithin(block.document(), block.position(), block.position(), globalFrom, globalTo); foreach (const KoTextRange *range, endingTextRangesAtStart) { range->saveOdf(context, block.position(), KoTextRange::EndTag); } } QString previousFragmentLink; // stores the end position of the last fragment, is position of the block without any fragment at all int lastEndPosition = block.position(); for (it = block.begin(); !(it.atEnd()); ++it) { QTextFragment currentFragment = it.fragment(); const int fragmentStart = currentFragment.position(); const int fragmentEnd = fragmentStart + currentFragment.length(); if (to != -1 && fragmentStart >= to) break; if (currentFragment.isValid()) { QTextCharFormat charFormat = currentFragment.charFormat(); if ((!previousFragmentLink.isEmpty()) && (charFormat.anchorHref() != previousFragmentLink || !charFormat.isAnchor())) { // Close the current text:a closeTagRegion(); previousFragmentLink.clear(); } if (charFormat.isAnchor() && charFormat.anchorHref() != previousFragmentLink) { // Open a text:a previousFragmentLink = charFormat.anchorHref(); TagInformation linkTagInformation; if (charFormat.intProperty(KoCharacterStyle::AnchorType) == KoCharacterStyle::Bookmark) { linkTagInformation.setTagName("text:bookmark-ref"); QString href = previousFragmentLink.right(previousFragmentLink.size()-1); linkTagInformation.addAttribute("text:ref-name", href); //linkTagInformation.addAttribute("text:ref-format", add the style of the ref here); } else { linkTagInformation.setTagName("text:a"); linkTagInformation.addAttribute("xlink:type", "simple"); linkTagInformation.addAttribute("xlink:href", charFormat.anchorHref()); } if (KoTextInlineRdf* inlineRdf = KoTextInlineRdf::tryToGetInlineRdf(charFormat)) { // Write xml:id here for Rdf kDebug(30015) << "have inline rdf xmlid:" << inlineRdf->xmlId(); saveInlineRdf(inlineRdf, &linkTagInformation); } openTagRegion(KoTextWriter::Private::Span, linkTagInformation); } KoInlineTextObjectManager *textObjectManager = KoTextDocument(document).inlineTextObjectManager(); KoInlineObject *inlineObject = textObjectManager ? textObjectManager->inlineTextObject(charFormat) : 0; // If we are in an inline object if (currentFragment.length() == 1 && inlineObject && currentFragment.text()[0].unicode() == QChar::ObjectReplacementCharacter) { bool saveInlineObject = true; if (KoTextMeta* z = dynamic_cast(inlineObject)) { if (z->position() < from) { // // This starts before the selection, default // to not saving it with special cases to allow saving // saveInlineObject = false; if (z->type() == KoTextMeta::StartBookmark) { if (z->endBookmark()->position() > from) { // // They have selected something starting after the // opening but before the // saveInlineObject = true; } } } } // get all text ranges which start before this inline object // or end directly after it (+1 to last position for that) const QHash textRanges = textRangeManager ? textRangeManager->textRangesChangingWithin(block.document(), currentFragment.position(), currentFragment.position()+1, globalFrom, (globalTo==-1)?-1:globalTo+1) : QHash(); // get all text ranges which start before this const QList textRangesBefore = textRanges.values(currentFragment.position()); // write tags for ranges which start before this content or at positioned at it foreach (const KoTextRange *range, textRangesBefore) { range->saveOdf(context, currentFragment.position(), KoTextRange::StartTag); } bool saveSpan = dynamic_cast(inlineObject) != 0; if (saveSpan) { QString styleName = saveCharacterStyle(charFormat, blockCharFormat); if (!styleName.isEmpty()) { writer->startElement("text:span", false); writer->addAttribute("text:style-name", styleName); } else { saveSpan = false; } } if (saveInlineObject) { inlineObject->saveOdf(context); } if (saveSpan) { writer->endElement(); } // write tags for ranges which end after this inline object const QList textRangesAfter = textRanges.values(currentFragment.position()+1); foreach (const KoTextRange *range, textRangesAfter) { range->saveOdf(context, currentFragment.position()+1, KoTextRange::EndTag); } // // Track the end marker for matched pairs so we produce valid // ODF // if (KoTextMeta* z = dynamic_cast(inlineObject)) { kDebug(30015) << "found kometa, type:" << z->type(); if (z->type() == KoTextMeta::StartBookmark) currentPairedInlineObjectsStack->push(z->endBookmark()); if (z->type() == KoTextMeta::EndBookmark && !currentPairedInlineObjectsStack->isEmpty()) currentPairedInlineObjectsStack->pop(); }/* else if (KoBookmark* z = dynamic_cast(inlineObject)) { if (z->type() == KoBookmark::StartBookmark) currentPairedInlineObjectsStack->push(z->endBookmark()); if (z->type() == KoBookmark::EndBookmark && !currentPairedInlineObjectsStack->isEmpty()) currentPairedInlineObjectsStack->pop(); }*/ } else { // Normal block, easier to handle QString styleName = saveCharacterStyle(charFormat, blockCharFormat); TagInformation fragmentTagInformation; if (!styleName.isEmpty() /*&& !identical*/) { fragmentTagInformation.setTagName("text:span"); fragmentTagInformation.addAttribute("text:style-name", styleName); } openTagRegion(KoTextWriter::Private::Span, fragmentTagInformation); QString text = currentFragment.text(); int spanFrom = fragmentStart >= from ? fragmentStart : from; int spanTo = to == -1 ? fragmentEnd : (fragmentEnd > to ? to : fragmentEnd); // get all text ranges which change within this span // or end directly after it (+1 to last position to include those) const QHash textRanges = textRangeManager ? textRangeManager->textRangesChangingWithin(block.document(), spanFrom, spanTo, globalFrom, (globalTo==-1)?-1:globalTo+1) : QHash(); // avoid mid, if possible if (spanFrom != fragmentStart || spanTo != fragmentEnd || !textRanges.isEmpty()) { if (textRanges.isEmpty()) { writer->addTextSpan(text.mid(spanFrom - fragmentStart, spanTo - spanFrom)); } else { // split the fragment into subspans at the points of range starts/ends QList subSpanTos = textRanges.uniqueKeys(); qSort(subSpanTos); // ensure last subSpanTo to be at the end if (subSpanTos.last() != spanTo) { subSpanTos.append(spanTo); } // spanFrom should not need to be included if (subSpanTos.first() == spanFrom) { subSpanTos.removeOne(spanFrom); } int subSpanFrom = spanFrom; // for all subspans foreach (int subSpanTo, subSpanTos) { // write tags for text ranges which start before this subspan or are positioned at it const QList textRangesStartingBefore = textRanges.values(subSpanFrom); foreach (const KoTextRange *range, textRangesStartingBefore) { range->saveOdf(context, subSpanFrom, KoTextRange::StartTag); } // write subspan content writer->addTextSpan(text.mid(subSpanFrom - fragmentStart, subSpanTo - subSpanFrom)); // write tags for text ranges which end behind this subspan const QList textRangesEndingBehind = textRanges.values(subSpanTo); foreach (const KoTextRange *range, textRangesEndingBehind) { range->saveOdf(context, subSpanTo, KoTextRange::EndTag); } subSpanFrom = subSpanTo; } } } else { writer->addTextSpan(text); } closeTagRegion(); } // if (inlineObject) previousCharFormat = charFormat; lastEndPosition = fragmentEnd; } } if (!previousFragmentLink.isEmpty()) { writer->endElement(); } if (it.atEnd() && textRangeManager && ((to == -1) || (lastEndPosition <= to))) { // write tags for ranges which start at the last position of the block, // i.e. at the position after the last (text) fragment const QHash startingTextRangesAtEnd = textRangeManager->textRangesChangingWithin(block.document(), lastEndPosition, lastEndPosition, globalFrom, globalTo); foreach (const KoTextRange *range, startingTextRangesAtEnd) { range->saveOdf(context, lastEndPosition, KoTextRange::StartTag); } } QString text = block.text(); if (text.length() == 0 || text.at(text.length()-1) == QChar(0x2028)) { if (block.blockFormat().hasProperty(KoParagraphStyle::EndCharStyle)) { QVariant v = block.blockFormat().property(KoParagraphStyle::EndCharStyle); QSharedPointer endCharStyle = v.value< QSharedPointer >(); if (!endCharStyle.isNull()) { QTextCharFormat charFormat; endCharStyle->applyStyle(charFormat); QString styleName = saveCharacterStyle(charFormat, blockCharFormat); if (!styleName.isEmpty()) { writer->startElement("text:span", false); writer->addAttribute("text:style-name", styleName); writer->endElement(); } } } } if (to !=-1 && to < block.position() + block.length()) { foreach (KoInlineObject* inlineObject, *currentPairedInlineObjectsStack) { inlineObject->saveOdf(context); } } closeTagRegion(); } void KoTextWriter::Private::saveTable(QTextTable *table, QHash &listStyles, int from, int to) { KoTableColumnAndRowStyleManager tcarManager = KoTableColumnAndRowStyleManager::getManager(table); int numberHeadingRows = table->format().property(KoTableStyle::NumberHeadingRows).toInt(); TagInformation tableTagInformation; QString tableStyleName = saveTableStyle(*table); tableTagInformation.setTagName("table:table"); tableTagInformation.addAttribute("table:style-name", tableStyleName); if (table->format().boolProperty(KoTableStyle::TableIsProtected)) { tableTagInformation.addAttribute("table:protected", "true"); } if (table->format().hasProperty(KoTableStyle::TableTemplate)) { tableTagInformation.addAttribute("table:template-name", sharedData->styleName(table->format().intProperty(KoTableStyle::TableTemplate))); } if (table->format().boolProperty(KoTableStyle::UseBandingColumnStyles)) { tableTagInformation.addAttribute("table:use-banding-columns-styles", "true"); } if (table->format().boolProperty(KoTableStyle::UseBandingRowStyles)) { tableTagInformation.addAttribute("table:use-banding-rows-styles", "true"); } if (table->format().boolProperty(KoTableStyle::UseFirstColumnStyles)) { tableTagInformation.addAttribute("table:use-first-column-styles", "true"); } if (table->format().boolProperty(KoTableStyle::UseFirstRowStyles)) { tableTagInformation.addAttribute("table:use-first-row-styles", "true"); } if (table->format().boolProperty(KoTableStyle::UseLastColumnStyles)) { tableTagInformation.addAttribute("table:use-last-column-styles", "true"); } if (table->format().boolProperty(KoTableStyle::UseLastRowStyles)) { tableTagInformation.addAttribute("table:use-last-row-styles", "true"); } int firstColumn = 0; int lastColumn = table->columns() -1; int firstRow = 0; int lastRow = table->rows() -1; if (to != -1 && from >= table->firstPosition() && to <= table->lastPosition()) { firstColumn = table->cellAt(from).column(); firstRow = table->cellAt(from).row(); lastColumn = table->cellAt(to).column(); lastRow = table->cellAt(to).row(); if (firstColumn == lastColumn && firstRow == lastRow && from >= table->firstPosition()) { // we only selected something inside a single cell so don't save a table writeBlocks(table->document(), from, to, listStyles, table); return; } } openTagRegion(KoTextWriter::Private::Table, tableTagInformation); for (int c = firstColumn ; c <= lastColumn; c++) { KoTableColumnStyle columnStyle = tcarManager.columnStyle(c); int repetition = 0; for (; repetition <= (lastColumn - c) ; repetition++) { if (columnStyle != tcarManager.columnStyle(c + repetition + 1)) break; } TagInformation tableColumnInformation; tableColumnInformation.setTagName("table:table-column"); QString columnStyleName = saveTableColumnStyle(columnStyle, c, tableStyleName); tableColumnInformation.addAttribute("table:style-name", columnStyleName); if (repetition > 0) tableColumnInformation.addAttribute("table:number-columns-repeated", repetition + 1); openTagRegion(KoTextWriter::Private::TableColumn, tableColumnInformation); closeTagRegion(); c += repetition; } if (numberHeadingRows) writer->startElement("table:table-header-rows"); // TODO make work for copying part of table that has header rows - copy header rows additionally or not ? for (int r = firstRow; r <= lastRow; r++) { TagInformation tableRowInformation; tableRowInformation.setTagName("table:table-row"); KoTableRowStyle rowStyle = tcarManager.rowStyle(r); if (!rowStyle.isEmpty()) { QString rowStyleName = saveTableRowStyle(rowStyle, r, tableStyleName); tableRowInformation.addAttribute("table:style-name", rowStyleName); } openTagRegion(KoTextWriter::Private::TableRow, tableRowInformation); for (int c = firstColumn; c <= lastColumn; c++) { QTextTableCell cell = table->cellAt(r, c); TagInformation tableCellInformation; if ((cell.row() == r) && (cell.column() == c)) { tableCellInformation.setTagName("table:table-cell"); if (cell.rowSpan() > 1) tableCellInformation.addAttribute("table:number-rows-spanned", cell.rowSpan()); if (cell.columnSpan() > 1) tableCellInformation.addAttribute("table:number-columns-spanned", cell.columnSpan()); if (cell.format().boolProperty(KoTableCellStyle::CellIsProtected)) { tableCellInformation.addAttribute("table:protected", "true"); } // Save the Rdf for the table cell QTextTableCellFormat cellFormat = cell.format().toTableCellFormat(); QVariant v = cellFormat.property(KoTableCellStyle::InlineRdf); if (KoTextInlineRdf* inlineRdf = v.value()) { inlineRdf->saveOdf(context, writer); } QString cellStyleName = saveTableCellStyle(cellFormat, c, tableStyleName); tableCellInformation.addAttribute("table:style-name", cellStyleName); openTagRegion(KoTextWriter::Private::TableCell, tableCellInformation); writeBlocks(table->document(), cell.firstPosition(), cell.lastPosition(), listStyles, table); } else { tableCellInformation.setTagName("table:covered-table-cell"); if (cell.format().boolProperty(KoTableCellStyle::CellIsProtected)) { tableCellInformation.addAttribute("table:protected", "true"); } openTagRegion(KoTextWriter::Private::TableCell, tableCellInformation); } closeTagRegion(); } closeTagRegion(); if (r + 1 == numberHeadingRows) { writer->endElement(); // table:table-header-rows writer->startElement("table:table-rows"); } } if (numberHeadingRows) writer->endElement(); // table:table-rows closeTagRegion(); } void KoTextWriter::Private::saveTableOfContents(QTextDocument *document, QHash &listStyles, QTextBlock toc) { Q_UNUSED(document); writer->startElement("text:table-of-content"); KoTableOfContentsGeneratorInfo *info = toc.blockFormat().property(KoParagraphStyle::TableOfContentsData).value(); QTextDocument *tocDocument = toc.blockFormat().property(KoParagraphStyle::GeneratedDocument).value(); if (!info->m_styleName.isNull()) { writer->addAttribute("text:style-name",info->m_styleName); } writer->addAttribute("text:name",info->m_name); info->saveOdf(writer); writer->startElement("text:index-body"); // write the title (one p block) QTextCursor localBlock = tocDocument->rootFrame()->firstCursorPosition(); localBlock.movePosition(QTextCursor::NextBlock); int endTitle = localBlock.position(); writer->startElement("text:index-title"); writer->addAttribute("text:name", QString("%1_Head").arg(info->m_name)); writeBlocks(tocDocument, 0, endTitle, listStyles); writer->endElement(); // text:index-title writeBlocks(tocDocument, endTitle, -1, listStyles); writer->endElement(); // table:index-body writer->endElement(); // table:table-of-content } void KoTextWriter::Private::saveBibliography(QTextDocument *document, QHash &listStyles, QTextBlock bib) { Q_UNUSED(document); writer->startElement("text:bibliography"); KoBibliographyInfo *info = bib.blockFormat().property(KoParagraphStyle::BibliographyData).value(); QTextDocument *bibDocument = bib.blockFormat().property(KoParagraphStyle::GeneratedDocument).value(); if (!info->m_styleName.isNull()) { writer->addAttribute("text:style-name",info->m_styleName); } writer->addAttribute("text:name",info->m_name); info->saveOdf(writer); writer->startElement("text:index-body"); // write the title (one p block) QTextCursor localBlock = bibDocument->rootFrame()->firstCursorPosition(); localBlock.movePosition(QTextCursor::NextBlock); int endTitle = localBlock.position(); writer->startElement("text:index-title"); writeBlocks(bibDocument, 0, endTitle, listStyles); writer->endElement(); // text:index-title writeBlocks(bibDocument, endTitle, -1, listStyles); writer->endElement(); // table:index-body writer->endElement(); // table:bibliography } QTextBlock& KoTextWriter::Private::saveList(QTextBlock &block, QHash &listStyles, int level, QTextTable *currentTable) { QTextList *textList, *topLevelTextList; topLevelTextList = textList = block.textList(); int headingLevel = 0, numberedParagraphLevel = 0; QTextBlockFormat blockFormat = block.blockFormat(); headingLevel = blockFormat.intProperty(KoParagraphStyle::OutlineLevel); numberedParagraphLevel = blockFormat.intProperty(KoParagraphStyle::ListLevel); KoTextDocument textDocument(block.document()); KoList *list = textDocument.list(block); int topListLevel = KoList::level(block); bool listStarted = false; if (!headingLevel && !numberedParagraphLevel) { listStarted = true; TagInformation listTagInformation; listTagInformation.setTagName("text:list"); listTagInformation.addAttribute("text:style-name", listStyles[textList]); if (list && listXmlIds.contains(list->listContinuedFrom())) { listTagInformation.addAttribute("text:continue-list", listXmlIds.value(list->listContinuedFrom())); } QString listXmlId = QString("list-%1").arg(createXmlId()); listTagInformation.addAttribute("xml:id", listXmlId); if (! listXmlIds.contains(list)) { listXmlIds.insert(list, listXmlId); } openTagRegion(KoTextWriter::Private::List, listTagInformation); } if (!headingLevel) { do { if (numberedParagraphLevel) { TagInformation paraTagInformation; paraTagInformation.setTagName("text:numbered-paragraph"); paraTagInformation.addAttribute("text:level", numberedParagraphLevel); paraTagInformation.addAttribute("text:style-name", listStyles.value(textList)); QString listId = numberedParagraphListIds.value(list, QString("list-%1").arg(createXmlId())); numberedParagraphListIds.insert(list, listId); paraTagInformation.addAttribute("text:list-id", listId); openTagRegion(KoTextWriter::Private::NumberedParagraph, paraTagInformation); writeBlocks(textDocument.document(), block.position(), block.position() + block.length() - 1, listStyles, currentTable, textList); closeTagRegion(); } else { const bool listHeader = blockFormat.boolProperty(KoParagraphStyle::IsListHeader)|| blockFormat.boolProperty(KoParagraphStyle::UnnumberedListItem); TagInformation listItemTagInformation; listItemTagInformation.setTagName(listHeader ? "text:list-header" : "text:list-item"); if (block.blockFormat().hasProperty(KoParagraphStyle::ListStartValue)) { int startValue = block.blockFormat().intProperty(KoParagraphStyle::ListStartValue); listItemTagInformation.addAttribute("text:start-value", startValue); } if (textList == topLevelTextList) { openTagRegion(KoTextWriter::Private::ListItem, listItemTagInformation); } else { // This is a sub-list. So check for a list-change openTagRegion(KoTextWriter::Private::List, listItemTagInformation); } if (KoListStyle::isNumberingStyle(textList->format().style())) { KoTextBlockData blockData(block); writer->startElement("text:number", false); writer->addTextSpan(blockData.counterText()); writer->endElement(); } if (topListLevel == level && textList == topLevelTextList) { writeBlocks(textDocument.document(), block.position(), block.position() + block.length() - 1, listStyles, currentTable, textList); // we are generating a text:list-item. Look forward and generate unnumbered list items. while (true) { QTextBlock nextBlock = block.next(); if (!nextBlock.textList() || !nextBlock.blockFormat().boolProperty(KoParagraphStyle::UnnumberedListItem)) break; block = nextBlock; saveParagraph(block, block.position(), block.position() + block.length() - 1); } } else { //This is a sub-list while (KoList::level(block) >= (level + 1) && !(headingLevel || numberedParagraphLevel)) { block = saveList(block, listStyles, level + 1, currentTable); blockFormat = block.blockFormat(); headingLevel = blockFormat.intProperty(KoParagraphStyle::OutlineLevel); numberedParagraphLevel = blockFormat.intProperty(KoParagraphStyle::ListLevel); } //saveList will return a block one-past the last block of the list. //Since we are doing a block.next() below, we need to go one back. block = block.previous(); } closeTagRegion(); } block = block.next(); blockFormat = block.blockFormat(); headingLevel = blockFormat.intProperty(KoParagraphStyle::OutlineLevel); numberedParagraphLevel = blockFormat.intProperty(KoParagraphStyle::ListLevel); textList = block.textList(); } while ((textDocument.list(block) == list) && (KoList::level(block) >= topListLevel)); } if (listStarted) { closeTagRegion(); } return block; } void KoTextWriter::Private::addNameSpaceDefinitions(QString &generatedXmlString) { //Generate the name-space definitions so that it can be parsed. Like what is office:text, office:delta etc QString nameSpaceDefinitions; QTextStream nameSpacesStream(&nameSpaceDefinitions); nameSpacesStream << ""; generatedXmlString.prepend(nameSpaceDefinitions); generatedXmlString.append(""); } void KoTextWriter::Private::writeAttributes(QTextStream &outputXmlStream, KoXmlElement &element) { QList > attributes = element.attributeFullNames(); foreach (const Attribute &attributeNamePair, attributes) { if (attributeNamePair.first == KoXmlNS::text) { outputXmlStream << " text:" << attributeNamePair.second << "="; outputXmlStream << "\"" << element.attributeNS(KoXmlNS::text, attributeNamePair.second) << "\""; } else { //To Be Added when needed } } } void KoTextWriter::Private::writeNode(QTextStream &outputXmlStream, KoXmlNode &node, bool writeOnlyChildren) { if (node.isText()) { outputXmlStream << node.toText().data(); } else if (node.isElement()) { KoXmlElement element = node.toElement(); if ((element.localName() == "removed-content") && !element.childNodesCount()) { return; } if (!writeOnlyChildren) { outputXmlStream << "<" << element.prefix() << ":" << element.localName(); writeAttributes(outputXmlStream,element); outputXmlStream << ">"; } for (KoXmlNode node = element.firstChild(); !node.isNull(); node = node.nextSibling()) { writeNode(outputXmlStream, node); } if (!writeOnlyChildren) { outputXmlStream << ""; } } } QString KoTextWriter::Private::createXmlId() { QString uuid = QUuid::createUuid().toString(); uuid.remove('{'); uuid.remove('}'); return uuid; } diff --git a/libs/kotext/tests/CMakeLists.txt b/libs/kotext/tests/CMakeLists.txt index 4bfd7fb1ad..30a32aaa75 100644 --- a/libs/kotext/tests/CMakeLists.txt +++ b/libs/kotext/tests/CMakeLists.txt @@ -1,43 +1,37 @@ set( EXECUTABLE_OUTPUT_PATH ${CMAKE_CURRENT_BINARY_DIR} ) include_directories( ${KOTEXT_INCLUDES} ) if(MSVC OR (WIN32 AND "${CMAKE_CXX_COMPILER_ID}" STREQUAL "Intel")) # avoid "cannot open file 'LIBC.lib'" error set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} /NODEFAULTLIB:LIBC.LIB") endif() ########### next target ############### set(TestBorder_test_SRCS TestBorder.cpp) kde4_add_unit_test(TestBorder TESTNAME libs-kotext-TestBorder ${TestBorder_test_SRCS}) target_link_libraries(TestBorder kotext Qt5::Test) ########### next target ############### set(TestKoTableColumnAndRowStyleManager_test_SRCS TestKoTableColumnAndRowStyleManager.cpp) kde4_add_unit_test(TestKoTableColumnAndRowStyleManager TESTNAME libs-kotext-KoTableColumnAndRowStyleManager ${TestKoTableColumnAndRowStyleManager_test_SRCS}) target_link_libraries(TestKoTableColumnAndRowStyleManager kotext Qt5::Test) ########### next target ############### -set(TestSection_test_SRCS TestSection.cpp) -kde4_add_unit_test(TestSection TESTNAME libs-kotext-TestSection ${TestSection_test_SRCS}) -target_link_libraries(TestSection kotext Qt5::Test) - -########### next target ############### - set(TestKoTextEditor_test_SRCS TestKoTextEditor.cpp) kde4_add_unit_test(TestKoTextEditor TESTNAME libs-kotext-TestKoTextEditor ${TestKoTextEditor_test_SRCS}) target_link_libraries(TestKoTextEditor kotext Qt5::Test) ########### next target ############### set(TestKoBookmarkManager_test_SRCS TestKoBookmarkManager.cpp) kde4_add_unit_test(TestKoBookmarkManager TESTNAME libs-kotext-TestKoBookmarkManager ${TestKoBookmarkManager_test_SRCS}) target_link_libraries(TestKoBookmarkManager kotext Qt5::Test) ########### next target ############### set(TestKoInlineTextObjectManager_test_SRCS TestKoInlineTextObjectManager.cpp) kde4_add_unit_test(TestKoInlineTextObjectManager TESTNAME libs-kotext-TestKoInlineTextObjectManager ${TestKoInlineTextObjectManager_test_SRCS}) target_link_libraries(TestKoInlineTextObjectManager kotext Qt5::Test) diff --git a/libs/kotext/tests/TestDeleteSectionHandling_data.cpp b/libs/kotext/tests/TestDeleteSectionHandling_data.cpp new file mode 100644 index 0000000000..c096b3d1bd --- /dev/null +++ b/libs/kotext/tests/TestDeleteSectionHandling_data.cpp @@ -0,0 +1,466 @@ +/* This file is part of the KDE project + * + * Copyright (c) 2014-2015 Denis Kuplyakov + * + * 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. + */ + +void TestKoTextEditor::testDeleteSectionHandling_data() +{ + QTest::addColumn("selectionStart"); + QTest::addColumn("selectionEnd"); + QTest::addColumn("neededBlockCount"); + QTest::addColumn< QVector< QVector > >("needStartings"); + QTest::addColumn< QVector< QVector > >("needEndings"); + + QTest::newRow("Simple deletion, no effect to sections.") << 1 << 2 << 11 + << (QVector< QVector >() + << (QVector() << "0") + << (QVector() << "1") + << (QVector() << "2") + << (QVector()) + << (QVector()) + << (QVector() << "3") + << (QVector()) + << (QVector() << "4") + << (QVector()) + << (QVector()) + << (QVector())) + << (QVector< QVector >() + << (QVector()) + << (QVector()) + << (QVector()) + << (QVector() << "2") + << (QVector() << "1") + << (QVector()) + << (QVector() << "3") + << (QVector()) + << (QVector() << "4") + << (QVector() << "0") + << (QVector())); + QTest::newRow("Deleting entire 1st section begin.") << 4 << 8 << 10 + << (QVector< QVector >() + << (QVector() << "0") + << (QVector() << "1" << "2") + << (QVector()) + << (QVector()) + << (QVector() << "3") + << (QVector()) + << (QVector() << "4") + << (QVector()) + << (QVector()) + << (QVector())) + << (QVector< QVector >() + << (QVector()) + << (QVector()) + << (QVector() << "2") + << (QVector() << "1") + << (QVector()) + << (QVector() << "3") + << (QVector()) + << (QVector() << "4") + << (QVector() << "0") + << (QVector())); + QTest::newRow("Deleting entire 1st section begin and part of 2nd.") << 4 << 9 << 10 + << (QVector< QVector >() + << (QVector() << "0") + << (QVector() << "1" << "2") + << (QVector()) + << (QVector()) + << (QVector() << "3") + << (QVector()) + << (QVector() << "4") + << (QVector()) + << (QVector()) + << (QVector())) + << (QVector< QVector >() + << (QVector()) + << (QVector()) + << (QVector() << "2") + << (QVector() << "1") + << (QVector()) + << (QVector() << "3") + << (QVector()) + << (QVector() << "4") + << (QVector() << "0") + << (QVector())); + QTest::newRow("Deleting part of 1st section begin.") << 5 << 8 << 10 + << (QVector< QVector >() + << (QVector() << "0") + << (QVector() << "1") + << (QVector() << "2") + << (QVector()) + << (QVector() << "3") + << (QVector()) + << (QVector() << "4") + << (QVector()) + << (QVector()) + << (QVector())) + << (QVector< QVector >() + << (QVector()) + << (QVector()) + << (QVector() << "2") + << (QVector() << "1") + << (QVector()) + << (QVector() << "3") + << (QVector()) + << (QVector() << "4") + << (QVector() << "0") + << (QVector())); + QTest::newRow("Deleting part of 1st section begin and part of 2nd.") << 5 << 9 << 10 + << (QVector< QVector >() + << (QVector() << "0") + << (QVector() << "1") + << (QVector() << "2") + << (QVector()) + << (QVector() << "3") + << (QVector()) + << (QVector() << "4") + << (QVector()) + << (QVector()) + << (QVector())) + << (QVector< QVector >() + << (QVector()) + << (QVector()) + << (QVector() << "2") + << (QVector() << "1") + << (QVector()) + << (QVector() << "3") + << (QVector()) + << (QVector() << "4") + << (QVector() << "0") + << (QVector())); + QTest::newRow("Deleting all sections except 0th one.") << 4 << 36 << 3 + << (QVector< QVector >() + << (QVector() << "0") + << (QVector()) + << (QVector())) + << (QVector< QVector >() + << (QVector()) + << (QVector() << "0") + << (QVector())); + QTest::newRow("Deleting 3rd and part of 4th.") << 20 << 32 << 8 + << (QVector< QVector >() + << (QVector() << "0") + << (QVector() << "1") + << (QVector() << "2") + << (QVector()) + << (QVector()) + << (QVector() << "4") + << (QVector()) + << (QVector())) + << (QVector< QVector >() + << (QVector()) + << (QVector()) + << (QVector()) + << (QVector() << "2") + << (QVector() << "1") + << (QVector() << "4") + << (QVector() << "0") + << (QVector())); + QTest::newRow("Deleting all the sections.") << 0 << 40 << 1 + << (QVector< QVector >() + << (QVector())) + << (QVector< QVector >() + << (QVector())); + QTest::newRow("Deleting part of 3rd and part of 4th.") << 25 << 29 << 10 + << (QVector< QVector >() + << (QVector() << "0") + << (QVector() << "1") + << (QVector() << "2") + << (QVector()) + << (QVector()) + << (QVector() << "3") + << (QVector()) + << (QVector() << "4") + << (QVector()) + << (QVector())) + << (QVector< QVector >() + << (QVector()) + << (QVector()) + << (QVector()) + << (QVector() << "2") + << (QVector() << "1") + << (QVector()) + << (QVector() << "3") + << (QVector() << "4") + << (QVector() << "0") + << (QVector())); + QTest::newRow("Deleting 2nd end.") << 12 << 16 << 10 + << (QVector< QVector >() + << (QVector() << "0") + << (QVector() << "1") + << (QVector() << "2") + << (QVector()) + << (QVector() << "3") + << (QVector()) + << (QVector() << "4") + << (QVector()) + << (QVector()) + << (QVector())) + << (QVector< QVector >() + << (QVector()) + << (QVector()) + << (QVector() << "2") + << (QVector() << "1") + << (QVector()) + << (QVector() << "3") + << (QVector()) + << (QVector() << "4") + << (QVector() << "0") + << (QVector())); + QTest::newRow("Deleting 2nd end and part of 1st.") << 12 << 17 << 10 + << (QVector< QVector >() + << (QVector() << "0") + << (QVector() << "1") + << (QVector() << "2") + << (QVector()) + << (QVector() << "3") + << (QVector()) + << (QVector() << "4") + << (QVector()) + << (QVector()) + << (QVector())) + << (QVector< QVector >() + << (QVector()) + << (QVector()) + << (QVector() << "2") + << (QVector() << "1") + << (QVector()) + << (QVector() << "3") + << (QVector()) + << (QVector() << "4") + << (QVector() << "0") + << (QVector())); + QTest::newRow("Deleting part of 2nd end.") << 13 << 16 << 10 + << (QVector< QVector >() + << (QVector() << "0") + << (QVector() << "1") + << (QVector() << "2") + << (QVector()) + << (QVector() << "3") + << (QVector()) + << (QVector() << "4") + << (QVector()) + << (QVector()) + << (QVector())) + << (QVector< QVector >() + << (QVector()) + << (QVector()) + << (QVector()) + << (QVector() << "2" << "1") + << (QVector()) + << (QVector() << "3") + << (QVector()) + << (QVector() << "4") + << (QVector() << "0") + << (QVector())); + QTest::newRow("Deleting part of 2nd end and part of 1st.") << 13 << 17 << 10 + << (QVector< QVector >() + << (QVector() << "0") + << (QVector() << "1") + << (QVector() << "2") + << (QVector()) + << (QVector() << "3") + << (QVector()) + << (QVector() << "4") + << (QVector()) + << (QVector()) + << (QVector())) + << (QVector< QVector >() + << (QVector()) + << (QVector()) + << (QVector()) + << (QVector() << "2" << "1") + << (QVector()) + << (QVector() << "3") + << (QVector()) + << (QVector() << "4") + << (QVector() << "0") + << (QVector())); + QTest::newRow("Random test #0") << 5 << 36 << 3 + << (QVector< QVector >() + << (QVector() << "0") + << (QVector() << "1") + << (QVector())) + << (QVector< QVector >() + << (QVector()) + << (QVector() << "1" << "0") + << (QVector())); + QTest::newRow("Random test #1") << 0 << 23 << 6 + << (QVector< QVector >() + << (QVector() << "0" << "3") + << (QVector()) + << (QVector() << "4") + << (QVector()) + << (QVector()) + << (QVector())) + << (QVector< QVector >() + << (QVector()) + << (QVector() << "3") + << (QVector()) + << (QVector() << "4") + << (QVector() << "0") + << (QVector())); + QTest::newRow("Random test #2") << 7 << 19 << 8 + << (QVector< QVector >() + << (QVector() << "0") + << (QVector() << "1") + << (QVector() << "3") + << (QVector()) + << (QVector() << "4") + << (QVector()) + << (QVector()) + << (QVector())) + << (QVector< QVector >() + << (QVector()) + << (QVector() << "1") + << (QVector()) + << (QVector() << "3") + << (QVector()) + << (QVector() << "4") + << (QVector() << "0") + << (QVector())); + QTest::newRow("Random test #3") << 6 << 32 << 4 + << (QVector< QVector >() + << (QVector() << "0") + << (QVector() << "1") + << (QVector()) + << (QVector())) + << (QVector< QVector >() + << (QVector()) + << (QVector() << "1") + << (QVector() << "0") + << (QVector())); + QTest::newRow("Random test #4") << 17 << 23 << 10 + << (QVector< QVector >() + << (QVector() << "0") + << (QVector() << "1") + << (QVector() << "2") + << (QVector()) + << (QVector()) + << (QVector() << "3") + << (QVector() << "4") + << (QVector()) + << (QVector()) + << (QVector())) + << (QVector< QVector >() + << (QVector()) + << (QVector()) + << (QVector()) + << (QVector() << "2") + << (QVector() << "1") + << (QVector() << "3") + << (QVector()) + << (QVector() << "4") + << (QVector() << "0") + << (QVector())); + QTest::newRow("Random test #5") << 6 << 27 << 6 + << (QVector< QVector >() + << (QVector() << "0") + << (QVector() << "1") + << (QVector() << "4") + << (QVector()) + << (QVector()) + << (QVector())) + << (QVector< QVector >() + << (QVector()) + << (QVector() << "1") + << (QVector()) + << (QVector() << "4") + << (QVector() << "0") + << (QVector())); + QTest::newRow("Random test #6") << 6 << 17 << 8 + << (QVector< QVector >() + << (QVector() << "0") + << (QVector() << "1") + << (QVector() << "3") + << (QVector()) + << (QVector() << "4") + << (QVector()) + << (QVector()) + << (QVector())) + << (QVector< QVector >() + << (QVector()) + << (QVector() << "1") + << (QVector()) + << (QVector() << "3") + << (QVector()) + << (QVector() << "4") + << (QVector() << "0") + << (QVector())); + QTest::newRow("Random test #7") << 8 << 22 << 8 + << (QVector< QVector >() + << (QVector() << "0") + << (QVector() << "1") + << (QVector() << "3") + << (QVector()) + << (QVector() << "4") + << (QVector()) + << (QVector()) + << (QVector())) + << (QVector< QVector >() + << (QVector()) + << (QVector() << "1") + << (QVector()) + << (QVector() << "3") + << (QVector()) + << (QVector() << "4") + << (QVector() << "0") + << (QVector())); + QTest::newRow("Random test #8") << 14 << 19 << 10 + << (QVector< QVector >() + << (QVector() << "0") + << (QVector() << "1") + << (QVector() << "2") + << (QVector()) + << (QVector() << "3") + << (QVector()) + << (QVector() << "4") + << (QVector()) + << (QVector()) + << (QVector())) + << (QVector< QVector >() + << (QVector()) + << (QVector()) + << (QVector()) + << (QVector() << "2" << "1") + << (QVector()) + << (QVector() << "3") + << (QVector()) + << (QVector() << "4") + << (QVector() << "0") + << (QVector())); + QTest::newRow("Random test #9") << 3 << 13 << 8 + << (QVector< QVector >() + << (QVector() << "0") + << (QVector() << "1") + << (QVector() << "3") + << (QVector()) + << (QVector() << "4") + << (QVector()) + << (QVector()) + << (QVector())) + << (QVector< QVector >() + << (QVector()) + << (QVector() << "1") + << (QVector()) + << (QVector() << "3") + << (QVector()) + << (QVector() << "4") + << (QVector() << "0") + << (QVector())); +} diff --git a/libs/kotext/tests/TestInsertSectionHandling_data.cpp b/libs/kotext/tests/TestInsertSectionHandling_data.cpp new file mode 100644 index 0000000000..26eb6701c2 --- /dev/null +++ b/libs/kotext/tests/TestInsertSectionHandling_data.cpp @@ -0,0 +1,217 @@ +/* This file is part of the KDE project + * + * Copyright (c) 2014-2015 Denis Kuplyakov + * + * 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. + */ + +void TestKoTextEditor::testInsertSectionHandling_data() +{ + QTest::addColumn("insertPosition"); + QTest::addColumn("neededBlockCount"); + QTest::addColumn< QVector< QVector > >("needStartings"); + QTest::addColumn< QVector< QVector > >("needEndings"); + + QTest::newRow("Test #0") << 0 << 12 + << (QVector< QVector >() + << (QVector() << "0") + << (QVector() << "New section 6") + << (QVector() << "1") + << (QVector() << "2") + << (QVector()) + << (QVector()) + << (QVector() << "3") + << (QVector()) + << (QVector() << "4") + << (QVector()) + << (QVector()) + << (QVector())) + << (QVector< QVector >() + << (QVector()) + << (QVector() << "New section 6") + << (QVector()) + << (QVector()) + << (QVector() << "2") + << (QVector() << "1") + << (QVector()) + << (QVector() << "3") + << (QVector()) + << (QVector() << "4") + << (QVector() << "0") + << (QVector())); + QTest::newRow("Test #1") << 39 << 12 + << (QVector< QVector >() + << (QVector() << "0") + << (QVector() << "1") + << (QVector() << "2") + << (QVector()) + << (QVector()) + << (QVector() << "3") + << (QVector()) + << (QVector() << "4") + << (QVector()) + << (QVector()) + << (QVector() << "New section 6") + << (QVector())) + << (QVector< QVector >() + << (QVector()) + << (QVector()) + << (QVector()) + << (QVector() << "2") + << (QVector() << "1") + << (QVector()) + << (QVector() << "3") + << (QVector()) + << (QVector() << "4") + << (QVector()) + << (QVector() << "New section 6" << "0") + << (QVector())); + QTest::newRow("Test #2") << 40 << 12 + << (QVector< QVector >() + << (QVector() << "0") + << (QVector() << "1") + << (QVector() << "2") + << (QVector()) + << (QVector()) + << (QVector() << "3") + << (QVector()) + << (QVector() << "4") + << (QVector()) + << (QVector()) + << (QVector()) + << (QVector() << "New section 6")) + << (QVector< QVector >() + << (QVector()) + << (QVector()) + << (QVector()) + << (QVector() << "2") + << (QVector() << "1") + << (QVector()) + << (QVector() << "3") + << (QVector()) + << (QVector() << "4") + << (QVector() << "0") + << (QVector()) + << (QVector() << "New section 6")); + QTest::newRow("Test #3") << 5 << 12 + << (QVector< QVector >() + << (QVector() << "0") + << (QVector() << "1") + << (QVector() << "New section 6") + << (QVector() << "2") + << (QVector()) + << (QVector()) + << (QVector() << "3") + << (QVector()) + << (QVector() << "4") + << (QVector()) + << (QVector()) + << (QVector())) + << (QVector< QVector >() + << (QVector()) + << (QVector()) + << (QVector() << "New section 6") + << (QVector()) + << (QVector() << "2") + << (QVector() << "1") + << (QVector()) + << (QVector() << "3") + << (QVector()) + << (QVector() << "4") + << (QVector() << "0") + << (QVector())); + QTest::newRow("Test #4") << 8 << 12 + << (QVector< QVector >() + << (QVector() << "0") + << (QVector() << "1") + << (QVector() << "2") + << (QVector() << "New section 6") + << (QVector()) + << (QVector()) + << (QVector() << "3") + << (QVector()) + << (QVector() << "4") + << (QVector()) + << (QVector()) + << (QVector())) + << (QVector< QVector >() + << (QVector()) + << (QVector()) + << (QVector()) + << (QVector() << "New section 6") + << (QVector() << "2") + << (QVector() << "1") + << (QVector()) + << (QVector() << "3") + << (QVector()) + << (QVector() << "4") + << (QVector() << "0") + << (QVector())); + QTest::newRow("Test #5") << 20 << 12 + << (QVector< QVector >() + << (QVector() << "0") + << (QVector() << "1") + << (QVector() << "2") + << (QVector()) + << (QVector()) + << (QVector() << "3") + << (QVector() << "New section 6") + << (QVector()) + << (QVector() << "4") + << (QVector()) + << (QVector()) + << (QVector())) + << (QVector< QVector >() + << (QVector()) + << (QVector()) + << (QVector()) + << (QVector() << "2") + << (QVector() << "1") + << (QVector()) + << (QVector() << "New section 6") + << (QVector() << "3") + << (QVector()) + << (QVector() << "4") + << (QVector() << "0") + << (QVector())); + QTest::newRow("Test #6") << 1 << 12 + << (QVector< QVector >() + << (QVector() << "0") + << (QVector() << "New section 6") + << (QVector() << "1") + << (QVector() << "2") + << (QVector()) + << (QVector()) + << (QVector() << "3") + << (QVector()) + << (QVector() << "4") + << (QVector()) + << (QVector()) + << (QVector())) + << (QVector< QVector >() + << (QVector()) + << (QVector() << "New section 6") + << (QVector()) + << (QVector()) + << (QVector() << "2") + << (QVector() << "1") + << (QVector()) + << (QVector() << "3") + << (QVector()) + << (QVector() << "4") + << (QVector() << "0") + << (QVector())); +} diff --git a/libs/kotext/tests/TestKoTextEditor.cpp b/libs/kotext/tests/TestKoTextEditor.cpp index 7c7b52a5f5..6a4f88a6b3 100644 --- a/libs/kotext/tests/TestKoTextEditor.cpp +++ b/libs/kotext/tests/TestKoTextEditor.cpp @@ -1,774 +1,546 @@ /* This file is part of the KDE project * * Copyright (c) 2011 Boudewijn Rempt + * Copyright (c) 2014-2015 Denis Kuplyakov * * 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 "TestKoTextEditor.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 -Q_DECLARE_METATYPE(QVector< QVector >) +/** + * Convenient class to create a document and assign + * stuff like KoTextRangeManager, etc. automatically. + */ +class TestDocument : public KoShapeBasedDocumentBase +{ +public: + + TestDocument() + { + m_document = new QTextDocument(); + + KoTextDocument textDoc(m_document); + KoTextEditor *editor = new KoTextEditor(m_document); + KUndo2Stack *undoStack = new KUndo2Stack(); + textDoc.setUndoStack(undoStack); + + textDoc.setInlineTextObjectManager(&m_inlineObjectManager); + textDoc.setTextRangeManager(&m_rangeManager); + textDoc.setStyleManager(new KoStyleManager(0)); + textDoc.setTextEditor(editor); + } + + virtual ~TestDocument() + { + delete m_document; + } + + virtual void addShape(KoShape *shape) + { + m_shapes << shape; + } + + virtual void removeShape(KoShape *shape) + { + m_shapes.removeAll(shape); + } + + KoTextEditor *textEditor() + { + return KoTextDocument(m_document).textEditor(); + } + + KoSectionModel *sectionModel() + { + return KoTextDocument(m_document).sectionModel(); + } + + QList m_shapes; + + QTextDocument *m_document; + KoInlineTextObjectManager m_inlineObjectManager; + KoTextRangeManager m_rangeManager; + KoDocumentRdfBase m_rdfBase; +}; const QString lorem( "Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor" "incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud" "exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.\n" "Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla" "pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia" "deserunt mollit anim id est laborum.\n" - ); +); +/* FIXME: all the meaning part of this test was commented by boud in 2011 void TestKoTextEditor::testInsertInlineObject() { QObject parent; // create a document QTextDocument doc; KoInlineTextObjectManager inlineObjectManager(&parent); KoTextDocument textDoc(&doc); textDoc.setInlineTextObjectManager(&inlineObjectManager); KoTextEditor editor(&doc); textDoc.setTextEditor(&editor); - /* Hmm, what kind of inline object should we test. variables maybe? + // Hmm, what kind of inline object should we test. variables maybe? // enter some lorem ipsum editor.insertText(lorem); KoBookmark *startmark = new KoBookmark(editor.document()); startmark->setName("single!"); editor.insertInlineObject(startmark); Q_ASSERT(startmark->id() == 1); Q_ASSERT(startmark->name() == "single!"); Q_ASSERT(startmark->position() == 444); QTextCursor cursor = doc.find(QString(QChar::ObjectReplacementCharacter), 0); Q_ASSERT(cursor.position() == 444); KoInlineObject *obj = inlineObjectManager.inlineTextObject(cursor.charFormat()); Q_ASSERT(obj == startmark); -*/ -} +} */ void TestKoTextEditor::testRemoveSelectedText() { - QObject parent; - - // create a document - QTextDocument doc; - - KoTextRangeManager rangeManager(&parent); - KoTextDocument textDoc(&doc); - textDoc.setTextRangeManager(&rangeManager); + TestDocument doc; - KoTextEditor editor(&doc); - textDoc.setTextEditor(&editor); + KoTextEditor *editor = doc.textEditor(); + KoTextRangeManager *rangeManager = &doc.m_rangeManager; // enter some lorem ipsum - editor.insertText(lorem); + editor->insertText(lorem); - QTextCursor cur(&doc); - cur.setPosition(editor.position()); + QTextCursor cur(doc.m_document); + cur.setPosition(editor->position()); KoBookmark *bookmark = new KoBookmark(cur); bookmark->setName("start!"); bookmark->setPositionOnlyMode(false); // we want it to be several chars long - rangeManager.insert(bookmark); + rangeManager->insert(bookmark); - editor.insertText(lorem); + editor->insertText(lorem); - bookmark->setRangeEnd(editor.position()); + bookmark->setRangeEnd(editor->position()); QCOMPARE(bookmark->rangeStart(), lorem.length()); QCOMPARE(bookmark->rangeEnd(), lorem.length() * 2); - Q_ASSERT(rangeManager.textRanges().length() == 1); + Q_ASSERT(rangeManager->textRanges().length() == 1); // select all text - editor.setPosition(0, QTextCursor::MoveAnchor); - editor.movePosition(QTextCursor::End, QTextCursor::KeepAnchor); + editor->setPosition(0, QTextCursor::MoveAnchor); + editor->movePosition(QTextCursor::End, QTextCursor::KeepAnchor); - Q_ASSERT(editor.hasSelection()); + Q_ASSERT(editor->hasSelection()); // remove the text + the bookmark from the document - editor.deleteChar(); + editor->deleteChar(); // check whether the bookmark has gone. - Q_ASSERT(rangeManager.textRanges().length() == 0); + Q_ASSERT(rangeManager->textRanges().length() == 0); } -void TestKoTextEditor::pushSectionStart(int num, KoSection *sec, KoTextEditor &editor) +void TestKoTextEditor::pushSectionStart(int num, KoSection *sec, KoTextEditor *editor) { - editor.insertText(QString("[ %1").arg(num)); + editor->insertText(QString("[ %1").arg(num)); - QTextBlockFormat fmt = editor.blockFormat(); - fmt.clearProperty(KoParagraphStyle::SectionStartings); - fmt.clearProperty(KoParagraphStyle::SectionEndings); + QTextBlockFormat fmt = editor->blockFormat(); KoSectionUtils::setSectionStartings(fmt, QList() << sec); - editor.setBlockFormat(fmt); + editor->setBlockFormat(fmt); - editor.insertText("\n"); + editor->insertText("\n"); fmt.clearProperty(KoParagraphStyle::SectionEndings); fmt.clearProperty(KoParagraphStyle::SectionStartings); - editor.setBlockFormat(fmt); + editor->setBlockFormat(fmt); } -void TestKoTextEditor::pushSectionEnd(int num, KoSectionEnd *secEnd, KoTextEditor &editor) +void TestKoTextEditor::pushSectionEnd(int num, KoSectionEnd *secEnd, KoTextEditor *editor) { - editor.insertText(QString("%1 ]").arg(num)); - - QTextBlockFormat fmt = editor.blockFormat(); - fmt.clearProperty(KoParagraphStyle::SectionStartings); - fmt.clearProperty(KoParagraphStyle::SectionEndings); + editor->insertText(QString("%1 ]").arg(num)); + QTextBlockFormat fmt = editor->blockFormat(); KoSectionUtils::setSectionEndings(fmt, QList() << secEnd); - editor.setBlockFormat(fmt); + editor->setBlockFormat(fmt); + secEnd->correspondingSection()->setKeepEndBound(true); - editor.insertText("\n"); + editor->insertText("\n"); fmt.clearProperty(KoParagraphStyle::SectionEndings); fmt.clearProperty(KoParagraphStyle::SectionStartings); - editor.setBlockFormat(fmt); + editor->setBlockFormat(fmt); +} + +void TestKoTextEditor::formSectionTestDocument(TestDocument *doc) +{ + // Here we are going to create next document with nested sections: + // ** offset ** block num + // [ 0P 0 0 + // [ 1P 4 1 + // [ 2P 8 2 + // 2 ]P 12 3 + // 1 ]P 16 4 + // [ 3P 20 5 + // 3 ]P 24 6 + // [ 4P 28 7 + // 4 ]P 32 8 + // 0 ]P 36 9 + // (**empty_block**) 10 + // + // Sections will receive names "0", "1", etc. + // [ and ] is actual text, not a sign! + + KoTextEditor *editor = doc->textEditor(); + + const int TOTAL_SECTIONS = 5; + KoSection *sec[TOTAL_SECTIONS]; + KoSectionEnd *secEnd[TOTAL_SECTIONS]; + + sec[0] = doc->sectionModel()->createSection(editor->constCursor(), 0, QString::number(0)); + pushSectionStart(0, sec[0], editor); + + sec[1] = doc->sectionModel()->createSection(editor->constCursor(), sec[0], QString::number(1)); + pushSectionStart(1, sec[1], editor); + + sec[2] = doc->sectionModel()->createSection(editor->constCursor(), sec[1], QString::number(2)); + pushSectionStart(2, sec[2], editor); + + secEnd[2] = doc->sectionModel()->createSectionEnd(sec[2]); + pushSectionEnd(2, secEnd[2], editor); + + secEnd[1] = doc->sectionModel()->createSectionEnd(sec[1]); + pushSectionEnd(1, secEnd[1], editor); + + sec[3] = doc->sectionModel()->createSection(editor->constCursor(), sec[0], QString::number(3)); + pushSectionStart(3, sec[3], editor); + + secEnd[3] = doc->sectionModel()->createSectionEnd(sec[3]); + pushSectionEnd(3, secEnd[3], editor); + + sec[4] = doc->sectionModel()->createSection(editor->constCursor(), sec[0], QString::number(4)); + pushSectionStart(4, sec[4], editor); + + secEnd[4] = doc->sectionModel()->createSectionEnd(sec[4]); + pushSectionEnd(4, secEnd[4], editor); + + secEnd[0] = doc->sectionModel()->createSectionEnd(sec[0]); + pushSectionEnd(0, secEnd[0], editor); + + doc->sectionModel()->allowMovingEndBound(); } -bool TestKoTextEditor::checkStartings(const QVector &needStartings, KoSection **sec, KoTextEditor &editor) +bool TestKoTextEditor::checkStartings(const QVector &needStartings, KoTextEditor *editor) { - QList lst = KoSectionUtils::sectionStartings(editor.blockFormat()); + QList lst = KoSectionUtils::sectionStartings(editor->blockFormat()); if (lst.size() != needStartings.size()) { - kDebug() << QString("Startings list size is wrong. Found %1, Expected %2").arg(lst.size()).arg(needStartings.size()); + kDebug() << QString("Startings list size is wrong." + " Found %1, Expected %2.").arg(lst.size()).arg(needStartings.size()); return false; } for (int i = 0; i < needStartings.size(); i++) { - if (lst[i] != sec[needStartings[i]]) { - kDebug() << QString("Found unexpected section starting. Expected %1 section.").arg(needStartings[i]); + if (lst[i]->name() != needStartings[i]) { + kDebug() << QString("Found unexpected section starting." + " Expected %1 section.").arg(needStartings[i]); return false; } } return true; } -bool TestKoTextEditor::checkEndings(const QVector &needEndings, KoSectionEnd **secEnd, KoTextEditor &editor) +bool TestKoTextEditor::checkEndings(const QVector &needEndings, KoTextEditor *editor) { - QList lst = KoSectionUtils::sectionEndings(editor.blockFormat()); + QList lst = KoSectionUtils::sectionEndings(editor->blockFormat()); if (lst.size() != needEndings.size()) { - kDebug() << QString("Endings list size is wrong. Found %1, expected %2").arg(lst.size()).arg(needEndings.size()); + kDebug() << QString("Endings list size is wrong." + " Found %1, expected %2.").arg(lst.size()).arg(needEndings.size()); return false; } for (int i = 0; i < needEndings.size(); i++) { - if (lst[i] != secEnd[needEndings[i]]) { - kDebug() << QString("Found unexpected section ending. Expected %1 section.").arg(needEndings[i]); + if (lst[i]->correspondingSection()->name() != needEndings[i]) { + kDebug() << QString("Found unexpected section ending." + " Expected %1 section.").arg(needEndings[i]); return false; } } return true; } -void TestKoTextEditor::testDeleteSectionHandling_data() +void TestKoTextEditor::checkSectionFormattingLevel( + TestDocument *doc, + int neededBlockCount, + const QVector< QVector > &needStartings, + const QVector< QVector > &needEndings) { - QTest::addColumn("selectionStart"); - QTest::addColumn("selectionEnd"); - QTest::addColumn("neededBlockCount"); - QTest::addColumn< QVector< QVector > >("needStartings"); - QTest::addColumn< QVector< QVector > >("needEndings"); - - QTest::newRow("Simple deletion, no effect to sections.") << 1 << 2 << 11 - << (QVector< QVector >() - << (QVector() << 0) - << (QVector() << 1) - << (QVector() << 2) - << (QVector()) - << (QVector()) - << (QVector() << 3) - << (QVector()) - << (QVector() << 4) - << (QVector()) - << (QVector()) - << (QVector())) - << (QVector< QVector >() - << (QVector()) - << (QVector()) - << (QVector()) - << (QVector() << 2) - << (QVector() << 1) - << (QVector()) - << (QVector() << 3) - << (QVector()) - << (QVector() << 4) - << (QVector() << 0) - << (QVector())); - QTest::newRow("Deleting entire 1st section begin.") << 4 << 8 << 10 - << (QVector< QVector >() - << (QVector() << 0) - << (QVector() << 1 << 2) - << (QVector()) - << (QVector()) - << (QVector() << 3) - << (QVector()) - << (QVector() << 4) - << (QVector()) - << (QVector()) - << (QVector())) - << (QVector< QVector >() - << (QVector()) - << (QVector()) - << (QVector() << 2) - << (QVector() << 1) - << (QVector()) - << (QVector() << 3) - << (QVector()) - << (QVector() << 4) - << (QVector() << 0) - << (QVector())); - QTest::newRow("Deleting entire 1st section begin and part of 2nd.") << 4 << 9 << 10 - << (QVector< QVector >() - << (QVector() << 0) - << (QVector() << 1 << 2) - << (QVector()) - << (QVector()) - << (QVector() << 3) - << (QVector()) - << (QVector() << 4) - << (QVector()) - << (QVector()) - << (QVector())) - << (QVector< QVector >() - << (QVector()) - << (QVector()) - << (QVector() << 2) - << (QVector() << 1) - << (QVector()) - << (QVector() << 3) - << (QVector()) - << (QVector() << 4) - << (QVector() << 0) - << (QVector())); - QTest::newRow("Deleting part of 1st section begin.") << 5 << 8 << 10 - << (QVector< QVector >() - << (QVector() << 0) - << (QVector() << 1) - << (QVector() << 2) - << (QVector()) - << (QVector() << 3) - << (QVector()) - << (QVector() << 4) - << (QVector()) - << (QVector()) - << (QVector())) - << (QVector< QVector >() - << (QVector()) - << (QVector()) - << (QVector() << 2) - << (QVector() << 1) - << (QVector()) - << (QVector() << 3) - << (QVector()) - << (QVector() << 4) - << (QVector() << 0) - << (QVector())); - QTest::newRow("Deleting part of 1st section begin and part of 2nd.") << 5 << 9 << 10 - << (QVector< QVector >() - << (QVector() << 0) - << (QVector() << 1) - << (QVector() << 2) - << (QVector()) - << (QVector() << 3) - << (QVector()) - << (QVector() << 4) - << (QVector()) - << (QVector()) - << (QVector())) - << (QVector< QVector >() - << (QVector()) - << (QVector()) - << (QVector() << 2) - << (QVector() << 1) - << (QVector()) - << (QVector() << 3) - << (QVector()) - << (QVector() << 4) - << (QVector() << 0) - << (QVector())); - QTest::newRow("Deleting all sections except 0th one.") << 4 << 36 << 3 - << (QVector< QVector >() - << (QVector() << 0) - << (QVector()) - << (QVector())) - << (QVector< QVector >() - << (QVector()) - << (QVector() << 0) - << (QVector())); - QTest::newRow("Deleting 3rd and part of 4th.") << 20 << 32 << 8 - << (QVector< QVector >() - << (QVector() << 0) - << (QVector() << 1) - << (QVector() << 2) - << (QVector()) - << (QVector()) - << (QVector() << 4) - << (QVector()) - << (QVector())) - << (QVector< QVector >() - << (QVector()) - << (QVector()) - << (QVector()) - << (QVector() << 2) - << (QVector() << 1) - << (QVector() << 4) - << (QVector() << 0) - << (QVector())); - QTest::newRow("Deleting all the sections.") << 0 << 40 << 1 - << (QVector< QVector >() - << (QVector())) - << (QVector< QVector >() - << (QVector())); - QTest::newRow("Deleting part of 3rd and part of 4th.") << 25 << 29 << 10 - << (QVector< QVector >() - << (QVector() << 0) - << (QVector() << 1) - << (QVector() << 2) - << (QVector()) - << (QVector()) - << (QVector() << 3) - << (QVector()) - << (QVector() << 4) - << (QVector()) - << (QVector())) - << (QVector< QVector >() - << (QVector()) - << (QVector()) - << (QVector()) - << (QVector() << 2) - << (QVector() << 1) - << (QVector()) - << (QVector() << 3) - << (QVector() << 4) - << (QVector() << 0) - << (QVector())); - QTest::newRow("Deleting 2nd end.") << 12 << 16 << 10 - << (QVector< QVector >() - << (QVector() << 0) - << (QVector() << 1) - << (QVector() << 2) - << (QVector()) - << (QVector() << 3) - << (QVector()) - << (QVector() << 4) - << (QVector()) - << (QVector()) - << (QVector())) - << (QVector< QVector >() - << (QVector()) - << (QVector()) - << (QVector()) - << (QVector() << 2 << 1) - << (QVector()) - << (QVector() << 3) - << (QVector()) - << (QVector() << 4) - << (QVector() << 0) - << (QVector())); - QTest::newRow("Deleting 2nd end and part of 1st.") << 12 << 17 << 10 - << (QVector< QVector >() - << (QVector() << 0) - << (QVector() << 1) - << (QVector() << 2) - << (QVector()) - << (QVector() << 3) - << (QVector()) - << (QVector() << 4) - << (QVector()) - << (QVector()) - << (QVector())) - << (QVector< QVector >() - << (QVector()) - << (QVector()) - << (QVector()) - << (QVector() << 2 << 1) - << (QVector()) - << (QVector() << 3) - << (QVector()) - << (QVector() << 4) - << (QVector() << 0) - << (QVector())); - QTest::newRow("Deleting part of 2nd end.") << 13 << 16 << 10 - << (QVector< QVector >() - << (QVector() << 0) - << (QVector() << 1) - << (QVector() << 2) - << (QVector()) - << (QVector() << 3) - << (QVector()) - << (QVector() << 4) - << (QVector()) - << (QVector()) - << (QVector())) - << (QVector< QVector >() - << (QVector()) - << (QVector()) - << (QVector()) - << (QVector() << 2 << 1) - << (QVector()) - << (QVector() << 3) - << (QVector()) - << (QVector() << 4) - << (QVector() << 0) - << (QVector())); - QTest::newRow("Deleting part of 2nd end and part of 1st.") << 13 << 17 << 10 - << (QVector< QVector >() - << (QVector() << 0) - << (QVector() << 1) - << (QVector() << 2) - << (QVector()) - << (QVector() << 3) - << (QVector()) - << (QVector() << 4) - << (QVector()) - << (QVector()) - << (QVector())) - << (QVector< QVector >() - << (QVector()) - << (QVector()) - << (QVector()) - << (QVector() << 2 << 1) - << (QVector()) - << (QVector() << 3) - << (QVector()) - << (QVector() << 4) - << (QVector() << 0) - << (QVector())); - QTest::newRow("Random test #0") << 5 << 36 << 3 - << (QVector< QVector >() - << (QVector() << 0) - << (QVector() << 1) - << (QVector())) - << (QVector< QVector >() - << (QVector()) - << (QVector() << 1 << 0) - << (QVector())); - QTest::newRow("Random test #1") << 0 << 23 << 6 - << (QVector< QVector >() - << (QVector() << 0 << 3) - << (QVector()) - << (QVector() << 4) - << (QVector()) - << (QVector()) - << (QVector())) - << (QVector< QVector >() - << (QVector()) - << (QVector() << 3) - << (QVector()) - << (QVector() << 4) - << (QVector() << 0) - << (QVector())); - QTest::newRow("Random test #2") << 7 << 19 << 8 - << (QVector< QVector >() - << (QVector() << 0) - << (QVector() << 1) - << (QVector() << 3) - << (QVector()) - << (QVector() << 4) - << (QVector()) - << (QVector()) - << (QVector())) - << (QVector< QVector >() - << (QVector()) - << (QVector() << 1) - << (QVector()) - << (QVector() << 3) - << (QVector()) - << (QVector() << 4) - << (QVector() << 0) - << (QVector())); - QTest::newRow("Random test #3") << 6 << 32 << 4 - << (QVector< QVector >() - << (QVector() << 0) - << (QVector() << 1) - << (QVector()) - << (QVector())) - << (QVector< QVector >() - << (QVector()) - << (QVector() << 1) - << (QVector() << 0) - << (QVector())); - QTest::newRow("Random test #4") << 17 << 23 << 10 - << (QVector< QVector >() - << (QVector() << 0) - << (QVector() << 1) - << (QVector() << 2) - << (QVector()) - << (QVector()) - << (QVector() << 3) - << (QVector() << 4) - << (QVector()) - << (QVector()) - << (QVector())) - << (QVector< QVector >() - << (QVector()) - << (QVector()) - << (QVector()) - << (QVector() << 2) - << (QVector() << 1) - << (QVector() << 3) - << (QVector()) - << (QVector() << 4) - << (QVector() << 0) - << (QVector())); - QTest::newRow("Random test #5") << 6 << 27 << 6 - << (QVector< QVector >() - << (QVector() << 0) - << (QVector() << 1) - << (QVector() << 4) - << (QVector()) - << (QVector()) - << (QVector())) - << (QVector< QVector >() - << (QVector()) - << (QVector() << 1) - << (QVector()) - << (QVector() << 4) - << (QVector() << 0) - << (QVector())); - QTest::newRow("Random test #6") << 6 << 17 << 8 - << (QVector< QVector >() - << (QVector() << 0) - << (QVector() << 1) - << (QVector() << 3) - << (QVector()) - << (QVector() << 4) - << (QVector()) - << (QVector()) - << (QVector())) - << (QVector< QVector >() - << (QVector()) - << (QVector() << 1) - << (QVector()) - << (QVector() << 3) - << (QVector()) - << (QVector() << 4) - << (QVector() << 0) - << (QVector())); - QTest::newRow("Random test #7") << 8 << 22 << 8 - << (QVector< QVector >() - << (QVector() << 0) - << (QVector() << 1) - << (QVector() << 3) - << (QVector()) - << (QVector() << 4) - << (QVector()) - << (QVector()) - << (QVector())) - << (QVector< QVector >() - << (QVector()) - << (QVector()) - << (QVector() << 1) - << (QVector() << 3) - << (QVector()) - << (QVector() << 4) - << (QVector() << 0) - << (QVector())); - QTest::newRow("Random test #8") << 14 << 19 << 10 - << (QVector< QVector >() - << (QVector() << 0) - << (QVector() << 1) - << (QVector() << 2) - << (QVector()) - << (QVector() << 3) - << (QVector()) - << (QVector() << 4) - << (QVector()) - << (QVector()) - << (QVector())) - << (QVector< QVector >() - << (QVector()) - << (QVector()) - << (QVector()) - << (QVector() << 2 << 1) - << (QVector()) - << (QVector() << 3) - << (QVector()) - << (QVector() << 4) - << (QVector() << 0) - << (QVector())); - QTest::newRow("Random test #9") << 3 << 13 << 8 - << (QVector< QVector >() - << (QVector() << 0) - << (QVector() << 1) - << (QVector() << 3) - << (QVector()) - << (QVector() << 4) - << (QVector()) - << (QVector()) - << (QVector())) - << (QVector< QVector >() - << (QVector()) - << (QVector() << 1) - << (QVector()) - << (QVector() << 3) - << (QVector()) - << (QVector() << 4) - << (QVector() << 0) - << (QVector())); + // Assuming here that we can check names of the sections + // instead of actual pointers. This seems to be true for now. + QCOMPARE(needStartings.size(), neededBlockCount); + QCOMPARE(needEndings.size(), neededBlockCount); + + KoTextEditor *editor = doc->textEditor(); + editor->movePosition(QTextCursor::Start); + + QCOMPARE(doc->m_document->blockCount(), neededBlockCount); + for (int i = 0; i < doc->m_document->blockCount(); i++) { + if (!checkStartings(needStartings[i], editor) + || !checkEndings(needEndings[i], editor)) { + QFAIL("Wrong section information."); + } + editor->movePosition(QTextCursor::NextBlock); + } } -void TestKoTextEditor::testDeleteSectionHandling() +void TestKoTextEditor::checkSectionModelLevelRecursive(QModelIndex index, TestKoTextEditor::SectionHandle *handle) { - QObject parent; - - // create a document - QTextDocument doc; - - KoTextRangeManager rangeManager(&parent); - KoTextDocument textDoc(&doc); - textDoc.setTextRangeManager(&rangeManager); - - KoTextEditor editor(&doc); - textDoc.setTextEditor(&editor); - - const int TOTAL_SECTIONS = 5; - KoSection *sec[TOTAL_SECTIONS]; - KoSectionEnd *secEnd[TOTAL_SECTIONS]; - for (int i = 0; i < TOTAL_SECTIONS; i++) { - sec[i] = new KoSection(editor.constCursor()); - secEnd[i] = new KoSectionEnd(sec[i]); + QCOMPARE(index.data(KoSectionModel::PointerRole).value(), handle->sec); + QCOMPARE(index.model()->rowCount(index), handle->children.size()); + QModelIndex parent = index.parent(); + QCOMPARE(parent.data(KoSectionModel::PointerRole).value(), handle->parent); + for (int i = 0; i < handle->children.size(); i++) { + checkSectionModelLevelRecursive(index.child(i, 0), handle->children[i]); } +} - pushSectionStart(0, sec[0], editor); - pushSectionStart(1, sec[1], editor); - pushSectionStart(2, sec[2], editor); - pushSectionEnd(2, secEnd[2], editor); - pushSectionEnd(1, secEnd[1], editor); - pushSectionStart(3, sec[3], editor); - pushSectionEnd(3, secEnd[3], editor); - pushSectionStart(4, sec[4], editor); - pushSectionEnd(4, secEnd[4], editor); - pushSectionEnd(0, secEnd[0], editor); - - /** ** offset ** block num - * [ 0$ 0 0 - * [ 1$ 4 1 - * [ 2$ 8 2 - * 2 ]$ 12 3 - * 1 ]$ 16 4 - * [ 3$ 20 5 - * 3 ]$ 24 6 - * [ 4$ 28 7 - * 4 ]$ 32 8 - * 0 ]$ 36 9 - * (**empty_block**) 10 - */ +void TestKoTextEditor::checkSectionModelLevel(TestDocument *doc) +{ + // Assuming here that Formatting level is OK + // Below I will rebuild sections structure from scratch + // and compare it then with a KoSectionModel + + QVector allSections, rootSections; + QStack sectionStack; + + QTextBlock curBlock = doc->m_document->firstBlock(); + // This kind of cycle should visit all blocks + // including ones in tables and frames. + while (curBlock.isValid()) { + QList secStartings = KoSectionUtils::sectionStartings(curBlock.blockFormat()); + QList secEndings = KoSectionUtils::sectionEndings(curBlock.blockFormat()); + + foreach(KoSection *sec, secStartings) { + SectionHandle *handle = new SectionHandle(sec); + if (sectionStack.empty()) { + rootSections.push_back(handle); + handle->parent = 0; + } else { + sectionStack.top()->children.push_back(handle); + handle->parent = sectionStack.top()->sec; + } + + allSections.push_back(handle); + sectionStack.push(handle); + } - QFETCH(int, selectionStart); - QFETCH(int, selectionEnd); - QFETCH(int, neededBlockCount); - QFETCH(QVector< QVector >, needStartings); - QFETCH(QVector< QVector >, needEndings); + foreach(KoSectionEnd *secEnd, secEndings) { + sectionStack.pop(); + } - // placing selection - editor.setPosition(selectionStart); - editor.movePosition(QTextCursor::NextCharacter, QTextCursor::KeepAnchor, selectionEnd - selectionStart); + curBlock = curBlock.next(); + } - // doing deletion - editor.deleteChar(); + // Now lets compare builded tree with KoSectionModel + KoSectionModel *model = doc->sectionModel(); + QCOMPARE(model->rowCount(), rootSections.size()); + for (int i = 0; i < rootSections.size(); i++) { + checkSectionModelLevelRecursive(model->index(i, 0), rootSections[i]); + } - // checking - editor.movePosition(QTextCursor::Start); + foreach (SectionHandle *handle, allSections) { + delete handle; + } +} - QCOMPARE(doc.blockCount(), neededBlockCount); +void TestKoTextEditor::dumpSectionFormattingLevel(TestDocument *doc) +{ + QString result; + result += QString("QTest::newRow(\"%1\") << %2\n").arg(QTest::currentDataTag()).arg(doc->m_document->blockCount()); + result += " << (QVector< QVector >()\n"; + QTextBlock curBlock = doc->m_document->firstBlock(); + // This kind of cycle should visit all blocks + // including ones in tables and frames. + while (curBlock.isValid()) { + result += " << (QVector()"; + QList l = KoSectionUtils::sectionStartings(curBlock.blockFormat()); + foreach (KoSection *s, l) { + result += QString(" << \"%1\"").arg(s->name()); + } + result += ")"; + curBlock = curBlock.next(); + if (curBlock.isValid()) { + result += "\n"; + } else { + result += ")\n"; + } + } - for (int i = 0; i < doc.blockCount(); i++) { - if (!checkStartings(needStartings[i], sec, editor) - || !checkEndings(needEndings[i], secEnd, editor)) { - QFAIL("Wrong section information."); + result += " << (QVector< QVector >()\n"; + curBlock = doc->m_document->firstBlock(); + while (curBlock.isValid()) { + result += " << (QVector()"; + QList l = KoSectionUtils::sectionEndings(curBlock.blockFormat()); + foreach (KoSectionEnd *e, l) { + result += QString(" << \"%1\"").arg(e->correspondingSection()->name()); } - editor.movePosition(QTextCursor::NextBlock); + result += ")"; + curBlock = curBlock.next(); + if (curBlock.isValid()) { + result += "\n"; + } else { + result += ")\n"; + } + } + result += ";"; + + QFile out(QString("dump_%1.txt").arg(QTest::currentDataTag())); + if (out.open(QIODevice::ReadWrite)) { + QTextStream(&out) << result; } + out.close(); } -class TestDocument : public KoShapeBasedDocumentBase +void TestKoTextEditor::checkSectionTestDocument(TestDocument *doc) { -public: + int neededBlockCount = 11; + QVector< QVector > needStartings = + QVector< QVector >() + << (QVector() << "0") + << (QVector() << "1") + << (QVector() << "2") + << (QVector()) + << (QVector()) + << (QVector() << "3") + << (QVector()) + << (QVector() << "4") + << (QVector()) + << (QVector()) + << (QVector()); + QVector< QVector > needEndings = + QVector< QVector >() + << (QVector()) + << (QVector()) + << (QVector()) + << (QVector() << "2") + << (QVector() << "1") + << (QVector()) + << (QVector() << "3") + << (QVector()) + << (QVector() << "4") + << (QVector() << "0") + << (QVector()); + + checkSectionFormattingLevel(doc, neededBlockCount, needStartings, needEndings); + checkSectionModelLevel(doc); +} - TestDocument() - { - m_document = new QTextDocument(); +void TestKoTextEditor::testBasicSectionCreation() +{ + TestDocument doc; + formSectionTestDocument(&doc); + checkSectionTestDocument(&doc); +} - KoTextDocument textDoc(m_document); - KoTextEditor *editor = new KoTextEditor(m_document); +#include "TestInsertSectionHandling_data.cpp" - textDoc.setInlineTextObjectManager(&m_inlineObjectManager); - textDoc.setTextRangeManager(&m_rangeManager); - textDoc.setStyleManager(new KoStyleManager(0)); - textDoc.setTextEditor(editor); +void TestKoTextEditor::testInsertSectionHandling() +{ + TestDocument doc; + formSectionTestDocument(&doc); - } + KoTextEditor *editor = doc.textEditor(); - virtual ~TestDocument() - { - delete m_document; - } + QFETCH(int, insertPosition); + editor->setPosition(insertPosition); + editor->newSection(); - virtual void addShape(KoShape *shape) - { - m_shapes << shape; - } + QFETCH(int, neededBlockCount); + QFETCH(QVector< QVector >, needStartings); + QFETCH(QVector< QVector >, needEndings); + checkSectionFormattingLevel(&doc, neededBlockCount, needStartings, needEndings); + checkSectionModelLevel(&doc); + + // undo changes and check a source document + KoTextDocument(doc.m_document).undoStack()->undo(); + checkSectionTestDocument(&doc); +} - virtual void removeShape(KoShape *shape) - { - m_shapes.removeAll(shape); - } +#include "TestDeleteSectionHandling_data.cpp" +#include - KoTextEditor *textEditor() - { - return KoTextDocument(m_document).textEditor(); - } +// This test tests delete handling only on Formatting Level +// See KoSectionModel +void TestKoTextEditor::testDeleteSectionHandling() +{ + testBasicSectionCreation(); + // create a document + TestDocument doc; + formSectionTestDocument(&doc); + KoTextEditor *editor = doc.textEditor(); - QList m_shapes; + QFETCH(int, selectionStart); + QFETCH(int, selectionEnd); + QFETCH(int, neededBlockCount); + QFETCH(QVector< QVector >, needStartings); + QFETCH(QVector< QVector >, needEndings); - QTextDocument *m_document; - KoInlineTextObjectManager m_inlineObjectManager; - KoTextRangeManager m_rangeManager; - KoDocumentRdfBase m_rdfBase; -}; + // placing selection + editor->setPosition(selectionStart); + editor->movePosition(QTextCursor::NextCharacter, QTextCursor::KeepAnchor, selectionEnd - selectionStart); + + // doing deletion + editor->deleteChar(); + + checkSectionFormattingLevel(&doc, neededBlockCount, needStartings, needEndings); + checkSectionModelLevel(&doc); + + // undo changes and check a source document + KoTextDocument(doc.m_document).undoStack()->undo(); + checkSectionTestDocument(&doc); +} QTEST_MAIN(TestKoTextEditor) diff --git a/libs/kotext/tests/TestKoTextEditor.h b/libs/kotext/tests/TestKoTextEditor.h index b080ca8703..e61f59dbe8 100644 --- a/libs/kotext/tests/TestKoTextEditor.h +++ b/libs/kotext/tests/TestKoTextEditor.h @@ -1,48 +1,88 @@ /* This file is part of the KDE project * * Copyright (c) 2011 Boudewijn Rempt + * Copyright (c) 2014-2015 Denis Kuplyakov * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef TEST_KO_TEXT_EDITOR_H #define TEST_KO_TEXT_EDITOR_H #include class KoTextEditor; class KoSection; class KoSectionEnd; +class TestDocument; class TestKoTextEditor : public QObject { Q_OBJECT private Q_SLOTS: - void testInsertInlineObject(); + //FIXME: see cpp file: why it is commented out +// void testInsertInlineObject(); void testRemoveSelectedText(); - bool checkEndings(const QVector &needEndings, KoSectionEnd **secEnd, KoTextEditor &editor); - bool checkStartings(const QVector &needStartings, KoSection **sec, KoTextEditor &editor); + // Section tests + void testBasicSectionCreation(); - void pushSectionStart(int num, KoSection *sec, KoTextEditor &editor); - void pushSectionEnd(int num, KoSectionEnd *secEnd, KoTextEditor &editor); + void testInsertSectionHandling_data(); + void testInsertSectionHandling(); void testDeleteSectionHandling_data(); void testDeleteSectionHandling(); + +private: + // Sections stuff + struct SectionHandle + { + explicit SectionHandle(KoSection *_sec) + : sec(_sec) + , parent(0) + { + } + + KoSection *sec; + KoSection *parent; + QList children; + }; + + bool checkEndings(const QVector &needEndings, KoTextEditor *editor); + bool checkStartings(const QVector &needStartings, KoTextEditor *editor); + void checkSectionFormattingLevel( + TestDocument *doc, + int neededBlockCount, + const QVector< QVector > &needStartings, + const QVector< QVector > &needEndings); + void checkSectionModelLevel(TestDocument *doc); + void checkSectionModelLevelRecursive(QModelIndex index, SectionHandle *handle); + + void pushSectionStart(int num, KoSection *sec, KoTextEditor *editor); + void pushSectionEnd(int num, KoSectionEnd *secEnd, KoTextEditor *editor); + void formSectionTestDocument(TestDocument *doc); + void checkSectionTestDocument(TestDocument *doc); + + /** + * This one is used to generate unittest data. + * Use it if you are sure that current implementation is right + * or double check results. + */ + void dumpSectionFormattingLevel(TestDocument *doc); }; #endif // TEST_KO_TEXT_EDITOR_H diff --git a/libs/kotext/tests/TestSection.cpp b/libs/kotext/tests/TestSection.cpp deleted file mode 100644 index 37defd0a77..0000000000 --- a/libs/kotext/tests/TestSection.cpp +++ /dev/null @@ -1,37 +0,0 @@ -/* - * This file is part of Calligra tests - * - * Copyright (C) 2011 Boudewijn Rempt - * - * 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 "TestSection.h" - -#include - -void TestSection::testSection() -{ -} - -void TestSection::testLoadOdf() -{ -} - -void TestSection::testSaveOdf() -{ -} - -QTEST_MAIN(TestSection) diff --git a/libs/kotext/tests/TestSection.h b/libs/kotext/tests/TestSection.h deleted file mode 100644 index 6ae4f14110..0000000000 --- a/libs/kotext/tests/TestSection.h +++ /dev/null @@ -1,36 +0,0 @@ -/* - * This file is part of Calligra tests - * - * Copyright (C) 2011 Boudewijn Rempt - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ -#ifndef TESTSECTION_H -#define TESTSECTION_H - -#include - -class TestSection : public QObject -{ - Q_OBJECT - -private Q_SLOTS: - void testSection(); - void testLoadOdf(); - void testSaveOdf(); - -}; - -#endif diff --git a/plugins/textshape/CMakeLists.txt b/plugins/textshape/CMakeLists.txt index adba13ebce..342912dedd 100644 --- a/plugins/textshape/CMakeLists.txt +++ b/plugins/textshape/CMakeLists.txt @@ -1,197 +1,199 @@ project( textPlugin) add_definitions(-DTRANSLATION_DOMAIN=\"calligra_shape_text\") # only build the textdocument inspector in non-release builds if(NOT RELEASE_BUILD) set(BUILD_TEXTDOCUMENT_INSPECTOR TRUE) add_definitions(-DCREATE_TEXTDOCUMENT_INSPECTOR) else() set(BUILD_TEXTDOCUMENT_INSPECTOR FALSE) endif() add_subdirectory( pics ) #add_subdirectory( tests ) include_directories( ${KOTEXT_INCLUDES} ${TEXTLAYOUT_INCLUDES} ${KOWIDGETS_INCLUDES} ) ########### Flake Plugin library ############### set ( textshape_SRCS TextPlugin.cpp TextShape.cpp TextShapeFactory.cpp TextTool.cpp TextEditingPluginContainer.cpp TextToolFactory.cpp ShrinkToFitShapeContainer.cpp SimpleRootAreaProvider.cpp AnnotationTextShape.cpp AnnotationTextShapeFactory.cpp ChangeTracker.cpp ReviewTool.cpp ReviewToolFactory.cpp TextChanges.cpp TextChange.cpp FontSizeAction.cpp FontFamilyAction.cpp ReferencesTool.cpp ReferencesToolFactory.cpp # dialogs/StylesWidget.cpp # dialogs/SpecialButton.cpp dialogs/StylesCombo.cpp dialogs/StylesComboPreview.cpp dialogs/DockerStylesComboModel.cpp dialogs/SimpleCharacterWidget.cpp dialogs/SimpleParagraphWidget.cpp dialogs/SimpleTableWidget.cpp dialogs/SimpleInsertWidget.cpp dialogs/LinkInsertionDialog.cpp dialogs/SimpleTableOfContentsWidget.cpp dialogs/SimpleCitationBibliographyWidget.cpp dialogs/SimpleLinksWidget.cpp dialogs/SimpleSpellCheckingWidget.cpp dialogs/CitationInsertionDialog.cpp dialogs/InsertBibliographyDialog.cpp dialogs/SimpleFootEndNotesWidget.cpp dialogs/NotesConfigurationDialog.cpp dialogs/SimpleCaptionsWidget.cpp dialogs/ParagraphLayout.cpp dialogs/ParagraphIndentSpacing.cpp dialogs/ParagraphDecorations.cpp dialogs/ParagraphBulletsNumbers.cpp dialogs/ParagraphSettingsDialog.cpp dialogs/ParagraphDropCaps.cpp dialogs/ListsSpinBox.cpp dialogs/StylesModel.cpp dialogs/StylesManagerModel.cpp dialogs/StylesSortFilterProxyModel.cpp dialogs/AbstractStylesModel.cpp dialogs/StylesFilteredModelBase.cpp dialogs/ValidParentStylesProxyModel.cpp dialogs/StylesDelegate.cpp dialogs/StyleManager.cpp dialogs/StyleManagerDialog.cpp dialogs/ParagraphGeneral.cpp dialogs/CharacterGeneral.cpp dialogs/CharacterHighlighting.cpp dialogs/InsertCharacter.cpp dialogs/FontDia.cpp dialogs/FontDecorations.cpp dialogs/LanguageTab.cpp dialogs/FormattingPreview.cpp dialogs/StyleManagerWelcome.cpp dialogs/TableDialog.cpp dialogs/QuickTableButton.cpp dialogs/FormattingButton.cpp dialogs/ChangeConfigureDialog.cpp dialogs/AcceptRejectChangeDialog.cpp dialogs/TrackedChangeModel.cpp dialogs/TrackedChangeManager.cpp dialogs/BibliographyConfigureDialog.cpp dialogs/TableOfContentsConfigure.cpp dialogs/TableOfContentsStyleConfigure.cpp dialogs/TableOfContentsStyleModel.cpp dialogs/TableOfContentsStyleDelegate.cpp dialogs/TableOfContentsPreview.cpp dialogs/TableOfContentsEntryDelegate.cpp dialogs/TableOfContentsEntryModel.cpp dialogs/TableOfContentsTemplate.cpp dialogs/BibliographyTemplate.cpp dialogs/BibliographyPreview.cpp dialogs/ListLevelChooser.cpp dialogs/SimpleAnnotationWidget.cpp dialogs/ManageBookmarkDialog.cpp dialogs/SectionFormatDialog.cpp + dialogs/SectionsSplitDialog.cpp commands/ChangeListLevelCommand.cpp commands/ShowChangesCommand.cpp commands/AcceptChangeCommand.cpp commands/RejectChangeCommand.cpp commands/AutoResizeCommand.cpp ) if(BUILD_TEXTDOCUMENT_INSPECTOR) set ( textshape_SRCS ${textshape_SRCS} TextDocumentStructureModel.cpp TextDocumentInspectionDocker.cpp TextDocumentInspectionDockerFactory.cpp TextDocumentInspectionPlugin.cpp ) endif() ki18n_wrap_ui(textshape_SRCS dialogs/SimpleCharacterWidget.ui dialogs/SimpleParagraphWidget.ui dialogs/SimpleTableWidget.ui dialogs/SimpleInsertWidget.ui dialogs/SimpleTableOfContentsWidget.ui dialogs/SimpleCitationBibliographyWidget.ui dialogs/SimpleSpellCheckingWidget.ui dialogs/CitationInsertionDialog.ui dialogs/InsertBibliographyDialog.ui dialogs/SimpleFootEndNotesWidget.ui dialogs/NotesConfigurationDialog.ui dialogs/SimpleCaptionsWidget.ui dialogs/StylesWidget.ui dialogs/ParagraphLayout.ui dialogs/ParagraphIndentSpacing.ui dialogs/ParagraphDecorations.ui dialogs/ParagraphBulletsNumbers.ui dialogs/ParagraphDropCaps.ui dialogs/StyleManager.ui dialogs/CharacterGeneral.ui dialogs/CharacterHighlighting.ui dialogs/StyleManagerWelcome.ui dialogs/TableDialog.ui dialogs/BibliographyConfigureDialog.ui dialogs/TableOfContentsConfigure.ui dialogs/SimpleLinksWidget.ui dialogs/TableOfContentsStyleConfigure.ui dialogs/SimpleAnnotationWidget.ui dialogs/FontDecorations.ui dialogs/LanguageTab.ui dialogs/ChangeConfigureDialog.ui dialogs/AcceptRejectChangeDialog.ui dialogs/TrackedChangeManager.ui dialogs/LinkInsertionDialog.ui dialogs/ManageBookmark.ui dialogs/SectionFormatDialog.ui + dialogs/SectionsSplitDialog.ui ) add_library(calligra_shape_text MODULE ${textshape_SRCS}) kcoreaddons_desktop_to_json(calligra_shape_text calligra_shape_text.desktop) target_link_libraries(calligra_shape_text kotext kotextlayout kowidgets ${KDE4_KFILE_LIBS} ${KDE4_KIO_LIBS}) if( SHOULD_BUILD_FEATURE_RDF ) target_link_libraries(calligra_shape_text ${SOPRANO_LIBRARIES}) endif() install(TARGETS calligra_shape_text DESTINATION ${CALLIGRA_PLUGIN_INSTALL_DIR}) ########### install files ############### #ecm_install_icons( ${DATA_INSTALL_DIR}/calligra/icons ) if(BUILD_TEXTDOCUMENT_INSPECTOR) # workaround: to avoid translations the real desktop file is only generated configure_file(${CMAKE_CURRENT_SOURCE_DIR}/calligra_docker_textdocumentinspection.desktop.in ${CMAKE_CURRENT_BINARY_DIR}/calligra_docker_textdocumentinspection.desktop) install( FILES ${CMAKE_CURRENT_BINARY_DIR}/calligra_docker_textdocumentinspection.desktop DESTINATION ${SERVICES_INSTALL_DIR}/calligra) endif() diff --git a/plugins/textshape/TextTool.cpp b/plugins/textshape/TextTool.cpp index 1fe70f410c..42b8858657 100644 --- a/plugins/textshape/TextTool.cpp +++ b/plugins/textshape/TextTool.cpp @@ -1,3092 +1,3110 @@ /* This file is part of the KDE project * Copyright (C) 2006-2010 Thomas Zander * Copyright (C) 2008 Thorsten Zachmann * Copyright (C) 2008 Girish Ramakrishnan * Copyright (C) 2008, 2012 Pierre Stirnweiss * Copyright (C) 2009 KO GmbH * Copyright (C) 2011 Mojtaba Shahi Senobari * Copyright (C) 2014 Denis Kuplyakov * * 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 "TextTool.h" #include "TextEditingPluginContainer.h" #include "dialogs/SimpleCharacterWidget.h" #include "dialogs/SimpleParagraphWidget.h" #include "dialogs/SimpleTableWidget.h" #include "dialogs/SimpleInsertWidget.h" #include "dialogs/ParagraphSettingsDialog.h" #include "dialogs/StyleManagerDialog.h" #include "dialogs/InsertCharacter.h" #include "dialogs/FontDia.h" #include "dialogs/TableDialog.h" #include "dialogs/SectionFormatDialog.h" +#include "dialogs/SectionsSplitDialog.h" #include "dialogs/SimpleTableWidget.h" #include "commands/AutoResizeCommand.h" #include "commands/ChangeListLevelCommand.h" #include "FontSizeAction.h" #include "FontFamilyAction.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 #include #include #include #include #include #include #include "AnnotationTextShape.h" #define AnnotationShape_SHAPEID "AnnotationTextShapeID" #include "KoShapeBasedDocumentBase.h" #include #include #include #include class TextToolSelection : public KoToolSelection { public: TextToolSelection(QWeakPointer editor) : KoToolSelection(0) , m_editor(editor) { } bool hasSelection() { if (!m_editor.isNull()) { return m_editor.data()->hasSelection(); } return false; } QWeakPointer m_editor; }; static bool hit(const QKeySequence &input, KStandardShortcut::StandardShortcut shortcut) { foreach (const QKeySequence & ks, KStandardShortcut::shortcut(shortcut)) { if (input == ks) return true; } return false; } TextTool::TextTool(KoCanvasBase *canvas) : KoToolBase(canvas) , m_textShape(0) , m_textShapeData(0) , m_changeTracker(0) , m_allowActions(true) , m_allowAddUndoCommand(true) , m_allowResourceManagerUpdates(true) , m_prevCursorPosition(-1) , m_caretTimer(this) , m_caretTimerState(true) , m_currentCommand(0) , m_currentCommandHasChildren(false) , m_specialCharacterDocker(0) , m_textTyping(false) , m_textDeleting(false) , m_editTipTimer(this) , m_delayedEnsureVisible(false) , m_toolSelection(0) , m_tableDraggedOnce(false) , m_tablePenMode(false) , m_lastImMicroFocus(QRectF(0,0,0,0)) , m_drag(0) { setTextMode(true); createActions(); m_unit = canvas->resourceManager()->unitResource(KoCanvasResourceManager::Unit); foreach (KoTextEditingPlugin* plugin, textEditingPluginContainer()->values()) { connect(plugin, SIGNAL(startMacro(const QString &)), this, SLOT(startMacro(const QString &))); connect(plugin, SIGNAL(stopMacro()), this, SLOT(stopMacro())); QHash actions = plugin->actions(); QHash::iterator i = actions.begin(); while (i != actions.end()) { addAction(i.key(), i.value()); ++i; } } // setup the context list. QSignalMapper *signalMapper = new QSignalMapper(this); connect(signalMapper, SIGNAL(mapped(QString)), this, SLOT(startTextEditingPlugin(QString))); QList list; list.append(this->action("format_font")); foreach (const QString &key, KoTextEditingRegistry::instance()->keys()) { KoTextEditingFactory *factory = KoTextEditingRegistry::instance()->value(key); if (factory->showInMenu()) { KAction *a = new KAction(factory->title(), this); connect(a, SIGNAL(triggered()), signalMapper, SLOT(map())); signalMapper->setMapping(a, factory->id()); list.append(a); addAction(QString("apply_%1").arg(factory->id()), a); } } setPopupActionList(list); connect(canvas->shapeManager()->selection(), SIGNAL(selectionChanged()), this, SLOT(shapeAddedToCanvas())); m_caretTimer.setInterval(500); connect(&m_caretTimer, SIGNAL(timeout()), this, SLOT(blinkCaret())); m_editTipTimer.setInterval(500); m_editTipTimer.setSingleShot(true); connect(&m_editTipTimer, SIGNAL(timeout()), this, SLOT(showEditTip())); } void TextTool::createActions() { bool useAdvancedText = !(canvas()->resourceManager()->intResource(KoCanvasResourceManager::ApplicationSpeciality) & KoCanvasResourceManager::NoAdvancedText); m_actionConfigureSection = new KAction(koIcon("configure"), i18n("Configure current section"), this); //FIXME: Find another icon for this. addAction("configure_section", m_actionConfigureSection); connect(m_actionConfigureSection, SIGNAL(triggered(bool)), this, SLOT(configureSection())); m_actionInsertSection = new KAction(koIcon("insert-text"), i18n("Insert new section"), this); //FIXME: Find another icon for this. addAction("insert_section", m_actionInsertSection); connect(m_actionInsertSection, SIGNAL(triggered(bool)), this, SLOT(insertNewSection())); + m_actionSplitSections = new KAction(koIcon("split"), i18n("Insert paragraph between sections"), this); //FIXME: Find another icon for this. + addAction("split_sections", m_actionSplitSections); + connect(m_actionSplitSections, SIGNAL(triggered(bool)), this, SLOT(splitSections())); + m_actionPasteAsText = new KAction(koIcon("edit-paste"), i18n("Paste As Text"), this); addAction("edit_paste_text", m_actionPasteAsText); m_actionPasteAsText->setShortcut(Qt::CTRL + Qt::SHIFT + Qt::Key_V); connect(m_actionPasteAsText, SIGNAL(triggered(bool)), this, SLOT(pasteAsText())); m_actionFormatBold = new KAction(koIcon("format-text-bold"), i18n("Bold"), this); addAction("format_bold", m_actionFormatBold); m_actionFormatBold->setShortcut(Qt::CTRL + Qt::Key_B); m_actionFormatBold->setCheckable(true); connect(m_actionFormatBold, SIGNAL(triggered(bool)), this, SLOT(bold(bool))); m_actionFormatItalic = new KAction(koIcon("format-text-italic"), i18n("Italic"), this); addAction("format_italic", m_actionFormatItalic); m_actionFormatItalic->setShortcut(Qt::CTRL + Qt::Key_I); m_actionFormatItalic->setCheckable(true); connect(m_actionFormatItalic, SIGNAL(triggered(bool)), this, SLOT(italic(bool))); m_actionFormatUnderline = new KAction(koIcon("format-text-underline"), i18nc("Text formatting", "Underline"), this); addAction("format_underline", m_actionFormatUnderline); m_actionFormatUnderline->setShortcut(Qt::CTRL + Qt::Key_U); m_actionFormatUnderline->setCheckable(true); connect(m_actionFormatUnderline, SIGNAL(triggered(bool)), this, SLOT(underline(bool))); m_actionFormatStrikeOut = new KAction(koIcon("format-text-strikethrough"), i18n("Strikethrough"), this); addAction("format_strike", m_actionFormatStrikeOut); m_actionFormatStrikeOut->setCheckable(true); connect(m_actionFormatStrikeOut, SIGNAL(triggered(bool)), this, SLOT(strikeOut(bool))); QActionGroup *alignmentGroup = new QActionGroup(this); m_actionAlignLeft = new KAction(koIcon("format-justify-left"), i18n("Align Left"), this); addAction("format_alignleft", m_actionAlignLeft); m_actionAlignLeft->setShortcut(Qt::CTRL + Qt::Key_L); m_actionAlignLeft->setCheckable(true); alignmentGroup->addAction(m_actionAlignLeft); connect(m_actionAlignLeft, SIGNAL(triggered(bool)), this, SLOT(alignLeft())); m_actionAlignRight = new KAction(koIcon("format-justify-right"), i18n("Align Right"), this); addAction("format_alignright", m_actionAlignRight); m_actionAlignRight->setShortcut(Qt::CTRL + Qt::Key_R); m_actionAlignRight->setCheckable(true); alignmentGroup->addAction(m_actionAlignRight); connect(m_actionAlignRight, SIGNAL(triggered(bool)), this, SLOT(alignRight())); m_actionAlignCenter = new KAction(koIcon("format-justify-center"), i18n("Align Center"), this); addAction("format_aligncenter", m_actionAlignCenter); m_actionAlignCenter->setShortcut(Qt::CTRL + Qt::Key_E); m_actionAlignCenter->setCheckable(true); alignmentGroup->addAction(m_actionAlignCenter); connect(m_actionAlignCenter, SIGNAL(triggered(bool)), this, SLOT(alignCenter())); m_actionAlignBlock = new KAction(koIcon("format-justify-fill"), i18n("Align Block"), this); addAction("format_alignblock", m_actionAlignBlock); m_actionAlignBlock->setShortcut(Qt::CTRL + Qt::Key_J); m_actionAlignBlock->setCheckable(true); alignmentGroup->addAction(m_actionAlignBlock); connect(m_actionAlignBlock, SIGNAL(triggered(bool)), this, SLOT(alignBlock())); m_actionChangeDirection = new KAction(koIcon("format-text-direction-rtl"), i18n("Change text direction"), this); addAction("change_text_direction", m_actionChangeDirection); m_actionChangeDirection->setToolTip(i18n("Change writing direction")); m_actionChangeDirection->setShortcut(Qt::CTRL + Qt::SHIFT + Qt::Key_D); m_actionChangeDirection->setCheckable(true); connect(m_actionChangeDirection, SIGNAL(triggered()), this, SLOT(textDirectionChanged())); m_actionFormatSuper = new KAction(koIcon("format-text-superscript"), i18n("Superscript"), this); m_actionFormatSuper->setShortcut(Qt::CTRL + Qt::SHIFT + Qt::Key_P); addAction("format_super", m_actionFormatSuper); m_actionFormatSuper->setCheckable(true); connect(m_actionFormatSuper, SIGNAL(triggered(bool)), this, SLOT(superScript(bool))); m_actionFormatSub = new KAction(koIcon("format-text-subscript"), i18n("Subscript"), this); m_actionFormatSub->setShortcut(Qt::CTRL + Qt::SHIFT + Qt::Key_B); addAction("format_sub", m_actionFormatSub); m_actionFormatSub->setCheckable(true); connect(m_actionFormatSub, SIGNAL(triggered(bool)), this, SLOT(subScript(bool))); const char* const increaseIndentActionIconName = QApplication::isRightToLeft() ? koIconNameCStr("format-indent-less") : koIconNameCStr("format-indent-more"); m_actionFormatIncreaseIndent = new KAction( KIcon(QLatin1String(increaseIndentActionIconName)), i18n("Increase Indent"), this); addAction("format_increaseindent", m_actionFormatIncreaseIndent); connect(m_actionFormatIncreaseIndent, SIGNAL(triggered()), this, SLOT(increaseIndent())); const char* const decreaseIndentActionIconName = QApplication::isRightToLeft() ? koIconNameCStr("format-indent-more") : koIconNameCStr("format-indent-less"); m_actionFormatDecreaseIndent = new KAction(KIcon(QLatin1String(decreaseIndentActionIconName)), i18n("Decrease Indent"), this); addAction("format_decreaseindent", m_actionFormatDecreaseIndent); connect(m_actionFormatDecreaseIndent, SIGNAL(triggered()), this, SLOT(decreaseIndent())); QAction *action = new KAction(koIcon("format-list-unordered"), i18n("Bullet list"), this); addAction("format_bulletlist", action); action = new KAction(koIcon("format-list-ordered"), i18n("Numbered list"), this); addAction("format_numberlist", action); action = new KAction(i18n("Increase Font Size"), this); action->setShortcut(Qt::CTRL + Qt::Key_Greater); addAction("fontsizeup", action); connect(action, SIGNAL(triggered()), this, SLOT(increaseFontSize())); action = new KAction(i18n("Decrease Font Size"), this); action->setShortcut(Qt::CTRL + Qt::Key_Less); addAction("fontsizedown", action); connect(action, SIGNAL(triggered()), this, SLOT(decreaseFontSize())); m_actionFormatFontFamily = new KoFontFamilyAction(this); m_actionFormatFontFamily->setText(i18n("Font Family")); addAction("format_fontfamily", m_actionFormatFontFamily); connect(m_actionFormatFontFamily, SIGNAL(triggered(const QString &)), this, SLOT(setFontFamily(const QString &))); m_variableMenu = new KActionMenu(i18n("Variable"), this); addAction("insert_variable", m_variableMenu); // ------------------- Actions with a key binding and no GUI item action = new KAction(i18n("Insert Non-Breaking Space"), this); addAction("nonbreaking_space", action); action->setShortcut(Qt::CTRL + Qt::Key_Space); connect(action, SIGNAL(triggered()), this, SLOT(nonbreakingSpace())); action = new KAction(i18n("Insert Non-Breaking Hyphen"), this); addAction("nonbreaking_hyphen", action); action->setShortcut(Qt::CTRL + Qt::SHIFT + Qt::Key_Minus); connect(action, SIGNAL(triggered()), this, SLOT(nonbreakingHyphen())); action = new KAction(i18n("Insert Index"), this); action->setShortcut(Qt::CTRL + Qt::Key_T); addAction("insert_index", action); connect(action, SIGNAL(triggered()), this, SLOT(insertIndexMarker())); action = new KAction(i18n("Insert Soft Hyphen"), this); addAction("soft_hyphen", action); //action->setShortcut(Qt::CTRL + Qt::Key_Minus); // TODO this one is also used for the kde-global zoom-out :( connect(action, SIGNAL(triggered()), this, SLOT(softHyphen())); if (useAdvancedText) { action = new KAction(i18n("Line Break"), this); addAction("line_break", action); action->setShortcut(Qt::SHIFT + Qt::Key_Return); connect(action, SIGNAL(triggered()), this, SLOT(lineBreak())); action = new KAction(koIcon("insert-pagebreak"), i18n("Page Break"), this); addAction("insert_framebreak", action); action->setShortcut(Qt::CTRL + Qt::Key_Return); connect(action, SIGNAL(triggered()), this, SLOT(insertFrameBreak())); action->setToolTip(i18n("Insert a page break")); action->setWhatsThis(i18n("All text after this point will be moved into the next page.")); } action = new KAction(i18n("Font..."), this); addAction("format_font", action); action->setShortcut(Qt::ALT + Qt::CTRL + Qt::Key_F); action->setToolTip(i18n("Change character size, font, boldface, italics etc.")); action->setWhatsThis(i18n("Change the attributes of the currently selected characters.")); connect(action, SIGNAL(triggered()), this, SLOT(selectFont())); m_actionFormatFontSize = new FontSizeAction(i18n("Font Size"), this); addAction("format_fontsize", m_actionFormatFontSize); connect(m_actionFormatFontSize, SIGNAL(fontSizeChanged(qreal)), this, SLOT(setFontSize(qreal))); m_actionFormatTextColor = new KoColorPopupAction(this); m_actionFormatTextColor->setIcon(koIcon("format-text-color")); m_actionFormatTextColor->setToolTip(i18n("Text Color...")); m_actionFormatTextColor->setText(i18n("Text Color")); addAction("format_textcolor", m_actionFormatTextColor); connect(m_actionFormatTextColor, SIGNAL(colorChanged(const KoColor &)), this, SLOT(setTextColor(const KoColor &))); m_actionFormatBackgroundColor = new KoColorPopupAction(this); m_actionFormatBackgroundColor->setIcon(koIcon("format-fill-color")); m_actionFormatBackgroundColor->setToolTip(i18n("Background Color...")); m_actionFormatBackgroundColor->setText(i18n("Background")); addAction("format_backgroundcolor", m_actionFormatBackgroundColor); connect(m_actionFormatBackgroundColor, SIGNAL(colorChanged(const KoColor &)), this, SLOT(setBackgroundColor(const KoColor &))); m_growWidthAction = new KAction(koIcon("zoom-fit-best"), i18n("Grow To Fit Width"), this); addAction("grow_to_fit_width", m_growWidthAction); m_growWidthAction->setCheckable(true); connect(m_growWidthAction, SIGNAL(triggered(bool)), this, SLOT(setGrowWidthToFit(bool))); m_growHeightAction = new KAction(koIcon("zoom-fit-best"), i18n("Grow To Fit Height"), this); addAction("grow_to_fit_height", m_growHeightAction); m_growHeightAction->setCheckable(true); connect(m_growHeightAction, SIGNAL(triggered(bool)), this, SLOT(setGrowHeightToFit(bool))); m_shrinkToFitAction = new KAction(koIcon("zoom-fit-best"), i18n("Shrink To Fit"), this); addAction("shrink_to_fit", m_shrinkToFitAction); m_shrinkToFitAction->setCheckable(true); connect(m_shrinkToFitAction, SIGNAL(triggered(bool)), this, SLOT(setShrinkToFit(bool))); if (useAdvancedText) { action = new KAction(koIcon("insert-table"), i18n("Insert Custom..."), this); addAction("insert_table", action); action->setToolTip(i18n("Insert a table into the document.")); connect(action, SIGNAL(triggered()), this, SLOT(insertTable())); action = new KAction(koIcon("edit-table-insert-row-above"), i18n("Row Above"), this); action->setToolTip(i18n("Insert Row Above")); addAction("insert_tablerow_above", action); connect(action, SIGNAL(triggered(bool)), this, SLOT(insertTableRowAbove())); action = new KAction(koIcon("edit-table-insert-row-below"), i18n("Row Below"), this); action->setToolTip(i18n("Insert Row Below")); addAction("insert_tablerow_below", action); connect(action, SIGNAL(triggered(bool)), this, SLOT(insertTableRowBelow())); action = new KAction(koIcon("edit-table-insert-column-left"), i18n("Column Left"), this); action->setToolTip(i18n("Insert Column Left")); addAction("insert_tablecolumn_left", action); connect(action, SIGNAL(triggered(bool)), this, SLOT(insertTableColumnLeft())); action = new KAction(koIcon("edit-table-insert-column-right"), i18n("Column Right"), this); action->setToolTip(i18n("Insert Column Right")); addAction("insert_tablecolumn_right", action); connect(action, SIGNAL(triggered(bool)), this, SLOT(insertTableColumnRight())); action = new KAction(koIcon("edit-table-delete-column"), i18n("Column"), this); action->setToolTip(i18n("Delete Column")); addAction("delete_tablecolumn", action); connect(action, SIGNAL(triggered(bool)), this, SLOT(deleteTableColumn())); action = new KAction(koIcon("edit-table-delete-row"), i18n("Row"), this); action->setToolTip(i18n("Delete Row")); addAction("delete_tablerow", action); connect(action, SIGNAL(triggered(bool)), this, SLOT(deleteTableRow())); action = new KAction(koIcon("edit-table-cell-merge"), i18n("Merge Cells"), this); addAction("merge_tablecells", action); connect(action, SIGNAL(triggered(bool)), this, SLOT(mergeTableCells())); action = new KAction(koIcon("edit-table-cell-split"), i18n("Split Cells"), this); addAction("split_tablecells", action); connect(action, SIGNAL(triggered(bool)), this, SLOT(splitTableCells())); action = new KAction(koIcon("borderpainter"), "", this); action->setToolTip(i18n("Select a border style and paint that style onto a table")); addAction("activate_borderpainter", action); } action = new KAction(i18n("Paragraph..."), this); addAction("format_paragraph", action); action->setShortcut(Qt::ALT + Qt::CTRL + Qt::Key_P); action->setToolTip(i18n("Change paragraph margins, text flow, borders, bullets, numbering etc.")); action->setWhatsThis(i18n("

Change paragraph margins, text flow, borders, bullets, numbering etc.

Select text in multiple paragraphs to change the formatting of all selected paragraphs.

If no text is selected, the paragraph where the cursor is located will be changed.

")); connect(action, SIGNAL(triggered()), this, SLOT(formatParagraph())); action = new KAction(i18n("Style Manager..."), this); action->setShortcut(Qt::ALT + Qt::CTRL + Qt::Key_S); action->setToolTip(i18n("Change attributes of styles")); action->setWhatsThis(i18n("

Change font and paragraph attributes of styles.

Multiple styles can be changed using the dialog box.

")); addAction("format_stylist", action); connect(action, SIGNAL(triggered()), this, SLOT(showStyleManager())); action = KStandardAction::selectAll(this, SLOT(selectAll()), this); addAction("edit_select_all", action); action = new KAction(i18n("Special Character..."), this); action->setIcon(koIcon("character-set")); action->setShortcut(Qt::ALT + Qt::SHIFT + Qt::Key_C); addAction("insert_specialchar", action); action->setToolTip(i18n("Insert one or more symbols or characters not found on the keyboard")); action->setWhatsThis(i18n("Insert one or more symbols or characters not found on the keyboard.")); connect(action, SIGNAL(triggered()), this, SLOT(insertSpecialCharacter())); action = new KAction(i18n("Repaint"), this); action->setIcon(koIcon("view-refresh")); addAction("repaint", action); connect(action, SIGNAL(triggered()), this, SLOT(relayoutContent())); action = new KAction(i18n("Insert Comment"), this); addAction("insert_annotation", action); action->setShortcut(Qt::CTRL + Qt::SHIFT + Qt::Key_C); connect(action, SIGNAL(triggered()), this, SLOT(insertAnnotation())); #ifndef NDEBUG action = new KAction("Paragraph Debug", this); // do NOT add i18n! action->setShortcut(Qt::CTRL + Qt::SHIFT + Qt::ALT + Qt::Key_P); addAction("detailed_debug_paragraphs", action); connect(action, SIGNAL(triggered()), this, SLOT(debugTextDocument())); action = new KAction("Styles Debug", this); // do NOT add i18n! action->setShortcut(Qt::CTRL + Qt::SHIFT + Qt::ALT + Qt::Key_S); addAction("detailed_debug_styles", action); connect(action, SIGNAL(triggered()), this, SLOT(debugTextStyles())); #endif } #ifndef NDEBUG #include "tests/MockShapes.h" #include #include TextTool::TextTool(MockCanvas *canvas) // constructor for our unit tests; : KoToolBase(canvas), m_textShape(0), m_textShapeData(0), m_changeTracker(0), m_allowActions(true), m_allowAddUndoCommand(true), m_allowResourceManagerUpdates(true), m_prevCursorPosition(-1), m_caretTimer(this), m_caretTimerState(true), m_currentCommand(0), m_currentCommandHasChildren(false), m_specialCharacterDocker(0), m_textEditingPlugins(0) , m_editTipTimer(this) , m_delayedEnsureVisible(false) , m_tableDraggedOnce(false) , m_tablePenMode(false) { // we could init some vars here, but we probably don't have to QLocale::setDefault(QLocale("en")); QTextDocument *document = new QTextDocument(); // this document is leaked KoInlineTextObjectManager *inlineManager = new KoInlineTextObjectManager(); KoTextDocument(document).setInlineTextObjectManager(inlineManager); KoTextRangeManager *locationManager = new KoTextRangeManager(); KoTextDocument(document).setTextRangeManager(locationManager); m_textEditor = new KoTextEditor(document); KoTextDocument(document).setTextEditor(m_textEditor.data()); m_toolSelection = new TextToolSelection(m_textEditor); m_changeTracker = new KoChangeTracker(); KoTextDocument(document).setChangeTracker(m_changeTracker); KoTextDocument(document).setUndoStack(new KUndo2Stack()); } #endif TextTool::~TextTool() { delete m_toolSelection; } void TextTool::showEditTip() { if (!m_textShapeData || m_editTipPointedAt.position == -1) return; QTextCursor c(m_textShapeData->document()); c.setPosition(m_editTipPointedAt.position); QString text = "

"; int toolTipWidth = 0; if (m_changeTracker && m_changeTracker->containsInlineChanges(c.charFormat()) && m_changeTracker->displayChanges()) { KoChangeTrackerElement *element = m_changeTracker->elementById(c.charFormat().property(KoCharacterStyle::ChangeTrackerId).toInt()); if (element->isEnabled()) { QString changeType; if (element->getChangeType() == KoGenChange::InsertChange) changeType = i18n("Insertion"); else if (element->getChangeType() == KoGenChange::DeleteChange) changeType = i18n("Deletion"); else changeType = i18n("Formatting"); text += "" + changeType + "
"; QString date = element->getDate(); //Remove the T which separates the Data and Time. date[10] = QLatin1Char(' '); date = element->getCreator() + QLatin1Char(' ') + date; text += date + "

"; toolTipWidth = QFontMetrics(QToolTip::font()).boundingRect(date).width(); } } if (m_editTipPointedAt.bookmark || !m_editTipPointedAt.externalHRef.isEmpty()) { QString help = i18n("Ctrl+click to go to link "); help += m_editTipPointedAt.externalHRef; text += help + "

"; toolTipWidth = QFontMetrics(QToolTip::font()).boundingRect(help).width(); } if (m_editTipPointedAt.note) { QString help = i18n("Ctrl+click to go to the note "); text += help + "

"; toolTipWidth = QFontMetrics(QToolTip::font()).boundingRect(help).width(); } if (m_editTipPointedAt.noteReference>0) { QString help = i18n("Ctrl+click to go to the note reference"); text += help + "

"; toolTipWidth = QFontMetrics(QToolTip::font()).boundingRect(help).width(); } QToolTip::hideText(); if (toolTipWidth) { QRect keepRect(m_editTipPos - QPoint(3,3), QSize(6,6)); QToolTip::showText(m_editTipPos - QPoint(toolTipWidth/2, 0), text, canvas()->canvasWidget(), keepRect); } } void TextTool::blinkCaret() { if (!(canvas()->canvasWidget() ? canvas()->canvasWidget()->hasFocus() : canvas()->canvasItem()->hasFocus())) { m_caretTimer.stop(); m_caretTimerState = false; // not visible. } else { m_caretTimerState = !m_caretTimerState; } repaintCaret(); } void TextTool::relayoutContent() { KoTextDocumentLayout *lay = qobject_cast(m_textShapeData->document()->documentLayout()); Q_ASSERT(lay); foreach (KoTextLayoutRootArea *rootArea, lay->rootAreas()) { rootArea->setDirty(); } lay->emitLayoutIsDirty(); } void TextTool::paint(QPainter &painter, const KoViewConverter &converter) { if (m_textEditor.isNull()) return; if (canvas() && (( canvas()->canvasWidget() && canvas()->canvasWidget()->hasFocus()) || (canvas()->canvasItem() && canvas()->canvasItem()->hasFocus()) ) && !m_caretTimer.isActive()) { // make sure we blink m_caretTimer.start(); m_caretTimerState = true; } if (!m_caretTimerState) m_caretTimer.setInterval(500); // we set it lower during typing, so set it back to normal if (!m_textShapeData) return; if (m_textShapeData->isDirty()) return; qreal zoomX, zoomY; converter.zoom(&zoomX, &zoomY); painter.save(); QTransform shapeMatrix = m_textShape->absoluteTransformation(&converter); shapeMatrix.scale(zoomX, zoomY); shapeMatrix.translate(0, -m_textShapeData->documentOffset()); // Possibly draw table dragging visual cues const qreal boxHeight = 20; if (m_tableDragInfo.tableHit == KoPointedAt::ColumnDivider) { QPointF anchorPos = m_tableDragInfo.tableDividerPos - QPointF(m_dx, 0.0); if (m_tableDragInfo.tableColumnDivider > 0) { //let's draw left qreal w = m_tableDragInfo.tableLeadSize - m_dx; QRectF rect(anchorPos - QPointF(w, 0.0), QSizeF(w, 0.0)); QRectF drawRect(shapeMatrix.map(rect.topLeft()), shapeMatrix.map(rect.bottomRight())); drawRect.setHeight(boxHeight); drawRect.moveTop(drawRect.top() - 1.5 * boxHeight); QString label = m_unit.toUserStringValue(w); int labelWidth = QFontMetrics(QToolTip::font()).boundingRect(label).width(); painter.fillRect(drawRect, QColor(64, 255, 64, 196)); painter.setPen(QColor(0, 0, 0, 196)); if (labelWidth + 10 < drawRect.width()) { QPointF centerLeft(drawRect.left(), drawRect.center().y()); QPointF centerRight(drawRect.right(), drawRect.center().y()); painter.drawLine(centerLeft, drawRect.center() - QPointF(labelWidth/2+5, 0.0)); painter.drawLine(centerLeft, centerLeft + QPointF(7, -5)); painter.drawLine(centerLeft, centerLeft + QPointF(7, 5)); painter.drawLine(drawRect.center() + QPointF(labelWidth/2+5, 0.0), centerRight); painter.drawLine(centerRight, centerRight + QPointF(-7, -5)); painter.drawLine(centerRight, centerRight + QPointF(-7, 5)); painter.drawText(drawRect, Qt::AlignCenter, label); } } if (m_tableDragInfo.tableColumnDivider < m_tableDragInfo.table->columns()) { //let's draw right qreal w = m_tableDragInfo.tableTrailSize + m_dx; QRectF rect(anchorPos, QSizeF(w, 0.0)); QRectF drawRect(shapeMatrix.map(rect.topLeft()), shapeMatrix.map(rect.bottomRight())); drawRect.setHeight(boxHeight); drawRect.moveTop(drawRect.top() - 1.5 * boxHeight); QString label; int labelWidth; if (m_tableDragWithShift) { label = i18n("follows along"); labelWidth = QFontMetrics(QToolTip::font()).boundingRect(label).width(); drawRect.setWidth(2 * labelWidth); QLinearGradient g(drawRect.topLeft(), drawRect.topRight()); g.setColorAt(0.6, QColor(255, 64, 64, 196)); g.setColorAt(1.0, QColor(255, 64, 64, 0)); QBrush brush(g); painter.fillRect(drawRect, brush); } else { label = m_unit.toUserStringValue(w); labelWidth = QFontMetrics(QToolTip::font()).boundingRect(label).width(); drawRect.setHeight(boxHeight); painter.fillRect(drawRect, QColor(64, 255, 64, 196)); } painter.setPen(QColor(0, 0, 0, 196)); if (labelWidth + 10 < drawRect.width()) { QPointF centerLeft(drawRect.left(), drawRect.center().y()); QPointF centerRight(drawRect.right(), drawRect.center().y()); painter.drawLine(centerLeft, drawRect.center() - QPointF(labelWidth/2+5, 0.0)); painter.drawLine(centerLeft, centerLeft + QPointF(7, -5)); painter.drawLine(centerLeft, centerLeft + QPointF(7, 5)); if (!m_tableDragWithShift) { painter.drawLine(drawRect.center() + QPointF(labelWidth/2+5, 0.0), centerRight); painter.drawLine(centerRight, centerRight + QPointF(-7, -5)); painter.drawLine(centerRight, centerRight + QPointF(-7, 5)); } painter.drawText(drawRect, Qt::AlignCenter, label); } if (!m_tableDragWithShift) { // let's draw a helper text too label = i18n("Press shift to not resize this"); labelWidth = QFontMetrics(QToolTip::font()).boundingRect(label).width(); labelWidth += 10; //if (labelWidth < drawRect.width()) { drawRect.moveTop(drawRect.top() + boxHeight); drawRect.moveLeft(drawRect.left() + (drawRect.width() - labelWidth)/2); drawRect.setWidth(labelWidth); painter.fillRect(drawRect, QColor(64, 255, 64, 196)); painter.drawText(drawRect, Qt::AlignCenter, label); } } } } // Possibly draw table dragging visual cues if (m_tableDragInfo.tableHit == KoPointedAt::RowDivider) { QPointF anchorPos = m_tableDragInfo.tableDividerPos - QPointF(0.0, m_dy); if (m_tableDragInfo.tableRowDivider > 0) { qreal h = m_tableDragInfo.tableLeadSize - m_dy; QRectF rect(anchorPos - QPointF(0.0, h), QSizeF(0.0, h)); QRectF drawRect(shapeMatrix.map(rect.topLeft()), shapeMatrix.map(rect.bottomRight())); drawRect.setWidth(boxHeight); drawRect.moveLeft(drawRect.left() - 1.5 * boxHeight); QString label = m_unit.toUserStringValue(h); QRectF labelRect = QFontMetrics(QToolTip::font()).boundingRect(label); labelRect.setHeight(boxHeight); labelRect.setWidth(labelRect.width() + 10); labelRect.moveTopLeft(drawRect.center() - QPointF(labelRect.width(), labelRect.height())/2); painter.fillRect(drawRect, QColor(64, 255, 64, 196)); painter.fillRect(labelRect, QColor(64, 255, 64, 196)); painter.setPen(QColor(0, 0, 0, 196)); if (labelRect.height() + 10 < drawRect.height()) { QPointF centerTop(drawRect.center().x(), drawRect.top()); QPointF centerBottom(drawRect.center().x(), drawRect.bottom()); painter.drawLine(centerTop, drawRect.center() - QPointF(0.0, labelRect.height()/2+5)); painter.drawLine(centerTop, centerTop + QPointF(-5, 7)); painter.drawLine(centerTop, centerTop + QPointF(5, 7)); painter.drawLine(drawRect.center() + QPointF(0.0, labelRect.height()/2+5), centerBottom); painter.drawLine(centerBottom, centerBottom + QPointF(-5, -7)); painter.drawLine(centerBottom, centerBottom + QPointF(5, -7)); } painter.drawText(labelRect, Qt::AlignCenter, label); } } if (m_caretTimerState) { // Lets draw the caret ourselves, as the Qt method doesn't take cursor // charFormat into consideration. QTextBlock block = m_textEditor.data()->block(); if (block.isValid()) { int posInParag = m_textEditor.data()->position() - block.position(); if (posInParag <= block.layout()->preeditAreaPosition()) posInParag += block.layout()->preeditAreaText().length(); QTextLine tl = block.layout()->lineForTextPosition(m_textEditor.data()->position() - block.position()); if (tl.isValid()) { painter.setRenderHint(QPainter::Antialiasing, false); QRectF rect = caretRect(m_textEditor.data()->cursor()); QPointF baselinePoint; if (tl.ascent() > 0) { QFontMetricsF fm(m_textEditor.data()->charFormat().font(), painter.device()); rect.setY(rect.y() + tl.ascent() - qMin(tl.ascent(), fm.ascent())); rect.setHeight(qMin(tl.ascent(), fm.ascent()) + qMin(tl.descent(), fm.descent())); baselinePoint = QPoint(rect.x(), rect.y() + tl.ascent()); } else { //line only filled with characters-without-size (eg anchors) // layout will make sure line has height of block font QFontMetricsF fm(block.charFormat().font(), painter.device()); rect.setHeight(fm.ascent() + fm.descent()); baselinePoint = QPoint(rect.x(), rect.y() + fm.ascent()); } QRectF drawRect(shapeMatrix.map(rect.topLeft()), shapeMatrix.map(rect.bottomLeft())); drawRect.setWidth(2); painter.setCompositionMode(QPainter::RasterOp_SourceXorDestination); if (m_textEditor.data()->isEditProtected(true)) { QRectF circleRect(shapeMatrix.map(baselinePoint),QSizeF(14, 14)); circleRect.translate(-6.5, -6.5); QPen pen(QColor(16, 255, 255)); pen.setWidthF(2.0); painter.setPen(pen); painter.setRenderHint(QPainter::Antialiasing, true); painter.drawEllipse(circleRect); painter.drawLine(circleRect.topLeft() + QPointF(4.5,4.5), circleRect.bottomRight() - QPointF(4.5,4.5)); } else { painter.fillRect(drawRect, QColor(128, 255, 128)); } } } } painter.restore(); } void TextTool::updateSelectedShape(const QPointF &point, bool noDocumentChange) { QRectF area(point, QSizeF(1, 1)); if (m_textEditor.data()->hasSelection()) repaintSelection(); else repaintCaret(); QList sortedShapes = canvas()->shapeManager()->shapesAt(area, true); qSort(sortedShapes.begin(), sortedShapes.end(), KoShape::compareShapeZIndex); for (int count = sortedShapes.count() - 1; count >= 0; count--) { KoShape *shape = sortedShapes.at(count); if (shape->isContentProtected()) continue; TextShape *textShape = dynamic_cast(shape); if (textShape) { if (textShape != m_textShape) { if (static_cast(textShape->userData())->document() != m_textShapeData->document()) { //we should only change to another document if allowed if (noDocumentChange) { return; } // if we change to another textdocument we need to remove selection in old document // or it would continue to be painted etc m_textEditor.data()->setPosition(m_textEditor.data()->position()); } m_textShape = textShape; setShapeData(static_cast(m_textShape->userData())); // This is how we inform the rulers of the active range // For now we will not consider table cells, but just give the shape dimensions QVariant v; QRectF rect(QPoint(), m_textShape->size()); rect = m_textShape->absoluteTransformation(0).mapRect(rect); v.setValue(rect); canvas()->resourceManager()->setResource(KoCanvasResourceManager::ActiveRange, v); } return; } } } void TextTool::mousePressEvent(KoPointerEvent *event) { if (m_textEditor.isNull()) return; // request the software keyboard, if any if (event->button() == Qt::LeftButton && qApp->autoSipEnabled()) { QStyle::RequestSoftwareInputPanel behavior = QStyle::RequestSoftwareInputPanel(qApp->style()->styleHint(QStyle::SH_RequestSoftwareInputPanel)); // the two following bools just make it all a lot easier to read in the following if() // basically, we require a widget for this to work (passing NULL to QApplication::sendEvent // crashes) and there are three tests any one of which can be true to trigger the event const bool hasWidget = canvas()->canvasWidget(); const bool hasItem = canvas()->canvasItem(); if ((behavior == QStyle::RSIP_OnMouseClick && (hasWidget || hasItem)) || (hasWidget && canvas()->canvasWidget()->hasFocus()) || (hasItem && canvas()->canvasItem()->hasFocus())) { QEvent event(QEvent::RequestSoftwareInputPanel); if (hasWidget) { QApplication::sendEvent(canvas()->canvasWidget(), &event); } else { QApplication::sendEvent(canvas()->canvasItem(), &event); } } } bool shiftPressed = event->modifiers() & Qt::ShiftModifier; updateSelectedShape(event->point, shiftPressed); KoSelection *selection = canvas()->shapeManager()->selection(); if (m_textShape && !selection->isSelected(m_textShape) && m_textShape->isSelectable()) { selection->deselectAll(); selection->select(m_textShape); } KoPointedAt pointedAt = hitTest(event->point); m_tableDraggedOnce = false; m_clickWithinSelection = false; if (pointedAt.position != -1) { m_tablePenMode = false; if ((event->button() == Qt::LeftButton) && !shiftPressed && m_textEditor.data()->hasSelection() && m_textEditor.data()->isWithinSelection(pointedAt.position)) { m_clickWithinSelection = true; m_draggingOrigin = event->pos(); //we store the pixel pos } else if (! (event->button() == Qt::RightButton && m_textEditor.data()->hasSelection() && m_textEditor.data()->isWithinSelection(pointedAt.position))) { m_textEditor.data()->setPosition(pointedAt.position, shiftPressed ? QTextCursor::KeepAnchor : QTextCursor::MoveAnchor); useCursor(Qt::IBeamCursor); } m_tableDragInfo.tableHit = KoPointedAt::None; if (m_caretTimer.isActive()) { // make the caret not blink, (blinks again after first draw) m_caretTimer.stop(); m_caretTimer.setInterval(50); m_caretTimer.start(); m_caretTimerState = true; // turn caret instantly on on click } } else { if (event->button() == Qt::RightButton) { m_tablePenMode = false; KoTextEditingPlugin *plugin = textEditingPluginContainer()->spellcheck(); if (plugin) plugin->setCurrentCursorPosition(m_textShapeData->document(), -1); event->ignore(); } else if (m_tablePenMode) { m_textEditor.data()->beginEditBlock(kundo2_i18n("Change Border Formatting")); if (pointedAt.tableHit == KoPointedAt::ColumnDivider) { if (pointedAt.tableColumnDivider < pointedAt.table->columns()) { m_textEditor.data()->setTableBorderData(pointedAt.table, pointedAt.tableRowDivider, pointedAt.tableColumnDivider, KoBorder::LeftBorder, m_tablePenBorderData); } if (pointedAt.tableColumnDivider > 0) { m_textEditor.data()->setTableBorderData(pointedAt.table, pointedAt.tableRowDivider, pointedAt.tableColumnDivider - 1, KoBorder::RightBorder, m_tablePenBorderData); } } else if (pointedAt.tableHit == KoPointedAt::RowDivider) { if (pointedAt.tableRowDivider < pointedAt.table->rows()) { m_textEditor.data()->setTableBorderData(pointedAt.table, pointedAt.tableRowDivider, pointedAt.tableColumnDivider, KoBorder::TopBorder, m_tablePenBorderData); } if (pointedAt.tableRowDivider > 0) { m_textEditor.data()->setTableBorderData(pointedAt.table, pointedAt.tableRowDivider-1, pointedAt.tableColumnDivider, KoBorder::BottomBorder, m_tablePenBorderData); } } m_textEditor.data()->endEditBlock(); } else { m_tableDragInfo = pointedAt; m_tablePenMode = false; } return; } if (shiftPressed) // altered selection. repaintSelection(); else repaintCaret(); updateSelectionHandler(); updateStyleManager(); updateActions(); //activate context-menu for spelling-suggestions if (event->button() == Qt::RightButton) { KoTextEditingPlugin *plugin = textEditingPluginContainer()->spellcheck(); if (plugin) plugin->setCurrentCursorPosition(m_textShapeData->document(), m_textEditor.data()->position()); event->ignore(); } if (event->button() == Qt::MidButton) { // Paste const QMimeData *data = QApplication::clipboard()->mimeData(QClipboard::Selection); // on windows we do not have data if we try to paste this selection if (data) { m_prevCursorPosition = m_textEditor.data()->position(); m_textEditor.data()->paste(canvas(), data, canvas()->resourceManager()); editingPluginEvents(); } } } void TextTool::setShapeData(KoTextShapeData *data) { bool docChanged = !data || !m_textShapeData || m_textShapeData->document() != data->document(); if (m_textShapeData) { disconnect(m_textShapeData, SIGNAL(destroyed (QObject*)), this, SLOT(shapeDataRemoved())); } m_textShapeData = data; if (!m_textShapeData) return; connect(m_textShapeData, SIGNAL(destroyed (QObject*)), this, SLOT(shapeDataRemoved())); if (docChanged) { if (!m_textEditor.isNull()) { disconnect(m_textEditor.data(), SIGNAL(textFormatChanged()), this, SLOT(updateActions())); } m_textEditor = KoTextDocument(m_textShapeData->document()).textEditor(); Q_ASSERT(m_textEditor.data()); if (!m_toolSelection) { m_toolSelection = new TextToolSelection(m_textEditor.data()); } else { m_toolSelection->m_editor = m_textEditor.data(); } m_variableMenu->menu()->clear(); KoTextDocument document(m_textShapeData->document()); foreach (QAction *action, document.inlineTextObjectManager()->createInsertVariableActions(canvas())) { m_variableMenu->addAction(action); connect(action, SIGNAL(triggered()), this, SLOT(returnFocusToCanvas())); } connect(m_textEditor.data(), SIGNAL(textFormatChanged()), this, SLOT(updateActions())); updateActions(); } } void TextTool::updateSelectionHandler() { if (m_textEditor) { emit selectionChanged(m_textEditor.data()->hasSelection()); if (m_textEditor.data()->hasSelection()) { QClipboard *clipboard = QApplication::clipboard(); if (clipboard->supportsSelection()) clipboard->setText(m_textEditor.data()->selectedText(), QClipboard::Selection); } } KoCanvasResourceManager *p = canvas()->resourceManager(); m_allowResourceManagerUpdates = false; if (m_textEditor && m_textShapeData) { p->setResource(KoText::CurrentTextPosition, m_textEditor.data()->position()); p->setResource(KoText::CurrentTextAnchor, m_textEditor.data()->anchor()); QVariant variant; variant.setValue(m_textShapeData->document()); p->setResource(KoText::CurrentTextDocument, variant); } else { p->clearResource(KoText::CurrentTextPosition); p->clearResource(KoText::CurrentTextAnchor); p->clearResource(KoText::CurrentTextDocument); } m_allowResourceManagerUpdates = true; } QMimeData *TextTool::generateMimeData() const { if (!m_textShapeData || m_textEditor.isNull() || !m_textEditor.data()->hasSelection()) return 0; int from = m_textEditor.data()->position(); int to = m_textEditor.data()->anchor(); KoTextOdfSaveHelper saveHelper(m_textShapeData->document(), from, to); KoTextDrag drag; #ifdef SHOULD_BUILD_RDF KoDocumentResourceManager *rm = 0; if (canvas()->shapeController()) { rm = canvas()->shapeController()->resourceManager(); } if (rm && rm->hasResource(KoText::DocumentRdf)) { KoDocumentRdfBase *rdf = qobject_cast(rm->resource(KoText::DocumentRdf).value()); if (rdf) { saveHelper.setRdfModel(rdf->model()); } } #endif drag.setOdf(KoOdf::mimeType(KoOdf::Text), saveHelper); QTextDocumentFragment fragment = m_textEditor.data()->selection(); drag.setData("text/html", fragment.toHtml("utf-8").toUtf8()); drag.setData("text/plain", fragment.toPlainText().toUtf8()); return drag.takeMimeData(); } TextEditingPluginContainer *TextTool::textEditingPluginContainer() { m_textEditingPlugins = canvas()->resourceManager()-> resource(TextEditingPluginContainer::ResourceId).value(); if (m_textEditingPlugins == 0) { m_textEditingPlugins = new TextEditingPluginContainer(canvas()->resourceManager()); QVariant variant; variant.setValue(m_textEditingPlugins.data()); canvas()->resourceManager()->setResource(TextEditingPluginContainer::ResourceId, variant); foreach (KoTextEditingPlugin* plugin, m_textEditingPlugins->values()) { connect(plugin, SIGNAL(startMacro(const QString &)), this, SLOT(startMacro(const QString &))); connect(plugin, SIGNAL(stopMacro()), this, SLOT(stopMacro())); QHash actions = plugin->actions(); QHash::iterator i = actions.begin(); while (i != actions.end()) { addAction(i.key(), i.value()); ++i; } } } return m_textEditingPlugins; } void TextTool::copy() const { QMimeData *mimeData = generateMimeData(); if (mimeData) { QApplication::clipboard()->setMimeData(mimeData); } } void TextTool::deleteSelection() { m_textEditor.data()->deleteChar(); editingPluginEvents(); } bool TextTool::paste() { const QMimeData *data = QApplication::clipboard()->mimeData(QClipboard::Clipboard); // on windows we do not have data if we try to paste the selection if (!data) return false; // since this is not paste-as-text we will not paste in urls, but instead let KoToolProxy solve it if (data->hasUrls()) return false; if (data->hasFormat(KoOdf::mimeType(KoOdf::Text)) || data->hasText()) { m_prevCursorPosition = m_textEditor.data()->position(); m_textEditor.data()->paste(canvas(), data); editingPluginEvents(); return true; } return false; } void TextTool::cut() { if (m_textEditor.data()->hasSelection()) { copy(); KUndo2Command *topCmd = m_textEditor.data()->beginEditBlock(kundo2_i18n("Cut")); m_textEditor.data()->deleteChar(false, topCmd); m_textEditor.data()->endEditBlock(); } } QStringList TextTool::supportedPasteMimeTypes() const { QStringList list; list << "text/plain" << "text/html" << "application/vnd.oasis.opendocument.text"; return list; } void TextTool::dragMoveEvent(QDragMoveEvent *event, const QPointF &point) { if (event->mimeData()->hasFormat(KoOdf::mimeType(KoOdf::Text)) || event->mimeData()->hasFormat(KoOdf::mimeType(KoOdf::OpenOfficeClipboard)) || event->mimeData()->hasText()) { if (m_drag) { event->setDropAction(Qt::MoveAction); event->accept(); } else if (event->proposedAction() == Qt::CopyAction) { event->acceptProposedAction(); } else { event->ignore(); return; } KoPointedAt pointedAt = hitTest(point); if (pointedAt.position == -1) { event->ignore(); } if (m_caretTimer.isActive()) { // make the caret not blink, (blinks again after first draw) m_caretTimer.stop(); m_caretTimer.setInterval(50); m_caretTimer.start(); m_caretTimerState = true; // turn caret instantly on on click } if (m_preDragSelection.cursor.isNull()) { repaintSelection(); m_preDragSelection.cursor = QTextCursor(*m_textEditor.data()->cursor()); if (m_drag) { // Make a selection that looks like the current cursor selection // so we can move the real carent around freely QVector< QAbstractTextDocumentLayout::Selection > sels = KoTextDocument(m_textShapeData->document()).selections(); m_preDragSelection.format = QTextCharFormat(); m_preDragSelection.format.setBackground(qApp->palette().brush(QPalette::Highlight)); m_preDragSelection.format.setForeground(qApp->palette().brush(QPalette::HighlightedText)); sels.append(m_preDragSelection); KoTextDocument(m_textShapeData->document()).setSelections(sels); } // else we wantt the selection ot disappaear } repaintCaret(); // will erase caret m_textEditor.data()->setPosition(pointedAt.position); repaintCaret(); // will paint caret in new spot // Selection has visually not appeared at a new spot so no need to repaint it } } void TextTool::dragLeaveEvent(QDragLeaveEvent *event) { if (m_drag) { // restore the old selections QVector< QAbstractTextDocumentLayout::Selection > sels = KoTextDocument(m_textShapeData->document()).selections(); sels.pop_back(); KoTextDocument(m_textShapeData->document()).setSelections(sels); } repaintCaret(); // will erase caret in old spot m_textEditor.data()->setPosition(m_preDragSelection.cursor.anchor()); m_textEditor.data()->setPosition(m_preDragSelection.cursor.position(), QTextCursor::KeepAnchor); repaintCaret(); // will paint caret in new spot if (!m_drag) { repaintSelection(); // will paint selection again } // mark that we now are back to normal selection m_preDragSelection.cursor = QTextCursor(); event->accept(); } void TextTool::dropEvent(QDropEvent *event, const QPointF &) { if (m_drag) { // restore the old selections QVector< QAbstractTextDocumentLayout::Selection > sels = KoTextDocument(m_textShapeData->document()).selections(); sels.pop_back(); KoTextDocument(m_textShapeData->document()).setSelections(sels); } QTextCursor insertCursor(*m_textEditor.data()->cursor()); m_textEditor.data()->setPosition(m_preDragSelection.cursor.anchor()); m_textEditor.data()->setPosition(m_preDragSelection.cursor.position(), QTextCursor::KeepAnchor); repaintSelection(); // will erase the selection in new spot if (m_drag) { m_textEditor.data()->deleteChar(); } m_prevCursorPosition = insertCursor.position(); m_textEditor.data()->setPosition(m_prevCursorPosition); m_textEditor.data()->paste(canvas(), event->mimeData()); m_textEditor.data()->setPosition(m_prevCursorPosition); //since the paste made insertCursor we can now use that for the end position m_textEditor.data()->setPosition(insertCursor.position(), QTextCursor::KeepAnchor); // mark that we no are back to normal selection m_preDragSelection.cursor = QTextCursor(); event->accept(); } KoPointedAt TextTool::hitTest(const QPointF & point) const { if (!m_textShape || !m_textShapeData) { return KoPointedAt(); } QPointF p = m_textShape->convertScreenPos(point); KoTextLayoutRootArea *rootArea = m_textShapeData->rootArea(); return rootArea ? rootArea->hitTest(p, Qt::FuzzyHit) : KoPointedAt(); } void TextTool::mouseDoubleClickEvent(KoPointerEvent *event) { if (canvas()->shapeManager()->shapeAt(event->point) != m_textShape) { event->ignore(); // allow the event to be used by another return; } if (event->modifiers() & Qt::ShiftModifier) { // When whift is pressed we behave as a single press return mousePressEvent(event); } m_textEditor.data()->select(QTextCursor::WordUnderCursor); m_clickWithinSelection = false; repaintSelection(); updateSelectionHandler(); } void TextTool::mouseTripleClickEvent(KoPointerEvent *event) { if (canvas()->shapeManager()->shapeAt(event->point) != m_textShape) { event->ignore(); // allow the event to be used by another return; } if (event->modifiers() & Qt::ShiftModifier) { // When whift is pressed we behave as a single press return mousePressEvent(event); } m_textEditor.data()->clearSelection(); m_textEditor.data()->movePosition(QTextCursor::StartOfBlock); m_textEditor.data()->movePosition(QTextCursor::EndOfBlock, QTextCursor::KeepAnchor); m_clickWithinSelection = false; repaintSelection(); updateSelectionHandler(); } void TextTool::mouseMoveEvent(KoPointerEvent *event) { m_editTipPos = event->globalPos(); if (event->buttons()) { updateSelectedShape(event->point, true); } m_editTipTimer.stop(); if (QToolTip::isVisible()) QToolTip::hideText(); KoPointedAt pointedAt = hitTest(event->point); if (event->buttons() == Qt::NoButton) { if (m_tablePenMode) { if (pointedAt.tableHit == KoPointedAt::ColumnDivider || pointedAt.tableHit == KoPointedAt::RowDivider) { useTableBorderCursor(); } else { useCursor(Qt::IBeamCursor); } // do nothing else return; } if (!m_textShapeData || pointedAt.position < 0) { if (pointedAt.tableHit == KoPointedAt::ColumnDivider) { useCursor(Qt::SplitHCursor); m_draggingOrigin = event->point; } else if (pointedAt.tableHit == KoPointedAt::RowDivider) { if (pointedAt.tableRowDivider > 0) { useCursor(Qt::SplitVCursor); m_draggingOrigin = event->point; } else useCursor(Qt::IBeamCursor); } else { useCursor(Qt::IBeamCursor); } return; } QTextCursor mouseOver(m_textShapeData->document()); mouseOver.setPosition(pointedAt.position); if (m_changeTracker && m_changeTracker->containsInlineChanges(mouseOver.charFormat())) { m_editTipPointedAt = pointedAt; if (QToolTip::isVisible()) { QTimer::singleShot(0, this, SLOT(showEditTip())); }else { m_editTipTimer.start(); } } if ((pointedAt.bookmark || !pointedAt.externalHRef.isEmpty()) || pointedAt.note || (pointedAt.noteReference>0)) { if (event->modifiers() & Qt::ControlModifier) { useCursor(Qt::PointingHandCursor); } m_editTipPointedAt = pointedAt; if (QToolTip::isVisible()) { QTimer::singleShot(0, this, SLOT(showEditTip())); }else { m_editTipTimer.start(); } return; } // check if mouse pointer is over shape with hyperlink KoShape *selectedShape = canvas()->shapeManager()->shapeAt(event->point); if (selectedShape != 0 && selectedShape != m_textShape && selectedShape->hyperLink().size() != 0) { useCursor(Qt::PointingHandCursor); return; } useCursor(Qt::IBeamCursor); // Set Arrow Cursor when mouse is on top of annotation shape. if (selectedShape) { if (selectedShape->shapeId() == "AnnotationTextShapeID") { QPointF point(event->point); if (point.y() <= (selectedShape->position().y() + 25)) useCursor(Qt::ArrowCursor); } } return; } else { if (m_tableDragInfo.tableHit == KoPointedAt::ColumnDivider) { m_tableDragWithShift = event->modifiers() & Qt::ShiftModifier; if(m_tableDraggedOnce) { canvas()->shapeController()->resourceManager()->undoStack()->undo(); } KUndo2Command *topCmd = m_textEditor.data()->beginEditBlock(kundo2_i18n("Adjust Column Width")); m_dx = m_draggingOrigin.x() - event->point.x(); if (m_tableDragInfo.tableColumnDivider < m_tableDragInfo.table->columns() && m_tableDragInfo.tableTrailSize + m_dx < 0) { m_dx = -m_tableDragInfo.tableTrailSize; } if (m_tableDragInfo.tableColumnDivider > 0) { if (m_tableDragInfo.tableLeadSize - m_dx < 0) { m_dx = m_tableDragInfo.tableLeadSize; } m_textEditor.data()->adjustTableColumnWidth(m_tableDragInfo.table, m_tableDragInfo.tableColumnDivider - 1, m_tableDragInfo.tableLeadSize - m_dx, topCmd); } else { m_textEditor.data()->adjustTableWidth(m_tableDragInfo.table, -m_dx, 0.0); } if (m_tableDragInfo.tableColumnDivider < m_tableDragInfo.table->columns()) { if (!m_tableDragWithShift) { m_textEditor.data()->adjustTableColumnWidth(m_tableDragInfo.table, m_tableDragInfo.tableColumnDivider, m_tableDragInfo.tableTrailSize + m_dx, topCmd); } } else { m_tableDragWithShift = true; // act like shift pressed } if (m_tableDragWithShift) { m_textEditor.data()->adjustTableWidth(m_tableDragInfo.table, 0.0, m_dx); } m_textEditor.data()->endEditBlock(); m_tableDragInfo.tableDividerPos.setY(m_textShape->convertScreenPos(event->point).y()); if (m_tableDraggedOnce) { //we need to redraw like this so we update outside the textshape too if (canvas()->canvasWidget()) canvas()->canvasWidget()->update(); if (canvas()->canvasItem()) canvas()->canvasItem()->update(); } m_tableDraggedOnce = true; } else if (m_tableDragInfo.tableHit == KoPointedAt::RowDivider) { if(m_tableDraggedOnce) { canvas()->shapeController()->resourceManager()->undoStack()->undo(); } if (m_tableDragInfo.tableRowDivider > 0) { KUndo2Command *topCmd = m_textEditor.data()->beginEditBlock(kundo2_i18n("Adjust Row Height")); m_dy = m_draggingOrigin.y() - event->point.y(); if (m_tableDragInfo.tableLeadSize - m_dy < 0) { m_dy = m_tableDragInfo.tableLeadSize; } m_textEditor.data()->adjustTableRowHeight(m_tableDragInfo.table, m_tableDragInfo.tableRowDivider - 1, m_tableDragInfo.tableLeadSize - m_dy, topCmd); m_textEditor.data()->endEditBlock(); m_tableDragInfo.tableDividerPos.setX(m_textShape->convertScreenPos(event->point).x()); if (m_tableDraggedOnce) { //we need to redraw like this so we update outside the textshape too if (canvas()->canvasWidget()) canvas()->canvasWidget()->update(); if (canvas()->canvasItem()) canvas()->canvasItem()->update(); } m_tableDraggedOnce = true; } } else if (m_tablePenMode) { // do nothing } else if (m_clickWithinSelection) { if (!m_drag && (event->pos() - m_draggingOrigin).manhattanLength() >= QApplication::startDragDistance()) { QMimeData *mimeData = generateMimeData(); if (mimeData) { m_drag = new QDrag(canvas()->canvasWidget()); m_drag->setMimeData(mimeData); m_drag->exec(Qt::MoveAction | Qt::CopyAction, Qt::CopyAction); m_drag = 0; } } } else { useCursor(Qt::IBeamCursor); if (pointedAt.position == m_textEditor.data()->position()) return; if (pointedAt.position >= 0) { if (m_textEditor.data()->hasSelection()) repaintSelection(); // will erase selection else repaintCaret(); m_textEditor.data()->setPosition(pointedAt.position, QTextCursor::KeepAnchor); if (m_textEditor.data()->hasSelection()) repaintSelection(); else repaintCaret(); } } updateSelectionHandler(); } } void TextTool::mouseReleaseEvent(KoPointerEvent *event) { event->ignore(); editingPluginEvents(); m_tableDragInfo.tableHit = KoPointedAt::None; if (m_tableDraggedOnce) { m_tableDraggedOnce = false; //we need to redraw like this so we update outside the textshape too if (canvas()->canvasWidget()) canvas()->canvasWidget()->update(); if (canvas()->canvasItem()) canvas()->canvasItem()->update(); } if (!m_textShapeData) return; // check if mouse pointer is not over some shape with hyperlink KoShape *selectedShape = canvas()->shapeManager()->shapeAt(event->point); if (selectedShape != 0 && selectedShape != m_textShape && selectedShape->hyperLink().size() != 0) { QString url = selectedShape->hyperLink(); runUrl(event, url); return; } KoPointedAt pointedAt = hitTest(event->point); if (m_clickWithinSelection && !m_drag) { if (m_caretTimer.isActive()) { // make the caret not blink, (blinks again after first draw) m_caretTimer.stop(); m_caretTimer.setInterval(50); m_caretTimer.start(); m_caretTimerState = true; // turn caret instantly on on click } repaintCaret(); // will erase caret repaintSelection(); // will erase selection m_textEditor.data()->setPosition(pointedAt.position); repaintCaret(); // will paint caret in new spot } // Is there an anchor here ? if ((event->modifiers() & Qt::ControlModifier) && !m_textEditor.data()->hasSelection()) { if (pointedAt.bookmark) { m_textEditor.data()->setPosition(pointedAt.bookmark->rangeStart()); ensureCursorVisible(); event->accept(); return; } if (pointedAt.note) { m_textEditor.data()->setPosition(pointedAt.note->textFrame()->firstPosition()); ensureCursorVisible(); event->accept(); return; } if (pointedAt.noteReference>0) { m_textEditor.data()->setPosition(pointedAt.noteReference); ensureCursorVisible(); event->accept(); return; } if (!pointedAt.externalHRef.isEmpty()) { runUrl(event, pointedAt.externalHRef); } } } void TextTool::keyPressEvent(QKeyEvent *event) { int destinationPosition = -1; // for those cases where the moveOperation is not relevant; QTextCursor::MoveOperation moveOperation = QTextCursor::NoMove; KoTextEditor *textEditor = m_textEditor.data(); m_tablePenMode = false; // keypress always stops the table (border) pen mode Q_ASSERT(textEditor); if (event->key() == Qt::Key_Backspace) { if (!textEditor->hasSelection() && textEditor->block().textList() && (textEditor->position() == textEditor->block().position()) && !(m_changeTracker && m_changeTracker->recordChanges())) { if (!textEditor->blockFormat().boolProperty(KoParagraphStyle::UnnumberedListItem)) { // backspace at beginning of numbered list item, makes it unnumbered textEditor->toggleListNumbering(false); } else { KoListLevelProperties llp; llp.setStyle(KoListStyle::None); llp.setLevel(0); // backspace on numbered, empty parag, removes numbering. textEditor->setListProperties(llp); } } else if (textEditor->position() > 0 || textEditor->hasSelection()) { if (!textEditor->hasSelection() && event->modifiers() & Qt::ControlModifier) { // delete prev word. textEditor->movePosition(QTextCursor::PreviousWord, QTextCursor::KeepAnchor); } textEditor->deletePreviousChar(); editingPluginEvents(); } } else if ((event->key() == Qt::Key_Tab) && ((!textEditor->hasSelection() && (textEditor->position() == textEditor->block().position())) || (textEditor->block().document()->findBlock(textEditor->anchor()) != textEditor->block().document()->findBlock(textEditor->position()))) && textEditor->block().textList()) { ChangeListLevelCommand::CommandType type = ChangeListLevelCommand::IncreaseLevel; ChangeListLevelCommand *cll = new ChangeListLevelCommand(*textEditor->cursor(), type, 1); textEditor->addCommand(cll); editingPluginEvents(); } else if ((event->key() == Qt::Key_Backtab) && ((!textEditor->hasSelection() && (textEditor->position() == textEditor->block().position())) || (textEditor->block().document()->findBlock(textEditor->anchor()) != textEditor->block().document()->findBlock(textEditor->position()))) && textEditor->block().textList() && !(m_changeTracker && m_changeTracker->recordChanges())) { ChangeListLevelCommand::CommandType type = ChangeListLevelCommand::DecreaseLevel; ChangeListLevelCommand *cll = new ChangeListLevelCommand(*textEditor->cursor(), type, 1); textEditor->addCommand(cll); editingPluginEvents(); } else if (event->key() == Qt::Key_Delete) { if (!textEditor->hasSelection() && event->modifiers() & Qt::ControlModifier) {// delete next word. textEditor->movePosition(QTextCursor::NextWord, QTextCursor::KeepAnchor); } // the event only gets through when the Del is not used in the app // if the app forwards Del then deleteSelection is used textEditor->deleteChar(); editingPluginEvents(); } else if ((event->key() == Qt::Key_Left) && (event->modifiers() & Qt::ControlModifier) == 0) { moveOperation = QTextCursor::Left; } else if ((event->key() == Qt::Key_Right) && (event->modifiers() & Qt::ControlModifier) == 0) { moveOperation = QTextCursor::Right; } else if ((event->key() == Qt::Key_Up) && (event->modifiers() & Qt::ControlModifier) == 0) { moveOperation = QTextCursor::Up; } else if ((event->key() == Qt::Key_Down) && (event->modifiers() & Qt::ControlModifier) == 0) { moveOperation = QTextCursor::Down; } else { // check for shortcuts. QKeySequence item(event->key() | ((Qt::ControlModifier | Qt::AltModifier) & event->modifiers())); if (hit(item, KStandardShortcut::Begin)) // Goto beginning of the document. Default: Ctrl-Home destinationPosition = 0; else if (hit(item, KStandardShortcut::End)) { // Goto end of the document. Default: Ctrl-End if (m_textShapeData) { QTextBlock last = m_textShapeData->document()->lastBlock(); destinationPosition = last.position() + last.length() - 1; } } else if (hit(item, KStandardShortcut::Prior)) { // page up // Scroll up one page. Default: Prior event->ignore(); // let app level actions handle it return; } else if (hit(item, KStandardShortcut::Next)) { // Scroll down one page. Default: Next event->ignore(); // let app level actions handle it return; } else if (hit(item, KStandardShortcut::BeginningOfLine)) // Goto beginning of current line. Default: Home moveOperation = QTextCursor::StartOfLine; else if (hit(item, KStandardShortcut::EndOfLine)) // Goto end of current line. Default: End moveOperation = QTextCursor::EndOfLine; else if (hit(item, KStandardShortcut::BackwardWord)) moveOperation = QTextCursor::WordLeft; else if (hit(item, KStandardShortcut::ForwardWord)) moveOperation = QTextCursor::WordRight; #ifdef Q_WS_MAC // Don't reject "alt" key, it may be used for typing text on Mac OS else if ((event->modifiers() & Qt::ControlModifier) || event->text().length() == 0) { #else else if ((event->modifiers() & (Qt::ControlModifier | Qt::AltModifier)) || event->text().length() == 0) { #endif event->ignore(); return; } else if ((event->key() == Qt::Key_Enter || event->key() == Qt::Key_Return)) { m_prevCursorPosition = textEditor->position(); textEditor->newLine(); updateActions(); editingPluginEvents(); } else if ((event->key() == Qt::Key_Tab || !(event->text().length() == 1 && !event->text().at(0).isPrint()))) { // insert the text m_prevCursorPosition = textEditor->position(); startingSimpleEdit(); //signal editing plugins that this is a simple edit textEditor->insertText(event->text()); editingPluginEvents(); } } if (moveOperation != QTextCursor::NoMove || destinationPosition != -1) { useCursor(Qt::BlankCursor); bool shiftPressed = event->modifiers() & Qt::ShiftModifier; if (textEditor->hasSelection()) repaintSelection(); // will erase selection else repaintCaret(); QTextBlockFormat format = textEditor->blockFormat(); KoText::Direction dir = static_cast(format.intProperty(KoParagraphStyle::TextProgressionDirection)); bool isRtl; if (dir == KoText::AutoDirection) isRtl = textEditor->block().text().isRightToLeft(); else isRtl = dir == KoText::RightLeftTopBottom; if (isRtl) { // if RTL toggle direction of cursor movement. switch (moveOperation) { case QTextCursor::Left: moveOperation = QTextCursor::Right; break; case QTextCursor::Right: moveOperation = QTextCursor::Left; break; case QTextCursor::WordRight: moveOperation = QTextCursor::WordLeft; break; case QTextCursor::WordLeft: moveOperation = QTextCursor::WordRight; break; default: break; } } int prevPosition = textEditor->position(); if (moveOperation != QTextCursor::NoMove) textEditor->movePosition(moveOperation, shiftPressed ? QTextCursor::KeepAnchor : QTextCursor::MoveAnchor); else textEditor->setPosition(destinationPosition, shiftPressed ? QTextCursor::KeepAnchor : QTextCursor::MoveAnchor); if (moveOperation == QTextCursor::Down && prevPosition == textEditor->position()) { // change behavior a little big from Qt; at the bottom of the doc we go to the end of the doc textEditor->movePosition(QTextCursor::End, shiftPressed ? QTextCursor::KeepAnchor : QTextCursor::MoveAnchor); } if (shiftPressed) // altered selection. repaintSelection(); else repaintCaret(); updateActions(); editingPluginEvents(); } if (m_caretTimer.isActive()) { // make the caret not blink but decide on the action if its visible or not. m_caretTimer.stop(); m_caretTimer.setInterval(50); m_caretTimer.start(); m_caretTimerState = true; // turn caret on while typing } if (moveOperation != QTextCursor::NoMove) // this difference in handling is need to prevent leaving a trail of old cursors onscreen ensureCursorVisible(); else m_delayedEnsureVisible = true; updateActions(); updateSelectionHandler(); } QVariant TextTool::inputMethodQuery(Qt::InputMethodQuery query, const KoViewConverter &converter) const { KoTextEditor *textEditor = m_textEditor.data(); if (!textEditor || !m_textShapeData) return QVariant(); switch (query) { case Qt::ImMicroFocus: { // The rectangle covering the area of the input cursor in widget coordinates. QRectF rect = caretRect(textEditor->cursor()); rect.moveTop(rect.top() - m_textShapeData->documentOffset()); QTransform shapeMatrix = m_textShape->absoluteTransformation(&converter); qreal zoomX, zoomY; converter.zoom(&zoomX, &zoomY); shapeMatrix.scale(zoomX, zoomY); rect = shapeMatrix.mapRect(rect); return rect.toRect(); } case Qt::ImFont: // The currently used font for text input. return textEditor->charFormat().font(); case Qt::ImCursorPosition: // The logical position of the cursor within the text surrounding the input area (see ImSurroundingText). return textEditor->position() - textEditor->block().position(); case Qt::ImSurroundingText: // The plain text around the input area, for example the current paragraph. return textEditor->block().text(); case Qt::ImCurrentSelection: // The currently selected text. return textEditor->selectedText(); default: ; // Qt 4.6 adds ImMaximumTextLength and ImAnchorPosition } return QVariant(); } void TextTool::inputMethodEvent(QInputMethodEvent *event) { KoTextEditor *textEditor = m_textEditor.data(); if (textEditor == 0) return; if (event->replacementLength() > 0) { textEditor->setPosition(textEditor->position() + event->replacementStart()); for (int i = event->replacementLength(); i > 0; --i) { textEditor->deleteChar(); } } if (!event->commitString().isEmpty()) { QKeyEvent ke(QEvent::KeyPress, -1, 0, event->commitString()); keyPressEvent(&ke); // The cursor may reside in a different block before vs. after keyPressEvent. QTextBlock block = textEditor->block(); QTextLayout *layout = block.layout(); Q_ASSERT(layout); layout->setPreeditArea(-1, QString()); } else { QTextBlock block = textEditor->block(); QTextLayout *layout = block.layout(); Q_ASSERT(layout); layout->setPreeditArea(textEditor->position() - block.position(), event->preeditString()); const_cast(textEditor->document())->markContentsDirty(textEditor->position(), event->preeditString().length()); } event->accept(); } void TextTool::ensureCursorVisible(bool moveView) { KoTextEditor *textEditor = m_textEditor.data(); if (!textEditor || !m_textShapeData) return; bool upToDate; QRectF cRect = caretRect(textEditor->cursor(), &upToDate); KoTextDocumentLayout *lay = qobject_cast(m_textShapeData->document()->documentLayout()); Q_ASSERT(lay); KoTextLayoutRootArea *rootArea = lay->rootAreaForPoint(cRect.center()); if (rootArea && rootArea->associatedShape() && m_textShapeData->rootArea() != rootArea) { // If we have changed root area we need to update m_textShape and m_textShapeData m_textShape = static_cast(rootArea->associatedShape()); Q_ASSERT(m_textShape); disconnect(m_textShapeData, SIGNAL(destroyed (QObject*)), this, SLOT(shapeDataRemoved())); m_textShapeData = static_cast(m_textShape->userData()); Q_ASSERT(m_textShapeData); connect(m_textShapeData, SIGNAL(destroyed (QObject*)), this, SLOT(shapeDataRemoved())); } if (!moveView) { return; } if (! upToDate) { // paragraph is not yet layouted. // The number one usecase for this is when the user pressed enter. // try to do it on next caret blink m_delayedEnsureVisible = true; return; // we shouldn't move to an obsolete position } cRect.moveTop(cRect.top() - m_textShapeData->documentOffset()); canvas()->ensureVisible(m_textShape->absoluteTransformation(0).mapRect(cRect)); } void TextTool::keyReleaseEvent(QKeyEvent *event) { event->accept(); } void TextTool::updateActions() { bool notInAnnotation = !dynamic_cast(m_textShape); KoTextEditor *textEditor = m_textEditor.data(); if (textEditor == 0) { return; } m_allowActions = false; //Update the characterStyle related GUI elements QTextCharFormat cf = textEditor->charFormat(); m_actionFormatBold->setChecked(cf.fontWeight() > QFont::Normal); m_actionFormatItalic->setChecked(cf.fontItalic()); m_actionFormatUnderline->setChecked(cf.intProperty(KoCharacterStyle::UnderlineType) != KoCharacterStyle::NoLineType); m_actionFormatStrikeOut->setChecked(cf.intProperty(KoCharacterStyle::StrikeOutType) != KoCharacterStyle::NoLineType); bool super = false, sub = false; switch (cf.verticalAlignment()) { case QTextCharFormat::AlignSuperScript: super = true; break; case QTextCharFormat::AlignSubScript: sub = true; break; default:; } m_actionFormatSuper->setChecked(super); m_actionFormatSub->setChecked(sub); m_actionFormatFontSize->setFontSize(cf.font().pointSizeF()); m_actionFormatFontFamily->setFont(cf.font().family()); KoTextShapeData::ResizeMethod resizemethod = KoTextShapeData::AutoResize; if(m_textShapeData) { resizemethod = m_textShapeData->resizeMethod(); } m_shrinkToFitAction->setEnabled(resizemethod != KoTextShapeData::AutoResize && notInAnnotation); m_shrinkToFitAction->setChecked(resizemethod == KoTextShapeData::ShrinkToFitResize); m_growWidthAction->setEnabled(resizemethod != KoTextShapeData::AutoResize && notInAnnotation); m_growWidthAction->setChecked(resizemethod == KoTextShapeData::AutoGrowWidth || resizemethod == KoTextShapeData::AutoGrowWidthAndHeight); m_growHeightAction->setEnabled(resizemethod != KoTextShapeData::AutoResize && notInAnnotation); m_growHeightAction->setChecked(resizemethod == KoTextShapeData::AutoGrowHeight || resizemethod == KoTextShapeData::AutoGrowWidthAndHeight); //update paragraphStyle GUI element QTextBlockFormat bf = textEditor->blockFormat(); if (bf.hasProperty(KoParagraphStyle::TextProgressionDirection)) { switch(bf.intProperty(KoParagraphStyle::TextProgressionDirection)) { case KoText::RightLeftTopBottom: m_actionChangeDirection->setChecked(true); break; case KoText::LeftRightTopBottom: default: m_actionChangeDirection->setChecked(false); break; } } else { m_actionChangeDirection->setChecked(textEditor->block().text().isRightToLeft()); } if (bf.alignment() == Qt::AlignLeading || bf.alignment() == Qt::AlignTrailing) { bool revert = (textEditor->block().layout()->textOption().textDirection() == Qt::RightToLeft); if ((bf.alignment() == Qt::AlignLeading) ^ revert) m_actionAlignLeft->setChecked(true); else m_actionAlignRight->setChecked(true); } else if (bf.alignment() == Qt::AlignHCenter) m_actionAlignCenter->setChecked(true); if (bf.alignment() == Qt::AlignJustify) m_actionAlignBlock->setChecked(true); else if (bf.alignment() == (Qt::AlignLeft | Qt::AlignAbsolute)) m_actionAlignLeft->setChecked(true); else if (bf.alignment() == (Qt::AlignRight | Qt::AlignAbsolute)) m_actionAlignRight->setChecked(true); if (textEditor->block().textList()) { QTextListFormat listFormat = textEditor->block().textList()->format(); if(listFormat.intProperty(KoListStyle::Level) > 1) { m_actionFormatDecreaseIndent->setEnabled(true); } else { m_actionFormatDecreaseIndent->setEnabled(false); } if (listFormat.intProperty(KoListStyle::Level) < 10) { m_actionFormatIncreaseIndent->setEnabled(true); } else { m_actionFormatIncreaseIndent->setEnabled(false); } } else { m_actionFormatDecreaseIndent->setEnabled(textEditor->blockFormat().leftMargin() > 0.); } m_allowActions = true; bool useAdvancedText = !(canvas()->resourceManager()->intResource(KoCanvasResourceManager::ApplicationSpeciality) & KoCanvasResourceManager::NoAdvancedText); if (useAdvancedText) { action("insert_table")->setEnabled(notInAnnotation); bool hasTable = textEditor->currentTable(); action("insert_tablerow_above")->setEnabled(hasTable && notInAnnotation); action("insert_tablerow_below")->setEnabled(hasTable && notInAnnotation); action("insert_tablecolumn_left")->setEnabled(hasTable && notInAnnotation); action("insert_tablecolumn_right")->setEnabled(hasTable && notInAnnotation); action("delete_tablerow")->setEnabled(hasTable && notInAnnotation); action("delete_tablecolumn")->setEnabled(hasTable && notInAnnotation); action("merge_tablecells")->setEnabled(hasTable && notInAnnotation); action("split_tablecells")->setEnabled(hasTable && notInAnnotation); action("activate_borderpainter")->setEnabled(hasTable && notInAnnotation); } action("insert_annotation")->setEnabled(notInAnnotation); ///TODO if selection contains several different format emit blockChanged(textEditor->block()); emit charFormatChanged(cf, textEditor->blockCharFormat()); emit blockFormatChanged(bf); } void TextTool::updateStyleManager() { if (!m_textShapeData) return; KoStyleManager *styleManager = KoTextDocument(m_textShapeData->document()).styleManager(); emit styleManagerChanged(styleManager); //TODO move this to its own method m_changeTracker = KoTextDocument(m_textShapeData->document()).changeTracker(); } void TextTool::activate(ToolActivation toolActivation, const QSet &shapes) { Q_UNUSED(toolActivation); m_caretTimer.start(); m_caretTimerState = true; foreach (KoShape *shape, shapes) { m_textShape = dynamic_cast(shape); if (m_textShape) break; } if (!m_textShape) { // none found emit done(); // This is how we inform the rulers of the active range // No shape means no active range canvas()->resourceManager()->setResource(KoCanvasResourceManager::ActiveRange, QVariant(QRectF())); return; } // This is how we inform the rulers of the active range // For now we will not consider table cells, but just give the shape dimensions QVariant v; QRectF rect(QPoint(), m_textShape->size()); rect = m_textShape->absoluteTransformation(0).mapRect(rect); v.setValue(rect); canvas()->resourceManager()->setResource(KoCanvasResourceManager::ActiveRange, v); if ((!m_oldTextEditor.isNull()) && m_oldTextEditor.data()->document() != static_cast(m_textShape->userData())->document()) { m_oldTextEditor.data()->setPosition(m_oldTextEditor.data()->position()); //we need to redraw like this so we update the old textshape whereever it may be if (canvas()->canvasWidget()) canvas()->canvasWidget()->update(); } setShapeData(static_cast(m_textShape->userData())); useCursor(Qt::IBeamCursor); updateStyleManager(); repaintSelection(); updateSelectionHandler(); updateActions(); if (m_specialCharacterDocker) m_specialCharacterDocker->setEnabled(true); } void TextTool::deactivate() { m_caretTimer.stop(); m_caretTimerState = false; repaintCaret(); m_textShape = 0; // This is how we inform the rulers of the active range // No shape means no active range canvas()->resourceManager()->setResource(KoCanvasResourceManager::ActiveRange, QVariant(QRectF())); m_oldTextEditor = m_textEditor; setShapeData(0); updateSelectionHandler(); if (m_specialCharacterDocker) { m_specialCharacterDocker->setEnabled(false); m_specialCharacterDocker->setVisible(false); } } void TextTool::repaintDecorations() { if (m_textShapeData) repaintSelection(); } void TextTool::repaintCaret() { KoTextEditor *textEditor = m_textEditor.data(); if (!textEditor || !m_textShapeData) return; KoTextDocumentLayout *lay = qobject_cast(m_textShapeData->document()->documentLayout()); Q_ASSERT(lay); Q_UNUSED(lay); // If we have changed root area we need to update m_textShape and m_textShapeData if (m_delayedEnsureVisible) { m_delayedEnsureVisible = false; ensureCursorVisible(); return; } ensureCursorVisible(false); // ensures the various vars are updated bool upToDate; QRectF repaintRect = caretRect(textEditor->cursor(), &upToDate); repaintRect.moveTop(repaintRect.top() - m_textShapeData->documentOffset()); if (repaintRect.isValid()) { repaintRect = m_textShape->absoluteTransformation(0).mapRect(repaintRect); // Make sure there is enough space to show an icon QRectF iconSize = canvas()->viewConverter()->viewToDocument(QRect(0, 0, 18, 18)); repaintRect.setX(repaintRect.x() - iconSize.width() / 2); repaintRect.setRight(repaintRect.right() + iconSize.width() / 2); repaintRect.setTop(repaintRect.y() - iconSize.height() / 2); repaintRect.setBottom(repaintRect.bottom() + iconSize.height() / 2); canvas()->updateCanvas(repaintRect); } } void TextTool::repaintSelection() { KoTextEditor *textEditor = m_textEditor.data(); if (textEditor == 0) return; QTextCursor cursor = *textEditor->cursor(); QList shapes; KoTextDocumentLayout *lay = qobject_cast(textEditor->document()->documentLayout()); Q_ASSERT(lay); foreach (KoShape* shape, lay->shapes()) { TextShape *textShape = dynamic_cast(shape); if (textShape == 0) // when the shape is being deleted its no longer a TextShape but a KoShape continue; //Q_ASSERT(!shapes.contains(textShape)); if (!shapes.contains(textShape)) { shapes.append(textShape); } } // loop over all shapes that contain the text and update per shape. QRectF repaintRect = textRect(cursor); foreach (TextShape *ts, shapes) { QRectF rect = repaintRect; rect.moveTop(rect.y() - ts->textShapeData()->documentOffset()); rect = ts->absoluteTransformation(0).mapRect(rect); QRectF r = ts->boundingRect().intersected(rect); canvas()->updateCanvas(r); } } QRectF TextTool::caretRect(QTextCursor *cursor, bool *upToDate) const { QTextCursor tmpCursor(*cursor); tmpCursor.setPosition(cursor->position()); // looses the anchor QRectF rect = textRect(tmpCursor); if (rect.size() == QSizeF(0,0)) { if (upToDate) { *upToDate = false; } rect = m_lastImMicroFocus; // prevent block changed but layout not done } else { if (upToDate) { *upToDate = true; } m_lastImMicroFocus = rect; } return rect; } QRectF TextTool::textRect(QTextCursor &cursor) const { if (!m_textShapeData) return QRectF(); KoTextEditor *textEditor = m_textEditor.data(); KoTextDocumentLayout *lay = qobject_cast(textEditor->document()->documentLayout()); return lay->selectionBoundingBox(cursor); } KoToolSelection* TextTool::selection() { return m_toolSelection; } QList > TextTool::createOptionWidgets() { QList > widgets; SimpleCharacterWidget *scw = new SimpleCharacterWidget(this, 0); SimpleParagraphWidget *spw = new SimpleParagraphWidget(this, 0); if (m_textEditor.data()) { // connect(m_textEditor.data(), SIGNAL(paragraphStyleApplied(KoParagraphStyle*)), spw, SLOT(slotParagraphStyleApplied(KoParagraphStyle*))); // connect(m_textEditor.data(), SIGNAL(characterStyleApplied(KoCharacterStyle*)), scw, SLOT(slotCharacterStyleApplied(KoCharacterStyle*))); //initialise the char- and par- widgets with the current block and formats. scw->setCurrentBlockFormat(m_textEditor.data()->blockFormat()); scw->setCurrentFormat(m_textEditor.data()->charFormat(), m_textEditor.data()-> blockCharFormat()); spw->setCurrentBlock(m_textEditor.data()->block()); spw->setCurrentFormat(m_textEditor.data()->blockFormat()); } SimpleTableWidget *stw = new SimpleTableWidget(this, 0); SimpleInsertWidget *siw = new SimpleInsertWidget(this, 0); /* We do not use these for now. Let's see if they become useful at a certain point in time. If not, we can remove the whole chain (SimpleCharWidget, SimpleParWidget, DockerStyleComboModel) if (m_textShapeData && KoTextDocument(m_textShapeData->document()).styleManager()) { scw->setInitialUsedStyles(KoTextDocument(m_textShapeData->document()).styleManager()->usedCharacterStyles()); spw->setInitialUsedStyles(KoTextDocument(m_textShapeData->document()).styleManager()->usedParagraphStyles()); } */ // Connect to/with simple character widget (docker) connect(this, SIGNAL(styleManagerChanged(KoStyleManager *)), scw, SLOT(setStyleManager(KoStyleManager *))); connect(this, SIGNAL(charFormatChanged(QTextCharFormat, QTextCharFormat)), scw, SLOT(setCurrentFormat(QTextCharFormat, QTextCharFormat))); connect(this, SIGNAL(blockFormatChanged(QTextBlockFormat)), scw, SLOT(setCurrentBlockFormat(QTextBlockFormat))); connect(scw, SIGNAL(doneWithFocus()), this, SLOT(returnFocusToCanvas())); connect(scw, SIGNAL(characterStyleSelected(KoCharacterStyle *)), this, SLOT(setStyle(KoCharacterStyle*))); connect(scw, SIGNAL(newStyleRequested(QString)), this, SLOT(createStyleFromCurrentCharFormat(QString))); connect(scw, SIGNAL(showStyleManager(int)), this, SLOT(showStyleManager(int))); // Connect to/with simple paragraph widget (docker) connect(this, SIGNAL(styleManagerChanged(KoStyleManager *)), spw, SLOT(setStyleManager(KoStyleManager *))); connect(this, SIGNAL(blockChanged(const QTextBlock&)), spw, SLOT(setCurrentBlock(const QTextBlock&))); connect(this, SIGNAL(blockFormatChanged(QTextBlockFormat)), spw, SLOT(setCurrentFormat(QTextBlockFormat))); connect(spw, SIGNAL(doneWithFocus()), this, SLOT(returnFocusToCanvas())); connect(spw, SIGNAL(paragraphStyleSelected(KoParagraphStyle *)), this, SLOT(setStyle(KoParagraphStyle*))); connect(spw, SIGNAL(newStyleRequested(QString)), this, SLOT(createStyleFromCurrentBlockFormat(QString))); connect(spw, SIGNAL(showStyleManager(int)), this, SLOT(showStyleManager(int))); // Connect to/with simple table widget (docker) connect(this, SIGNAL(styleManagerChanged(KoStyleManager *)), stw, SLOT(setStyleManager(KoStyleManager *))); connect(stw, SIGNAL(doneWithFocus()), this, SLOT(returnFocusToCanvas())); connect(stw, SIGNAL(tableBorderDataUpdated(const KoBorder::BorderData &)), this, SLOT(setTableBorderData(const KoBorder::BorderData &))); // Connect to/with simple insert widget (docker) connect(siw, SIGNAL(doneWithFocus()), this, SLOT(returnFocusToCanvas())); connect(siw, SIGNAL(insertTableQuick(int, int)), this, SLOT(insertTableQuick(int, int))); updateStyleManager(); if (m_textShape) { updateActions(); } scw->setWindowTitle(i18n("Character")); widgets.append(scw); spw->setWindowTitle(i18n("Paragraph")); widgets.append(spw); bool useAdvancedText = !(canvas()->resourceManager()->intResource(KoCanvasResourceManager::ApplicationSpeciality) & KoCanvasResourceManager::NoAdvancedText); if (useAdvancedText) { stw->setWindowTitle(i18n("Table")); widgets.append(stw); siw->setWindowTitle(i18n("Insert")); widgets.append(siw); } return widgets; } void TextTool::returnFocusToCanvas() { canvas()->canvasWidget()->setFocus(); } void TextTool::startEditing(KUndo2Command* command) { m_currentCommand = command; m_currentCommandHasChildren = true; } void TextTool::stopEditing() { m_currentCommand = 0; m_currentCommandHasChildren = false; } void TextTool::insertNewSection() { KoTextEditor *textEditor = m_textEditor.data(); if (!textEditor) return; textEditor->newSection(); } void TextTool::configureSection() { KoTextEditor *textEditor = m_textEditor.data(); if (!textEditor) return; SectionFormatDialog *dia = new SectionFormatDialog(0, m_textEditor.data()); dia->exec(); delete dia; returnFocusToCanvas(); updateActions(); } +void TextTool::splitSections() +{ + KoTextEditor *textEditor = m_textEditor.data(); + if (!textEditor) return; + + SectionsSplitDialog *dia = new SectionsSplitDialog(0, m_textEditor.data()); + dia->exec(); + delete dia; + + returnFocusToCanvas(); + updateActions(); +} + void TextTool::pasteAsText() { KoTextEditor *textEditor = m_textEditor.data(); if (!textEditor) return; const QMimeData *data = QApplication::clipboard()->mimeData(QClipboard::Clipboard); // on windows we do not have data if we try to paste this selection if (!data) return; if (data->hasFormat(KoOdf::mimeType(KoOdf::Text)) || data->hasText()) { m_prevCursorPosition = m_textEditor.data()->position(); m_textEditor.data()->paste(canvas(), data, true); editingPluginEvents(); } } void TextTool::bold(bool bold) { m_textEditor.data()->bold(bold); } void TextTool::italic(bool italic) { m_textEditor.data()->italic(italic); } void TextTool::underline(bool underline) { m_textEditor.data()->underline(underline); } void TextTool::strikeOut(bool strikeOut) { m_textEditor.data()->strikeOut(strikeOut); } void TextTool::nonbreakingSpace() { if (!m_allowActions || !m_textEditor.data()) return; m_textEditor.data()->insertText(QString(QChar(Qt::Key_nobreakspace))); } void TextTool::nonbreakingHyphen() { if (!m_allowActions || !m_textEditor.data()) return; m_textEditor.data()->insertText(QString(QChar(0x2013))); } void TextTool::softHyphen() { if (!m_allowActions || !m_textEditor.data()) return; m_textEditor.data()->insertText(QString(QChar(Qt::Key_hyphen))); } void TextTool::lineBreak() { if (!m_allowActions || !m_textEditor.data()) return; m_textEditor.data()->insertText(QString(QChar(0x2028))); } void TextTool::alignLeft() { if (!m_allowActions || !m_textEditor.data()) return; m_textEditor.data()->setHorizontalTextAlignment(Qt::AlignLeft | Qt::AlignAbsolute); } void TextTool::alignRight() { if (!m_allowActions || !m_textEditor.data()) return; m_textEditor.data()->setHorizontalTextAlignment(Qt::AlignRight | Qt::AlignAbsolute); } void TextTool::alignCenter() { if (!m_allowActions || !m_textEditor.data()) return; m_textEditor.data()->setHorizontalTextAlignment(Qt::AlignHCenter); } void TextTool::alignBlock() { if (!m_allowActions || !m_textEditor.data()) return; m_textEditor.data()->setHorizontalTextAlignment(Qt::AlignJustify); } void TextTool::superScript(bool on) { if (!m_allowActions || !m_textEditor.data()) return; if (on) m_actionFormatSub->setChecked(false); m_textEditor.data()->setVerticalTextAlignment(on ? Qt::AlignTop : Qt::AlignVCenter); } void TextTool::subScript(bool on) { if (!m_allowActions || !m_textEditor.data()) return; if (on) m_actionFormatSuper->setChecked(false); m_textEditor.data()->setVerticalTextAlignment(on ? Qt::AlignBottom : Qt::AlignVCenter); } void TextTool::increaseIndent() { if (!m_allowActions || !m_textEditor.data()) return; if (m_textEditor.data()->block().textList()) { ChangeListLevelCommand::CommandType type = ChangeListLevelCommand::IncreaseLevel; ChangeListLevelCommand *cll = new ChangeListLevelCommand(*(m_textEditor.data()->cursor()), type, 1); m_textEditor.data()->addCommand(cll); editingPluginEvents(); } else { m_textEditor.data()->increaseIndent(); } updateActions(); } void TextTool::decreaseIndent() { if (!m_allowActions || !m_textEditor.data()) return; if (m_textEditor.data()->block().textList()) { ChangeListLevelCommand::CommandType type = ChangeListLevelCommand::DecreaseLevel; ChangeListLevelCommand *cll = new ChangeListLevelCommand(*(m_textEditor.data()->cursor()), type, 1); m_textEditor.data()->addCommand(cll); editingPluginEvents(); } else { m_textEditor.data()->decreaseIndent(); } updateActions(); } void TextTool::decreaseFontSize() { if (!m_allowActions || !m_textEditor.data()) return; m_textEditor.data()->decreaseFontSize(); } void TextTool::increaseFontSize() { if (!m_allowActions || !m_textEditor.data()) return; m_textEditor.data()->increaseFontSize(); } void TextTool::setFontFamily(const QString &font) { if (!m_allowActions || !m_textEditor.data()) return; m_textEditor.data()->setFontFamily(font); } void TextTool::setFontSize (qreal size) { if (!m_allowActions || !m_textEditor.data()) return; m_textEditor.data()->setFontSize(size); } void TextTool::insertIndexMarker() { // TODO handle result when we figure out how to report errors from a tool. m_textEditor.data()->insertIndexMarker(); } void TextTool::insertFrameBreak() { m_textEditor.data()->insertFrameBreak(); ensureCursorVisible(); m_delayedEnsureVisible = true; } void TextTool::setStyle(KoCharacterStyle *style) { KoCharacterStyle *charStyle = style; //if the given KoCharacterStyle is null, set the KoParagraphStyle character properties if (!charStyle){ charStyle = static_cast(KoTextDocument(m_textShapeData->document()).styleManager()->paragraphStyle(m_textEditor.data()->blockFormat().intProperty(KoParagraphStyle::StyleId))); } if (charStyle) { m_textEditor.data()->setStyle(charStyle); updateActions(); } } void TextTool::setStyle(KoParagraphStyle *style) { m_textEditor.data()->setStyle(style); updateActions(); } void TextTool::insertTable() { TableDialog *dia = new TableDialog(0); if (dia->exec() == TableDialog::Accepted) m_textEditor.data()->insertTable(dia->rows(), dia->columns()); delete dia; updateActions(); } void TextTool::insertTableQuick(int rows, int columns) { m_textEditor.data()->insertTable(rows, columns); updateActions(); } void TextTool::insertTableRowAbove() { m_textEditor.data()->insertTableRowAbove(); } void TextTool::insertTableRowBelow() { m_textEditor.data()->insertTableRowBelow(); } void TextTool::insertTableColumnLeft() { m_textEditor.data()->insertTableColumnLeft(); } void TextTool::insertTableColumnRight() { m_textEditor.data()->insertTableColumnRight(); } void TextTool::deleteTableColumn() { m_textEditor.data()->deleteTableColumn(); } void TextTool::deleteTableRow() { m_textEditor.data()->deleteTableRow(); } void TextTool::mergeTableCells() { m_textEditor.data()->mergeTableCells(); } void TextTool::splitTableCells() { m_textEditor.data()->splitTableCells(); } void TextTool::useTableBorderCursor() { static const unsigned char data[] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x68, 0x00, 0x00, 0x00, 0xf4, 0x00, 0x00, 0x00, 0xfa, 0x00, 0x00, 0x00, 0xfd, 0x00, 0x00, 0x80, 0x7e, 0x00, 0x00, 0x40, 0x3f, 0x00, 0x00, 0xa0, 0x1f, 0x00, 0x00, 0xd0, 0x0f, 0x00, 0x00, 0xe8, 0x07, 0x00, 0x00, 0xf4, 0x03, 0x00, 0x00, 0xe4, 0x01, 0x00, 0x00, 0xc2, 0x00, 0x00, 0x80, 0x41, 0x00, 0x00, 0x40, 0x32, 0x00, 0x00, 0xa0, 0x0f, 0x00, 0x00, 0xd0, 0x0f, 0x00, 0x00, 0xd0, 0x0f, 0x00, 0x00, 0xe8, 0x07, 0x00, 0x00, 0xf4, 0x01, 0x00, 0x00, 0x7e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; QBitmap result(32, 32); result.fill(Qt::color0); QPainter painter(&result); painter.drawPixmap(0, 0, QBitmap::fromData(QSize(25, 23), data)); QBitmap brushMask = result.createHeuristicMask(false); useCursor(QCursor(result, brushMask, 1, 21)); } void TextTool::setTableBorderData(const KoBorder::BorderData &data) { m_tablePenMode = true; m_tablePenBorderData = data; } void TextTool::formatParagraph() { ParagraphSettingsDialog *dia = new ParagraphSettingsDialog(this, m_textEditor.data()); dia->setUnit(canvas()->unit()); dia->setImageCollection(m_textShape->imageCollection()); dia->exec(); delete dia; returnFocusToCanvas(); } void TextTool::testSlot(bool on) { kDebug(32500) << "signal received. bool:" << on; } void TextTool::selectAll() { KoTextEditor *textEditor = m_textEditor.data(); if (!textEditor || !m_textShapeData) return; const int selectionLength = qAbs(textEditor->position() - textEditor->anchor()); textEditor->movePosition(QTextCursor::End); textEditor->setPosition(0, QTextCursor::KeepAnchor); repaintSelection(); if (selectionLength != qAbs(textEditor->position() - textEditor->anchor())) // it actually changed emit selectionChanged(true); } void TextTool::startMacro(const QString &title) { if (title != i18n("Key Press") && title !=i18n("Autocorrection")) //dirty hack while waiting for refactor of text editing m_textTyping = false; else m_textTyping = true; if (title != i18n("Delete") && title != i18n("Autocorrection")) //same dirty hack as above m_textDeleting = false; else m_textDeleting = true; if (m_currentCommand) return; class MacroCommand : public KUndo2Command { public: MacroCommand(const KUndo2MagicString &title) : KUndo2Command(title), m_first(true) {} virtual void redo() { if (! m_first) KUndo2Command::redo(); m_first = false; } virtual bool mergeWith(const KUndo2Command *) { return false; } bool m_first; }; /** * FIXME: The messages genearted by the Text Tool might not be * properly translated, since we don't control it in * type-safe way. * * The title is already translated string, we just don't * have any type control over it. */ KUndo2MagicString title_workaround = kundo2_noi18n(title); m_currentCommand = new MacroCommand(title_workaround); m_currentCommandHasChildren = false; } void TextTool::stopMacro() { if (!m_currentCommand) return; if (! m_currentCommandHasChildren) delete m_currentCommand; m_currentCommand = 0; } void TextTool::showStyleManager(int styleId) { if (!m_textShapeData) return; KoStyleManager *styleManager = KoTextDocument(m_textShapeData->document()).styleManager(); Q_ASSERT(styleManager); if (!styleManager) return; //don't crash StyleManagerDialog *dia = new StyleManagerDialog(canvas()->canvasWidget()); dia->setStyleManager(styleManager); dia->setUnit(canvas()->unit()); KoParagraphStyle *paragraphStyle = styleManager->paragraphStyle(styleId); if (paragraphStyle) { dia->setParagraphStyle(paragraphStyle); } KoCharacterStyle *characterStyle = styleManager->characterStyle(styleId); if (characterStyle) { dia->setCharacterStyle(characterStyle); } dia->show(); } void TextTool::startTextEditingPlugin(const QString &pluginId) { KoTextEditingPlugin *plugin = textEditingPluginContainer()->plugin(pluginId); if (plugin) { if (m_textEditor.data()->hasSelection()) { plugin->checkSection(m_textShapeData->document(), m_textEditor.data()->selectionStart(), m_textEditor.data()->selectionEnd()); } else plugin->finishedWord(m_textShapeData->document(), m_textEditor.data()->position()); } } void TextTool::canvasResourceChanged(int key, const QVariant &var) { if (m_textEditor.isNull()) return; if (!m_textShapeData) return; if (m_allowResourceManagerUpdates == false) return; if (key == KoText::CurrentTextPosition) { repaintSelection(); m_textEditor.data()->setPosition(var.toInt()); ensureCursorVisible(); } else if (key == KoText::CurrentTextAnchor) { repaintSelection(); int pos = m_textEditor.data()->position(); m_textEditor.data()->setPosition(var.toInt()); m_textEditor.data()->setPosition(pos, QTextCursor::KeepAnchor); } else if (key == KoCanvasResourceManager::Unit) { m_unit = var.value(); } else return; repaintSelection(); } void TextTool::insertSpecialCharacter() { if (m_specialCharacterDocker == 0) { m_specialCharacterDocker = new InsertCharacter(canvas()->canvasWidget()); connect(m_specialCharacterDocker, SIGNAL(insertCharacter(const QString&)), this, SLOT(insertString(const QString&))); } m_specialCharacterDocker->show(); } void TextTool::insertString(const QString& string) { m_textEditor.data()->insertText(string); returnFocusToCanvas(); } void TextTool::selectFont() { FontDia *fontDlg = new FontDia(m_textEditor.data()); fontDlg->exec(); delete fontDlg; returnFocusToCanvas(); } void TextTool::shapeAddedToCanvas() { kDebug(); if (m_textShape) { KoSelection *selection = canvas()->shapeManager()->selection(); KoShape *shape = selection->firstSelectedShape(); if (shape != m_textShape && canvas()->shapeManager()->shapes().contains(m_textShape)) { // this situation applies when someone, not us, changed the selection by selecting another // text shape. Possibly by adding one. // Deselect the new shape again, so we can keep editing what we were already editing selection->select(m_textShape); selection->deselect(shape); } } } void TextTool::shapeDataRemoved() { m_textShapeData = 0; m_textShape = 0; if (!m_textEditor.isNull() && !m_textEditor.data()->cursor()->isNull()) { const QTextDocument *doc = m_textEditor.data()->document(); Q_ASSERT(doc); KoTextDocumentLayout *lay = qobject_cast(doc->documentLayout()); if (!lay || lay->shapes().isEmpty()) { emit done(); return; } m_textShape = static_cast(lay->shapes().first()); m_textShapeData = static_cast(m_textShape->userData()); connect(m_textShapeData, SIGNAL(destroyed (QObject*)), this, SLOT(shapeDataRemoved())); } } void TextTool::createStyleFromCurrentBlockFormat(const QString &name) { KoTextDocument document(m_textShapeData->document()); KoStyleManager *styleManager = document.styleManager(); KoParagraphStyle *paragraphStyle = new KoParagraphStyle(m_textEditor.data()->blockFormat(), m_textEditor.data()->charFormat()); paragraphStyle->setName(name); styleManager->add(paragraphStyle); m_textEditor.data()->setStyle(paragraphStyle); emit charFormatChanged(m_textEditor.data()->charFormat(), m_textEditor.data()->blockCharFormat()); emit blockFormatChanged(m_textEditor.data()->blockFormat()); } void TextTool::createStyleFromCurrentCharFormat(const QString &name) { KoTextDocument document(m_textShapeData->document()); KoStyleManager *styleManager = document.styleManager(); KoCharacterStyle *originalCharStyle = styleManager->characterStyle(m_textEditor.data()->charFormat().intProperty(KoCharacterStyle::StyleId)); KoCharacterStyle *autoStyle; if (!originalCharStyle) { KoCharacterStyle blankStyle; originalCharStyle = &blankStyle; autoStyle = originalCharStyle->autoStyle(m_textEditor.data()->charFormat(), m_textEditor.data()->blockCharFormat()); autoStyle->setParentStyle(0); } else { autoStyle = originalCharStyle->autoStyle(m_textEditor.data()->charFormat(), m_textEditor.data()->blockCharFormat()); } autoStyle->setName(name); styleManager->add(autoStyle); m_textEditor.data()->setStyle(autoStyle); emit charFormatChanged(m_textEditor.data()->charFormat(), m_textEditor.data()->blockCharFormat()); } // ---------- editing plugins methods. void TextTool::editingPluginEvents() { if (m_prevCursorPosition == -1 || m_prevCursorPosition == m_textEditor.data()->position()) { kDebug()<<"m_prevCursorPosition="<position()="<position(); return; } QTextBlock block = m_textEditor.data()->block(); if (! block.contains(m_prevCursorPosition)) { kDebug()<<"m_prevCursorPosition="<position(); if (from > to) qSwap(from, to); QString section = block.text().mid(from - block.position(), to - from); kDebug()<<"from="<values()) { plugin->finishedWord(m_textShapeData->document(), m_prevCursorPosition); } } } void TextTool::finishedParagraph() { if (m_textShapeData && textEditingPluginContainer()) { foreach (KoTextEditingPlugin* plugin, textEditingPluginContainer()->values()) { plugin->finishedParagraph(m_textShapeData->document(), m_prevCursorPosition); } } } void TextTool::startingSimpleEdit() { if (m_textShapeData && textEditingPluginContainer()) { foreach (KoTextEditingPlugin* plugin, textEditingPluginContainer()->values()) { plugin->startingSimpleEdit(m_textShapeData->document(), m_prevCursorPosition); } } } void TextTool::setTextColor(const KoColor &color) { m_textEditor.data()->setTextColor(color.toQColor()); } void TextTool::setBackgroundColor(const KoColor &color) { m_textEditor.data()->setTextBackgroundColor(color.toQColor()); } void TextTool::setGrowWidthToFit(bool enabled) { m_textEditor.data()->addCommand(new AutoResizeCommand(m_textShapeData, KoTextShapeData::AutoGrowWidth, enabled)); updateActions(); } void TextTool::setGrowHeightToFit(bool enabled) { m_textEditor.data()->addCommand(new AutoResizeCommand(m_textShapeData, KoTextShapeData::AutoGrowHeight, enabled)); updateActions(); } void TextTool::setShrinkToFit(bool enabled) { m_textEditor.data()->addCommand(new AutoResizeCommand(m_textShapeData, KoTextShapeData::ShrinkToFitResize, enabled)); updateActions(); } void TextTool::runUrl(KoPointerEvent *event, QString &url) { bool isLocalLink = (url.indexOf("file:") == 0); QString type = KMimeType::findByUrl(url, 0, isLocalLink)->name(); if (KRun::isExecutableFile(url, type)) { QString question = i18n("This link points to the program or script '%1'.\n" "Malicious programs can harm your computer. " "Are you sure that you want to run this program?", url); // this will also start local programs, so adding a "don't warn again" // checkbox will probably be too dangerous int choice = KMessageBox::warningYesNo(0, question, i18n("Open Link?")); if (choice != KMessageBox::Yes) return; } event->accept(); new KRun(url, 0); } void TextTool::debugTextDocument() { #ifndef NDEBUG if (!m_textShapeData) return; const int CHARSPERLINE = 80; // TODO Make configurable using ENV var? const int CHARPOSITION = 278301935; KoTextDocument document(m_textShapeData->document()); KoStyleManager *styleManager = document.styleManager(); KoInlineTextObjectManager *inlineManager = document.inlineTextObjectManager(); QTextBlock block = m_textShapeData->document()->begin(); for (;block.isValid(); block = block.next()) { QVariant var = block.blockFormat().property(KoParagraphStyle::StyleId); if (!var.isNull()) { KoParagraphStyle *ps = styleManager->paragraphStyle(var.toInt()); kDebug(32500) << "--- Paragraph Style:" << (ps ? ps->name() : QString()) << var.toInt(); } var = block.charFormat().property(KoCharacterStyle::StyleId); if (!var.isNull()) { KoCharacterStyle *cs = styleManager->characterStyle(var.toInt()); kDebug(32500) << "--- Character Style:" << (cs ? cs->name() : QString()) << var.toInt(); } int lastPrintedChar = -1; QTextBlock::iterator it; QString fragmentText; QList inlineCharacters; for (it = block.begin(); !it.atEnd(); ++it) { QTextFragment fragment = it.fragment(); if (!fragment.isValid()) continue; QTextCharFormat fmt = fragment.charFormat(); kDebug(32500) << "changeId: " << fmt.property(KoCharacterStyle::ChangeTrackerId); const int fragmentStart = fragment.position() - block.position(); for (int i = fragmentStart; i < fragmentStart + fragment.length(); i += CHARSPERLINE) { if (lastPrintedChar == fragmentStart-1) fragmentText += '|'; if (lastPrintedChar < fragmentStart || i > fragmentStart) { QString debug = block.text().mid(lastPrintedChar, CHARSPERLINE); lastPrintedChar += CHARSPERLINE; if (lastPrintedChar > block.length()) debug += "\\n"; kDebug(32500) << debug; } var = fmt.property(KoCharacterStyle::StyleId); QString charStyleLong, charStyleShort; if (! var.isNull()) { // named style charStyleShort = QString::number(var.toInt()); KoCharacterStyle *cs = styleManager->characterStyle(var.toInt()); if (cs) charStyleLong = cs->name(); } if (inlineManager && fmt.hasProperty(KoCharacterStyle::InlineInstanceId)) { QTextCharFormat inlineFmt = fmt; inlineFmt.setProperty(CHARPOSITION, fragmentStart); inlineCharacters << inlineFmt; } if (fragment.length() > charStyleLong.length()) fragmentText += charStyleLong; else if (fragment.length() > charStyleShort.length()) fragmentText += charStyleShort; else if (fragment.length() >= 2) fragmentText += QChar(8230); // elipses int rest = fragmentStart - (lastPrintedChar-CHARSPERLINE) + fragment.length() - fragmentText.length(); rest = qMin(rest, CHARSPERLINE - fragmentText.length()); if (rest >= 2) fragmentText = QString("%1%2").arg(fragmentText).arg(' ', rest); if (rest >= 0) fragmentText += '|'; if (fragmentText.length() >= CHARSPERLINE) { kDebug(32500) << fragmentText; fragmentText.clear(); } } } if (!fragmentText.isEmpty()) { kDebug(32500) << fragmentText; } else if (block.length() == 1) { // no actual tet kDebug(32500) << "\\n"; } foreach (const QTextCharFormat &cf, inlineCharacters) { KoInlineObject *object= inlineManager->inlineTextObject(cf); kDebug(32500) << "At pos:" << cf.intProperty(CHARPOSITION) << object; // kDebug(32500) << "-> id:" << cf.intProperty(577297549); } QTextList *list = block.textList(); if (list) { if (list->format().hasProperty(KoListStyle::StyleId)) { KoListStyle *ls = styleManager->listStyle(list->format().intProperty(KoListStyle::StyleId)); kDebug(32500) << " List style applied:" << ls->styleId() << ls->name(); } else kDebug(32500) << " +- is a list..." << list; } } #endif } void TextTool::debugTextStyles() { #ifndef NDEBUG if (!m_textShapeData) return; KoTextDocument document(m_textShapeData->document()); KoStyleManager *styleManager = document.styleManager(); QSet seenStyles; foreach (KoParagraphStyle *style, styleManager->paragraphStyles()) { kDebug(32500) << style->styleId() << style->name() << (styleManager->defaultParagraphStyle() == style ? "[Default]" : ""); KoListStyle *ls = style->listStyle(); if (ls) { // optional ;) kDebug(32500) << " +- ListStyle: " << ls->styleId() << ls->name() << (ls == styleManager->defaultListStyle() ? "[Default]":""); foreach (int level, ls->listLevels()) { KoListLevelProperties llp = ls->levelProperties(level); kDebug(32500) << " | level" << llp.level() << " style (enum):" << llp.style(); if (llp.bulletCharacter().unicode() != 0) { kDebug(32500) << " | bullet" << llp.bulletCharacter(); } } seenStyles << ls->styleId(); } } bool first = true; foreach (KoCharacterStyle *style, styleManager->characterStyles()) { if (seenStyles.contains(style->styleId())) continue; if (first) { kDebug(32500) << "--- Character styles ---"; first = false; } kDebug(32500) << style->styleId() << style->name(); kDebug(32500) << style->font(); } first = true; foreach (KoListStyle *style, styleManager->listStyles()) { if (seenStyles.contains(style->styleId())) continue; if (first) { kDebug(32500) << "--- List styles ---"; first = false; } kDebug(32500) << style->styleId() << style->name() << (style == styleManager->defaultListStyle() ? "[Default]":""); } #endif } void TextTool::textDirectionChanged() { if (!m_allowActions || !m_textEditor.data()) return; QTextBlockFormat blockFormat; if (m_actionChangeDirection->isChecked()) { blockFormat.setProperty(KoParagraphStyle::TextProgressionDirection, KoText::RightLeftTopBottom); } else { blockFormat.setProperty(KoParagraphStyle::TextProgressionDirection, KoText::LeftRightTopBottom); } m_textEditor.data()->mergeBlockFormat(blockFormat); } void TextTool::setListLevel(int level) { if (level < 1 || level > 10) { return; } KoTextEditor *textEditor = m_textEditor.data(); if (textEditor->block().textList()) { ChangeListLevelCommand::CommandType type = ChangeListLevelCommand::SetLevel; ChangeListLevelCommand *cll = new ChangeListLevelCommand(*textEditor->cursor(), type, level); textEditor->addCommand(cll); editingPluginEvents(); } } void TextTool::insertAnnotation() { AnnotationTextShape *shape = (AnnotationTextShape*)KoShapeRegistry::instance()->value(AnnotationShape_SHAPEID)->createDefaultShape(canvas()->shapeController()->resourceManager()); textEditor()->addAnnotation(shape); // Set annotation creator. KConfig cfg("calligrarc"); cfg.reparseConfiguration(); KConfigGroup authorGroup(&cfg, "Author"); QStringList profiles = authorGroup.readEntry("profile-names", QStringList()); KGlobal::config()->reparseConfiguration(); KConfigGroup appAuthorGroup(KGlobal::config(), "Author"); QString profile = appAuthorGroup.readEntry("active-profile", ""); KConfigGroup cgs(&authorGroup, "Author-" + profile); if (profiles.contains(profile)) { KConfigGroup cgs(&authorGroup, "Author-" + profile); shape->setCreator(cgs.readEntry("creator")); } else { if (profile == "anonymous") { shape->setCreator("Anonymous"); } else { KUser user(KUser::UseRealUserID); shape->setCreator(user.property(KUser::FullName).toString()); } } // Set Annotation creation date. shape->setDate(QDate::currentDate().toString(Qt::ISODate)); } diff --git a/plugins/textshape/TextTool.h b/plugins/textshape/TextTool.h index 79ce4d5e6a..89207bc3d3 100644 --- a/plugins/textshape/TextTool.h +++ b/plugins/textshape/TextTool.h @@ -1,414 +1,417 @@ /* This file is part of the KDE project * Copyright (C) 2006-2009 Thomas Zander * Copyright (C) 2008 Thorsten Zachmann * Copyright (C) 2009 KO GmbH * Copyright (C) 2011 Mojtaba Shahi Senobari * Copyright (C) 2008, 2012 Pierre Stirnweiss * Copyright (C) 2014 Denis Kuplyakov * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef KOTEXTTOOL_H #define KOTEXTTOOL_H #include "TextShape.h" #include "KoPointedAt.h" #include #include #include #include #include #include #include #include #include #include #include #include #include class InsertCharacter; class KoChangeTracker; class KoCharacterStyle; class KoColor; class KoColorPopupAction; class KoParagraphStyle; class KoStyleManager; class KoTextEditor; class UndoTextCommand; class KAction; class KActionMenu; class KoFontFamilyAction; class FontSizeAction; class KUndo2Command; class QDrag; class QMimeData; class MockCanvas; class TextToolSelection; /** * This is the tool for the text-shape (which is a flake-based plugin). */ class TextTool : public KoToolBase, public KoUndoableTool { Q_OBJECT public: explicit TextTool(KoCanvasBase *canvas); #ifndef NDEBUG explicit TextTool(MockCanvas *canvas); #endif virtual ~TextTool(); /// reimplemented from superclass virtual void paint(QPainter &painter, const KoViewConverter &converter); /// reimplemented from superclass virtual void mousePressEvent(KoPointerEvent *event); /// reimplemented from superclass virtual void mouseDoubleClickEvent(KoPointerEvent *event); /// reimplemented from superclass virtual void mouseTripleClickEvent(KoPointerEvent *event); /// reimplemented from superclass virtual void mouseMoveEvent(KoPointerEvent *event); /// reimplemented from superclass virtual void mouseReleaseEvent(KoPointerEvent *event); /// reimplemented from superclass virtual void keyPressEvent(QKeyEvent *event); /// reimplemented from superclass virtual void keyReleaseEvent(QKeyEvent *event); /// reimplemented from superclass virtual void activate(ToolActivation toolActivation, const QSet &shapes); /// reimplemented from superclass virtual void deactivate(); /// reimplemented from superclass virtual void copy() const; /// reimplemented from KoUndoableTool virtual void setAddUndoCommandAllowed(bool allowed) { m_allowAddUndoCommand = allowed; } ///reimplemented virtual void deleteSelection(); /// reimplemented from superclass virtual void cut(); /// reimplemented from superclass virtual bool paste(); /// reimplemented from superclass virtual QStringList supportedPasteMimeTypes() const; /// reimplemented from superclass virtual void dragMoveEvent(QDragMoveEvent *event, const QPointF &point); /// reimplemented from superclass void dragLeaveEvent(QDragLeaveEvent *event); /// reimplemented from superclass virtual void dropEvent(QDropEvent *event, const QPointF &point); /// reimplemented from superclass virtual void repaintDecorations(); /// reimplemented from superclass virtual KoToolSelection* selection(); /// reimplemented from superclass virtual QList > createOptionWidgets(); /// reimplemented from superclass virtual QVariant inputMethodQuery(Qt::InputMethodQuery query, const KoViewConverter &converter) const; /// reimplemented from superclass virtual void inputMethodEvent(QInputMethodEvent * event); /// The following two methods allow an undo/redo command to tell the tool, it will modify the QTextDocument and wants to be parent of the undo/redo commands resulting from these changes. void startEditing(KUndo2Command* command); void stopEditing(); void setShapeData(KoTextShapeData *data); QRectF caretRect(QTextCursor *cursor, bool *upToDate=0) const; QRectF textRect(QTextCursor &cursor) const; protected: virtual void createActions(); TextShape *textShape() {return m_textShape;} friend class SimpleParagraphWidget; friend class ParagraphSettingsDialog; KoTextEditor *textEditor() { return m_textEditor.data(); } public Q_SLOTS: /// Insert comment to document. void insertAnnotation(); /// start the textedit-plugin. void startTextEditingPlugin(const QString &pluginId); /// reimplemented from KoToolBase virtual void canvasResourceChanged(int key, const QVariant &res); Q_SIGNALS: /// emitted every time a different styleManager is set. void styleManagerChanged(KoStyleManager *manager); /// emitted every time a caret move leads to a different character format being under the caret void charFormatChanged(const QTextCharFormat &format, const QTextCharFormat& refBlockCharFormat); /// emitted every time a caret move leads to a different paragraph format being under the caret void blockFormatChanged(const QTextBlockFormat &format); /// emitted every time a caret move leads to a different paragraph format being under the caret void blockChanged(const QTextBlock &block); private Q_SLOTS: /// inserts new paragraph and includes it into the new section void insertNewSection(); /// configures params of the current section void configureSection(); + /// inserts paragraph between sections bounds + void splitSections(); /// paste text from the clipboard without formatting void pasteAsText(); /// make the selected text bold or not void bold(bool); /// make the selected text italic or not void italic(bool); /// underline of the selected text void underline(bool underline); /// strikethrough of the selected text void strikeOut(bool strikeOut); /// insert a non breaking space at the caret position void nonbreakingSpace(); /// insert a non breaking hyphen at the caret position void nonbreakingHyphen(); /// insert a soft hyphen at the caret position void softHyphen(); /// insert a linebreak at the caret position void lineBreak(); /// force the remainder of the text into the next page void insertFrameBreak(); /// align all of the selected text left void alignLeft(); /// align all of the selected text right void alignRight(); /// align all of the selected text centered void alignCenter(); /// align all of the selected text block-justified void alignBlock(); /// make the selected text switch to be super-script void superScript(bool); /// make the selected text switch to be sub-script void subScript(bool); /// move the paragraph indent of the selected text to be less (left on LtR text) void decreaseIndent(); /// move the paragraph indent of the selected text to be more (right on LtR text) void increaseIndent(); /// Increase the font size. This will preserve eventual difference in font size within the selection. void increaseFontSize(); /// Decrease font size. See above. void decreaseFontSize(); /// Set font family void setFontFamily(const QString &); /// Set Font size void setFontSize(qreal size); /// see KoTextEditor::insertIndexMarker void insertIndexMarker(); /// shows a dialog to insert a table void insertTable(); /// insert a table of given dimensions void insertTableQuick(int rows, int columns); /// insert a row above void insertTableRowAbove(); /// insert a row below void insertTableRowBelow(); /// insert a column left void insertTableColumnLeft(); /// insert a column right void insertTableColumnRight(); /// delete a column void deleteTableColumn(); /// delete a row void deleteTableRow(); /// merge table cells void mergeTableCells(); /// split previous merged table cells void splitTableCells(); /// format the table border (enter table pen mode) void setTableBorderData(const KoBorder::BorderData &data); /// shows a dialog to alter the paragraph properties void formatParagraph(); /// select all text in the current document. void selectAll(); /// show the style manager void showStyleManager(int styleId = -1); /// change color of a selected text void setTextColor(const KoColor &color); /// change background color of a selected text void setBackgroundColor(const KoColor &color); /// Enable or disable grow-width-to-fit-text. void setGrowWidthToFit(bool enabled); /// Enable or disable grow-height-to-fit-text. void setGrowHeightToFit(bool enabled); /// Enable or disable shrink-to-fit-text. void setShrinkToFit(bool enabled); /// set Paragraph style of current selection. Existing style will be completely overridden. void setStyle(KoParagraphStyle *syle); /// set the characterStyle of the current selection. see above. void setStyle(KoCharacterStyle *style); /// set the level of current selected list void setListLevel(int level); /// slot to call when a series of commands is started that together need to become 1 undo action. void startMacro(const QString &title); /// slot to call when a series of commands has ended that together should be 1 undo action. void stopMacro(); /// show the insert special character docker. void insertSpecialCharacter(); /// insert string void insertString(const QString &string); /// returns the focus to canvas when styles are selected in the optionDocker void returnFocusToCanvas(); void selectFont(); void shapeAddedToCanvas(); void blinkCaret(); void relayoutContent(); // called when the m_textShapeData has been deleted. void shapeDataRemoved(); //Show tooltip with editing info void showEditTip(); /// print debug about the details of the text document void debugTextDocument(); /// print debug about the details of the styles on the current text document void debugTextStyles(); void ensureCursorVisible(bool moveView = true); void createStyleFromCurrentBlockFormat(const QString &name); void createStyleFromCurrentCharFormat(const QString &name); void testSlot(bool); /// change block text direction void textDirectionChanged(); void updateActions(); private: void repaintCaret(); void repaintSelection(); KoPointedAt hitTest(const QPointF & point) const; void updateStyleManager(); void updateSelectedShape(const QPointF &point, bool noDocumentChange); void updateSelectionHandler(); void editingPluginEvents(); void finishedWord(); void finishedParagraph(); void startingSimpleEdit(); void runUrl(KoPointerEvent *event, QString &url); void useTableBorderCursor(); QMimeData *generateMimeData() const; TextEditingPluginContainer *textEditingPluginContainer(); private: friend class UndoTextCommand; friend class ChangeTracker; friend class TextCutCommand; friend class ShowChangesCommand; TextShape *m_textShape; // where caret of m_textEditor currently is KoTextShapeData *m_textShapeData; // where caret of m_textEditor currently is QWeakPointer m_textEditor; QWeakPointer m_oldTextEditor; KoChangeTracker *m_changeTracker; KoUnit m_unit; bool m_allowActions; bool m_allowAddUndoCommand; bool m_allowResourceManagerUpdates; int m_prevCursorPosition; /// used by editingPluginEvents int m_prevMouseSelectionStart, m_prevMouseSelectionEnd; QTimer m_caretTimer; bool m_caretTimerState; KAction *m_actionPasteAsText; KAction *m_actionFormatBold; KAction *m_actionFormatItalic; KAction *m_actionFormatUnderline; KAction *m_actionFormatStrikeOut; KAction *m_actionAlignLeft; KAction *m_actionAlignRight; KAction *m_actionAlignCenter; KAction *m_actionAlignBlock; KAction *m_actionFormatSuper; KAction *m_actionFormatSub; KAction *m_actionFormatIncreaseIndent; KAction *m_actionFormatDecreaseIndent; KAction *m_growWidthAction; KAction *m_growHeightAction; KAction *m_shrinkToFitAction; KAction *m_actionChangeDirection; KAction *m_actionInsertSection; KAction *m_actionConfigureSection; + KAction *m_actionSplitSections; KActionMenu *m_variableMenu; FontSizeAction *m_actionFormatFontSize; KoFontFamilyAction *m_actionFormatFontFamily; KoColorPopupAction *m_actionFormatTextColor; KoColorPopupAction *m_actionFormatBackgroundColor; KUndo2Command *m_currentCommand; //this command will be the direct parent of undoCommands generated as the result of QTextDocument changes bool m_currentCommandHasChildren; InsertCharacter *m_specialCharacterDocker; QPointer m_textEditingPlugins; bool m_textTyping; bool m_textDeleting; QTimer m_editTipTimer; KoPointedAt m_editTipPointedAt; QPoint m_editTipPos; bool m_delayedEnsureVisible; TextToolSelection *m_toolSelection; KoPointedAt m_tableDragInfo; bool m_tableDraggedOnce; bool m_tableDragWithShift; QPointF m_draggingOrigin; qreal m_dx; qreal m_dy; bool m_tablePenMode; KoBorder::BorderData m_tablePenBorderData; mutable QRectF m_lastImMicroFocus; bool m_clickWithinSelection; QDrag *m_drag; QAbstractTextDocumentLayout::Selection m_preDragSelection; }; #endif diff --git a/plugins/textshape/dialogs/SectionFormatDialog.cpp b/plugins/textshape/dialogs/SectionFormatDialog.cpp index edaa2a3a2d..b3a098655c 100644 --- a/plugins/textshape/dialogs/SectionFormatDialog.cpp +++ b/plugins/textshape/dialogs/SectionFormatDialog.cpp @@ -1,130 +1,182 @@ /* This file is part of the KDE project - * Copyright (C) 2014 Denis Kuplyakov + * Copyright (C) 2014-2015 Denis Kuplyakov * * 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 "SectionFormatDialog.h" #include +#include #include +#include +#include #include #include - #include +class SectionFormatDialog::ProxyModel : public QIdentityProxyModel +{ +public: + ProxyModel(KoSectionModel *model, QObject *parent = 0) + : QIdentityProxyModel(parent) + { + setSourceModel(model); + } + + virtual int columnCount(const QModelIndex &parent = QModelIndex()) const + { + Q_UNUSED(parent); + return 1; // We have one column with "Name of section" + } + + virtual QVariant headerData(int section, Qt::Orientation orientation, int role) const + { + if (orientation != Qt::Horizontal || section != 0) { + return QVariant(); + } + + if (role == Qt::DisplayRole) { + return i18n("Section name"); + } + return QVariant(); + } + + virtual QVariant data(const QModelIndex &proxyIndex, int role = Qt::DisplayRole) const + { + if (!proxyIndex.isValid() || proxyIndex.column() != 0) { + return QVariant(); + } + + if (role == Qt::DisplayRole) { + KoSection *ptr = getSectionByIndex(proxyIndex); + return ptr->name(); + } + return QVariant(); + } + + KoSection *getSectionByIndex(const QModelIndex &idx) const + { + return sourceModel()->data( + mapToSource(idx), + KoSectionModel::PointerRole + ).value(); + } + +private: + // Make it private. It is intented to be used only with KoSectionModel that is passed through constructor + virtual void setSourceModel(QAbstractItemModel *sourceModel) + { + QAbstractProxyModel::setSourceModel(sourceModel); + } +}; + class SectionFormatDialog::SectionNameValidator : public QValidator { public: - SectionNameValidator(QObject *parent, KoSectionManager *sectionManager, KoSection *section) - : QValidator(parent) - , m_sectionManager(sectionManager) - , m_section(section) + SectionNameValidator(QObject *parent, KoSectionModel *sectionManager, KoSection *section) + : QValidator(parent) + , m_sectionModel(sectionManager) + , m_section(section) { } virtual State validate(QString &input, int &pos) const { Q_UNUSED(pos); - if (m_section->name() == input || m_sectionManager->isValidNewName(input)) { + if (m_section->name() == input || m_sectionModel->isValidNewName(input)) { return QValidator::Acceptable; } return QValidator::Intermediate; } private: - KoSectionManager *m_sectionManager; + KoSectionModel *m_sectionModel; KoSection *m_section; }; SectionFormatDialog::SectionFormatDialog(QWidget *parent, KoTextEditor *editor) : KDialog(parent) , m_editor(editor) { setCaption(i18n("Configure sections")); setButtons(KDialog::Ok | KDialog::Cancel); showButtonSeparator(true); QWidget *form = new QWidget; m_widget.setupUi(form); setMainWidget(form); - m_sectionManager = KoTextDocument(editor->document()).sectionManager(); - QStandardItemModel *model = m_sectionManager->update(true); - model->setColumnCount(1); - QStringList header; - header << i18n("Section name"); - model->setHorizontalHeaderLabels(header); - m_widget.sectionTree->setModel(model); + m_sectionModel = KoTextDocument(editor->document()).sectionModel(); + m_widget.sectionTree->setModel(new ProxyModel(m_sectionModel, this)); m_widget.sectionTree->expandAll(); m_widget.sectionNameLineEdit->setEnabled(false); connect(m_widget.sectionTree, SIGNAL(activated(QModelIndex)), this, SLOT(sectionSelected(QModelIndex))); connect(m_widget.sectionNameLineEdit, SIGNAL(editingFinished()), this, SLOT(sectionNameChanged())); connect(m_widget.sectionNameLineEdit, SIGNAL(textEdited(QString)), this, SLOT(updateTreeState())); m_curIdx = m_widget.sectionTree->currentIndex(); } void SectionFormatDialog::sectionNameChanged() { m_editor->renameSection(sectionFromModel(m_curIdx), m_widget.sectionNameLineEdit->text()); - m_widget.sectionTree->model()->setData(m_curIdx, m_widget.sectionNameLineEdit->text(), Qt::DisplayRole); m_widget.sectionNameLineEdit->setModified(false); // value is set to line edit isn't modified (has new default value) } void SectionFormatDialog::sectionSelected(const QModelIndex &idx) { KoSection *curSection = sectionFromModel(idx); m_curIdx = m_widget.sectionTree->currentIndex(); // Update widgets m_widget.sectionNameLineEdit->setEnabled(true); m_widget.sectionNameLineEdit->setText(curSection->name()); m_widget.sectionNameLineEdit->setValidator( - new SectionNameValidator(this, m_sectionManager, curSection)); + new SectionNameValidator(this, m_sectionModel, curSection)); } void SectionFormatDialog::updateTreeState() { if (!m_curIdx.isValid()) { return; } bool allOk = true; QPalette pal = m_widget.sectionNameLineEdit->palette(); if (!m_widget.sectionNameLineEdit->hasAcceptableInput()) { KColorScheme::adjustBackground(pal, KColorScheme::NegativeBackground); m_widget.sectionNameLineEdit->setPalette(pal); QToolTip::showText(m_widget.sectionNameLineEdit->mapToGlobal(QPoint()), i18n("Invalid characters or section with such name exists.")); allOk = false; } else { KColorScheme::adjustBackground(pal, KColorScheme::NormalBackground); m_widget.sectionNameLineEdit->setPalette(pal); } m_widget.sectionTree->setEnabled(allOk); enableButtonOk(allOk); } inline KoSection* SectionFormatDialog::sectionFromModel(const QModelIndex &idx) { - return m_widget.sectionTree->model()->itemData(idx)[Qt::UserRole + 1].value(); + return dynamic_cast(m_widget.sectionTree->model())->getSectionByIndex(idx); } diff --git a/plugins/textshape/dialogs/SectionFormatDialog.h b/plugins/textshape/dialogs/SectionFormatDialog.h index 9bd2fe8f11..8eb554e88e 100644 --- a/plugins/textshape/dialogs/SectionFormatDialog.h +++ b/plugins/textshape/dialogs/SectionFormatDialog.h @@ -1,54 +1,54 @@ /* This file is part of the KDE project - * Copyright (C) 2014 Denis Kuplyakov + * Copyright (C) 2014-2015 Denis Kuplyakov * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef SECTIONFORMATDIALOG_H #define SECTIONFORMATDIALOG_H -#include -#include - #include -#include -#include +class KoTextEditor; +class KoSection; +class KoSectionModel; +#include class SectionFormatDialog : public KDialog { Q_OBJECT public: explicit SectionFormatDialog(QWidget *parent, KoTextEditor *editor); private Q_SLOTS: void sectionSelected(const QModelIndex &idx); void sectionNameChanged(); void updateTreeState(); private: + class ProxyModel; + class SectionNameValidator; + Ui::SectionFormatDialog m_widget; KoTextEditor* m_editor; QModelIndex m_curIdx; - KoSectionManager *m_sectionManager; - - class SectionNameValidator; + KoSectionModel *m_sectionModel; KoSection *sectionFromModel(const QModelIndex &idx); }; #endif //SECTIONFORMATDIALOG_H diff --git a/plugins/textshape/dialogs/SectionsSplitDialog.cpp b/plugins/textshape/dialogs/SectionsSplitDialog.cpp new file mode 100644 index 0000000000..53e7b17abb --- /dev/null +++ b/plugins/textshape/dialogs/SectionsSplitDialog.cpp @@ -0,0 +1,81 @@ +/* This file is part of the KDE project + * Copyright (C) 2015 Denis Kuplyakov + * + * 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 "SectionsSplitDialog.h" + +#include +#include +#include +#include + +#include + +SectionsSplitDialog::SectionsSplitDialog(QWidget *parent, KoTextEditor *editor) + : KDialog(parent) + , m_editor(editor) +{ + setCaption(i18n("Configure sections")); + setButtons(KDialog::Ok | KDialog::Cancel); + enableButton(KDialog::Ok, false); + showButtonSeparator(true); + QWidget *form = new QWidget; + m_widget.setupUi(form); + setMainWidget(form); + + QList secStartings = KoSectionUtils::sectionStartings(editor->blockFormat()); + QList secEndings = KoSectionUtils::sectionEndings(editor->blockFormat()); + foreach (KoSection *sec, secStartings) { + m_widget.beforeList->addItem(sec->name()); + } + foreach (KoSectionEnd *secEnd, secEndings) { + m_widget.afterList->addItem(secEnd->name()); + } + + connect(m_widget.beforeList, SIGNAL(itemSelectionChanged()), this, SLOT(beforeListSelection())); + connect(m_widget.afterList, SIGNAL(itemSelectionChanged()), this, SLOT(afterListSelection())); + + connect(this, SIGNAL(okClicked()), this, SLOT(okClicked())); +} + +void SectionsSplitDialog::afterListSelection() +{ + if (m_widget.afterList->selectedItems().size()) { // FIXME: more elegant way to check selection? + enableButton(KDialog::Ok, true); + m_widget.beforeList->clearSelection(); + } +} + +void SectionsSplitDialog::beforeListSelection() +{ + if (m_widget.beforeList->selectedItems().size()) { + enableButton(KDialog::Ok, true); + m_widget.afterList->clearSelection(); + } +} + +void SectionsSplitDialog::okClicked() +{ + if (m_widget.beforeList->selectedItems().size()) { + m_editor->splitSectionsStartings(m_widget.beforeList->currentRow()); + } else { + m_editor->splitSectionsEndings(m_widget.afterList->currentRow()); + } +} + +#include diff --git a/plugins/textshape/dialogs/SectionFormatDialog.h b/plugins/textshape/dialogs/SectionsSplitDialog.h similarity index 57% copy from plugins/textshape/dialogs/SectionFormatDialog.h copy to plugins/textshape/dialogs/SectionsSplitDialog.h index 9bd2fe8f11..1a595afc98 100644 --- a/plugins/textshape/dialogs/SectionFormatDialog.h +++ b/plugins/textshape/dialogs/SectionsSplitDialog.h @@ -1,54 +1,48 @@ /* This file is part of the KDE project - * Copyright (C) 2014 Denis Kuplyakov + * Copyright (C) 2015 Denis Kuplyakov * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ -#ifndef SECTIONFORMATDIALOG_H -#define SECTIONFORMATDIALOG_H - -#include -#include +#ifndef SECTIONSSPLITDIALOG_H +#define SECTIONSSPLITDIALOG_H #include -#include -#include +class KoTextEditor; +class KoSection; +class KoSectionModel; -class SectionFormatDialog : public KDialog +#include +class SectionsSplitDialog : public KDialog { Q_OBJECT - + public: - explicit SectionFormatDialog(QWidget *parent, KoTextEditor *editor); - + explicit SectionsSplitDialog(QWidget *parent, KoTextEditor *editor); + private Q_SLOTS: - void sectionSelected(const QModelIndex &idx); - void sectionNameChanged(); - void updateTreeState(); - + void beforeListSelection(); + void afterListSelection(); + + void okClicked(); + private: - Ui::SectionFormatDialog m_widget; + Ui::SectionsSplitDialog m_widget; KoTextEditor* m_editor; - QModelIndex m_curIdx; - KoSectionManager *m_sectionManager; - - class SectionNameValidator; - - KoSection *sectionFromModel(const QModelIndex &idx); }; -#endif //SECTIONFORMATDIALOG_H +#endif //SECTIONSSPLITDIALOG_H diff --git a/plugins/textshape/dialogs/SectionsSplitDialog.ui b/plugins/textshape/dialogs/SectionsSplitDialog.ui new file mode 100644 index 0000000000..994a520a83 --- /dev/null +++ b/plugins/textshape/dialogs/SectionsSplitDialog.ui @@ -0,0 +1,52 @@ + + + SectionsSplitDialog + + + Split sections + + + + + + Insert paragpraph... + + + + + + + + + + + before start of section: + + + + + + + + + + + + + + after end of section: + + + + + + + + + + + + + + + diff --git a/plugins/textshape/dialogs/SimpleInsertWidget.cpp b/plugins/textshape/dialogs/SimpleInsertWidget.cpp index 4c0d16e0c9..864edb8899 100644 --- a/plugins/textshape/dialogs/SimpleInsertWidget.cpp +++ b/plugins/textshape/dialogs/SimpleInsertWidget.cpp @@ -1,55 +1,57 @@ /* This file is part of the KDE project * Copyright (C) 2010-2011 C. Boemann * Copyright (C) 2014 Denis Kuplyakov * * 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 "SimpleInsertWidget.h" #include "TextTool.h" #include #include #include SimpleInsertWidget::SimpleInsertWidget(TextTool *tool, QWidget *parent) : QWidget(parent), m_blockSignals(false), m_tool(tool) { widget.setupUi(this); widget.insertVariable->setDefaultAction(tool->action("insert_variable")); widget.insertVariable->setPopupMode(QToolButton::InstantPopup); //because action overrode ui file widget.insertSpecialChar->setDefaultAction(tool->action("insert_specialchar")); widget.quickTable->addAction(tool->action("insert_table")); widget.insertSection->setDefaultAction(tool->action("insert_section")); widget.configureSection->setDefaultAction(tool->action("configure_section")); widget.insertPageBreak->setDefaultAction(tool->action("insert_framebreak")); + widget.splitSections->setDefaultAction(tool->action("split_sections")); connect(widget.insertVariable, SIGNAL(clicked(bool)), this, SIGNAL(doneWithFocus())); connect(widget.insertSpecialChar, SIGNAL(clicked(bool)), this, SIGNAL(doneWithFocus())); connect(widget.insertPageBreak, SIGNAL(clicked(bool)), this, SIGNAL(doneWithFocus())); connect(widget.insertSection, SIGNAL(clicked(bool)), this, SIGNAL(doneWithFocus())); connect(widget.configureSection, SIGNAL(clicked(bool)), this, SIGNAL(doneWithFocus())); + connect(widget.splitSections, SIGNAL(clicked(bool)), this, SIGNAL(doneWithFocus())); connect(widget.quickTable, SIGNAL(create(int, int)), this, SIGNAL(insertTableQuick(int, int))); connect(widget.quickTable, SIGNAL(create(int, int)), this, SIGNAL(doneWithFocus())); } void SimpleInsertWidget::setStyleManager(KoStyleManager *sm) { m_styleManager = sm; } diff --git a/plugins/textshape/dialogs/SimpleInsertWidget.ui b/plugins/textshape/dialogs/SimpleInsertWidget.ui index be668860c0..2433f50658 100644 --- a/plugins/textshape/dialogs/SimpleInsertWidget.ui +++ b/plugins/textshape/dialogs/SimpleInsertWidget.ui @@ -1,181 +1,200 @@ SimpleInsertWidget 0 0 230 66 0 0 Other insertions from "References" below and in "Add Shape" docker QLayout::SetMinAndMaxSize 0 0 2 + + + + ... + + + + 16 + 16 + + + + QToolButton::InstantPopup + + + true + + + ... 16 16 QToolButton::InstantPopup true ... 16 16 QToolButton::InstantPopup true ... 16 16 QToolButton::InstantPopup true Qt::Horizontal 40 20 ... 16 16 QToolButton::InstantPopup true ... 16 16 true ... 16 16 true 0 0 QuickTableButton QToolButton
dialogs/QuickTableButton.h