Commit 500f574c authored by Denis Kuplyakov's avatar Denis Kuplyakov

KoSectionModel instead of KoSectionManager implemented

Summary:
**The main goal. Why it is done?**
We need to store and maintain section tree in document. KoSectionManager were doing it lazy way: some changes can destroy its data and in that case you should call from code a special method to rebuild //entirely whole tree// of a document. This way comes more uncomfortable when we are going to visualize section tree (Author's outliner for example), as in that case you need to update widget by timer and in case of big documents such method will require high amount of computing.

So I decided to use Qts model as a base for the new KoSectionModel. New main aim: tree should be up-to-date always, and should be updated only by commiting local changes to it instead or rebuilding. Another pro of using Qt model that it is now super easy to show tree in UI with any data you want (check now QIdentityModel using with this in ConfigureSectionDialog).

**Other changes**
  # I've found some bugs in handling of sections during deletion, unit-test reference data;
  # Added more unit-tests for other types of sections operation, now also testing KoSectionModel;
  # Small rewrites in a few places;
  # Putted a detailed documentation on how sections handling now done to KoSectionModel.h;
  # Introduced SplitSectionsCommand and corresponding tool that allows to insert paragraphs between directly nested sections, with that options user can create //any// section structure;

That's all that I remember, look at commit messages for details.

**Connected future plans**
I think that that SplitSections dialog UI should be reworked, some tips should be added to user about "why I need this?". Unfortunately, there is no place to look how it can be done, as LO doesn't allow such operation and there is no sections analog in MSO.

And good luck to reviewers: change is big enough ;)

Test Plan:
**What I have done**
  - testKoTextEditor unit-test passing;
  - Manually checked split sections functionality.

**What I want to reviewers make attention**
There is a bunch of FIXME and TODO in code. Personally to boud: there is some unit-test I have commented in KoTextEditor, it is pointed with FIXME, check it plz.

Reviewers: boemann, rempt, staniek

Reviewed By: staniek

Subscribers: staniek, #calligra:_3.0

Projects: #calligra:_3.0

Differential Revision: https://phabricator.kde.org/D235
parent df7d29aa
/* This file is part of the KDE project
Copyright (C) 2007-2009, 2011 Thorsten Zachmann <zachmann@kde.org>
Copyright (C) 2007 Jan Hambrecht <jaham@gmx.net>
Copyright (C) 2014 Denis Kuplyakov <dener.kup@gmail.com>
Copyright (C) 2014-2015 Denis Kuplyakov <dener.kup@gmail.com>
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 @@ public:
, zIndex(0)
, documentResources(resourceManager)
, documentRdf(0)
, sectionManager(0)
, sectionModel(0)
{
}
......@@ -66,7 +66,7 @@ public:
QMap<KoShape *, KoLoadingShapeUpdater*> updaterByShape;
KoDocumentResourceManager *documentResources;
QObject *documentRdf;
KoSectionManager *sectionManager;
KoSectionModel *sectionModel;
};
KoShapeLoadingContext::KoShapeLoadingContext(KoOdfLoadingContext & context, KoDocumentResourceManager *documentResources)
......@@ -210,12 +210,12 @@ void KoShapeLoadingContext::setDocumentRdf(QObject *documentRdf)
d->documentRdf = documentRdf;
}
KoSectionManager* KoShapeLoadingContext::sectionManager()
KoSectionModel *KoShapeLoadingContext::sectionModel()
{
return d->sectionManager;
return d->sectionModel;
}
void KoShapeLoadingContext::setSectionManager(KoSectionManager *sectionManager)
void KoShapeLoadingContext::setSectionModel(KoSectionModel *sectionModel)
{
d->sectionManager = sectionManager;
d->sectionModel = sectionModel;
}
/* This file is part of the KDE project
Copyright (C) 2007 Thorsten Zachmann <zachmann@kde.org>
Copyright (C) 2007 Jan Hambrecht <jaham@gmx.net>
Copyright (C) 2014 Denis Kuplyakov <dener.kup@gmail.com>
Copyright (C) 2014-2015 Denis Kuplyakov <dener.kup@gmail.com>
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 KoLoadingShapeUpdater;
class KoImageCollection;
class KoSharedLoadingData;
class KoDocumentResourceManager;
class KoSectionManager;
class KoSectionModel;
/**
* Context passed to shapes during loading.
......@@ -191,16 +191,16 @@ public:
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
......
......@@ -41,7 +41,7 @@ set(kotext_LIB_SRCS
KoSection.cpp
KoSectionEnd.cpp
KoSectionUtils.cpp
KoSectionManager.cpp
KoSectionModel.cpp
KoTextLocator.cpp
KoTextReference.cpp
KoAnchorInlineObject.cpp
......@@ -120,6 +120,7 @@ set(kotext_LIB_SRCS
commands/ParagraphFormattingCommand.cpp
commands/RenameSectionCommand.cpp
commands/NewSectionCommand.cpp
commands/SplitSectionsCommand.cpp
KoTextDrag.cpp
KoTextCommandBase.cpp
......@@ -194,7 +195,7 @@ install(
KoSection.h
KoSectionEnd.h
KoSectionUtils.h
KoSectionManager.h
KoSectionModel.h
KoTextCommandBase.h
KoTextTableTemplate.h
......
......@@ -96,7 +96,6 @@ void KoInlineNote::setMotherFrame(QTextFrame *motherFrame)
KoParagraphStyle *style = static_cast<KoParagraphStyle *>(notesConfig->defaultNoteParagraphStyle());
if (style) {
QTextBlock block = cursor.block();
QTextBlockFormat bf;
QTextCharFormat cf;
style->applyStyle(bf);
......
/*
* Copyright (c) 2011 Boudewijn Rempt <boud@valdyas.org>
* Copyright (c) 2014 Denis Kuplyakov <dener.kup@gmail.com>
* Copyright (c) 2014-2015 Denis Kuplyakov <dener.kup@gmail.com>
*
* 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 <KoShapeSavingContext.h>
#include <KoXmlWriter.h>
#include <KoSectionStyle.h>
#include <KoSectionManager.h>
#include <KoSectionModel.h>
#include <KoSectionEnd.h>
#include <KoTextDocument.h>
#include <KoTextInlineRdf.h>
......@@ -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 @@ public:
QString style_name;
KoSectionStyle *sectionStyle;
QScopedPointer<KoSectionEnd> sectionEnd; //< pointer to the corresponding section end
int level; //< level of the section in document, root sections have 0 level
QPair<int, int> bounds; //< start and end position of section in QDocument
QScopedPointer<KoSectionEnd> 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<KoSection *> 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 @@ QString KoSection::name() const
QPair<int, int> KoSection::bounds() const
{
Q_D(const KoSection);
d->manager->update();
return d->bounds;
return QPair<int, int>(
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 @@ bool KoSection::loadOdf(const KoXmlElement &element, KoTextSharedLoadingData *sh
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 @@ void KoSection::saveOdf(KoShapeSavingContext &context) const
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 @@ void KoSection::setSectionEnd(KoSectionEnd* sectionEnd)
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 *> 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
......
/*
* Copyright (c) 2011 Boudewijn Rempt <boud@valdyas.org>
* Copyright (c) 2014 Denis Kuplyakov <dener.kup@gmail.com>
* Copyright (c) 2014-2015 Denis Kuplyakov <dener.kup@gmail.com>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
......@@ -57,7 +57,6 @@ class KoSectionPrivate;
class KOTEXT_EXPORT KoSection
{
public:
explicit KoSection(const QTextCursor &cursor);
~KoSection();
/// Returns section name
......@@ -66,11 +65,6 @@ public:
QPair<int, int> 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 @@ private:
Q_DISABLE_COPY(KoSection)
Q_DECLARE_PRIVATE(KoSection)
explicit KoSection(const QTextCursor &cursor, const QString &name, KoSection *parent);
/// Changes section's name to @param name
void setName(const QString &name);
/// Sets paired KoSectionsEnd for this section.
void setSectionEnd(KoSectionEnd *sectionEnd);
void setBeginPos(int pos);
void setEndPos(int pos);
/**
* Sets level of section in section tree.
* Root sections have @c 0 level.
*/
void setLevel(int level);
friend class KoSectionManager;
/// Returns a pointer to the parent of the section in tree.
KoSection *parent() const;
/// Returns a vector of pointers to the children of the section.
QVector<KoSection *> 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 *)
......
/*
* Copyright (c) 2011 Boudewijn Rempt <boud@valdyas.org>
* Copyright (c) 2014 Denis Kuplyakov <dener.kup@gmail.com>
* Copyright (c) 2014-2015 Denis Kuplyakov <dener.kup@gmail.com>
*
* 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 KoSectionEndPrivate;
*/
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 @@ protected:
private:
Q_DISABLE_COPY(KoSectionEnd)
Q_DECLARE_PRIVATE(KoSectionEnd)
explicit KoSectionEnd(KoSection *section);
friend class KoSectionModel;
friend class TestKoTextEditor;
};
Q_DECLARE_METATYPE(KoSectionEnd *)
......
/*
* Copyright (c) 2014 Denis Kuplyakov <dener.kup@gmail.com>
*
* 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 <KoSection.h>
#include <KoParagraphStyle.h>
#include "KoSectionEnd.h"
#include <KLocalizedString>
#include <KoTextDocument.h>
#include "KoSectionUtils.h"
#include <QHash>
#include <QString>
#include <qstack.h>
#include <QTextBlock>
#include <kdebug.h>
class KoSectionManagerPrivate
{
public:
KoSectionManagerPrivate(KoSectionManager *parent, QTextDocument *_doc)
: doc(_doc)
, valid(false)
, q_ptr(parent)
{
Q_ASSERT(_doc);
}
~KoSectionManagerPrivate()
{
QSet<KoSection *>::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<KoSection *> registeredSections; //< stores pointer to sections that sometime was registered
// used to prevent using sectionNames without update
QHash<QString, KoSection *> &sectionNames()
{
Q_Q(KoSectionManager);
q->update();
return m_sectionNames;
}
protected:
KoSectionManager *q_ptr;
private:
Q_DISABLE_COPY(KoSectionManagerPrivate)
Q_DECLARE_PUBLIC(KoSectionManager);
QHash<QString, KoSection *> 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<QString, KoSection *>::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<KoSection *>::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<QStandardItem *> curChain;
if (needModel) {
model = new QStandardItemModel();
curChain.push(model->invisibleRootItem());
}
int curLevel = -1;
do {