diff --git a/libs/flake/KoShapeLoadingContext.h b/libs/flake/KoShapeLoadingContext.h --- a/libs/flake/KoShapeLoadingContext.h +++ b/libs/flake/KoShapeLoadingContext.h @@ -1,7 +1,7 @@ /* 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 @@ -37,7 +37,7 @@ class KoImageCollection; class KoSharedLoadingData; class KoDocumentResourceManager; -class KoSectionManager; +class KoSectionModel; /** * Context passed to shapes during loading. @@ -191,16 +191,16 @@ 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 diff --git a/libs/flake/KoShapeLoadingContext.cpp b/libs/flake/KoShapeLoadingContext.cpp --- a/libs/flake/KoShapeLoadingContext.cpp +++ b/libs/flake/KoShapeLoadingContext.cpp @@ -1,7 +1,7 @@ /* 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 @@ -46,7 +46,7 @@ , zIndex(0) , documentResources(resourceManager) , documentRdf(0) - , sectionManager(0) + , sectionModel(0) { } @@ -66,7 +66,7 @@ QMap updaterByShape; KoDocumentResourceManager *documentResources; QObject *documentRdf; - KoSectionManager *sectionManager; + KoSectionModel *sectionModel; }; KoShapeLoadingContext::KoShapeLoadingContext(KoOdfLoadingContext & context, KoDocumentResourceManager *documentResources) @@ -210,12 +210,12 @@ 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/kotext/CMakeLists.txt b/libs/kotext/CMakeLists.txt --- a/libs/kotext/CMakeLists.txt +++ b/libs/kotext/CMakeLists.txt @@ -41,7 +41,7 @@ KoSection.cpp KoSectionEnd.cpp KoSectionUtils.cpp - KoSectionManager.cpp + KoSectionModel.cpp KoTextLocator.cpp KoTextReference.cpp KoAnchorInlineObject.cpp @@ -120,6 +120,7 @@ commands/ParagraphFormattingCommand.cpp commands/RenameSectionCommand.cpp commands/NewSectionCommand.cpp + commands/SplitSectionsCommand.cpp KoTextDrag.cpp KoTextCommandBase.cpp @@ -194,7 +195,7 @@ KoSection.h KoSectionEnd.h KoSectionUtils.h - KoSectionManager.h + KoSectionModel.h KoTextCommandBase.h KoTextTableTemplate.h diff --git a/libs/kotext/KoInlineNote.cpp b/libs/kotext/KoInlineNote.cpp --- a/libs/kotext/KoInlineNote.cpp +++ b/libs/kotext/KoInlineNote.cpp @@ -96,7 +96,6 @@ KoParagraphStyle *style = static_cast(notesConfig->defaultNoteParagraphStyle()); if (style) { - QTextBlock block = cursor.block(); QTextBlockFormat bf; QTextCharFormat cf; style->applyStyle(bf); diff --git a/libs/kotext/KoSection.h b/libs/kotext/KoSection.h --- a/libs/kotext/KoSection.h +++ b/libs/kotext/KoSection.h @@ -1,6 +1,6 @@ /* * 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 @@ -57,20 +57,14 @@ 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 @@ -92,13 +86,47 @@ 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 *) diff --git a/libs/kotext/KoSection.cpp b/libs/kotext/KoSection.cpp --- a/libs/kotext/KoSection.cpp +++ b/libs/kotext/KoSection.cpp @@ -1,6 +1,6 @@ /* * 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 @@ -26,7 +26,7 @@ #include #include #include -#include +#include #include #include #include @@ -38,18 +38,18 @@ 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; @@ -60,24 +60,41 @@ 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 @@ -89,33 +106,18 @@ 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); @@ -129,9 +131,10 @@ 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"); @@ -171,7 +174,9 @@ 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) { @@ -185,22 +190,46 @@ 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 diff --git a/libs/kotext/KoSectionEnd.h b/libs/kotext/KoSectionEnd.h --- a/libs/kotext/KoSectionEnd.h +++ b/libs/kotext/KoSectionEnd.h @@ -1,6 +1,6 @@ /* * 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 @@ -36,8 +36,7 @@ */ 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; @@ -50,6 +49,11 @@ private: Q_DISABLE_COPY(KoSectionEnd) Q_DECLARE_PRIVATE(KoSectionEnd) + + explicit KoSectionEnd(KoSection *section); + + friend class KoSectionModel; + friend class TestKoTextEditor; }; Q_DECLARE_METATYPE(KoSectionEnd *) diff --git a/libs/kotext/KoSectionManager.h b/libs/kotext/KoSectionManager.h deleted file mode 100644 --- 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/KoSectionManager.cpp b/libs/kotext/KoSectionManager.cpp deleted file mode 100644 --- 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/KoSectionModel.h b/libs/kotext/KoSectionModel.h new file mode 100644 --- /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/KoSectionModel.cpp b/libs/kotext/KoSectionModel.cpp new file mode 100644 --- /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/KoSectionUtils.h b/libs/kotext/KoSectionUtils.h --- a/libs/kotext/KoSectionUtils.h +++ b/libs/kotext/KoSectionUtils.h @@ -41,16 +41,16 @@ * @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. diff --git a/libs/kotext/KoSectionUtils.cpp b/libs/kotext/KoSectionUtils.cpp --- a/libs/kotext/KoSectionUtils.cpp +++ b/libs/kotext/KoSectionUtils.cpp @@ -37,7 +37,7 @@ return true; } -void KoSectionUtils::setSectionStartings(QTextBlockFormat &fmt, QList &list) +void KoSectionUtils::setSectionStartings(QTextBlockFormat &fmt, const QList &list) { if (list.empty()) { fmt.clearProperty(KoParagraphStyle::SectionStartings); @@ -47,7 +47,7 @@ } } -void KoSectionUtils::setSectionEndings(QTextBlockFormat &fmt, QList &list) +void KoSectionUtils::setSectionEndings(QTextBlockFormat &fmt, const QList &list) { if (list.empty()) { fmt.clearProperty(KoParagraphStyle::SectionEndings); diff --git a/libs/kotext/KoTextDocument.h b/libs/kotext/KoTextDocument.h --- a/libs/kotext/KoTextDocument.h +++ b/libs/kotext/KoTextDocument.h @@ -2,7 +2,7 @@ * 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 @@ -39,7 +39,7 @@ class KoOdfLineNumberingConfiguration; class KoChangeTracker; class KoShapeController; -class KoSectionManager; +class KoSectionModel; class QTextCharFormat; @@ -141,15 +141,11 @@ /// 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; @@ -241,7 +237,7 @@ FrameCharFormat, FrameBlockFormat, ShapeController, - SectionManager + SectionModel }; static const QUrl StyleManagerURL; @@ -262,7 +258,7 @@ static const QUrl FrameCharFormatUrl; static const QUrl FrameBlockFormatUrl; static const QUrl ShapeControllerUrl; - static const QUrl SectionManagerUrl; + static const QUrl SectionModelUrl; private: QTextDocument *m_document; diff --git a/libs/kotext/KoTextDocument.cpp b/libs/kotext/KoTextDocument.cpp --- a/libs/kotext/KoTextDocument.cpp +++ b/libs/kotext/KoTextDocument.cpp @@ -3,7 +3,7 @@ * 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 @@ -42,7 +42,7 @@ #include "KoOdfLineNumberingConfiguration.h" #include "changetracker/KoChangeTracker.h" #include -#include +#include Q_DECLARE_METATYPE(QAbstractTextDocumentLayout::Selection) Q_DECLARE_METATYPE(QTextFrame*) @@ -66,7 +66,7 @@ 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) @@ -399,20 +399,18 @@ 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/KoTextEditor.h b/libs/kotext/KoTextEditor.h --- a/libs/kotext/KoTextEditor.h +++ b/libs/kotext/KoTextEditor.h @@ -141,6 +141,7 @@ friend class ParagraphFormattingCommand; friend class RenameSectionCommand; friend class NewSectionCommand; + friend class SplitSectionsCommand; // for unittests friend class TestKoInlineTextObjectManager; @@ -460,7 +461,49 @@ 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); diff --git a/libs/kotext/KoTextEditor.cpp b/libs/kotext/KoTextEditor.cpp --- a/libs/kotext/KoTextEditor.cpp +++ b/libs/kotext/KoTextEditor.cpp @@ -3,7 +3,7 @@ * 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 @@ -68,6 +68,7 @@ #include "commands/AddAnnotationCommand.h" #include "commands/RenameSectionCommand.h" #include "commands/NewSectionCommand.h" +#include "commands/SplitSectionsCommand.h" #include @@ -1515,14 +1516,36 @@ 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() diff --git a/libs/kotext/KoTextPaste.cpp b/libs/kotext/KoTextPaste.cpp --- a/libs/kotext/KoTextPaste.cpp +++ b/libs/kotext/KoTextPaste.cpp @@ -30,12 +30,12 @@ #include #include #include -#include +#include #include #ifdef SHOULD_BUILD_RDF #include "KoTextRdfCore.h" -#include "KoSectionManager.h" +#include "KoSectionModel.h" #include #endif @@ -77,15 +77,15 @@ 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; diff --git a/libs/kotext/commands/DeleteCommand.h b/libs/kotext/commands/DeleteCommand.h --- a/libs/kotext/commands/DeleteCommand.h +++ b/libs/kotext/commands/DeleteCommand.h @@ -2,7 +2,7 @@ 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 @@ -30,11 +30,13 @@ #include class QTextDocument; + class KoShapeController; class KoInlineObject; +class KoTextRange; +class KoSection; class DeleteVisitor; -class KoTextRange; class DeleteCommand : public KoTextCommandBase { @@ -56,14 +58,28 @@ 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; @@ -74,6 +90,9 @@ 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/DeleteCommand.cpp b/libs/kotext/commands/DeleteCommand.cpp --- a/libs/kotext/commands/DeleteCommand.cpp +++ b/libs/kotext/commands/DeleteCommand.cpp @@ -3,7 +3,7 @@ * 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 @@ -35,51 +35,79 @@ #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) { @@ -93,48 +121,51 @@ } } +// 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. @@ -150,7 +181,17 @@ 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)); } @@ -173,10 +214,11 @@ 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(); @@ -194,95 +236,19 @@ } } - 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) @@ -302,15 +268,135 @@ }; 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(); @@ -329,25 +415,34 @@ 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); @@ -369,17 +464,24 @@ } } + // 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) { diff --git a/libs/kotext/commands/NewSectionCommand.h b/libs/kotext/commands/NewSectionCommand.h --- a/libs/kotext/commands/NewSectionCommand.h +++ b/libs/kotext/commands/NewSectionCommand.h @@ -20,11 +20,14 @@ #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: @@ -36,8 +39,10 @@ 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/NewSectionCommand.cpp b/libs/kotext/commands/NewSectionCommand.cpp --- a/libs/kotext/commands/NewSectionCommand.cpp +++ b/libs/kotext/commands/NewSectionCommand.cpp @@ -1,6 +1,6 @@ /* * 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 @@ -22,9 +22,9 @@ #include #include #include -#include #include #include +#include #include #include @@ -44,31 +44,45 @@ 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); diff --git a/libs/kotext/commands/RenameSectionCommand.h b/libs/kotext/commands/RenameSectionCommand.h --- a/libs/kotext/commands/RenameSectionCommand.h +++ b/libs/kotext/commands/RenameSectionCommand.h @@ -1,6 +1,6 @@ /* * 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 @@ -20,17 +20,20 @@ #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(); @@ -40,10 +43,11 @@ 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/RenameSectionCommand.cpp b/libs/kotext/commands/RenameSectionCommand.cpp --- a/libs/kotext/commands/RenameSectionCommand.cpp +++ b/libs/kotext/commands/RenameSectionCommand.cpp @@ -1,6 +1,6 @@ /* * 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 @@ -19,12 +19,15 @@ #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) @@ -39,21 +42,22 @@ 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; } @@ -64,15 +68,9 @@ } 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/NewSectionCommand.h b/libs/kotext/commands/SplitSectionsCommand.h copy from libs/kotext/commands/NewSectionCommand.h copy to libs/kotext/commands/SplitSectionsCommand.h --- a/libs/kotext/commands/NewSectionCommand.h +++ b/libs/kotext/commands/SplitSectionsCommand.h @@ -1,6 +1,6 @@ /* * 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 @@ -17,27 +17,37 @@ * 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/commands/SplitSectionsCommand.cpp b/libs/kotext/commands/SplitSectionsCommand.cpp new file mode 100644 --- /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/opendocument/KoTextLoader.h b/libs/kotext/opendocument/KoTextLoader.h --- a/libs/kotext/opendocument/KoTextLoader.h +++ b/libs/kotext/opendocument/KoTextLoader.h @@ -129,7 +129,7 @@ /** * 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 . @@ -157,17 +157,17 @@ /** * 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. diff --git a/libs/kotext/opendocument/KoTextLoader.cpp b/libs/kotext/opendocument/KoTextLoader.cpp --- a/libs/kotext/opendocument/KoTextLoader.cpp +++ b/libs/kotext/opendocument/KoTextLoader.cpp @@ -14,7 +14,7 @@ * 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 @@ -83,6 +83,7 @@ #include "styles/KoTableCellStyle.h" #include "styles/KoSectionStyle.h" #include +#include #include #include @@ -140,6 +141,7 @@ QVector nameSpacesList; QList openingSections; + QStack sectionStack; // Used to track the parent of current section QMap xmlIdToListMap; QVector m_previousList; @@ -430,6 +432,10 @@ //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) @@ -740,7 +746,7 @@ } } -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"; @@ -813,23 +819,28 @@ 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) @@ -856,7 +867,6 @@ } } - void KoTextLoader::loadCite(const KoXmlElement ¬eElem, QTextCursor &cursor) { KoInlineTextObjectManager *textObjectManager = KoTextDocument(cursor.block().document()).inlineTextObjectManager(); @@ -1313,7 +1323,7 @@ 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(); @@ -1343,7 +1353,7 @@ 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); @@ -1386,7 +1396,7 @@ } } -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; diff --git a/libs/kotext/opendocument/KoTextWriter_p.cpp b/libs/kotext/opendocument/KoTextWriter_p.cpp --- a/libs/kotext/opendocument/KoTextWriter_p.cpp +++ b/libs/kotext/opendocument/KoTextWriter_p.cpp @@ -90,7 +90,7 @@ // 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; diff --git a/libs/kotext/tests/CMakeLists.txt b/libs/kotext/tests/CMakeLists.txt --- a/libs/kotext/tests/CMakeLists.txt +++ b/libs/kotext/tests/CMakeLists.txt @@ -20,12 +20,6 @@ ########### 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) diff --git a/libs/kotext/tests/TestDeleteSectionHandling_data.cpp b/libs/kotext/tests/TestDeleteSectionHandling_data.cpp new file mode 100644 --- /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 --- /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.h b/libs/kotext/tests/TestKoTextEditor.h --- a/libs/kotext/tests/TestKoTextEditor.h +++ b/libs/kotext/tests/TestKoTextEditor.h @@ -1,6 +1,7 @@ /* 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 @@ -25,24 +26,63 @@ 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/TestKoTextEditor.cpp b/libs/kotext/tests/TestKoTextEditor.cpp --- a/libs/kotext/tests/TestKoTextEditor.cpp +++ b/libs/kotext/tests/TestKoTextEditor.cpp @@ -1,6 +1,7 @@ /* 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 @@ -43,18 +44,75 @@ #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; @@ -69,7 +127,7 @@ 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()); @@ -85,690 +143,404 @@ 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/TestSection.h b/libs/kotext/tests/TestSection.h deleted file mode 100644 --- 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/libs/kotext/tests/TestSection.cpp b/libs/kotext/tests/TestSection.cpp deleted file mode 100644 --- 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/plugins/textshape/CMakeLists.txt b/plugins/textshape/CMakeLists.txt --- a/plugins/textshape/CMakeLists.txt +++ b/plugins/textshape/CMakeLists.txt @@ -111,6 +111,7 @@ dialogs/SimpleAnnotationWidget.cpp dialogs/ManageBookmarkDialog.cpp dialogs/SectionFormatDialog.cpp + dialogs/SectionsSplitDialog.cpp commands/ChangeListLevelCommand.cpp commands/ShowChangesCommand.cpp @@ -169,6 +170,7 @@ dialogs/LinkInsertionDialog.ui dialogs/ManageBookmark.ui dialogs/SectionFormatDialog.ui + dialogs/SectionsSplitDialog.ui ) diff --git a/plugins/textshape/TextTool.h b/plugins/textshape/TextTool.h --- a/plugins/textshape/TextTool.h +++ b/plugins/textshape/TextTool.h @@ -181,6 +181,8 @@ 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 @@ -371,6 +373,7 @@ KAction *m_actionChangeDirection; KAction *m_actionInsertSection; KAction *m_actionConfigureSection; + KAction *m_actionSplitSections; KActionMenu *m_variableMenu; FontSizeAction *m_actionFormatFontSize; diff --git a/plugins/textshape/TextTool.cpp b/plugins/textshape/TextTool.cpp --- a/plugins/textshape/TextTool.cpp +++ b/plugins/textshape/TextTool.cpp @@ -36,6 +36,7 @@ #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" @@ -220,6 +221,10 @@ 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); @@ -2274,6 +2279,19 @@ 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(); diff --git a/plugins/textshape/dialogs/SectionFormatDialog.h b/plugins/textshape/dialogs/SectionFormatDialog.h --- a/plugins/textshape/dialogs/SectionFormatDialog.h +++ b/plugins/textshape/dialogs/SectionFormatDialog.h @@ -1,5 +1,5 @@ /* 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 @@ -20,14 +20,13 @@ #ifndef SECTIONFORMATDIALOG_H #define SECTIONFORMATDIALOG_H -#include -#include - #include -#include -#include +class KoTextEditor; +class KoSection; +class KoSectionModel; +#include class SectionFormatDialog : public KDialog { Q_OBJECT @@ -41,12 +40,13 @@ 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); }; diff --git a/plugins/textshape/dialogs/SectionFormatDialog.cpp b/plugins/textshape/dialogs/SectionFormatDialog.cpp --- a/plugins/textshape/dialogs/SectionFormatDialog.cpp +++ b/plugins/textshape/dialogs/SectionFormatDialog.cpp @@ -1,5 +1,5 @@ /* 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 @@ -20,34 +20,92 @@ #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; }; @@ -62,13 +120,8 @@ 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); @@ -83,7 +136,6 @@ 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) } @@ -96,7 +148,7 @@ 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() @@ -126,5 +178,5 @@ 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/SectionsSplitDialog.h copy from plugins/textshape/dialogs/SectionFormatDialog.h copy to plugins/textshape/dialogs/SectionsSplitDialog.h --- a/plugins/textshape/dialogs/SectionFormatDialog.h +++ b/plugins/textshape/dialogs/SectionsSplitDialog.h @@ -1,5 +1,5 @@ /* 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 @@ -17,38 +17,32 @@ * 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.cpp b/plugins/textshape/dialogs/SectionsSplitDialog.cpp new file mode 100644 --- /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/SectionsSplitDialog.ui b/plugins/textshape/dialogs/SectionsSplitDialog.ui new file mode 100644 --- /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 --- a/plugins/textshape/dialogs/SimpleInsertWidget.cpp +++ b/plugins/textshape/dialogs/SimpleInsertWidget.cpp @@ -38,12 +38,14 @@ 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())); diff --git a/plugins/textshape/dialogs/SimpleInsertWidget.ui b/plugins/textshape/dialogs/SimpleInsertWidget.ui --- a/plugins/textshape/dialogs/SimpleInsertWidget.ui +++ b/plugins/textshape/dialogs/SimpleInsertWidget.ui @@ -34,6 +34,25 @@ 2 + + + + ... + + + + 16 + 16 + + + + QToolButton::InstantPopup + + + true + + + diff --git a/words/part/CMakeLists.txt b/words/part/CMakeLists.txt --- a/words/part/CMakeLists.txt +++ b/words/part/CMakeLists.txt @@ -83,14 +83,11 @@ ) endif() -if( SHOULD_BUILD_FEATURE_RDF ) -# TODO: depends hard on Soprano set(wordsprivate_LIB_SRCS ${wordsprivate_LIB_SRCS} dockers/KWDebugDocker.cpp dockers/KWDebugDockerFactory.cpp dockers/KWDebugWidget.cpp ) -endif() if( SHOULD_BUILD_FEATURE_RDF ) set(wordsprivate_LIB_SRCS ${wordsprivate_LIB_SRCS} diff --git a/words/part/KWFactory.cpp b/words/part/KWFactory.cpp --- a/words/part/KWFactory.cpp +++ b/words/part/KWFactory.cpp @@ -38,8 +38,9 @@ #include "dockers/KWRdfDocker.h" #include "dockers/KWRdfDockerFactory.h" #endif -#include "dockers/KWStatisticsDocker.h" + #include "pagetool/KWPageToolFactory.h" +#include "dockers/KWStatisticsDocker.h" #include "dockers/KWNavigationDockerFactory.h" #ifndef NDEBUG @@ -96,10 +97,8 @@ dockRegistry->add(new KWStatisticsDockerFactory()); dockRegistry->add(new KWNavigationDockerFactory()); #ifndef NDEBUG -#ifdef SHOULD_BUILD_RDF dockRegistry->add(new KWDebugDockerFactory()); #endif -#endif #ifdef SHOULD_BUILD_RDF // TODO reenable after release diff --git a/words/part/KWOdfLoader.cpp b/words/part/KWOdfLoader.cpp --- a/words/part/KWOdfLoader.cpp +++ b/words/part/KWOdfLoader.cpp @@ -46,8 +46,8 @@ #include #include #include -#include #include +#include #ifdef SHOULD_BUILD_RDF #include @@ -218,7 +218,7 @@ mainFs->setPageStyle(m_document->pageManager()->pageStyle("Standard")); m_document->addFrameSet(mainFs); textShapeData.setDocument(mainFs->document(), false); - sc.setSectionManager(new KoSectionManager(mainFs->document())); + sc.setSectionModel(new KoSectionModel(mainFs->document())); // disable the undo recording during load so the kotexteditor is in sync with // the app's undostack @@ -349,18 +349,18 @@ //1.6: KWOasisLoader::loadOasisHeaderFooter void KWOdfLoader::loadHeaderFooter(KoShapeLoadingContext &context, KWPageStyle &pageStyle, - const KoXmlElement &masterPage, HFLoadType headerFooter) + const KoXmlElement &masterPage, HFLoadType headerFooter) { // The actual content of the header/footer. KoXmlElement elem = KoXml::namedItemNS(masterPage, KoXmlNS::style, - headerFooter == LoadHeader ? "header" : "footer"); + headerFooter == LoadHeader ? "header" : "footer"); // The two additional elements and // specifies if defined that even and odd pages should be displayed // different. If they are missing, the content of odd and even (aka left // and right) pages are the same. KoXmlElement leftElem = KoXml::namedItemNS(masterPage, KoXmlNS::style, - headerFooter == LoadHeader ? "header-left" : "footer-left"); + headerFooter == LoadHeader ? "header-left" : "footer-left"); // Used in KWPageStyle to determine if, and what kind of header/footer to use. Words::HeaderFooterType hfType = elem.isNull() ? Words::HFTypeNone @@ -370,15 +370,15 @@ // header-left and footer-left if (! leftElem.isNull()) { loadHeaderFooterFrame(context, pageStyle, leftElem, - headerFooter == LoadHeader ? Words::EvenPagesHeaderTextFrameSet - : Words::EvenPagesFooterTextFrameSet); + headerFooter == LoadHeader ? Words::EvenPagesHeaderTextFrameSet + : Words::EvenPagesFooterTextFrameSet); } // header and footer if (! elem.isNull()) { loadHeaderFooterFrame(context, pageStyle, elem, - headerFooter == LoadHeader ? Words::OddPagesHeaderTextFrameSet - : Words::OddPagesFooterTextFrameSet); + headerFooter == LoadHeader ? Words::OddPagesHeaderTextFrameSet + : Words::OddPagesFooterTextFrameSet); } if (headerFooter == LoadHeader) { diff --git a/words/part/dockers/KWDebugWidget.cpp b/words/part/dockers/KWDebugWidget.cpp --- a/words/part/dockers/KWDebugWidget.cpp +++ b/words/part/dockers/KWDebugWidget.cpp @@ -1,5 +1,5 @@ /* 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 @@ -18,21 +18,20 @@ */ #include "KWDebugWidget.h" +#include +#include #include #include #include #include #include -#include -#include #include #include -#include -#include #include #include #include +#include KWDebugWidget::KWDebugWidget(QWidget *parent) : QWidget(parent) @@ -94,7 +93,8 @@ QString willShow = "This sections starts here :"; foreach (const KoSection *sec, KoSectionUtils::sectionStartings(fmt)) { - willShow += " \"" + sec->name() + "\""; + QPair bnds = sec->bounds(); + willShow += " \"" + sec->name() + "\"(" + QString::number(bnds.first) + "; " + QString::number(bnds.second) + ")"; } willShow.append("\n"); @@ -123,111 +123,10 @@ void KWDebugWidget::doSetMagic() { - KoTextEditor *editor = KoTextEditor::getTextEditorFromCanvas(m_canvas); - if (!editor) { - return; - } - - QTextDocument *doc = editor->document(); - KoSectionManager *manager = KoTextDocument(doc).sectionManager(); - - int pos = editor->position(); - - KoSection *sec = manager->sectionAtPosition(pos); - - if (!sec) { - return; - } - - KWDocument *kwdoc = dynamic_cast(m_canvas->shapeController()->resourceManager()->odfDocument()); - if (!kwdoc) { - return; - } - - KoDocumentRdf *rdf = dynamic_cast(kwdoc->documentRdf()); - if (!rdf) { - return; - } -// KoTextInlineRdf *inlineRdf = new KoTextInlineRdf(doc, sec); -// sec->setInlineRdf(inlineRdf); - -// rdf->rememberNewInlineRdfObject(inlineRdf); - - Soprano::Node sectionNode = Soprano::Node::createResourceNode(QUrl("http://www.caligra.org/author/sections/UID_HERE")); - - rdf->prefixMapping()->insert("cau", "http://www.caligra.org/author#"); - - qDebug() << rdf->model()->addStatement( - sectionNode, - Soprano::Node::createResourceNode(rdf->prefixMapping()->PrefexedLocalnameToURI("rdf:type")), - Soprano::Node::createResourceNode(rdf->prefixMapping()->PrefexedLocalnameToURI("cau:Section")), - rdf->manifestRdfNode()); - -// qDebug() << rdf->model()->addStatement( -// sectionNode, -// Soprano::Node::createResourceNode(rdf->prefixMapping()->PrefexedLocalnameToURI("pkg:idref")), -// Soprano::Node::createLiteralNode(inlineRdf->xmlId()), -// rdf->manifestRdfNode()); - - Soprano::Node authorContext = Soprano::Node::createResourceNode(QUrl(rdf->RDF_PATH_CONTEXT_PREFIX + "author.rdf")); - - qDebug() << rdf->model()->addStatement( - sectionNode, - Soprano::Node::createResourceNode(QUrl("http://www.caligra.org/author/section#descr")), - Soprano::Node::createLiteralNode("Some TEST descr"), - authorContext); - - Soprano::Node authorRdfFileNode = Soprano::Node::createBlankNode("CAU_META_DATA_FILE"); - qDebug() << rdf->model()->addStatement( - authorRdfFileNode, - Soprano::Node::createResourceNode(rdf->prefixMapping()->PrefexedLocalnameToURI("rdf:type")), - Soprano::Node::createResourceNode(rdf->prefixMapping()->PrefexedLocalnameToURI("odf:MetaDataFile")), - rdf->manifestRdfNode()); - - qDebug() << rdf->model()->addStatement( - authorRdfFileNode, - Soprano::Node::createResourceNode(rdf->prefixMapping()->PrefexedLocalnameToURI("pkg:path")), - Soprano::Node::createLiteralNode("author.rdf"), - rdf->manifestRdfNode()); + updateData(); } void KWDebugWidget::doGetMagic() { - KoTextEditor *editor = KoTextEditor::getTextEditorFromCanvas(m_canvas); - if (!editor) { - return; - } - - QTextDocument *doc = editor->document(); - KoSectionManager *manager = KoTextDocument(doc).sectionManager(); - - int pos = editor->position(); - - KoSection *sec = manager->sectionAtPosition(pos); - - if (!sec) { - return; - } - - KWDocument *kwdoc = dynamic_cast(m_canvas->shapeController()->resourceManager()->odfDocument()); - if (!kwdoc) { - return; - } - - KoDocumentRdf *rdf = dynamic_cast(kwdoc->documentRdf()); - if (!rdf) { - return; - } - -// KoTextInlineRdf *inlineRdf = sec->inlineRdf(); - - Soprano::Node authorContext = Soprano::Node::createResourceNode(QUrl(rdf->RDF_PATH_CONTEXT_PREFIX + "author.rdf")); - - Soprano::StatementIterator it = rdf->model()->listStatements( - Soprano::Node::createResourceNode(QUrl("http://www.caligra.org/author/sections/UID_HERE")), - Soprano::Node::createResourceNode(QUrl("http://www.caligra.org/author/section#descr")), - Soprano::Node::createEmptyNode(), - authorContext); - - m_buttonGet->setText(it.current().object().toString()); + m_canvas->view()->setShowSectionBounds(true); }