diff options
Diffstat (limited to 'quanta/parts/kafka')
34 files changed, 16270 insertions, 0 deletions
diff --git a/quanta/parts/kafka/ChangeLog b/quanta/parts/kafka/ChangeLog new file mode 100644 index 00000000..98727e7b --- /dev/null +++ b/quanta/parts/kafka/ChangeLog @@ -0,0 +1,25 @@ +2003-03-15 Nicolas Deschildre <nicolasdchd@ifrance.com> + * kafkaHTMLPart moved to Quanta + +2003-03-15 Nicolas Deschildre <nicolasdchd@ifrance.com> + * kafkahtmlpart [h|cpp] : added delete support, up/down navigation, + cursor now always visible, improved backspace + * added domtreeview [h|cpp] : for debugging purposes + +2003-02-15 Nicolas Deschildre <nicolasdchd@ifrance.com> + * kafkahtmlpart [cpp] : added backspace support + +2003-02-14 Nicolas Deschildre <nicolasdchd@ifrance.com> + * kafkahtmlpart [h|cpp] : added half cursor, text insertion support, and + left/right navigation + +2001-12-21 Joseph Wenninger <jowenn@kde.org> + * kafkapropertyeditor.[h|cpp} : the editor allows now painting of custom cells + +2001-12-21 Joseph Wenninger <jowenn@kde.org> + * kafkapropertyeditor.[h|cpp] : second example plugin implementation TextLine + access functions implemented + +2001-12-20 Joseph Wenninger <jowenn@kde.org> + * added test subdirectory :for test of the library + * added kafkapropertyeditor.[h|cpp] :not useful (means not really working), just a preview) diff --git a/quanta/parts/kafka/DESIGN b/quanta/parts/kafka/DESIGN new file mode 100644 index 00000000..d983309f --- /dev/null +++ b/quanta/parts/kafka/DESIGN @@ -0,0 +1,131 @@ +17/02/2004 + +This file is intented to provide some informations about the internal design of VPL as well as all its oddities ;-) + +Summary: +1- A bit of history. +2- Some definitions. +3- A quick overview of the Quanta/KHTML stuff interacting with VPL. +4- Basic design and interaction with Quanta. +5- VPL Classes +6- Synchronizations +7- TODO + +If you find an error (shouldn't be so hard ;-), could you report me please? + +1) History +In early 2003, I was looking for a good HTML WYSIWYG editor, and I didn't find what I wanted! So I decided to code one. After a quick search, I've found a dead project, Kafka, in kdenonbeta, which was supposed to become an full-featured WYSIWYG editor based on khtml. But at this time (2000-2001 I think) khtml wasn't ready. So it was abandonned. +Meanwhile khtml have been greatly improved, partially thanks to the Apple Safari merging. Then I started to hack kafka a bit, adding basic cursor navigation, insertion/deletion, and so on... But I quickly realised that it would be too hard and too long for me alone to come to a decent editor. So I was looking to join an existing project, and I choose Quanta+, basically because it was (and still is, in my humble opinion) the best HTML editor in the KDE environment. +It seemed I came to Quanta+ exactly at the best time: they were considering to add WYSIWYG capabilities! So for now one year, I've been coded VPL during my free time, and I am not far from a stable status. + + +2) Some definitions +First let us quickly define some things in order to better understand the next parts. + +* XML (http://www.w3.org/XML/): Defined by the W3C (http://www.w3.org/), it is widely used as the next generation way to exchange and store data. Many file formats are based on it, e.g. OpenOffice files, Quanta's data files, and recent HTML files. Just open one of quanta .tag file to see what it looks like (quanta/data/dtep/**/**.tag). + +* SGML (more infos here: http://www.w3.org/MarkUp/SGML/): The ancestor of XML, is less strict, but looks like XML. The old HTML file formats are based on him. + +* DTD : Document Type Definition, define how a XML file should look like e.g. which elements are allowed in one. For example when we speak of HTML, we usually speak of the HTML DTD, which tells us what elements exists (A/IMG/TABLE/...) and how to use them (TBODY inside TABLE,...). + +* HTML (http://www.w3.org/MarkUp/): Hey, we all know what it is!! Yep, but for some people (/me looking at myself one year ago), it only exists one sort of HTML. In fact, the current version of HTML is 4.01, and it exists three versions of HTML DTD: HTML transitional, HTML strict and HTML frameset. HTML transitional includes all the elements plus the deprecated ones, HTML strict includes all the elements minus the deprecated ones and the HTML frameset includes all the elements necessary to build some frames. These HTML DTDs are using SGML, that is why there are not recommended. Instead the following DTDs are recommended: + +* XHTML (http://www.w3.org/TR/xhtml1/): We have the XHTML 1.0 Transitional/Strict/Frameset DTDs which are basically the same thing that the HTML Transitional/Strict/Frameset DTDs but it is using XML. And finally we have XHTML 1.1, and the upcoming XHTML 2.0. + +* CSS (http://www.w3.org/Style/CSS/): It is a way to add style (e.g. fonts, color,...) to a web page. It was created in order to separate the contents (the information) from the style. + +* DOM (http://www.w3.org/DOM/) is a sort of "treeview" of a XML/SGML file. E.g. <html><body>text<img href="boo"> </body></html> has for DOM representation: +HTML + *-- BODY + *-- #text (text) + *-- IMG + *-- attribute (name:href, value:boo) + +* DTEP : (stands for Document Type Editing Package) It is Quanta's way to store the DTD information (and also includes supplemental elements like toolbars and more - see the .tag files in quanta/data/dtep). Why not use the DTD file directly? Because it doesn't contains all we want (no descriptions) and are written in a very odd way (just take a look... You will get sick soon :) + + + +3) A quick overview of the Quanta/KHTML stuff interacting with VPL. +First, the most important thing: the parser. Defined in the quanta/parser/ directory, it is composed of the Node class, the Tag class, the Parser class and the QTag class. The parser reads and parses (Parser::parse) or rebuilds from an already parsed document (Parser::rebuild) a Node Tree, which is basically a DOM like representation of the document, but even closing Tags and empty text are represented (as well as server side scripting elements like PHP.) In fact, everything is put in the tree so that we can get back the original SGML/XML file from the tree. From now, I call it the Node tree. For example <html><body>text<img href="boo"> </body></html> has for Node tree: +HTML + *-- BODY + *-- #text (text) + *-- IMG (attr1 name:href, value:boo) + *-- Empty text ( ) + *-- /BODY +/HTML +The Node class handle the pointers to the parent, next, previous and first child Node. *Each* Node has a valid pointer to a Tag. The Tag takes care to remember all the information concerning the Tag itself, like the attributes, the type, etc...) +One QTag per Element is created from the .tag files when Quanta is started. Each QTag contains all the DTD information about the Tag. E.g. the "IMG" Qtag says that it is a single Tag, and what are its attributes. You can get a QTag with QuantaCommon::tagFromDTD, but don't delete the QTag! + +Now to khtml. The class KHTMLPart is the HTML renderer widget of konqueror. It internally works with a Node Tree (another? Yep!) but these Nodes are real DOM::Nodes. (From now, I will call it the DOM::Node tree) Each of the DOM Nodes is khtml-internally linked to a rendering Node i.e. a change made to one DOM::Node will update the HTML rendering cf /path/to/kde/include/dom/*.h and also in the kdelibs cvs module, cf the nice kdelibs/khtml/DESIGN.html. WARNING about DOM::Nodes, they are just interfaces!! + + + +4) Basic design and interaction with Quanta. +Now we will enter VPL itself. VPL stands for Visual Page Layout, but you may as well call it WYSIWYG (What you See Is What You Get). (Eric's note: Except of course that HTML only suggests layout as opposed to a desktop publishing program unless you use absolute CSS very carefully. So WYSIWYG really is a ficticious misnomer with HTML.) ;-) +First have in mind that when editing a HTML file in Quanta, the Node Tree is always up to date. Loading a new file/switching tabs calls Parser::parse, and typing a letter calls Parser::rebuild. Then we can see the VPL design as this: + +Source (XML file) <=> Node tree <=> DOM::Node tree. + +Then when a change is made to the source file, Parser::rebuild is called and synchronize (not really, we will see this later) the corresponding DOM::Node. In the opposite, when a DOM::Node is modified, the corresponding Node is synchronized, and the source file is modified. Of course, it is a little more complicated, but let's see this later. + + +5) VPL classes. +VPL has several classes, but note sometimes it is not really object oriented, but I will clean up soon. + +* KafkaWidget(kafkahtmlpart.[h|cpp]): Derived from KHTMLPart, it uses the caret mode implemented by Leo Savernik in khtml (that means we don't have to care about cursor navigation). It handles every keypress in order to edit the widget (backspace/delete/return/<insertion of a letter>) and modify only the DOM::Node tree (not the Node tree). + +* KafkaDocument(wkafkapart.[h|cpp]): It takes care to load the DOM::Node tree from the Node tree, and when a change is made to the DOM::Node tree, it apply it in the Node tree. It basically takes care of the synchronization of the trees. + +* kafkaCommon(kafkacommon.[h|cpp]): A lot of useful functions, some need to be moved... Everything for Node modification, Node tree modification, DOM::Node tree, DOM::Node modification is here. + +* undoRedo(undoredo.[h|cpp]): Not functional yet, but it is intended to provide undo/redo functionality to both VPL and Quanta. But you are invited to use its structures. See section ? for more informations. + +* kNodeAttrs(nodeproperties.[h|cpp]): We can easily put a link to a DOM::Node from a Node in the Node class, but the opposite is impossible (we can't derive them). So we have a link DOM::Node => kNodeAttrs => Node (thanks to a QPtrDict). And we also have some informations about the way to handle this node when editing VPL. (Be careful, one Node can be linked against several Nodes. See after.) + +* NodeEnhancer(nodeenhancer.[h|cpp]): It is an interface class. Its aim it to "transform" or "enhance" DOM::Nodes when DOM::Nodes are synchronized from Nodes. Sometimes it is to add some style (e.g. adding a red dotted border to FORM elements) but sometimes it is essential (e.g. KHTML won't accept a TABLE without a TBODY even if it is DTD valid, so we had to manually add a TBODY. It explain why some Nodes can point to more than 1 Nodes.) + +* HTMLEnhancer(htmlenhancer.[h|cpp]): Derived from NodeEnhancer, it apply transformations for HTML files. + +* htmlDocumentProperties(htmldocumentproperties.[h|cpp]): A simple quick start dialog, which needs some work. + + +6) Synchronizations +So basically, we have the following design: +In whatever views, changes are made. These changes are directly applied to the Node tree. But we will wait xxx ms before an update of the opposite view (configurable). +In fact, it will *reload* the opposite view. Since the beginning, I wanted to update only the modified Nodes (UndoRedo's job) but I did some bad work and almost everything is commented now. + +So we are coming back to UndoRedo. Even if it is still not working, we should use it. You may have noticed that every function which modifies the Node tree has a strange parameter, a NodeModifsSet. +In fact, when you start modifying the Node tree, everything must be recorded. It has two aims: +* To provide undo/redo capabilities. +* To know which Nodes have been modified and thus when synchronizing a view from another, to update only the modified Nodes. + +Finally an important boolean which needs to be mentioned: Tag::cleanStrBuilt +This boolean specifies if the string stored in Tag::tagStr() is valid. In fact, when a change is made to a DOM::Node and a Node is being modified, only its members are modified (the attrs, the name, the type), but the tag string is not rebuilt (to save some CPU). +When reloading the source view, every tag with the Tag::cleanStrBuilt set to false has its string rebuilt. +For text Nodes, if: +Tag::cleanStrBuilt == false, the text contains no entities e.g. "boo a" as seen in the VPL view. +Tag::cleanStrBuilt == true, the text is parsed e.g. "boo a" as seen in the source view. + +Question: Why not (internally) directly modify the source view in dialogs (e.g. reading and modifying the HTML markup)? +Answer: Because it will limit this dialog to the source view only! When editing the document in whatever view: VPL, source but also others like the node treeview we have at the upper left, the only thing always in synchronization is the Node tree!! A change made in the source calls Parser::rebuild, a change in VPL update the Node tree. +So a dialog working on Nodes is nicer, because it will update the Node tree, and each view will be able to update itself from the Node Tree. For the moment, the only dialog working this way is the Document Properties Dialog. The Table editor dialog, for example, doesn't work in VPL because it is based on the source view. + +Question: But when a change is made in VPL, why not directly updating the source? +Answer: Because we will end up with a very unresponsive VPL! Updating two Nodes tree and a source view require a bit of CPU! + +7)TODO +* Implement some missing things: copy/paste, bad cursor behaviour,... +* Port the quanta dialogs (table editor,...) so that they can work in VPL +* Make VPL a KPart +* Make undo/redo works +* Put a error system +* Make VPL works for non-HTML DTDs! +* Visual PHP edition (??) +* Complete this file ;-) + +You can tell me what needs to be more explained! + +HAPPY HACKING! + +Nicolas Deschildre diff --git a/quanta/parts/kafka/Makefile.am b/quanta/parts/kafka/Makefile.am new file mode 100644 index 00000000..3b300dcb --- /dev/null +++ b/quanta/parts/kafka/Makefile.am @@ -0,0 +1,25 @@ +SUBDIRS = . pics + +KDE_CXXFLAGS = $(USE_EXCEPTIONS) +noinst_LTLIBRARIES = libkafkalibrary.la + +libkafkalibrary_la_SOURCES = htmlenhancer.cpp domtreeview.cpp kafkacommon.cpp \ + kafkahtmlpart.cpp nodeproperties.cpp wkafkapart.cpp undoredo.cpp kafkasyncoptionsui.ui \ + htmldocumentpropertiesui.ui kafkasyncoptions.cpp htmldocumentproperties.cpp cursors.cpp \ + kafkadragobject.cpp + +libkafkalibrary_la_METASOURCES = AUTO +libkafkalibrary_la_LDFLAGS = $(all_libraries) +libkafkalibrary_la_LIBADD = $(LIB_KHTML) + +kafkapartdir = $(kde_datadir)/kafkapart +kafkapart_DATA = entities + +AM_CPPFLAGS = -I$(top_srcdir)/quanta/utility \ + -I$(top_srcdir)/quanta/src \ + -I$(top_srcdir)/quanta/project \ + -I$(top_srcdir)/quanta/parsers \ + -I$(top_srcdir)/quanta/treeviews \ + -I$(top_srcdir)/lib \ + $(all_includes) +noinst_HEADERS = kafkadragobject.h diff --git a/quanta/parts/kafka/configure.in.in b/quanta/parts/kafka/configure.in.in new file mode 100644 index 00000000..967fc332 --- /dev/null +++ b/quanta/parts/kafka/configure.in.in @@ -0,0 +1,42 @@ +build_vpl="yes" + +#build_vpl="no" +AC_DEFUN([QUANTA_CHECK_VPL], +[ + AC_MSG_CHECKING(whether VPL can be compiled) + AC_CACHE_VAL(ac_cv_vpl_setup, + [ + AC_LANG_SAVE + AC_LANG_CPLUSPLUS + save_CXXFLAGS="$CXXFLAGS" + CXXFLAGS="$KDE_INCLUDES $QT_INCLUDES" + + AC_TRY_LINK([ + #include <kdeversion.h> + ], + [ + #if KDE_VERSION < ((3<<16) | (1<<8) | (90)) + KDE_choke me + #endif + ], + ac_cv_vpl_setup=yes, + ac_cv_vpl_setup=no + ) + CXXFLAGS="$save_CXXFLAGS" + AC_LANG_RESTORE + ]) + + if test "$ac_cv_vpl_setup" = "yes"; then + build_vpl="yes" + CXXFLAGS="$CXXFLAGS -DBUILD_KAFKAPART" + AC_MSG_RESULT(yes) + else + AC_MSG_RESULT(no) + fi + +]) + +#QUANTA_CHECK_VPL + +AM_CONDITIONAL(BUILD_WYSIWYG, test "$build_vpl" = "yes") + diff --git a/quanta/parts/kafka/cursors.cpp b/quanta/parts/kafka/cursors.cpp new file mode 100644 index 00000000..c70d6f78 --- /dev/null +++ b/quanta/parts/kafka/cursors.cpp @@ -0,0 +1,103 @@ +/*************************************************************************** + cursor.cpp + ------------------- + + copyright : (C) 2004 - Nicolas Deschildre + email : ndeschildre@kdewebdev.org + ***************************************************************************/ + +/*************************************************************************** + * * + * 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. * + * * + ***************************************************************************/ + + +#include "wkafkapart.h" +#include "kafkacommon.h" +#include "viewmanager.h" + +#include "cursors.h" + + +NodeSelectionInd::NodeSelectionInd() + : m_cursorOffset(-1), m_cursorOffsetEndSel(-1), m_cursorAtSelectionStart(true) +{ +} + +NodeSelectionInd::NodeSelectionInd(Node* cursor_node, int cursor_offset) + : m_cursorOffset(cursor_offset), m_cursorOffsetEndSel(-1), m_cursorAtSelectionStart(true) +{ + setCursorNode(kafkaCommon::getLocation(cursor_node)); +} + +NodeSelectionInd::NodeSelectionInd(Node* start_node, int start_offset, Node* end_node, int end_offset) + : m_cursorOffset(end_offset), m_cursorOffsetEndSel(end_offset), m_cursorAtSelectionStart(false) +{ + setCursorNode(kafkaCommon::getLocation(start_node)); + setCursorOffset(start_offset); + setCursorNodeEndSel(kafkaCommon::getLocation(end_node)); + setCursorOffsetEndSel(end_offset); +} + +NodeSelectionInd::~NodeSelectionInd() +{ +} + +bool NodeSelectionInd::operator==(const NodeSelectionInd & nodeSelection) +{ + return (m_cursorNode == nodeSelection.m_cursorNode && m_cursorNodeEndSel == nodeSelection.m_cursorNodeEndSel && + m_cursorOffset == nodeSelection.m_cursorOffset && m_cursorOffsetEndSel == nodeSelection.m_cursorOffsetEndSel && + m_cursorAtSelectionStart == nodeSelection.m_cursorAtSelectionStart); +} + +void NodeSelectionInd::operator=(const NodeSelectionInd & nodeSelection) +{ + m_cursorNode = nodeSelection.m_cursorNode; + m_cursorNodeEndSel = nodeSelection.m_cursorNodeEndSel; + m_cursorOffset = nodeSelection.m_cursorOffset; + m_cursorOffsetEndSel = nodeSelection.m_cursorOffsetEndSel; + m_cursorAtSelectionStart = nodeSelection.m_cursorAtSelectionStart; +} + +void NodeSelectionInd::fillWithVPLCursorSelection() +{ + KafkaDocument *kafkaDoc; + DOM::Node domNode, domNodeEndSel; + long domOffset, domOffsetEndSel; + Node *node = 0L; + Node *nodeEndSel = 0L; + long offset, offsetEndSel; + + kafkaDoc = KafkaDocument::ref(); + kafkaDoc->getKafkaWidget()->getCurrentNode(domNode, domOffset); + kafkaDoc->translateKafkaIntoNodeCursorPosition(domNode, domOffset, &node, offset); + m_cursorNode = kafkaCommon::getLocation(node); + m_cursorOffset = offset; + + if(kafkaDoc->getKafkaWidget()->hasSelection()) + { + kafkaDoc->getKafkaWidget()->selection(domNode, domOffset, domNodeEndSel, domOffsetEndSel); + KafkaDocument::ref()->translateKafkaIntoNodeCursorPosition(domNodeEndSel, domOffsetEndSel, + &nodeEndSel, offsetEndSel); + m_cursorNodeEndSel = kafkaCommon::getLocation(nodeEndSel); + m_cursorOffsetEndSel = offsetEndSel; + + m_cursorAtSelectionStart = !(m_cursorOffsetEndSel == m_cursorOffset && m_cursorNodeEndSel == m_cursorNode); + + if(!m_cursorAtSelectionStart) + { + KafkaDocument::ref()->translateKafkaIntoNodeCursorPosition(domNode, domOffset, &node, offset); + m_cursorNode = kafkaCommon::getLocation(node); + m_cursorOffset = offset; + } + } +} + +bool NodeSelectionInd::hasSelection() const +{ + return KafkaDocument::ref()->getKafkaWidget()->hasSelection(); +} diff --git a/quanta/parts/kafka/cursors.h b/quanta/parts/kafka/cursors.h new file mode 100644 index 00000000..22eea7b1 --- /dev/null +++ b/quanta/parts/kafka/cursors.h @@ -0,0 +1,150 @@ +/*************************************************************************** + cursor.h + ------------------- + + copyright : (C) 2004 - Nicolas Deschildre + email : ndeschildre@kdewebdev.org + ***************************************************************************/ + +/*************************************************************************** + * * + * 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. * + * * + ***************************************************************************/ + + #ifndef CURSORS_H + #define CURSORS_H + + /** + * This represents a Node selection : StartNode and StartOffset, endNode and endOffset. + * IT can also only hold the cursor. If so, cursorNodeEndSel() will be empty. + * TODO: selection support + */ + class NodeSelection + { + public: + NodeSelection(Node* node = 0L, int offset = 0){m_cursorNode = node; m_cursorOffset = offset;} + ~NodeSelection(){} + + /** + * @return Return the Node when the selection start (and where the cursor is if + * m_cursorAtSelectionStart). + */ + Node* cursorNode(){return m_cursorNode;} + void setCursorNode(Node* node){m_cursorNode = node;} + + /** + * @return Returns the offset of the cursor where the selection begin (and where the cursor is if + * m_cursorAtSelectionStart) + */ + int cursorOffset(){return m_cursorOffset;} + void setCursorOffset(int offset){m_cursorOffset = offset;} + + private: + Node* m_cursorNode, *m_cursorNodeEndSel; + int m_cursorOffset, m_cursorOffsetEndSel; + bool m_cursorAtSelectionStart; + }; + + /** + * This represents a Node selection : startNode and startOffset, endNode and endOffset. + * The difference with NodeSelection is that it don't store the Node address of startNode + * and endNode, but it store the Node position of the Node tree e.g. it is the first child + * of the second child of the rootNode... + * It can also only hold the cursor. If so, cursorNodeEndSel() will be empty. + */ +class NodeSelectionInd +{ +public: + NodeSelectionInd(); + NodeSelectionInd(Node* cursor_node, int cursor_offset); + NodeSelectionInd(Node* start_node, int start_offset, Node* end_node, int end_offset); + ~NodeSelectionInd(); + + /** + * Compare nodeselection to the current instance. + */ + bool operator==(const NodeSelectionInd & nodeSelection); + + void operator=(const NodeSelectionInd & nodeSelection); + + /** + * Take the current VPL cursor selection coordinates, if not speficied otherwise, translate + * them into Node cursor coordinates and store them. + */ + void fillWithVPLCursorSelection(); + + bool hasSelection() const; + + /**---------------------------------- GET/SET --------------------------------------------*/ + + /** + * @return Returns the location of the Node where the selection begin (and where the cursor is if + * m_cursorAtSelectionStart). + */ + QValueList<int>& cursorNode() {return m_cursorNode;} + + /** + * @param cursorNode Set the location of the Node where the selection begin (and where the cursor is if + * m_cursorAtSelectionStart) + */ + void setCursorNode(QValueList<int> cursorNode) {m_cursorNode = cursorNode;} + + /** + * @return Returns the offset of the cursor where the selection begin (and where the cursor is if + * m_cursorAtSelectionStart) + */ + int cursorOffset() {return m_cursorOffset;} + + /** + * @param cursorOffset Set the offset of the cursor where the selection begin (and where the cursor is if + * m_cursorAtSelectionStart) + */ + void setCursorOffset(int cursorOffset) {m_cursorOffset = cursorOffset;} + + /** + * @return Returns true if the cursor is at the beginning of the selection. Otherwise, it is + * at the end of the selection. + */ + bool cursorAtSelectionStart() {return m_cursorAtSelectionStart;} + + /** + * @param cursorAtSelectionStart Set if the cursor is at the beginning of the selection. + */ + void setCursorAtSelectionStart(bool cursorAtSelectionStart) {m_cursorAtSelectionStart = cursorAtSelectionStart;} + + /** + * @return Returns the location of the end selection Node where the + * selection ends. + * Empty if this instance only carry the cursor coordinates. + */ + QValueList<int>& cursorNodeEndSel() {return m_cursorNodeEndSel;} + + /** + * @param cursorNodeEndSel Set the location of the end selection Node where the + * selection ends. + */ + void setCursorNodeEndSel(QValueList<int> cursorNodeEndSel) + {m_cursorNodeEndSel = cursorNodeEndSel;} + + /** + * @return Returns the offset of the cursor in the Node of the end of the selection. + */ + int cursorOffsetEndSel() {return m_cursorOffsetEndSel;} + + /** + * @param cursorOffsetEndSel Set the offset of the cursor in the Node of the end of the selection + */ + void setCursorOffsetEndSel(int cursorOffsetEndSel) + {m_cursorOffsetEndSel = cursorOffsetEndSel;} + +private: + QValueList<int> m_cursorNode, m_cursorNodeEndSel; + int m_cursorOffset, m_cursorOffsetEndSel; + bool m_cursorAtSelectionStart; +}; + +#endif diff --git a/quanta/parts/kafka/domtreeview.cpp b/quanta/parts/kafka/domtreeview.cpp new file mode 100644 index 00000000..14fe17d2 --- /dev/null +++ b/quanta/parts/kafka/domtreeview.cpp @@ -0,0 +1,157 @@ +/*************************************************************************** + domtreeview.cpp + ------------------- + + copyright : (C) 2001 - The Kafka Team + email : kde-kafka@master.kde.org + ***************************************************************************/ + +/*************************************************************************** + * * + * 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. * + * * + ***************************************************************************/ + +#include "kafkacommon.h" +#ifdef HEAVY_DEBUG + +#include <kdebug.h> +#include <khtml_part.h> +#include <klocale.h> +#include <qstring.h> +#include <qlayout.h> +#include <dom/dom_text.h> + +#include "domtreeview.moc" + +DOMTreeView::DOMTreeView(QWidget *parent, KHTMLPart *currentpart, const char * name) : KListView(parent, name) +{ + setCaption(name); + setRootIsDecorated(true); + addColumn(i18n( "Name" )); + addColumn(i18n( "Value (limited to 20 char)" )); + addColumn(i18n( "Length" )); + addColumn(i18n( "ID" )); + addColumn(""); + setSorting(-1); + part = currentpart; + connect(part, SIGNAL(nodeActivated(const DOM::Node &)), this, SLOT(showTree(const DOM::Node &))); + connect(this, SIGNAL(clicked(QListViewItem *)), this, SLOT(slotItemClicked(QListViewItem *))); + m_nodedict.setAutoDelete(true); + title = ""; + titleItem = new QListViewItem(static_cast<QListView *>(this), title, ""); +} + +DOMTreeView::~DOMTreeView() +{ + disconnect(part); +} + +void DOMTreeView::setTitle(const QString &str) +{ + title = str; + titleItem->setText(1, title); +} + +void DOMTreeView::showTree(const DOM::Node &pNode) +{ + +// if(pNode.isNull() || document != pNode.ownerDocument()) +// { + clear(); + m_itemdict.clear(); + m_nodedict.clear(); + if(pNode.isNull()) + return; + if(pNode.firstChild() == 0) + return; + else if(pNode.ownerDocument().isNull()) + { + document = pNode.ownerDocument(); + recursive(0, pNode); + } + else + { + document = pNode.ownerDocument(); + recursive(0, pNode.ownerDocument()); + } + titleItem = new QListViewItem(static_cast<QListView *>(this), title, ""); +// } + setCurrentItem(m_itemdict[pNode.handle()]); + ensureItemVisible(m_itemdict[pNode.handle()]); +} + +void DOMTreeView::recursive(const DOM::Node &pNode, const DOM::Node &node) +{ + QListViewItem *cur_item; + int len; + if(pNode.ownerDocument() != document) + { + QString val = node.nodeValue().string(); + if ( val.length() > 20 ) + val.truncate( 20 ); + cur_item = new QListViewItem(static_cast<QListView *>(this), node.nodeName().string(), val); + document = pNode.ownerDocument(); + } + else { + QString val = node.nodeValue().string(); + if ( val.length() > 20 ) + val.truncate( 20 ); + if(node.nodeType() == DOM::Node::TEXT_NODE) + len = (static_cast<DOM::CharacterData>(node)).length(); + else + len = 0; + cur_item = new QListViewItem(m_itemdict[pNode.handle()], node.nodeName().string(), val, QString::number(len), QString::number(node.elementId()) ); + unsigned long i; + QListViewItem *tmp = new QListViewItem(cur_item, "properties"); + for(i = 0; i < node.attributes().length(); i++) + { + new QListViewItem(tmp, node.attributes().item(i).nodeName().string(), + node.attributes().item(i).nodeValue().string()); + } + } + + if(node.handle()) + { + m_itemdict.insert(node.handle(), cur_item); + m_nodedict.insert(cur_item, new DOM::Node(node)); + } + + DOM::Node cur_child = node.lastChild(); + while(!cur_child.isNull()) + { + recursive(node, cur_child); + cur_child = cur_child.previousSibling(); + } +} + +void DOMTreeView::slotItemClicked(QListViewItem *cur_item) +{ + DOM::Node *handle = m_nodedict[cur_item]; + if(handle) { + emit part->setActiveNode(*handle); + //kdDebug() << handle->toHTML() << endl; + } +} + +KafkaDOMTreeDialog::KafkaDOMTreeDialog(QWidget *parent, KHTMLPart *part, const char* name, bool modal, WFlags fl ) + : QDialog(parent, name, modal, fl) +{ + setSizePolicy( QSizePolicy( (QSizePolicy::SizeType)5, (QSizePolicy::SizeType)1, 0, 0, sizePolicy().hasHeightForWidth() ) ); + DialogLayout = new QGridLayout( this, 1, 1, 11, 6, "DialogLayout"); + domview = new DOMTreeView(this, part, name); + domview->setTitle(i18n( "Debugging KafkaWidget DOM Tree " )); + DialogLayout->addWidget(domview, 1,1); + +} + +KafkaDOMTreeDialog::~KafkaDOMTreeDialog() +{ + +} + +#endif + diff --git a/quanta/parts/kafka/domtreeview.h b/quanta/parts/kafka/domtreeview.h new file mode 100644 index 00000000..4e2a560f --- /dev/null +++ b/quanta/parts/kafka/domtreeview.h @@ -0,0 +1,77 @@ +/*************************************************************************** + domtreeview.cpp + ------------------- + + copyright : (C) 2001 - The Kafka Team + email : kde-kafka@master.kde.org + ***************************************************************************/ + +/*************************************************************************** + * * + * 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. * + * * + ***************************************************************************/ + +#ifndef DOMTREEVIEW_H +#define DOMTREEVIEW_H + +#include "kafkacommon.h" + +#ifdef HEAVY_DEBUG +#include <klistview.h> +#include <kdebug.h> +#include <qlistview.h> +#include <qptrdict.h> +#include <dom/dom_core.h> +#include <qdialog.h> + +class QString; +class QGridLayout; + +/** + * This class is very useful to see the DOM tree in a KListView + */ + +class DOMTreeView : public KListView +{ + Q_OBJECT + public: + DOMTreeView(QWidget *parent, KHTMLPart *part, const char * name = 0); + ~DOMTreeView(); + void setTitle(const QString &str); + void recursive(const DOM::Node &pNode, const DOM::Node &node); + + signals: + void sigNodeClicked(const DOM::Node &); + + public slots: + void showTree(const DOM::Node &pNode); + + protected slots: + void slotItemClicked(QListViewItem *); + + private: + QString title; + QListViewItem *titleItem; + QPtrDict<QListViewItem> m_itemdict; + QPtrDict<DOM::Node> m_nodedict; + DOM::Node document; + KHTMLPart *part; + +}; + +class KafkaDOMTreeDialog : public QDialog +{ + Q_OBJECT +public: + KafkaDOMTreeDialog(QWidget *parent = 0, KHTMLPart *part = 0, const char* name = 0, bool modal = FALSE, WFlags fl = 0 ); + ~KafkaDOMTreeDialog(); + DOMTreeView *domview; + QGridLayout *DialogLayout; +}; + +#endif +#endif diff --git a/quanta/parts/kafka/entities b/quanta/parts/kafka/entities new file mode 100644 index 00000000..a13cc1db --- /dev/null +++ b/quanta/parts/kafka/entities @@ -0,0 +1,112 @@ +--Markup/Internationalization-- + ( ) +" (") +& (&) +< (<) +> (>) +# (#) +$ ($) +% (%) +' (') +//Œ (Œ) +//œ (oelig;) +//Š (Š) +//š (š) +//Ÿ (Ÿ) +€ (€) +--ISO 8859-1 Character Entities-- +¡ (¡) +¢ (¢) +£ (£) +¤ (¤) +¥ (¥) +¦ (¦) +§ (§) +¨ (¨) +© (©) +ª (ª) +« («) +¬ (¬) + (­) +® (®) +¯ (¯) +° (°) +± (±) +² (²) +³ (³) +´ (´) +µ (µ) +¶ (¶) +· (·) +¸ (¸) +¹ (¹) +º (º) +» (») +¼ (¼) +½ (½) +¾ (¾) +¿ (¿) +À (À) +Á (Á) + (Â) +à (Ã) +Ä (Ä) +Å (Å) +Æ (Æ) +Ç (Ç) +È (È) +É (É) +Ê (Ê) +Ë (Ë) +Ì (Ì) +Í (Í) +Î (Î) +Ï (Ï) +Ð (Ð) +Ñ (Ñ) +Ò (Ò) +Ó (Ó) +Ô (Ô) +Õ (Õ) +Ö (Ö) +× (×) +Ø (Ø) +Ù (Ù) +Ú (Ú) +Û (Û) +Ü (Ü) +Ý (Ý) +Þ (Þ) +ß (ß) +à (à) +á (á) +â (â) +ã (ã) +ä (ä) +å (å) +æ (æ) +ç (ç) +è (è) +é (é) +ê (ê) +ë (ë) +ì (ì) +í (í) +î (î) +ï (ï) +ð (ð) +ñ (ñ) +ò (ò) +ó (ó) +ô (ô) +õ (õ) +ö (ö) +÷ (÷) +ø (ø) +ù (ù) +ú (ú) +û (û) +ü (ü) +ý (ý) +þ (þ) +//ÿ (ÿ) diff --git a/quanta/parts/kafka/htmldocumentproperties.cpp b/quanta/parts/kafka/htmldocumentproperties.cpp new file mode 100644 index 00000000..0099f313 --- /dev/null +++ b/quanta/parts/kafka/htmldocumentproperties.cpp @@ -0,0 +1,748 @@ +/*************************************************************************** + htmldocumentproperties.cpp + ------------------- + + copyright : (C) 2003, 2004 - Nicolas Deschildre + email : ndeschildre@kdewebdev.org + ***************************************************************************/ + +/*************************************************************************** + * * + * 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. * + * * + ***************************************************************************/ + + +#include <kiconloader.h> +#include <klineedit.h> +#include <klistview.h> +#include <kpushbutton.h> +#include <kurlrequester.h> +#include <kurl.h> +#include <kmessagebox.h> +#include <klocale.h> +#include <kdebug.h> + +#include "node.h" +#include "tag.h" +#include "qtag.h" +#include "resource.h" +#include "quanta.h" +#include "quantacommon.h" +#include "quantaview.h" +#include "document.h" +#include "tagattributetree.h" +#include "qextfileinfo.h" +#include "kafkacommon.h" +#include "wkafkapart.h" +#include "undoredo.h" +#include "cursors.h" +#include "htmldocumentproperties.h" + +#include "viewmanager.h" + +htmlDocumentProperties::htmlDocumentProperties( QWidget* parent, bool forceInsertionOfBasicNodes, const char* name, + bool modal, WFlags fl) : + htmlDocumentPropertiesui(parent, name, modal, fl), titleNode( 0L ), htmlNode( 0L ), + headNode( 0L ), linkNode( 0L ), bodyNode( 0L), doctypeNode( 0L ), CSSNode ( 0L ), + xmlNode( 0L ), titleDirty(false), linkDirty(false) +{ + Node *node; + QString text, nodeName; + bool b; + int index; + KURL url, baseURL; + + m_forceInsertionOfBasicNodes = forceInsertionOfBasicNodes; + + //set the "autodefault" property + metaItemsAdd->setAutoDefault(false); + metaItemsDelete->setAutoDefault(false); + cssRulesAdd->setAutoDefault(false); + cssRulesEdit->setAutoDefault(false); + cssRulesDelete->setAutoDefault(false); + cssStylesheet->button()->setAutoDefault(false); + cancel->setAutoDefault(false); + ok->setAutoDefault(true); + + //set the taborder and disable focus for some widgets. + currentDTD->setFocusPolicy(QWidget::NoFocus); + QWidget::setTabOrder(title, metaItems); + QWidget::setTabOrder(metaItems, metaItemsAdd); + QWidget::setTabOrder(metaItemsAdd, metaItemsDelete); + QWidget::setTabOrder(metaItemsDelete, cssRules); + QWidget::setTabOrder(cssRules, cssRulesAdd); + QWidget::setTabOrder(cssRulesAdd, cssRulesEdit); + QWidget::setTabOrder(cssRulesEdit, cssRulesDelete); + QWidget::setTabOrder(cssRulesDelete, cssStylesheet); + QWidget::setTabOrder(cssStylesheet, ok); + QWidget::setTabOrder(ok, cancel); + + //set the current DTD name + currentDTD->setText(ViewManager::ref()->activeDocument()->defaultDTD()->nickName); + + //set the metaItems DualEditableTree + metaItems->addColumn(i18n("Name")); + metaItems->addColumn(i18n("Content")); + metaItems->setFrameStyle( QFrame::Panel | QFrame::Sunken ); + metaItems->setLineWidth( 2 ); + metaItems->setSorting(0, true); + + + //set the cssRules KListView + //cssRules->setEditable(false); + cssRules->addColumn(i18n("Selector")); + cssRules->addColumn(i18n("Rule")); + cssRules->setFrameStyle( QFrame::Panel | QFrame::Sunken ); + cssRules->setLineWidth( 2 ); + //cssRules->setFocusPolicy(QWidget::ClickFocus); + cssRules->setSorting(-1); + + //search for the head, html, title Node + if(baseNode) + { + node = baseNode; + while(node) + { + nodeName = node->tag->name.lower(); + if(nodeName == "?xml" || nodeName.contains("xml pi block")) + xmlNode = node; + if(nodeName == "html") + htmlNode = node; + if(nodeName == "head") + headNode = node; + if(nodeName == "body") + bodyNode = node; + if(nodeName == "!doctype" || nodeName.contains("dtd block")) + doctypeNode = node; + if(nodeName == "title") + titleNode = node; + if(nodeName == "link") + linkNode = node; + if(nodeName == "meta") + loadMetaNode(node); + if(nodeName == "style") + loadCSS(node); + node = node->next; + } + //support for old Node organization + if(doctypeNode) + { + node = doctypeNode->child; + while(node) + { + nodeName = node->tag->name.lower(); + if(nodeName == "html") + htmlNode = node; + if(nodeName == "head") + headNode = node; + if(nodeName == "body") + bodyNode = node; + if(nodeName == "title") + titleNode = node; + if(nodeName == "link") + linkNode = node; + if(nodeName == "meta") + loadMetaNode(node); + if(nodeName == "style") + loadCSS(node); + node = node->next; + } + } + if(htmlNode) + { + node = htmlNode->child; + while(node) + { + nodeName = node->tag->name.lower(); + if(nodeName == "head") + headNode = node; + if(nodeName == "body") + bodyNode = node; + if(nodeName == "title") + titleNode = node; + if(nodeName == "link") + linkNode = node; + if(nodeName == "meta") + loadMetaNode(node); + if(nodeName == "style") + loadCSS(node); + node = node->next; + } + } + if(headNode) + { + node = headNode->child; + while(node) + { + nodeName = node->tag->name.lower(); + if(nodeName == "title") + titleNode = node; + if(nodeName == "link") + linkNode = node; + if(nodeName == "meta") + loadMetaNode(node); + if(nodeName == "style") + loadCSS(node); + node = node->next; + } + } + } + + //set the current title + if(titleNode) + { + node = titleNode->child; + b = false; + while(node) + { + text += node->tag->tagStr(); + node = kafkaCommon::getNextNode(node, b, titleNode); + } + title->setText(KafkaDocument::ref()->getDecodedText(text)); + } + + //set the link + if(linkNode) + { + index = linkNode->tag->attributeIndex("rel"); + if(index != -1) + { + if(linkNode->tag->attributeValue(index).lower() == "stylesheet") + { + index = linkNode->tag->attributeIndex("href"); + if(index != -1) + { + cssStylesheet->setMode(KFile::File | KFile::ExistingOnly ); + baseURL.setPath(ViewManager::ref()->activeDocument()->url().directory()); + QuantaCommon::setUrl(url, linkNode->tag->attributeValue(index)); + url = QExtFileInfo::toAbsolute(url, baseURL); + cssStylesheet->setURL(url.url()); + } + } + } + } + + ok->setIconSet(SmallIconSet("button_ok")); + cancel->setIconSet(SmallIconSet("button_cancel")); + + //connect buttons + connect(ok, SIGNAL(clicked()), this, SLOT(accept())); + connect(cancel, SIGNAL(clicked()), this, SLOT(reject())); + connect(cssRulesAdd, SIGNAL(clicked()), this, SLOT(newCSSRule())); + connect(cssRulesEdit, SIGNAL(clicked()), this, SLOT(editCSSRule())); + connect (cssRulesDelete, SIGNAL(clicked()), this, SLOT(deleteCurrentCSSRule())); + connect(metaItemsAdd, SIGNAL(clicked()), this, SLOT(newMetaItem())); + connect(metaItemsDelete, SIGNAL(clicked()), this, SLOT(deleteCurrentMetaItem())); + connect(title, SIGNAL(textChanged(const QString &)), this, SLOT(titleChanged(const QString &))); + connect(metaItems, SIGNAL(itemModified( QListViewItem * )), + this, SLOT(metaChanged(QListViewItem * ))); + connect(cssRules, SIGNAL(itemModified( QListViewItem * )), + this, SLOT(CSSChanged(QListViewItem * ))); + connect(cssStylesheet, SIGNAL(textChanged(const QString &)), + this, SLOT(linkChanged( const QString& ))); +} + +htmlDocumentProperties::~htmlDocumentProperties() +{ + +} + +void htmlDocumentProperties::loadMetaNode(Node *node) +{ + int index; + QString name, content; + NodeLinkedViewItem *item; + + index = node->tag->attributeIndex("name"); + if(index != -1) + name = node->tag->attributeValue(index); + index = node->tag->attributeIndex("http-equiv"); + if(index != -1) + name = node->tag->attributeValue(index); + index = node->tag->attributeIndex("content"); + if(index != -1) + content = node->tag->attributeValue(index); + item = new NodeLinkedViewItem(metaItems, name, content); + item->node = node; + metaList.append(item); + + metaItems->sort(); +} + +void htmlDocumentProperties::loadCSS(Node *node) +{ + NodeLinkedViewItem *item; + QString selector; + + CSSNode = node; + node = node->child; + while(node) + { + if(node->tag->type == Tag::ScriptStructureBegin) + { + selector = node->tag->tagStr(); + selector = selector.left((uint)selector.find("{")).stripWhiteSpace(); + if(node->child) + item = new NodeLinkedViewItem(cssRules, selector, + node->child->tag->tagStr().replace('\n'," ")); + else + item = new NodeLinkedViewItem(cssRules, selector, ""); + item->node = node; + item->moveItem(cssRules->lastChild()); + CSSList.append(item); + } + node = node->next; + } +} + +void htmlDocumentProperties::newMetaItem() +{ + NodeLinkedViewItem *item; + item = new NodeLinkedViewItem(metaItems, "", ""); + item->node = 0L; + metaList.append(item); + + if(metaItems->lastItem()) + item->moveItem(metaItems->lastItem()); +} + +void htmlDocumentProperties::deleteCurrentMetaItem() +{ + if(metaItems->currentItem()) + { + QListViewItem *item = metaItems->currentItem(); + (static_cast<NodeLinkedViewItem *>(item))->deleted = true; + (static_cast<NodeLinkedViewItem *>(item))->dirty = true; + (static_cast<AttributeItem *>(item))->hideEditor(0); + (static_cast<AttributeItem *>(item))->hideEditor(1); + metaItems->takeItem(metaItems->currentItem()); + } +} + +void htmlDocumentProperties::newCSSRule() +{ + NodeLinkedViewItem *item; + item = new NodeLinkedViewItem(cssRules, "", ""); + item->node = 0L; + CSSList.append(item); + + if(cssRules->lastItem()) + item->moveItem(cssRules->lastItem()); +} + +void htmlDocumentProperties::editCSSRule() +{ +//Make this using the big CSS dialog, need parsing!! + KMessageBox::information(this, i18n("Sorry, VPL does not support this functionality yet.")); +} + +void htmlDocumentProperties::deleteCurrentCSSRule() +{ + if(cssRules->currentItem()) + { + QListViewItem *item = cssRules->currentItem(); + (static_cast<NodeLinkedViewItem *>(item))->deleted = true; + (static_cast<NodeLinkedViewItem *>(item))->dirty = true; + (static_cast<AttributeItem *>(item))->hideEditor(0); + (static_cast<AttributeItem *>(item))->hideEditor(1); + cssRules->takeItem(cssRules->currentItem()); + } +} + + +void htmlDocumentProperties::titleChanged(const QString &) +{ + titleDirty = true; +} + +void htmlDocumentProperties::metaChanged(QListViewItem * item) +{ + if(item) + (static_cast<NodeLinkedViewItem *>(item))->dirty = true; +} + +void htmlDocumentProperties::CSSChanged(QListViewItem * item) +{ + if(item) + (static_cast<NodeLinkedViewItem *>(item))->dirty = true; +} + +void htmlDocumentProperties::linkChanged( const QString& ) +{ + linkDirty = true; +} + + +void htmlDocumentProperties::accept() +{ + Node *node, *nodeNext; + NodeLinkedViewItem *item; + TagAttr attr; + NodeModifsSet *modifs = new NodeModifsSet(); + KURL url, baseURL; + QString finalURL; + NodeSelection *cursorPos; + bool goUp; + //TODO:see for !doctype + + QuantaView *view = ViewManager::ref()->activeView(); + //set the TITLE if necessary. + if(titleDirty) + { + if(!titleNode) + { + addBasicNodes(modifs); + //create title + titleNode = kafkaCommon::createAndInsertNode("title", "", Tag::XmlTag, + view->document(), headNode, 0L, 0L, modifs); + } + node = titleNode->child; + if(node && (node->next || node->tag->type != Tag::Text)) + { + while(node) + { + nodeNext = node->next; + kafkaCommon::extractAndDeleteNode(node, modifs, true, false); + node = nodeNext; + } + } + if(!titleNode->child) + { + //create text! + node = kafkaCommon::createAndInsertNode("", title->text(),Tag::Text, + view->document(), titleNode, 0L, 0L, modifs); + } + else + titleNode->child->tag->setStr(KafkaDocument::ref()->getEncodedText(title->text())); + } + + //set the METAs if necessary + if(metaItems->currentItem()) + (static_cast<NodeLinkedViewItem *>(metaItems->currentItem()))->dirty = true; + item = metaList.first(); + while(item) + { + if((static_cast<NodeLinkedViewItem *>(item))->dirty) + { + if((static_cast<NodeLinkedViewItem *>(item))->deleted) + { + if((static_cast<NodeLinkedViewItem *>(item))->node) + { + //delete the meta + kafkaCommon::extractAndDeleteNode( + (static_cast<NodeLinkedViewItem *>(item))->node, modifs); + } + } + else + { + if(!(static_cast<NodeLinkedViewItem *>(item))->node) + { + if(!(static_cast<AttributeItem *>(item))->editorText(0).isEmpty() || + !(static_cast<AttributeItem *>(item))->editorText(1).isEmpty()) + { + addBasicNodes(modifs); + //create the meta!! + node = kafkaCommon::createAndInsertNode("meta", "",Tag::XmlTag, + view->document(), headNode, 0L, 0L, modifs); + } + else + node = 0L; + } + else + node = (static_cast<NodeLinkedViewItem *>(item))->node; + if(node) + { + if(node->tag->hasAttribute("name")) + node->tag->editAttribute("name", (static_cast<AttributeItem *>(item))->editorText(0)); + else + node->tag->editAttribute("http-equiv", (static_cast<AttributeItem *>(item))->editorText(0)); + node->tag->editAttribute("content", (static_cast<AttributeItem *>(item))->editorText(1)); + node->tag->setCleanStrBuilt(false); + } + } + } + item = metaList.next(); + } + + //set the CSSs rules if necessary + if(cssRules->currentItem()) + (static_cast<NodeLinkedViewItem *>(cssRules->currentItem()))->dirty = true; + item = CSSList.first(); + while(item) + { + if((static_cast<NodeLinkedViewItem *>(item))->dirty) + { + if((static_cast<NodeLinkedViewItem *>(item))->deleted) + { + if((static_cast<NodeLinkedViewItem *>(item))->node) + { + //Delete the CSS Node + node = (static_cast<NodeLinkedViewItem *>(item))->node; + if(node->next && node->next->tag->type == Tag::ScriptStructureEnd) + kafkaCommon::extractAndDeleteNode(node->next, modifs); + kafkaCommon::extractAndDeleteNode(node, modifs); + } + } + else + { + if(!(static_cast<NodeLinkedViewItem *>(item))->node) + { + if(!(static_cast<AttributeItem *>(item))->editorText(0).isEmpty() || + !(static_cast<AttributeItem *>(item))->editorText(1).isEmpty()) + { + addBasicNodes(modifs); + addBasicCssNodes(modifs); + //create the CSS Nodes! + node = kafkaCommon::createAndInsertNode( + (static_cast<AttributeItem *>(item))->editorText(0), + (static_cast<AttributeItem *>(item))->editorText(0) + "{", + Tag::ScriptStructureBegin, view->document(), + CSSNode, 0L, 0L, modifs); + (void)kafkaCommon::createAndInsertNode("#text", "",Tag::Text, + view->document(), node, 0L, 0L, modifs); + nodeNext = kafkaCommon::createAndInsertNode("", "}",Tag::ScriptStructureEnd, + view->document(), CSSNode, 0L, 0L, modifs); + } + else + node = 0L; + } + else + node = (static_cast<NodeLinkedViewItem *>(item))->node; + if(node && node->child) + { + node->tag->name = (static_cast<AttributeItem *>(item))->editorText(0); + node->tag->setStr((static_cast<AttributeItem *>(item))->editorText(0) + "{"); + node->child->tag->setStr((static_cast<AttributeItem *>(item))->editorText(1)); + } + } + } + item = CSSList.next(); + } + + //set the LINK, if necessary + if(linkDirty) + { + if(!linkNode) + { + //create LINK + addBasicNodes(modifs); + linkNode = kafkaCommon::createAndInsertNode("link", "",Tag::XmlTag, view->document(), + headNode, 0L, 0L, modifs); + } + //modify it! + QuantaCommon::setUrl(url, cssStylesheet->url()); + baseURL = view->document()->url(); + baseURL.setPath(baseURL.directory()); + url = QExtFileInfo::toRelative(url, baseURL); + finalURL = url.url(); + if (url.protocol() == view->document()->url().protocol()) + finalURL.remove(0, url.protocol().length() + 1); + if(linkNode->tag->attributeValue("rel").lower() != "stylesheet") + linkNode->tag->editAttribute("rel", "stylesheet"); + linkNode->tag->editAttribute("href", finalURL); + linkNode->tag->setCleanStrBuilt(false); + } + + if(m_forceInsertionOfBasicNodes) + addBasicNodes(modifs); + + //Set the cursor at the beginning of the document. + goUp = false; + node = kafkaCommon::getNextNode(bodyNode, goUp, bodyNode); + while(node && node->tag->type != Tag::Text) + node = kafkaCommon::getNextNode(node, goUp, bodyNode); + cursorPos = new NodeSelection(); + cursorPos->setCursorNode(node?node:bodyNode); + cursorPos->setCursorOffset(0); + + view->document()->docUndoRedo->addNewModifsSet(modifs, undoRedo::NodeTreeModif, cursorPos); + + delete cursorPos; + + //view->reloadBothViews(); + + done(0); +} + +void htmlDocumentProperties::reject() +{ + NodeModifsSet *modifs = new NodeModifsSet(); + + if(m_forceInsertionOfBasicNodes) + addBasicNodes(modifs); + + ViewManager::ref()->activeDocument()->docUndoRedo->addNewModifsSet(modifs, undoRedo::NodeTreeModif); + + done(0); +} + +void htmlDocumentProperties::addBasicCssNodes(NodeModifsSet *modifs) +{ + if(CSSNode || !htmlNode || !headNode) + return; + //TODO:quick hack, modify createAndInsertNode + CSSNode = kafkaCommon::createAndInsertNode("style", "", Tag::XmlTag, ViewManager::ref()->activeDocument(), + headNode, 0L, 0L, modifs); +} + +void htmlDocumentProperties::addBasicNodes(NodeModifsSet *modifs) +{ + Node *allTheNodes, *lastHeadChild, *lastBodyChild, *lastHtmlChild; + Node *n, *nextNode; + bool htmlNodeCreated = false, bodyNodeCreated = false; + QTag *qHead, *qBody; + QString tagName; + + if(headNode) + return; + + QuantaView *view = ViewManager::ref()->activeView(); + + if(!xmlNode && view->document()->defaultDTD()->name.contains("XHTML", false)) + { + //if the xml node is not present and the current DTD is a xhtml like, create it. + allTheNodes = baseNode; + baseNode = 0L; + xmlNode = kafkaCommon::createXmlDeclarationNode(view->document(), + quantaApp->defaultEncoding()); + nextNode = xmlNode->next; + xmlNode = kafkaCommon::insertNode(xmlNode, 0L, 0L, modifs); + kafkaCommon::insertNode(nextNode, 0L, 0L, modifs); + xmlNode->next->next = allTheNodes; + } + + if(!doctypeNode) + { + //if the !doctype node is not present, create it + if(!view->document()->defaultDTD()->name.contains("XHTML", false)) + { + allTheNodes = baseNode; + baseNode = 0L; + } + else + { + allTheNodes = xmlNode->next->next; + xmlNode->next->next = 0L; + } + doctypeNode = kafkaCommon::createDoctypeNode(view->document()); + nextNode = doctypeNode->next; + doctypeNode = kafkaCommon::insertNode(doctypeNode, 0L, 0L, modifs); + kafkaCommon::insertNode(nextNode, 0L, 0L, modifs); + doctypeNode->next->next = allTheNodes; + } + + if(!htmlNode && !headNode) + { + //if the HTML node is not present, create it + allTheNodes = doctypeNode->next->next; + doctypeNode->next->next = 0L; + htmlNode = kafkaCommon::createAndInsertNode("html", "", Tag::XmlTag, + view->document(), 0L, 0L, 0L, modifs); + + //TODO: hardcoded + //If it is XML, it add the namespace. + if(view->document()->defaultDTD()->name.contains("XHTML", false)) + htmlNode->tag->editAttribute("xmlns", "http://www.w3.org/1999/xhtml"); + + htmlNode->child = allTheNodes; + while(allTheNodes) + { + allTheNodes->parent = htmlNode; + allTheNodes = allTheNodes->next; + } + htmlNodeCreated = true; + } + + //Create the HEAD Node. + allTheNodes = htmlNode->child; + htmlNode->child = 0L; + headNode = kafkaCommon::createAndInsertNode("head", "", Tag::XmlTag, view->document(), + htmlNode, 0L, 0L, modifs); + + if(!bodyNode && htmlNodeCreated) + { + //let's create BODY to take all the Nodes which can't be in the newly created HTML + bodyNode = kafkaCommon::createAndInsertNode("body", "", Tag::XmlTag, + view->document(), htmlNode, 0L, 0L, modifs); + bodyNodeCreated = true; + } + + //we now move the necessary Nodes to HEAD (and to BODY if htmlNodeCreated) + qHead = QuantaCommon::tagFromDTD(view->document()->defaultDTD(), "head"); + qBody = QuantaCommon::tagFromDTD(view->document()->defaultDTD(), "body"); + lastHeadChild = 0L; + lastBodyChild = 0L; + lastHtmlChild = htmlNode->child; + while(lastHtmlChild && lastHtmlChild->next) + lastHtmlChild = lastHtmlChild->next; + while(allTheNodes) + { + n = allTheNodes->next; + if(qHead->isChild(allTheNodes)) + { + /*//TODO:LOG this into the modif!! + allTheNodes->parent = headNode; + if(lastHeadChild) + { + lastHeadChild->next = allTheNodes; + allTheNodes->prev = lastHeadChild; + } + else + { + headNode->child = allTheNodes; + } + lastHeadChild = allTheNodes;*/ + kafkaCommon::moveNode(allTheNodes, headNode, 0L, modifs); + } + else if(bodyNodeCreated && htmlNodeCreated && qBody->isChild(allTheNodes)) + { + //TODO:log!! + /**allTheNodes->parent = bodyNode; + if(lastBodyChild) + { + lastBodyChild->next = allTheNodes; + allTheNodes->prev = lastBodyChild; + } + else + { + bodyNode->child = allTheNodes; + } + lastBodyChild = allTheNodes;*/ + kafkaCommon::moveNode(allTheNodes, bodyNode, 0L, modifs); + } + else + { + //TODO:log? no. + /**allTheNodes->parent = htmlNode; + lastHtmlChild->next = allTheNodes; + allTheNodes->prev = lastHtmlChild; + lastHtmlChild = allTheNodes;*/ + kafkaCommon::moveNode(allTheNodes, htmlNode, 0L, modifs); + } + /**n = allTheNodes->next; + if(allTheNodes->next) + allTheNodes->next->prev = 0L; + allTheNodes->next = 0L; + allTheNodes = n;*/ + allTheNodes = n; + } +} + +NodeLinkedViewItem::NodeLinkedViewItem(EditableTree *listView, const QString& title, const QString& title2) +: AttributeItem(listView, title, title2, 0L) +{ + node = 0L; + dirty = false; + deleted = false; +} + +NodeLinkedViewItem::~NodeLinkedViewItem() +{ + +} + +#include "htmldocumentproperties.moc" diff --git a/quanta/parts/kafka/htmldocumentproperties.h b/quanta/parts/kafka/htmldocumentproperties.h new file mode 100644 index 00000000..62a64659 --- /dev/null +++ b/quanta/parts/kafka/htmldocumentproperties.h @@ -0,0 +1,91 @@ +/*************************************************************************** + htmldocumentproperties.h + ------------------- + + copyright : (C) 2003, 2004 - Nicolas Deschildre + email : ndeschildre@kdewebdev.org + ***************************************************************************/ + +/*************************************************************************** + * * + * 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. * + * * + ***************************************************************************/ + +#ifndef HTMLDOCUMENTPROPERTIES_H +#define HTMLDOCUMENTPROPERTIES_H + +class Node; +class NodeModifsSet; +class AttributeItem; +class EditableTree; + +#include <qptrlist.h> + +#include "tagattributeitems.h" +#include "htmldocumentpropertiesui.h" + +class NodeLinkedViewItem : public AttributeItem +{ +public: + NodeLinkedViewItem(EditableTree *listView, const QString& title, const QString& title2); + virtual ~NodeLinkedViewItem(); + + Node *node; + bool dirty, deleted; +}; + +/** + * The HTML Document properties dialog. + * TODO: Use KDialogBase so it looks more consistent with the rest of the dialogs + */ +class htmlDocumentProperties : public htmlDocumentPropertiesui +{ + Q_OBJECT +public: + /** + * @param forceInsertionOfBasicNodes Force the insertion of the basic Nodes (HTML, BODY, HEAD, ...) if pressing OK + * without having made any changes. + */ + htmlDocumentProperties( QWidget* parent = 0, bool forceInsertionOfBasicNodes = false, + const char* name = 0, bool modal = FALSE, WFlags fl = 0 ); + ~htmlDocumentProperties(); + +protected slots: + virtual void aboutToClose() {} + +private slots: + virtual void accept(); + virtual void reject(); + virtual void newMetaItem(); + virtual void deleteCurrentMetaItem(); + virtual void newCSSRule(); + virtual void editCSSRule(); + virtual void deleteCurrentCSSRule(); + + virtual void titleChanged(const QString &); + virtual void metaChanged(QListViewItem * ); + virtual void CSSChanged(QListViewItem * ); + virtual void linkChanged( const QString& ); + +private: + /** + * If we want to add a new Node, we must first create the basics (if necessary) i.e. html, body + * head nodes, and moving if necessary all the existing Nodes. + * @param modifs The changes are logged for the undo/redo system. + */ + void addBasicNodes(NodeModifsSet *modifs); + //must be called after addBasicnodes + void addBasicCssNodes(NodeModifsSet *modifs); + void loadMetaNode(Node *node); + void loadCSS(Node *node); + + QPtrList<NodeLinkedViewItem> CSSList, metaList; + Node *titleNode, *htmlNode, *headNode, *linkNode, *bodyNode, *doctypeNode, *CSSNode, *xmlNode; + bool titleDirty, linkDirty, m_forceInsertionOfBasicNodes; +}; + +#endif diff --git a/quanta/parts/kafka/htmldocumentpropertiesui.ui b/quanta/parts/kafka/htmldocumentpropertiesui.ui new file mode 100644 index 00000000..0834a6bd --- /dev/null +++ b/quanta/parts/kafka/htmldocumentpropertiesui.ui @@ -0,0 +1,456 @@ +<!DOCTYPE UI><UI version="3.3" stdsetdef="1"> +<class>htmlDocumentPropertiesui</class> +<widget class="QDialog"> + <property name="name"> + <cstring>htmlDocumentPropertiesui</cstring> + </property> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>479</width> + <height>423</height> + </rect> + </property> + <property name="caption"> + <string>Document Properties</string> + </property> + <grid> + <property name="name"> + <cstring>unnamed</cstring> + </property> + <widget class="QListView" row="0" column="0"> + <column> + <property name="text"> + <string>Column 1</string> + </property> + <property name="clickable"> + <bool>true</bool> + </property> + <property name="resizable"> + <bool>true</bool> + </property> + </column> + <item> + <property name="text"> + <string>New Item</string> + </property> + <property name="pixmap"> + <pixmap></pixmap> + </property> + </item> + <property name="name"> + <cstring>listView1</cstring> + </property> + <property name="maximumSize"> + <size> + <width>0</width> + <height>0</height> + </size> + </property> + </widget> + <widget class="Line" row="9" column="0" rowspan="2" colspan="7"> + <property name="name"> + <cstring>line1</cstring> + </property> + <property name="frameShape"> + <enum>HLine</enum> + </property> + <property name="frameShadow"> + <enum>Sunken</enum> + </property> + <property name="orientation"> + <enum>Horizontal</enum> + </property> + </widget> + <widget class="QLabel" row="0" column="0"> + <property name="name"> + <cstring>textLabel1</cstring> + </property> + <property name="sizePolicy"> + <sizepolicy> + <hsizetype>4</hsizetype> + <vsizetype>5</vsizetype> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="text"> + <string>Current DTD: </string> + </property> + </widget> + <widget class="QLabel" row="1" column="0"> + <property name="name"> + <cstring>textLabel2</cstring> + </property> + <property name="sizePolicy"> + <sizepolicy> + <hsizetype>4</hsizetype> + <vsizetype>5</vsizetype> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="text"> + <string>Title: </string> + </property> + </widget> + <widget class="QLabel" row="8" column="0"> + <property name="name"> + <cstring>textLabel1_2</cstring> + </property> + <property name="sizePolicy"> + <sizepolicy> + <hsizetype>4</hsizetype> + <vsizetype>5</vsizetype> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="text"> + <string>Link CSS stylesheet:</string> + </property> + </widget> + <widget class="QLabel" row="2" column="0"> + <property name="name"> + <cstring>textLabel3</cstring> + </property> + <property name="sizePolicy"> + <sizepolicy> + <hsizetype>4</hsizetype> + <vsizetype>5</vsizetype> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="text"> + <string>Meta items:</string> + </property> + </widget> + <spacer row="3" column="0" rowspan="2" colspan="1"> + <property name="name"> + <cstring>spacer7</cstring> + </property> + <property name="orientation"> + <enum>Vertical</enum> + </property> + <property name="sizeType"> + <enum>Preferred</enum> + </property> + <property name="sizeHint"> + <size> + <width>21</width> + <height>86</height> + </size> + </property> + </spacer> + <widget class="QLabel" row="5" column="0"> + <property name="name"> + <cstring>textLabel4</cstring> + </property> + <property name="sizePolicy"> + <sizepolicy> + <hsizetype>4</hsizetype> + <vsizetype>5</vsizetype> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="text"> + <string>CSS rules:</string> + </property> + </widget> + <spacer row="6" column="0" rowspan="2" colspan="1"> + <property name="name"> + <cstring>spacer8</cstring> + </property> + <property name="orientation"> + <enum>Vertical</enum> + </property> + <property name="sizeType"> + <enum>Preferred</enum> + </property> + <property name="sizeHint"> + <size> + <width>21</width> + <height>84</height> + </size> + </property> + </spacer> + <spacer row="11" column="0" rowspan="1" colspan="2"> + <property name="name"> + <cstring>spacer10</cstring> + </property> + <property name="orientation"> + <enum>Horizontal</enum> + </property> + <property name="sizeType"> + <enum>Expanding</enum> + </property> + <property name="sizeHint"> + <size> + <width>226</width> + <height>21</height> + </size> + </property> + </spacer> + <widget class="KPushButton" row="10" column="3" rowspan="2" colspan="2"> + <property name="name"> + <cstring>ok</cstring> + </property> + <property name="sizePolicy"> + <sizepolicy> + <hsizetype>1</hsizetype> + <vsizetype>0</vsizetype> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="text"> + <string>&OK</string> + </property> + </widget> + <spacer row="11" column="5"> + <property name="name"> + <cstring>spacer11</cstring> + </property> + <property name="orientation"> + <enum>Horizontal</enum> + </property> + <property name="sizeType"> + <enum>Preferred</enum> + </property> + <property name="sizeHint"> + <size> + <width>41</width> + <height>20</height> + </size> + </property> + </spacer> + <widget class="DualEditableTree" row="5" column="1" rowspan="2" colspan="6"> + <property name="name"> + <cstring>cssRules</cstring> + </property> + <property name="sizePolicy"> + <sizepolicy> + <hsizetype>5</hsizetype> + <vsizetype>7</vsizetype> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + </widget> + <widget class="DualEditableTree" row="2" column="1" rowspan="2" colspan="6"> + <property name="name"> + <cstring>metaItems</cstring> + </property> + <property name="sizePolicy"> + <sizepolicy> + <hsizetype>5</hsizetype> + <vsizetype>7</vsizetype> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + </widget> + <widget class="KURLRequester" row="8" column="1" rowspan="1" colspan="6"> + <property name="name"> + <cstring>cssStylesheet</cstring> + </property> + <property name="sizePolicy"> + <sizepolicy> + <hsizetype>5</hsizetype> + <vsizetype>5</vsizetype> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + </widget> + <widget class="KPushButton" row="7" column="1"> + <property name="name"> + <cstring>cssRulesAdd</cstring> + </property> + <property name="sizePolicy"> + <sizepolicy> + <hsizetype>4</hsizetype> + <vsizetype>0</vsizetype> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="text"> + <string>Add</string> + </property> + </widget> + <widget class="KPushButton" row="4" column="1"> + <property name="name"> + <cstring>metaItemsAdd</cstring> + </property> + <property name="sizePolicy"> + <sizepolicy> + <hsizetype>4</hsizetype> + <vsizetype>0</vsizetype> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="text"> + <string>Add</string> + </property> + </widget> + <widget class="KPushButton" row="4" column="2" rowspan="1" colspan="3"> + <property name="name"> + <cstring>metaItemsDelete</cstring> + </property> + <property name="sizePolicy"> + <sizepolicy> + <hsizetype>4</hsizetype> + <vsizetype>0</vsizetype> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="text"> + <string>Remove</string> + </property> + </widget> + <widget class="KPushButton" row="7" column="2" rowspan="1" colspan="2"> + <property name="name"> + <cstring>cssRulesEdit</cstring> + </property> + <property name="sizePolicy"> + <sizepolicy> + <hsizetype>4</hsizetype> + <vsizetype>0</vsizetype> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="text"> + <string>Edit</string> + </property> + </widget> + <widget class="KPushButton" row="7" column="4" rowspan="1" colspan="3"> + <property name="name"> + <cstring>cssRulesDelete</cstring> + </property> + <property name="sizePolicy"> + <sizepolicy> + <hsizetype>4</hsizetype> + <vsizetype>0</vsizetype> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="text"> + <string>Remove</string> + </property> + </widget> + <widget class="KPushButton" row="10" column="6" rowspan="2" colspan="1"> + <property name="name"> + <cstring>cancel</cstring> + </property> + <property name="sizePolicy"> + <sizepolicy> + <hsizetype>1</hsizetype> + <vsizetype>0</vsizetype> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="text"> + <string>&Cancel</string> + </property> + </widget> + <widget class="KLineEdit" row="1" column="1" rowspan="1" colspan="6"> + <property name="name"> + <cstring>title</cstring> + </property> + <property name="sizePolicy"> + <sizepolicy> + <hsizetype>5</hsizetype> + <vsizetype>5</vsizetype> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="minimumSize"> + <size> + <width>250</width> + <height>0</height> + </size> + </property> + </widget> + <widget class="KLineEdit" row="0" column="1" rowspan="1" colspan="6"> + <property name="name"> + <cstring>currentDTD</cstring> + </property> + <property name="sizePolicy"> + <sizepolicy> + <hsizetype>5</hsizetype> + <vsizetype>5</vsizetype> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="minimumSize"> + <size> + <width>250</width> + <height>0</height> + </size> + </property> + <property name="readOnly"> + <bool>true</bool> + </property> + </widget> + </grid> +</widget> +<customwidgets> + <customwidget> + <class>DualEditableTree</class> + <header location="local">../../treeviews/tagattributetree.h</header> + <sizehint> + <width>250</width> + <height>100</height> + </sizehint> + <container>0</container> + <sizepolicy> + <hordata>5</hordata> + <verdata>5</verdata> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + <pixmap>image0</pixmap> + </customwidget> +</customwidgets> +<images> + <image name="image0"> + <data format="XBM.GZ" length="79">789c534e494dcbcc4b554829cdcdad8c2fcf4c29c95030e0524611cd48cd4ccf28010a1797249664262b2467241641a592324b8aa363156c15aab914146aadb90067111b1f</data> + </image> +</images> +<tabstops> + <tabstop>currentDTD</tabstop> + <tabstop>title</tabstop> + <tabstop>metaItemsAdd</tabstop> + <tabstop>metaItemsDelete</tabstop> + <tabstop>cssRulesAdd</tabstop> + <tabstop>cssRulesEdit</tabstop> + <tabstop>cssRulesDelete</tabstop> + <tabstop>cssStylesheet</tabstop> + <tabstop>listView1</tabstop> + <tabstop>ok</tabstop> + <tabstop>cancel</tabstop> +</tabstops> +<slots> + <slot access="protected" specifier="pure virtual">newMetaItem()</slot> + <slot access="protected" specifier="pure virtual">deleteCurrentMetaItem()</slot> + <slot access="protected" specifier="pure virtual">newCSSRule()</slot> + <slot access="protected" specifier="pure virtual">editCSSRule()</slot> + <slot access="protected" specifier="pure virtual">deleteCurrentCSSRule()</slot> + <slot access="protected" specifier="pure virtual">titleChanged(const QString &)</slot> + <slot access="protected" specifier="pure virtual">metaChanged(QListViewItem * )</slot> + <slot access="protected" specifier="pure virtual">CSSChanged(QListViewItem * )</slot> + <slot access="protected" specifier="pure virtual">linkChanged( const QString& )</slot> +</slots> +<layoutdefaults spacing="6" margin="11"/> +</UI> diff --git a/quanta/parts/kafka/htmlenhancer.cpp b/quanta/parts/kafka/htmlenhancer.cpp new file mode 100644 index 00000000..257ad239 --- /dev/null +++ b/quanta/parts/kafka/htmlenhancer.cpp @@ -0,0 +1,392 @@ +/*************************************************************************** + htmltranslator.cpp + ------------------- + + copyright : (C) 2003, 2004 - Nicolas Deschildre + email : ndeschildre@kdewebdev.org + ***************************************************************************/ + +/*************************************************************************** + * * + * 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. * + * * + ***************************************************************************/ + +#include <dom/dom_node.h> +#include <dom/dom_string.h> +#include <dom/dom_exception.h> +#include <kdebug.h> +#include <kstandarddirs.h> +#include <kconfig.h> + +#include "quantacommon.h" +#include "document.h" +#include "tag.h" +#include "node.h" +#include "wkafkapart.h" +#include "nodeproperties.h" +#include "kafkacommon.h" +#include "qextfileinfo.h" +#include "viewmanager.h" + +#include "htmlenhancer.h" + +HTMLEnhancer::HTMLEnhancer(KafkaDocument *_wkafkapart) + : NodeEnhancer() +{ + m_showIconForScripts = true; + m_wkafkapart = _wkafkapart; + m_stddirs = new KStandardDirs(); +} + +HTMLEnhancer::~HTMLEnhancer() +{ + delete m_stddirs; +} + +bool HTMLEnhancer::enhanceNode(Node *node, DOM::Node parentDNode, DOM::Node nextDNode) +{ + DOM::Node domNode, domNode2, attr, *ptDomNode; + bool tbody, goUp; + Node *n; + QString script, filename, text, oldName; + KURL url, baseURL; + int oldType; + + //FIRST update the src attr with the baseURL + if(node->rootNode()) + { + domNode = node->rootNode()->attributes().getNamedItem("src"); + if(!domNode.isNull()) + { + baseURL.setPath(ViewManager::ref()->activeDocument()->url().directory()); + QuantaCommon::setUrl(url, domNode.nodeValue().string()); + url = QExtFileInfo::toAbsolute(url, baseURL); + domNode.setNodeValue(url.url()); +#ifdef HEAVY_DEBUG + kdDebug(25001)<< "HTMLTranslator::translateNode() - new src : " << url.url() << endl; +#endif + } + } + + //THEN update the href attr of the LINK node with the baseURL + if(node->tag->name.lower() == "link" && node->rootNode()) + { + domNode = node->rootNode()->attributes().getNamedItem("href"); + if(!domNode.isNull()) + { + baseURL.setPath(ViewManager::ref()->activeDocument()->url().directory()); + QuantaCommon::setUrl(url, domNode.nodeValue().string()); + url = QExtFileInfo::toAbsolute(url, baseURL); + domNode.setNodeValue(url.url()); +#ifdef HEAVY_DEBUG + kdDebug(25001)<< "HTMLTranslator::translateNode() - new href : " << url.url() << endl; +#endif + } + } + + //THEN if it is the style element, add a DOM::Node::TEXT_NODE child gathering all the CSS + //by default, the parser parse it as a script, which can't be translated in DOM::Nodes. + if((node->tag->type == Tag::XmlTag && node->tag->name.lower() == "style") || + (node->tag->type == Tag::ScriptTag && node->tag->name.lower().contains("style") != 0)) + { + //If the style Node doesn't exists, create it + if(!node->rootNode()) + { + oldType = node->tag->type; + node->tag->type = Tag::XmlTag; + oldName = node->tag->name; + node->tag->name = "style"; + m_wkafkapart->buildKafkaNodeFromNode(node); + node->tag->type = oldType; + node->tag->name = oldName; + } + + if(node->rootNode()) + { + domNode = *node->rootNode(); + n = node->child; + text = ""; + goUp = false; + while(n) + { + text += n->tag->tagStr(); + n = kafkaCommon::getNextNode(n, goUp, node); + } +#ifdef HEAVY_DEBUG + kdDebug(25001)<< "HTMLTranslator::translateNode() - CSS code : " << text << endl; +#endif + domNode2 = kafkaCommon::createTextDomNode(text, m_wkafkapart->getKafkaWidget()->document()); + if(!kafkaCommon::insertDomNode(domNode2, domNode)) + return false; + m_wkafkapart->connectDomNodeToQuantaNode(domNode2, node); + } + } + + QTag* qTag = QuantaCommon::tagFromDTD(m_wkafkapart->getCurrentDoc()->defaultDTD(), + parentDNode.nodeName().string()); + + //THEN replace, if asked, scripts by a little icon. + if(node->tag->type == Tag::ScriptTag && m_showIconForScripts && qTag->isChild("IMG", false)) + { + script = node->tag->name.left(node->tag->name.find("block", 0, false) - 1).lower(); +#ifdef LIGHT_DEBUG + kdDebug(25001)<< "HTMLTranslator::translateNode() - BLOCK:" << script << ":" << endl; +#endif + filename = m_stddirs->findResource("data", "kafkapart/pics/" + script + ".png" ); + if(!filename.isEmpty()) + { + //FIXME DTD! + domNode = kafkaCommon::createDomNode("IMG", m_wkafkapart->defaultDTD(), + m_wkafkapart->getKafkaWidget()->document()); + + kafkaCommon::editDomNodeAttribute(domNode, "IMG", m_wkafkapart->defaultDTD(), "src", + filename, m_wkafkapart->getKafkaWidget()->document()); + + //Add a tooltip indicating the content of the script + n = node->child; + text = ""; + goUp = false; + while(n && n != node) + { + text += n->tag->tagStr(); + n = kafkaCommon::getNextNode(n, goUp, node); + } + //if(text == "") + // text = i18n("Empty") + kafkaCommon::editDomNodeAttribute(domNode, "img", m_wkafkapart->defaultDTD(), + "title", text, m_wkafkapart->getKafkaWidget()->document()); + + if(!kafkaCommon::insertDomNode(domNode, parentDNode, nextDNode)) + return false; + m_wkafkapart->connectDomNodeToQuantaNode(domNode, node); + } + } + + //THEN if it is a comment, add a little icon ;o) + if(node->tag->type == Tag::Comment && m_showIconForScripts && qTag->isChild("IMG", false)) + { +#ifdef LIGHT_DEBUG + kdDebug(25001)<< "HTMLTranslator::translateNode() - Comment" << endl; +#endif + + filename = m_stddirs->findResource("data", "kafkapart/pics/comment.png" ); + if(!filename.isEmpty()) + { + //FIXME DTD! + domNode = kafkaCommon::createDomNode("IMG", m_wkafkapart->defaultDTD(), + m_wkafkapart->getKafkaWidget()->document()); + kafkaCommon::editDomNodeAttribute(domNode, "IMG", m_wkafkapart->defaultDTD(), "src", + filename, m_wkafkapart->getKafkaWidget()->document()); + + //Add a tooltip indicating the content of the script + n = node->child; + text = ""; + goUp = false; + while(n && n != node) + { + text += n->tag->tagStr(); + n = kafkaCommon::getNextNode(n, goUp, node); + } + //if(text == "") + // text = i18n("Empty") + kafkaCommon::editDomNodeAttribute(domNode, "img", m_wkafkapart->defaultDTD(), + "title", text, m_wkafkapart->getKafkaWidget()->document()); + + if(!kafkaCommon::insertDomNode(domNode, parentDNode, nextDNode)) + return false; + m_wkafkapart->connectDomNodeToQuantaNode(domNode, node); + } + } + + //THEN add a TBODY tag if necessary + if(node->rootNode() && node->rootNode()->nodeName().string().lower() == "table") + { + tbody = false; + n = node->child; + while(n) + { + if(n->tag->name.lower() == "tbody") + tbody = true; + n = n->next; + } + if(!tbody) + { + domNode = kafkaCommon::createDomNode("TBODY", m_wkafkapart->defaultDTD(), + m_wkafkapart->getKafkaWidget()->htmlDocument()); + if(!kafkaCommon::insertDomNode(domNode, *node->rootNode())) + return false; + m_wkafkapart->connectDomNodeToQuantaNode(domNode, node); + ptDomNode = new DOM::Node(domNode); + node->setLeafNode(ptDomNode); + } + } + + //THEN add a red dotted border to FORM tags. + if(node->rootNode() && node->rootNode()->nodeName().string().lower() == "form") + { + kafkaCommon::editDomNodeAttribute(*node->rootNode(), node, "style", "border: 1px dotted red", + m_wkafkapart->getKafkaWidget()->document()); + } + + // THEN add a tooltip indicating the content of the name attribute + if(node->rootNode() && node->rootNode()->nodeName().string().lower() == "input") + { + domNode = *(node->rootNode()); + QString text = node->tag->attributeValue("name"); + kafkaCommon::editDomNodeAttribute(domNode, "input", m_wkafkapart->defaultDTD(), + "title", text, m_wkafkapart->getKafkaWidget()->document()); + } + + //THEN add a blue dotted border to DL, OL, UL tags + if(node->rootNode()) + { + text = node->rootNode()->nodeName().string().lower(); + if(text == "dl" || text == "ol" || text == "ul") + { + kafkaCommon::editDomNodeAttribute(*node->rootNode(), node, "style", "border: 1px dotted blue", + m_wkafkapart->getKafkaWidget()->document()); + } + } + + //THEN add a minimal border for borderless tables + //TODO: make it configurable, and look if CSS hasn't defined a border first + if(node->rootNode() && node->rootNode()->nodeName().string().lower() == "table") + { + attr = node->rootNode()->attributes().getNamedItem("border"); + if(attr.isNull() || (!attr.isNull() && attr.nodeValue().string() == "0")) + { + kafkaCommon::editDomNodeAttribute(*node->rootNode(), node, "border", "1", + m_wkafkapart->getKafkaWidget()->document()); + } + } + + //THEN add a blue dotted border to DIV tags + if(node->rootNode()) + { + text = node->rootNode()->nodeName().string().lower(); + if(text == "div") + { + kafkaCommon::editDomNodeAttribute(*node->rootNode(), node, "style", "border: 1px dotted green", + m_wkafkapart->getKafkaWidget()->document()); + } + } + + return true; +} + +void HTMLEnhancer::postEnhanceNode(DOM::Node domNode) +{ + DOM::Node textNode; + kNodeAttrs *props; + QTag *qTag; + bool isInline; + + if(domNode.isNull()) + return; + + //If domNode is a Block and there is no text around, and if domNode's parent can handle + //text or a P tag, add an empty text DOM::Node + // so that the user can access this area. + qTag = QuantaCommon::tagFromDTD(m_wkafkapart->getCurrentDoc()->defaultDTD(), + domNode.nodeName().string()); + isInline = kafkaCommon::isInline(domNode.nodeName().string()); + if(domNode.nodeType() == DOM::Node::ELEMENT_NODE && + (!isInline || (isInline && qTag && qTag->isSingle()))) + { + qTag = QuantaCommon::tagFromDTD(m_wkafkapart->getNode(domNode.parentNode())); + + if((domNode.nextSibling().isNull() || + (!domNode.nextSibling().isNull() && + domNode.nextSibling().nodeType() == DOM::Node::ELEMENT_NODE && + !kafkaCommon::isInline(domNode.nextSibling().nodeName().string()))) + && qTag && (qTag->isChild("#text", false) || qTag->isChild("p", false)) && + domNode.nodeName().string().lower() != "p") + { + textNode = kafkaCommon::createTextDomNode("", + m_wkafkapart->getKafkaWidget()->document()); + props = m_wkafkapart->connectDomNodeToQuantaNode(textNode, 0L); + props->setIsLinkedToNode(false); + props->setSpecialBehavior(kNodeAttrs::emptyTextSurroundingBlockElementAtTheRight); + kafkaCommon::insertDomNode(textNode, domNode.parentNode(), + domNode.nextSibling()); + } + + if((domNode.previousSibling().isNull() || (!domNode.previousSibling().isNull() && + domNode.previousSibling().nodeType() == DOM::Node::ELEMENT_NODE && + !kafkaCommon::isInline(domNode.previousSibling().nodeName().string()))) + && qTag && (qTag->isChild("#text", false) || qTag->isChild("p", false)) && + domNode.nodeName().string().lower() != "p") + { + textNode = kafkaCommon::createTextDomNode("", + m_wkafkapart->getKafkaWidget()->document()); + props = m_wkafkapart->connectDomNodeToQuantaNode(textNode, 0L); + props->setIsLinkedToNode(false); + props->setSpecialBehavior(kNodeAttrs::emptyTextSurroundingBlockElementAtTheLeft); + kafkaCommon::insertDomNode(textNode, domNode.parentNode(), + domNode); + } + } + + //If domNode is an childless element, and if it can handle Text or a P tag, + //add an empty text DOM::Node so that the + //user can access this area. + qTag = QuantaCommon::tagFromDTD(m_wkafkapart->getNode(domNode)); + if(domNode.nodeType() == DOM::Node::ELEMENT_NODE && + !domNode.hasChildNodes() && qTag && (qTag->isChild("#text", false) || + qTag->isChild("p", false))) + { + textNode = kafkaCommon::createTextDomNode("", + m_wkafkapart->getKafkaWidget()->document()); + props = m_wkafkapart->connectDomNodeToQuantaNode(textNode, 0L); + props->setIsLinkedToNode(false); + props->setSpecialBehavior(kNodeAttrs::emptyTextAsChildOfAChildlessElement); + kafkaCommon::insertDomNode(textNode, domNode); + } +} + +void HTMLEnhancer::postUnenhanceNode(DOM::Node domNode) +{ + DOM::Node child, next; + kNodeAttrs *attrs; + + if(domNode.isNull()) + return; + + //Try to remove the EmptyTextAsChildOfAChildlessElement Node first if present + if(domNode.hasChildNodes()) + { + child = domNode.firstChild(); + while(!child.isNull()) + { + attrs = m_wkafkapart->getAttrs(child); + next = child.nextSibling(); + if(attrs && attrs->specialBehavior() == kNodeAttrs::emptyTextAsChildOfAChildlessElement) + kafkaCommon::removeDomNode(child); + child = next; + } + } + + //Then try to remove the emptyTextSurroundingBlockElement* Nodes if present. + if(!domNode.previousSibling().isNull()) + { + attrs = m_wkafkapart->getAttrs(domNode.previousSibling()); + if(attrs && attrs->specialBehavior() == kNodeAttrs::emptyTextSurroundingBlockElementAtTheLeft) + kafkaCommon::removeDomNode(domNode.previousSibling()); + } + if(!domNode.nextSibling().isNull()) + { + attrs = m_wkafkapart->getAttrs(domNode.nextSibling()); + if(attrs && attrs->specialBehavior() == kNodeAttrs::emptyTextSurroundingBlockElementAtTheRight) + kafkaCommon::removeDomNode(domNode.nextSibling()); + } +} + +void HTMLEnhancer::readConfig(KConfig *m_config) +{ + m_config->setGroup("HTML Enhancer"); + m_showIconForScripts = m_config->readBoolEntry("Show Scripts Icons", true); +} diff --git a/quanta/parts/kafka/htmlenhancer.h b/quanta/parts/kafka/htmlenhancer.h new file mode 100644 index 00000000..29ebe82f --- /dev/null +++ b/quanta/parts/kafka/htmlenhancer.h @@ -0,0 +1,105 @@ +/*************************************************************************** + htmlenhancer.h + ------------------- + + copyright : (C) 2003, 2004 - Nicolas Deschildre + email : ndeschildre@kdewebdev.org + ***************************************************************************/ + +/*************************************************************************** + * * + * 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. * + * * + ***************************************************************************/ + +#ifndef HTMLENHANCER_H +#define HTMLENHANCER_H + +#include <kurl.h> + +#include "nodeenhancer.h" + +class KConfig; +class Node; +class KafkaDocument; +class KStandardDirs; + +/** + * This class takes care of the following tasks for [X]HTML DTDs : + * - Add a TBODY DOM::Node to TABLE DOM::Node if necessary. + * - Update the src attr of the DOM::Node. + * - Display an little icon for scripts. + * - Load the CSS informations into the DOM tree. + * - Add a colourfull border to FORM, OL, DL, UL + */ + +class HTMLEnhancer : public NodeEnhancer +{ +public: + HTMLEnhancer(KafkaDocument *_wkafkapart); + virtual ~HTMLEnhancer(); + + /** + * This function modify the DOM::Node of the node. + * The DOM::Node must be built before calling this + * function. + * @param node The Node we want to enhance. + * @param parentDNode the parent DOM::Node of the root DOM::Node of node. + * @param nextDNode the DOM::Node next to the root DOM::Node of node. + */ + virtual bool enhanceNode(Node *node, DOM::Node parentDNode, DOM::Node nextDNode); + + /** + * This functions is called once the whole DOM::Node tree is built. It will add empty + * TEXT DOM::Node around block element or inside element when necessary + * so that the user can access every single part of kafka with the cursor. + * @param domNode The node we want to add these empty text around. + */ + virtual void postEnhanceNode(DOM::Node domNode); + + /** + * This function do the opposite of postEnhanceNode. Usefull to remove the + * extra Text DOM::Nodes when deleting a DOM::Node. + */ + virtual void postUnenhanceNode(DOM::Node domNode); + + /** + * Read the config. + * @m_config The config to read. + */ + void readConfig(KConfig *m_config); + + /** + * Set the base URL of the current document to update src attrs. + * @param baseURL The base URL of the current Document. + */ + void setBaseURL(KURL baseURL) {m_baseURL = baseURL;} + + /** + * Get the current base URL. + * @return Returns the current base URL. + */ + KURL baseURL() {return m_baseURL;} + + /** + * @return Specifies if we should show icons for scripts. + */ + bool showIconsForScripts() {return m_showIconForScripts;} + + /** + * Set if we should these little icons. + * @param showIcons Should we show these icons? + */ + void showIconsForScripts(bool showIcons) {m_showIconForScripts = showIcons;} + +private: + KURL m_baseURL; + bool m_showIconForScripts; + KafkaDocument *m_wkafkapart; + KStandardDirs *m_stddirs; +}; + +#endif diff --git a/quanta/parts/kafka/kafkacommon.cpp b/quanta/parts/kafka/kafkacommon.cpp new file mode 100644 index 00000000..fe147bc7 --- /dev/null +++ b/quanta/parts/kafka/kafkacommon.cpp @@ -0,0 +1,4257 @@ +/*************************************************************************** + kafkacommon.cpp + ------------------- + + copyright : (C) 2003, 2004 - Nicolas Deschildre + email : ndeschildre@kdewebdev.org + ***************************************************************************/ + +/*************************************************************************** + * * + * 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. * + * * + ***************************************************************************/ + +#include <qptrdict.h> + +#include <kdebug.h> +#include <dom/dom_exception.h> +#include <dom/dom_doc.h> +#include <dom/dom_element.h> +#include <dom/dom_text.h> + +#include "node.h" +#include "tag.h" +#include "document.h" +#include "resource.h" +#include "quantacommon.h" + +#include "kafkacommon.h" +#include "wkafkapart.h" +#include "undoredo.h" +#include "cursors.h" + +#include <cassert> + +Node *kafkaCommon::getNextNode(Node *node, bool &goUp, Node *endNode) +{ + //goto next node, my favorite part :) + if(!node || node == endNode) + return 0L; + if(goUp) + { + if(node->next) + { + goUp = false; + if(node->next == endNode) + return 0L; + return node->next; + } + else + { + if(node->parent == endNode) + return 0L; + return getNextNode(node->parent, goUp); + } + } + else + { + if(node->child) + { + if(node->child == endNode) + return 0L; + return node->child; + } + else if(node->next) + { + if(node->next == endNode) + return 0L; + return node->next; + } + else + { + goUp = true; + if(node->parent == endNode) + return 0L; + return getNextNode(node->parent, goUp); + } + } +} + +Node* kafkaCommon::getNextNodeNE(Node *node, bool &goUp, Node *endNode) +{ + Node *n = node; + n = getNextNode(n, goUp, endNode); + while(n && n->tag->type == Tag::Empty) + n = getNextNode(n, goUp, endNode); + return n; +} + +Node* kafkaCommon::getPrevNode(Node *node, Node *endNode) +{ + Node *n = node; + + if(!node) + return 0L; + + if(n->prev && n->prev->child) + { + n = n->prev; + if(n == endNode) + return 0L; + while(n->child) + { + n = n->child; + while(n && n->next) + n = n->next; + if(n == endNode) + return 0L; + } + } + else if(n->prev) + { + n = n->prev; + if(n == endNode) + return 0L; + } + else + { + n = n->parent; + if(n == endNode) + return 0L; + } + return n; +} + +Node* kafkaCommon::getPrevNodeNE(Node *node, Node *endNode) +{ + Node *n = node; + n = getPrevNode(node, endNode); + while(n && n->tag->type == Tag::Empty) + n = getPrevNode(n, endNode); + return n; +} + +Node* kafkaCommon::DTDGetCommonParent(Node* startNode, Node* endNode, + QValueList<int>& commonParentStartChildLocation, + QValueList<int>& commonParentEndChildLocation, Node* nodeSubtree) +{ + // look for commonParent + Node* commonParent = 0; + Node* commonParentStartChild = 0, *commonParentEndChild = 0; + int locOffset = 1; + QValueList<int> startNodeLocation = getLocation(startNode); + QValueList<int> endNodeLocation = getLocation(endNode); + QValueList<int>::iterator itStart = startNodeLocation.begin(); + QValueList<int>::iterator itEnd = endNodeLocation.begin(); + while(itStart != startNodeLocation.end() && itEnd != endNodeLocation.end() && + (*itStart) == (*itEnd)) + { + commonParent = getNodeFromSubLocation(startNodeLocation, locOffset, nodeSubtree); + itStart++; + itEnd++; + locOffset++; + } + + //look for commonParentStartChild and commonParentEndChild + if(itStart != startNodeLocation.end()) + commonParentStartChild = getNodeFromSubLocation(startNodeLocation, locOffset, nodeSubtree); + else + commonParentStartChild = commonParent; + + if(itEnd != endNodeLocation.end()) + commonParentEndChild = getNodeFromSubLocation(endNodeLocation, locOffset, nodeSubtree); + else + commonParentEndChild = commonParent; + + //If commonParent isn't inline, move commonParent to the closest non inline node + if(commonParent && (commonParent->tag->type == Tag::Text || commonParent->tag->type == Tag::Empty)) + { + Node* oldCommonParent = commonParent; + commonParent = commonParent->parent; + commonParentStartChild = oldCommonParent; + commonParentEndChild = oldCommonParent; + } + //startNode or endNode can't be the commonParent. + else if(commonParent && (itStart == startNodeLocation.end() || itEnd == endNodeLocation.end())) + commonParent = commonParent->parent; + + commonParentStartChildLocation = getLocation(commonParentStartChild); + commonParentEndChildLocation = getLocation(commonParentEndChild); + + return commonParent; +} + +Node* kafkaCommon::DTDGetNonInlineCommonParent(Node* startNode, Node* endNode, + QValueList<int>& commonParentStartChildLocation, + QValueList<int>& commonParentEndChildLocation, Node* nodeSubtree) +{ + // look for commonParent + Node* commonParent = 0; + Node* commonParentStartChild = 0, *commonParentEndChild = 0; + int locOffset = 1; + QValueList<int> startNodeLocation = getLocation(startNode); + QValueList<int> endNodeLocation = getLocation(endNode); + QValueList<int>::iterator itStart = startNodeLocation.begin(); + QValueList<int>::iterator itEnd = endNodeLocation.begin(); + while(itStart != startNodeLocation.end() && itEnd != endNodeLocation.end() && + (*itStart) == (*itEnd)) + { + commonParent = getNodeFromSubLocation(startNodeLocation, locOffset, nodeSubtree); + itStart++; + itEnd++; + locOffset++; + } + + //look for commonParentStartChild and commonParentEndChild + if(itStart != startNodeLocation.end()) + commonParentStartChild = getNodeFromSubLocation(startNodeLocation, locOffset, nodeSubtree); + else + commonParentStartChild = commonParent; + + if(itEnd != endNodeLocation.end()) + commonParentEndChild = getNodeFromSubLocation(endNodeLocation, locOffset, nodeSubtree); + else + commonParentEndChild = commonParent; + + //If commonParent isn't inline, move commonParent to the closest non inline node + if(commonParent && (isInline(commonParent->tag->name) || + commonParent->tag->type == Tag::Text || commonParent->tag->type == Tag::Empty)) + { + Node* oldCommonParent = commonParent; + commonParent = commonParent->parent; + while(commonParent && isInline(commonParent->tag->name)) + { + oldCommonParent = commonParent; + commonParent = commonParent->parent; + } + commonParentStartChild = oldCommonParent; + commonParentEndChild = oldCommonParent; + } + //startNode or endNode can't be the commonParent. + else if(commonParent && (itStart == startNodeLocation.end() || itEnd == endNodeLocation.end())) + commonParent = commonParent->parent; + + commonParentStartChildLocation = getLocation(commonParentStartChild); + commonParentEndChildLocation = getLocation(commonParentEndChild); + + return commonParent; +} + +DOM::Node kafkaCommon::getNextDomNode(DOM::Node node, bool &goUp, bool returnParentNode, DOM::Node endNode) +{ + if(node.isNull()) + return 0L; + if(node.hasChildNodes() && !goUp) + { + if(endNode == node.firstChild()) + return 0L; + else + return node.firstChild(); + } + else if(!node.nextSibling().isNull()) + { + goUp = false; + if(endNode == node.nextSibling()) + return 0L; + else + return node.nextSibling(); + } + else + { + goUp = true; + if(node.parentNode().isNull() || endNode == node.parentNode()) + return 0L; + if(returnParentNode) + return node.parentNode(); + else + return getNextDomNode(node.parentNode(), goUp, returnParentNode, endNode); + } +} + +DOM::Node kafkaCommon::getPrevDomNode(DOM::Node node, DOM::Node endNode) +{ + DOM::Node n = node; + + if(node.isNull()) + return 0L; + + if(!n.previousSibling().isNull() && !n.previousSibling().firstChild().isNull()) + { + n = n.previousSibling(); + if(n == endNode) + return 0L; + while(!n.firstChild().isNull()) + { + n = n.firstChild(); + while(!n.isNull() && !n.nextSibling().isNull()) + n = n.nextSibling(); + if(n == endNode) + return 0L; + } + } + else if(!n.previousSibling().isNull()) + { + n = n.previousSibling(); + if(n == endNode) + return 0L; + } + else + { + n = n.parentNode(); + if(n == endNode) + return 0L; + } + return n; +} + +Node* kafkaCommon::getCorrectStartNode(Node* startNode, int& start_offset) +{ + Node* start_node = startNode; + + while(start_node && (start_node->tag->type != Tag::Text || (uint)start_offset == start_node->tag->tagStr().length())) + { + start_node = start_node->nextSibling(); + if(start_node->tag->type == Tag::Text || start_node->tag->type == Tag::Empty) + { + start_offset = 0; + break; + } + } + + return start_node; +} + +Node* kafkaCommon::getCorrectEndNode(Node* endNode, int& end_offset) +{ + Node* end_node = endNode; + + while(end_node && (end_node->tag->type != Tag::Text || end_offset == 0)) + { + end_node = end_node->previousSibling(); + if(end_node && end_node->tag->type == Tag::Text) + { + end_offset = end_node->tag->tagStr().length(); + break; + } + } + + return end_node; +} + +Node* kafkaCommon::getCommonParentChild(Node* node, Node* commonParent) +{ + assert(node && commonParent); + + Node* aux = commonParent->child; + assert(aux); + + while(aux && aux != node) + { + if(aux->hasForChild(node)) + return aux; + aux = aux->nextSibling(); + } + return aux; +} + + +void kafkaCommon::applyIndentation(Node *node, int nbOfSpaces, int nbOfTabs, NodeModifsSet* modifs, bool inlineNodeIndentation) +{ +#ifdef LIGHT_DEBUG + kdDebug(25001)<< "kafkaCommon::applyIndentation()" << endl; +#endif + + Node *parent, *nextNE, *prevNE, *realPrevNE, *realNextNE, *realPrev, *realNext, *prev, *next; + int nonInlineDepth = 0, nonInlineDepth2 = 0, i; + bool b = false; + QString indentation1, indentation2, text; + + if(!node) + return; + + prev = node->previousSibling(); + next = node->nextSibling(); + prevNE = getPrevNodeNE(node); + nextNE = getNextNodeNE(node, b); + realPrevNE = node->prevNE(); + realNextNE = node->nextNE(); + realPrev = node->prev; + realNext = node->next; + + if(inlineNodeIndentation && + !node->prev && getNodeDisplay(node->parent, true) == kafkaCommon::blockDisplay) + { + AreaStruct node_area = node->tag->area(); + AreaStruct parent_area = node->parent->tag->area(); + + if(node_area.bLine == parent_area.bLine) + { + node->tag->setIndentationDone(true); + return; + } + } + + //First remove all the indentation + if(node->tag->type == Tag::Text) + setTagString(node, removeUnnecessaryWhitespaces(node->tag->tagStr()), modifs); + + //compute the "non-inline depth" of the Node and of the next NE (not Empty) Node + // i.e. we count how many non-inline parent they have. + parent = node->parent; + while(parent) + { + if(getNodeDisplay(parent, true) == kafkaCommon::blockDisplay) + ++nonInlineDepth; + parent = parent->parent; + } + + //compute the "non-inline depth" of the next non-empty Node. + if (nextNE) + parent = nextNE->parent; + else + parent = 0L; + while(parent) + { + if(getNodeDisplay(parent, true) == kafkaCommon::blockDisplay) + ++nonInlineDepth2; + parent = parent->parent; + } + + parent = node->parent; + + if(!parent || getNodeDisplay(parent, true) == kafkaCommon::blockDisplay) + { + //prepare the indentation + indentation1 = "\n"; + indentation2 = "\n"; + + if(nbOfSpaces == 0) // tabs are used + { + indentation1 += QString().fill('\t', nbOfTabs * nonInlineDepth); + indentation2 += QString().fill('\t', nbOfTabs * nonInlineDepth2); + } + else // spaces are used + { + indentation1 += QString().fill(' ', nbOfSpaces * nonInlineDepth); + indentation2 += QString().fill(' ', nbOfSpaces * nonInlineDepth2); + } + + //test and add indentations if necessary + if(!prevNE || (prevNE && getNodeDisplay(node, true) == + kafkaCommon::blockDisplay) || + (prevNE && getNodeDisplay(node, true) == kafkaCommon::inlineDisplay && + getNodeDisplay(prevNE, true) == kafkaCommon::blockDisplay)) + { + if(node->tag->type == Tag::Text && !hasParent(node, "pre")) + { + setTagStringAndFitsNodes(node, indentation1 + node->tag->tagStr(), modifs); + } + else if(prev && prev->tag->type == Tag::Empty) + { + setTagStringAndFitsNodes(prev, indentation1, modifs); + } + //The indentation is always done at the left because we apply this function "from left to right" + else if(prev && prev->tag->type == Tag::Text /** && prev->tag->indentationDone() */) + { + //Remove the indentation at the right of the text Node + text = prev->tag->tagStr(); + for(i = 0; (unsigned)i < text.length(); ++i) + { + if(!text[i].isSpace()) + break; + } + if(i == 0) + prev->tag->setStr(removeUnnecessaryWhitespaces(text)); + else + prev->tag->setStr(text.mid(0, i) + removeUnnecessaryWhitespaces(text, true)); + setTagStringAndFitsNodes(prev, prev->tag->tagStr() + indentation1, modifs); + } + } + + if(!nextNE || (nextNE && getNodeDisplay(node, true) == + kafkaCommon::blockDisplay) || + (nextNE && getNodeDisplay(node, true) == kafkaCommon::inlineDisplay && + getNodeDisplay(nextNE, true) == kafkaCommon::blockDisplay)) + { + if(node->tag->type == Tag::Text && !hasParent(node, "pre")) + { + setTagStringAndFitsNodes(node, node->tag->tagStr() + indentation2, modifs); + } + else if(next && next->tag->type == Tag::Empty) + { + setTagStringAndFitsNodes(next, indentation2, modifs); + } + //If next's cleanStrBuilt is not true, the next node to be processed will be this + //one and the indentation spaces will be handled as real spaces. + else if(next && next->tag->type == Tag::Text && next->tag->indentationDone()) + { + //Remove the indentation at the left of the text Node + text = next->tag->tagStr(); + for(i = text.length() - 1; i <= 0; i--) + { + if(!text[i].isSpace()) + break; + } + if((unsigned)i == text.length() - 1) + next->tag->setStr(removeUnnecessaryWhitespaces(text)); + else + next->tag->setStr(removeUnnecessaryWhitespaces(text, false, true) + + text.mid(i + 1)); + setTagStringAndFitsNodes(next, indentation2 + next->tag->tagStr(), modifs); + } + } + } + else + { + //The parent is inline, so no indentation. + //Nothing to do. + } + node->tag->setIndentationDone(true); +} + +void kafkaCommon::fitIndentationNodes(Node *n1, Node *n2, NodeModifsSet *modifs) +{ +#ifdef LIGHT_DEBUG + kdDebug(25001)<< "kafkaCommon::fitIndentationNodes()" << endl; +#endif + + Node *parent, *child, *node, *emptyNode = 0L, *emptyNode2 = 0L; + int nbEmptyNodes = 0, n1Depth, n2Depth; + bool lastChild = false, firstChild = false; + + if(!n1 || !n2 || n1 == n2 || n1->tag->type == Tag::Empty || n2->tag->type == Tag::Empty) + return; + + n1Depth = nodeDepth(n1); + n2Depth = nodeDepth(n2); + + if(n1Depth != n2Depth) + { + if(n1Depth > n2Depth) + { + child = n1; + parent = n2; + } + else + { + child = n2; + parent = n1; + } + if(child->parent->firstChildNE() == child) + firstChild = true; + if(child->parent->lastChildNE() == child) + lastChild = true; + + //counting the Empty Nodes and deleting them to have only one empty node. + if(firstChild) + { + node = child->prev; + while(node) + { + if(node->tag->type == Tag::Empty) + nbEmptyNodes++; + node = node->prev; + } + node = child->prev; + while(nbEmptyNodes > 1) + { + extractAndDeleteNode(node, modifs, false, false, false); + nbEmptyNodes--; + node = child->prev; + } + if(nbEmptyNodes == 1) + emptyNode = child->prev; + } + + nbEmptyNodes = 0; + if(lastChild) + { + node = child->next; + while(node) + { + if(node->tag->type == Tag::Empty) + nbEmptyNodes++; + node = node->next; + } + node = child->next; + while(nbEmptyNodes > 1) + { + extractAndDeleteNode(node, modifs, false, false, false); + nbEmptyNodes--; + node = child->next; + } + if(nbEmptyNodes == 1) + emptyNode2 = child->next; + } + + //adding/deleting a empty node if necessary + if(firstChild) + { + if(getNodeDisplay(parent, true) == kafkaCommon::blockDisplay) + { + if(child->tag->type != Tag::Text && !emptyNode) + { + createAndInsertNode("", "", Tag::Empty, n2->tag->write(), child->parent, + child, child, modifs); + } + } + else + { + if(child->tag->type == Tag::Text && emptyNode) + { + extractAndDeleteNode(emptyNode, modifs, false, false, false); + } + } + } + + if(lastChild) + { + if(getNodeDisplay(parent, true) == kafkaCommon::blockDisplay) + { + if(child->tag->type != Tag::Text && !emptyNode2) + { + createAndInsertNode("", "", Tag::Empty, n2->tag->write(), child->parent, + 0L, 0L, modifs); + } + } + else + { + if(child->tag->type == Tag::Text && emptyNode2) + { + extractAndDeleteNode(emptyNode2, modifs, false, false, false); + } + } + } + } + else + { + if(n1->next != n2) + { + //counting the Empty Nodes and deleting them to have only one empty node. + node = n1->next; + while(node && node != n2) + { + if(node->tag->type == Tag::Empty) + nbEmptyNodes++; + node = node->next; + } + node = n1->next; + while(nbEmptyNodes > 1 || (nbEmptyNodes > 0 && n1->getClosingNode() == n2)) + { + extractAndDeleteNode(node, modifs, false, false, false); + nbEmptyNodes--; + node = n1->next; + } + if(nbEmptyNodes == 1) + emptyNode = n1->next; + + if(n1->getClosingNode() == n2 && n1->child && n1->child->tag->type == Tag::Empty) + emptyNode = n1->child; + } + + //adding/deleting a empty node if necessary + parent = n1->parent; + if(!parent || getNodeDisplay(parent, true) == kafkaCommon::blockDisplay) + { + if(getNodeDisplay(n1, true) == kafkaCommon::blockDisplay && + n1->tag->type != Tag::Text) + { + if(n2->tag->type == Tag::Text && emptyNode) + { + extractAndDeleteNode(emptyNode, modifs, false, false, false); + } + else if(n2->tag->type != Tag::Text && !emptyNode) + { + if(n1->getClosingNode() == n2) + { + createAndInsertNode("", "", Tag::Empty, n2->tag->write(), n1, 0L, 0L, modifs); + } + else + { + createAndInsertNode("", "", Tag::Empty, n2->tag->write(), parent, n2, n2, modifs); + } + } + } + else + { + if((n2->tag->type == Tag::Text || + getNodeDisplay(n2, true) == kafkaCommon::inlineDisplay) && + emptyNode) + { + extractAndDeleteNode(emptyNode, modifs, false, false, false); + } + else if(n2->tag->type != Tag::Text && + getNodeDisplay(n2, true) == kafkaCommon::blockDisplay && + n1->tag->type != Tag::Text && !emptyNode) + { + if(n1->getClosingNode() == n2) + { + createAndInsertNode("", "", Tag::Empty, n2->tag->write(), n1, 0L, 0L, modifs); + } + else + { + createAndInsertNode("", "", Tag::Empty, n2->tag->write(), parent, n2, n2, modifs); + } + } + } + } + else + { + if(emptyNode) + extractAndDeleteNode(emptyNode, modifs, false, false, false); + } + } +} + +void kafkaCommon::fitsNodesPosition(Node* startNode, int colMovement, int lineMovement, int colEnd, int lineEnd) +{ + bool b = false; + int j, SNbeginLine, SNbeginCol/**, SNlastLine, SNlastCol*/; + int beginLine, beginCol, lastLine, lastCol; + Node *node = startNode; + + if(!node) + return; + + node->tag->beginPos(SNbeginLine, SNbeginCol); + //node->tag->endPos(SNlastLine, SNlastCol); + + while(node) + { + node->tag->beginPos(beginLine, beginCol); + node->tag->endPos(lastLine, lastCol); + if(beginLine >= lineEnd && beginCol >= colEnd && + colEnd != -2 && lineEnd != -2) + return; + if(beginLine == SNbeginLine && lastLine == SNbeginLine) + node->tag->setTagPosition(beginLine + lineMovement, + beginCol + colMovement, lastLine + lineMovement, + lastCol + colMovement); + else if(beginLine == SNbeginLine)//&&lastLine != SNbeginLine + node->tag->setTagPosition(beginLine + lineMovement, + beginCol + colMovement, lastLine + lineMovement, + lastCol); + else + node->tag->setTagPosition(beginLine + lineMovement, + beginCol, lastLine + lineMovement, lastCol); + for(j = 0; j < node->tag->attrCount(); ++j) + { + if(node->tag->getAttribute(j).nameLine == SNbeginLine) + { + node->tag->getAttribute(j).nameLine += lineMovement; + node->tag->getAttribute(j).nameCol += colMovement; + node->tag->getAttribute(j).valueLine += lineMovement; + node->tag->getAttribute(j).valueCol += colMovement; + } + else + { + node->tag->getAttribute(j).nameLine += lineMovement; + node->tag->getAttribute(j).valueLine += lineMovement; + } + } + node = getNextNode(node, b); + } +} + +int kafkaCommon::getNodeDisplay(Node *node, bool closingNodeToo) +{ + QString nodeName; + + if(!node) + return kafkaCommon::errorDisplay; + + if(node->tag->type == Tag::Text) + return kafkaCommon::inlineDisplay; + else if(node->tag->type == Tag::XmlTag || (node->tag->type == Tag::XmlTagEnd && + closingNodeToo)) + { + //If we areusing a non (X)HTML DTD, make everything blockDisplay by default + if(node->tag->dtd() && node->tag->dtd()->name.contains("HTML", false) == 0) + return kafkaCommon::blockDisplay; + + nodeName = node->tag->name.lower(); + if(closingNodeToo && nodeName.startsWith("/")) + nodeName = nodeName.mid(1); + if(nodeName == "html" || nodeName == "head" || nodeName == "meta" || + nodeName == "link" || nodeName == "style" || nodeName == "option" || + nodeName == "optgroup" || nodeName == "area" || nodeName == "param" || + nodeName == "thead" || nodeName == "tbody" || nodeName == "dt" || + nodeName == "tfoot" || nodeName == "col" || nodeName == "colgroup" || + nodeName == "tr" || nodeName == "td" || nodeName == "th" || nodeName == "caption" || + nodeName == "ins" || nodeName == "legend") + //Ok right, but this is only for indentation... + //return kafkaCommon::noneDisplay; + return kafkaCommon::blockDisplay; + else if(nodeName == "body" || nodeName == "p" || nodeName == "div" || + nodeName == "blockquote" || nodeName == "isindex" || + nodeName == "center" || nodeName == "hr" || nodeName == "h1" || + nodeName == "h2" || nodeName == "h3" || nodeName == "h4" || nodeName == "h5" || + nodeName == "h6" || nodeName == "table" || + nodeName == "ul" || nodeName == "menu" || nodeName == "dir" || nodeName == "ol" || + nodeName == "li" || nodeName == "ul" || nodeName == "dd" || nodeName == "dl" || + nodeName == "form" || nodeName == "fieldset" || + nodeName == "pre" || nodeName == "noscript" || nodeName == "noframes" || + nodeName == "frameset" || nodeName == "frame" || + nodeName == "address" || nodeName == "del" || nodeName == "br") + return kafkaCommon::blockDisplay; + else if(nodeName == "q" || nodeName == "u" || nodeName == "i" || nodeName == "b" || + nodeName == "cite" || nodeName == "em" || nodeName == "var" || nodeName == "em" || + nodeName == "tt" || nodeName == "code" || nodeName == "kbd" || nodeName == "samp" || + nodeName == "big" || nodeName == "small" || nodeName == "s" || nodeName == "strike" || + nodeName == "sub" || nodeName == "sup" || nodeName == "abbr" || nodeName == "title" || + nodeName == "acronym" || nodeName == "a" || nodeName == "bdo" || + nodeName == "font" || nodeName == "#text" || nodeName == "strong" || nodeName == "dfn" || + nodeName == "img" || nodeName == "applet" || nodeName == "object" || nodeName == "basefont" || nodeName == "script" || nodeName == "map" || nodeName == "span" || + nodeName == "iframe" || nodeName == "input" || nodeName == "select" || nodeName == "textarea" || + nodeName == "label" || nodeName == "button" ) + return kafkaCommon::inlineDisplay; + else + { +#ifdef LIGHT_DEBUG + kdDebug(25001)<< "kafkaCommon::getNodeType() - ERROR " << nodeName << + " not found" << endl; +#endif + + return kafkaCommon::noneDisplay; + } + } + return kafkaCommon::errorDisplay; +} + +QString kafkaCommon::removeUnnecessaryWhitespaces(const QString &string, + bool removeAllSpacesAtTheLeft, bool removeAllSpacesAtTheRight) +{ + /**QString newString; + int i; + + if(string.length() == 0) + return ""; + + newString = string[0]; + for(i = 1; (unsigned)i < string.length(); ++i) + { + if(!string[i - 1].isSpace() || !string[i].isSpace()) + newString += string[i]; + } + + if(removeAllSpacesAtTheLeft && newString.length() > 0 && newString[0].isSpace()) + newString = newString.mid(1); + if(removeAllSpacesAtTheRight && newString.length() > 0 && + newString[newString.length() - 1].isSpace()) + newString = newString.mid(0, newString.length() - 1); + + return newString;*/ + QString newString; + bool hasLeftWhiteSpaces, hasRightWhiteSpaces; + + if(string.length() == 0) + return QString(); + + hasLeftWhiteSpaces = (string[0].isSpace()); + hasRightWhiteSpaces = (string[string.length() - 1].isSpace()); + + newString = string.stripWhiteSpace(); + if(hasLeftWhiteSpaces && !removeAllSpacesAtTheLeft) + newString.insert(0, " "); + if(hasRightWhiteSpaces && !removeAllSpacesAtTheRight) + newString.insert(newString.length(), " "); + + return newString; +} + +Node* kafkaCommon::createNode(const QString &nodeName, const QString &tagString, int nodeType, Document *doc) +{ + Node *node; + + //create the Node. + node = new Node(0L); + + //Create the corresponding Tag. + node->tag = new Tag(); + if(doc) + node->tag->setDtd(doc->defaultDTD()); + else + node->tag->setDtd(0L); + node->tag->setWrite(doc); + node->tag->type = nodeType; + node->tag->name = QuantaCommon::tagCase(nodeName); + if(doc) + node->tag->single = QuantaCommon::isSingleTag(doc->defaultDTD()->name, nodeName); + else + node->tag->single = false; + node->tag->setStr(tagString); + node->tag->setCleanStrBuilt(false); + node->tag->setIndentationDone(false); + return node; +} + +void kafkaCommon::restorePastedNode(Node* node, Document* doc) +{ + if(doc) + node->tag->setDtd(doc->defaultDTD()); + else + node->tag->setDtd(0L); + + node->tag->setWrite(doc); + +} + +Node *kafkaCommon::createDoctypeNode(Document *doc) +{ + Node *node, *child, *closingNode; + + if(!doc) + return 0L; + + //Build the script Tag + node = kafkaCommon::createNode("DTD block", "", Tag::ScriptTag, doc); + closingNode = kafkaCommon::createNode("", "", Tag::XmlTagEnd, doc); + node->next = closingNode; + closingNode->prev = node; + + //Then build the Script tag which will be child of the above node. + child = kafkaCommon::createNode("#text", "DOCTYPE" + doc->defaultDTD()->doctypeStr, Tag::Text, doc); + child->tag->setCleanStrBuilt(true); + child->insideSpecial = true; + insertNode(child, node, 0L, 0L, false); + + return node; +} + +Node *kafkaCommon::createXmlDeclarationNode(Document *doc, const QString &encoding) +{ + Node *node, *child, *closingNode; + QString text; + + if(!doc) + return 0L; + + //build the script Tag + node = kafkaCommon::createNode("XML PI block" ,"", Tag::ScriptTag, doc); + closingNode = kafkaCommon::createNode("", "", Tag::XmlTagEnd, doc); + node->next = closingNode; + closingNode->prev = node; + + //Then build the Text tag which will be child of the above node. + text = " encoding=\"" + encoding + "\" version=\"1.0\""; + child = kafkaCommon::createNode("#text", text, Tag::Text, doc); + child->tag->setCleanStrBuilt(true); + child->insideSpecial = true; + insertNode(child, node, 0L, 0L, false); + + return node; +} + +Node* kafkaCommon::createMandatoryNodeSubtree(Node *node, Document *doc) +{ + QTag *nodeQTag, *oldNodeQTag; + bool searchForMandatoryNode; + Node *currentParent; + QMap<QString, bool>::iterator it; + + if(!node) + return 0L; + + nodeQTag = QuantaCommon::tagFromDTD(node); + if(!nodeQTag) + return false; + + searchForMandatoryNode = true; + currentParent = node; + while(searchForMandatoryNode) + { + oldNodeQTag = nodeQTag; + for(it = nodeQTag->childTags.begin(); it != nodeQTag->childTags.end(); ++it) + { + if(it.data()) + { + nodeQTag = QuantaCommon::tagFromDTD(nodeQTag->parentDTD, it.key()); + if(!nodeQTag) + return node; + currentParent = createAndInsertNode(nodeQTag->name(), "", Tag::XmlTag, doc, + currentParent, 0L, 0L, (NodeModifsSet*)0L); + break; + } + } + if(oldNodeQTag == nodeQTag) + searchForMandatoryNode = false; + } + + return currentParent; +} + +Node* kafkaCommon::insertNode(Node *node, Node* parentNode, Node* nextSibling, + NodeModifsSet *modifs, bool merge) +{ +#ifdef LIGHT_DEBUG + kdDebug(25001)<< "kafkaCommon::insertNode()" << endl; +#endif + + NodeModif* modif; + Node *n, *closingNode; + bool nodeIsFirstChild = false, b; + + if(!node) + return 0L; + + //Reset the listviews items pointers for node and its children + n = node; + b = false; + while(n) + { + /**node->mainListItem = 0L; + node->listItems.clear(); + node->groupElementLists.clear();*/ + n = getNextNode(n, b); + } + + //place the new Node. + if(parentNode) + n = parentNode->child; + else + n = baseNode; + while(n && n->next) + n = n->next; + + if(!parentNode && (!baseNode || (nextSibling && !nextSibling->prev))) + { + nodeIsFirstChild = true; + baseNode = node; + parser->setRootNode(baseNode); + } + if(parentNode && (!parentNode->child || nextSibling == parentNode->child)) + { + nodeIsFirstChild = true; + parentNode->child = node; + } + node->parent = parentNode; + + if(nextSibling && nextSibling->prev) + { + nextSibling->prev->next = node; + node->prev = nextSibling->prev; + } + else if(n && !nodeIsFirstChild) + { + n->next = node; + node->prev = n; + } + + if(nextSibling) + nextSibling->prev = node; + node->next = nextSibling; + + //log this. + if(modifs) + { + modif = new NodeModif(); + if(node->child) + modif->setType(NodeModif::NodeAndChildsAdded); + else + modif->setType(NodeModif::NodeAdded); + modif->setLocation(getLocation(node)); + modifs->addNodeModif(modif); + } + + //Then try to merge with the siblings + if(merge) + { + if(node->prev) + { + n = node->prev; + if(mergeNodes(node->prev, node, modifs)) + node = n; + } + if(node->next) + { + mergeNodes(node, node->next, modifs); + } + } + + //update the closesPrevious switch + closingNode = node->getClosingNode(); + if(closingNode) + closingNode->closesPrevious = true; + +#ifdef HEAVY_DEBUG + + coutTree(baseNode, 2); +#endif + + return node; +} + +Node* kafkaCommon::insertNode(Node *node, Node* parentNode, Node* nextSibling, NodeSelection& cursorHolder, + NodeModifsSet *modifs, bool merge) +{ +#ifdef LIGHT_DEBUG + kdDebug(25001)<< "kafkaCommon::insertNode()" << endl; +#endif + + NodeModif* modif; + Node *n, *closingNode; + bool nodeIsFirstChild = false, b; + + if(!node) + return 0L; + + //Reset the listviews items pointers for node and its children + n = node; + b = false; + while(n) + { + /**node->mainListItem = 0L; + node->listItems.clear(); + node->groupElementLists.clear();*/ + n = getNextNode(n, b); + } + + //place the new Node. + if(parentNode) + n = parentNode->child; + else + n = baseNode; + while(n && n->next) + n = n->next; + + if(!parentNode && (!baseNode || (nextSibling && !nextSibling->prev))) + { + nodeIsFirstChild = true; + baseNode = node; + parser->setRootNode(baseNode); + } + if(parentNode && (!parentNode->child || nextSibling == parentNode->child)) + { + nodeIsFirstChild = true; + parentNode->child = node; + } + node->parent = parentNode; + + if(nextSibling && nextSibling->prev) + { + nextSibling->prev->next = node; + node->prev = nextSibling->prev; + } + else if(n && !nodeIsFirstChild) + { + n->next = node; + node->prev = n; + } + + if(nextSibling) + nextSibling->prev = node; + node->next = nextSibling; + + //log this. + if(modifs) + { + modif = new NodeModif(); + if(node->child) + modif->setType(NodeModif::NodeAndChildsAdded); + else + modif->setType(NodeModif::NodeAdded); + modif->setLocation(getLocation(node)); + modifs->addNodeModif(modif); + } + + //Then try to merge with the siblings + if(merge) + { + if(node->prev) + { + n = node->prev; + if(mergeNodes(node->prev, node, cursorHolder, modifs)) + node = n; + } + if(node->next) + { + mergeNodes(node, node->next, cursorHolder, modifs); + } + } + + //update the closesPrevious switch + closingNode = node->getClosingNode(); + if(closingNode) + closingNode->closesPrevious = true; + +#ifdef HEAVY_DEBUG + + coutTree(baseNode, 2); +#endif + + return node; +} + +Node *kafkaCommon::insertNode(Node *newNode, Node *parent, Node *nextSibling, Node *nextEndSibling, + NodeModifsSet *modifs, bool merge) +{ +#ifdef LIGHT_DEBUG + kdDebug(25001)<< "kafkaCommon::insertNode()1" << endl; +#endif + + Node *n, *nodeEnd = 0; + + if(!newNode) + return 0L; + + //place the new Node. + newNode = insertNode(newNode, parent, nextSibling, modifs, merge); + + if(!newNode->tag->single && newNode->tag->type == Tag::XmlTag) + { + //create the new closing Node. + nodeEnd = createNode("/" + newNode->tag->name, "", Tag::XmlTagEnd, newNode->tag->write()); + nodeEnd->closesPrevious = true; + + //place the new closing Node. + nodeEnd = insertNode(nodeEnd, parent, nextEndSibling, modifs, merge); + } + + //If nextSibling != nextEndSibling, move all Nodes between node and nodeEnd as child of node + if(nextSibling != nextEndSibling) + { + n = newNode->next; + while(newNode->next && newNode->next != nodeEnd) + moveNode(newNode->next, newNode, 0L, modifs); + } + + return newNode; +} + +Node* kafkaCommon::insertNode(Node *newNode, Node *parent, Node *startNodeToSurround, + Node *endNodeToSurround, int startOffset, int endOffset, NodeModifsSet *modifs) +{ +#ifdef LIGHT_DEBUG + kdDebug(25001)<< "kafkaCommon::insertNode()2" << endl; +#endif + + if(!newNode || !startNodeToSurround || !endNodeToSurround) + return 0L; + + //first split the Nodes. + if(splitNode(startNodeToSurround, startOffset, modifs)) + { + if(endNodeToSurround == startNodeToSurround) + { + endNodeToSurround = endNodeToSurround->next; + endOffset -= startOffset; + } + startNodeToSurround = startNodeToSurround->next; + } + if(splitNode(endNodeToSurround, endOffset, modifs)) + endNodeToSurround = endNodeToSurround->next; + + //Then create and insert the new Node. + return insertNode(newNode, parent, startNodeToSurround, + endNodeToSurround, modifs); +} + +Node* kafkaCommon::insertNodeSubtree(Node *node, Node* parentNode, Node* nextSibling, + NodeModifsSet *modifs, bool merge) +{ + Node *nextNode, *currentNode; + + if(!node || (node && node->prev)) + return 0L; + + //insert the node subtree + currentNode = node; + while(currentNode) + { + nextNode = currentNode->next; + if(currentNode == node) + node = insertNode(currentNode, parentNode, nextSibling, nextSibling, modifs, merge); + else + insertNode(currentNode, parentNode, nextSibling, nextSibling, modifs, merge); + + currentNode = nextNode; + } + + return node; +} + +Node* kafkaCommon::insertNodeSubtree(Node *node, Node* parentNode, Node* nextSibling, + Node* nextEndSibling, NodeModifsSet *modifs, bool merge) +{ + Node *nextNode, *currentNode, *currentParent; + + if(!node || (node && node->prev)) + return 0L; + + //insert the node subtree. + currentNode = node; + currentParent = parentNode; + while(currentNode) + { + nextNode = currentNode->child; + currentNode->child = 0L; + + //If the closing tag of currentNode is present, let's delete it + if(currentNode->next && QuantaCommon::closesTag(currentNode->tag, currentNode->next->tag)) + delete extractNode(currentNode->next, 0L); + + //insert the node and its closing tag if necessary. + if(currentNode == node) + { + currentParent = insertNode(currentNode, currentParent, nextSibling, + nextEndSibling, modifs, merge); + node = currentParent; + } + else + currentParent = insertNode(currentNode, currentParent, nextSibling, + 0L, modifs, merge); + + currentNode = nextNode; + } + + return node; +} + +Node* kafkaCommon::DTDInsertNodeSubtree(Node *newNode, NodeSelectionInd& selection, + Node **cursorNode, long& cursorOffset, NodeModifsSet *modifs) +{ + Q_ASSERT(!selection.hasSelection()); + + Node* startNode = 0; + if(!(*cursorNode)) // see KafkaDocument::slotPaste() + startNode = getNodeFromLocation(selection.cursorNode()); + else + startNode = *cursorNode; + + if(!startNode) + { + kdError() << "NULL startNode in kafkaCommon::DTDInsertNodeSubtree given by NodeSelectionInd::cursorNode()" << endl; + return 0; + } + + Node* endNode = 0; + if(!cursorNode) + return 0; + //int startOffset = selection.cursorOffset(); + int startOffset = cursorOffset; + + /** + * TODO : Optionnal for the moment : move the cursor coordinates so that we have good locations. + * e.g. <b>boo|</b>baa should be translated to <b>boo</b>|baa + */ + + if(cursorOffset == (signed)startNode->tag->tagStr().length()) + { + while(startNode && startNode->tag->type != Tag::Text) + startNode = startNode->nextSibling(); + if(!startNode) + { + insertNodeSubtree(newNode, baseNode->child, 0, modifs, true); + return newNode; + } + else + cursorOffset = 0; + } + + // look for commonParent + QValueList<int> commonParentStartChildLocation; + QValueList<int> commonParentEndChildLocation; + + Node* commonParent = DTDGetNonInlineCommonParent(startNode, startNode, + commonParentStartChildLocation, commonParentEndChildLocation, 0); + + Node* commonParentStartChild = getNodeFromLocation(commonParentStartChildLocation); + + //OK now, we are sure the node can be inserted. Start the work by splitting + //startNode if necessary + if(cursorOffset != 0) + { + if(startNode->tag->type == Tag::Text || startNode->tag->type == Tag::Empty) + { + if(splitNode(startNode, startOffset, modifs)) + { + //</TEMPORARY> + if(startNode == commonParentStartChild) + commonParentStartChild = commonParentStartChild->nextSibling(); + endNode = startNode->nextSibling(); + } + else if(startOffset == (signed)startNode->tag->tagStr().length()) + { + //No need to update endNode. If endNode == startNode && startOffset == endOffset, + //we'll catch this later. + if(startNode == commonParentStartChild) + commonParentStartChild = commonParentStartChild->nextSibling(); + startNode = startNode->nextSibling(); + } + } + } + + if(newNode->tag->type == Tag::Text || newNode->tag->type == Tag::Empty) + { + *cursorNode = newNode; + cursorOffset = newNode->tag->tagStr().length(); + return insertNodeSubtree(newNode, startNode->parent, endNode, modifs); + } + + //Then we "split" the lastValidStartParent - startNode subtree into two : the first part is untouched + // and the second will be surrounded by the new Node. Same thing for endNode. + Node* node = startNode; + Node* parentNode = startNode->parent; + Node* newParentNode = 0, *child = 0, *next = 0; + while(parentNode && commonParent && parentNode != commonParent) + { + if(true/*node != parentNode->firstChild()*/) + { + //node is not the first Child of parentNode, we have to duplicate parentNode, and put node and + //all its next sibling as child of the new parentNode. + /**newParentNode = insertNode(parentNode->tag->name, parentNode->tag->tagStr(), + parentNode->tag->type, parentNode->tag->write(), parentNode->parentNode(), + parentNode, parentNode, modifs);*/ + newParentNode = duplicateNode(parentNode); + insertNode(newParentNode, parentNode->parentNode(), parentNode, parentNode, modifs); + child = parentNode->firstChild(); + if(cursorOffset != 0) + { + while(child && (child != endNode) && !child->hasForChild(endNode)) + { + next = child->next; + moveNode(child, newParentNode, 0L, modifs); + child = next; + } + } + else + { + while(child) + { + next = child->next; + moveNode(child, newParentNode, 0L, modifs, true, true); + if(child == startNode || child->hasForChild(startNode)) + break; + + child = next; + } + } + } + //commonParentStartChild = parentNode; + node = parentNode; + parentNode = parentNode->parent; + } + + if(endNode) + { + node = endNode; + parentNode = endNode->parent; + while(parentNode && commonParent && parentNode != commonParent) + { + if(true/*node != parentNode->firstChild()*/) + { + //node is not the first Child of parentNode, we have to duplicate parentNode, and put node and + //all its next sibling as child of the new parentNode. + /**newParentNode = insertNode(parentNode->tag->name, parentNode->tag->tagStr(), + parentNode->tag->type, parentNode->tag->write(), parentNode->parentNode(), + parentNode, parentNode, modifs);*/ + newParentNode = duplicateNode(parentNode); + insertNode(newParentNode, parentNode->parentNode(), parentNode, parentNode, modifs); + child = parentNode->firstChild(); + while(child /*&& child == endNode*/ && + (child == endNode || child->hasForChild(endNode)/* || + (child->prev && child->prev->hasForChild(endNode) && child->closesPrevious)*/)) + { + next = child->next; + moveNode(child, newParentNode, 0L, modifs, true, true); + child = next; + } + } + commonParentStartChild = newParentNode; + node = parentNode; + Node* aux = parentNode; + parentNode = parentNode->parent; + // Remove node subtree if empty + if(!aux->hasChildNodes()) + extractAndDeleteNode(aux, modifs); + } + } + if(newNode->next && QuantaCommon::closesTag(newNode->tag, newNode->next->tag)) + delete extractNode(newNode->next, 0L); + + Node* nextSibling = commonParentStartChild; + /* + if(cursorOffset == 0) + nextSibling = nextSibling->SNext(); + */ + return insertNodeSubtree(newNode, commonParent, nextSibling/*, nextSibling*/, modifs); + + //mergeInlineNode(commonParent, commonParent->next, cursorNode, cursorOffset, modifs); + //return newNode; +} + +Node* kafkaCommon::DTDInsertNodeSubtree(Node* newNode, Node* parentNode, Node* nextSibling, + NodeSelection& /*cursorHolder*/, NodeModifsSet *modifs) +{ + QTag* nodeQTag = QuantaCommon::tagFromDTD(parentNode); + if(!nodeQTag || !nodeQTag->isChild(newNode)) + return 0; + else + return insertNodeSubtree(newNode, parentNode, nextSibling, modifs); +} + +bool kafkaCommon::DTDinsertNode(Node *newNode, Node *startNode, int startOffset, Node *endNode, + int endOffset, Document *doc, Node **cursorNode, long &cursorOffset, NodeModifsSet *modifs) +{ +#ifdef LIGHT_DEBUG + kdDebug(25001)<< "kafkaCommon::DTDinsertNode()" << endl; +#endif + + QValueList<int> startNodeLocation, endNodeLocation; + QValueList<int>::iterator itStart, itEnd; + Node *commonParent = 0L, *commonParentStartChild, *commonParentEndChild, *parentNode, *node; + Node *lastValidStartParent = 0L, *lastValidEndParent = 0L, *newParentNode, *child, *next; + Node *oldCommonParent, *lastNewNode, *oldParentNode; + QTag *parentNodeQTag = 0, *newNodeQTag, *lastNewNodeQTag; + NodeModif modif; + int locOffset = 1; + bool newNodeIsInline, isAfter; + + if(!startNode || !endNode || !newNode || !doc) + { + Node::deleteNode(newNode); + return false; + } + + //FIrst get the mandatory Nodes if necessary, and get the qTag of the first and last Node. + lastNewNode = createMandatoryNodeSubtree(newNode, doc); + lastNewNodeQTag = QuantaCommon::tagFromDTD(lastNewNode); + newNodeQTag = QuantaCommon::tagFromDTD(newNode); + if(!newNodeQTag || !lastNewNodeQTag) + { + Node::deleteNode(newNode); + return false; + } + + //Then search for the common parent of startNode and endNode (commonParent) + //and for the childs of commonParent which are parent of startNode and endNode + //(commonParentStartChild && commonParentEndChild) + //CommonParent will be the limit (startNode -- commonNode) where Nodes can + //be splitted in order to insert the newNode. + startNodeLocation = getLocation(startNode); + endNodeLocation = getLocation(endNode); + itStart = startNodeLocation.begin(); + itEnd = endNodeLocation.begin(); + while(itStart != startNodeLocation.end() && itEnd != endNodeLocation.end() && + (*itStart) == (*itEnd)) + { + commonParent = getNodeFromSubLocation(startNodeLocation, locOffset); + itStart++; + itEnd++; + locOffset++; + } + //look for commonParentStartChild and commonParentEndChild + if(itStart != startNodeLocation.end()) + commonParentStartChild = getNodeFromSubLocation(startNodeLocation, locOffset); + else + commonParentStartChild = commonParent; + if(itEnd != endNodeLocation.end()) + commonParentEndChild = getNodeFromSubLocation(endNodeLocation, locOffset); + else + commonParentEndChild = commonParent; + //If newNode isn't inline, move commonParent to the closest non inline node + newNodeIsInline = isInline(newNode->tag->name); + if(!newNodeIsInline && commonParent && (isInline(commonParent->tag->name) || + commonParent->tag->type == Tag::Text || commonParent->tag->type == Tag::Empty)) + { + oldCommonParent = commonParent; + commonParent = commonParent->parent; + while(commonParent && isInline(commonParent->tag->name)) + { + oldCommonParent = commonParent; + commonParent = commonParent->parent; + } + commonParentStartChild = oldCommonParent; + commonParentEndChild = oldCommonParent; + } + //startNode or endNode can't be the commonParent. + else if(commonParent && (itStart == startNodeLocation.end() || itEnd == endNodeLocation.end())) + commonParent = commonParent->parent; + + //Now look if at least one of the parent Nodes between startNode and commonParent + //can have nodeName as child. If so for startNode and endNode, let's find the last + //parent Nodes which can have nodeName as child. + parentNode = startNode->parent; + oldParentNode = startNode; + while(parentNode && commonParent && parentNode != commonParent->parent) + { + parentNodeQTag = QuantaCommon::tagFromDTD(parentNode); + if(parentNodeQTag && parentNodeQTag->isChild(newNode) && + lastNewNodeQTag->isChild(oldParentNode)) + lastValidStartParent = parentNode; + else if(newNodeIsInline || !isInline(parentNode->tag->name)) + break; + //else if(!newNodeIsInline && isInline(parentNode)), we continue : BLOCK element can + //cut some inline tag in order to be inserted. + oldParentNode = parentNode; + parentNode = parentNode->parent; + } + parentNode = endNode->parent; + oldParentNode = endNode; + while(parentNode && commonParent && parentNode != commonParent->parent) + { + parentNodeQTag = QuantaCommon::tagFromDTD(parentNode); + if(parentNodeQTag && parentNodeQTag->isChild(newNode) && + lastNewNodeQTag->isChild(oldParentNode)) + lastValidEndParent = parentNode; + else if(newNodeIsInline || !isInline(parentNode->tag->name)) + break; + //else if(!newNodeIsInline && isInline(parentNode)), we continue : BLOCK element can + //cut some inline tag in order to be inserted. + oldParentNode = parentNode; + parentNode = parentNode->parent; + } + + /**if(!lastValidEndParent || !lastValidStartParent) + { + Node::deleteNode(newNode); + return false; + }*/ + + //OK now, we are sure the node can be inserted. Start the work by splitting + //startNode and endNode if necessary + if(startNode->tag->type == Tag::Text || startNode->tag->type == Tag::Empty) + { + if(splitNode(startNode, startOffset, modifs)) + { + //<TEMPORARY> + if(startNode == (*cursorNode) && cursorOffset > startOffset) + { + (*cursorNode) = (*cursorNode)->nextSibling(); + cursorOffset -= startOffset; + } + //</TEMPORARY> + if(startNode == commonParentStartChild) + commonParentStartChild = commonParentStartChild->nextSibling(); + if(startNode == endNode) + { + endNode = endNode->nextSibling(); + endOffset -= startOffset; + } + startNode = startNode->nextSibling(); + startOffset = 0; + } + else if(startOffset == (signed)startNode->tag->tagStr().length()) + { + //No need to update endNode. If endNode == startNode && startOffset == endOffset, + //we'll catch this later. + if(startNode == commonParentStartChild) + commonParentStartChild = commonParentStartChild->nextSibling(); + startNode = startNode->nextSibling(); + } + } + if(endNode->tag->type == Tag::Text || endNode->tag->type == Tag::Empty) + { + if(!splitNode(endNode, endOffset, modifs) && endOffset == 0) + { + //No need to update startNode. If startNode == endNode && startOffset == endOffset, + //we'll catch this later. + if(endNode == commonParentEndChild) + commonParentEndChild = commonParentEndChild->previousSibling(); + if (endNode->previousSibling()) + endNode = endNode->previousSibling(); + } + } + + //Then we "split" the lastValidStartParent - startNode subtree into two : the first part is untouched + // and the second will be surrounded by the new Node. Same thing for endNode. + node = startNode; + if (!startNode) //Andras: it can happen. + return false; + parentNode = startNode->parent; + while(lastValidStartParent && parentNode && parentNode != lastValidStartParent) + { + if(node != parentNode->firstChild()) + { + //node is not the first Child of parentNode, we have to duplicate parentNode, and put node and + //all its next sibling as child of the new parentNode. + /**newParentNode = insertNode(parentNode->tag->name, parentNode->tag->tagStr(), + parentNode->tag->type, parentNode->tag->write(), parentNode->parentNode(), + parentNode, parentNode, modifs);*/ + newParentNode = duplicateNode(parentNode); + insertNode(newParentNode, parentNode->parentNode(), parentNode, parentNode, modifs); + child = parentNode->firstChild(); + while(child && child != startNode && !child->hasForChild(startNode)) + { + next = child->next; + moveNode(child, newParentNode, 0L, modifs); + child = next; + } + } + node = parentNode; + parentNode = parentNode->parent; + } + node = endNode; + parentNode = endNode->parent; + while(lastValidEndParent && parentNode && parentNode != lastValidEndParent) + { + if(node != parentNode->lastChild()) + { + //node is not the last Child of parentNode, we have to duplicate parentNode, and put all + //the next sibling of node as child of the new parentNode + /**newParentNode = insertNode(parentNode->tag->name, parentNode->tag->tagStr(), + parentNode->tag->type, parentNode->tag->write(), parentNode->parentNode(), + parentNode, parentNode, modifs);*/ + newParentNode = duplicateNode(parentNode); + insertNode(newParentNode, parentNode->parentNode(), parentNode, parentNode, modifs); + if(parentNode == commonParentStartChild) + commonParentStartChild = newParentNode; + if(parentNode == commonParentEndChild) + commonParentEndChild = newParentNode; + child = parentNode->firstChild(); + while(child) + { + next = child->next; + moveNode(child, newParentNode, 0L, modifs); + if(child == endNode || child->hasForChild(endNode)) + { + if(QuantaCommon::closesTag(child->tag, next->tag)) + moveNode(next, newParentNode, 0L, modifs); + break; + } + child = next; + } + } + node = parentNode; + parentNode = parentNode->parent; + } + + //Now if startNode is after endNode, this means that a selectionless insertion is being done. + //(This is due to the text splitting) + //Let's insert it and return + isAfter = (compareNodePosition(startNode, endNode) == kafkaCommon::isAfter); + if(isAfter || (startNode == endNode && startOffset == endOffset && + (signed)startNode->tag->tagStr().length() == startOffset)) + { + if(isAfter) + parentNodeQTag = QuantaCommon::tagFromDTD(commonParent); + else if((signed)startNode->tag->tagStr().length() == startOffset && startNode->tag->type == Tag::XmlTag) + parentNodeQTag = QuantaCommon::tagFromDTD(startNode); + else if((signed)startNode->tag->tagStr().length() == startOffset && startNode->tag->type == Tag::XmlTagEnd) + parentNodeQTag = QuantaCommon::tagFromDTD(startNode->parent); + if(!parentNodeQTag || (parentNodeQTag && parentNodeQTag->isChild(newNode))) + { + if(isAfter) + insertNodeSubtree(newNode, commonParent, commonParentStartChild, modifs); + else if((signed)startNode->tag->tagStr().length() == startOffset && startNode->tag->type == Tag::XmlTag) + insertNodeSubtree(newNode, startNode, 0L, modifs); + else if((signed)startNode->tag->tagStr().length() == startOffset && startNode->tag->type == Tag::XmlTagEnd) + insertNodeSubtree(newNode, startNode->parent, startNode->next, modifs); + //<TEMPORARY> + (*cursorNode) = lastNewNode; + cursorOffset = 0; + //</TEMPORARY> + return true; + } + else + { + Node::deleteNode(newNode); + return false; + } + } + else + { + //Else we apply the recursive function to add the new Node when necessary/possible. + bool addingStarted = false; + bool examinationStarted = false; + bool nodeInserted = false; + int level = 0; + addNodeRecursively(newNode, lastNewNode, + (compareNodePosition(lastValidStartParent, commonParentStartChild) == + kafkaCommon::isAfter)?lastValidStartParent:commonParentStartChild, + (compareNodePosition(lastValidEndParent, commonParentEndChild) == + kafkaCommon::isAfter)?lastValidEndParent:commonParentEndChild, + startNode, endNode, commonParentStartChild, examinationStarted, + addingStarted, nodeInserted, level, modifs); + + //And we merge if necessary some identical inline Nodes. + mergeInlineNode(startNode, endNode, cursorNode, cursorOffset, modifs); + return nodeInserted; + } +} + +bool kafkaCommon::DTDinsertRemoveNode(Node *newNode, Node *startNode, int startOffset, + Node *endNode, int endOffset, Document *doc, Node **cursorNode, long &cursorOffset, + NodeModifsSet *modifs) +{ + int result; + + if(!newNode || !startNode || !endNode || !doc) + return false; + + //First try to remove the Nodes. If unsuccessfull, try to insert it. + result = DTDExtractNode(newNode->tag->name, doc, startNode, startOffset, endNode, endOffset, + cursorNode, cursorOffset, modifs); + if(result == kafkaCommon::nothingExtracted || result == kafkaCommon::extractionBadParameters) + { + return DTDinsertNode(newNode, startNode, startOffset, endNode, endOffset, doc, cursorNode, + cursorOffset, modifs); + } + else + return true; + //else if result == kafkaCommon::extractionStoppedDueToBadNodes, + //what should we do? +} + +Node *kafkaCommon::createAndInsertNode(const QString &nodeName, const QString &tagString, + int nodeType, Document *doc, Node* parent, Node* nextSibling, NodeModifsSet *modifs, + bool merge) +{ +#ifdef LIGHT_DEBUG + kdDebug(25001)<< "kafkaCommon::createAndInsertNode() - nodeName :" << nodeName << + " - tagStr :" << tagString << " - nodeType :" << nodeType << endl; +#endif + + Node *node; + + //create the new Node. + node = createNode(nodeName, tagString, nodeType, doc); + + //insert the new Node. + insertNode(node, parent, nextSibling, modifs, merge); + + return node; +} + +Node *kafkaCommon::createAndInsertNode(const QString &nodeName, const QString &tagString, + int nodeType, Document *doc, Node *parent, Node *nextSibling, Node *nextEndSibling, + NodeModifsSet *modifs) +{ +#ifdef LIGHT_DEBUG + kdDebug(25001)<< "kafkaCommon::createAndInsertNode()2- nodeName :" << nodeName << + " - tagStr :" << tagString << " - nodeType :" << nodeType << endl; +#endif + + Node *node; + + //create the new Node. + node = createNode(nodeName, tagString, nodeType, doc); + + //insert the new Node. + insertNode(node, parent, nextSibling, nextEndSibling, modifs); + + return node; +} + +Node *kafkaCommon::createAndInsertNode(const QString &nodeName, const QString &tagString, + int nodeType, Document *doc, Node *parent, Node *startNodeToSurround, + Node *endNodeToSurround, int startOffset, int endOffset, NodeModifsSet *modifs) +{ +#ifdef LIGHT_DEBUG + kdDebug(25001)<< "kafkaCommon::createAndInsertNode()3 - nodeName :" << nodeName << + " - tagStr :" << tagString << " - nodeType :" << nodeType << endl; +#endif + + Node *node; + + if(!startNodeToSurround || !endNodeToSurround) + return 0L; + + //create the new Node. + node = createNode(nodeName, tagString, nodeType, doc); + + //insert the new Node. + insertNode(node, parent, startNodeToSurround, endNodeToSurround, startOffset, endOffset, + modifs); + + return node; + +} + +bool kafkaCommon::DTDcreateAndInsertNode(const QString &nodeName, const QString &tagString, + int nodeType, Document *doc, Node *startNode, int startOffset, Node *endNode, int endOffset, + Node **cursorNode, long &cursorOffset, NodeModifsSet *modifs) +{ +#ifdef LIGHT_DEBUG + kdDebug(25001)<< "kafkaCommon::DTDcreateAndInsertNode()2 - nodeName : " << nodeName << + " - tagStr" <<tagString << endl; +#endif + + Node *node; + + if(!startNode || !endNode) + return false; + + //create the new Node. + node = createNode(nodeName, tagString, nodeType, doc); + + //insert the new Node. + return DTDinsertNode(node, startNode, startOffset, endNode, endOffset, doc, cursorNode, + cursorOffset, modifs); + +} + +bool kafkaCommon::addNodeRecursively(Node *newNode, Node *leafNode, + Node *startExaminationNode, Node *endExaminationNode, Node* startNode, Node *endNode, + Node* currentNode, bool &examinationStarted, bool &addingStarted, bool &nodeInserted, int level, + NodeModifsSet *modifs) +{ + + QTag *leafNodeQTag, *currentNodeParentQTag; + Node *startSelection = 0L, *endSelection = 0L, *oldCurrentNode, *copyNewNode; + bool selectionInProgress = false, validCurNodeParent = false; + + leafNodeQTag = QuantaCommon::tagFromDTD(leafNode); + if(!leafNodeQTag) + return false; + + if(currentNode && currentNode->parent) + { + currentNodeParentQTag = QuantaCommon::tagFromDTD(currentNode->parent); + if(currentNodeParentQTag && currentNodeParentQTag->isChild(newNode)) + validCurNodeParent = true; + } + + while(currentNode) + { +#ifdef HEAVY_DEBUG + kdDebug(25001)<< "kafkaCommon::addNodeRevursively() [" << level << "] - currentNode :" << + currentNode->tag->name << "("<< currentNode->tag->type << ")(" << currentNode << ")" << endl; +#endif + //If currentNode is the startExaminationNode, let's start to examine Nodes (=> search the startNode) + if(currentNode == startExaminationNode) + examinationStarted = true; + + //If currentNode is the startNode, let's start to try to add Nodes. + if(currentNode == startNode) + addingStarted = true; + + //If the currentNode is text or XmlTag, and if it is DTD valid to insert the node Subtree and + //if the examination has started and currentNode doesn't have endExaminationNode as + //child, let's start/extend the selection over this node. + if((currentNode->tag->type == Tag::XmlTag || currentNode->tag->type == Tag::Text) && + leafNodeQTag->isChild(currentNode) && validCurNodeParent && examinationStarted && + !currentNode->hasForChild(endExaminationNode)) + { +#ifdef HEAVY_DEBUG + kdDebug(25001)<< "kafkaCommon::addNodeRevursively() [" << level << + "] - Valid Child : " << currentNode->tag->name << endl; +#endif + //extend the selection to this node. + if(currentNode->tag->type == Tag::XmlTag && currentNode->getClosingNode()) + endSelection = currentNode->getClosingNode(); + else + endSelection = currentNode; + + //If this Node is, or has for child startNode, let's start to add newNode + if(currentNode->hasForChild(startNode) || currentNode == startNode) + { +#ifdef HEAVY_DEBUG + kdDebug(25001)<< "kafkaCommon::addNodeRevursively() [" << level << + "] - This Node has the startNode as Child : " << currentNode->tag->name << endl; +#endif + + addingStarted = true; + } + + //If there isn't a previously started selection, let's start it now. + if(!selectionInProgress && addingStarted) + { +#ifdef HEAVY_DEBUG + kdDebug(25001)<< "kafkaCommon::addNodeRevursively() [" << level << + "] - selection started at Node " << currentNode->tag->name << endl; +#endif + + selectionInProgress = true; + startSelection = currentNode; + } + } + else if(currentNode->tag->type == Tag::XmlTag || currentNode->tag->type == Tag::Text) + { +#ifdef HEAVY_DEBUG + kdDebug(25001)<< "kafkaCommon::addNodeRevursively() [" << level << + "] - Invalid Child : " << currentNode->tag->name << endl; +#endif + //the current Node can't handle newNode as a child, let's stop the selection + // here and surround the current selection with newNode + endSelection = currentNode->prev; + if(selectionInProgress) + { +#ifdef HEAVY_DEBUG + kdDebug(25001)<< "kafkaCommon::addNodeRevursively() [" << level << + "] - selection ended(2) at Node " << currentNode->tag->name << endl; +#endif + + selectionInProgress = false; + if(addingStarted) + { + while(startSelection && startSelection->tag->type == Tag::Empty) + startSelection = startSelection->next; + while(endSelection && endSelection->tag->type == Tag::Empty) + endSelection = endSelection->prev; + if (startSelection && endSelection) + { + /**copyNewNode = duplicateNode(newNode); + insertNode(copyNewNode, startSelection->parentNode(), startSelection, + endSelection->next, modifs);*/ + copyNewNode = duplicateNodeSubtree(newNode); + insertNodeSubtree(copyNewNode, startSelection->parentNode(), startSelection, + endSelection->next, modifs); + nodeInserted = true; + } + } + } + + //TESTING: If this Node is, or has for child startNode, let's start to add newNode + /**if(currentNode->hasForChild(startNode) || currentNode == startNode) + { + #ifdef HEAVY_DEBUG + kdDebug(25001)<< "kafkaCommon::addNodeRevursively() [" << level << + "] - This Node has the startNode as Child : " << currentNode->tag->name << endl; + #endif + + addingStarted = true; + }*/ + + //Let's try to surround some of the childs of currentNode. + if(currentNode->child) + { + addNodeRecursively(newNode, leafNode, startExaminationNode, + endExaminationNode, startNode, endNode, currentNode->child, + examinationStarted, addingStarted, nodeInserted, level + 1, modifs); + } + } + //If the currentNode is XmlTagEnd, Empty or whatever but not XmlTag and Text, + // we will surround them with newNode if a selection was started. + else + { + if(selectionInProgress) + { + if((currentNode->tag->type == Tag::XmlTag || currentNode->tag->type == Tag::ScriptTag) && + currentNode->getClosingNode()) + endSelection = currentNode->getClosingNode(); + else + endSelection = currentNode; + } + //If this Node is, or has for child startNode, let's start to add newNode + if((currentNode->hasForChild(startNode) || currentNode == startNode) && + examinationStarted) + { +#ifdef HEAVY_DEBUG + kdDebug(25001)<< "kafkaCommon::addNodeRevursively() [" << level << + "] - This Node has the startNode as Child : " << currentNode->tag->name << endl; +#endif + + addingStarted = true; + } + } + + //If the current Node is, or has for child endNode, or if currentNode is + //endExaminationNode or if examination is stopped, let's stop the current selection. + if(currentNode->hasForChild(endNode) || currentNode == endNode || + currentNode == endExaminationNode) + { +#ifdef HEAVY_DEBUG + kdDebug(25001)<< "kafkaCommon::addNodeRevursively() [" << level << + "] - This Node has the endNode as Child : " << currentNode->tag->name << endl; +#endif + + addingStarted = false; + examinationStarted = false; + if(selectionInProgress) + { +#ifdef HEAVY_DEBUG + kdDebug(25001)<< "kafkaCommon::addNodeRevursively() [" << level << + "] - selection ended at Node " << currentNode->tag->name << endl; +#endif + + selectionInProgress = false; + while(startSelection && startSelection->tag->type == Tag::Empty) + startSelection = startSelection->next; + while(endSelection && endSelection->tag->type == Tag::Empty) + endSelection = endSelection->prev; + if (startSelection && endSelection) + { + /**copyNewNode = duplicateNode(newNode); + insertNode(copyNewNode, startSelection->parentNode(), startSelection, + endSelection->next, modifs);*/ + copyNewNode = duplicateNodeSubtree(newNode); + insertNodeSubtree(copyNewNode, startSelection->parentNode(), startSelection, + endSelection->next, modifs); + nodeInserted = true; + } + } + } + + oldCurrentNode = currentNode; + currentNode = currentNode->next; + } + + if(selectionInProgress) + { +#ifdef HEAVY_DEBUG + kdDebug(25001)<< "kafkaCommon::addNodeRevursively() [" << level << + "] - selection ended(3) at Node " << oldCurrentNode->tag->name << endl; +#endif + + selectionInProgress = false; + endSelection = oldCurrentNode; + if(addingStarted) + { + while(startSelection && startSelection->tag->type == Tag::Empty) + startSelection = startSelection->next; + while(endSelection && endSelection->tag->type == Tag::Empty) + endSelection = endSelection->prev; + /**copyNewNode = duplicateNode(newNode); + insertNode(copyNewNode, startSelection->parentNode(), startSelection, + endSelection->next, modifs);*/ + copyNewNode = duplicateNodeSubtree(newNode); + insertNodeSubtree(copyNewNode, startSelection->parentNode(), startSelection, + endSelection->next, modifs); + nodeInserted = true; + } + } + + //The newNode was a template, let's delete it now. + if(level == 0) + Node::deleteNode(newNode); + return true; +} + +Node *kafkaCommon::duplicateNode(Node *node) +{ + Node *newNode; + + if(!node) + return 0L; + + newNode = new Node(0L); + (*newNode) = node; + newNode->tag->setCleanStrBuilt(false); + newNode->tag->setIndentationDone(false); + + return newNode; +} + +typedef struct boo +{ + boo() + { + m_n1 = m_n2 = 0L; + } + boo(Node *n1, Node *n2) + { + m_n1 = n1; + m_n2 = n2; + } + Node *m_n1; + Node *m_n2; +} +NodeLink; + +Node* kafkaCommon::getLastChild(Node* node) +{ + assert(node); + + Node* end_node = node->getClosingNode(); + if(!end_node && node->hasChildNodes()) + end_node = node->lastChildNE(); + else if(!end_node) + end_node = node; + + assert(end_node); + + return end_node; +} + +Node *kafkaCommon::duplicateNodeSubtree(Node *node, bool childAndClosingTagOnly) +{ + QPtrList<NodeLink> nodeLinkList; + bool goUp = false; + Node *currentNode, *currentNewNode, *newRootNode = 0, *newNext, *newParent, *newPrev; + NodeLink *link; + Node* endNode = 0; + if(!node) + return 0L; + + if(childAndClosingTagOnly) + endNode = getLastChild(node); + + + nodeLinkList.setAutoDelete(true); + currentNode = node; + while(currentNode) + { + currentNewNode = duplicateNode(currentNode); + nodeLinkList.append(new NodeLink(currentNode, currentNewNode)); + + newNext = 0L; + newParent = 0L; + newPrev = 0L; + for(link = nodeLinkList.first(); link; link = nodeLinkList.next()) + { + if(link->m_n1 == currentNode->parent) + newParent = link->m_n2; + else if(link->m_n1 == currentNode->next) + newNext = link->m_n2; + else if(link->m_n1 == currentNode->prev) + newPrev = link->m_n2; + } + + if(!newParent && !newPrev) + newRootNode = currentNewNode; + else if(!newParent) + { + //Temporary, insertNode would rely on baseNode which can be dangerous + currentNewNode->prev = newPrev; + newPrev->next = currentNewNode; + } + else + insertNode(currentNewNode, newParent, newNext, 0L, false); + + if(childAndClosingTagOnly) + currentNode = getNextNode(currentNode, goUp, endNode); + else + currentNode = getNextNode(currentNode, goUp, node); + } + + return newRootNode; +} + +Node* kafkaCommon::extractNode(Node *node, NodeModifsSet *modifs, bool extractChildren, + bool extractClosingTag) +{ + NodeModif *modif = 0, *modifChild; + Node *lastChild, *curNode; + Node *parent, *next, *child, *n; + //Node *prev; + bool isSingle; + int type; + QString namespaceName, nodeName, caseSensitive; + QString closingNamespaceName, closingNodeName, closingCaseSensitive; + QValueList<int> location; + + if(!node) + return 0L; + + if(!node->child) + extractChildren = true; + + parent = node->parent; + next = node->next; + //prev = node->prev; //Should this be used at all? + child = node->child; + lastChild = node->lastChild(); + isSingle = node->tag->single; + type = node->tag->type; + namespaceName = node->tag->nameSpace; + nodeName = node->tag->name; + caseSensitive = node->tag->dtd()->caseSensitive; + + //logging + if(modifs) + { + modif = new NodeModif(); + if(extractChildren) + modif->setType(NodeModif::NodeAndChildsRemoved); + else + modif->setType(NodeModif::NodeRemoved); + modif->setLocation(getLocation(node)); + + //log the children move if we don't extract the children + if(!extractChildren) + { + location = getLocation(node); + location.last()++; + n = lastChild; + while(n) + { + modifChild = new NodeModif(); + modifChild->setType(NodeModif::NodeAndChildsMoved); + modifChild->setLocation(getLocation(n)); + modifChild->setFinalLocation(location); + modifs->addNodeModif(modifChild); + n = n->prev; + } + } + } + + //starting to extract. + if(node == baseNode) + { + if(extractChildren) + baseNode = 0L; + else + baseNode = node->child; + parser->setRootNode(baseNode); + } + if(!extractChildren) + { + curNode = node->child; + while(curNode) + { + curNode->parent = node->parent; + curNode = curNode->next; + } + } + if(node->parent && node->parent->child == node) + { + if(extractChildren) + node->parent->child = node->next; + else + node->parent->child = node->child; + } + node->parent = 0L; + if(node->prev) + { + if(extractChildren) + node->prev->next = node->next; + else + { + node->prev->next = node->child; + node->child->prev = node->prev; + } + } + if(node->next) + { + if(extractChildren) + node->next->prev = node->prev; + else + { + /**lastChild = node->child; + while(lastChild->next) + lastChild = lastChild->next;*/ + node->next->prev = lastChild; + lastChild->next = node->next; + } + } + node->prev = 0L; + node->next = 0L; + if(!extractChildren) + node->child = 0L; + + if(modifs) + { + modif->setNode(0/*node*/); // this deletes the node!!??? + modifs->addNodeModif(modif); + } + + //extract the closing Tag + if(extractClosingTag && type == Tag::XmlTag && !isSingle && next) + { + while(next && next->tag->type == Tag::Empty) + next = next->next; + if(next) + { + closingNamespaceName = next->tag->nameSpace; + closingNodeName = next->tag->name; + closingCaseSensitive = next->tag->dtd()->caseSensitive; + if(QuantaCommon::closesTag(namespaceName, nodeName, caseSensitive, + closingNamespaceName, closingNodeName, closingCaseSensitive)) + extractNode(next, modifs, false, false); + } + } + +#ifdef HEAVY_DEBUG + coutTree(baseNode, 2); +#endif + + return node; +} + +Node* kafkaCommon::DTDExtractNodeSubtree(Node *startNode, int startOffset, Node *endNode, int endOffset, + Node **cursorNode, long &cursorOffset, NodeModifsSet *modifs, bool extractInlineParentNodes) +{ +#ifdef LIGHT_DEBUG + kdDebug(25001) << "kafkaCommon::extractNodeSubtree()" << endl; +#endif + + if(!startNode || !endNode) + return 0; + + QValueList<int> commonParentStartChildLocation; + QValueList<int> commonParentEndChildLocation; + + Node* commonParent = 0; + + NodeSelection cursorHolder; + cursorHolder.setCursorNode(*cursorNode); + cursorHolder.setCursorOffset(cursorOffset); + + splitStartAndEndNodeSubtree(startNode, startOffset, endNode, endOffset, commonParent, + commonParentStartChildLocation, commonParentEndChildLocation, + cursorHolder, 0, modifs, extractInlineParentNodes); + + *cursorNode = cursorHolder.cursorNode(); + cursorOffset = cursorHolder.cursorOffset(); + Node* commonParentStartChild = getNodeFromLocation(commonParentStartChildLocation); + Node* commonParentEndChild = getNodeFromLocation(commonParentEndChildLocation); + + if(startNode == endNode) + { + Q_ASSERT(startNode->tag->type == Tag::Text || startNode->tag->type == Tag::Empty); + + Node* prev = startNode->prev; + Node* next = startNode->next; + + Node* aux = extractNode(startNode, modifs); + + mergeInlineNode(prev, next, cursorNode, cursorOffset, modifs); + + return aux; + } + + // now let us extract the subtree + + if(!commonParentEndChild) + commonParentEndChild = endNode; + extractNodeSubtreeAux(commonParentStartChild, commonParentEndChild, modifs); + + // merge identical nodes + Node* commonParentEndChild_next = commonParentEndChild->SNext(); + mergeInlineNode(commonParent, commonParentEndChild_next, cursorNode, cursorOffset, modifs); + mergeInlineNode(commonParentStartChild, commonParentEndChild, cursorNode, cursorOffset, modifs); + +#ifdef LIGHT_DEBUG + coutTree(commonParentStartChild, 3); +#endif + + return commonParentStartChild; +} + +Node* kafkaCommon::DTDExtractNodeSubtree(Node *startNode, int startOffset, Node *endNode, int endOffset, + Node* nodeSubtree, NodeModifsSet* modifs, bool extractInlineParentNodes) +{ +#ifdef LIGHT_DEBUG + kdDebug(25001) << "kafkaCommon::extractNodeSubtree()" << endl; +#endif + + if(!startNode || !endNode) + return 0; + + QValueList<int> commonParentStartChildLocation; + QValueList<int> commonParentEndChildLocation; + + Node* commonParent = 0; + if(extractInlineParentNodes) + { + commonParent = DTDGetNonInlineCommonParent(startNode, endNode, + commonParentStartChildLocation, commonParentEndChildLocation, nodeSubtree); + } + else + { + commonParent = DTDGetCommonParent(startNode, endNode, + commonParentStartChildLocation, commonParentEndChildLocation, nodeSubtree); + } + assert(commonParent == nodeSubtree); + + NodeSelection selection; + splitStartAndEndNodeSubtree(startNode, startOffset, endNode, endOffset, commonParent, + commonParentStartChildLocation, commonParentEndChildLocation, + selection, nodeSubtree, modifs); + + Node* cursorNode = selection.cursorNode(); + long cursorOffset = selection.cursorOffset(); + Node* commonParentStartChild = getNodeFromLocation(commonParentStartChildLocation, nodeSubtree); + Node* commonParentEndChild = getNodeFromLocation(commonParentEndChildLocation, nodeSubtree); + + if(startNode == endNode) + { + Q_ASSERT(startNode->tag->type == Tag::Text || startNode->tag->type == Tag::Empty); + return extractNode(startNode, modifs); + } + + // now let us extract the subtree + commonParentStartChild = getNodeFromLocation(commonParentStartChildLocation, commonParent); + commonParentEndChild = getNodeFromLocation(commonParentEndChildLocation, commonParent); + + if(!commonParentEndChild) + commonParentEndChild = endNode; + extractNodeSubtreeAux(commonParentStartChild, commonParentEndChild, modifs); + + //merge identical nodes +/* Node* cursorNode = 0; + int cursorOffset = 0;*/ + Node* commonParentEndChild_next = commonParentEndChild->SNext(); + mergeInlineNode(commonParent, commonParentEndChild_next, &cursorNode, cursorOffset, modifs); + mergeInlineNode(commonParentStartChild, commonParentEndChild, &cursorNode, cursorOffset, modifs); + +#ifdef LIGHT_DEBUG + coutTree(commonParentStartChild, 3); +#endif + + return commonParentStartChild; +} + +Node* kafkaCommon::extractNodeSubtreeAux(Node* commonParentStartChild, Node* commonParentEndChild, NodeModifsSet* modifs) +{ + Node* node = commonParentStartChild; + Node* prev_node = 0; + Node* next_node = 0; + Node* significant_next_node = 0; + Node* node_extracted = 0; + + Node* commonParentEndChild_next = commonParentEndChild->SNext(); + + while(node && node != commonParentEndChild_next) + { + next_node = node->next; + significant_next_node = node->SNext(); + node_extracted = extractNode(node, modifs, true, true); + if(node_extracted) + { + node_extracted->prev = prev_node; + if(significant_next_node != commonParentEndChild_next || (next_node && next_node->closesPrevious)) + node_extracted->next = next_node; + if(next_node && next_node->closesPrevious) + { + next_node->prev = node_extracted; + node_extracted->_closingNode = next_node; + } + } + prev_node = node_extracted; + node = significant_next_node; + } + + return commonParentStartChild; +} + +Node* kafkaCommon::getNodeSubtree(Node *startNode, int startOffset, Node *endNode, int endOffset, bool extractInlineParentNodes) +{ +#ifdef LIGHT_DEBUG + kdDebug(25001) << "kafkaCommon::getNodeSubtree()" << endl; +#endif + + if(!startNode || !endNode) + return 0; + + QValueList<int> commonParentStartChildLocation; + QValueList<int> commonParentEndChildLocation; + + Node* commonParent = 0; + if(extractInlineParentNodes) + commonParent = DTDGetNonInlineCommonParent(startNode, endNode, + commonParentStartChildLocation, commonParentEndChildLocation, 0); + else + commonParent = DTDGetCommonParent(startNode, endNode, + commonParentStartChildLocation, commonParentEndChildLocation, 0); + + // get the subtree to operate + Node* newStartNode = 0; + Node* newEndNode = 0; + + Node* newCommonParent = duplicateNodeSubtree(commonParent, true); + + QValueList<int> const startNodeLocation = getLocation(startNode); + QValueList<int> const commonParentLocation = getLocation(commonParent); + uint const commonParentDepth = commonParentLocation.size(); + uint const newStartNodeDepth = startNodeLocation.size() - commonParentDepth + 1; + uint const newEndNodeDepth = startNodeLocation.size() - commonParentDepth + 1; + + QValueList<int> newStartNodeLocation, newEndNodeLocation; + newStartNodeLocation.push_back(1); + newEndNodeLocation.push_back(1); + + for(uint i = 1; i != newStartNodeDepth; ++i) + newStartNodeLocation.push_back(startNodeLocation[i + commonParentDepth - 1]); + + QValueList<int> const endNodeLocation = getLocation(endNode); + for(uint i = 1; i != newEndNodeDepth; ++i) + newEndNodeLocation.push_back(endNodeLocation[i + commonParentDepth - 1]); + + newStartNode = getNodeFromLocation(newStartNodeLocation, newCommonParent); + newEndNode = getNodeFromLocation(newEndNodeLocation, newCommonParent); + + return DTDExtractNodeSubtree(newStartNode, startOffset, newEndNode, endOffset, newCommonParent, 0); +} + +Node* kafkaCommon::DTDRemoveSelection(NodeSelectionInd& selection, + Node **cursorNode, long& cursorOffset, NodeModifsSet *modifs, bool extractInlineParentNodes) +{ + Q_ASSERT(selection.hasSelection()); + + int startOffset = selection.cursorOffset(); + int endOffset = selection.cursorOffsetEndSel(); + Node* startNode = getNodeFromLocation(selection.cursorNode()); + Node* endNode = getNodeFromLocation(selection.cursorNodeEndSel()); + + return DTDExtractNodeSubtree(startNode, startOffset, endNode, endOffset, cursorNode, cursorOffset, modifs, extractInlineParentNodes); +} + +void kafkaCommon::extractAndDeleteNode(Node *node, NodeModifsSet *modifs, bool deleteChildren, + bool deleteClosingTag, bool mergeAndFormat) +{ + NodeModif modif; + Node *curNode, *nodePrev, *nodeNext, *nodeNext2, *n, *n2; + QString nodeName, closingNodeName, namespaceName, namespaceName2; + bool isSingle, caseSensitive, caseSensitive2; + + if(!node) + return; + + isSingle = node->tag->single; + nodeName = node->tag->name; + namespaceName = node->tag->nameSpace; + caseSensitive = node->tag->dtd()->caseSensitive; + nodePrev = node->prev; + nodeNext = node->next; + if(!node->child) + deleteChildren = true; + node = extractNode(node, modifs, deleteChildren); + + //delete the closing Tag + if(!isSingle && deleteClosingTag && nodeNext) + { + curNode = nodeNext; + while(curNode && curNode->tag->type == Tag::Empty) + curNode = curNode->next; + if(curNode) + { + closingNodeName = curNode->tag->name; + namespaceName2 = curNode->tag->nameSpace; + caseSensitive2 = curNode->tag->dtd()->caseSensitive; + if(QuantaCommon::closesTag(namespaceName, nodeName, caseSensitive, + namespaceName2, closingNodeName, caseSensitive2)) + { + curNode = nodeNext; + while(curNode) + { + nodeNext2 = curNode->next; + closingNodeName = curNode->tag->name; + namespaceName2 = curNode->tag->nameSpace; + caseSensitive2 = curNode->tag->dtd()->caseSensitive; + curNode = extractNode(curNode, modifs, deleteChildren); + curNode = nodeNext2; + if(QuantaCommon::closesTag(namespaceName, nodeName, caseSensitive, + namespaceName2, closingNodeName, caseSensitive2)) + break; + } + nodeNext = curNode; + } + } + } + + //merge the next and prev Nodes if they are both of type Text or Empty + if(mergeAndFormat && nodePrev) + { + n = nodePrev; + n2 = nodePrev->next; + while(n && n2 && n2->prev != nodeNext) + { + if(!mergeNodes(n, n2, modifs)) + break; + n2 = n->next; + } + } +} + +int kafkaCommon::DTDExtractNode(const QString &nodeName, Document *doc, Node *startNode, + int startOffset, Node *endNode, int endOffset, Node **cursorNode, long &cursorOffset, + NodeModifsSet *modifs) +{ + QTag *nodeNameQTag, *parentQTag; + Node *node, *lastNodeNameStartNode, *lastNodeNameEndNode; + Node *parentNode, *newParentNode, *child, *next; + bool goUp, nodesRemoved = false, DTDError = false, result; + bool startNodeSplitted = false, endNodeSplitted = false; + + if(!doc || !startNode || !endNode) + return kafkaCommon::extractionBadParameters; + + //First check that nodeName is really inline and that an area is selected. + nodeNameQTag = QuantaCommon::tagFromDTD(doc->defaultDTD(), nodeName); + if(!nodeNameQTag) + return kafkaCommon::extractionBadParameters; + if(!isInline(nodeName)) + return kafkaCommon::extractionBadParameters; + if(startNode->tag->type == Tag::Text && startOffset == (signed)startNode->tag->tagStr().length()) + { + startOffset = 0; + while(startNode && startNode->nextSibling()) + { + startNode = startNode->nextSibling(); + if(startNode == endNode || startNode->tag->type == Tag::Text) + break; + } + } + if(startNode == endNode && startOffset == endOffset) + return kafkaCommon::extractionBadParameters; + + //Then, process startNode and endNode : look if a nodeName parent is one of + //startNode/endNode's inline parents and if it is the case, split the necessary Nodes. + //The comparaison is made in lowercase, even in xml : it could be strange, for an user, to have + //its nodes not removed because there are in the wrong case. + node = startNode; + lastNodeNameStartNode = 0L; + while(node && (isInline(node->tag->name) || node->tag->type == Tag::Text)) + { + if(node->tag->name.lower() == nodeName.lower()) + lastNodeNameStartNode = node; + node = node->parent; + } + node = endNode; + lastNodeNameEndNode = 0L; + while(node && (isInline(node->tag->name) || node->tag->type == Tag::Text)) + { + if(node->tag->name.lower() == nodeName.lower()) + lastNodeNameEndNode = node; + node = node->parent; + } + + if(startNode->tag->type == Tag::Text) + { + if(splitNode(startNode, startOffset, modifs)) + { + startNodeSplitted = true; + //<TEMPORARY> + if(startNode == (*cursorNode) && cursorOffset > startOffset) + { + (*cursorNode) = (*cursorNode)->nextSibling(); + cursorOffset -= startOffset; + } + //</TEMPORARY> + if(startNode == endNode) + { + endNode = endNode->nextSibling(); + endOffset -= startOffset; + } + startNode = startNode->nextSibling(); + } + } + if(endNode->tag->type == Tag::Text) + { + result = splitNode(endNode, endOffset, modifs); + if(result) + endNodeSplitted = true; + else if(!result && endOffset == 0) + endNode = endNode->previousSibling(); + } + + if(lastNodeNameStartNode) + { + node = startNode; + parentNode = startNode->parent; + while(parentNode && parentNode != lastNodeNameStartNode->parent) + { + if(node != parentNode->firstChild()) + { + newParentNode = duplicateNode(parentNode); + insertNode(newParentNode, parentNode->parentNode(), parentNode, parentNode, modifs); + child = parentNode->firstChild(); + while(child && child != startNode && !child->hasForChild(startNode)) + { + next = child->next; + moveNode(child, newParentNode, 0L, modifs); + child = next; + } + } + node = parentNode; + parentNode = parentNode->parent; + } + } + if(lastNodeNameEndNode) + { + node = endNode; + parentNode = endNode->parent; + while(parentNode && parentNode != lastNodeNameEndNode->parent) + { + if(node != parentNode->SLastChild()) + { + newParentNode = duplicateNode(parentNode); + insertNode(newParentNode, parentNode->parentNode(), parentNode, parentNode, modifs); + if(parentNode == lastNodeNameStartNode) + lastNodeNameStartNode = newParentNode; + child = parentNode->firstChild(); + while(child) + { + next = child->next; + moveNode(child, newParentNode, 0L, modifs); + if(child == endNode || child->hasForChild(endNode)) + { + if(QuantaCommon::closesTag(child->tag, next->tag)) + moveNode(next, newParentNode, 0L, modifs); + break; + } + child = next; + } + } + node = parentNode; + parentNode = parentNode->parent; + } + } + + //Now delete the nodeName Nodes when possible from lastNodeNameStartParent to endNode. + node = lastNodeNameStartNode?lastNodeNameStartNode:startNode; + goUp = false; + while(node && !DTDError) + { + next = getNextNode(node, goUp); + if(node->tag->type == Tag::XmlTag && node->tag->name.lower() == nodeName.lower()) + { + parentQTag = QuantaCommon::tagFromDTD(node->parent); + if(parentQTag) + { + child = node->firstChild(); + while(child) + { + if(!parentQTag->isChild(child)) + DTDError = true; + child = child->next; + } + if(!DTDError) + { + extractNode(node, modifs, false, true); + nodesRemoved = true; + } + } + } + if(node == endNode) + break; + node = next; + } + + //TODO: merge the unnecessary splitted Nodes. + if(endNode && endNodeSplitted) + mergeNodes(endNode, endNode->nextSibling(), modifs, true); + if(startNode && startNodeSplitted) + { + node = startNode->previousSibling(); + result = mergeNodes(startNode->previousSibling(), startNode, modifs, true); + startNode = node; + //<TEMPORARY> + if(result) + { + (*cursorNode) = node; + cursorOffset += startOffset; + } + //</TEMPORARY> + } + + if(DTDError) + return kafkaCommon::extractionStoppedDueToBadNodes; + else if(!nodesRemoved) + return kafkaCommon::nothingExtracted; + else + { + //merge when necessary some text/identical inlines. + mergeInlineNode(startNode, endNode, cursorNode, cursorOffset, modifs); + + return kafkaCommon::extractionDone; + } +} + + +void kafkaCommon::moveNode(Node *nodeToMove, Node *newParent, Node *newNextSibling, + NodeModifsSet *modifs, bool merge, bool moveClosingNode) +{ + NodeModif *modif = 0; + Node *newNode, *closingNode; + closingNode = nodeToMove->getClosingNode(); + + //DON'T log the removal and addition of the same Node!! When spliting the undoRedo stack + //it will delete the remove NodeModif and thus the Node inside which is the Node inserted. + if(modifs) + { + modif = new NodeModif(); + modif->setType(NodeModif::NodeAndChildsMoved); + modif->setLocation(getLocation(nodeToMove)); + } + + //extract the old Node. + newNode = extractNode(nodeToMove, 0L, true); + + //insert the new Node. + insertNode(newNode, newParent, newNextSibling, 0L, merge); + if(modifs) + modif->setFinalLocation(getLocation(newNode)); + + if(moveClosingNode && closingNode) + moveNode(closingNode, newParent, newNextSibling, + modifs, merge, false); + + if(modifs) + modifs->addNodeModif(modif); +} + +void kafkaCommon::moveNode(Node *nodeToMove, Node *newParent, Node *newNextSibling, NodeSelection& cursorHolder, + NodeModifsSet *modifs, bool merge, bool moveClosingNode) +{ + NodeModif *modif = 0; + Node *newNode, *closingNode; + closingNode = nodeToMove->getClosingNode(); + + //DON'T log the removal and addition of the same Node!! When spliting the undoRedo stack + //it will delete the remove NodeModif and thus the Node inside which is the Node inserted. + if(modifs) + { + modif = new NodeModif(); + modif->setType(NodeModif::NodeAndChildsMoved); + modif->setLocation(getLocation(nodeToMove)); + } + + //extract the old Node. + newNode = extractNode(nodeToMove, 0L, true); + cursorHolder.setCursorNode(newNode); + + //insert the new Node. + insertNode(newNode, newParent, newNextSibling, cursorHolder, 0L, merge); + if(modifs) + modif->setFinalLocation(getLocation(newNode)); + + if(moveClosingNode && closingNode) + moveNode(closingNode, newParent, newNextSibling, + modifs, merge, false); + + if(modifs) + modifs->addNodeModif(modif); +} + +bool kafkaCommon::splitNode(Node *n, int offset, NodeModifsSet *modifs) +{ + NodeModif *modif; + Tag *tag; + QString tagStr; + Node *node; + + if(!n || (n->tag->type != Tag::Text && n->tag->type != Tag::Empty) || offset <= 0 || offset >= + (signed)n->tag->tagStr().length()) + return false; + + //logging + if(modifs) + { + tag = new Tag(*(n->tag)); + modif = new NodeModif(); + modif->setType(NodeModif::NodeModified); + modif->setTag(tag); + modif->setLocation(getLocation(n)); + modifs->addNodeModif(modif); + } + + tagStr = n->tag->tagStr(); + n->tag->setStr(tagStr.left(offset)); + + if(n->tag->type == Tag::Text) + node = createAndInsertNode("#text", tagStr.right(tagStr.length() - offset), Tag::Text, n->tag->write(), + n->parent, n->next, modifs, false); + else + node = createAndInsertNode("", tagStr.right(tagStr.length() - offset), Tag::Empty, n->tag->write(), + n->parent, n->next, modifs, false); + + //Node's string is a part of n's clean string + node->tag->setCleanStrBuilt(true); + node->tag->setIndentationDone(true); + return true; +} + +void kafkaCommon::splitStartNodeSubtree(Node* startNode, Node* commonParent, + QValueList<int>& commonParentStartChildLocation, NodeModifsSet* modifs) +{ + //Then we "split" the lastValidStartParent - startNode subtree into two : the first part is untouched + // and the second will be surrounded by the new Node. Same thing for endNode. + Node* node = startNode; + Node* parentNode = startNode->parent; + Node* commonParentStartChild = 0; + while(parentNode && commonParent && parentNode != commonParent) + { + if(node != parentNode->firstChild()) + { + Node* newParentNode = duplicateNode(parentNode); + insertNode(newParentNode, parentNode->parentNode(), parentNode, parentNode, modifs); + Node* child = parentNode->firstChild(); + while(child && child != startNode && !child->hasForChild(startNode)) + { + Node* next = child->next; + moveNode(child, newParentNode, 0L, modifs); + child = next; + } + } + commonParentStartChild = parentNode; + node = parentNode; + parentNode = parentNode->parent; + } + + if(commonParentStartChild) + commonParentStartChildLocation = getLocation(commonParentStartChild); +} + +void kafkaCommon::splitEndNodeSubtree(Node* endNode, Node* commonParent, + QValueList<int>& commonParentStartChildLocation, + QValueList<int>& commonParentEndChildLocation, + bool subTree, NodeModifsSet* modifs) +{ + Node* node = endNode; + Node* parentNode = endNode->parent; + + Node* aux = 0; + if(subTree) + aux = commonParent; + else + aux = baseNode; + Node* commonParentStartChild = getNodeFromLocation(commonParentStartChildLocation, aux); + Node* commonParentEndChild = getNodeFromLocation(commonParentEndChildLocation, aux); + while(parentNode && commonParent && parentNode != commonParent) + { + if(node != parentNode->lastChild()) + { + Node* newParentNode = duplicateNode(parentNode); + insertNode(newParentNode, parentNode->parentNode(), parentNode, parentNode, modifs); + if(parentNode == commonParentStartChild) + commonParentStartChild = newParentNode; + if(parentNode == commonParentEndChild) + commonParentEndChild = newParentNode; + Node* child = parentNode->firstChild(); + while(child) + { + Node* next = child->next; + moveNode(child, newParentNode, 0L, modifs); + if(child == endNode || child->hasForChild(endNode)) + { + if(QuantaCommon::closesTag(child->tag, next->tag)) + moveNode(next, newParentNode, 0L, modifs); + break; + } + child = next; + } + } + node = parentNode; + parentNode = parentNode->parent; + } + commonParentStartChildLocation = getLocation(commonParentStartChild); + commonParentEndChildLocation = getLocation(commonParentEndChild); +} + +void kafkaCommon::splitStartAndEndNodeSubtree(Node*& startNode, int startOffset, Node*& endNode, int endOffset, Node*& commonParent, + QValueList<int>& commonParentStartChildLocation, + QValueList<int>& commonParentEndChildLocation, + NodeSelection& cursorHolder, + Node* subTree, NodeModifsSet* modifs, bool extractInlineParentNodes) +{ + assert(startNode && endNode); + assert(startOffset >= 0); + assert(endOffset >= 0); + + // get correct start and end nodes and offsets + startNode = getCorrectStartNode(startNode, startOffset); + endNode = getCorrectEndNode(endNode, endOffset); + + // look for common parent + if(!commonParent) + { + if(extractInlineParentNodes) + // get the non inline common parent + commonParent = + DTDGetNonInlineCommonParent(startNode, endNode, commonParentStartChildLocation, commonParentEndChildLocation, subTree); + else + commonParent = + DTDGetCommonParent(startNode, endNode, commonParentStartChildLocation, commonParentEndChildLocation, subTree); + } + else + { + assert(commonParent->hasForChild(startNode)); + assert(commonParent->hasForChild(endNode)); + assert(!commonParentStartChildLocation.empty()); + assert(!commonParentEndChildLocation.empty()); + } + + Node* commonParentStartChild = kafkaCommon::getNodeFromLocation(commonParentStartChildLocation, subTree); + Node* commonParentEndChild = kafkaCommon::getNodeFromLocation(commonParentEndChildLocation, subTree); + + Node* cursorNode = cursorHolder.cursorNode(); + int cursorOffset = cursorHolder.cursorOffset(); + + // split start and end node + if(splitNode(startNode, startOffset, modifs)) + { + if(startNode == cursorNode && cursorOffset > startOffset) + { + cursorNode = cursorNode->nextSibling(); + cursorOffset -= startOffset; + } + if(startNode == commonParentStartChild) + commonParentStartChild = commonParentStartChild->nextSibling(); + + if(startNode == endNode) + { + endNode = endNode->nextSibling(); + endOffset -= startOffset; + } + startNode = startNode->nextSibling(); + startOffset = 0; + } + splitNode(endNode, endOffset, modifs); + + // split start and end nodes subtree in function of common parent + commonParentStartChildLocation = kafkaCommon::getLocation(commonParentStartChild); + splitStartNodeSubtree(startNode, commonParent, commonParentStartChildLocation, modifs); + + commonParentEndChildLocation = kafkaCommon::getLocation(commonParentEndChild); + splitEndNodeSubtree(endNode, commonParent, commonParentStartChildLocation, commonParentEndChildLocation, subTree, modifs); + + cursorHolder.setCursorNode(cursorNode); + cursorHolder.setCursorOffset(cursorOffset); +} + +bool kafkaCommon::mergeNodes(Node *n, Node *n2, NodeModifsSet *modifs, bool mergeTextOnly) +{ + NodeModif *modif; + Tag *tag; + if(!n || !n2) + return false; + + if(((n->tag->type == Tag::Empty && !mergeTextOnly) || n->tag->type == Tag::Text) && + ((n2->tag->type == Tag::Empty && !mergeTextOnly) || n2->tag->type == Tag::Text)) + { + tag = new Tag(*(n->tag)); + + //logging + if(modifs) + { + modif = new NodeModif(); + modif->setType(NodeModif::NodeModified); + modif->setTag(tag); + modif->setLocation(getLocation(n)); + modifs->addNodeModif(modif); + } + + // have in consideration two spaces in a row + QString nStr(n->tag->tagStr()); + QString n2Str(n2->tag->tagStr()); + if(nStr[nStr.length() - 1] == ' ' && n2Str[0] == ' ') + { + nStr = nStr.left(nStr.length() - 1); + nStr.append(" "); + n->tag->setStr(nStr); + + n2Str = n2Str.right(n2Str.length() - 1); + n2Str.prepend(" "); + n2->tag->setStr(n2Str); + } + + if((n->tag->type == Tag::Text && n2->tag->type == Tag::Text) || + (n->tag->type == Tag::Empty && n2->tag->type == Tag::Empty)) + n->tag->setStr(n->tag->tagStr() + n2->tag->tagStr()); + else if(n->tag->type == Tag::Empty && n2->tag->type == Tag::Text) + n->tag->setStr(n2->tag->tagStr()); + //else n's string is already in n + + if(n->tag->type == Tag::Text || n2->tag->type == Tag::Text) + n->tag->type = Tag::Text; + if(!n->tag->cleanStrBuilt() || !n2->tag->cleanStrBuilt()) + n->tag->setCleanStrBuilt(false); + if(!n->tag->indentationDone() || !n2->tag->indentationDone()) + n->tag->setIndentationDone(false); + kafkaCommon::extractAndDeleteNode(n2, modifs, false, false, false); + + return true; + } + return false; +} + +bool kafkaCommon::mergeNodes(Node *n, Node *n2, NodeSelection& cursorHolder, NodeModifsSet *modifs, bool mergeTextOnly) +{ + NodeModif *modif; + Tag *tag; + if(!n || !n2) + return false; + + if(((n->tag->type == Tag::Empty && !mergeTextOnly) || n->tag->type == Tag::Text) && + ((n2->tag->type == Tag::Empty && !mergeTextOnly) || n2->tag->type == Tag::Text)) + { + tag = new Tag(*(n->tag)); + + //logging + if(modifs) + { + modif = new NodeModif(); + modif->setType(NodeModif::NodeModified); + modif->setTag(tag); + modif->setLocation(getLocation(n)); + modifs->addNodeModif(modif); + } + + // have in consideration two spaces in a row + QString nStr(n->tag->tagStr()); + QString n2Str(n2->tag->tagStr()); + if(nStr[nStr.length() - 1] == ' ' && n2Str[0] == ' ') + { + nStr = nStr.left(nStr.length() - 1); + nStr.append(" "); + n->tag->setStr(nStr); + + n2Str = n2Str.right(n2Str.length() - 1); + n2Str.prepend(" "); + n2->tag->setStr(n2Str); + } + + if((n->tag->type == Tag::Text && n2->tag->type == Tag::Text) || + (n->tag->type == Tag::Empty && n2->tag->type == Tag::Empty)) + { + if(cursorHolder.cursorNode() == n2) + cursorHolder.setCursorOffset(n->tag->tagStr().length() + cursorHolder.cursorOffset() - 1); + + n->tag->setStr(n->tag->tagStr() + n2->tag->tagStr()); + } + else if(n->tag->type == Tag::Empty && n2->tag->type == Tag::Text) + n->tag->setStr(n2->tag->tagStr()); + //else n's string is already in n + + if(n->tag->type == Tag::Text || n2->tag->type == Tag::Text) + n->tag->type = Tag::Text; + if(!n->tag->cleanStrBuilt() || !n2->tag->cleanStrBuilt()) + n->tag->setCleanStrBuilt(false); + if(!n->tag->indentationDone() || !n2->tag->indentationDone()) + n->tag->setIndentationDone(false); + kafkaCommon::extractAndDeleteNode(n2, modifs, false, false, false); + + cursorHolder.setCursorNode(n); + + return true; + } + return false; +} + +void kafkaCommon::mergeInlineNode(Node *startNode, Node *endNode, Node **cursorNode, + long &cursorOffset, NodeModifsSet *modifs) +{ + Node *startNodeLastInlineParent, *parent, *node, *next; + bool goUp, success, isCursorNode, isEndNode; + int nodeLength; + + if(!startNode || !endNode) + return; + + //first search for the last inline parent of startNode, and then its last prev neighbour + // which is also inline : the merge will start from this Node. + startNodeLastInlineParent = startNode; + parent = startNode->parent; + while(parent && isInline(parent->tag->name)) + { + startNodeLastInlineParent = parent; + parent = parent->parent; + } + if(startNodeLastInlineParent->prev) + { + if(startNodeLastInlineParent->prev->tag->type == Tag::Text) + startNodeLastInlineParent = startNodeLastInlineParent->prev; + else + { + node = startNodeLastInlineParent->prev; + while(node && (node->tag->type == Tag::Empty || node->tag->type == Tag::XmlTagEnd)) + node = node->prev; + if(node && node->tag->type == Tag::XmlTag && isInline(node->tag->name)) + startNodeLastInlineParent = node; + } + } + + + //Then navigate though the tree and merge. + node = startNodeLastInlineParent; + goUp = false; + while(node) + { + if(node->tag->type == Tag::XmlTag && isInline(node->tag->name)) + { + next = node->next; + while(next && (next->tag->type == Tag::XmlTagEnd || next->tag->type == Tag::Empty)) + next = next->next; + while(next && next != node && compareNodes(node, next)) + { + while(next->firstChild()) + moveNode(next->firstChild(), node, 0L, modifs, false); + if(next == endNode) + endNode = node; + else if((*cursorNode) == node->next) + { + //<TEMPORARY> + (*cursorNode) = node; + //</TEMPORARY> + } + extractNode(next, modifs, false, true); + next = node->next; + while(next && (next->tag->type == Tag::XmlTagEnd || next->tag->type == Tag::Empty)) + next = next->next; + } + } + else if(node->tag->type == Tag::Text) + { + while(node->next && (node->next->tag->type == Tag::Text || + node->next->tag->type == Tag::Empty)) + { + nodeLength = (signed)node->tag->tagStr().length(); + isCursorNode = ((*cursorNode) == node->next); + isEndNode = (endNode == node->next); + success = mergeNodes(node, node->next, modifs); + if(isCursorNode && success) + { + //<TEMPORARY> + (*cursorNode) = node; + cursorOffset += nodeLength; + //</TEMPORARY> + } + else if(isEndNode && success) + endNode = node; + } + } + if(node == endNode) + break; + node = getNextNode(node, goUp); + } +} + +void kafkaCommon::getEndPosition(const QString &tagString, int bLine, int bCol, int &eLine, int &eCol) +{ + /**int result, oldResult; + + result = tagString.find("\n", 0); + if(result == -1) + { + eLine = bLine; + eCol = bCol + tagString.length() - 1; + } + else + { + eLine = bLine; + while(result != -1) + { + eLine++; + oldResult = result; + result = tagString.find("\n", result + 1); + } + eCol = tagString.length() - oldResult - 2; + }*/ + int i; + + eLine = bLine; + eCol = bCol - 1; + for(i = 0; i < (signed)tagString.length(); ++i) + { + if(tagString[i] == "\n") + { + eLine++; + eCol = -1; + } + else + eCol++; + } +} + +void kafkaCommon::getEndPosition(Node *node, int bLine, int bCol, int &eLine, int &eCol) +{ + if(!node) + { + eLine = 0; + eCol = 0; + return; + } + + getEndPosition(node->tag->tagStr(), bLine, bCol, eLine, eCol); +} + +void kafkaCommon::setTagString(Node *node, const QString &newTagString, NodeModifsSet* modifs) +{ + int eLine, eCol, bLine, bCol; + Tag *tag; + NodeModif* modif; + + if(!node) + return; + + //logging + if(modifs) + { + tag = new Tag(*(node->tag)); + modif = new NodeModif(); + modif->setType(NodeModif::NodeModified); + modif->setTag(tag); + modif->setLocation(getLocation(node)); + modifs->addNodeModif(modif); + } + + node->tag->beginPos(bLine, bCol); + node->tag->setStr(newTagString); + getEndPosition(node, bLine, bCol, eLine, eCol); + node->tag->setTagPosition(bLine, bCol, eLine, eCol); +} + +void kafkaCommon::setTagStringAndFitsNodes(Node *node, const QString &newTagString, NodeModifsSet* modifs) +{ + int eLine, eCol, oldELine, oldECol; + bool b = false; + + if(!node) + return; + + node->tag->endPos(oldELine, oldECol); + setTagString(node, newTagString, modifs); + node->tag->endPos(eLine, eCol); + + fitsNodesPosition(getNextNode(node, b), eCol - oldECol, eLine - oldELine); +} + +void kafkaCommon::editNodeAttribute(Node* node, const QString& name, const QString& value, NodeModifsSet* modifs) +{ + NodeModif *modif = 0; + + if(!node) + return; + + if(modifs) + { + modif = new NodeModif(); + modif->setType(NodeModif::NodeModified); + modif->setTag(new Tag(*(node->tag))); + modif->setLocation(getLocation(node)); + } + + if(node->tag->editAttribute(name, value)) + { + node->tag->setCleanStrBuilt(false); + if(modifs) + modifs->addNodeModif(modif); + } +} + +QValueList<int> kafkaCommon::getLocation(Node * node) +{ + QValueList<int> loc; + int i = 0; + + while(node) + { + i = 1; + while(node->prev) + { + ++i; + node = node->prev; + } + loc.prepend(i); + node = node->parent; + } + return loc; +} + +QValueList<int> kafkaCommon::getLocation(DOM::Node domNode) +{ + QValueList<int> loc; + int i = 0; + + while(!domNode.isNull()) + { + i = 1; + while(!domNode.previousSibling().isNull()) + { + ++i; + domNode = domNode.previousSibling(); + } + loc.prepend(i); + domNode = domNode.parentNode(); + } + return loc; +} + +Node* kafkaCommon::getNodeFromLocation(QValueList<int> loc) +{ + QValueList<int>::iterator it; + Node *node = baseNode; + Node *m = 0L; + int i; + + if(!node) + return 0L; + for(it = loc.begin(); it != loc.end(); ++it) + { + if(!node) + return 0L; + for(i = 1; i < (*it); ++i) + { + if(!node->next) + return 0L; + node = node->next; + } + m = node; + node = node->child; + } + return m; +} + +Node* kafkaCommon::getNodeFromLocation(QValueList<int> loc, Node* nodeTree) +{ + QValueList<int>::iterator it; + Node *node = nodeTree; + if(!node) + node = baseNode; + Node *m = 0L; + int i; + + if(!node) + return 0L; + for(it = loc.begin(); it != loc.end(); ++it) + { + if(!node) + return 0L; + for(i = 1; i < (*it); ++i) + { + if(!node->next) + return 0L; + node = node->next; + } + m = node; + node = node->child; + } + return m; +} + +DOM::Node kafkaCommon::getNodeFromLocation(QValueList<int> loc, DOM::Node rootNode) +{ + QValueList<int>::iterator it; + DOM::Node node = rootNode; + DOM::Node m = rootNode; + int i; + + if(rootNode.isNull()) + return DOM::Node(); + + for(it = loc.begin(); it != loc.end(); ++it) + { + if(node.isNull()) + return DOM::Node(); + for(i = 1; i < (*it); ++i) + { + if(node.nextSibling().isNull()) + return DOM::Node(); + node = node.nextSibling(); + } + m = node; + node = node.firstChild(); + } + return m; +} + +Node* kafkaCommon::getNodeFromSubLocation(QValueList<int> loc, int locOffset) +{ + QValueList<int>::iterator it = loc.begin(); + QValueList<int> list; + int i; + + for(i = 0; i < locOffset; ++i) + { + list.append((*it)); + ++it; + } + + return getNodeFromLocation(list); +} + +Node* kafkaCommon::getNodeFromSubLocation(QValueList<int> loc, int locOffset, Node* nodeTree) +{ + QValueList<int>::iterator it = loc.begin(); + QValueList<int> list; + int i; + + for(i = 0; i != locOffset; ++i) + { + list.append((*it)); + ++it; + } + + return getNodeFromLocation(list, nodeTree); +} + +int kafkaCommon::compareNodePosition(QValueList<int> pos1, QValueList<int> pos2) +{ + QValueList<int>::iterator it1, it2; + + it1 = pos1.begin(); + it2 = pos2.begin(); + while(it1 != pos1.end() && it2 != pos2.end() && (*it1) == (*it2)) + { + it1++; + it2++; + } + + if(it1 == pos1.end() && it2 == pos2.end()) + return kafkaCommon::isAtTheSamePosition; + else if(it1 == pos1.end()) + return kafkaCommon::isBefore; + else if(it2 == pos2.end() || (*it1) > (*it2)) + return kafkaCommon::isAfter; + else if((*it1) < (*it2)) + return kafkaCommon::isBefore; + else + return kafkaCommon::positionError; +} + +int kafkaCommon::compareNodePosition(Node *n1, Node *n2) +{ + QValueList<int> pos1, pos2; + + if(!n1 || !n2) + return kafkaCommon::positionError; + + pos1 = getLocation(n1); + pos2 = getLocation(n2); + + return compareNodePosition(pos1, pos2); +} + +bool kafkaCommon::compareNodes(Node *n1, Node *n2) +{ + int i, j; + + if(!n1 || !n2) + return false; + + if(n1->tag->type != n2->tag->type) + return false; + + if(n1->tag->type == Tag::XmlTag) + { + if(n1->tag->name.lower() != n2->tag->name.lower()) + return false; + + if(n1->tag->attrCount() != n2->tag->attrCount()) + return false; + + for(i = 0; i < n1->tag->attrCount(); ++i) + { + for(j = 0; j < n2->tag->attrCount(); ++j) + { + if(n1->tag->getAttribute(i).name.lower() == n2->tag->getAttribute(j).name.lower() && + n1->tag->getAttribute(i).value.lower() == n2->tag->getAttribute(j).value.lower()) + break; + } + if(j == n2->tag->attrCount()) + return false; + } + } + else if(n1->tag->type == Tag::Text) + { + //TODO + } + + return true; +} + +int kafkaCommon::nodeDepth(Node *node) +{ + int depth = 0; + + if(!node) + return -1; + + node = node->parent; + while(node) + { + depth++; + node = node->parent; + } + + return depth; +} + +Node* kafkaCommon::hasParent(Node *node, const QString &name) +{ + node = node->parent; + while(node) + { + if(node->tag->name.lower() == name.lower()) + return node; + node = node->parent; + } + + return 0L; +} + +Node* kafkaCommon::hasParent(Node* startNode, Node* endNode, const QString &name) +{ + Q_ASSERT(startNode && endNode); + //Andras: don't crash + if (!startNode || !endNode) + return 0; + + QValueList<int> commonParentStartChildLocation; + QValueList<int> commonParentEndChildLocation; + + Node* node = DTDGetCommonParent(startNode, endNode, commonParentStartChildLocation, commonParentEndChildLocation, 0); + + while(node) + { + if(node->tag->name.lower() == name.lower()) + return node; + node = node->parent; + } + + return 0; +} + +bool kafkaCommon::insertDomNode(DOM::Node node, DOM::Node parent, DOM::Node nextSibling, + DOM::Node rootNode) +{ + if(node.isNull()) + return false; + + if(parent.isNull()) + { + if(rootNode.isNull()) + return false; + parent = rootNode; + } + //Andras: avoid exceptions + if (!nextSibling.isNull() && nextSibling.parentNode() != parent) + { + kdDebug(25001)<< "kafkaCommon::insertDomNode() - invalid nextSibling!" << endl; + return false; + } + if (node.ownerDocument() != parent.ownerDocument()) + { + kdDebug(25001)<< "kafkaCommon::insertDomNode() - ownerDocument is different!" << endl; + return false; + } + + try + { + parent.insertBefore(node, nextSibling); + } + catch(DOM::DOMException e) + { + kdDebug(25001)<< "kafkaCommon::insertDomNode() - ERROR code :" << e.code << endl; + } + return true; +} + +bool kafkaCommon::removeDomNode(DOM::Node node) +{ + DOM::Node parent = node.parentNode(); + + if(parent.isNull()) + return false; + + parent.removeChild(node); + + return true; +} + +DOM::Node kafkaCommon::createDomNode(const QString &nodeName, const DTDStruct* dtd, + DOM::Document rootNode) +{ + // FIXME + //this will change with the futur multi-DTDs support + //It does not use exceptions handling, so everything is checked via the DTEP definitions. + DOM::Node dn; + QTag *qTag = 0L; + + qTag = QuantaCommon::tagFromDTD(dtd, nodeName); + + if(qTag) + dn = rootNode.createElement(nodeName); +#ifdef HEAVY_DEBUG + + else + kdDebug(25001)<< "kafkaCommon::createDomNode() - ERROR bad nodeName :" << + nodeName << endl; +#endif + + return dn; +} + +DOM::Node kafkaCommon::createDomNode(Node *node, DOM::Document rootNode) +{ + if(!node) + return DOM::Node(); + + return createDomNode(node->tag->name, node->tag->dtd(), rootNode); +} + +DOM::Node kafkaCommon::createTextDomNode(const QString &textString, DOM::Document rootNode) +{ + return rootNode.createTextNode(textString); +} + +DOM::Node kafkaCommon::createDomNodeAttribute(const QString &nodeName, const DTDStruct* dtd, + const QString &attrName, const QString &attrValue, DOM::Document rootNode) +{ + DOM::Node attr; + QTag *qTag = 0L; + + qTag = QuantaCommon::tagFromDTD(dtd, nodeName); + if(!qTag) + return DOM::Node(); + + if(qTag->isAttribute(attrName)) + { + attr = rootNode.createAttribute(attrName); + attr.setNodeValue(attrValue); + } +#ifdef HEAVY_DEBUG + else + kdDebug(25001)<< "kafkaCommon::createDomNodeAttribute() - ERROR bad attrName " << + attrName << endl; +#endif + + return attr; +} + +DOM::Node kafkaCommon::createDomNodeAttribute(Node* node, const QString &attrName, + DOM::Document rootNode) +{ + if(!node) + return DOM::Node(); + + return createDomNodeAttribute(node->tag->name, node->tag->dtd(), attrName, "", rootNode); +} + +//DOM::node kafkaCommon::createDomNodeAttribute(DOM::Node node, const QString &attrName, +// DOM::Document rootNode) +//{ +/**if(node.isNull()) + return DOM::node(); + +return createDomNodeAttribute()*/ +//} + +bool kafkaCommon::insertDomNodeAttribute(DOM::Node node, DOM::Node attr) +{ + if(node.isNull()) + return false; + + //should we check if the attr is valid??? + node.attributes().setNamedItem(attr); + + return true; +} + +bool kafkaCommon::editDomNodeAttribute(DOM::Node node, const QString &nodeName, const DTDStruct* dtd, + const QString &attrName, const QString &attrValue, DOM::Document rootNode) +{ + DOM::Node attr; + + if(node.isNull()) + return false; + + attr = node.attributes().getNamedItem(attrName); + if(attr.isNull()) + { + //let's create it + attr = createDomNodeAttribute(nodeName, dtd, attrName, attrValue, rootNode); + if(attr.isNull()) + return false; + insertDomNodeAttribute(node, attr); + } + + return true; +} + +bool kafkaCommon::editDomNodeAttribute(DOM::Node domNode, Node* node, + const QString &attrName, const QString &attrValue, DOM::Document rootNode) +{ + if(!node) + return false; + + return editDomNodeAttribute(domNode, node->tag->name, node->tag->dtd(), + attrName, attrValue, rootNode); +} + +DOM::Node kafkaCommon::hasParent(DOM::Node domNode, const QString &name) +{ + while(!domNode.isNull()) + { + if(domNode.nodeName().string().lower() == name.lower()) + return domNode; + domNode = domNode.parentNode(); + } + + return DOM::Node(); +} + +int kafkaCommon::childPosition(DOM::Node domNode) +{ + DOM::Node parentNode, child; + int position = 1; + + if(domNode.isNull()) + return -1; + + parentNode = domNode.parentNode(); + child = parentNode.firstChild(); + while(!child.isNull() && child != domNode) + { + position++; + child = child.nextSibling(); + } + + if(child == domNode) + return position; + else + return -1; +} + +DOM::Node kafkaCommon::getChildNode(DOM::Node parentNode, int position, bool fallback) +{ + DOM::Node child; + + if(parentNode.isNull()) + return DOM::Node(); + + child = parentNode.firstChild(); + while(!child.isNull() && position > 1 && ((fallback && !child.nextSibling().isNull()) || !fallback )) + { + child = child.nextSibling(); + position--; + } + + return child; +} + +bool kafkaCommon::isInline(DOM::Node domNode) +{ + if(domNode.isNull()) + return false; + + if(domNode.nodeType() == DOM::Node::TEXT_NODE) + return true; + + return isInline(domNode.nodeName().string()); +} + +bool kafkaCommon::parentSupports(DOM::Node parent, DOM::Node startNode, DOM::Node endNode, + const DTDStruct* dtd) +{ + QTag *parentQTag; + DOM::Node child; + + if(!dtd || parent.isNull()) + return false; + + parentQTag = QuantaCommon::tagFromDTD(dtd, parent.nodeName().string()); + + if(!parentQTag) + return false; + + child = startNode; + while(!child.isNull()) + { + if(!parentQTag->isChild(child.nodeName().string())) + return false; + if(child == endNode) + return true; + child = child.nextSibling(); + } + + return true; +} + +bool kafkaCommon::isInline(const QString &nodeNam) +{ + QString nodeName = nodeNam.lower(); + if(nodeName == "q" || nodeName == "u" || nodeName == "i" || nodeName == "b" || + nodeName == "cite" || nodeName == "em" || nodeName == "var" || nodeName == "em" || + nodeName == "tt" || nodeName == "code" || nodeName == "kbd" || nodeName == "samp" || + nodeName == "big" || nodeName == "small" || nodeName == "s" || nodeName == "strike" || + nodeName == "sub" || nodeName == "sup" || nodeName == "abbr" || + nodeName == "acronym" || nodeName == "a" || nodeName == "bdo" || + nodeName == "font" || nodeName == "#text" || nodeName == "strong" || nodeName == "dfn" || + nodeName == "img" || nodeName == "applet" || nodeName == "object" || nodeName == "basefont" || + nodeName == "br" || nodeName == "script" || nodeName == "map" || nodeName == "span" || + nodeName == "iframe" || nodeName == "input" || nodeName == "select" || nodeName == "textarea" || + nodeName == "label" || nodeName == "button" ) + return true; + else + return false; +} + +#ifdef HEAVY_DEBUG +void kafkaCommon::coutDomTree(DOM::Node rootNode, int indent) +#else +void kafkaCommon::coutDomTree(DOM::Node, int) +#endif +{ +#ifdef HEAVY_DEBUG + QString output, dots; + int j; + DOM::Node node; + if(rootNode.isNull()) + kdDebug(25001)<< "kafkaCommon::coutDomTree() - bad node!" << endl; + + node = rootNode; + while (!node.isNull()) + { + dots = ""; + dots.fill('_', indent); + output = dots; + if (node.nodeType() != DOM::Node::TEXT_NODE) + output += node.nodeName().string().replace('\n'," "); + else + { + output += "\""; + output+= node.nodeValue().string().replace('\n'," "); + output += "\""; + } + kdDebug(25001) << output <<" (" << node.nodeType() << ") "<< + node.handle() << endl; + kdDebug(25001)<< dots << " +++ prev " << node.previousSibling().handle() << " next " << + node.nextSibling().handle() << " parent " << + node.parentNode().handle() << " child " << node.firstChild().handle() << endl; + for(j = 0; j < (int)node.attributes().length(); ++j) + { + kdDebug(25001)<< dots << " *** attr" << j << " " << + node.attributes().item(j).nodeName().string() << " - " << + node.attributes().item(j).nodeValue().string() << endl; + } + + if (node.hasChildNodes()) + coutDomTree(node.firstChild(), indent + 4); + node = node.nextSibling(); + } +#endif +} + +void kafkaCommon::coutTree(Node *node, int indent) +{ + QString output, dots; + int bLine, bCol, eLine, eCol, j; + if(!node) + kdDebug(25001)<< "kafkaCommon::coutTree() - bad node!" << endl; + + while (node) + { + dots = ""; + dots.fill('.', indent); + output = dots; + node->tag->beginPos(bLine, bCol); + node->tag->endPos(eLine, eCol); + if (node->tag->type == Tag::XmlTag || node->tag->type == Tag::XmlTagEnd || + node->tag->type == Tag::ScriptTag) + output += node->tag->name.replace('\n',"<return>"); + else + { + output += "\""; + output+= node->tag->tagStr().replace('\n',"<return>"); + output += "\""; + } + kdDebug(25001) << output <<" (" << node->tag->type << ", " << node->tag->cleanStrBuilt() << ", " << + node->tag->indentationDone() << ") "<< node << " at pos " << bLine << ":" << bCol << " - " << + eLine << ":" << eCol << endl; + kdDebug(25001)<< dots << " +++ prev " << node->prev << " next " << node->next << " parent " << + node->parent << " child " << node->child << endl; + for(j = 0; j < node->tag->attrCount(); ++j) + { + kdDebug(25001)<< dots << " *** attr" << j << " " << + node->tag->getAttribute(j).nameLine << ":" << + node->tag->getAttribute(j).nameCol << ":" << + node->tag->getAttribute(j).name << " - " << + node->tag->getAttribute(j).valueLine << ":" << + node->tag->getAttribute(j).valueCol << ":" << + node->tag->getAttribute(j).value << endl; + } + + if (node->child) + coutTree(node->child, indent + 4); + if(node == node->next || (node->next && node == node->next->next) || + (node->next && node->next->next && node == node->next->next->next) || + (node->next && node->next->next && node->next->next->next && + node == node->next->next->next->next) || (node->next && node->next->next && + node->next->next->next && node->next->next->next->next && node == + node->next->next->next->next->next)) + { + //try to detect invalid pointers. + kdDebug(25001)<< "ERROR - node == node->[..]next" << endl; + return; + } + node = node->next; + } +} + +int kafkaCommon::isInsideTag(Node* start_node, Node* end_node, QString const& tag_name) +{ + Q_ASSERT(start_node && end_node); + //Andras: don't crash + if (!start_node || !end_node) + return -1; + + Node* tag_start = hasParent(start_node, end_node, tag_name); + if(tag_start) + return 1; // both start_node and end_node are surrounded by tag_name + + tag_start = hasParent(start_node, tag_name); + if(tag_start) + return 0; // only start_node has tag_name as parent + + tag_start = hasParent(end_node, tag_name); + if(tag_start) + return 0; // only end_node has tag_name as parent + + return -1; // neither the nodes have tag_name as parent +} + +int kafkaCommon::isInsideTag(Node* start_node, Node* end_node, QString const& tag_name, + QString const& attribute_name, QString const& attribute_value) +{ + Q_ASSERT(start_node && end_node); + //Andras: don't crash + if (!start_node || !end_node) + return -1; + + Node* tag_start = hasParent(start_node, end_node, tag_name); + if(tag_start && tag_start->tag->hasAttribute(attribute_name) && tag_start->tag->attributeValue(attribute_name, true) == attribute_value) + return 1; // both start_node and end_node are surrounded by tag_name + + tag_start = hasParent(start_node, tag_name); + if(tag_start && tag_start->tag->hasAttribute(attribute_name) && tag_start->tag->attributeValue(attribute_name, true) == attribute_value) + return 0; // only start_node has tag_name as parent + + tag_start = hasParent(end_node, tag_name); + if(tag_start && tag_start->tag->hasAttribute(attribute_name) && tag_start->tag->attributeValue(attribute_name, true) == attribute_value) + return 0; // only end_node has tag_name as parent + + return -1; // neither the nodes have tag_name as parent +} + +bool kafkaCommon::isBetweenWords(Node* node, int offset) +{ + Q_ASSERT(node->tag->type == Tag::Text || node->tag->type == Tag::Empty); + Q_ASSERT(offset >= 0); + Q_ASSERT(node); + if (!node) + return false; //FIXME: Andras: don't crash + + QString tag_str = node->tag->tagStr(); + + return ! + (tag_str[offset].isSpace() || tag_str[offset].isPunct() || + tag_str[offset - 1].isSpace() || tag_str[offset - 1].isPunct());/* || + tag_str[offset + 1].isSpace() || tag_str[offset + 1].isPunct());*/ +} + +void kafkaCommon::getStartOfWord(Node*& node, int& offset) +{ + Q_ASSERT(node); +// Q_ASSERT(isBetweenWords(node, offset)); recursive + Q_ASSERT(offset >= 0); + //Andras: don't crash + if (!node || offset < 0) + return; + + kdDebug(23100) << "getStartOfWord node length: " << node->tag->tagStr().length() << endl; + kdDebug(23100) << "getStartOfWord offset BEGIN: " << offset << endl; + + QString tag_str = node->tag->tagStr(); + while(offset >= 0 && !tag_str[offset].isSpace() && !tag_str[offset].isPunct()) + --offset; + + if(offset == -1) + { + Node* aux = node->previousSibling(); + while(aux && aux->tag->type != Tag::Text) + { + if(!isInline(aux->tag->name)) + { + ++offset; + return; + } + + aux = aux->previousSibling(); + } + if(aux) + { + node = aux; + offset = aux->tag->tagStr().length() - 1; + kdDebug(23100) << "getStartOfWord node length: " << node->tag->tagStr().length() << endl; + kdDebug(23100) << "getStartOfWord offset RECURS: " << offset << endl; + getStartOfWord(node, offset); + return; + } + } + ++offset; + kdDebug(23100) << "getStartOfWord node length: " << node->tag->tagStr().length() << endl; + kdDebug(23100) << "getStartOfWord offset END: " << offset << endl; +} + +void kafkaCommon::getEndOfWord(Node*& node, int& offset) +{ + Q_ASSERT(node); +// assert(isBetweenWords(node, offset)); recursive + Q_ASSERT(isBetweenWords(node, offset)); + Q_ASSERT(offset >= 0); + + //Andras: if the following asserts are hit, don't do anything = don't crash + if (!node || !isBetweenWords(node, offset) || offset < 0) + return; + + + QString tag_str = node->tag->tagStr(); + while((uint)offset != tag_str.length() && !tag_str[offset].isSpace() && !tag_str[offset].isPunct()) + ++offset; + + if((uint)offset == tag_str.length()) + { + Node* aux = node->nextSibling(); + while(aux && aux->tag->type != Tag::Text) + { + if(!isInline(aux->tag->name)) + return; + + aux = aux->nextSibling(); + } + if(aux) + { + node = aux; + offset = 0; + getEndOfWord(node, offset); + } + } +} + +void kafkaCommon::getStartOfParagraph(Node*& node, int& offset) +{ + Q_ASSERT(node); + //Andras: don't crash + if (!node) + { + offset = 0; + return; + } + + Node* previous = node->previousSibling(); + while(previous && (isInline(previous->tag->name) || previous->tag->name.lower() == "br" || previous->tag->type == Tag::Text)) + previous = previous->previousSibling(); + + offset = 0; + if(previous) + { + node = previous->nextSibling(); + return; + } + Q_ASSERT(node->tag->type == Tag::Text); +} + +void kafkaCommon::getEndOfParagraph(Node*& node, int& offset) +{ + Q_ASSERT(node); + if (!node) + { + offset = 0; + return; + } + + Node* begin_paragraph = node; + getStartOfParagraph(begin_paragraph, offset); + + Node* next = begin_paragraph->nextSibling(); + while(nodeDepth(next) > nodeDepth(begin_paragraph)) + next = next->nextSibling(); + while(next && (isInline(next->tag->name) || next->tag->name.lower() == "br" || next->tag->type == Tag::Text)) + { + next = next->nextSibling(); + while(nodeDepth(next) > nodeDepth(node)) + next = next->nextSibling(); + } + if(next) + { + node = next; + if(nodeDepth(next) < nodeDepth(begin_paragraph)) + node = node->previousSibling(); + + if(node->tag->type == Tag::Text) + offset = node->tag->tagStr().length() - 1; + else + offset = 0; + return; + } +} + + diff --git a/quanta/parts/kafka/kafkacommon.h b/quanta/parts/kafka/kafkacommon.h new file mode 100644 index 00000000..d71783aa --- /dev/null +++ b/quanta/parts/kafka/kafkacommon.h @@ -0,0 +1,1177 @@ +/*************************************************************************** + kafkacommon.h + ------------------- + + copyright : (C) 2003, 2004 - Nicolas Deschildre + email : ndeschildre@kdewebdev.org + ***************************************************************************/ + +/*************************************************************************** + * * + * 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. * + * * + ***************************************************************************/ + +#ifndef KAFKACOMMON_H +#define KAFKACOMMON_H + +#include <qvaluelist.h> +#include <dom/dom_node.h> + +namespace DOM +{ + class Document; +} +class Node; +class NodeModifsSet; +class NodeSelection; +class NodeSelectionInd; +class Document; +struct DTDStruct; + +/** + * For heavy debug including Node Tree in stdout printing, a DOM::Node tree widget. + */ +//#define HEAVY_DEBUG + +/** + * Light debugging, including functions name in stdout printing. + */ +//#define LIGHT_DEBUG + + +/** This class gathers all the basic functions needed by kafka. + * It's very useful for manipulating nodes. + */ + +class kafkaCommon +{ +public: + kafkaCommon() {} + ~kafkaCommon() {} + + /** ----------------------- NODE & DOM::NODE TREE NAVIGATION -------------------------------------*/ + + /** + * This function returns the next Node after node : the first child of + * node if available, else its next sibling if available, else the next + * available next sibling of a parent of node. + * @param _node It is the Node from which we want the next Node. + * @param goUp This boolean specifies if we should go up (torwards the root Node) + * i.e. not looking at the childs of node, or make a standart iteration. + * For a normal use, It must be set to false at the + * beginning and then the same boolean must be used when using + * several times this function. + * @param endNode Specifies at which Node the search should end. + * @return Returns the next Node. + */ + static Node* getNextNode(Node *node, bool &goUp, Node *endNode = 0L); + + /** + * It behaves essentially like the above function except that it will skip empty Nodes. + */ + static Node* getNextNodeNE(Node *node, bool &goUp, Node *endNode = 0L); + + /** + * This function returns the prev Node after node. + */ + static Node* getPrevNode(Node *node, Node *endNode = 0L); + + /** + * It behaves essentially like the above function except that it will skip empty Nodes. + */ + static Node* getPrevNodeNE(Node *node, Node *endNode = 0L); + + + /** + * Returns the first common parent to startNode and endNode that isn't inline. + * @param startNode Start node. + * @param endNode End node. + * @param commonParentStartChildLocation Is the child of commonParent which is parent of startNode + * @param commonParentEndChildLocation Is the child of commonParent which is parent of endNode + * @param nodeSubtree When startNode and endNode doesn't belong to the current document tree. Pass 0 if they do. + * @return The first, non inline, common parent of startNode and endNode. + */ + static Node* DTDGetNonInlineCommonParent(Node* startNode, Node* endNode, + QValueList<int>& commonParentStartChildLocation, + QValueList<int>& commonParentEndChildLocation, Node* nodeSubtree); + + /** + * Same as above, only that the common parent can be inline. + */ + static Node* DTDGetCommonParent(Node* startNode, Node* endNode, + QValueList<int>& commonParentStartChildLocation, + QValueList<int>& commonParentEndChildLocation, Node* nodeSubtree); + + /** + * This function returns the next DOM::Node after node : the first child of + * DOM::Node if available, else its next sibling if available, else the next + * available next sibling of a parent of node. + * @param node The DOM::Node the search starts from. + * @param goUp This boolean specifies if we should go up or down in the tree. + * For a normal use, It must be set to false at the + * beginning and then the same boolean must be used when using + * several times this function. + * @param returnParentNode Specifies if there are no child and next sibling, if + * we should return the parent. + * @param endNode Specifies at which DOM::Node the search should end. It is useful + * when setting returnParentNode to false. + * @return the next Node. + */ + static DOM::Node getNextDomNode(DOM::Node node, bool &goUp, bool returnParentNode = false, + DOM::Node endNode = DOM::Node()); + + /** + * This function returns the previous Node after node. + * @return Returns the previous DOM::Node of node. + */ + static DOM::Node getPrevDomNode(DOM::Node node, DOM::Node endNode = DOM::Node()); + + /** + * If node is not a text node or the cursor is at the end of node's tag string, this method return the next + * text node at offset 0, or a null pointer. + * This is particular useful when finding start and end nodes of a selection, because you can have a closing node + * as the start node, etc. + * @param startNode The current start node. startNode isn't changed inside the method. + * @param startOffset The current start offset. startOffset is changed inside the method. + * @return The next text node or a null pointer. + */ + static Node* getCorrectStartNode(Node* startNode, int& startOffset); + + /** + * If node is not a text node or the cursor is at the beggining of node's tag string, this method return the previous + * text node at offset at the end of the tag string, or a null pointer. + * This is particular useful when finding start and end nodes of a selection, because you can have a closing node + * as the start node, etc. + * @param endNode The current start node. + * @param endOffset The current start offset. + * @return The next text node or a null pointer. + */ + static Node* getCorrectEndNode(Node* endNode, int& endOffset); + + /** + * Get the first child of commonParent which is parent of node + * @param node + * @param commonParent + * @return + */ + static Node* getCommonParentChild(Node* node, Node* commonParent); + + + /** ----------------------- NODE INDENTATION STUFF -------------------------------------*/ + + /** + * This function takes care to modify the current node or/and the next sibling in order to have a nice + * indentation. WARNING it doesn't create the necessary Nodes. Call fitIndentationNodes first. + * @param node Apply nice indentation to this Node. + * @param nbOfSpaces Specifies the number of spaces the minimal indentation must be. + * @param nbOfTabs Specifies the number of tabs the minimal indentation must be. + * @param modifs The changes made are logged into modifs. + * @param inlineNodeIndentation + * If true: + * <body>Text</body> + * else: + * <body> + * Text + * </body> + */ + static void applyIndentation(Node *node, int nbOfSpaces, int nbOfTabs, NodeModifsSet *modifs, + bool inlineNodeIndentation = false); + + /** + * Create/Delete the necessary Empty Nodes between n1 and n2 so that a nice indentation can be + * generated by the undoRedo system. The undoRedo system can't create itself these Nodes + * because it will create them only during the synchronization, making all the QValueList<int> + * Nodes location wrong. + * WARNING n1 and n2 must be siblings or parent-child. If there are sibling and n1 is a XmlTag, + * n1 should not have non empty childs. + * @param n1 The start node. + * @param n2 The end node. + * @param modifs The changes made are logged into modifs. + */ + static void fitIndentationNodes(Node *n1, Node *n2, NodeModifsSet *modifs); + + /** + * Fits the Nodes positions after a change in the Node tree. + * @param startNode The Node where the update of the Node positions starts. + * @param colMovement The number of columns that should be + * added/retrieved from the column position. It is the difference of the new last char col position and the + * old last char col position. + * @param lineMovement The number of lines that should be + * added/retrieved from the line position. It is the difference of the number of lines of the new tag string + * and the number of lines of the old tag string. + * @param colEnd The column position where the update should stop. + * @param lineEnd The line position where the update should stop. + */ + static void fitsNodesPosition(Node* startNode, int colMovement, int lineMovement = 0, + int colEnd = -2, int lineEnd = -2); + + /** + * Get the display type of a Node. NOT an official list, more a little hack to + * handle the indentation. Text are inline. The rest return an error. + * @param closingNodeToo Specifies if we consider that closing Node have the same type as + * their opening tag. + * @return Returns the type. + */ + static int getNodeDisplay(Node *node, bool closingNodeToo); + + //the enumeration of the different display types + enum nodeDisplay + { + noneDisplay = 0, + inlineDisplay, + blockDisplay, + errorDisplay + }; + + /** + * Remove the indentation whitespaces in a string + * e.g. this function returns : " a b cd " for parameter: " a b cd " + * @param string The text to modify. + * @param removeAllSpacesAtTheLeft Specifies if it should remove ALL spaces in the left + * unlike the above example. + * @param removeAllSpacesAtTheRight Specifies if it should remove ALL spaces in the right + * unlike the above example. + * @return Returns the modified string. + */ + static QString removeUnnecessaryWhitespaces(const QString &string, + bool removeAllSpacesAtTheLeft = false, bool removeAllSpacesAtTheRight = false); + + + /** ----------------------- NODE TREE MODIFICATIONS -------------------------------------*/ + + /** + * Create a simple Node, without taking care of building the closing Node. + * @param nodeName The name of the Node. + * @param tagString The String of the tag as it will appears in the editor. + * @param nodeType The node type, cf Tag::TokenType + * @param doc The document the Node will belong to. + * @return Returns the newly created node. + */ + static Node* createNode(const QString &nodeName, const QString &tagString, int nodeType, + Document *doc); + + /** + * Restore a Node that has been pasted, i.e., his doc and dtd pointers. + * @param node The Node to be restored. + * @param doc The document the Node will belong to. + */ + static void restorePastedNode(Node* node, Document* doc); + + /** + * Create a !doctype Node with all the necessary attributes. It has a child and a closing Node. + * @param doc It needs the document where the !doctype node will be inserted in order to + * build the right attributes. + */ + static Node *createDoctypeNode(Document *doc); + + /** + * Create a <?xml ... ?> Node. It has a child and a closing Node. + * @param doc It needs the document where the xml node will be inserted. + * @param encoding The encoding to use (usually get it with quantaApp->defaultEncoding()) + */ + static Node *createXmlDeclarationNode(Document *doc, const QString &encoding); + + /** + * Create a node subtree which contains the mandatory Nodes in order to be DTD compliant. + * e.g. TABLE alone isn't DTD compliant, this function will return TABLE->TR->TD. + * WARNING : it won't log change thus node must NOT be in the Node tree. + * @param node The root Node of the Node subtree. + * @param doc The document the Node subtree will belong to. + * @return Returns the last Node of the subtree or node if there was nothing to add. + */ + static Node* createMandatoryNodeSubtree(Node *node, Document *doc); + + /** + * Insert node in the tree. WARNING This function will log that node was added. + * WARNING : baseNode is used as the rootNode. + * It will also try to merge text/Empty Nodes. + * @param node The node to insert. + * @param parentNode This Node will be the parent of node. + * @param nextSibling This Node will be the next Sibling of Node. If null, node will be appended at + * the child list of parentNode. + * TODO: @param rootNode The rootNode of the tree we want to insert the Node (usually &baseNode). + * @param modifs The changes made are logged into modifs. Put 0L if you don't want to log + * and if you know what you're doing! + * @param merge Try to merge with the siblings if possible. + * @return Returns a pointer to the node inserted. + */ + static Node* insertNode(Node *node, Node* parentNode, Node* nextSibling, + NodeModifsSet *modifs/**, Node **rootNode*/, bool merge = true); + + static Node* insertNode(Node *node, Node* parentNode, Node* nextSibling, NodeSelection& selection, + NodeModifsSet *modifs, bool merge = true); + + /** + * It behaves essentially like the above function except that it can "surround" a set of Nodes with the + * new Node. Thus, the closing Node is created if necessary. + * nextSibling and nextEndSibling MUST have the same parent. If not, use the + * DTDinsertNode. + * This function does not try to know if the location of the new Node is DTD valid. + * @param newNode The new Node to insert. + * @param parent The parent of the Node. + * @param nextSibling The next sibling of the Node. + * @param nextEndSibling The next sibling of the closing Node if created. If nextEndSibling == + * nextSibling, the closing Node will be placed at the right of the newly created Node. + * All the Nodes between the new Node and its closing Tag will be moved as childs of the new Node. + * @param modifs The changes made are logged into modifs. + * @return Returns a pointer to the node inserted. + */ + static Node *insertNode(Node *newNode, Node *parent, Node *nextSibling, Node *nextEndSibling, + NodeModifsSet *modifs, bool merge = true); + + /** + * It behaves essentially like the above function except that it can split the endNodeToSurround and + * startNodeToSurround if necessary, according to the offsets. + * startNodeToSurround et endNodeToSurround MUST have the same parent. If not, use the last + * DTDinsertNode. + * This function does not try to know if the location of the new Node is valid. + * @param startNodeToSurround The first Node which will be enclosed by the new Node. + * @param endNodeToSurround The last Node which will be enclosed by the new Node. + * @param startOffset The first Node will be splitted at offset startOffset, the right part will be enclosed. + * @param endOffset The last Node will be splitted at offset endOffset, the left part will be enclosed. + */ + static Node* insertNode(Node *newNode, Node *parent, Node *startNodeToSurround, + Node *endNodeToSurround, int startOffset, int endOffset, NodeModifsSet *modifs); + + /** + * It behaves essentially like the above function except that it will insert the new Node only + * if the DTD allows it. The new Tag can surround any subtree. If + * necessary, several copies of the Node will be used. + * This function takes care of the DTD validity of the Nodes created. + * It will build the necessary mandatory Nodes (e.g. insertion of TABLE will also insert TR and TD). + * This is the key function making the toolbars working. + * @param startNode The first Node which must be surrounded by the new Node. + * @param startOffset If firstNode is a text, specify at which offset the new Node must begin to surround. + * @param endNode The last Node which must be surrounded by the new Node. + * @param endOffset If endNode is a text, specify at which offset the new Node must stop to surround. + * @param doc The document is needed in order to build the mandatory Node tree if necessary. + * <TEMPORARY> : We want to keep track of the cursor position. TODO : cursor class + * @param cursorNode The cursor is inside cursorNode. + * @param cursorOffset The offset of the cursor inside cursorNode. + * </TEMPORARY> + * @return Returns false if it wasn't possible to insert the tag because e.g. of an invalid parent. + */ + static bool DTDinsertNode(Node *newNode, Node *startNode, int startOffset, Node *endNode, + int endOffset, Document *doc, Node **cursorNode, long &cursorOffset, NodeModifsSet *modifs); + + /** + * It behaves essentially like the above function except that it will try first to remove newNode + * from the area, by calling DTDExtractNode. If newNode wasn't present, it will then insert it by + * calling DTDinsertNode. + * This is the key function making the toolbars working. + * @return Returns true if a modification was done (Node inserted/removed) + */ + static bool DTDinsertRemoveNode(Node *newNode, Node *startNode, int startOffset, Node *endNode, + int endOffset, Document *doc, Node **cursorNode, long &cursorOffset, NodeModifsSet *modifs); + + /** + * Insert a node subtree in the tree. WARNING This function will log that the nodes were added. + * WARNING : baseNode is used as the rootNode. + * It will also try to merge text/Empty Nodes. + * @param node The root node of the Node subtree to insert. + * @param parentNode This Node will be the parent of node. + * @param nextSibling This Node will be the next Sibling of Node. If null, node will be appended at + * the child list of parentNode. + * @param modifs The changes made are logged into modifs. Put 0L if you don't want to log + * and if you know what you're doing! + * @param merge Try to merge with the siblings if possible. + * @return Returns a pointer to the node inserted. + */ + static Node* insertNodeSubtree(Node *node, Node* parentNode, Node* nextSibling, + NodeModifsSet *modifs, bool merge = true); + + /** + * It behaves essentially like the above function except that it can "surround" a set of Nodes with the + * new Node. Thus, the closing Node is created if necessary. + * nextSibling and nextEndSibling MUST have the same parent. If not, use the + * DTDinsertNode. + * The Node Subtree MUST be a single-Node-per-parent subtree. + * This function does not try to know if the location of the new Node is DTD valid. + * @param node The root node of the Node subtree to insert. + * @param parent The parent of the Node. + * @param nextSibling The next sibling of the Node. + * @param nextEndSibling The next sibling of the closing Node if created. If nextEndSibling == + * nextSibling, the closing Node will be placed at the right of the newly created Node. + * All the Nodes between the new Node and its closing Tag will be moved as childs of the + * last Node of the Node subtree.. + * @param modifs The changes made are logged into modifs. + * @return Returns a pointer to the node inserted. + */ + static Node* insertNodeSubtree(Node *node, Node* parentNode, Node* nextSibling, + Node* nextEndSibling, NodeModifsSet *modifs, bool merge = true); + + /** + * Split the Nodes as necessary, then check that the subtree is allowed to be inserted + * and then insert the subtree. + * @param node The root node of the Node subtree to insert. + * @param selection contains the cursor Node where the insertion will take place. + * @param modifs The changes made are logged into modifs. + */ + static Node* DTDInsertNodeSubtree(Node *node, NodeSelectionInd& selection, + Node **cursorNode, long& cursorOffset, NodeModifsSet *modifs); + + static Node* DTDInsertNodeSubtree(Node* newNode, Node* parentNode, Node* nextSibling, + NodeSelection& cursorHolder, NodeModifsSet *modifs); + + /** + * Create a Node of name nodeName, of type nodeType, (see tag.h) connected to the document doc, + * and nextSibling as Node's next sibling. + * This function does not try to know if the location of the new Node is valid. + * @param nodeName The node's name of the node to create. + * @param tagString The string of the tag. + * @param nodeType The type of the Node cf Tag::TokenType. + * @param doc The Node belongs to this Document. + * @param parent The parent of the Node. + * @param nextSibling The next sibling of the Node. + * @return Returns a pointer to the newly created Node. + */ + static Node *createAndInsertNode(const QString &nodeName, const QString &tagString, int nodeType, + Document *doc, Node* parent, Node* nextSibling, NodeModifsSet *modifs, bool merge = true); + + /** + * It behaves essentially like the above function except that it reate its closing Node if necessary + * and then insert them with parent as Node's parent. + * nextSibling and nextEndSibling MUST have the same parent. If not, use the + * DTDcreateAndInsertNode. + * @param nextEndSibling The next sibling of the closing Node if created. If nextEndSibling == + * nextSibling, the closing Node will be placed at the right of the newly created Node. + * All the Nodes between the new Node and its closing Tag will be moved as childs of the new Node. + * @param modifs The changes made are logged into modifs. + */ + static Node *createAndInsertNode(const QString &nodeName, const QString &tagString, int nodeType, + Document *doc, Node *parent, Node *nextSibling, Node *nextEndSibling, NodeModifsSet *modifs); + + /** + * It behaves essentially like the above function except that if necessary, it will split the Nodes. + * startNodeToSurround et endNodeToSurround MUST have the same parent. If not, use the + * DTDcreateAndInsertNode. + * This function does not try to know if the location of the new Node is valid. + * @param startNodeToSurround The first Node which will be enclosed by the new Node. + * @param endNodeToSurround The last Node which will be enclosed by the new Node. + * @param startOffset The first Node will be splitted at offset startOffset, the right part will be enclosed. + * @param endOffset The last Node will be splitted at offset endOffset, the left part will be enclosed. + */ + static Node *createAndInsertNode(const QString &nodeName, const QString &tagString, + int nodeType, Document *doc, Node *parent, Node *startNodeToSurround, + Node *endNodeToSurround, int startOffset, int endOffset, NodeModifsSet *modifs); + + /** + * It behaves essentially like the above function except that it will insert the new Node only + * if the DTD allows it. The new Tag can surround any subtree. If + * necessary, several copies of the Node will be used. + * This function takes care of the DTD validity of the Nodes created. + * It will build the necessary mandatory Nodes (e.g. insertion of TABLE will also insert TR and TD). + * This is the key function making the toolbars working. + * @param startNode The first Node which must be surrounded by the new Node. + * @param startOffset If firstNode is a text, specify at which offset the new Node must begin to surround. + * @param endNode The last Node which must be surrounded by the new Node. + * @param endOffset If endNode is a text, specify at which offset the new Node must stop to surround. + * @return Returns false if it wasn't possible to insert the tag because e.g. of an invalid parent. + */ + static bool DTDcreateAndInsertNode(const QString &nodeName, const QString &tagString, int nodeType, + Document *doc, Node *startNode, int startOffset, Node *endNode, int endOffset, + Node **cursorNode, long &cursorOffset, NodeModifsSet *modifs); + + /** + * For internal use. From startNode to endNode, it add where possible/necessary a new Node in order + * to surround the maximum of Nodes. This is used by the above function. This function calls itself. + * @param newNode The root Node of the node subtree to insert. + * @param leafNode The leaf Node of the node subtree to insert. + * @param startExaminationNode It will start examine Nodes from startExaminationNode. + * @param endExaminationNode It will stop examine Nodes from endExaminationNode. + * @param startNode This function will start adding newNode from startNode. + * @param endNode This function will stop adding newNode at endNode. + * @param currentNode This node is currently examined. + * @param examinationStarted Specifies if we have begun to examine the Nodes. + * @param addingStarted Specifies if we have begun to add the new Node. + * @param nodeInserted Returns true if newNode was inserted at least once. Set to false before calling the function. + * @level The relative level of the current Node Sibling (level 0 : root Node, level 1 : childs, and so on...) + * MUST BE set to 0. + */ + static bool addNodeRecursively(Node *newNode, Node *leafNode, Node *startExaminationNode, + Node *endExaminationNode, Node* startNode, Node *endNode, Node* currentNode, + bool &examinationStarted, bool &addingStarted, bool &nodeInserted, int level, NodeModifsSet *modifs); + + /** + * Create a copy of Node. It use the Node copy operator and add some kafka-specific flags : + * It set the node->tag->cleanStrBuilt and node->tag->indentationDone to false; + * @param node The node to duplicate. + * @return Returns the duplicated Node. I wonder if i should always write so obvious things ;-) + */ + static Node *duplicateNode(Node *node); + + /** + * It behaves essentially like the above function except that it can handle a node Subtree. + * INNEFICIENT for bi + */ + static Node *duplicateNodeSubtree(Node *node, bool childAndClosingTagOnly = false); + + /** + * Returns the closing tag of node or its last child or itself. + * @param node + * @return + */ + static Node* getLastChild(Node* node); + + /** + * Extract a Node from the Node Tree. WARNING this will log that the Node was removed. + * This mean that the undo/redo system will delete it when necessary so don't reuse it!!!! + * @param node The node to delete. + * @param modifs The changes made are logged into modifs. + * @param extractChilds If we extract or move up the children. WARNING: it don't check + * if the children of node are legal childs of the parent of node. + * @param removeClosingTag Extract the closingTag if node isn't single and is Tag::XmlTag. + * TODO: @param removeEmbeddedTags Specifies if we delete the embedded Nodes e.g. + * <a href="<? boo ?>" > : the PHP block is an embedded block. + * @return Returns the node extracted with its childs + */ + static Node* extractNode(Node *node, NodeModifsSet *modifs, bool extractChildren = true, + bool extractClosingTag = false/**, bool removeEmbeddedTags = false*/); + + /** + * It behaves essentially like the above function. + * Extract and BUT NOT DELETE RIGHT NOW node from the Tree. The undo/redo system will delete it + * when necessary. + * TODO: remove it, and use extractNode instead. + * @param deleteClosingTag Delete the closingTag if node isn't single. + */ + static void extractAndDeleteNode(Node *node, NodeModifsSet *modifs, bool deleteChildren = true, + bool deleteClosingTag = true, bool mergeAndFormat = true); + + /** + * Extract a node subtree in the tree. WARNING This function will log that the nodes were added. + * This funtion not only extract the start node but also will extract inline parents. + * @param startNode The node from which we start the removal. + * @param startOffset The offset of startNode from which we start the removal. + * @param endNode The node from which we end the removal. + * @param endOffset The offset of endNode from which we end the removal. + * @param cursorNode The cursor is inside cursorNode. + * @param cursorOffset The offset of the cursor inside cursorNode. + * @return Returns a pointer to the node inserted. + */ + static Node* DTDExtractNodeSubtree(Node *startNode, int startOffset, Node *endNode, int endOffset, + Node **cursorNode, long &cursorOffset, NodeModifsSet *modifs, bool extractInlineParentNodes = true); + + /** + * Similar to the above function but it operates on the given node tree. See DTDGetNodeSubtree. + * @param nodeSubtree The Node tree on which we're going to make the removal. + * @return Returns a pointer to the node inserted. + */ + static Node* DTDExtractNodeSubtree(Node *startNode, int startOffset, Node *endNode, int endOffset, + Node* nodeSubtree, NodeModifsSet* modifs, bool extractInlineParentNodes = true); + + static Node* extractNodeSubtreeAux(Node* commonParentStartChild, Node* commonParentEndChild, NodeModifsSet* modifs); + + /** + * It behaves essentially like the above function. Provided for convenience. + */ + static Node* DTDRemoveSelection(NodeSelectionInd& selection, + Node **cursorNode, long& cursorOffset, NodeModifsSet *modifs, bool extractInlineParentNodes = true); + + /** + * Get a node subtree from the tree. It is similar to extractNodeSubtree() + * but it doesn't extract anything. + * It's useful to get a copy of the Node subtree from a selection, for example. + * This funtion not only extract the start node but also will extract inline parents. + * @param startNode The starting Node. + * @param startOffset If firstNode is a text, specify at which offset the new start Node will be splitted. + * @param endNode The ending Node. + * @param endOffset If endNode is a text, specify at which offset the new end Node will be splitted. + * @return Returns a pointer to the Node subtree. + */ + static Node* getNodeSubtree(Node *startNode, int startOffset, Node *endNode, int endOffset, bool extractInlineParentNodes = true); + + /** + * An enumeration of all the possible return states of DTDExtractNode + */ + enum extractNodeStatus + { + //The node to extract was not found. + nothingExtracted = 0, + //The extract operation stopped because of a DTD error : if the node was removed, the child + //weren't able to be childs of the node's parent, according to the DTD. Should not occur + //except really bad HTML. + extractionStoppedDueToBadNodes, + //everything has gone fine + extractionDone, + //Invalid start or end position, or the given Node was a block. + extractionBadParameters + }; + + /** + * This function will try to extract the node nodeName (of type XmlTag) from a given subtree, + * according to the DTD. If the DTD don't allow it, it won't remove it. + * This function is only interesting for the removal of Inline Nodes thus it will return an error if + * a block nodeName is submitted. + * TODO: AVOID splitting of Node when the DTD don't allow the removal. + * @param nodeName The name of the Node to remove (must be inline). + * @param doc It is needed to get the DTD informations. + * @param startNode The node from which we start the removal. + * @param startOffset The offset of startNode from which we start the removal. + * @param endNode The node from which we end the removal. + * @param endOffset The offset of endNode from which we end the removal. + * <TEMPORARY> : We want to keep track of the cursor position. TODO : cursor class + * @param cursorNode The cursor is inside cursorNode. + * @param cursorOffset The offset of the cursor inside cursorNode. + * </TEMPORARY> + * @param modifs The usual modifs to log the modifications made for the undo/redo system. + * @return Returns a kafkaCommon::extractNodeStatus. + */ + static int DTDExtractNode(const QString &nodeName, Document *doc, Node *startNode, + int startOffset, Node *endNode, int endOffset, Node **cursorNode, long &cursorOffset, + NodeModifsSet *modifs); + + /** + * Moves a Node somewhere else. + * @param nodeToMove The node to move :-) + * @param newParent The new parent of nodeToMove. + * @param newNextSibling The new next Sibling of nodeToMove. If null, node will be appended at + * the child list of parentNode. + * @param modifs The changes made are logged into modifs. + * @param merge Specifies if it should try to merge the Node at its new location. + */ + static void moveNode(Node *nodeToMove, Node *newParent, Node *newNextSibling, + NodeModifsSet *modifs, bool merge = true, bool moveClosingNode = false); + + static void moveNode(Node *nodeToMove, Node *newParent, Node *newNextSibling, NodeSelection& cursorHolder, + NodeModifsSet *modifs, bool merge = true, bool moveClosingNode = false); + + /** + * Split a Text Node at offset offset. If offset or n is invalid, nothing is done. + * @param n The Node to split. + * @param offset Where to split the node. + * @param modifs The change made are logged into modifs. + * @return Returns if the node was splitted. + */ + static bool splitNode(Node *n, int offset, NodeModifsSet *modifs); + + /** + * This method takes care of spliting start and end nodes, if needed, finding the commonParent, + * commonParentStartChild and commonParentEndChild and split the start and end node subtrees, + * calling splitStartNodeSubtree and splitEndNodeSubtree. + * The following tree: + * <body> + * <b> --> commonParent + * <i> --> commonParentStartChild + * select| + * here --> startNode + * </i> + * continue + * <u> --> commonParentEndChild + * stop| --> endNode + * more + * </u> + * text + * </b> + * <body> + * Is changed to: + * <body> + * <b> + * <i> + * select| + * </i> + * </b> + * <b> + * <i> + * here + * </i> + * continue + * <u> + * stop + * </u> + * </b> + * <b> + * <u> + * more + * </u> + * text + * </b> + * </body> + * @param startNode The node where a selection starts, for example. + * @param startOffset + * @param endNode The node where a selection ends, for example. + * @param endOffset + * @param commonParent This is the common parent between start and end node. + * If 0, it tries to find the commonParent, else it uses the passed node. + * @param commonParentStartChildLocation The first child of commonParent which is parent of startNode is stored here. + * @param commonParentEndChildLocation The first child of commonParent which is parent of endNode is stored here. + * @param cursorNode The cursor node is stored here. + * @param cursorOffset The cursor offset is stored here. + * @param subTree The node corresponding to the start of a subtree that doesn't belong to the current document, or 0. + * @param modifs The changes made are logged into modifs. + */ + static void splitStartAndEndNodeSubtree(Node*& startNode, int startOffset, Node*& endNode, int endOffset, Node*& commonParent, + QValueList<int>& commonParentStartChildLocation, + QValueList<int>& commonParentEndChildLocation, + NodeSelection& cursorHolder, + Node* subTree, NodeModifsSet* modifs, bool extractInlineParentNodes = true); + + /** + * If n and n2 are both Text or Empty Nodes, merge them into one. + * WARNING if merging occurs, n2 is deleted. + * @param modifs The changes made are logged into modifs. + * @param mergeTextOnly Specify if we should only merge text Nodes, not empty ones. + * @return Returns true if the Nodes were merged, else false. + */ + static bool mergeNodes(Node *n, Node *n2, NodeModifsSet *modifs, bool mergeTextOnly = false); + + static bool mergeNodes(Node *n, Node *n2, NodeSelection& cursorHolder, NodeModifsSet *modifs, bool mergeTextOnly = false); + + /** + * This function will navigate through the Nodes from startNode to endNode and + * merge identical inline Nodes as well as text Nodes. + * @param startNode The node from which the merge starts. + * @param endNode The node from which the merge ends. + * @param modifs The usual modifs, to log the changes. + * <TEMPORARY> : We want to keep track of the cursor position. TODO : cursor class + * @param cursorNode The cursor is inside cursorNode. + * @param cursorOffset The offset of the cursor inside cursorNode. + * </TEMPORARY> + */ + static void mergeInlineNode(Node *startNode, Node *endNode, Node **cursorNode, + long &cursorOffset, NodeModifsSet *modifs); + + + /** ----------------------- NODE MODIFICATIONS -------------------------------------*/ + + /** + * Computes the end position of a string starting at pos (bLine, bCol). + * @param tagString The tagString, representing usually a tag string ;-) + * @param bLine The line of the first letter of tagString. + * @param bCol The column of the first letter of tagString. + * @param eLine Returns the line of the last letter of tagString. + * @param eCol Returns the col of the last letter of tagString. + */ + static void getEndPosition(const QString & tagString, int bLine, int bCol, int &eLine, int &eCol); + + /** + * It behaves essentially like the above function except that the string is the Tag String of Node. + * @param node The tag string is taken from node. + */ + static void getEndPosition(Node *node, int bLine, int bCol, int &eLine, int &eCol); + + /** + * Set the tag string of node, and update the start/end position of the Node. + * @param node The node which get the new tag string. + * @param newTagString The new tag String :-) + * @param modifs The changes made are logged into modifs. + */ + static void setTagString(Node *node, const QString &newTagString, NodeModifsSet* modifs); + + /** + * This function behaves essentially like the above function except that all the others Nodes' position + * are updated too. + */ + static void setTagStringAndFitsNodes(Node *node, const QString &newTagString, NodeModifsSet* modifs); + + /** + * This function behaves exactly like Node::editAttribute except that the change is logged inside a NodeModifsSet. + */ + static void editNodeAttribute(Node* node, const QString& name, const QString& value, NodeModifsSet* modifs); + + /** + * Gets the location of a Node in a pointer-independant suit of ints e.g. 1,3,5 means + * that the node is the fifth child of the third child of the root Node. Efficient when + * deleting the Node tree and rebuilding it when switching between Documents. + * @param node The Node we want the location. + * @return Returns the location. + */ + static QValueList<int> getLocation(Node* node); + + /** + * It behaves essentially like the above function except that it operate on DOM::Nodes. + */ + static QValueList<int> getLocation(DOM::Node domNode); + + /** + * Get the node corresponding to a location. See the above function. + * @param loc We want the Node from this location. + * @return Returns the Node at location loc. + */ + static Node* getNodeFromLocation(QValueList<int> loc); + + /** + * Similar to the above function but instead of using baseNode it uses the passes Node tree. + * @param nodeTree Node tree where to get the location. + */ + static Node* getNodeFromLocation(QValueList<int> loc, Node* nodeTree); + + /** + * It behaves essentially like the above function except that it operate on DOM::Nodes. + * @rootNode It needs the root Node of the DOM::Node Tree i.e. the document() Node. + */ + static DOM::Node getNodeFromLocation(QValueList<int> loc, DOM::Node rootNode); + + /** + * Get the node corresponding to a sublocation. + * @param loc A location of a Node. + * @locOffset We want the (totalNumberOfParent - locOffset)th parent of Node. + * @return Returns a parent of the node pointed by loc. + */ + static Node* getNodeFromSubLocation(QValueList<int> loc, int locOffset); + + static Node* getNodeFromSubLocation(QValueList<int> loc, int locOffset, Node* nodeTree); + + /** + * A enumeration for kafkaCommon::compareNodePosition(). + */ + enum position + { + //It means that it is a previous sibling (not the dom/dom_node.h definition, but rather + // the node.h definition) + isBefore = 0, + //It is the same Node. + isAtTheSamePosition, + //It means that it is a next sibling (in the node.h way). + isAfter, + //guess what? + positionError + }; + + /** + * Compare the position of two Nodes. + * e.g. (pos1)->next = (pos2); compareNodePosition(n1, n2) == kafkaCommon::before. + * @param pos1 The location of the Node to compare. + * @param pos2 The location of the Node to be compared to. + * @return Return a kafkaCommon::position flag. + */ + static int compareNodePosition(QValueList<int> pos1, QValueList<int> pos2); + + /** + * It behave essentially like the above function except that it is based on Nodes. + */ + static int compareNodePosition(Node *n1, Node *n2); + + /** + * Compare n1 and n2's node type, node name, and node attributes. + * @return Returns true if there are indentical. + */ + static bool compareNodes(Node *n1, Node *n2); + + /** + * Get the node's depth in the tree. + * @param node The node we want the depth. + * @return Returns the depth of node. It is basically the number of parents of node. + * It will return 0 if node has no parent Nodes, and -1 if node doesn't exists. + */ + static int nodeDepth(Node *node); + + /** + * Looks if node has a parent which is named name. + * @return Returns the first parent which is named name or 0L if not found. + */ + static Node* hasParent(Node *node, const QString &name); + + /** + * Tries to find the common parent to startNode and endNode, in the same conditions as above. + */ + static Node* hasParent(Node* startNode, Node* endNode, const QString &name); + + + /** ----------------- DOM::NODE TREE MODIFICATIONS --------------------*/ + + /** + * Insert a DOM::Node in the DOM::Node tree. It takes care to handle the exceptions. + * WARNING : The postEnhancement is not done (cf htmlenhancer.h) + * Prefer using KafkaDocument::insertDomNode() + * @param node The node to insert. + * @param parent The new parent of node. If null, insert node at the top level. + * @param nextSibling The new next sibling of node. If null, append node at the end of the child list. + * @param rootNode The root DOM::Node of the DOM::Node tree. Useful when no parent is provided. + * @return Returns true if the operation was successfull. + */ + static bool insertDomNode(DOM::Node node, DOM::Node parent = DOM::Node(), + DOM::Node nextSibling = DOM::Node(), DOM::Node rootNode = DOM::Node()); + + + /** + * Removes a DOM::Node from the DOM::Node Tree. It takes care to handle the exceptions. + * WARNING : The postUnenhancement is not done (cf htmlenhancer.h) + * Prefer using KafkaDocument::removeDomNode() + * @param node The Node to remove from the tree. + * @retun Returns true if the operation was successfull.. + */ + static bool removeDomNode(DOM::Node node); + + + /** --------------------- DOM::NODE MODIFICATIONS ---------------------- */ + + /** + * Create a new DOM::Node. It takes care to check if nodeName is valid. + * @param nodeName The DOM::Node's name. + * @param dtd The currently used dtd. + * @param rootNode The rootNode is needed in order to create a new DOM::Node. + * @return Returns the DOM::Node created or a null DOM::Node if nodeName is invalid. + */ + static DOM::Node createDomNode(const QString &nodeName, const DTDStruct* dtd, DOM::Document rootNode); + + /** + * It behaves essentially like the above function. + * @param node The DOM::Node will be created with node's name. + */ + static DOM::Node createDomNode(Node *node, DOM::Document rootNode); + + /** + * Create a new Text Node. + * @param textString The text inside the new text DOM::Node. + * @param rootNode The rootNode is needed in order to create a new Text DOM::Node. + * @return a new text DOM::Node. + */ + static DOM::Node createTextDomNode(const QString &textString, DOM::Document rootNode); + + /** + * Create a new attribute and check if the attrName can have this attribute. + * @param nodeName The node name of the DOM::Node which will get this attribute. + * @param dtd The currently used dtd. + * @param attrName The name of the new attribute. + * @param attrValue The value of the new attribute. + * @param rootNode The rootNode is needed in order to create a new Attribute. + * @return Returns the new Attribute or a null DOM::Node if attrName is invalid. + */ + static DOM::Node createDomNodeAttribute(const QString &nodeName, const DTDStruct* dtd, + const QString &attrName, const QString &attrValue, DOM::Document rootNode); + + /** + * It behaves essentially like the above function. + * @param node The corresponding DOM::Node of node will get the attribute. It don't add the attribute. + */ + static DOM::Node createDomNodeAttribute(Node* node, const QString &attrName, DOM::Document rootNode); + + /** + * It behaves essentially like the above function except that it use the DOM::Node->Node* link to get the + * corresponding Node. So be sure that the link is set. + * @param node The node which will get the attribute. It don't add the attribute. + */ + //static DOM::node createDomNodeAttribute(DOM::Node node, const QString &attrName, + // DOM::Document rootNode); + + /** + * Append a new attribute to a DOM::Node. + * @param node The node which will get the new attribute. + * @param attr The new attribute to add. + * @return Returns if the operation was successfull. + */ + static bool insertDomNodeAttribute(DOM::Node node, DOM::Node attr); + + /** + * It behaves essentially like the above function except that if the attribute doesn't exist, it will create it, + * and then it fills the attribute with attrValue. + * @param nodeName The name of the Node corresponding to node. + * @param dtd The currently used DTD. + * @param attrName The name of the (new) Attribute. + * @param attrValue The value of the new Attribute. + * @param rootNode The rootNode is needed in order to create a new Attribute. + * @return Returns if the operation was successfull. + */ + static bool editDomNodeAttribute(DOM::Node node, const QString &nodeName, const DTDStruct* dtd, + const QString &attrName, const QString &attrValue, DOM::Document rootNode); + + /** + * It behaves essentially like the above function. + * @param node The DOM::Node comes from this node. + */ + static bool editDomNodeAttribute(DOM::Node domNode, Node* node, + const QString &attrName, const QString &attrValue, DOM::Document rootNode); + + /** + * Looks if domNode has a parent which is named name. + * @return Returns the first parent which is named name or an empty DOM::Node if not found. + */ + static DOM::Node hasParent(DOM::Node domNode, const QString &name); + + /** + * Returns the position of the child domNode. + * @param domNode This is the DOM::Node we want the position. + * @return Returns the position of domNode inside domNode's parent's children or -1 if not found. + */ + static int childPosition(DOM::Node domNode); + + /** + * Returns the position'th child of parentNode. + * @param parentNode The parent Node of the node to return. + * @param position We return the position'th child Node. + * @param fallback If set to true, it will always return a valid Node (except if there is no child!!) + */ + static DOM::Node getChildNode(DOM::Node parentNode, int position, bool fallback = false); + + /** + * Specify if a DOM::Node is inline (as specified in isInline()) or text. + * @return true if it is an inline Node. + */ + static bool isInline(DOM::Node domNode); + + /** + * Specify if parent supports the siblings DOM::Nodes starting from startNode to endNode + * according to the DTD dtd. + */ + static bool parentSupports(DOM::Node parent, DOM::Node startNode, DOM::Node endNode, + const DTDStruct* dtd); + + + /** ----------------------- MISCELLANEOUS -------------------------------------*/ + + /** + * TEMPORARY, HTML specific + * @return Returns true if it is a inline Node. Official DTD List, unlike getNodeDisplay(). + */ + static bool isInline(const QString &nodename); + + /** + * Prints in stdout the current DOM::Node tree. + * @param rootNode The root Node of the DOM::Node Tree (usually document()) + * @param indent The indentation. + */ + static void coutDomTree(DOM::Node rootNode, int indent); + + /** + * Prints in stdout the current Node tree. + * @param node The startNode + * @param indent The number of little dots per parent relationship. + */ + static void coutTree(Node *node, int indent); + + /** + * Returns whether a range is surrounded by a tag. + * @param start_node The start of the range to be checked. + * @param end_node The end of the range to be checked. + * @param tag_name The name of the tag, e.g., "strong". + * @return -1 is not inside tag_name + * 1 is inside tag_name + * 0 mixed + */ + static int isInsideTag(Node* start_node, Node* end_node, QString const& tag_name); + + static int isInsideTag(Node* start_node, Node* end_node, QString const& tag_name, + QString const& attribute_name, QString const& attribute_value); + + /** + * Return whether the offset is placed between two words in a text node. + * @pre node is a Node of type text. + * @pre offset >= 0 + * @param node The text node to be checked. + * @param offset The position in text we want to see if it's between words. + * @return true if is a space between words or if it's in the limit of a word. + */ + static bool isBetweenWords(Node* node, int offset); + + /** + * Set node and offset to the beggining of the word + * @pre node is a text node. + * @pre isBetweenWords + * @param node The text node, which will be changed (or not) to the start of the word. + * @param offset The current offset of the text node which will be changed (or not) to the start of the word. + * @return the offset of the beggining of the word + */ + static void getStartOfWord(Node*& node, int& offset); + + /** + * Same as above, but will get the end of the word + */ + static void getEndOfWord(Node*& node, int& offset); + + /** + * Set node and offset to the beggining of the paragraph. + * The distinction between inline/block nodes is used here. + * @param node + * @param offset + */ + static void getStartOfParagraph(Node*& node, int& offset); + + static void getEndOfParagraph(Node*& node, int& offset); + +private: + /** + * Split the last valid start parent (commonParentStartChild) into two. + * This and the method above are related and are used in sequence. + * The following tree: + * <body> + * <b> --> commonParent + * <i> --> commonParentStartChild + * select| + * here --> startNode + * </i> + * continue + * <u> + * stop|more + * </u> + * text + * </b> + * <body> + * Is changed to: + * <body> + * <b> + * <i> + * select| + * </i> + * </b> + * <b> + * <i> + * here + * </i> + * continue + * <u> + * stop|more + * </u> + * text + * </b> + * </body> + * @param startNode The node where a selection starts, for example. + * @param commonParent This is the common parent between start and end node. + * @param commonParentStartChildLocation The first child of commonParent which is parent of startNode + * @param modifs The changes made are logged into modifs. + */ + static void splitStartNodeSubtree(Node* startNode, Node* commonParent, + QValueList<int>& commonParentStartChildLocation, NodeModifsSet* modifs); + /** + * Split the last valid start parent (commonParentStartChild) into two. + * The following tree: + * <body> + * <b> --> commonParent + * <i> --> commonParentStartChild + * select| + * here + * </i> + * continue + * <u> --> commonParentEndChild + * stop| --> endNode + * more + * </u> + * text + * </b> + * <body> + * Is changed to: + * <body> + * <b> + * <i> + * select|here + * </i> + * continue + * <u> + * stop| + * </u> + * </b> + * <b> + * <u> + * more + * </u> + * text + * </b> + * </body> + * @param endNode The node where a selection ends, for example. + * @param commonParent This is the common parent between start and end node. + * @param commonParentStartChildLocation The first child of commonParent which is parent of startNode. + * @param commonParentEndChildLocation The first child of commonParent which is parent of endNode. + * @param subTree True if we are dealing with a tree that doesn't belong to the current document. + * @param modifs The changes made are logged into modifs. + */ + static void splitEndNodeSubtree(Node* endNode, Node* commonParent, + QValueList<int>& commonParentStartChildLocation, + QValueList<int>& commonParentEndChildLocation, + bool subTree, NodeModifsSet* modifs); +}; + +#endif diff --git a/quanta/parts/kafka/kafkadragobject.cpp b/quanta/parts/kafka/kafkadragobject.cpp new file mode 100644 index 00000000..652ea6f2 --- /dev/null +++ b/quanta/parts/kafka/kafkadragobject.cpp @@ -0,0 +1,90 @@ +/*************************************************************************** + kafkadragobject.h + ------------------- + + copyright : (C) 2004 - Paulo Moura Guedes + email : moura@kdewebdev.org + ***************************************************************************/ + +/*************************************************************************** + * * + * 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. * + * * + ***************************************************************************/ + +#include <kdebug.h> + +#include <qdom.h> +#include <qtextstream.h> + +#include "kafkadragobject.h" +#include "node.h" +#include "kafkacommon.h" // for debug only + +char const* KafkaDragObject::m_mimetype = "application/kafka-nodeselection"; + +KafkaDragObject::KafkaDragObject(Node const* item, QWidget* dragSource, const char* name) + : QStoredDrag(m_mimetype, dragSource, name), m_item(item) +{ + QDomDocument doc("KafkaSelection"); + QString docstart = "<KafkaSelection/>"; + doc.setContent(docstart); + QDomElement itemsElement = doc.createElement("Items"); + doc.documentElement().appendChild(itemsElement); + //kdDebug(25001) << doc.toString() << endl; + item->save(itemsElement); + //QDataStream s(m_array, IO_WriteOnly); + QTextStream s(m_array, IO_WriteOnly); + s << doc.toString(); + //s << "biltre"; + /* + kdDebug(25001) << m_array.size() << endl; + kdDebug(25001) << doc.toString() << endl; + kdDebug(25001) << QString(m_array) << endl; + */ + //setEncodedData(m_array); +} + +KafkaDragObject::~KafkaDragObject() +{} + +QByteArray KafkaDragObject::encodedData(const char* mimetype) const +{ + if(m_mimetype == mimetype) + return m_array; + + return QByteArray(); +} + +Node const* KafkaDragObject::getItem() const +{ + return m_item; +} + +bool KafkaDragObject::canDecode(QMimeSource *e) +{ + return e->provides(m_mimetype); +} + +bool KafkaDragObject::decode(QMimeSource* e, Node* node) +{ + if(!node || !e->provides(m_mimetype)) + return false; + + QByteArray data = e->encodedData(m_mimetype); + //kdDebug(25001) << QString(data) << endl; + QDomDocument doc; + doc.setContent(data, false); + //kdDebug(25001) << doc.toString() << endl; + node->load(doc.documentElement().firstChild().toElement()); + + kdDebug(25001) << "Load: " << endl; + kafkaCommon::coutTree(node, 3); + + return node; +} + +#include "kafkadragobject.moc" diff --git a/quanta/parts/kafka/kafkadragobject.h b/quanta/parts/kafka/kafkadragobject.h new file mode 100644 index 00000000..3325812c --- /dev/null +++ b/quanta/parts/kafka/kafkadragobject.h @@ -0,0 +1,50 @@ +/*************************************************************************** + kafkadragobject.h + ------------------- + + copyright : (C) 2004 - Paulo Moura Guedes + email : moura@kdewebdev.org + ***************************************************************************/ + +/*************************************************************************** + * * + * 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. * + * * + ***************************************************************************/ + +#ifndef KAFKADRAGOBJECT_H +#define KAFKADRAGOBJECT_H + +#include <qcstring.h> +#include <qdragobject.h> + +class Node; + +/** +@author Paulo Moura Guedes +*/ +class KafkaDragObject: public QStoredDrag +{ + Q_OBJECT + +public: + KafkaDragObject(Node const* item, QWidget* dragSource = 0, const char* name = 0); + ~KafkaDragObject(); + + QByteArray encodedData(const char* c) const; + Node const* getItem() const; + + static bool canDecode(QMimeSource*); + static bool decode(QMimeSource*, Node* node); + +private: + QByteArray m_array; + Node const* m_item; + + static char const* m_mimetype; +}; + +#endif diff --git a/quanta/parts/kafka/kafkahtmlpart.cpp b/quanta/parts/kafka/kafkahtmlpart.cpp new file mode 100644 index 00000000..493a9a3f --- /dev/null +++ b/quanta/parts/kafka/kafkahtmlpart.cpp @@ -0,0 +1,2272 @@ +/*************************************************************************** + kafkahtmlpart.cpp + ------------------- + + copyright : (C) 2001 - The Kafka Team + (C) 2003, 2004 - Nicolas Deschildre + email : kde-kafka@master.kde.org && ndeschildre@kdewebdev.org + ***************************************************************************/ + +/*************************************************************************** + * * + * 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. * + * * + ***************************************************************************/ + +#include <qfile.h> +#include <qpainter.h> +#include <qtextstream.h> +#include <qstringlist.h> +#include <qlayout.h> +#include <qmainwindow.h> +#include <qtimer.h> +#include <qtooltip.h> +#include <qpopupmenu.h> + +#include <kdebug.h> +#include <klocale.h> +#include <khtmlview.h> +#include <kmessagebox.h> +#include <ktrader.h> +#include <klibloader.h> +#include <kparts/factory.h> +#include <dom/dom_node.h> +#include <dom/dom_text.h> +#include <dom/dom_exception.h> +#include <dom/dom_string.h> +#include <dom/dom2_range.h> +#include <khtml_events.h> +//#include <khtml_part.h> + +#include "kafkacommon.h" +#ifdef HEAVY_DEBUG +#include "domtreeview.h" +#include <qdialog.h> +#endif +#include "wkafkapart.h" +#include "undoredo.h" +#include "nodeproperties.h" +#include "tagaction.h" +#include "tagactionset.h" +#include "document.h" +#include "resource.h" +#include "quantacommon.h" +#include "quanta.h" +#include "quantaview.h" +#include "tagattributetree.h" +#include "tagactionmanager.h" +#include "tagactionset.h" +#include "cursors.h" + +#include "viewmanager.h" + +class KafkaWidgetPrivate +{ +public: + KafkaWidgetPrivate() + {} + ~KafkaWidgetPrivate() + {} + int m_cursorOffset; + int m_pressOffset; + int m_releaseOffset; + /** when going up and down, trying to be as close as possible from the + original node X pos like a good text editor :=) */ + bool stuckCursorHorizontalPos; + int stuckedCursorPosX; + +#ifdef HEAVY_DEBUG + KafkaDOMTreeDialog *domdialog; +#endif +}; + +KafkaWidget::KafkaWidget(QWidget *parent, QWidget *widgetParent, KafkaDocument *part, + const char *name) + : KHTMLPart(widgetParent, name, parent, name), + w(part) +{ + m_contextPopupMenu = new QPopupMenu(); + + d = new KafkaWidgetPrivate(); + + d->m_cursorOffset = 0; + d->m_pressOffset = 0; + d->m_releaseOffset = 0; + d->stuckCursorHorizontalPos = false; + + m_modifs = 0L; + + // With the mix of Leo Savernik's caret Mode and the current editing + // functions, it will be kind of VERY messy + setCaretMode(true); + connect(this, SIGNAL(caretPositionChanged(const DOM::Node &, long)), + this, SLOT(slotNewCursorPos(const DOM::Node &, long))); + setCaretDisplayPolicyNonFocused(KHTMLPart::CaretVisible); + + connect(this, SIGNAL(popupMenu(const QString&, const QPoint&)), + this, SLOT(slotContextMenuRequested(const QString&, const QPoint&))); + + view()->setMouseTracking(true); + view()->installEventFilter(this); + + //for debug purposes, we add a DOM tree view +#ifdef HEAVY_DEBUG + //d->domdialog = new KafkaDOMTreeDialog(view(), this); + //d->domdialog->show(); +#endif + //IMPORTANT:without him, no document() is created in khtmlPart + begin(); + write("<html></html>"); + end(); +} + +KafkaWidget::~KafkaWidget() +{} + +void KafkaWidget::newDocument() +{ + //FIXME: Somehow we should get it from Quanta settings: qConfig.attrValueQuotation + //-->No need for that: Quotations aren't stored in the DOM::Nodes + QString newPageHTMLCode = "<html>\n" + "<head>\n" + "</head>\n" + "<body>\n" + "</body>\n" + "</html>\n"; + + begin(); + write(newPageHTMLCode); + end(); + +} + +void KafkaWidget::insertText(DOM::Node node, const QString &text, int position) +{ +#ifdef LIGHT_DEBUG + kdDebug(25001)<< "KafkaWidget::insertText text " << text << " pos " << position << endl; +#endif + + int focus; + kNodeAttrs *attrs = w->getAttrs(node); + + if(!attrs) + return; + + focus = attrs->chCurFoc(); + + if(position < 0) + return;//nothing to do if something is selected + //if(focus == kNodeAttrs::no || !cbModified) return;//can't add text in this Node. + if(position == 0 && node.nodeName().string().lower() == "body") + { + //SPECIFIC HTML code!!! + //doesn't work! + //putCursorAtFirstAvailableLocation(); + if(!node.firstChild().isNull() && node.firstChild().nodeType() == DOM::Node::TEXT_NODE) + { + node = m_currentNode = node.firstChild(); + position = 0; + } + + if(position == 0 && node.nodeName().string().lower() == "body") + { + //We shouldn't go here... + DOM::Text textNode = document().createTextNode(text); + node.appendChild(textNode); + m_currentNode = textNode; + d->m_cursorOffset = text.length(); + emit domNodeInserted(textNode, false, m_modifs); +#ifdef LIGHT_DEBUG + + kdDebug(25001) << "KafkaWidget::insertText() - added text - 1" << endl; +#endif + + QTimer::singleShot(0, this, SLOT(slotDelayedSetCaretPosition())); + return; + } + } + + if(focus == kNodeAttrs::textNode && node.nodeType() == DOM::Node::TEXT_NODE) + { + DOM::DOMString textNode = node.nodeValue(); + DOM::DOMString textSplitted = textNode.split(position); + node.setNodeValue(textNode + text + textSplitted); + d->m_cursorOffset += text.length(); + emit domNodeModified(node, m_modifs); +#ifdef LIGHT_DEBUG + + kdDebug(25001) << "KafkaWidget::insertText() - added text" << endl; +#endif + + } + else if(position == 0) + { + DOM::Text textNode = document().createTextNode(text); + DOM::Node parent = node.parentNode(); +//FIXME: Andras: safety checks, as parent can be null. Maybe it just hides the error... + if (!parent.isNull()) + parent.insertBefore(textNode, node); + else + node.appendChild(textNode); + m_currentNode = textNode; + d->m_cursorOffset = text.length(); + emit domNodeInserted(textNode, false, m_modifs); +#ifdef LIGHT_DEBUG + + kdDebug(25001) << "KafkaWidget::insertText() - added text - 2" << endl; +#endif + + } + else if(position == 3 || (position == 1 && (focus == kNodeAttrs::singleNodeAndItself))) + { + DOM::Text textNode = document().createTextNode(text); + DOM::Node parent = node.parentNode(); +//FIXME: Andras: safety checks, as parent and node.nextSibling can be null. Maybe it just hides the error... +//Also it seems that position can be 3 and node is "body". See bug 112733. + if (node.nodeName().string().lower() != "body" && !parent.isNull()) + { + if (!node.nextSibling().isNull()) + parent.insertBefore(textNode, node.nextSibling()); + else + parent.insertBefore(textNode, node); + } + else + node.appendChild(textNode); + m_currentNode = textNode; + d->m_cursorOffset = text.length(); + emit domNodeInserted(textNode, false, m_modifs); +#ifdef LIGHT_DEBUG + + kdDebug(25001) << "KafkaWidget::insertText() - added text - 3" << endl; +#endif + + } + else if(position == 1) + { + DOM::Text textNode = document().createTextNode(text); + if(!node.firstChild().isNull()) + node.insertBefore(textNode, node.firstChild()); + else + node.appendChild(textNode); + m_currentNode = textNode; + d->m_cursorOffset = text.length(); + emit domNodeInserted(textNode, false, m_modifs); +#ifdef LIGHT_DEBUG + + kdDebug(25001) << "KafkaWidget::insertText() - added text - 4" << endl; +#endif + + } + //document().updateRendering(); + QTimer::singleShot(0, this, SLOT(slotDelayedSetCaretPosition())); +} + +void KafkaWidget::slotDelayedSetCaretPosition() +{ + setCaretPosition(m_currentNode, (long)d->m_cursorOffset); + emit domNodeNewCursorPos(m_currentNode, d->m_cursorOffset); +} + +void KafkaWidget::insertText(const QString &text, int position) +{ + insertText(m_currentNode, text, (position == -1 ? d->m_cursorOffset : position)); +} + + +void KafkaWidget::normalize(DOM::Node _node) +{ +#ifdef LIGHT_DEBUG + kdDebug(25001)<< "KafkaWidget::normalize()" << endl; +#endif + +//FIXME: Andras: getAttrs() can sometimes return NULL and causes a crash. No idea why and what it means though. + DOM::Node childNode = _node.firstChild(); + while(!childNode.isNull()) + { + if(w->getAttrs(childNode) && w->getAttrs(childNode)->chCurFoc() == kNodeAttrs::textNode) + { + while(!childNode.nextSibling().isNull() && + w->getAttrs(childNode.nextSibling()) && w->getAttrs(childNode.nextSibling())->chCurFoc() == + kNodeAttrs::textNode ) + { + childNode.setNodeValue(childNode.nodeValue() + + childNode.nextSibling().nodeValue()); + emit domNodeModified(childNode, m_modifs); + emit domNodeIsAboutToBeRemoved(childNode.nextSibling(), true, m_modifs); + _node.removeChild(childNode.nextSibling()); + } + } + childNode = childNode.nextSibling(); + } +} + +void KafkaWidget::keyReturn(bool specialPressed) +{ + //WARNING : HTML-specific function + DOM::Node text, text2, pDomNode, pDomNode2, brDomNode, brDomNode2, tmp, PNode, emptyText; + int focus; + // QTag *qTag; + bool childOfP; + //kNodeAttrs *props; + + if(m_currentNode.isNull()) + return; + if(!w->getAttrs(m_currentNode)) + return; + + focus = w->getAttrs(m_currentNode)->chCurFoc(); + if (focus == kNodeAttrs::textNode) + { + //First determine if the current Node is a child of a P + childOfP = false; + tmp = m_currentNode; + while(!tmp.isNull()) + { + if(tmp.nodeName().string().lower() == "p") + { + PNode = tmp; + childOfP = true; + break; + } + tmp = tmp.parentNode(); + } + + //Then split if necessary the text + if((static_cast<DOM::CharacterData>(m_currentNode)).length() == 0) + text = m_currentNode; + else if(d->m_cursorOffset <= 0) + text2 = m_currentNode; + else if((unsigned)d->m_cursorOffset >= + (static_cast<DOM::CharacterData>(m_currentNode)).length()) + text = m_currentNode; + else + { + text = m_currentNode; + text2 = (static_cast<DOM::Text>(m_currentNode)).splitText(d->m_cursorOffset); + emit domNodeModified(m_currentNode, m_modifs); + emit domNodeInserted(text2, false, m_modifs); + } + + if(!specialPressed) + { + if(childOfP) + {} + else + {} + } + else + { + if(childOfP) + {} + else + {} + } + + + //Then look if we are in a area which can handle a P + //and if it is ok and necessary, insert the current text in a P + //TODO: Change a bit for the p so that it handle every case + /**qTag = QuantaCommon::tagFromDTD(w->getCurrentDoc()->defaultDTD(), + m_currentNode.parentNode().nodeName().string()); + pDomNode = kafkaCommon::hasParent(m_currentNode, "p"); + if(pDomNode.isNull() && qTag && qTag->isChild("p")) + { + if(!text.isNull()) + { + emit domNodeIsAboutToBeRemoved(text, false); + w->removeDomNode(text); + } + + pDomNode = kafkaCommon::createDomNode("p", w->getCurrentDoc()->defaultDTD(), + document()); + w->insertDomNode(pDomNode, m_currentNode.parentNode(), + m_currentNode.nextSibling()); + emit domNodeInserted(pDomNode, false); + + if(!text.isNull()) + { + w->::insertDomNode(text, pDomNode); + emit domNodeInserted(text, false); + } + } + + //Then we insert either a P or a BR tag. + if(qTag && qTag->isChild("p") && !pDomNode.isNull()) + { + if(!text2.isNull()) + { + emit domNodeIsAboutToBeRemoved(text2, false); + w->::removeDomNode(text2); + } + + pDomNode2 = kafkaCommon::createDomNode("p", w->getCurrentDoc()->defaultDTD(), + document()); + w->insertDomNode(pDomNode2, pDomNode.parentNode(), + pDomNode.nextSibling()); + emit domNodeInserted(pDomNode2, false); + + if(!text2.isNull()) + { + w->insertDomNode(text2, pDomNode2); + emit domNodeInserted(text2, false); + } + m_currentNode = pDomNode2.firstChild(); + d->m_cursorOffset = 0; + } + else + {*/ + brDomNode = kafkaCommon::createDomNode("br", w->getCurrentDoc()->defaultDTD(), + document()); + if(!text.isNull()) + w->insertDomNode(brDomNode, m_currentNode.parentNode(), + text.nextSibling()); + else + w->insertDomNode(brDomNode, m_currentNode.parentNode(), + text2); + emit domNodeInserted(brDomNode, false, m_modifs); + if(!text2.isNull()) + m_currentNode = text2; + else + { + if(!brDomNode.nextSibling().isNull()) + m_currentNode = brDomNode.nextSibling(); + if(!brDomNode.nextSibling().isNull() && brDomNode.nextSibling().nextSibling().isNull()) + { + //TEMP before the webcore caret. + brDomNode2 = kafkaCommon::createDomNode("br", w->getCurrentDoc()->defaultDTD(), + document()); + if(!brDomNode.nextSibling().isNull()) + w->insertDomNode(brDomNode2, m_currentNode.parentNode(), + DOM::Node()); + + emit domNodeInserted(brDomNode2, false, m_modifs); + m_currentNode = brDomNode; + } + + } + d->m_cursorOffset = 0; + + } + else if( m_currentNode.nodeName().string().lower() == "br") + { + brDomNode = kafkaCommon::createDomNode("br", w->getCurrentDoc()->defaultDTD(), + document()); + w->insertDomNode(brDomNode, m_currentNode.parentNode(), + brDomNode.nextSibling()); + emit domNodeInserted(brDomNode, false, m_modifs); + m_currentNode = brDomNode; + d->m_cursorOffset = 0; + } + +#ifdef HEAVY_DEBUG + + kdDebug(25001)<< "CURNODE : " << m_currentNode.nodeName().string() << ":" + << m_currentNode.nodeValue().string() << " : " << d->m_cursorOffset << endl; + QTimer::singleShot(0, this, SLOT(slotDelayedSetCaretPosition())); + kdDebug(25001)<< "CURNODE : " << m_currentNode.nodeName().string() << ":" + << m_currentNode.nodeValue().string() << " : " << d->m_cursorOffset << endl; + //emit domNodeNewCursorPos(m_currentNode, d->m_cursorOffset); + // kdDebug(25001)<< "CURNODE : " << m_currentNode.nodeName().string() << ":" + // << m_currentNode.nodeValue().string() << " : " << d->m_cursorOffset << endl; + //postprocessCursorPosition(); + kdDebug(25001)<< "CURNODE : " << m_currentNode.nodeName().string() << ":" + << m_currentNode.nodeValue().string() << " : " << d->m_cursorOffset << endl; +#endif + + QTimer::singleShot(0, this, SLOT(slotDelayedSetCaretPosition())); +} + +bool KafkaWidget::eventFilter(QObject *, QEvent *event) +{ + bool forgetEvent = false; + //tmp + //DOM::Node attr, tmpNode; + //end tmp + + if(event->type() == QEvent::FocusIn) + { +#ifdef LIGHT_DEBUG + kdDebug(25001) << "KafkaWidget::eventFilter() FocusIn" << endl; +#endif + + emit hasFocus(true); + } + + if(event->type() == QEvent::FocusOut) + { +#ifdef LIGHT_DEBUG + kdDebug(25001) << "KafkaWidget::eventFilter() FocusOut" << endl; +#endif + + emit hasFocus(false); + } + + if(event->type() == QEvent::KeyPress) + { + QKeyEvent *keyevent = static_cast<QKeyEvent *>(event); + + //Create a new NodeModifsSet where the changes will be logged. + m_modifs = new NodeModifsSet(); + + switch(keyevent->key()) + { + case Key_Left: +#ifdef LIGHT_DEBUG + + kdDebug(25001) << "KafkaWidget::eventFilter() Left" << endl; +#endif + //previousOffset(1); + d->stuckCursorHorizontalPos = false; + //forgetEvent = true;//to avoid the scrolling of the page + break; + + case Key_Right: +#ifdef LIGHT_DEBUG + + kdDebug(25001) << "KafkaWidget::eventFilter() Right" << endl; +#endif + //nextOffset(1); + d->stuckCursorHorizontalPos = false; + //forgetEvent = true; + break; + + case Key_Backspace: +#ifdef LIGHT_DEBUG + + kdDebug(25001)<< "KafkaWidget::eventFilter() Backspace" << endl; +#endif + + keyBackspace(); + d->stuckCursorHorizontalPos = false; + break; + + case Key_Delete: +#ifdef LIGHT_DEBUG + + kdDebug(25001)<< "KafkaWidget::eventFilter() Delete" << endl; +#endif + + keyDelete(); + d->stuckCursorHorizontalPos = false; + break; + + case Key_Up: +#ifdef LIGHT_DEBUG + + kdDebug(25001)<< "KafkaWidget::eventFilter() Up" << endl; +#endif + //keyUp(); + //forgetEvent = true; + break; + case Key_Down: +#ifdef LIGHT_DEBUG + + kdDebug(25001)<< "KafkaWidget::eventFilter() Down" << endl; +#endif + //keyDown(); + //forgetEvent = true; + break; + case Key_Escape: + break; + case Key_Tab: + if(!m_currentNode.isNull() && w->getAttrs(m_currentNode) && + w->getAttrs(m_currentNode)->chCurFoc() != kNodeAttrs::no) + { + // @todo check tab settings in Quanta + if(hasSelection()) + removeSelection(); + insertText(" ", -1); + makeCursorVisible(); + } + forgetEvent = true; + d->stuckCursorHorizontalPos = false; + break; + case Key_BackTab: + break; + case Key_Return: + case Key_Enter: +#ifdef LIGHT_DEBUG + + kdDebug(25001)<< "KafkaWidget::eventFilter() Return" << endl; +#endif + if(hasSelection()) + removeSelection(); + + applyQueuedToggableTagActions(); + + keyReturn(keyevent->state() & ControlButton); + d->stuckCursorHorizontalPos = false; + break; + case Key_Insert: + break; + case Key_Pause: +#ifdef HEAVY_DEBUG + + kafkaCommon::coutTree(baseNode, 2); + kafkaCommon::coutDomTree(document(), 2); + w->coutLinkTree(baseNode, 2); +#endif + + break; + case Key_Print: + break; + case Key_SysReq: + break; + case Key_Home: + d->stuckCursorHorizontalPos = false; + break; + case Key_End: + d->stuckCursorHorizontalPos = false; + break; + case Key_Next: + break; + case Key_Shift: + break; + case Key_Control: + break; + case Key_Meta: + break; + case Key_Alt: + break; + case Key_CapsLock: + break; + case Key_NumLock: + break; + case Key_ScrollLock: + break; + + default: + if(m_currentNode.isNull()) + { +#ifdef LIGHT_DEBUG + kdDebug(25001)<< "KafkaWidget::eventFilter() - DOM::Node NULL" << endl; +#endif + + break; + } + else if(w->getAttrs(m_currentNode) && + w->getAttrs(m_currentNode)->chCurFoc() != kNodeAttrs::no || + m_currentNode.nodeName().string().lower() == "body") + { +#ifdef LIGHT_DEBUG + kdDebug(25001) << "KafkaWidget::eventFilter() Text - " << + keyevent->text() << endl; +#endif + //if(( keyevent->state() & Qt::ShiftButton) || ( keyevent->state() == Qt::NoButton)) + if( keyevent->text().length() && + ( !( keyevent->state() & ControlButton ) && + !( keyevent->state() & AltButton ) && + !( keyevent->state() & MetaButton ) || + ( ( (keyevent->state()&ControlButton) | AltButton ) == (ControlButton|AltButton) ) ) && + ( !keyevent->ascii() || keyevent->ascii() >= 32 || keyevent->text() == "\t" ) ) + { + if(hasSelection()) + removeSelection(); + + applyQueuedToggableTagActions(); + + insertText(keyevent->text(), -1); + } + makeCursorVisible(); +#ifdef HEAVY_DEBUG + //w->coutLinkTree(baseNode, 2); +#endif + + } + forgetEvent = true; + d->stuckCursorHorizontalPos = false; + break; + } + + //Submit the modifs to the undoRedo system. + ViewManager::ref()->activeDocument()->docUndoRedo->addNewModifsSet(m_modifs, undoRedo::KafkaModif, 0, qConfig.replaceAccented); + m_modifs = 0L; + } + +#ifdef LIGHT_DEBUG + kdDebug(25001)<< "Current Offset : " << m_currentNode.nodeName().string() << ":" << + d->m_cursorOffset << " (" << event->type() << ")" << endl; +#endif + + return forgetEvent; +} + +void KafkaWidget::slotContextMenuRequested(const QString& /*url*/, const QPoint& point) +{ + TagActionManager::self()->fillWithTagActions(m_contextPopupMenu, nodeUnderMouse()); + + if(m_contextPopupMenu->count() != 0) + m_contextPopupMenu->popup(point); +} + + +#if 0 +void KafkaWidget::keyDeleteNodes(DOM::Node &startNode, long &offset, bool backspace) +{ + DOM::Node domNode = startNode, nextNode; + kNodeAttrs *attrs; + long nextOffset; + DOM::DOMString nodeText, textSplitted; + + while(!domNode.isNull()) + { + //Start by getting the attributes of the Node + attrs = w->getAttrs(domNode); + if(!attrs) + { +#ifdef LIGHT_DEBUG + kdDebug(25001)<<"KafkaWidget::keyDeleteNodes() - ERROR KNodeAttrs not found!" + << endl; +#endif + + break; + } + + //Get the next Location + nextNode = domNode; + nextOffset = offset; + if(backspace) + getPrevNodeRangeSpecs(nextNode, offset, blok! ); + else + getNextNodeRangeSpecs(nextNode, offset); + + //Nodes that can't be deleted stop the cursor (e.g. TBODY) + if(!attrs->cbDel()) + break; + + //If we are in some text, and a letter can be deleted, delete it. + if(domNode.nodeType() == DOM::Node::TEXT_NODE && ((backspace && offset != 0)) || + (!backspace && offset != (static_cast<DOM::CharacterData>(domNode)).length())) + ) + { + nodeText = domNode.nodeValue(); + textSplitted = nodeText.split(backspace?offset:offset + 1); + nodeText.split(backspace?offset - 1:offset); + domNode.setNodeValue(nodeText + textSplitted); + //m_currentNode.parentNode().applyChanges(); + emit domNodeModified(domNode); + //postprocessCursorPosition(); + break; + } + + //If we are in an empty text (shoudn't occur), delete it + if(domNode.nodeType() + == DOM::Node::TEXT_NODE && + (static_cast<DOM::CharacterData>(domNode)).length() == 0) + { + emit domNodeIsAboutToBeRemoved(domNode, true); + domNode.parentNode().removeChild(domNode); + domNode = nextNode; + continue; + } + + //If we are in an empty Node (Inline), delete it + if(domNode.nodeType() + == DOM::Node::ELEMENT_NODE && offset == 0 && !domNode.hasChildNodes()) + { + + continue; + } + + //If the current Node is an empty Text, delete it + if() + { + continue; + } + + //If the current Node is an empty Node (kNodeAttrs::singleNodeAndItself)), delete it + if() + { + return; + } + + //If the current Node is an empty Node (Inline) delete it + if() + { + continue; + } +} + + +} +#endif + +void KafkaWidget::keyDelete() +{ + kNodeAttrs *attrs, *attrsTmp; + int focus, childPosition; + DOM::Node _nodeParent, _node, _nodeNext, temp, tempParent, nextSibling, nodeNextNext; + DOM::Node toplevelBlock, toplevelBlock2, startNode, endNode, startNode2, endNode2; + DOM::Node childOfCommonParent, childOfCommonParent2, commonParent; + bool _goingTowardsRootNode, isParent, singleNodeDeleted, nextIsBlock, startNode2IsNotInline; + + if(hasSelection()) + { + removeSelection(); + return; + } + + if(m_currentNode.isNull()) + return; + attrs = w->getAttrs(m_currentNode); + if(!attrs) + return; + + //OLD PART, TO BE REMOVED or #ifdef'ed + if(attrs->chCurFoc() == kNodeAttrs::textNode && (unsigned)d->m_cursorOffset != + (static_cast<DOM::CharacterData>(m_currentNode)).length()) + {//if we are in the middle of some text, we remove one letter + if(!attrs->cbMod()) + return; +#ifdef LIGHT_DEBUG + + kdDebug(25001)<< "KafkaWidget::keyDelete() - one letter removed - 1" << endl; +#endif + + DOM::DOMString nodeText = m_currentNode.nodeValue(); + DOM::DOMString textSplitted = nodeText.split(d->m_cursorOffset + 1); + nodeText.split(d->m_cursorOffset); + m_currentNode.setNodeValue(nodeText + textSplitted); + m_currentNode.parentNode().applyChanges(); + emit domNodeModified(m_currentNode, m_modifs); + postprocessCursorPosition(); + return; + } + + if(attrs->chCurFoc() != kNodeAttrs::no && attrs->chCurFoc() != kNodeAttrs::textNode && + d->m_cursorOffset < 0) + {//if we delete ourselves, which node will be m_currentNode?? + if(!attrs->cbDel()) + return; +#ifdef LIGHT_DEBUG + + kdDebug(25001)<< "KafkaWidget::keyDelete() - deleting a Node - 2" << endl; +#endif + + DOM::Node _node = m_currentNode; + bool b = false; + while(1) + {//try to find a prev node from which we can delete the node + _node = getPrevNode(_node, b); + if(_node == 0) + break; + attrs = w->getAttrs(_node); + if(attrs && attrs->chCurFoc() == kNodeAttrs::textNode) + { + m_currentNode = _node; + d->m_cursorOffset = + (static_cast<DOM::CharacterData>(_node)).length(); + keyDelete(); + setCaretPosition(m_currentNode, (long)d->m_cursorOffset); + emit domNodeNewCursorPos(m_currentNode, d->m_cursorOffset); + return; + } + if(attrs && attrs->chCurFoc() != kNodeAttrs::no && + attrs->chCurFoc() != kNodeAttrs::textNode) + { + m_currentNode = _node; + d->m_cursorOffset = 1; + keyDelete(); + setCaretPosition(m_currentNode, (long)d->m_cursorOffset); + emit domNodeNewCursorPos(m_currentNode, d->m_cursorOffset); + return; + } + } + b = false; + while(1) + {//try to find a next node from which we can delete the node + _node = getNextNode(_node, b); + if(_node == 0) + break; + attrs = w->getAttrs(_node); + if(attrs && attrs->chCurFoc() != kNodeAttrs::no) + { + m_currentNode = _node; + d->m_cursorOffset = 0; + keyBackspace(); + setCaretPosition(m_currentNode, (long)d->m_cursorOffset); + emit domNodeNewCursorPos(m_currentNode, d->m_cursorOffset); + return; + } + } + //here, there is no node right and left that can have the cursor focus + _node = m_currentNode.parentNode(); + emit domNodeIsAboutToBeRemoved(m_currentNode, true, m_modifs); + _node.removeChild(m_currentNode); + m_currentNode = document().createTextNode(""); + _node.appendChild(m_currentNode); + emit domNodeInserted(m_currentNode, false, m_modifs); + setCaretPosition(m_currentNode, (long)d->m_cursorOffset); + emit domNodeNewCursorPos(m_currentNode, d->m_cursorOffset); + } + + //Beginning of the actual keyDelete + _node = m_currentNode; + _goingTowardsRootNode = false; + singleNodeDeleted = false; + _nodeNext = getNextNode(_node, _goingTowardsRootNode); + while(!_nodeNext.isNull()) + { +#ifdef LIGHT_DEBUG + kdDebug(25001)<< "KafkaWidget::keyDelete() - currentNode: " << + _nodeNext.nodeName().string() << endl; +#endif + + attrs = w->getAttrs(_nodeNext); + + //If this Node can't be deleted, we stop here. + if(!attrs || !attrs->cbDel()) + return; + + //If we are in a TEXT node, we remove a letter + if(attrs->chCurFoc() == kNodeAttrs::textNode) + { + if((static_cast<DOM::CharacterData>(_nodeNext)).length() != 0) + { +#ifdef LIGHT_DEBUG + kdDebug(25001)<< "KafkaWidget::keyDelete() - one letter" << + " removed - 2" << endl; +#endif + + DOM::DOMString nodeText = _nodeNext.nodeValue(); + DOM::DOMString textSplitted = nodeText.split(1); + _nodeNext.setNodeValue(textSplitted); + emit domNodeModified(_nodeNext, m_modifs); + postprocessCursorPosition(); + normalize(_nodeNext.parentNode()); + break; + } + else + {//if we are in an empty text, delete it +#ifdef LIGHT_DEBUG + kdDebug(25001)<< "KafkaWidget::keyDelete() - deleting" << + "empty #text" << endl; +#endif + + _nodeParent = _nodeNext.parentNode(); + //If this empty text contains the cursor, change node to its parent. + if(_nodeNext == _node && _nodeParent.firstChild() == _nodeNext && + _nodeNext.nextSibling().isNull()) + { + _node = _nodeParent; + //d->m_cursorOffset = -2; + } + emit domNodeIsAboutToBeRemoved(_nodeNext, true, m_modifs); + _nodeParent.removeChild(_nodeNext); + singleNodeDeleted = true; + _nodeParent.applyChanges(); + _nodeNext = _node; + } + } + //Else if the current Node is a BLOCK which can be entered/leaved e.g. H1 + else if(attrs->chCurFoc() == kNodeAttrs::blockNode) + { + //First look if it is one of _node's parent + isParent = false; + temp = _node; + while(!temp.isNull()) + { + if(_nodeNext == temp) + isParent = true; + temp = temp.parentNode(); + } + + //1 - Locate the toplevel blocks + temp = _nodeNext; + if(isParent) + { + toplevelBlock = temp; + while(temp.parentNode().lastChild() == temp && w->getAttrs(temp.parentNode()) && + w->getAttrs(temp.parentNode())->chCurFoc() == kNodeAttrs::blockNode) + temp = temp.parentNode(); + childOfCommonParent = temp; + temp = temp.nextSibling(); + } + if(temp.isNull()) + break; + childOfCommonParent2 = temp; + commonParent = temp.parentNode(); + attrsTmp = w->getAttrs(temp); + nextIsBlock = (attrsTmp && attrsTmp->chCurFoc() == kNodeAttrs::blockNode); + while(!temp.isNull() && temp.hasChildNodes() && w->getAttrs(temp.firstChild()) && + w->getAttrs(temp.firstChild())->chCurFoc() == kNodeAttrs::blockNode) + temp = temp.firstChild(); + toplevelBlock2 = temp; + + //2 - Determine the Nodes which could be moved + if(!toplevelBlock.isNull() && toplevelBlock.hasChildNodes()) + endNode = toplevelBlock.lastChild(); + else if(!childOfCommonParent2.isNull() && !childOfCommonParent2.previousSibling().isNull()) + endNode = childOfCommonParent2.previousSibling(); + temp = endNode; + while(!temp.isNull() && !temp.previousSibling().isNull() && + ((kafkaCommon::isInline(temp) && (temp.previousSibling().isNull() || + kafkaCommon::isInline(temp.previousSibling()))) /**|| + (!isInline(temp) && temp.previousSibling().isNull())*/)) + temp = temp.previousSibling(); + startNode = temp; + + if(!toplevelBlock2.isNull() && toplevelBlock2.hasChildNodes()) + startNode2 = toplevelBlock2.firstChild(); + else if(!childOfCommonParent.isNull() && !childOfCommonParent.nextSibling().isNull()) + startNode2 = childOfCommonParent.nextSibling(); + startNode2IsNotInline = false; + temp = startNode2; + attrsTmp = w->getAttrs(temp); + if(attrsTmp && (attrsTmp->chCurFoc() == kNodeAttrs::singleNodeAndItself || + attrsTmp->chCurFoc() == kNodeAttrs::no)) + startNode2IsNotInline = true; + while(!temp.isNull() && !temp.nextSibling().isNull() && + ((kafkaCommon::isInline(temp) && (temp.nextSibling().isNull() || + kafkaCommon::isInline(temp.nextSibling())))/** || + (!isInline(temp) && temp.nextSibling().isNull())*/)) + temp = temp.nextSibling(); + endNode2 = temp; + + //3 - Move Nodes. + if(!startNode2.isNull() && startNode2IsNotInline) + { + emit domNodeIsAboutToBeRemoved(startNode2, true, m_modifs); + startNode2.parentNode().removeChild(startNode2); + } + else if(isParent && !nextIsBlock) + { + if(kafkaCommon::parentSupports(toplevelBlock, startNode2, endNode2, + w->getCurrentDoc()->defaultDTD())) + moveDomNodes(toplevelBlock, startNode2, endNode2, DOM::Node(), false); + else + { + if(kafkaCommon::parentSupports(commonParent, startNode, endNode, + w->getCurrentDoc()->defaultDTD())) + moveDomNodes(commonParent, startNode, endNode, childOfCommonParent2, + true); + else + { + //Damn it! What to do?? + } + } + } + else if(isParent && nextIsBlock) + { + if(kafkaCommon::parentSupports(toplevelBlock, startNode2, endNode2, + w->getCurrentDoc()->defaultDTD())) + moveDomNodes(toplevelBlock, startNode2, endNode2, DOM::Node(), false); + else + { + if(kafkaCommon::parentSupports(commonParent, startNode, endNode, + w->getCurrentDoc()->defaultDTD()) && kafkaCommon::parentSupports( + commonParent, startNode2, endNode2, w->getCurrentDoc()->defaultDTD())) + { + moveDomNodes(commonParent, startNode, endNode, childOfCommonParent, + false); + moveDomNodes(commonParent, startNode2, endNode2, childOfCommonParent2, + true); + } + else + { + //Damn it! What to do?? + } + } + } + else if(!isParent && nextIsBlock) + { + if(kafkaCommon::parentSupports(commonParent, startNode2, endNode2, + w->getCurrentDoc()->defaultDTD())) + moveDomNodes(commonParent, startNode2, endNode2, childOfCommonParent2, true); + else + { + //Damn it! What to do?? + } + } + if(!endNode.isNull()) + normalize(endNode.parentNode()); + + //4 - Delete empty Block Nodes. + if(!toplevelBlock.isNull()) + { + temp = toplevelBlock; + attrsTmp = w->getAttrs(temp); + while(attrsTmp && attrsTmp->chCurFoc() == kNodeAttrs::blockNode && + !temp.hasChildNodes()) + { + tempParent = temp.parentNode(); + emit domNodeIsAboutToBeRemoved(temp, true, m_modifs); + tempParent.removeChild(temp); + temp = tempParent; + attrsTmp = w->getAttrs(temp); + } + } + if(!toplevelBlock2.isNull()) + { + temp = toplevelBlock2; + attrsTmp = w->getAttrs(temp); + while(attrsTmp && attrsTmp->chCurFoc() == kNodeAttrs::blockNode && + !temp.hasChildNodes()) + { + tempParent = temp.parentNode(); + emit domNodeIsAboutToBeRemoved(temp, true, m_modifs); + tempParent.removeChild(temp); + temp = tempParent; + attrsTmp = w->getAttrs(temp); + } + } + break; + } + //Else if the nextNode is a BLOCK, or an invisible Node, Inline Node + //which can be deleted, delete it! + else if(attrs->chCurFoc() == kNodeAttrs::singleNodeAndItself || ((attrs->chCurFoc() == + kNodeAttrs::no || attrs->chCurFoc() == kNodeAttrs::inlineNode) && + !_nodeNext.hasChildNodes())) + { +#ifdef LIGHT_DEBUG + kdDebug(25001)<< "KafkaWidget::keyDelete() - deleting" << + " a Node" << endl; +#endif + + _nodeParent = _nodeNext.parentNode(); + //If this block is used to define the cursor pos, change node to its parent. + if(_nodeNext == _node && _nodeParent.firstChild() == _nodeNext && + _nodeNext.nextSibling().isNull()) + { + _node = _nodeParent; + //d->m_cursorOffset = -2; + } + focus = w->getAttrs(_nodeNext)->chCurFoc(); + emit domNodeIsAboutToBeRemoved(_nodeNext, true, m_modifs); + _nodeParent.removeChild(_nodeNext); + singleNodeDeleted = true; + _nodeNext = _node; + if(focus == kNodeAttrs::singleNodeAndItself) + { + normalize(_nodeParent); + break; + } + } + _nodeNext = getNextNode(_nodeNext, _goingTowardsRootNode); + } + + //If the node which is defining the cursor position has been deleted (thus changed) + if(false && singleNodeDeleted) + { + //Now that we have deleted something, the cursor may end up in something weird, e.g. + //in an empty text or empty Inline. So delete them. + _nodeNext = _nodeParent;//<== !!!! + _nodeParent = _node.parentNode(); + childPosition = -1; + while(!_nodeNext.isNull()) + { + attrs = w->getAttrs(_nodeNext); + + //If this Node can't be deleted, we stop here. + if(!attrs || !attrs->cbDel()) + break; + + //Let's delete useless Nodes + if((_nodeNext.nodeType() == DOM::Node::TEXT_NODE && + (static_cast<DOM::CharacterData>(_nodeNext)).length() == 0) || + (attrs->chCurFoc() == kNodeAttrs::inlineNode && _nodeNext.hasChildNodes()) + ) + { + childPosition = kafkaCommon::childPosition(_node); + _node = _nodeParent; + emit domNodeIsAboutToBeRemoved(_nodeNext, true, m_modifs); + _nodeParent.removeChild(_nodeNext); + normalize(_nodeParent); + } + else + break; + + _nodeNext = _nodeParent; + } + + //And finally, if the cursor is at a bad place (e.g. inside a Inline with childs), move it + attrs = w->getAttrs(_node); + while(attrs && attrs->chCurFoc() == kNodeAttrs::inlineNode && _node.hasChildNodes()) + { + _node = kafkaCommon::getChildNode(_node, childPosition, true); + childPosition = 1; + } + } + + /**m_currentNode = _node; + setCaretPosition(m_currentNode, (long)d->m_cursorOffset); + emit domNodeNewCursorPos(m_currentNode, d->m_cursorOffset, m_modifs);*/ +} + +void KafkaWidget::keyBackspace() +{ + kNodeAttrs *attrs, *attrsTmp; + int focus, m_currentNodeType; + DOM::Node _nodeParent, _node, _nodePrev, oldCurrentNode, temp, tempParent, prevSibling, nodePrevPrev; + DOM::Node toplevelBlock, toplevelBlock2, startNode, endNode, startNode2, endNode2; + DOM::Node childOfCommonParent, childOfCommonParent2, commonParent; + bool _goingTowardsRootNode, singleNodeDeleted, isParent, prevIsBlock, endNodeIsNotInline, boolTmp; + QString text; + + if(hasSelection()) + { + removeSelection(); + return; + } + + if(m_currentNode.isNull()) + return; + + attrs = w->getAttrs(m_currentNode); + if(!attrs) + return; + m_currentNodeType = m_currentNode.nodeType(); + +#ifdef HEAVY_DEBUG + + kdDebug(25001)<< "m_currentNode(" << m_currentNode.handle() << ") : " << m_currentNode.nodeName() << + endl; +#endif + + //OLD PART, to be removed or #ifdef'ed + if(attrs->chCurFoc() == kNodeAttrs::textNode && d->m_cursorOffset != 0) + {//if we are in the middle of some text, we remove one letter + if(!attrs->cbMod()) + return; +#ifdef LIGHT_DEBUG + + kdDebug(25001)<< "KafkaWidget::keyBackspace() - one letter removed - 1" << endl; +#endif + + DOM::DOMString nodeText = m_currentNode.nodeValue(); + DOM::DOMString textSplitted = nodeText.split(d->m_cursorOffset); + nodeText.split(d->m_cursorOffset - 1); + +#ifdef LIGHT_DEBUG + kdDebug(25001) << nodeText.string() << textSplitted.string() << endl; +#endif + + m_currentNode.setNodeValue(nodeText + textSplitted); + m_currentNode.parentNode().applyChanges(); + --(d->m_cursorOffset); + emit domNodeModified(m_currentNode, m_modifs); + postprocessCursorPosition(); + setCaretPosition(m_currentNode, (long)d->m_cursorOffset); + emit domNodeNewCursorPos(m_currentNode, d->m_cursorOffset); + return; + } + if(attrs->chCurFoc() == kNodeAttrs::singleNodeAndItself && + d->m_cursorOffset != 0) + {//if we delete ourselves, which node will be m_currentNode?? + if(!attrs->cbDel()) + return; +#ifdef LIGHT_DEBUG + + kdDebug(25001)<< "KafkaWidget::keyBackspace() - deleting a TagDeletable - 2" << endl; +#endif + + DOM::Node _node = m_currentNode; + bool b = false; + while(1) + {//try to find a previous node from which we can delete the node + _node = getPrevNode(_node, b); + if(_node == 0) + break; + attrs = w->getAttrs(_node); + if(attrs && attrs->chCurFoc() == kNodeAttrs::textNode) + { + m_currentNode = _node; + d->m_cursorOffset = (static_cast<DOM::CharacterData>(_node)).length(); + keyDelete(); + setCaretPosition(m_currentNode, (long)d->m_cursorOffset); + emit domNodeNewCursorPos(m_currentNode, d->m_cursorOffset); + return; + } + if(attrs && attrs->chCurFoc() != kNodeAttrs::no && attrs->chCurFoc() != + kNodeAttrs::textNode) + { + m_currentNode = _node; + d->m_cursorOffset = 1; + keyDelete(); + setCaretPosition(m_currentNode, (long)d->m_cursorOffset); + emit domNodeNewCursorPos(m_currentNode, d->m_cursorOffset); + return; + } + } + _node = m_currentNode; + b = false; + while(1) + {//try to find a next node from which we can delete the node + _node = getNextNode(_node, b); + if(_node == 0) + break; + attrs = w->getAttrs(_node); + if(attrs && attrs->chCurFoc() != kNodeAttrs::no) + { + m_currentNode = _node; + d->m_cursorOffset = 0; + keyBackspace(); + setCaretPosition(m_currentNode, (long)d->m_cursorOffset); + emit domNodeNewCursorPos(m_currentNode, d->m_cursorOffset); + return; + } + } + //here, there is no node right and left that can have the cursor focus + _node = m_currentNode.parentNode(); + emit domNodeIsAboutToBeRemoved(m_currentNode, true, m_modifs); + _node.removeChild(m_currentNode); + m_currentNode = document().createTextNode(""); + _node.appendChild(m_currentNode); + emit domNodeInserted(m_currentNode, false, m_modifs); + setCaretPosition(m_currentNode, (long)d->m_cursorOffset); + emit domNodeNewCursorPos(m_currentNode, d->m_cursorOffset); + + } + + //Beginning of the actual keyBackspace + _node = m_currentNode; + _goingTowardsRootNode = false; + singleNodeDeleted = false; + _nodePrev = getPrevNode(_node, _goingTowardsRootNode); + oldCurrentNode = m_currentNode; + + while(!_nodePrev.isNull()) + { +#ifdef LIGHT_DEBUG + kdDebug(25001)<< "KafkaWidget::keyBackspace() - currentNode: " << + _nodePrev.nodeName().string() << endl; +#endif + + attrs = w->getAttrs(_nodePrev); + if(!attrs) + { + kdError(25001) << "NULL kNodeAttrs instance: attrs = w->getAttrs(_nodePrev);" << endl; + kafkaCommon::coutDomTree(_nodePrev, 3); + return; + // FIXME Understand why this happen. +// Test case: +// 1. Write two words in a new VPL document and make the first one a link; +// 2. Put the cursor at most right and then press backspace until it crashes +// When you get to the link the cursor stays in the same plave and you have press it several times until it crashes. + } + + //If this Node can't be deleted, we stop here. + if(!attrs->cbDel()) + return; + + //If we are in a TEXT node, we remove a letter + if(attrs->chCurFoc() == kNodeAttrs::textNode) + { + if((static_cast<DOM::CharacterData>(_nodePrev)).length() != 0) + {//if we are in text, remove a letter +#ifdef LIGHT_DEBUG + kdDebug(25001)<< "KafkaWidget::keyBackspace() - one" << + " letter removed - 2" << endl; +#endif + + DOM::DOMString nodeText = _nodePrev.nodeValue(); + nodeText.split((static_cast<DOM::CharacterData>(_nodePrev)).length() - 1); + _nodePrev.setNodeValue(nodeText); + _nodePrev.parentNode().applyChanges(); + + m_currentNode = _nodePrev; + d->m_cursorOffset = (static_cast<DOM::CharacterData>(_nodePrev)).length(); + + postprocessCursorPosition(); + emit domNodeModified(_nodePrev, m_modifs); + return; + } + else + {//if we are in an empty text +#ifdef LIGHT_DEBUG + kdDebug(25001)<< "KafkaWidget::keyBackspace() - deleting" << + " empty #text" << endl; +#endif + + _nodeParent = _nodePrev.parentNode(); + emit domNodeIsAboutToBeRemoved(_nodePrev, true, m_modifs); + _nodeParent.removeChild(_nodePrev); + _nodeParent.applyChanges(); + _nodePrev = _node; + continue; + } + } + //Else if the current Node if a BLOCK which can be entered/leaved e.g. H1, P + else if(attrs->chCurFoc() == kNodeAttrs::blockNode) + { + //First look if it is one of _node's parent + isParent = false; + temp = _node; + while(!temp.isNull()) + { + if(_nodePrev == temp) + isParent = true; + temp = temp.parentNode(); + } + + //1 - Locate the toplevel blocks + temp = _nodePrev; + if(isParent) + { + toplevelBlock2 = temp; + while(temp.parentNode().firstChild() == temp && w->getAttrs(temp.parentNode()) && + w->getAttrs(temp.parentNode())->chCurFoc() == kNodeAttrs::blockNode) + temp = temp.parentNode(); + childOfCommonParent2 = temp; + temp = temp.previousSibling(); + } + if(temp.isNull()) + break; + childOfCommonParent = temp; + commonParent = temp.parentNode(); + attrsTmp = w->getAttrs(temp); + prevIsBlock = (attrsTmp && attrsTmp->chCurFoc() == kNodeAttrs::blockNode); + while(!temp.isNull() && temp.hasChildNodes() && w->getAttrs(temp.lastChild()) && + w->getAttrs(temp.lastChild())->chCurFoc() == kNodeAttrs::blockNode) + temp = temp.lastChild(); + toplevelBlock = temp; + + //2 - Determine the Nodes which could be moved + if(!toplevelBlock.isNull() && toplevelBlock.hasChildNodes()) + endNode = toplevelBlock.lastChild(); + else if(!childOfCommonParent2.isNull() && !childOfCommonParent2.previousSibling().isNull()) + endNode = childOfCommonParent2.previousSibling(); + endNodeIsNotInline = false; + temp = endNode; + attrsTmp = w->getAttrs(temp); + if(attrsTmp && (attrsTmp->chCurFoc() == kNodeAttrs::singleNodeAndItself || + attrs->chCurFoc() == kNodeAttrs::no)) + endNodeIsNotInline = true; + while(!temp.isNull() && !temp.previousSibling().isNull() && + ((kafkaCommon::isInline(temp) && (temp.previousSibling().isNull() || + kafkaCommon::isInline(temp.previousSibling()))))) + temp = temp.previousSibling(); + startNode = temp; + + if(!toplevelBlock2.isNull() && toplevelBlock2.hasChildNodes()) + startNode2 = toplevelBlock2.firstChild(); + else if(!childOfCommonParent.isNull() && !childOfCommonParent.nextSibling().isNull()) + startNode2 = childOfCommonParent.nextSibling(); + temp = startNode2; + while(!temp.isNull() && !temp.nextSibling().isNull() && + ((kafkaCommon::isInline(temp) && (temp.nextSibling().isNull() || + kafkaCommon::isInline(temp.nextSibling()))))) + temp = temp.nextSibling(); + endNode2 = temp; + + //3 - Move Nodes. + if(!endNode.isNull() && endNodeIsNotInline) + { + emit domNodeIsAboutToBeRemoved(endNode, true, m_modifs); + endNode.parentNode().removeChild(endNode); + } + else if(isParent && !prevIsBlock) + { + if(kafkaCommon::parentSupports(toplevelBlock2, startNode, endNode, + w->getCurrentDoc()->defaultDTD())) + moveDomNodes(toplevelBlock2, startNode, endNode, toplevelBlock2.firstChild(), + true); + else + { + if(kafkaCommon::parentSupports(commonParent, startNode2, endNode2, + w->getCurrentDoc()->defaultDTD())) + moveDomNodes(commonParent, startNode2, endNode2, + childOfCommonParent2, true); + else + { + //Damn it! What to do?? + } + } + } + else if(isParent && prevIsBlock) + { + if(kafkaCommon::parentSupports(toplevelBlock2, startNode, endNode, + w->getCurrentDoc()->defaultDTD())) + moveDomNodes(toplevelBlock2, startNode, endNode, toplevelBlock2.firstChild(), + true); + else + { + if(kafkaCommon::parentSupports(commonParent, startNode, endNode, + w->getCurrentDoc()->defaultDTD()) && kafkaCommon::parentSupports( + commonParent, startNode2, endNode2, w->getCurrentDoc()->defaultDTD())) + { + moveDomNodes(commonParent, startNode, endNode, childOfCommonParent, + false); + moveDomNodes(commonParent, startNode2, endNode2, childOfCommonParent2, + true); + } + else + { + //Damn it! What to do?? + } + } + } + else if(!isParent && prevIsBlock) + { + if(kafkaCommon::parentSupports(commonParent, startNode, endNode, + w->getCurrentDoc()->defaultDTD())) + moveDomNodes(commonParent, startNode, endNode, childOfCommonParent, false); + else + { + //Damn it! What to do?? + } + } + if(!startNode2.isNull() && startNode2.nodeType() == DOM::Node::TEXT_NODE) + { + //normalize(startNode2.parentNode()); + temp = startNode2.previousSibling(); + if(!temp.isNull() && temp.nodeType() == DOM::Node::TEXT_NODE) + { + boolTmp = false; + if(m_currentNode == startNode2) + { + m_currentNode = temp; + d->m_cursorOffset += temp.nodeValue().length(); + boolTmp = true; + } + text = temp.nodeValue().string() + startNode2.nodeValue().string(); + tempParent = temp.parentNode(); + emit domNodeIsAboutToBeRemoved(startNode2, true, m_modifs); + tempParent.removeChild(startNode2); + + temp.setNodeValue(text); + emit domNodeModified(temp, m_modifs); + + if(boolTmp) + QTimer::singleShot(0, this, SLOT(slotDelayedSetCaretPosition())); + } + + } + + //4 - Delete empty Block Nodes. + if(!toplevelBlock.isNull()) + { + temp = toplevelBlock; + attrsTmp = w->getAttrs(temp); + while(attrsTmp && attrsTmp->chCurFoc() == kNodeAttrs::blockNode && + !temp.hasChildNodes()) + { + tempParent = temp.parentNode(); + emit domNodeIsAboutToBeRemoved(temp, true, m_modifs); + tempParent.removeChild(temp); + temp = tempParent; + attrsTmp = w->getAttrs(temp); + } + } + if(!toplevelBlock2.isNull()) + { + temp = toplevelBlock2; + attrsTmp = w->getAttrs(temp); + while(attrsTmp && attrsTmp->chCurFoc() == kNodeAttrs::blockNode && + !temp.hasChildNodes()) + { + tempParent = temp.parentNode(); + emit domNodeIsAboutToBeRemoved(temp, true, m_modifs); + tempParent.removeChild(temp); + temp = tempParent; + attrsTmp = w->getAttrs(temp); + } + } + break; + } + //Else if the prevNode is a BLOCK or an invisible Node, Inline Node + //which can be deleted, delete it! + else if(attrs->chCurFoc() == kNodeAttrs::singleNodeAndItself || ((attrs->chCurFoc() == + kNodeAttrs::no || attrs->chCurFoc() == kNodeAttrs::inlineNode) && + !_nodePrev.hasChildNodes())) + { +#ifdef LIGHT_DEBUG + kdDebug(25001)<< "KafkaWidget::keyBackspace() - deleting" << + " a Node" << endl; +#endif + + _nodeParent = _nodePrev.parentNode(); + focus = w->getAttrs(_nodePrev)->chCurFoc(); + emit domNodeIsAboutToBeRemoved(_nodePrev, true, m_modifs); + _nodeParent.removeChild(_nodePrev); + //normalize(_nodeParent); + if(focus == kNodeAttrs::singleNodeAndItself) + { + postprocessCursorPosition(); + //merge the previous DOM::Node if it is a text. + //domNodeIsAboutToBeRemoved() already do it in the Node tree. + //=> It seems it was removed from it. + _nodePrev = _node.previousSibling(); + if(!_nodePrev.isNull() && _nodePrev.nodeType() == DOM::Node::TEXT_NODE && + m_currentNodeType == DOM::Node::TEXT_NODE) + { + if(_node == m_currentNode) + { + m_currentNode = _nodePrev; + d->m_cursorOffset += (static_cast<DOM::CharacterData>(_nodePrev)).length(); + QTimer::singleShot(0, this, SLOT(slotDelayedSetCaretPosition())); + } + _nodePrev.setNodeValue(_nodePrev.nodeValue() + _node.nodeValue()); + emit domNodeModified(_nodePrev, m_modifs); + //_nodeParent = _nodePrev.parentNode(); + emit domNodeIsAboutToBeRemoved(_node, true, m_modifs); + _nodeParent.removeChild(_node); + } + //dirty workaround when after having deleted a br, there is only one br left + //Anyway webcore will override this + if(m_currentNode.nodeName().string().lower() == "br" && + (m_currentNode.previousSibling().isNull() || (m_currentNode.previousSibling().nodeType() == + DOM::Node::TEXT_NODE && m_currentNode.previousSibling().previousSibling().isNull())) && + (m_currentNode.nextSibling().isNull() || (m_currentNode.nextSibling().nodeType() == + DOM::Node::TEXT_NODE && m_currentNode.nextSibling().nextSibling().isNull()))) + { + if(!m_currentNode.previousSibling().isNull()) + { + m_currentNode = m_currentNode.previousSibling(); + d->m_cursorOffset = 0; + + QTimer::singleShot(0, this, SLOT(slotDelayedSetCaretPosition())); + } + else if(!m_currentNode.nextSibling().isNull()) + { + m_currentNode = m_currentNode.nextSibling(); + d->m_cursorOffset = 0; + } + } + break; + } + _nodePrev = _node; + } + _nodePrev = getPrevNode(_nodePrev, _goingTowardsRootNode); + } +} + +DOM::Node KafkaWidget::getNextNode(DOM::Node _node, bool &goingTowardsRootNode, bool skipParentNodes, bool dontBlock, DOM::Node _endNode) +{ + kNodeAttrs *attrs = 0L; + + if(_node == 0) + return 0; + attrs = w->getAttrs(_node); + if(!attrs) + { + kdDebug(25001)<< "KafkaWidget::getNextNode() Attrs not found!"<< endl; + return 0; + } + if(_node.hasChildNodes() && goingTowardsRootNode == false && + (attrs->ccanEnter() || dontBlock)) + {//if we can descend to a child node, we do it +#ifdef LIGHT_DEBUG + kdDebug(25001)<< "KafkaWidget::getNextNode() - descending from node : " << + _node.nodeName().string() << " to " << + _node.firstChild().nodeName().string() << endl; +#endif + + if(_endNode == _node.firstChild()) + return 0; + return _node.firstChild(); + } + if(_node.nextSibling() != 0) + {//else if there is a sibling, we move to it + goingTowardsRootNode = false; +#ifdef LIGHT_DEBUG + + kdDebug(25001)<< "KafkaWidget::getNextNode() - going from node : " << + _node.nodeName().string() << + " to " << _node.nextSibling().nodeName().string() << endl; +#endif + + if(_endNode == _node.nextSibling()) + return 0; + return _node.nextSibling(); + } + if(_node.nextSibling() == 0) + {//else if there is no sibling, we go up if we can + goingTowardsRootNode = true; + if(_node.parentNode().isNull()) + return 0; + if(w->getAttrs(_node.parentNode()) && + w->getAttrs(_node.parentNode())->ccanEnter() || dontBlock) + { + if(!_node.parentNode().isNull()) + { +#ifdef LIGHT_DEBUG + kdDebug(25001)<< "KafkaWidget::getNextNode() - going" << + " up from node : " << _node.nodeName().string() << + " to " << _node.parentNode().nodeName().string() << endl; +#endif + + } + else + { +#ifdef LIGHT_DEBUG + kdDebug(25001)<< "KafkaWidget::getNextNode() - going" << + " up from node : " << _node.nodeName().string() << + " to an empty Node" << endl; +#endif + + } + if(skipParentNodes) + { + if(_endNode == _node.parentNode()) + return 0; + return getNextNode(_node.parentNode(), goingTowardsRootNode, + skipParentNodes, dontBlock); + } + else + { + if(_endNode == _node.parentNode()) + return 0; + return _node.parentNode(); + } + } + else + return 0; + } + kdError()<< "KafkaWidget::getNextNode() ERROR" << endl; + return 0; +} + +DOM::Node KafkaWidget::getPrevNode(DOM::Node _node, bool &goingTowardsRootNode, bool skipParentNodes, bool dontBlock, DOM::Node _endNode) +{ + kNodeAttrs *attrs = 0L; + + if(_node == 0) + return 0; + attrs = w->getAttrs(_node); + if(!attrs) + { + kdDebug(25001)<< "KafkaWidget::getPrevNode() Attrs not found!"<< endl; + return 0; + } + if(_node.hasChildNodes() && goingTowardsRootNode == false && + (attrs->ccanEnter() || dontBlock)) + {//if we can descend to a child node, we do it +#ifdef LIGHT_DEBUG + kdDebug(25001)<< "KafkaWidget::getPrevNode() - descending from node : " << + _node.nodeName().string() << " to " << + _node.lastChild().nodeName().string() << endl; +#endif + + if(_endNode == _node.lastChild()) + return DOM::Node(); + return _node.lastChild(); + } + if(_node.previousSibling() != 0) + {//else if there is a sibling, we move to it + goingTowardsRootNode = false; +#ifdef LIGHT_DEBUG + + kdDebug(25001)<< "KafkaWidget::getPrevNode() - going from node : " << + _node.nodeName().string() << + " to " << _node.previousSibling().nodeName().string() << endl; +#endif + + if(_endNode == _node.previousSibling()) + return DOM::Node(); + return _node.previousSibling(); + } + if(_node.previousSibling() == 0) + {//else if there is no sibling, we go up if we can + goingTowardsRootNode = true; + if(_node.parentNode().isNull()) + return DOM::Node(); + if(w->getAttrs(_node.parentNode()) && + w->getAttrs(_node.parentNode())->ccanEnter() || dontBlock) + { + if(!_node.parentNode().isNull()) + { +#ifdef LIGHT_DEBUG + kdDebug(25001)<< "KafkaWidget::getPrevNode() - going up from" << + " node : " << _node.nodeName().string() << " to " << + _node.parentNode().nodeName().string() << endl; +#endif + + } + else + { +#ifdef LIGHT_DEBUG + kdDebug(25001)<< "KafkaWidget::getPrevNode() - going up from" << + " node : " << _node.nodeName().string() << " to an " << + "empty Node" << endl; +#endif + + } + if (skipParentNodes) + { + if(_endNode == _node.parentNode()) + return DOM::Node(); + return getPrevNode(_node.parentNode(), goingTowardsRootNode, + skipParentNodes, dontBlock); + } + else + { + if(_endNode == _node.parentNode()) + return DOM::Node(); + return _node.parentNode(); + } + } + else + return 0; + } + kdError()<< "KafkaWidget::getPrevNode() ERROR" << endl; + return 0; +} + +void KafkaWidget::updateToggableTagActions(/*const DOM::Node &domNode, long offset*/) const +{ +//Andras: Disable toggle behavior. It is just too broken. +return; + + quantaApp->removeAllTagActionPoolItems(); + + NodeSelectionInd selection; + selection.fillWithVPLCursorSelection(); + + Node* start_node = 0, *end_node = 0; +// int start_offset = 0, end_offset = 0; + + start_node = kafkaCommon::getNodeFromLocation(selection.cursorNode()); +// start_offset = selection.cursorOffset(); + + if(!start_node) + return; + + if(selection.hasSelection()) + { + end_node = kafkaCommon::getNodeFromLocation(selection.cursorNodeEndSel()); +// end_offset = selection.cursorOffsetEndSel(); + } + else + { + end_node = start_node; +// end_offset = start_offset; + } + + // Iterate all toggable toolbar actions and toggle them on or off + // Look if there is a selection + TagAction* tag_action = 0; + QPtrList<TagAction> tag_actions = quantaApp->tagActions(); + for (tag_action = tag_actions.first(); tag_action; tag_action = tag_actions.next()) + { + if(tag_action->toggable()) + { + QString tag_name = tag_action->XMLTagName(); + if(tag_name.isEmpty()) + break; + + QDomElement data(tag_action->data()); + QString attribute_name(data.attribute("attribute_name", QString())); + QString attribute_value(data.attribute("attribute_value", QString())); + + int inside_tag; + if(!attribute_name.isEmpty() && !attribute_value.isEmpty()) + inside_tag = kafkaCommon::isInsideTag(start_node, end_node, tag_name, attribute_name, attribute_value); + else + inside_tag = kafkaCommon::isInsideTag(start_node, end_node, tag_name); + + tag_action->setChecked(inside_tag == 1); + } + } +} + +void KafkaWidget::makeCursorVisible(int , int ) +{ + /**DOM::Range range; + if(m_currentNode == 0) + return; + kdDebug(25001)<< "KafkaWidget::makeCursorVisible()" << endl; + int X, Y, dummy; + getCursor(m_currentNode, d->m_cursorOffset, X, Y, dummy); + view()->ensureVisible (X, Y, xMargin, yMargin);*/ + //does not work... ??? + /**range = selection(); + //try{ + range.setStart(m_currentNode, d->m_cursorOffset); + } + catch(DOM::RangeException e) + { + //ignore + kdDebug(25001)<< "KafkaWidget::makeCursorVisible() - ERROR " << e.code << endl; + return; + } + catch(DOM::DOMException e) + { + kdDebug(25001)<< "KafkaWidget::makeCursorVisible() - ERROR " << e.code << endl; + }*/ + //range.setEnd(m_currentNode, d->m_cursorOffset); +} + +void KafkaWidget::postprocessCursorPosition() +{ + kNodeAttrs *attrs, *attrs2; + if(m_currentNode == 0) + return; + attrs = w->getAttrs(m_currentNode); + DOM::Node _prevNextNode; + DOM::Node _nextNode = m_currentNode; + bool b = false; + + if(!attrs) + { +#ifdef LIGHT_DEBUG + kdDebug(25001)<< "KafkaWidget::postprocessCursorPosition() - WARNING no Attrs!! " << endl; +#endif + + return; + } + + if(attrs->chCurFoc() == kNodeAttrs::textNode && + d->m_cursorOffset == 0) + { + /** while(1) + { + _prevNextNode = _nextNode; + _nextNode = kafkaCommon::getPrevDomNode(_nextNode); + if(_nextNode.isNull()) + break; + attrs2 = w->getAttrs(_nextNode); + if(attrs2 && attrs2->chCurFoc() == kNodeAttrs::textNode && + (static_cast<DOM::CharacterData>(_nextNode)).length() != 0) + { + m_currentNode = _nextNode; + d->m_cursorOffset = (static_cast<DOM::CharacterData>(_nextNode)).length(); + setCaretPosition(m_currentNode, (long)d->m_cursorOffset); + emit domNodeNewCursorPos(m_currentNode, d->m_cursorOffset); + #ifdef LIGHT_DEBUG + + kdDebug(25001)<< "KafkaWidget::postprocessCursorPosition()" << + " - new currentNode :" << + m_currentNode.nodeName().string() << endl; + #endif + + break; + } + else if(attrs2->chCurFoc() == kNodeAttrs::singleNodeAndItself || + attrs2->chCurFoc() == kNodeAttrs::inlineNode || + attrs2->chCurFoc() == kNodeAttrs::blockNode) + break; + else + continue; + }*/ + } + else if(attrs->chCurFoc() == kNodeAttrs::singleNodeAndItself) + { + if(d->m_cursorOffset == 0 && !m_currentNode.isNull() && + (m_currentNode.nodeName().string().lower() != "br" || + (m_currentNode.nodeName().string().lower() == "br" && /**!m_currentNode.nextSibling().isNull() && + m_currentNode.nextSibling().nodeType() == DOM::Node::TEXT_NODE && + m_currentNode.nextSibling().nodeValue().string().isEmpty() && + m_currentNode.nextSibling().nextSibling().isNull() &&*/ + !m_currentNode.previousSibling().isNull() && + m_currentNode.previousSibling().nodeType() == DOM::Node::TEXT_NODE && + !m_currentNode.previousSibling().nodeValue().string().isEmpty()))) + { + while(1) + { + _prevNextNode = _nextNode; + _nextNode = getPrevNode(_nextNode, b); + if(_nextNode == 0) + break; + attrs2 = w->getAttrs(_nextNode); + if(attrs2 && attrs2->chCurFoc() == kNodeAttrs::textNode && + (static_cast<DOM::CharacterData>(_nextNode)).length() + != 0) + { + m_currentNode = _nextNode; + d->m_cursorOffset = (static_cast<DOM::CharacterData>(_nextNode)).length(); + setCaretPosition(m_currentNode, (long)d->m_cursorOffset); + emit domNodeNewCursorPos(m_currentNode, d->m_cursorOffset); +#ifdef LIGHT_DEBUG + + kdDebug(25001)<< "KafkaWidget::postprocessCursorPosition()" << + " - new currentNode :" << m_currentNode.nodeName().string() << endl; +#endif + + break; + } + else if(attrs2 && attrs2->chCurFoc() == kNodeAttrs::singleNodeAndItself) + { + m_currentNode = _nextNode; + d->m_cursorOffset = 1; + setCaretPosition(m_currentNode, (long)d->m_cursorOffset); + emit domNodeNewCursorPos(m_currentNode, d->m_cursorOffset); +#ifdef LIGHT_DEBUG + + kdDebug(25001)<< "KafkaWidget::postprocessCursorPosition()" << + " - new currentNode :" << m_currentNode.nodeName().string() << endl; +#endif + + break; + } + else + continue; + } + } + else if(d->m_cursorOffset == 1) + { + while(1) + { + _prevNextNode = _nextNode; + _nextNode = getNextNode(_nextNode, b); + if(_nextNode == 0) + break; + attrs2 = w->getAttrs(_nextNode); + if(attrs2 && attrs2->chCurFoc() == kNodeAttrs::singleNodeAndItself) + break; + else if(attrs2 && attrs2->chCurFoc() == kNodeAttrs::textNode && + (static_cast<DOM::CharacterData>(_nextNode)).length() != 0) + { + m_currentNode = _nextNode; + d->m_cursorOffset = 0; + setCaretPosition(m_currentNode, (long)d->m_cursorOffset); + emit domNodeNewCursorPos(m_currentNode, d->m_cursorOffset); +#ifdef LIGHT_DEBUG + + kdDebug(25001)<< "KafkaWidget::postprocessCursorPosition() " << + "- new currentNode :" << m_currentNode.nodeName().string() << endl; +#endif + + break; + } + else + continue; + } + } + } + makeCursorVisible(); +} + +void KafkaWidget::khtmlMouseMoveEvent(khtml::MouseMoveEvent *event) +{ + DOM::Node mouseNode = event->innerNode(); + + if(mouseNode == 0) + { + return; + } + if(mouseNode.nodeType() == DOM::Node::TEXT_NODE) + view()->setCursor(Qt::ibeamCursor); + else + view()->setCursor(Qt::arrowCursor); + + KHTMLPart::khtmlMouseMoveEvent(event); +} + +void KafkaWidget::khtmlMouseReleaseEvent(khtml::MouseReleaseEvent *event) +{ + KHTMLPart::khtmlMouseReleaseEvent(event); + if(m_currentNode.isNull() || m_currentNode.nodeName().string().lower() == "#document") + { + m_currentNode = w->body; + d->m_cursorOffset = 0; + setCaretPosition(m_currentNode, (long)d->m_cursorOffset); + } + if(quantaApp->aTab) + quantaApp->aTab->setCurrentNode(w->getNode(event->innerNode())); +} + +void KafkaWidget::khtmlMousePressEvent(khtml::MousePressEvent *event) +{ + KHTMLPart::khtmlMousePressEvent(event); + if(d->m_cursorOffset == 0 && !m_currentNode.isNull() && + m_currentNode.nodeName().string().lower() == "body") + putCursorAtFirstAvailableLocation(); +#ifdef HEAVY_DEBUG + //d->domdialog->domview->showTree(document()); +#endif + +} + +void KafkaWidget::khtmlDrawContentsEvent(khtml::DrawContentsEvent *event) +{ + KHTMLPart::khtmlDrawContentsEvent(event); +} + +void KafkaWidget::getCurrentNode(DOM::Node &_currentNode, long &offset) +{ + _currentNode = m_currentNode; + offset = d->m_cursorOffset; +} + +void KafkaWidget::setCurrentNode(DOM::Node node, int offset) +{ + m_currentNode = node; + d->m_cursorOffset = offset; + makeCursorVisible(); + if(!m_currentNode.isNull() && m_currentNode.nodeName().string() != "#document") + QTimer::singleShot(0, this, SLOT(slotDelayedSetCaretPosition())); + //setCaretPosition(m_currentNode, (long)d->m_cursorOffset); +} + +void KafkaWidget::setCurrentNode(Node* cursorNode, int cursorOffset) +{ + DOM::Node domNode; + long longDomNodeOffset; + KafkaDocument::ref()->translateNodeIntoKafkaCursorPosition(cursorNode, cursorOffset, domNode, longDomNodeOffset); + if (!domNode.isNull() && domNode.nodeType() != DOM::Node::TEXT_NODE && + !domNode.firstChild().isNull() && domNode.firstChild().nodeType() == DOM::Node::TEXT_NODE) + domNode = domNode.firstChild(); + if (!domNode.isNull()) + setCurrentNode(domNode, (int)longDomNodeOffset); +} + +void KafkaWidget::putCursorAtFirstAvailableLocation() +{ + kNodeAttrs *attrs = 0L; + DOM::Node node = w->body; + bool b = false; + +#ifdef HEAVY_DEBUG + + w->coutLinkTree(baseNode, 2); + kafkaCommon::coutTree(baseNode, 2); + kafkaCommon::coutDomTree(document(), 2); +#endif + + while(!node.isNull()) + { + node = kafkaCommon::getNextDomNode(node, b); + if(node.isNull()) + { + if(!w->body.isNull()) + node = w->body; + else + node = DOM::Node(); + break; + } + attrs = w->getAttrs(node); + if(!attrs) + { + node = w->body; + break; + } + if(node.nodeType() == DOM::Node::TEXT_NODE) + break; + } + m_currentNode = node; + d->m_cursorOffset = 0; + QTimer::singleShot(0, this, SLOT(slotDelayedSetCaretPosition())); + +#ifdef LIGHT_DEBUG + + if(!m_currentNode.isNull()) + kdDebug(25001)<< "KafkaWidget::putCursorAtFirstAvailableLocation() - " << + m_currentNode.nodeName().string() << endl; +#endif + +} + +void KafkaWidget::slotNewCursorPos(const DOM::Node &domNode, long offset) +{ + if(!w->isLoaded()) + return; + + m_currentNode = domNode; + d->m_cursorOffset = (int)offset; +#ifdef LIGHT_DEBUG + + kdDebug(25001)<<"KafkaWidget::slotNewCursorPos() offset : " << d->m_cursorOffset << endl; +#endif + + if(quantaApp->aTab && ViewManager::ref()->activeView()->hadLastFocus() == QuantaView::VPLFocus) + quantaApp->aTab->setCurrentNode(w->getNode(domNode)); + + updateToggableTagActions(/*domNode, offset*/); +} + +void KafkaWidget::moveDomNodes(DOM::Node newParent, DOM::Node startNode, DOM::Node endNode, + DOM::Node refNode, bool before) +{ + DOM::Node domNode, domNodeNext; + + if(newParent.isNull()) + return; + + if(before) + { + domNode = endNode; + while(!domNode.isNull()) + { + domNodeNext = domNode.previousSibling(); + emit domNodeIsAboutToBeMoved(domNode, newParent, refNode, m_modifs); + //emit domNodeIsAboutToBeRemoved(domNode, true); + domNode = domNode.parentNode().removeChild(domNode); + if(!refNode.isNull()) + newParent.insertBefore(domNode, refNode); + else + newParent.insertBefore(domNode, DOM::Node()); + //emit domNodeInserted(domNode, true); + if(domNode == startNode) + break; + domNode = domNodeNext; + } + } + else + { + domNode = startNode; + while(!domNode.isNull()) + { + domNodeNext = domNode.nextSibling(); + //emit domNodeIsAboutToBeRemoved(domNode, true); + if(!refNode.isNull()) + emit domNodeIsAboutToBeMoved(domNode, newParent, refNode.nextSibling(), m_modifs); + else + emit domNodeIsAboutToBeMoved(domNode, newParent, DOM::Node(), m_modifs); + domNode = domNode.parentNode().removeChild(domNode); + if(!refNode.isNull()) + newParent.insertBefore(domNode, refNode.nextSibling()); + else + newParent.insertBefore(domNode, DOM::Node()); + //emit domNodeInserted(domNode, true); + if(domNode == endNode) + break; + domNode = domNodeNext; + } + } +} + +void KafkaWidget::removeSelection() +{ + Q_ASSERT(hasSelection()); + + NodeSelectionInd selection; + selection.fillWithVPLCursorSelection(); + Node* cursorNode = kafkaCommon::getNodeFromLocation(selection.cursorNode()); + long cursorOffset = 0; + long domNodeCursorOffset = 0; + + kafkaCommon::DTDRemoveSelection(selection, &cursorNode, cursorOffset, m_modifs); + + KafkaDocument::ref()->translateNodeIntoKafkaCursorPosition(cursorNode, cursorOffset, m_currentNode, domNodeCursorOffset); + d->m_cursorOffset = domNodeCursorOffset; + + setCurrentNode(m_currentNode, domNodeCursorOffset); + + QTimer::singleShot(0, this, SLOT(slotDelayedSetCaretPosition())); + + NodeSelection* cursorPos = new NodeSelection(); + cursorPos->setCursorNode(cursorNode); + cursorPos->setCursorOffset(cursorOffset); + + ViewManager::ref()->activeDocument()->docUndoRedo->addNewModifsSet(m_modifs, undoRedo::NodeTreeModif, cursorPos); + m_modifs = 0; + + delete cursorPos; + + makeCursorVisible(); +} + +void KafkaWidget::applyQueuedToggableTagActions() +{ + QStringList queued_actions = quantaApp->tagActionPool(); + QPtrList<TagAction> action_list = quantaApp->tagActions(); + for(QStringList::Iterator it = queued_actions.begin(); it != queued_actions.end(); ++it) + { + TagAction* tag_action = 0; + for (tag_action = action_list.first(); tag_action; tag_action = action_list.next()) + { + if(tag_action->name() == *it) + { + tag_action->slotActionActivated(KAction::EmulatedActivation, Qt::NoButton); + break; + } + } + } + quantaApp->removeAllTagActionPoolItems(); +} + +#include "kafkahtmlpart.moc" diff --git a/quanta/parts/kafka/kafkahtmlpart.h b/quanta/parts/kafka/kafkahtmlpart.h new file mode 100644 index 00000000..f1dc79c1 --- /dev/null +++ b/quanta/parts/kafka/kafkahtmlpart.h @@ -0,0 +1,315 @@ +/*************************************************************************** + kafkahtmlpart.h + ------------------- + + copyright : (C) 2001 - The Kafka Team + (C) 2003, 2004 - Nicolas Deschildre + email : kde-kafka@master.kde.org && ndeschildre@kdewebdev.org + ***************************************************************************/ + +/*************************************************************************** + * * + * 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. * + * * + ***************************************************************************/ + +#ifndef KAFKAHTMLPART_H +#define KAFKAHTMLPART_H + +#include <kurl.h> +#include <khtml_part.h> +#include <khtml_events.h> +#include <kparts/event.h> + +#include <qstringlist.h> +#include <qptrlist.h> +#include <qdockwindow.h> +#include <qevent.h> + +#include <dom/dom_node.h> +#include <dom/dom_element.h> + +class DOMString; +class KafkaWidgetPrivate; +class KafkaDocument; +class NodeModifsSet; +class QPopupMenu; + +/* + * It is the main central widget which get the cursor focus and takes care of "simple" edition + * such as typing text. + */ + +class KafkaWidget : public KHTMLPart +{ + Q_OBJECT +public: + KafkaWidget(QWidget *parent, QWidget *widgetParent, KafkaDocument *part, + const char *name = 0); + ~KafkaWidget(); + + /** + * Category: Standard Functions + * Called by KafkaPart to create a new document + */ + void newDocument(); + + /** + * Returns the current Node (which has the focus) and its offset. + * @param currentNode The Node which has the cursor focus. + * @param offset The offset of the cursor in _currentNode. + */ + void getCurrentNode(DOM::Node ¤tNode, long &offset); + + /** + * Set the focus. + * @param node The DOM::Node which will get the focus. + * @param offset The offset of the cursor if node is a DOM::Node::TEXT_NODE + */ + void setCurrentNode(DOM::Node node, int offset); + + /** + * Convenience function to set the current node. + * @param node The Quanta node which will get the focus. + * @param offset The offset of the cursor if node tag is of type Tag::Text. + */ + void setCurrentNode(Node* cursorNode, int cursorOffset); + + /** + * Get the next DOM::Node after _node. + * @param _node The DOM::Node the search starts from. + * @param goUp A needed boolean for several consecutive call to + * this function. Setting it to false by default, or to true if we don't want to go through + * the childs of _node. + * @param skipParentNodes Specifies if we should skip the parent Node when going up : + * this implies that Nodes will not be returned two times. + * @param dontBlock Specifies if the search should or not be blocked by BlockingTags. + * @param _endNode Specifies at which DOM::Node the search should end. It is useful + * when setting skipParentNodes to true. + * @return Returns the next Node :) + */ + DOM::Node getNextNode(DOM::Node _node, bool &goUp, bool skipParentNodes = false, + bool dontBlock = false, DOM::Node _endNode = DOM::Node()); + + /** + * The same that above, but this function search the previous DOM::Node. + */ + DOM::Node getPrevNode(DOM::Node _node, bool &goUp, bool skipParentNodes = false, + bool dontBlock = false, DOM::Node _endNode = DOM::Node()); + + /** + * Set the TagActions checked/unchecked in context. + * This is called when the cursor position changes. + * @param domNode + * @param offset + */ + void updateToggableTagActions(/*const DOM::Node &domNode, long offset*/) const; + + + /** --------------- DOM::Node modifications -------------------------- */ + + /** + * It will move DOM::Nodes from startNode to endNode as children of newParent. It does NOT check + * if the move is valid, so it may crash. Please check before with kafkaCommon::parentSupports(). + * @param newParent The new parent of the DOM::Nodes. + * @param startNode The first node to move. + * @param endNode Starting from endNode, the last sibling to move. + * @param refNode The reference DOM::Node. + * @bool before If set to true, the DOM::nodes will be moved at the left of refNode, + * otherwise they will be happenend at the right of refNode. + */ + void moveDomNodes(DOM::Node newParent, DOM::Node startNode, DOM::Node endNode, + DOM::Node refNode, bool before); + +public slots: + + /** + * Category: HTML Editing Functions + * Adds text into a DOM::Node of type DOM::Node::TEXT_NODE + * @param node The node where text should be added + * @param text The text to be added + * @param position Specifies the position where to add text + */ + void insertText(DOM::Node node, const QString &text, int position); + + /** + * Category: HTML Editing Functions + * Adds text into the current DOM::Node + * @param text The text to add + * @param position Specifies the position where to add text, if it's -1 + * the text will be appended after the last position + */ + void insertText(const QString &text, int position); + + /** + * Puts all the child Text DOM::Node into a "normal" form where only + * structure (e.g., elements, comments, processing instructions, CDATA + * sections, and entity references) separates Text nodes, i.e., there are + * neither adjacent Text nodes nor empty Text nodes. + * Should be used instead of DOM::Node::normalize(). + * @param _node This node is the parent node of the childs normalized + */ + void normalize(DOM::Node _node); + + /** + * Set the cursor after having loaded the DOM tree from scratch. + * Look for the first available location for the cursor. + */ + void putCursorAtFirstAvailableLocation(); + + /** + * Reload the TagAttributesTree if necessary; + * @param domNode The current DOM::Node. + * @param offset The current cursor offset. + */ + void slotNewCursorPos(const DOM::Node &domNode, long offset); + + /** + * Called to set the cursor Position with a QTimer::singleShot() after calling + * document->updateRendering() + */ + void slotDelayedSetCaretPosition(); + +signals: + /** + * Category: HTML Editing Signal + * Is emitted whenever the DOM tree has changed + * Not yet implemented + */ + void domChanged(); + + /** + * Category: HTML Editing Signal + * Is emitted whenever a dom Node is inserted to the tree. + * @param node is the node created. + * @param insertChilds Specifies if we should insert the _node's childs + * @param modifs The changes made are logged into modifs. + */ + void domNodeInserted(DOM::Node node, bool insertChilds, NodeModifsSet *modifs); + + /** + * Category: HTML Editing Signal + * Is emitted whenever a DOM node has its properties modified. + * @param node is the node modified. + * @param modifs The changes made are logged into modifs. + */ + void domNodeModified(DOM::Node node, NodeModifsSet *modifs); + + /** + * Category: HTML Editing Signal + * Is emitted whenever a DOM node is about to be removed from the tree. + * @param node is the node to be deleted. + * @param deleteChilds Specifies if we should delete the child nodes of _node. + * @param modifs The changes made are logged into modifs. + */ + void domNodeIsAboutToBeRemoved(DOM::Node node, bool deleteChilds, NodeModifsSet *modifs); + + /** + * Is emitted whenever a DOM::Node is about to be moved in the tree. + * @param node The DOM::Node which will be moved. + * @param newParent The new parent of domNode. + * @param before domNode will be placed before before. + * @param modifs The changes made are logged into modifs. + */ + void domNodeIsAboutToBeMoved(DOM::Node node, DOM::Node newParent, DOM::Node before, NodeModifsSet *modifs); + + /** + * Category: HTML Editing Signal + * Is emitted whenever the cursor position change in one DOM::Node. + * @param node The DOM::Node which contains the cursor. + * @param _offset The new cursorOffset. + */ + void domNodeNewCursorPos(DOM::Node node, int offset); + + /** + * Category: Widget Signal + * Is emitted whenever the KafkaWidget widget get or lost the focus + * @param focus True if it has got the focus, false otherwise. + */ + void hasFocus(bool focus); + +protected: + bool eventFilter(QObject *object, QEvent *event); + + virtual void khtmlMouseMoveEvent(khtml::MouseMoveEvent *event); + virtual void khtmlMousePressEvent(khtml::MousePressEvent *event); + virtual void khtmlMouseReleaseEvent(khtml::MouseReleaseEvent *event); + virtual void khtmlDrawContentsEvent(khtml::DrawContentsEvent *event); + +private slots: + + void slotContextMenuRequested(const QString& url, const QPoint& point); + +private: +#if 0 + /** + * NOT IMPLEMENTED. + * Internal function used by keyBackspace(), keyDelete() to delete Nodes. + * @param startNode The DOM::Node location of the point where we start deleting Nodes. + * @param offset The offset location of the point where we start deleting Nodes. They can be + * updated. + * @param backspace Specifies if it should delete Nodes backward (backspace keystroke) + * or torwards (delete backspace). + */ + void keyDeleteNodes(DOM::Node &startNode, long &offset, bool backspace); +#endif + + /** + * Delete one character/DOM::Node left to the cursor. + */ + void keyBackspace(); + + /** + * Delete one character/DOM::Node right to the cursor. + */ + void keyDelete(); + + /** + * Break the current line. + * @param specialPressed If a special touch is pressed e.g. ctrl, we try to insert a BR, + * otherwise we try to insert a P. + */ + void keyReturn(bool specialPressed); + + /** + * Postprocess the cursor position, i.e. when the cursor is between two + * DOM::Nodes, it set the cursor in the right node to make edition as + * in a word processor. + */ + void postprocessCursorPosition(); + /** + * Make the cursor visible whenever it is going :-) + * @param xMargin The minimum margin in the X axis between the cursor + * and the edge of the widget. + * @param yMargin The minimim margin in the Y axis between the cursor + * and the edge of the widget. + */ + void makeCursorVisible(int xMargin = 50, int yMargin = 50); + + /** + * Removes the selection and places the cursor in the right place. + */ + void removeSelection(); + + void applyQueuedToggableTagActions(); + +private: + /** + * When some changes are made, store the changes made in m_modifs. + * It will be created when a key is pressed, and will be submitted to + * the undoRedo system after the changes have been made. + */ + NodeModifsSet *m_modifs; + + DOM::Node m_currentNode; + KafkaWidgetPrivate *d; + KafkaDocument *w; + friend class KafkaWidgetPrivate; + + QPopupMenu* m_contextPopupMenu; +}; + +#endif diff --git a/quanta/parts/kafka/kafkaresource.h b/quanta/parts/kafka/kafkaresource.h new file mode 100644 index 00000000..e1261c14 --- /dev/null +++ b/quanta/parts/kafka/kafkaresource.h @@ -0,0 +1,34 @@ +/*************************************************************************** + kafkaresource.h + ------------------- + + copyright : (C) 2003 - Nicolas Deschildre + email : nicolasdchd@ifrance.com + ***************************************************************************/ + +/*************************************************************************** + * * + * 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. * + * * + ***************************************************************************/ + +#ifndef KAFKARESOURCE_H +#define KAFKARESOURCE_H + +/** + * A few globals variables avoiding to use the quantaApp global variables : + * quantaApp->view()->kafkaInterface()->getKafkaPart()... + * Also a (little) step torwards kafka independance from the quanta code (to be able to make it a kpart, at last!) + */ + +class KafkaDocument; +class KafkaWidget; + +extern KafkaWidget *kafkaWidget; +extern KafkaDocument *kafkaDoc; + +#endif + diff --git a/quanta/parts/kafka/kafkasyncoptions.cpp b/quanta/parts/kafka/kafkasyncoptions.cpp new file mode 100644 index 00000000..a355b99a --- /dev/null +++ b/quanta/parts/kafka/kafkasyncoptions.cpp @@ -0,0 +1,85 @@ +/*************************************************************************** + kafkasyncoptions.cpp + ------------------- + + copyright : (C) 2003 - Nicolas Deschildre + email : ndeschildre@kdewebdev.org + ***************************************************************************/ + +/*************************************************************************** + * * + * 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. * + * * + ***************************************************************************/ + +#include <qradiobutton.h> +#include <qcheckbox.h> +#include <qspinbox.h> + +#include <kconfig.h> +#include <kdebug.h> + +#include "kafkasyncoptions.h" +#include "kafkasyncoptions.moc" + +KafkaSyncOptions::KafkaSyncOptions( KConfig *a_config, QWidget* parent, const char* name ) + : KafkaSyncOptionsUI( parent, name ) +{ + config = a_config; + + config->setGroup("HTML Enhancer"); + bool showIcons = config->readBoolEntry("Show Scripts Icons", true); + + config->setGroup("Kafka Synchronization options"); + QString sourceRefresh = config->readEntry("Source refresh", "delay"); + int sourceRefreshDelay = config->readNumEntry("Source refresh delay", 500); + QString kafkaRefresh = config->readEntry("Kafka refresh", "focus"); + int kafkaRefreshDelay = config->readNumEntry("Kafka refresh delay", 4000); + + if ( !name ) + setName( "kafkaSyncOptions" ); + + showScriptsIcon->setChecked(showIcons); + + if(sourceRefresh == "focus") + sourceFocusRefresh->setChecked(true); + else if(sourceRefresh == "delay") + sourceDelayRefresh->setChecked(true); + sourceDelay->setValue(sourceRefreshDelay); + + if(kafkaRefresh == "focus") + kafkaFocusRefresh->setChecked(true); + else if(kafkaRefresh == "delay") + kafkaDelayRefresh->setChecked(true); + kafkaDelay->setValue(kafkaRefreshDelay); +} + +KafkaSyncOptions::~KafkaSyncOptions() +{ + +} + + +void KafkaSyncOptions::updateConfig() +{ + config->setGroup("HTML Enhancer"); + config->writeEntry("Show Scripts Icons", showScriptsIcon->isChecked()); + + config->setGroup("Kafka Synchronization options"); + if(sourceFocusRefresh->isChecked()) + config->writeEntry("Source refresh", "focus"); + else if(sourceDelayRefresh->isChecked()) + config->writeEntry("Source refresh", "delay"); + config->writeEntry("Source refresh delay", sourceDelay->value()); + + if(kafkaFocusRefresh->isChecked()) + config->writeEntry("Kafka refresh", "focus"); + else if(kafkaDelayRefresh->isChecked()) + config->writeEntry("Kafka refresh", "delay"); + config->writeEntry("Kafka refresh delay", kafkaDelay->value()); + +} + diff --git a/quanta/parts/kafka/kafkasyncoptions.h b/quanta/parts/kafka/kafkasyncoptions.h new file mode 100644 index 00000000..25b200d7 --- /dev/null +++ b/quanta/parts/kafka/kafkasyncoptions.h @@ -0,0 +1,39 @@ +/*************************************************************************** + kafkasyncoptions.h + ------------------- + + copyright : (C) 2003 - Nicolas Deschildre + email : ndeschildre@kdewebdev.org + ***************************************************************************/ + +/*************************************************************************** + * * + * 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. * + * * + ***************************************************************************/ + +#ifndef KAFKASYNCOPTIONS_H +#define KAFKASYNCOPTIONS_H + +#include "kafkasyncoptionsui.h" + +class KConfig; + +class KafkaSyncOptions : public KafkaSyncOptionsUI +{ + Q_OBJECT + +public: + KafkaSyncOptions( KConfig *config, QWidget* parent = 0, const char* name = 0); + ~KafkaSyncOptions(); + + void updateConfig(); + +protected: + KConfig *config; +}; + +#endif diff --git a/quanta/parts/kafka/kafkasyncoptionsui.ui b/quanta/parts/kafka/kafkasyncoptionsui.ui new file mode 100644 index 00000000..2fb73661 --- /dev/null +++ b/quanta/parts/kafka/kafkasyncoptionsui.ui @@ -0,0 +1,196 @@ +<!DOCTYPE UI><UI version="3.1" stdsetdef="1"> +<class>KafkaSyncOptionsUI</class> +<widget class="QWidget"> + <property name="name"> + <cstring>KafkaSyncOptionsUI</cstring> + </property> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>600</width> + <height>498</height> + </rect> + </property> + <grid> + <property name="name"> + <cstring>unnamed</cstring> + </property> + <widget class="QGroupBox" row="1" column="0"> + <property name="name"> + <cstring>views</cstring> + </property> + <property name="title"> + <string>Dual Views Mode Synchronization</string> + </property> + <grid> + <property name="name"> + <cstring>unnamed</cstring> + </property> + <widget class="QButtonGroup" row="1" column="0"> + <property name="name"> + <cstring>kafkaGroup</cstring> + </property> + <property name="lineWidth"> + <number>0</number> + </property> + <property name="title"> + <string></string> + </property> + <grid> + <property name="name"> + <cstring>unnamed</cstring> + </property> + <spacer row="1" column="1"> + <property name="name"> + <cstring>spacer5</cstring> + </property> + <property name="orientation"> + <enum>Horizontal</enum> + </property> + <property name="sizeType"> + <enum>Expanding</enum> + </property> + <property name="sizeHint"> + <size> + <width>120</width> + <height>31</height> + </size> + </property> + </spacer> + <widget class="QLabel" row="1" column="3"> + <property name="name"> + <cstring>ms2</cstring> + </property> + <property name="text"> + <string>ms</string> + </property> + </widget> + <widget class="QSpinBox" row="1" column="2"> + <property name="name"> + <cstring>kafkaDelay</cstring> + </property> + <property name="maxValue"> + <number>10000</number> + </property> + <property name="minValue"> + <number>10</number> + </property> + </widget> + <widget class="QRadioButton" row="0" column="0" rowspan="1" colspan="2"> + <property name="name"> + <cstring>kafkaFocusRefresh</cstring> + </property> + <property name="text"> + <string>Refresh the VPL editor on click</string> + </property> + </widget> + <widget class="QRadioButton" row="1" column="0"> + <property name="name"> + <cstring>kafkaDelayRefresh</cstring> + </property> + <property name="text"> + <string>Refresh the VPL editor every:</string> + </property> + </widget> + </grid> + </widget> + <widget class="QButtonGroup" row="0" column="0"> + <property name="name"> + <cstring>sourceGroup</cstring> + </property> + <property name="lineWidth"> + <number>0</number> + </property> + <property name="title"> + <string></string> + </property> + <grid> + <property name="name"> + <cstring>unnamed</cstring> + </property> + <spacer row="1" column="1"> + <property name="name"> + <cstring>spacer4</cstring> + </property> + <property name="orientation"> + <enum>Horizontal</enum> + </property> + <property name="sizeType"> + <enum>Expanding</enum> + </property> + <property name="sizeHint"> + <size> + <width>27</width> + <height>31</height> + </size> + </property> + </spacer> + <widget class="QLabel" row="1" column="3"> + <property name="name"> + <cstring>ms</cstring> + </property> + <property name="text"> + <string>ms</string> + </property> + </widget> + <widget class="QSpinBox" row="1" column="2"> + <property name="name"> + <cstring>sourceDelay</cstring> + </property> + <property name="maxValue"> + <number>10000</number> + </property> + <property name="minValue"> + <number>10</number> + </property> + </widget> + <widget class="QRadioButton" row="0" column="0" rowspan="1" colspan="4"> + <property name="name"> + <cstring>sourceFocusRefresh</cstring> + </property> + <property name="text"> + <string>Refresh the source editor on click</string> + </property> + </widget> + <widget class="QRadioButton" row="1" column="0"> + <property name="name"> + <cstring>sourceDelayRefresh</cstring> + </property> + <property name="text"> + <string>Refresh the source editor every:</string> + </property> + </widget> + </grid> + </widget> + </grid> + </widget> + <spacer row="2" column="0"> + <property name="name"> + <cstring>spacer6</cstring> + </property> + <property name="orientation"> + <enum>Vertical</enum> + </property> + <property name="sizeType"> + <enum>Expanding</enum> + </property> + <property name="sizeHint"> + <size> + <width>21</width> + <height>167</height> + </size> + </property> + </spacer> + <widget class="QCheckBox" row="0" column="0"> + <property name="name"> + <cstring>showScriptsIcon</cstring> + </property> + <property name="text"> + <string>Show an icon where scripts are located</string> + </property> + </widget> + </grid> +</widget> +<layoutdefaults spacing="6" margin="11"/> +</UI> diff --git a/quanta/parts/kafka/nodeenhancer.h b/quanta/parts/kafka/nodeenhancer.h new file mode 100644 index 00000000..376379f1 --- /dev/null +++ b/quanta/parts/kafka/nodeenhancer.h @@ -0,0 +1,76 @@ +/*************************************************************************** + nodeenhancer.h + ------------------- + + copyright : (C) 2003, 2004 - Nicolas Deschildre + email : ndeschildre@kdewebdev.org + ***************************************************************************/ + +/*************************************************************************** + * * + * 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. * + * * + ***************************************************************************/ + +#ifndef NODEENHANCER_H +#define NODEENHANCER_H + +class KConfig; +class Node; +namespace DOM +{ + class Node; +} + +/** + * Base class which define an interface to enhance DOM::Nodes + * i.e. to modify the corresponding DOM::Node of a Node. + * The aim is to modify the kafka view to provide visual aids + * like a error system which highlight syntax error, and so on... + * Defining this in XML files could be a nice idea once kafka + * support all the DTDs. + */ + +class NodeEnhancer +{ +public: + NodeEnhancer(){} + virtual ~NodeEnhancer(){} + + /** + * This function modify the DOM::Node of the node. + * The DOM::Node must be built before calling this + * function. + * @param node The Node we want to enhance. + * @param parentDNode the parent DOM::Node of the root DOM::Node of node. + * @param nextDNode the DOM::Node next to the root DOM::Node of node. + */ + virtual bool enhanceNode(Node *node, DOM::Node parentDNode, DOM::Node nextDNode) = 0; + + /** + * This function apply some modifications once the whole DOM::Node tree is built. + * @param domNode The domNode we want to add some modifications. + */ + virtual void postEnhanceNode(DOM::Node domNode) = 0; + + /** + * This function un-apply the modifications made by postEnhanceNode() + * @param domNode The DOM::Node we want to un-enhance! + */ + virtual void postUnenhanceNode(DOM::Node domNode) = 0; + + /** + * Read the config. + * @m_config The config to read. + */ + virtual void readConfig(KConfig *m_config) = 0; + + /** + * This need to be strongly extended. + */ +}; + +#endif diff --git a/quanta/parts/kafka/nodeproperties.cpp b/quanta/parts/kafka/nodeproperties.cpp new file mode 100644 index 00000000..8ce8a29d --- /dev/null +++ b/quanta/parts/kafka/nodeproperties.cpp @@ -0,0 +1,37 @@ +/*************************************************************************** + nodeproperties.cpp + ------------------- + + copyright : (C) 2003, 2004 - Nicolas Deschildre + email : ndeschildre@kdewebdev.org + ***************************************************************************/ + +/*************************************************************************** + * * + * 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. * + * * + ***************************************************************************/ + +#include "node.h" + +#include "nodeproperties.h" + +kNodeAttrs::kNodeAttrs() +{ + m_node = 0L; + m_isLinkedToNode = true; + m_specialBehavior = kNodeAttrs::none; + m_type = 0; + m_cbDeleted = false; + m_cbModified = false; + m_chCursorFocus = kNodeAttrs::no; + m_ccEnter = false; +} + +kNodeAttrs::~kNodeAttrs() +{ + +} diff --git a/quanta/parts/kafka/nodeproperties.h b/quanta/parts/kafka/nodeproperties.h new file mode 100644 index 00000000..592e69d7 --- /dev/null +++ b/quanta/parts/kafka/nodeproperties.h @@ -0,0 +1,154 @@ +/*************************************************************************** + nodeproperties.h + ------------------- + + copyright : (C) 2003, 2004 - Nicolas Deschildre + email : ndeschildre@kdewebdev.org + ***************************************************************************/ + +/*************************************************************************** + * * + * 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. * + * * + ***************************************************************************/ + +#ifndef NODEPROPERTIES_H +#define NODEPROPERTIES_H + +#include <dom/dom_node.h> + +class Node; + +/** + * This class basically acts as a link containing attributes between Nodes and DOM::Nodes. + * Node->rootNode(), Node->leafNode() are Node->DOM::Node links (cf node.h) + * domNodeProps[domNode.handle()] is DOM::Node->kNodeAttr link (cf wkafkapart.h) + */ +class kNodeAttrs +{ +public: + kNodeAttrs(); + ~kNodeAttrs(); + + /** + * Get/set the Node this kNodeAttrs is linked to. + */ + Node *getNode() {return m_node;} + void setNode(Node *node) {m_node = node;} + + /** + * Get/set the DOM::Node this kNodeAttrs is linked to. + */ + DOM::Node getDomNode() {return m_domNode;} + void setDomNode(DOM::Node domNode) {m_domNode = domNode;} + + /** + * Get/set if the DOM::Node linked by this kNodeAttrs has a corresponding Node. + * If not, this may reveal that it is a special DOM::Node which could be handled separatly. + */ + bool isLinkedToNode() {return m_isLinkedToNode;} + void setIsLinkedToNode(bool isLinkedToNode) {m_isLinkedToNode = isLinkedToNode;} + + /** + * The different possible reasons why this kNodeAttr isn't linked to a Node. + */ + enum specialBehavior + { + /** + * No special behavior. + */ + none = 0, + + /** + * The DOM::Node linked by this kNodeAttrs is an empty Text DOM::Node + * which has been set at the left of a block so that the user can + * access this area with the cursor. + */ + emptyTextSurroundingBlockElementAtTheLeft, + + /** + * Same as above but at the right of a block. + */ + emptyTextSurroundingBlockElementAtTheRight, + + /** + * The DOM::Node linked by this kNodeAttrs is an empty Text DOM::Node + * which has been set as the only child of a childless element so that the user + * can access this area with the cursor. + */ + emptyTextAsChildOfAChildlessElement + }; + + /** + * Get/set the reason why this kNodeAttrs isn't linked to a Node. + */ + int specialBehavior() {return m_specialBehavior;} + void setSpecialBehavior(int specialBehavior) {m_specialBehavior = specialBehavior;} + + /** + * SOME PARTS will be brought back later (for non-HTML DTD edition) + */ + + //DEPRECATED. + int getType() {return m_type;} + void setType(int type) {m_type = type;} + + /** + * Get/set the "type" of Node. (Can Have CUrsor FOCus) cf cursorType. Useful + * when manipulating Nodes with the caret. + */ + int chCurFoc() {return m_chCursorFocus;} + void setCHCursorFocus(int chCursorFocus) {m_chCursorFocus = chCursorFocus;} + + //Not real clear, comes from old deprecated code. + enum cursorType + { + //Can't have the cursor focus, or we don't care to know that. + no = 0, + //Inline Node + inlineNode, + //A Block Node in which the cursor can enter/leave e.g. H1, DIV + blockNode, + //A BLOCK Node in which the cursor can't enter/leave e.g. TABLE + singleNodeAndItself, + //A text Node. + textNode + }; + + /** + * Specifies if the cursor can enter/leave this Node. (Cursor CAN ENTER) + */ + bool ccanEnter() {return m_ccEnter;} + void setCCEnter(bool ccEnter) {m_ccEnter = ccEnter;} + + /** + * NOT USED. It will be used with the non HTML DTD support. + * Specifies if this Node can be modified e.g. resizing. + */ + bool cbMod() {return m_cbModified;} + void setCBModified(bool cbModified) {m_cbModified = cbModified;} + + /** + * NOT USED. It will be used with the non HTML DTD support. + * Specifies if this Node can be deleted. + */ + bool cbDel() {return m_cbDeleted;} + void setCBDeleted(bool cbDeleted) {m_cbDeleted = cbDeleted;} + +private: + Node* m_node; + DOM::Node m_domNode; + bool m_isLinkedToNode; + int m_specialBehavior; + int m_type; + bool m_cbDeleted; + bool m_cbModified; + int m_chCursorFocus; + bool m_ccEnter; + +}; + +#endif diff --git a/quanta/parts/kafka/pics/Makefile.am b/quanta/parts/kafka/pics/Makefile.am new file mode 100644 index 00000000..57a6d42e --- /dev/null +++ b/quanta/parts/kafka/pics/Makefile.am @@ -0,0 +1,2 @@ +kafkapartpicsdir = $(kde_datadir)/kafkapart/pics +kafkapartpics_DATA = php.png comment.png diff --git a/quanta/parts/kafka/pics/comment.png b/quanta/parts/kafka/pics/comment.png Binary files differnew file mode 100644 index 00000000..e8b58b4f --- /dev/null +++ b/quanta/parts/kafka/pics/comment.png diff --git a/quanta/parts/kafka/pics/php.png b/quanta/parts/kafka/pics/php.png Binary files differnew file mode 100644 index 00000000..2b03a087 --- /dev/null +++ b/quanta/parts/kafka/pics/php.png diff --git a/quanta/parts/kafka/undoredo.cpp b/quanta/parts/kafka/undoredo.cpp new file mode 100644 index 00000000..7ca12718 --- /dev/null +++ b/quanta/parts/kafka/undoredo.cpp @@ -0,0 +1,1539 @@ +/*************************************************************************** + undoredo.cpp + ------------------- + + copyright : (C) 2003, 2004 - Nicolas Deschildre + email : ndeschildre@kdewebdev.org + ***************************************************************************/ + +/*************************************************************************** + * * + * 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. * + * * + ***************************************************************************/ + +//debug only +#include <qdatetime.h> +//end debug only +#include <qtabwidget.h> + +#include <kapplication.h> +#include <kconfig.h> +#include <kdebug.h> +#include <kiconloader.h> +#include <ktexteditor/editinterface.h> +#include <ktexteditor/editinterfaceext.h> +#include <ktexteditor/selectioninterface.h> +#include <ktexteditor/viewcursorinterface.h> +#include <dom/dom_node.h> +#include <dom/dom_exception.h> + +#include "document.h" +#include "node.h" +#include "quantaview.h" +#include "quantacommon.h" +#include "resource.h" +#include "tag.h" +#include "viewmanager.h" +#include "wkafkapart.h" +#include "kafkacommon.h" +#include "kafkaresource.h" +#include "cursors.h" + +#include "undoredo.h" + + +NodeModif::NodeModif() +{ + m_type = -1; + m_node = 0L; + m_tag = 0L; + m_childrenMovedUp = 0; + m_neighboursMovedDown = 0; +} + +NodeModif::~NodeModif() +{ + if(m_node) + { + m_node->parent = 0L; + m_node->next = 0L; + m_node->prev = 0L; + if(m_type == NodeRemoved) + m_node->child = 0L; + Node::deleteNode(m_node); + } + if(m_tag) + delete m_tag; +} + +void NodeModif::setNode(Node *node) +{ + ViewManager *viewManager = ViewManager::ref(); + if(viewManager && viewManager->activeDocument() && !viewManager->activeDocument()->docUndoRedo->turnedOn()) + { + //FIXME: Andras: I don't have the slightest idea what this is supposed to do and what the + //below comment means, but without a real delete we are seriously leaking memory + Node::deleteNode(m_node); + m_node = 0L; + Node::deleteNode(node); + return; + } + else + { + m_node = node; + if(m_node) + { + m_node->parent = 0L; + m_node->next = 0L; + m_node->prev = 0L; + if(m_type == NodeRemoved) + m_node->child = 0L; + } + } +} + +void NodeModif::setTag(Tag *tag) +{ + ViewManager *viewManager = ViewManager::ref(); + if(viewManager && viewManager->activeDocument() && !viewManager->activeDocument()->docUndoRedo->turnedOn()) + { + //TEMPORARY cf setNode + m_tag = tag; + if(m_tag) + { + delete m_tag; + m_tag = 0L; + } + } + else + { + m_tag = tag; + } +} + +NodeModifsSet::NodeModifsSet() +{ + m_selectionBefore = new NodeSelectionInd(); + m_selectionAfter = new NodeSelectionInd(); + m_indentationStartOffset = -1; + + if(ViewManager::ref()->activeDocument()) + m_isModifiedBefore = ViewManager::ref()->activeDocument()->isModified(); + else + m_isModifiedBefore = true; + m_isModifiedAfter = true; + + //A NodeModifsSet instance is created before the changes are made => + //Recording the cursor position + if(ViewManager::ref()->activeView()->hadLastFocus() == QuantaView::VPLFocus) + { + m_selectionBefore->fillWithVPLCursorSelection(); + } + else + { + } +} + +NodeModifsSet::~NodeModifsSet() +{ + m_nodeModifList.setAutoDelete(true); + m_nodeModifList.clear(); + delete m_selectionBefore; + delete m_selectionAfter; +} + +undoRedo::undoRedo(Document *doc) + :documentIterator(m_undoList), + sourceIterator(m_undoList), + kafkaIterator(m_undoList), m_doc(doc) +{ +#ifdef LIGHT_DEBUG + kdDebug(25001)<< "undoRedo::undoRedo() - *doc" << endl; +#endif + //TODO:add it to the config + m_listLimit = 50; + m_merging = false; + m_mergeNext = false; + m_loggingEnabled = false; +} + +undoRedo::~undoRedo() +{ +#ifdef LIGHT_DEBUG + kdDebug(25001)<< "undoRedo::~undoRedo()" << endl; +#endif +} + +void undoRedo::addNewModifsSet(NodeModifsSet *modifs, int modifLocation, NodeSelection *selection, bool encodeText) +{ +#ifdef LIGHT_DEBUG + kdDebug(25001)<< "undoRedo::addNewModifsSet() - NodeModifsSet type: " << modifLocation << endl; +#endif + + QValueList<NodeModif>::iterator it2; + NodeModifsSet *NMSet; + QValueList<int> loc; + int curFocus, foo, foo2; + int diff, diff2; + NodeSelectionInd *nodeSelection; + Node *node; + bool goUp; + + //If modifs is empty, stop here + if(!modifs || modifs->nodeModifList().isEmpty()) + { + delete modifs; + return; + } + + KConfig* config = kapp->config(); + config->setGroup("Kate Document Defaults"); + int indentationWidth = config->readNumEntry("Indentation Width", 4); + + //Once the changes have been made, we will generate the "clean" string for Text Nodes only, and + //we will add the empty indentation Nodes. + modifs->startOfIndentation(); + node = baseNode; + while(node) + { + if(!node->tag->cleanStrBuilt() && + (node->tag->type == Tag::Text || (node->tag->type == Tag::Empty && !node->tag->tagStr().isEmpty()))) + { + if(!node->insideSpecial) + { + node->tag->setStr(KafkaDocument::ref()->generateCodeFromNode(node, 0, 0, foo, foo2, encodeText)); + node->tag->setCleanStrBuilt(true); + } + } + if(!node->tag->indentationDone() && !node->insideSpecial) + { + kafkaCommon::fitIndentationNodes(kafkaCommon::getPrevNodeNE(node), node, modifs); + goUp = false; + kafkaCommon::fitIndentationNodes(node, kafkaCommon::getNextNodeNE(node, goUp), modifs); + kafkaCommon::applyIndentation(node, indentationWidth, 0, modifs, qConfig.inlineNodeIndentation); + } + node = node->nextSibling(); + } + + //Set the modification flag + if(ViewManager::ref()->activeView()->hadLastFocus() == QuantaView::VPLFocus) + m_doc->setModified(true); + + //Store the cursor position after the changes. + nodeSelection = modifs->selectionAfter(); + if(ViewManager::ref()->activeView()->hadLastFocus() == QuantaView::VPLFocus) + nodeSelection->fillWithVPLCursorSelection(); + + //If the previous NodeModifsSet contains some text insertion/deletion and if + //the current one is doing the same thing, compress the two NodeModifsSet : delete modifs + if(modifs->nodeModifList().count() >= 1 && modifs->indentationStartOffset() == 1 && + modifs->nodeModifList().at(0)->type() == NodeModif::NodeModified) + { + QPtrListIterator<NodeModifsSet> it(m_undoList); + it = documentIterator; + if((*it) && (*it)->nodeModifList().count() >= 1 && (*it)->indentationStartOffset() == 1 && + (*it)->nodeModifList().at(0)->type() == NodeModif::NodeModified && + (*it)->isModifiedAfter()) + { + node = kafkaCommon::getNodeFromLocation(modifs->nodeModifList().at(0)->location()); + diff = modifs->nodeModifList().at(0)->tag()->tagStr().length() - (*it)->nodeModifList().at(0)->tag()->tagStr().length(); + diff2 = node->tag->tagStr().length() - modifs->nodeModifList().at(0)->tag()->tagStr().length(); + + if(*((*it)->selectionAfter()) == *(modifs->selectionBefore()) && + ((diff >= 0 && diff2 >= 0) || (diff <= 0 && diff2 <= 0))) + { + //Ok, we are skipping this one. Update the selection coordinates of (*it) + (*it)->setSelectionAfter(modifs->selectionAfter()); + modifs->setSelectionAfter(0L); + delete modifs; + + //Move backward the iterator so that it will refresh next time + curFocus = ViewManager::ref()->activeView()->hadLastFocus(); + if((modifLocation == undoRedo::SourceModif || + (modifLocation == undoRedo::NodeTreeModif && curFocus == QuantaView::SourceFocus)) && + kafkaIterator.atLast()) + --kafkaIterator; + else if((modifLocation == undoRedo::KafkaModif || + (modifLocation == undoRedo::NodeTreeModif && curFocus == QuantaView::VPLFocus)) && + sourceIterator.atLast()) + --sourceIterator; + if(modifLocation == undoRedo::NodeTreeModif) + { + if(curFocus == QuantaView::SourceFocus) + reloadQuantaEditor(); + else + reloadKafkaEditor(false, selection); + } + return; + } + } + } + + //Store the NodeModifsSet + m_undoList.append(modifs); +#ifdef HEAVY_DEBUG + debugOutput(); +#endif + while(m_undoList.count() > (unsigned)m_listLimit) + { + // FIXME FIXME FIXME FIXME FIXME FIXME FIXME FIXME FIXME FIXME FIXME FIXME + //FIXME: This is to prevent the list to be infinite, change when undoRedo is finished!! FIXME + if(!kafkaIterator.current() || kafkaIterator.atFirst()) + { + kafkaIterator = sourceIterator; + --kafkaIterator; + } + else if(sourceIterator.current() || sourceIterator.atFirst()) + { + sourceIterator = kafkaIterator; + --sourceIterator; + } + //END FIXME + NMSet = m_undoList.getFirst(); + m_undoList.remove(NMSet); + delete NMSet; + } + if(modifLocation == undoRedo::SourceModif) + { + sourceIterator.toLast(); + //The node Tree is ALWAYS in sync + documentIterator.toLast(); + } + else if(modifLocation == undoRedo::KafkaModif) + { + kafkaIterator.toLast(); + //The node Tree is ALWAYS in sync + documentIterator.toLast(); + } + else if(modifLocation == undoRedo::NodeTreeModif) + { + documentIterator.toLast(); + curFocus = ViewManager::ref()->activeView()->hadLastFocus(); + if(curFocus == QuantaView::SourceFocus) + reloadQuantaEditor(); + else + reloadKafkaEditor(false, selection); + } +#ifdef HEAVY_DEBUG + kdDebug(25001)<<"-------------------------------------------------------------------------------"<< endl; + debugOutput(); +#endif + /**}*/ + + /** A lot more to do: + * -NodeModifs fusionning in case of typing text multiple times, and also for some similar + * actions like NodeCreated and then just after NodeModified. + * -Flags to prevent fusionning in case of copy/paste, and to provoke NodeModifs separation + * in case of too heavy NodeModified (e.g. typing 100 lines of text shouldn't be undo'ed in one time) + */ +#ifdef HEAVY_DEBUG + //debugOutput(); +#endif +} + +void undoRedo::turnOn(bool on) +{ + if(!m_loggingEnabled && on) + { + //TEMPORARY : Delete all the undo/Redo stack, we only want to keep VPL NodeModifs + m_undoList.setAutoDelete(true); + m_undoList.clear(); + m_undoList.setAutoDelete(false); + } + + m_loggingEnabled = on; +} + +bool undoRedo::undo() +{ + bool success = true; + +#ifdef LIGHT_DEBUG + kdDebug(25001)<< "undoRedo::undo()"<< endl; + QTime t; + t.start(); +#endif + if(documentIterator.isEmpty() || !(*documentIterator)) + return false; + QPtrListIterator<NodeModif> it((*documentIterator)->nodeModifList()); + it.toLast(); + + while(*it) + { + //Undo the changes + if(!undoNodeModif(*it)) + { + //one undo has failed, trying to recover considering that the undo has done nothing. + kdDebug(25001)<< "Undo failed, trying to recover." << endl; + ++it; + while((*it) && success) + { + success = redoNodeModif(*it); +#ifdef LIGHT_DEBUG + kdDebug(25001) << "NodeModif type :" << (*it)->type() <<" redoed!" << endl; +#endif + ++it; + } + return false; + } + +#ifdef LIGHT_DEBUG + kdDebug(25001) << "NodeModif type :" << (*it)->type() <<" undoed!" << endl; +#endif + --it; + } + + //We need to update the internal pointer of baseNode in the parser. FIXME:why? + parser->setRootNode(baseNode); + + //Update the modified flag + m_doc->setModified((*documentIterator)->isModifiedBefore()); + + QPtrListIterator<NodeModifsSet> currentIt(documentIterator); + --documentIterator; + + //TEMP: Reload the kafka editor TODO: update only the modified Nodes + if(ViewManager::ref()->activeView()->hadLastFocus() == QuantaView::VPLFocus) + reloadKafkaEditor(); + + //Restore the cursor at the old coordinates + if(ViewManager::ref()->activeView()->hadLastFocus() == QuantaView::VPLFocus) + KafkaDocument::ref()->setCursorAndSelection((*currentIt)->selectionBefore()); + + +#ifdef LIGHT_DEBUG + kdDebug(25001) << "undoRedo::undo() : " << t.elapsed() << " ms" << endl; +#endif + +#ifdef HEAVY_DEBUG + //debugOutput(); +#endif + return !(*documentIterator); +} + +bool undoRedo::redo() +{ + bool success = true; + +#ifdef LIGHT_DEBUG + QTime t; + t.start(); + kdDebug(25001)<< "undoRedo::redo()" << endl; +#endif + if(documentIterator.isEmpty() || documentIterator.atLast()) + return false; + + if(!(*documentIterator)) + documentIterator.toFirst(); + else + ++documentIterator; + + QPtrListIterator<NodeModif> it((*documentIterator)->nodeModifList()); + it.toFirst(); + + while(*it) + { + //Redo the changes + if(!redoNodeModif(*it)) + { + //one redo has failed, trying to recover considering that the redo has done nothing. + kdDebug(25001)<< "Redo failed, trying to recover." << endl; + --it; + while((*it) && success) + { + success = undoNodeModif(*it); +#ifdef LIGHT_DEBUG + kdDebug(25001) << "NodeModif type :" << (*it)->type() <<" undoed!" << endl; +#endif + --it; + } + --documentIterator; + return false; + } +#ifdef LIGHT_DEBUG + kdDebug(25001) << "NodeModif type :" << (*it)->type() <<" redoed!" << endl; +#endif + ++it; + } + //We need to update the internal pointer of baseNode in the parser. FIXME: why? + parser->setRootNode(baseNode); + + //Update the modified flag + m_doc->setModified((*documentIterator)->isModifiedAfter()); + + //TEMP: Reload the kafka editor TODO: update only the modified Nodes + if(ViewManager::ref()->activeView()->hadLastFocus() == QuantaView::VPLFocus) + reloadKafkaEditor(); + + //Restore the cursor at the old coordinates + if(ViewManager::ref()->activeView()->hadLastFocus() == QuantaView::VPLFocus) + KafkaDocument::ref()->setCursorAndSelection((*documentIterator)->selectionAfter()); + +#ifdef LIGHT_DEBUG + kdDebug(25001) << "undoRedo::redo() : " << t.elapsed() << " ms" << endl; +#endif + +#ifdef HEAVY_DEBUG + //debugOutput(); +#endif + return !documentIterator.atLast(); +} + +bool undoRedo::undoNodeModif(NodeModif *nodeModif) +{ + Node *node, *newNode; + QValueList<int> ref; + Tag *tag; + bool b; + + //Note : for NodeModif::NodeMoved && NodeModif::NodeAndChildsMoved, we go + //through the processing of NodeModif::NodeAdded && NodeModif::NodeRemoved + + if(nodeModif->type() == NodeModif::NodeTreeAdded) + { + //Set baseNode to 0L + nodeModif->setNode(baseNode); + baseNode = 0L; + } + if(nodeModif->type() == NodeModif::NodeAndChildsAdded || + nodeModif->type() == NodeModif::NodeAdded || + nodeModif->type() == NodeModif::NodeMoved || + nodeModif->type() == NodeModif::NodeAndChildsMoved) + { + // Removing the node + if(nodeModif->type() == NodeModif::NodeAndChildsAdded || + nodeModif->type() == NodeModif::NodeAdded) + node = kafkaCommon::getNodeFromLocation(nodeModif->location()); + else + node = kafkaCommon::getNodeFromLocation(nodeModif->finalLocation()); + + if(!node) + { + kdDebug(25001)<< "undoRedo::undoNodeModif() - NodeModif::NodeAdded/Moved - ERROR1" << endl; + return false; + } + kafkaCommon::extractNode(node, 0L, true,false); + nodeModif->setNode(node); + } + if(nodeModif->type() == NodeModif::NodeModified) + { + // Simply replacing the tag(node->tag) of the node by the old tag. + node = kafkaCommon::getNodeFromLocation(nodeModif->location()); + if(!node) + { + kdDebug(25001)<< "undoRedo::undoNodeModif() - NodeModif::NodeModified - ERROR3" << endl; + return false; + } + tag = nodeModif->tag(); + nodeModif->setTag(node->tag); + b = false; + node->tag = tag; + } + if(nodeModif->type() == NodeModif::NodeRemoved || + nodeModif->type() == NodeModif::NodeAndChildsRemoved || + nodeModif->type() == NodeModif::NodeMoved || + nodeModif->type() == NodeModif::NodeAndChildsMoved) + { + // Adding the node + newNode = nodeModif->node(); + nodeModif->setNode(0L); + node = kafkaCommon::getNodeFromLocation(nodeModif->location()); + if(!node) + { + // No node at this location, getting the parent Node and appending newNode after + // the last child of the parent. + ref = nodeModif->location(); + QValueList<int> loc(ref); + loc.remove(loc.fromLast()); + if(loc.empty()) + { + // No parent, adding it on top of the tree. + kafkaCommon::insertNode(newNode, 0L, 0L, 0L, false); + } + else + { + node = kafkaCommon::getNodeFromLocation(loc); + if(!node) + { + kdDebug(25001)<< "undoRedo::undoNodeModif() - NodeModif::NodeRemoved - ERROR4" << endl; + return false; + } + kafkaCommon::insertNode(newNode, node, 0L, 0L, false); + } + } + else + { + // A node is already here. Moving it to the right and adding newNode here. + kafkaCommon::insertNode(newNode, node->parent, node, 0L, false); + } + } + if(nodeModif->type() == NodeModif::NodeTreeRemoved) + { + //Adding the tree. + baseNode = nodeModif->node(); + nodeModif->setNode(0L); + } + + return true; +} + +bool undoRedo::redoNodeModif(NodeModif *nodeModif) +{ + bool success; + QValueList<int> tmp; + + //To do the opposite action of undoNodeModif(), we simply have + //to change the type of nodeModif + + if(nodeModif->type() == NodeModif::NodeTreeAdded) + nodeModif->setType(NodeModif::NodeTreeRemoved); + else if(nodeModif->type() == NodeModif::NodeAndChildsAdded) + nodeModif->setType(NodeModif::NodeAndChildsRemoved); + else if(nodeModif->type() == NodeModif::NodeAdded) + nodeModif->setType(NodeModif::NodeRemoved); + else if(nodeModif->type() == NodeModif::NodeRemoved) + nodeModif->setType(NodeModif::NodeAdded); + else if(nodeModif->type() == NodeModif::NodeAndChildsRemoved) + nodeModif->setType(NodeModif::NodeAndChildsAdded); + else if(nodeModif->type() == NodeModif::NodeTreeRemoved) + nodeModif->setType(NodeModif::NodeTreeAdded); + else if(nodeModif->type() == NodeModif::NodeMoved || nodeModif->type() == NodeModif::NodeAndChildsMoved) + { + tmp = nodeModif->location(); + nodeModif->setLocation(nodeModif->finalLocation()); + nodeModif->setFinalLocation(tmp); + } + + success = undoNodeModif(nodeModif); + + if(nodeModif->type() == NodeModif::NodeTreeRemoved) + nodeModif->setType(NodeModif::NodeTreeAdded); + else if(nodeModif->type() == NodeModif::NodeAndChildsRemoved) + nodeModif->setType(NodeModif::NodeAndChildsAdded); + else if(nodeModif->type() == NodeModif::NodeRemoved) + nodeModif->setType(NodeModif::NodeAdded); + else if(nodeModif->type() == NodeModif::NodeAdded) + nodeModif->setType(NodeModif::NodeRemoved); + else if(nodeModif->type() == NodeModif::NodeAndChildsAdded) + nodeModif->setType(NodeModif::NodeAndChildsRemoved); + else if(nodeModif->type() == NodeModif::NodeTreeAdded) + nodeModif->setType(NodeModif::NodeTreeRemoved); + else if(nodeModif->type() == NodeModif::NodeMoved || nodeModif->type() == NodeModif::NodeAndChildsMoved) + { + tmp = nodeModif->location(); + nodeModif->setLocation(nodeModif->finalLocation()); + nodeModif->setFinalLocation(tmp); + } + + return success; +} + +bool undoRedo::undoNodeModifInKafka(NodeModif */**_nodeModif*/) +{ + /**Node *_node, *n; + Tag *_tag; + DOM::Node domNode, domNode2, dn, dm; + bool goUp; + KafkaDocument *kafkaInterface = quantaApp->view()->kafkaInterface(); + KafkaWidget *kafkaPart = quantaApp->view()->kafkaInterface()->getKafkaWidget(); + + if(_nodeModif.type == undoRedo::NodeTreeAdded) + { + //clear the kafkaPart + kafkaInterface->disconnectAllDomNodes(); + while(kafkaPart->document().hasChildNodes()) + { + //try{ + kafkaPart->document().removeChild(kafkaPart->document().firstChild()); + //} catch(DOM::DOMException e) {kafkaSyncError();} + } + //reload the minimum tree + domNode = kafkaPart->document().createElement("HTML"); + kafkaPart->document().appendChild(domNode); + _node = new Node(0L); + _tag = new Tag(); + _tag->name = "HTML"; + _node->tag = _tag; + kafkaInterface->connectDomNodeToQuantaNode(kafkaPart->document().firstChild(), _node); + kafkaInterface->html = kafkaPart->document().firstChild(); + domNode = kafkaPart->document().createElement("HEAD"); + kafkaPart->document().firstChild().appendChild(domNode); + _node = new Node(0L); + _tag = new Tag(); + _tag->name = "HEAD"; + _node->tag = _tag; + kafkaInterface->connectDomNodeToQuantaNode(kafkaPart->document().firstChild().firstChild(), _node); + kafkaInterface->head = kafkaPart->document().firstChild().firstChild(); + domNode = kafkaPart->document().createElement("BODY"); + kafkaPart->document().firstChild().appendChild(domNode); + _node = new Node(0L); + _tag = new Tag(); + _tag->name = "BODY"; + _node->tag = _tag; + kafkaInterface->connectDomNodeToQuantaNode(kafkaPart->document().firstChild().lastChild(), _node); + kafkaInterface->body = kafkaPart->document().firstChild().lastChild(); + } + else if(_nodeModif.type == undoRedo::NodeAndChildsAdded || + _nodeModif.type == undoRedo::NodeAdded) + { + //removing the Kakfa node and moving others nodes. + _node = kafkaCommon::getNodeFromLocation(_nodeModif.location); + if(!_node) + { + kdDebug(25001)<< "undoRedo::undoNodeModifInKafka() - ERROR1" << endl; + return false; + } + if(_node->_rootNode.isNull()) + return true;//no kafka node here, due to an invalid pos. + domNode = _node->_rootNode; + domNode2 = _node->_leafNode; + kafkaInterface->disconnectDomNodeFromQuantaNode(domNode); + if(_node->tag->type == Tag::XmlTag || _node->tag->type == Tag::Text) + { + if(_nodeModif.type == undoRedo::NodeAdded && _node->child) + { + n = _node->child; + while(n) + { + if(!n->_rootNode.isNull()) + { + //try + //{ + dn = n->_rootNode.parentNode().removeChild(n->_rootNode); + //} catch(DOM::DOMException e) {kafkaSyncError();} + //try{ + domNode.parentNode().insertBefore(dn, domNode); + //} catch(DOM::DOMException e) {} + } + else if(n->tag->type == Tag::XmlTag || n->tag->type == Tag::Text) + kafkaInterface->buildKafkaNodeFromNode(n, true); + n = n->next; + } + if(domNode.hasChildNodes() && domNode != domNode2) + { + //HTML Specific to handle one specific case!! + kafkaInterface->disconnectDomNodeFromQuantaNode(domNode.firstChild()); + //try{ + domNode.removeChild(domNode.firstChild()); + //} catch(DOM::DOMException e) {kafkaSyncError();} + } + } + else if(domNode.hasChildNodes()) + { + dm = domNode.firstChild(); + goUp = false; + while(!dm.isNull()) + { + kafkaInterface->disconnectDomNodeFromQuantaNode(dm); + dm = kafkaCommon::getNextDomNode(dm, goUp, false, domNode); + } + } + //try{ + domNode.parentNode().removeChild(domNode); + //} catch(DOM::DOMException e) {kafkaSyncError();} + } + else if(_node->tag->type == Tag::XmlTagEnd && _node->closesPrevious && + !domNode.nextSibling().isNull()) + { + n = _node->prev; + if(!n) + { + kdDebug(25001)<< "undoRedo::undoNodeModifInKafka() - ERROR2" << endl; + return false; + } + domNode2 = n->_leafNode; + if(domNode2.isNull()) + return true; + if(n->child) + { + while(n->child) + { + n = n->child; + while(n->next) + n = n->next; + } + if(n->parent->_leafNode.isNull()) + { + dm = domNode.nextSibling(); + goUp = false; + while(!dm.isNull()) + { + kafkaInterface->disconnectDomNodeFromQuantaNode(dm); + //try{ + dm.parentNode().removeChild(dm); + //} catch(DOM::DOMException e) {kafkaSyncError();} + dm = kafkaCommon::getNextDomNode(dm, goUp, false, domNode.parentNode()); + } + } + else + { + domNode2 = n->parent->_leafNode; + while(!domNode.nextSibling().isNull()) + { + //try{ + dn = domNode.parentNode().removeChild(domNode.nextSibling()); + //} catch(DOM::DOMException e) {kafkaSyncError();} + //try{ + domNode2.appendChild(dn); + //} catch(DOM::DOMException e) {} + } + } + } + else + { + while(!domNode.nextSibling().isNull()) + { + //try{ + dn = domNode.parentNode().removeChild(domNode.nextSibling()); + //} catch(DOM::DOMException e) {kafkaSyncError();} + //try{ + domNode2.appendChild(dn); + //} catch(DOM::DOMException e) {} + } + } + } + } + else if(_nodeModif.type == undoRedo::NodeModified) + { + //reload the kafka Node + _node = kafkaCommon::getNodeFromLocation(_nodeModif.location); + if(!_node) + { + kdDebug(25001)<< "undoRedo::undoNodeModifInKafka() - ERROR4" << endl; + return false; + } + if(_node->_rootNode.isNull()) + return true;//no kafka node here, due to an invalid pos. + domNode = _node->_rootNode; + //try{ + domNode.parentNode().removeChild(domNode); + //} catch(DOM::DOMException e) {kafkaSyncError();} + kafkaInterface->disconnectDomNodeFromQuantaNode(domNode); + kafkaInterface->buildKafkaNodeFromNode(_node); + } + else if(_nodeModif.type == undoRedo::NodeRemoved || + _nodeModif.type == undoRedo::NodeAndChildsRemoved) + { + //adding a kafka Node and moving the others. + _node = kafkaCommon::getNodeFromLocation(_nodeModif.location); + if(!_node) + { + kdDebug(25001)<< "undoRedo::undoNodeModifInKafka() - ERROR1" << endl; + return false; + } + if(_node->tag->type == Tag::XmlTag || _node->tag->type == Tag::Text) + { + kafkaInterface->buildKafkaNodeFromNode(_node, true); + domNode = _node->_leafNode; + if(!domNode.isNull() && _node->child) + { + n = _node->child; + while(n) + { + if(!n->_rootNode.isNull()) + { + //try{ + dn = n->_rootNode.parentNode().removeChild(n->_rootNode); + //} catch(DOM::DOMException e) {kafkaSyncError();} + //try{ + domNode.appendChild(dn); + //} catch(DOM::DOMException e) {} + } + else if(n->tag->type == Tag::XmlTag || n->tag->type == Tag::Text) + kafkaInterface->buildKafkaNodeFromNode(n, true); + n = n->next; + } + } + } + else if(_node->tag->type == Tag::XmlTagEnd && _node->closesPrevious && _node->next) + { + n = _node->next; + while(n) + { + if(!n->_rootNode.isNull()) + { + //try{ + dn = n->_rootNode.parentNode().removeChild(n->_rootNode); + //} catch(DOM::DOMException e) {kafkaSyncError();} + //try{ + domNode.parentNode().appendChild(dn); + //} catch(DOM::DOMException e) {} + } + else if(n->tag->type == Tag::XmlTag || n->tag->type == Tag::Text) + kafkaInterface->buildKafkaNodeFromNode(n, true); + n = n->next; + } + } + } + else if(_nodeModif.type == undoRedo::NodeTreeRemoved) + { + //fill the kafka tree. + goUp = false; + _node = baseNode; + while(_node) + { + if(!goUp) + kafkaInterface->buildKafkaNodeFromNode(_node); + _node = kafkaCommon::getNextNode(_node, goUp); + } + } + */ + return true; +} + +void undoRedo::reloadKafkaEditor(bool force, NodeSelection *selection) +{ +#ifdef LIGHT_DEBUG + kdDebug(25001)<< "undoRedo::reloadKafkaEditor()" << endl; +#endif + + if(kafkaIterator == documentIterator && !force) + { + syncKafkaCursorAndSelection(selection); + return; + } + + kafkaIterator = documentIterator; + KafkaDocument *kafkaInterface = KafkaDocument::ref(); + + kafkaInterface->reloadDocument(); + + syncKafkaCursorAndSelection(selection); +} + +void undoRedo::reloadQuantaEditor(bool force, bool syncQuantaCursor, bool encodeText) +{ + QString text, allText; + Node *node = baseNode, *child; + int bCol, bLine, eCol, eLine, bCol2, bLine2, bCol3, bLine3, eCol3, eLine3, i; + KafkaDocument *kafkaInterface = KafkaDocument::ref(); + bool updateClosing, goUp, isModified; + +#ifdef LIGHT_DEBUG + kdDebug(25001)<< "undoRedo::reloadQuantaEditor()" << endl; +#endif + + if(documentIterator == sourceIterator && !force) + { + if(syncQuantaCursor) + syncQuantaCursorAndSelection(); + return; + } + + if(m_doc->editIfExt) + m_doc->editIfExt->editBegin(); + sourceIterator = documentIterator; + + //save some values which must not be affected by / affect the reload + isModified = m_doc->isModified(); + updateClosing = qConfig.updateClosingTags; + m_doc->activateParser(false); + m_doc->activateRepaintView(false); + qConfig.updateClosingTags = false; + + //First build the tag string which needs to be updated, and add the necessary + //empty Nodes for the indentation. + while(node) + { + if(!node->tag->cleanStrBuilt()) + { + if(!node->insideSpecial) + { + node->tag->setStr(kafkaInterface->generateCodeFromNode(node, 0, 0, eLine, eCol, encodeText)); + } + else + { + //Script formatting + } + node->tag->setCleanStrBuilt(true); + } + //_node->tag->beginPos(bLine, bCol); + //i can't stop redraw events of Kate! + //m_doc->editIf->insertText(bLine, bCol, _node->tag->tagStr()); + //allText += _node->tag->tagStr(); + node->tag->endPos(bLine, bCol); + bCol++; + node = node->nextSibling(); + } + + //Then, we gather all the tag string and put it into kate, and we set the tag positions. + node = baseNode; + goUp = false; + bCol = 0; + bLine = 0; + while(node) + { + //kdDebug(25001)<< "CurNode : " << _node->tag->name << " - " << _node->tag->tagStr() << endl; + if(node->parent) + { + node->parent->tag->beginPos(bLine3, bCol3); + node->parent->tag->endPos(eLine3, eCol3); + } + node->tag->beginPos(bLine2, bCol2); + + //if we are in a Script inside a tag e.g. <a href="<? PHP stuff here ?>">, skip it + if(node->tag->type == Tag::ScriptTag && node->parent && + QuantaCommon::isBetween(bLine2, bCol2, bLine3, bCol3, eLine3,eCol3) == 0) + { + goUp = true; + + //if we found the closing script tag, skip it too + if(node->next && node->next->tag->type == Tag::XmlTagEnd) + node = node->next; + } + else + { + allText += node->tag->tagStr(); + + //If a child is a Script inside this Tag e.g. <a href="<? PHP stuff here ?>">, make + //its position fits inside the parent + node->tag->beginPos(bLine3, bCol3); + node->tag->endPos(eLine3, eCol3); + child = node->firstChild(); + while(child) + { + child->tag->beginPos(bLine2, bCol2); + if(child->tag->type == Tag::ScriptTag && + QuantaCommon::isBetween(bLine2, bCol2, bLine3, bCol3, eLine3,eCol3) == 0) + { + child->tag->setTagPosition(bLine, bCol + 1, bLine, bCol + 1); + } + child = child->next; + } + + //Update the node's positions + node->tag->setTagPosition(bLine, bCol, -1, -1); + for(i = 0; i < node->tag->attrCount(); i++) + { + bCol3 = node->tag->getAttribute(i).nameLine; + bLine3 = node->tag->getAttribute(i).nameCol; + eCol3 = node->tag->getAttribute(i).valueLine; + eLine3 = node->tag->getAttribute(i).valueCol; + + //FIXME: This is OK only when it has just been rebuild. + node->tag->setAttributePosition(i, bLine3 + bLine, bCol3 + bCol, eLine3 + bLine, + eCol3 + bCol); + } + kafkaCommon::getEndPosition(node->tag->tagStr(), bLine, bCol, eLine, eCol); + node->tag->setTagPosition(bLine, bCol, eLine, eCol); + + bCol = eCol + 1; + bLine = eLine; + } + node = kafkaCommon::getNextNode(node, goUp); + } + + //temp + m_doc->editIf->removeText(0, 0, m_doc->editIf->numLines() - 1, + m_doc->editIf->lineLength(m_doc->editIf->numLines() - 1)); + m_doc->editIf->insertText(0, 0, allText); + //m_doc->editIf->setText(allText); + if(m_doc->editIfExt) + m_doc->editIfExt->editEnd(); + if(syncQuantaCursor) + syncQuantaCursorAndSelection(); + + m_doc->setModified(isModified); + qConfig.updateClosingTags = updateClosing; + m_doc->activateRepaintView(true); + m_doc->activateParser(true); + +} + +void undoRedo::codeFormatting() +{ + Node *node = baseNode; + + while (node) + { + node->tag->setCleanStrBuilt(false); + node->tag->setIndentationDone(false); + node = node->nextSibling(); + } + reloadQuantaEditor(true, false, false); +} + +bool undoRedo::redoNodeModifInKafka(NodeModif */**_nodeModif*/) +{ + /**bool success; + if(_nodeModif.type == undoRedo::NodeTreeAdded) + _nodeModif.type = undoRedo::NodeTreeRemoved; + else if(_nodeModif.type == undoRedo::NodeAndChildsAdded) + _nodeModif.type = undoRedo::NodeAndChildsRemoved; + else if(_nodeModif.type == undoRedo::NodeAdded) + _nodeModif.type = undoRedo::NodeRemoved; + else if(_nodeModif.type == undoRedo::NodeRemoved) + _nodeModif.type = undoRedo::NodeAdded; + else if(_nodeModif.type == undoRedo::NodeAndChildsRemoved) + _nodeModif.type = undoRedo::NodeAndChildsAdded; + else if(_nodeModif.type == undoRedo::NodeTreeRemoved) + _nodeModif.type = undoRedo::NodeTreeAdded; + + success = undoNodeModifInKafka(_nodeModif); + + if(_nodeModif.type == undoRedo::NodeTreeRemoved) + _nodeModif.type = undoRedo::NodeTreeAdded; + else if(_nodeModif.type == undoRedo::NodeAndChildsRemoved) + _nodeModif.type = undoRedo::NodeAndChildsAdded; + else if(_nodeModif.type == undoRedo::NodeRemoved) + _nodeModif.type = undoRedo::NodeAdded; + else if(_nodeModif.type == undoRedo::NodeAdded) + _nodeModif.type = undoRedo::NodeRemoved; + else if(_nodeModif.type == undoRedo::NodeAndChildsAdded) + _nodeModif.type = undoRedo::NodeAndChildsRemoved; + else if(_nodeModif.type == undoRedo::NodeTreeAdded) + _nodeModif.type = undoRedo::NodeTreeRemoved; + return success;*/ + return true; +} + +bool undoRedo::syncKafkaView() +{ +#ifdef LIGHT_DEBUG + kdDebug(25001)<< "undoRedo::syncKafkaView()" << endl; +#endif + /** QValueList<NodeModifsSet>::iterator it; + QValueList<NodeModif>::iterator it2; + bool undoKafkaView = true; + + if(kafkaIterator == sourceIterator) + return true; + it = kafkaIterator; + while(it != end()) + { + if(it == sourceIterator) + { + undoKafkaView = false; + break; + } + ++it; + } + + it = sourceIterator; + if(!undoKafkaView) + { + //changes have been made to quanta, syncing the kafka view + //First undo all the node modifs made after the last update + //needed to have the right context to update the kafka tree. + while(it != kafkaIterator) + { + it2 = (*it).NodeModifList.fromLast(); + while(it2 != (*it).NodeModifList.end()) + { + if(!undoNodeModif((*it2), false)) + { + kdDebug(25001)<< "undoRedo::syncKafkaView() - ERROR 1" << endl; + return false; + } + #ifdef LIGHT_DEBUG + kdDebug(25001)<< "undoRedo::syncKafkaView() - Nodes without text undoed!" << endl; + #endif + if(it2 == (*it).NodeModifList.begin()) + break; + it2--; + } + it--; + } + //then for each NodeModif, it is redoed, and the kafka Nodes are build/deleted/modified + while(kafkaIterator != sourceIterator) + { + kafkaIterator++; + for (it2 = (*kafkaIterator).NodeModifList.begin(); it2 != (*kafkaIterator).NodeModifList.end(); ++it2) + { + if((*it2).type == undoRedo::NodeTreeAdded || (*it2).type == undoRedo::NodeAndChildsAdded || + (*it2).type == undoRedo::NodeAdded || (*it2).type == undoRedo::NodeModified) + { + if(!redoNodeModif((*it2), false)) + { + kdDebug(25001)<< "undoRedo::syncKafkaView() - ERROR 2" << endl; + return false; + } + if(!redoNodeModifInKafka(*it2)) + { + kdDebug(25001)<< "undoRedo::syncKafkaView() - ERROR 3" << endl; + return false; + } + } + else + { + if(!redoNodeModifInKafka(*it2)) + { + kdDebug(25001)<< "undoRedo::syncKafkaView() - ERROR 4" << endl; + return false; + } + if(!redoNodeModif((*it2), false)) + { + kdDebug(25001)<< "undoRedo::syncKafkaView() - ERROR 5" << endl; + return false; + } + } + #ifdef LIGHT_DEBUG + kdDebug(25001)<< "undoRedo::syncKafkaView() - Nodes without text, and kafka Nodes redoed!" << endl; + #endif + } + } + } + else + { + //undo operations have been done in the quanta view + //First redo all the Node modifs made after the last update. + //This might be called when an user action occurs after undoing : we must sync before the + //deletion of part of the undo stack. + while(it != kafkaIterator) + { + ++it; + for(it2 = (*it).NodeModifList.begin(); it2 != (*it).NodeModifList.end(); ++it2) + { + if(!redoNodeModif((*it2), false)) + { + kdDebug(25001)<< "undoRedo::syncKafkaView() - ERROR 6" << endl; + return false; + } + #ifdef LIGHT_DEBUG + kdDebug(25001)<< "undoRedo::syncKafkaView() - Nodes without text redoed!" << endl; + #endif + } + } + //then for each NodeModif, Nodes are undoed, and the kafka Nodes are build/deleted/modified + while(kafkaIterator != sourceIterator) + { + it2 = (*kafkaIterator).NodeModifList.fromLast(); + while(it2 != (*kafkaIterator).NodeModifList.end()) + { + if((*it2).type == undoRedo::NodeTreeAdded || (*it2).type == undoRedo::NodeAndChildsAdded || + (*it2).type == undoRedo::NodeAdded || (*it2).type == undoRedo::NodeModified) + { + if(!undoNodeModifInKafka(*it2)) + { + kdDebug(25001)<< "undoRedo::syncKafkaView() - ERROR 8" << endl; + return false; + } + if(!undoNodeModif((*it2), false)) + { + kdDebug(25001)<< "undoRedo::syncKafkaView() - ERROR 7" << endl; + return false; + } + } + else + { + if(!undoNodeModif((*it2), false)) + { + kdDebug(25001)<< "undoRedo::syncKafkaView() - ERROR 10" << endl; + return false; + } + if(!undoNodeModifInKafka(*it2)) + { + kdDebug(25001)<< "undoRedo::syncKafkaView() - ERROR 9" << endl; + return false; + } + } + #ifdef LIGHT_DEBUG + kdDebug(25001)<< "undoRedo::syncKafkaView() - Nodes without text, and kafka Nodes undoed!" << endl; + #endif + if(it2 == (*kafkaIterator).NodeModifList.begin()) + break; + it2--; + } + kafkaIterator--; + } + } + kafkaIterator = sourceIterator;*/ + return true; +} + +bool undoRedo::syncQuantaView() +{ +#ifdef LIGHT_DEBUG + kdDebug(25001)<< "undoRedo::syncQuantaView()" << endl; +#endif + /**QValueList<NodeModifsSet>::iterator it; + QValueList<NodeModif>::iterator it2; + bool undoQuantaView = true; + + if(kafkaIterator == sourceIterator) + return true; + it = sourceIterator; + while(it != end()) + { + if(it == kafkaIterator) + { + undoQuantaView = false; + break; + } + ++it; + } + + it = kafkaIterator; + if(!undoQuantaView) + { + //changes have been made to kafka, syncing the quanta view + //First undo all the node modifs made after the last update + //needed to have the right context to update the quanta tree. + while(it != sourceIterator) + { + it2 = (*it).NodeModifList.fromLast(); + while(it2 != (*it).NodeModifList.end()) + { + if(!undoNodeModif((*it2), false)) + { + kdDebug(25001)<< "undoRedo::syncQuantaView() - ERROR 1" << endl; + return false; + } + #ifdef LIGHT_DEBUG + kdDebug(25001)<< "undoRedo::syncQuantaView() - Nodes without text undoed!" << endl; + #endif + if(it2 == (*it).NodeModifList.begin()) + break; + it2--; + } + it--; + } + //then for each NodeModif, Nodes are redoed, and the tags text is generated and inserted. + while(sourceIterator != kafkaIterator) + { + sourceIterator++; + for (it2 = (*sourceIterator).NodeModifList.begin(); it2 != (*sourceIterator).NodeModifList.end(); ++it2) + { + if(!redoNodeModif((*it2), true, true)) + { + kdDebug(25001)<< "undoRedo::syncQuantaView() - ERROR 2" << endl; + return false; + } + #ifdef LIGHT_DEBUG + kdDebug(25001)<< "undoRedo::syncQuantaView() - Nodes and text redoed!" << endl; + #endif + } + } + } + else + { + //undo operations have been done in the kafka view + //First redo all the Node modifs made after the last update. + //This might be called when an user action occurs after undoing : we must sync before the + //deletion of part of the undo stack. + while(it != sourceIterator) + { + ++it; + for(it2 = (*it).NodeModifList.begin(); it2 != (*it).NodeModifList.end(); ++it2) + { + if(!redoNodeModif((*it2), false)) + { + kdDebug(25001)<< "undoRedo::syncQuantaView() - ERROR 3" << endl; + return false; + } + #ifdef LIGHT_DEBUG + kdDebug(25001)<< "undoRedo::syncQuantaView() - Nodes without text redoed!" << endl; + #endif + } + } + //then for each NodeModif, Nodes are undoed, and the tags text is generated and inserted. + while(sourceIterator != kafkaIterator) + { + it2 = (*sourceIterator).NodeModifList.fromLast(); + while(it2 != (*sourceIterator).NodeModifList.end()) + { + if(!undoNodeModif((*it2), true, true)) + { + kdDebug(25001)<< "undoRedo::syncQuantaView() - ERROR 4" << endl; + return false; + } + #ifdef LIGHT_DEBUG + kdDebug(25001)<< "undoRedo::syncQuantaView() - Nodes and text undoed!" << endl; + #endif + if(it2 == (*sourceIterator).NodeModifList.begin()) + break; + it2--; + } + sourceIterator--; + } + } + sourceIterator = kafkaIterator;*/ + return true; +} + +void undoRedo::syncKafkaCursorAndSelection(NodeSelection *selection) +{ + QuantaView *view = ViewManager::ref()->activeView(); + KafkaWidget *kafkaPart = KafkaDocument::ref()->getKafkaWidget(); + DOM::Node node; + long offset; + uint curLine, curCol/**, curLine2, curCol2*/; + /**DOM::Range range(kafkaPart) = kafkaPart->selection();*/ + + if(!KafkaDocument::ref()->isLoaded()) + return; + + /**DOM::Range tempRange(document()); + tempRange.setStart(document(), 0); + tempRange.setEnd(m_currentNode, 2); + setSelection(tempRange);*/ + + //Translate and set the cursor. + if(selection) + { + KafkaDocument::ref()->translateNodeIntoKafkaCursorPosition(selection->cursorNode(), + selection->cursorOffset(), node, offset); + kafkaPart->setCurrentNode(node, offset); + } + else + { + view->document()->viewCursorIf->cursorPositionReal(&curLine, &curCol); + KafkaDocument::ref()->translateQuantaIntoKafkaCursorPosition(curLine, + curCol, node, offset); + kafkaPart->setCurrentNode(node, offset); + } + + //Translate and set the selection. + //quantaApp->view()->write()->selectionIf() +} + +void undoRedo::syncQuantaCursorAndSelection() +{ + QuantaView *view = ViewManager::ref()->activeView(); + KafkaWidget *kafkaPart = KafkaDocument::ref()->getKafkaWidget(); + int curCol, curLine, curCol2, curLine2; + uint oldCurCol, oldCurLine; + DOM::Node domNode, domNodeEnd; + long offset, offsetBegin, offsetEnd; + DOM::Range range(kafkaPart); + + //Translate and set the cursor. + KafkaDocument::ref()->getKafkaWidget()->getCurrentNode(domNode, offset); + KafkaDocument::ref()->translateKafkaIntoQuantaCursorPosition(domNode, + offset, curLine, curCol); + view->document()->viewCursorIf->cursorPositionReal(&oldCurLine, &oldCurCol); + if(oldCurCol != (uint)curCol || oldCurLine != (uint)curLine) + view->document()->viewCursorIf->setCursorPositionReal((uint)curLine, (uint)curCol); + + //Translate and set the selection + kafkaPart->selection(domNode, offsetBegin, domNodeEnd, offsetEnd); + KafkaDocument::ref()->translateKafkaIntoQuantaCursorPosition( + domNode, (int)offsetBegin, curLine, curCol); + KafkaDocument::ref()->translateKafkaIntoQuantaCursorPosition( + domNodeEnd, (int)offsetEnd, curLine2, curCol2); + if (view->document()->selectionIf) + view->document()->selectionIf->setSelection(curLine, curCol, curLine2, curCol2); +} + +void undoRedo::debugOutput() +{ +#ifdef HEAVY_DEBUG + int i = 0; + bool afterEditorIt = false; + + kdDebug(24000)<< "Undo/redo stack contents:" << endl; + if(m_undoList.isEmpty()) + { + kdDebug(24000)<< "Empty!" << endl; + return; + } + QPtrListIterator<NodeModifsSet> it(m_undoList); + for(it.toFirst(); it ; ++it ) + { + kdDebug(24000)<< "== Node Modifications set #" << i << "(" << (*it)->isModifiedBefore() << "," << + (*it)->isModifiedAfter() << ")" << endl; + if((*it)->nodeModifList().isEmpty()) + { + kdDebug(24000)<< "== Empty!" << endl; + kdDebug(24000)<< "== End Node Modifications set #" << i << endl; + i++; + continue; + } + //kdDebug(24000)<< "== Cursor Pos: " << (*it).cursorY << ":" << (*it).cursorX << endl; + //kdDebug(24000)<< "== Cursor Pos2:" << (*it).cursorY2 << ":" << (*it).cursorX2 << endl; + QPtrListIterator<NodeModif> it2((*it)->nodeModifList()); + for(it2.toFirst(); it2; ++it2) + { + kdDebug(24000)<< "==== NodeModif type:" << (*it2)->type() << endl; + kdDebug(24000)<< "==== Location1: " << endl; + QValueList<int>::iterator it3; + if((*it2)->location().empty()) + { + kdDebug(24000)<< "==== Empty location!!" << endl; + } + else if((*it2)->type() != NodeModif::NodeTreeAdded && + (*it2)->type() != NodeModif::NodeTreeRemoved) + { + for(it3 = (*it2)->location().begin(); it3 != (*it2)->location().end(); ++it3) + kdDebug(24000)<< (*it3) << endl; + if((*it2)->type() != NodeModif::NodeMoved || + (*it2)->type() != NodeModif::NodeAndChildsMoved) + { + kdDebug(24000)<< "==== Location2: " << endl; + for(it3 = (*it2)->finalLocation().begin(); it3 != (*it2)->finalLocation().end(); ++it3) + kdDebug(24000)<< (*it3) << endl; + } + } + if((((*it2)->type() == NodeModif::NodeRemoved && !afterEditorIt) || + ((*it2)->type() == NodeModif::NodeAdded && afterEditorIt)) && (*it2)->node()) + kdDebug(24000)<< "==== Node: " << (*it2)->node()->tag->name << + " - contents: " << (*it2)->node()->tag->tagStr() << endl; + if((*it2)->type() == NodeModif::NodeModified && (*it2)->tag()) + kdDebug(24000)<< "==== Tag: " << (*it2)->tag()->name << + " - contents: " << (*it2)->tag()->tagStr() << endl; + if(((*it2)->type() == NodeModif::NodeRemoved && !afterEditorIt) || + ((*it2)->type() == NodeModif::NodeAdded && afterEditorIt)) + kdDebug(24000)<< "==== ChildsNumber1 : " << (*it2)->childrenMovedUp() << + " - ChildsNumber2 : " << (*it2)->neighboursMovedDown() << endl; + } + kdDebug(24000)<< "== End Node Modifications set #" << i << endl; + i++; + if(it == sourceIterator) + afterEditorIt = true; + } + kdDebug(24000)<< "End Undo/redo stack contents" << endl; + kafkaCommon::coutTree(baseNode, 2); +#endif +} + +void undoRedo::fileSaved() +{ + QPtrListIterator<NodeModifsSet> it(m_undoList); + bool previousWasDocIt = false; + for(it.toFirst(); it ; ++it ) + { + if(previousWasDocIt) + { + (*it)->setIsModifiedBefore(false); + (*it)->setIsModifiedAfter(true); + previousWasDocIt = false; + } + else if(it == documentIterator) + { + (*it)->setIsModifiedBefore(true); + (*it)->setIsModifiedAfter(false); + previousWasDocIt = true; + } + else + { + (*it)->setIsModifiedBefore(true); + (*it)->setIsModifiedAfter(true); + } + } + /** + QValueList<NodeModifsSet>::iterator it = sourceIterator; + (*sourceIterator).isModified = false; + //seting isModified = true to all others + while(it != begin()) + { + it--; + (*it).isModified = true; + } + it = sourceIterator; + ++it; + while(it != end()) + { + (*it).isModified = true; + ++it; + }*/ +} + +void undoRedo::kafkaLoaded() +{ + kafkaIterator = documentIterator; +} + diff --git a/quanta/parts/kafka/undoredo.h b/quanta/parts/kafka/undoredo.h new file mode 100644 index 00000000..f9ec09cf --- /dev/null +++ b/quanta/parts/kafka/undoredo.h @@ -0,0 +1,453 @@ +/*************************************************************************** + undoredo.h + ------------------- + + copyright : (C) 2003, 2004 - Nicolas Deschildre + email : ndeschildre@kdewebdev.org + ***************************************************************************/ + +/*************************************************************************** + * * + * 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. * + * * + ***************************************************************************/ + +#ifndef UNDOREDO_H +#define UNDOREDO_H + +#include <qvaluelist.h> +#include <qptrlist.h> +#include <qobject.h> +#include "node.h" + +class Document; +class NodeSelection; +class NodeSelectionInd; + +/** + * The basic unit of the undo/redo system : a Node modification. + */ +class NodeModif +{ +public: + NodeModif(); + ~NodeModif(); + + /** + * For all NodeModifs : Type of the Node modification : Added, removed, modified, moved,... + * cf the NodeModification enumeration. + * WARNING The type MUST be set first!! + * @param type The type of the NodeModif, as described in the NodeModification enumeration. + */ + void setType(int type){m_type = type;} + + /** + * @return Returns the current type of the NodeModif. + */ + int type() {return m_type;} + + /** + * For all type : Location of the Node added/modified/removed/moved. + * @param location The location of the Node, cf kafkaCommon::getNodeLocation() + */ + void setLocation(QValueList<int> location) {m_location = location;} + + /** + * @return Returns the location of the Node which have been modified. + */ + QValueList<int>& location() {return m_location;} + + /** + * For Node move : Final location of the Node moved. + * @param location The final location of the Node moved, cf kafkaCommon::getNodeLocation() + */ + void setFinalLocation(QValueList<int> location) {m_finalLocation = location;} + + /** + * @return Returns the final location of the Node which was moved. + */ + QValueList<int>& finalLocation() {return m_finalLocation;} + + /** + * For Node deletion: Store the deleted Node. + * @param node The deleted node. + */ + void setNode(Node *node); + + /** + * @return Returns the deleted Node. + */ + Node *node() {return m_node;} + + /** + * For Node modification : Store the old tag + * @param tag The original tag. + */ + void setTag(Tag *tag); + + /** + * @return Returns the original Tag. + */ + Tag *tag() {return m_tag;} + + /** + * TODO:REMOVE + * For non-XmlEnd Node deletion without its children. + * @param childrenNumber The number of children which are moved up + * at the location where was the deleted Node. + */ + void setChildrenMovedUp(int childrenNumber) {m_childrenMovedUp = childrenNumber;} + + /** + * TODO:REMOVE + * @return Returns the number of childs which were moved up. + */ + int childrenMovedUp() {return m_childrenMovedUp;} + + /** + * TODO:REMOVE + * For XmlEnd Node deletion : number of moved down + * @param number The number of right neighbours which are moved down. + */ + void setNeighboursMovedDown(int number) {m_neighboursMovedDown = number;} + + /** + * TODO:REMOVE + * @return Returns the number of right neighbours which were moved down. + */ + int neighboursMovedDown() {return m_neighboursMovedDown;} + + /** All the possible Node modifications */ + enum NodeModification { + //A complete Node Tree is added. Implemented. + NodeTreeAdded = 0, + //A Node and its childs are added. Implemented. + NodeAndChildsAdded, + //A Node is added. Implemented. + NodeAdded, + //WARNING : do not use this if the node type or the node name change. + //A Node is modified. Implemented. + NodeModified, + //A Node is removed. Implemented. + NodeRemoved, + //A Node and its childs are removed. Implemented. + NodeAndChildsRemoved, + //The complete Node tree is removed. Implemented. + NodeTreeRemoved, + //Moving a Node from one location to another. Implemented. + NodeMoved, + //Moving a Node and its children from one location to another. + NodeAndChildsMoved + }; + +private: + int m_type; + QValueList<int> m_location, m_finalLocation; + Node *m_node; + Tag *m_tag; + int m_childrenMovedUp; + int m_neighboursMovedDown; +}; + +/** + * A NodeModifsSet contains all the Node modifications made by one user input, and the + * cursor and selection location before and after the user input. + */ +class NodeModifsSet +{ +public: + NodeModifsSet(); + ~NodeModifsSet(); + + /** + * Add a new NodeModif to the list of NodeModifs. + */ + void addNodeModif(NodeModif *nodeModif) {m_nodeModifList.append(nodeModif);} + + /** + * Returns the list of NodeModifs. + */ + QPtrList<NodeModif> & nodeModifList() {return m_nodeModifList;} + + /** + * Set the Modified flag BEFORE the user input. + */ + void setIsModifiedBefore(bool isModifiedBefore) {m_isModifiedBefore = isModifiedBefore;} + + /** + * @return Returns the Modified flag BEFORE the user input. + */ + bool isModifiedBefore(){return m_isModifiedBefore;} + + /** + * Set the Modified flag AFTER the user input. + */ + void setIsModifiedAfter(bool isModifiedAfter) {m_isModifiedAfter = isModifiedAfter;} + + /** + * @return Returns the Modified flag AFTER the user input. + */ + bool isModifiedAfter(){return m_isModifiedAfter;} + + /** + * Set a description to the user input. + */ + void setDescription(const QString &description) {m_description = description;} + + /** + * @return Returns the description of the user input. + */ + QString description() {return m_description;} + + /** + * Set the coordinates of the selection before the user input. + */ + void setSelectionBefore(NodeSelectionInd* selectionBefore) {m_selectionBefore = selectionBefore;} + + /** + * @return Return the selection before the Node modifications. + */ + NodeSelectionInd *selectionBefore() {return m_selectionBefore;} + + /** + * Set the coordinates of the selection after the user input. + */ + void setSelectionAfter(NodeSelectionInd* selectionAfter) {m_selectionAfter = selectionAfter;} + + /** + * @return Return the selection after the Node modifications. + */ + NodeSelectionInd *selectionAfter() {return m_selectionAfter;} + + /** + * We can separate the NodeModif into two categories ; the Node modifs made by the user + * and then the indentation. + * Call this function when we have finished with the first category. + */ + void startOfIndentation() {m_indentationStartOffset = m_nodeModifList.count();} + + /** + * @return Return the offset (starting from 0) where the indentation NodeModifs begin in the list of NodeModif. + * Return -1 if it hasn't begin to add the indentation NodeModifs. + */ + int indentationStartOffset() {return m_indentationStartOffset;} + + +private: + QPtrList<NodeModif> m_nodeModifList; + bool m_isModifiedBefore, m_isModifiedAfter; + QString m_description; + NodeSelectionInd *m_selectionBefore, *m_selectionAfter; + int m_indentationStartOffset, m_type; +}; + +/** + * This class, basically a new undo/redo system, also helps KafkaDocument to synchronize the + * kafka and quanta view. + */ +class undoRedo : public QObject +{ +public: + /** + * Creates a document specific undo/redo system. + * @param doc The Document the undo/redo system is linked to. + */ + undoRedo(Document *doc); + + ~undoRedo(); + + /** The enumeration of all possible location where modifications can occur */ + enum modificationLocation { + // A modification was made in the source view (kate). + SourceModif = 0, + // A modification was made directly in the node Tree. + NodeTreeModif, + // A modification was made in the VPL view (kafka). + KafkaModif + }; + + /** + * Adds a new set of Node modification. This should be called whenever + * the kafka/quanta editor is modified. + * @param modifs The new modification set to add to the undo/redo stack. + * @param modifLocation Specifies where the modification was made + * @param selection If given, and if modifLocation == SourceModif, it will synchronize the cursor + * and the selection to 'selection'. Only work when the focus is in VPL for the moment. + * cf undoRedo::modificationLocation. + */ + void addNewModifsSet(NodeModifsSet *modifs, int modifLocation, NodeSelection *selection = 0L, bool encodeText = true); + + /** + * TEMPORARY function. + * First we will only enable undoRedo in VPL : this class will only log changes + * made in VPL. + * This function specify if we should log the changes submitted to addNewModifsSet or not. + * @param True => enable, false => disable + */ + void turnOn(bool on); + + /** + * TEMPORARY function. + * @return Returns true if the changes are logged. + */ + bool turnedOn() {return m_loggingEnabled;} + + /** + * TODO:REMOVE + * Ignores the ModifSet that will come in the number'th position. Useful when + * KTextEditor::EditInterface::insertText() is called before parser::rebuild() and + * thus parser::rebuild will be called two times. + * @param number Specifies the position of the ModifsSet to ignore. + */ + void dontAddModifsSet(int number) {m_dontAddModifSet = number;} + + /** + * TODO:REMOVE + * Merges the next ModifsSet with the previous one. Useful when autocompletion + * makes parser::rebuild() to be called again. + */ + void mergeNextModifsSet() {m_mergeNext = true;} + + /** + * Makes the undo operation. + * @param kafkaUndo Specifies if the undo operation is done in the kafka view. + * @return Returns true if a previous undo operation is available. + */ + bool undo(); + + /** + * Makes the redo operation. + * @param kafkaUndo Specifies if the undo operation is done in the kafka view. + * @return Returns true if a next redo operation is available. + */ + bool redo(); + + /** + * Synchronize the kafka view with the quanta view by applying the NodeModifs + * which have occured since the last synchronization. + * @return Returns if the synchronization was successful. + */ + bool syncKafkaView(); + + /** + * Synchronize the quanta view with the kafka view by applying the NodeModifs + * which have occured since the last synchronization. + * @return Returns if the synchronization was successful. + */ + bool syncQuantaView(); + + /** + * Synchronize the cursor position and the selection of the kafka view by translating + * the cursor position and selection of the quanta view. + * @param selection Synchronize the selection and the cursor position to "selection". + */ + void syncKafkaCursorAndSelection(NodeSelection *selection); + + /** + * Synchronize the cursor position and the selection of the quanta view by translating + * the cursor position and selection of the kafka view. + */ + void syncQuantaCursorAndSelection(); + + /** + * Reload kafka from the current document. + * @param force If set to true, it will reload kafka even if it is already up to date. + * @param selection If given, it will synchronize the selection and the cursor position to 'selection'. + */ + void reloadKafkaEditor(bool force = false, NodeSelection *selection = 0L); + + /** + * Reload the quanta editor from kafka. + * @param force If set to true, it will reload quanta even if it is already up to date. + * @param syncQuantaCursor If set to true, it will try to sync the quanta cursor from + * the kafka one. + * @param encodeText Usually when a Text Node has the dirty flag (cleanStrBuilt), it means that + * it was modified in VPL and thus it contains some unencoded text : we have to encode it. (e.g. + * whitespace => ) But some functions calling this function might not want that because + * the text is already encoded e.g. codeFormatting(). + */ + void reloadQuantaEditor(bool force = false, bool syncQuantaCursor = true, bool encodeText = true); + + /** + * Format the code of the document. It simply call reloadQuantaEditor() after having + * set the dirty flag to every Node. + */ + void codeFormatting(); + +public slots: + + /** + * Called by quantaApp whenever the current file is saved. The isModified + * flag of each NodeModisSet is updated. + */ + void fileSaved(); + + /** + * Called when the kafkaPart is loaded. + */ + void kafkaLoaded(); + +private: + + /** + * This is one of the main functions which apply the changes needed to undo a nodeModif + * in the text and in the Node tree. + * @param nodeModif The nodeModif to undo. + * @return Returns true if the undo has correctly worked. + */ + bool undoNodeModif(NodeModif *nodeModif); + + /** + * Convenient function which call undoNodeModif, + * while changing the type of the NodeModifs to make them redo. + */ + bool redoNodeModif(NodeModif *nodeModif); + + /** + * This is one of the main functions which apply the changes needed to undo a nodeModif + * in the kafka tree. + * @param _nodeModif The nodeModif to undo. + * @return Returns true if the undo has correctly worked. + */ + bool undoNodeModifInKafka(NodeModif *nodeModif); + + /** + * Convenient function which call undoNodeModifInKafka, + * while changing the type of the NodeModifs to make them redo. + */ + bool redoNodeModifInKafka(NodeModif *nodeModif); + + /** + * Prints in stdout a debugging flow. + */ + void debugOutput(); + +private: + /** + * The main undoRedo list which contains the NodeModifsSet. + */ + QPtrList<NodeModifsSet> m_undoList; + /** + * The undoRedo list iterators which point the current location of each component in + * the undoRedo list. + * documentIterator point the current location of the Node Tree. + * sourceIterator point the current location of the source view (kate). + * kafkaIterator point the current location of the VPL view (kafka). + */ + QPtrListIterator<NodeModifsSet> documentIterator; + QPtrListIterator<NodeModifsSet> sourceIterator; + QPtrListIterator<NodeModifsSet> kafkaIterator; + int m_listLimit; + bool m_merging; + bool addingText; + QValueList<int> m_currentLoc; + Document *m_doc; + bool m_mergeNext; + int m_dontAddModifSet; + bool m_loggingEnabled; +}; + +#endif diff --git a/quanta/parts/kafka/wkafkapart.cpp b/quanta/parts/kafka/wkafkapart.cpp new file mode 100644 index 00000000..5641bb36 --- /dev/null +++ b/quanta/parts/kafka/wkafkapart.cpp @@ -0,0 +1,2407 @@ +/*************************************************************************** + wkafkapart.cpp + ------------------- + + copyright : (C) 2003, 2004 - Nicolas Deschildre + email : ndeschildre@kdewebdev.org + ***************************************************************************/ + +/*************************************************************************** + * * + * 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. * + * * + ***************************************************************************/ + +#define HEAVY_DEBUG 1 + +#include <dom/dom_node.h> +#include <dom/dom_exception.h> +#include <dom/dom_string.h> +#include <dom/css_stylesheet.h> +#include <kdebug.h> +#include <khtmlview.h> +#include <kstandarddirs.h> +#include <ktexteditor/editinterface.h> +#include <ktexteditor/selectioninterface.h> +#include <kstandarddirs.h> +#include <klocale.h> +#include <kmultipledrag.h> +#include <kglobal.h> +#include <kcharsets.h> + +#include <qregexp.h> +#include <qfile.h> +#include <qtextstream.h> +#include <qdatetime.h> +#include <qclipboard.h> +#include <qptrvector.h> + +#include "document.h" +#include "viewmanager.h" +#include "quantacommon.h" +#include "resource.h" +#include "undoredo.h" +#include "node.h" +#include "parser.h" +#include "project.h" +#include "tag.h" +#include "nodeproperties.h" +#include "htmlenhancer.h" +#include "kafkacommon.h" +#include "kafkaresource.h" +#include "cursors.h" +#include "kafkadragobject.h" +#include "cursors.h" + +#include "wkafkapart.moc" + +KafkaWidget *kafkaWidget; +KafkaDocument *kafkaDoc; + +KafkaDocument::KafkaDocument(QWidget *parent, QWidget *widgetParent, const char *name) +:domNodeProps(1021), _docLoaded(false) +{ +// i18n reserve +QString a = i18n("Selector");QString b = i18n("Attribute");QString c = i18n("Class"); +QString d = i18n("Pseudo-class");QString e = i18n("CSS rules");QString f = i18n("Universal selector"); +QString g = i18n("Linked stylesheets");QString h = i18n("Embedded stylesheets");QString i = i18n("Inline style attribute"); +QString j = i18n("Link");QString k = i18n("Priority");QString l = i18n("ID"); +QString m = i18n("Browser support");QString n = i18n("Pseudo-element");QString o = i18n("Imported"); +QString p = i18n("Inheritance");QString q = i18n("Inherited");QString r = ""; + +QString s = i18n("Name");QString tt = i18n("Undo");QString u = i18n("Redo"); +QString v = i18n("Undo/Redo history");QString w = i18n("CSS styles"); + +QString x = i18n("Sorry, VPL does not support this functionality yet."); +QString y = i18n("Merge cells"); +QString z = i18n("Split cells"); +QString aa = i18n("Edit CSS style of this Tag"); +QString ab = i18n("Ident all"); +//end + +#ifdef LIGHT_DEBUG + kdDebug(25001)<< "KafkaDocument::KafkaDocument()" << endl; +#endif + + kafkaDoc = this; + + m_kafkaPart = new KafkaWidget(parent, widgetParent,this, name); + kafkaWidget = m_kafkaPart; + //m_kafkaPart->showDomTree(); + m_currentDoc = 0L; + mainEnhancer = new HTMLEnhancer(this); + domNodeProps.setAutoDelete(false); + + KStandardDirs *m_stddirs = new KStandardDirs(); + QFile file( m_stddirs->findResource("data", "kafkapart/entities" )/**locate("appdata","chars") */); + delete m_stddirs; + QString tmp; + if ( file.open(IO_ReadOnly) ) + { + QTextStream t( &file ); // use a text stream + t.setEncoding(QTextStream::UnicodeUTF8); + while ( !t.eof() ) + { + tmp = t.readLine(); + if(tmp.left(2) == "//") continue;//comments + int begin = tmp.find("(") + 1; + if(begin == (-1 + 1)) continue;//"(" not found : invalid line + int length = tmp.find(")") - begin; + decodedChars.insert(tmp.left(1), tmp.mid(begin, length)); + encodedChars.insert(tmp.mid(begin, length), tmp.left(1)); + } + file.close(); + } + + connect(m_kafkaPart, SIGNAL(domNodeInserted(DOM::Node, bool, NodeModifsSet*)), + this, SLOT(slotDomNodeInserted(DOM::Node, bool, NodeModifsSet*))); + connect(m_kafkaPart, SIGNAL(domNodeModified(DOM::Node, NodeModifsSet*)), + this, SLOT(slotDomNodeModified(DOM::Node, NodeModifsSet*))); + connect(m_kafkaPart, SIGNAL(domNodeIsAboutToBeRemoved(DOM::Node, bool, NodeModifsSet*)), + this, SLOT(slotDomNodeAboutToBeRemoved(DOM::Node, bool, NodeModifsSet*))); + connect(m_kafkaPart, SIGNAL(domNodeIsAboutToBeMoved(DOM::Node, DOM::Node, DOM::Node, NodeModifsSet*)), + this, SLOT(slotDomNodeIsAboutToBeMoved(DOM::Node, DOM::Node, DOM::Node, NodeModifsSet*))); + + connect(m_kafkaPart, SIGNAL(domNodeNewCursorPos(DOM::Node, int)), + this, SLOT(slotdomNodeNewCursorPos(DOM::Node, int))); + +} + +KafkaDocument::~KafkaDocument() +{ +#ifdef LIGHT_DEBUG + kdDebug(25001)<< "KafkaDocument::~KafkaDocument()" << endl; +#endif + //delete the empty node linked to the DOM::Node #document + disconnectAllDomNodes(); +} + +void KafkaDocument::loadDocument(Document *doc) +{ +#ifdef LIGHT_DEBUG + kdDebug(25001) << "KafkaDocument::loadDocument()" << endl; +#endif + Node *node; + Tag *tag; + DOM::Node domNode; + bool goUp; + + if(!m_kafkaPart) return; + if(!doc) return; +#ifdef LIGHT_DEBUG + QTime t; + t.start(); +#endif + m_currentDoc = doc; + + (static_cast<HTMLEnhancer *>(mainEnhancer))->setBaseURL(Project::ref()->projectBaseURL()); + + //create a empty document with a basic tree : HTML, HEAD, BODY + m_kafkaPart->newDocument(); + + // When loading a weird html file in khtml (e.g. without BODY or HTML), khtml takes care + // to create the necessary tags. But as we are handling directly the Nodes, we have to handle this!! + // creating and linking an empty node to the root DOM::Node (#document) and + // to HEAD, HTML, BODY + node = new Node(0L); + tag = new Tag(); + tag->name = "#document"; + tag->setNotInTree(true); + node->tag = tag; + connectDomNodeToQuantaNode(m_kafkaPart->document(), node); + node = new Node(0L); + tag = new Tag(); + tag->name = "HTML"; + tag->setNotInTree(true); + node->tag = tag; + connectDomNodeToQuantaNode(m_kafkaPart->document().firstChild(), node); + html = m_kafkaPart->document().firstChild(); + node = new Node(0L); + tag = new Tag(); + tag->name = "HEAD"; + tag->setNotInTree(true); + node->tag = tag; + connectDomNodeToQuantaNode(m_kafkaPart->document().firstChild().firstChild(), node); + head = m_kafkaPart->document().firstChild().firstChild(); + node = new Node(0L); + tag = new Tag(); + tag->name = "BODY"; + tag->setNotInTree(true); + node->tag = tag; + connectDomNodeToQuantaNode(m_kafkaPart->document().firstChild().lastChild(), node); + body = m_kafkaPart->document().firstChild().lastChild(); + + //load the DOM::Nodes from the node tree. + node = baseNode; + while(node) + { +#ifdef HEAVY_DEBUG + kdDebug(25001) << "KafkaDocument::loadDocument - Node name :" << + node->tag->name.upper() << "; type : " << + node->tag->type << "; tagstr : " << node->tag->tagStr() << + " is opened :" << node->opened << endl; +#endif + if(!buildKafkaNodeFromNode(node)) + emit loadingError(node); + node = node->nextSibling(); + } + + //post process the DOM::Node Tree by adding Empty TEXT so that the cursor can go + //everywhere the user wants. + domNode = m_kafkaPart->document(); + goUp = false; + while(!domNode.isNull()) + { + mainEnhancer->postEnhanceNode(domNode); + domNode = kafkaCommon::getNextDomNode(domNode, goUp); + } + + m_kafkaPart->putCursorAtFirstAvailableLocation(); + _docLoaded = true; + + //Avoid moving objects... + m_kafkaPart->stopAnimations(); + + m_currentDoc->docUndoRedo->kafkaLoaded(); + emit loaded(); + + m_currentDoc->docUndoRedo->syncKafkaCursorAndSelection(0); + //m_kafkaPart->document().updateRendering(); + +#ifdef LIGHT_DEBUG + kdDebug(25001)<< "KafkaDocument::loadDocument() in " << t.elapsed() << " ms only!" << endl; +#endif +#ifdef HEAVY_DEBUG + kafkaCommon::coutDomTree(m_kafkaPart->document(), 2); + coutLinkTree(baseNode, 2); +#endif +} + +void KafkaDocument::unloadDocument() +{ +#ifdef LIGHT_DEBUG + kdDebug(25001)<< "KafkaDocument::unloadDocument()" << endl; +#endif + Node *node; + + domNodeProps.clear(); + //clean the kafkapart + while(m_kafkaPart->document().hasChildNodes()) + m_kafkaPart->document().removeChild(m_kafkaPart->document().firstChild()); + m_currentDoc = 0L; + + html = body = head = DOM::Node(); + + _docLoaded = false; + node = baseNode; + while(node) + { + if(node->rootNode()) + delete node->rootNode(); + node->setRootNode(0L); + if(node->leafNode()) + delete node->leafNode(); + node->setLeafNode(0L); + node = node->nextSibling(); + } + emit unloaded(); +} + +void KafkaDocument::reloadDocument() +{ +#ifdef LIGHT_DEBUG + kdDebug(25001)<< "KafkaDocument::reloadDocument()" << endl; +#endif + Node *node; + int offsetX, offsetY; + + offsetX = m_kafkaPart->view()->contentsX(); + offsetY = m_kafkaPart->view()->contentsY(); + + m_kafkaPart->closeURL(); + + if(!_docLoaded) + return; + + domNodeProps.clear(); + while(m_kafkaPart->document().hasChildNodes()) + m_kafkaPart->document().removeChild(m_kafkaPart->document().firstChild()); + node = baseNode; + while(node) + { + if(node->rootNode()) + delete node->rootNode(); + node->setRootNode(0L); + if(node->leafNode()) + delete node->leafNode(); + node->setLeafNode(0L); + node = node->nextSibling(); + } + + /**KParts::URLArgs args(false, offsetX, offsetY); + (static_cast<KParts::BrowserExtension *>(((KParts::ReadOnlyPart *)m_kafkaPart)->child( 0L, + "KParts::BrowserExtension" )))->setURLArgs( args );*/ + + loadDocument(m_currentDoc); + + //m_kafkaPart->view()->setContentsPos(offsetX, offsetY); +} + +kNodeAttrs *KafkaDocument::getAttrs(DOM::Node _domNode) +{ + return domNodeProps[_domNode.handle()]; +} + + +Node *KafkaDocument::getNode(DOM::Node _domNode) +{ + if(_domNode.isNull() || _domNode.nodeName().string() == "#document") + { + kdDebug(25001)<< "KafkaDocument::getNode() - Bad Node given" << + endl; + return 0L; + } + kNodeAttrs *props = domNodeProps[_domNode.handle()]; + if(!props) + { + kdDebug(25001)<< "KafkaDocument::getNode() -" << + " Corresponding Node not Found!!!" << endl; + return 0L; + } + return props->getNode(); +} + +kNodeAttrs* KafkaDocument::connectDomNodeToQuantaNode(DOM::Node domNode, Node *node) +{ + QString name; + kNodeAttrs *props; + + if(domNode.isNull()) + { +#ifdef LIGHT_DEBUG + kdDebug(25001)<< "KafkaDocument::connectDomNodeToQuantaNode()" << + " - WARNING empty DOM::Node" << endl; +#endif + } + /**qtag = QuantaCommon::tagFromDTD(m_currentDoc->defaultDTD(), + _domNode.nodeName().string());*/ + else + name = domNode.nodeName().string().lower(); + +// kdDebug(25001) << "KafkaDocument::connectDomNodeToQuantaNode() - domNode name: |" << name << "|" << endl; + + props = new kNodeAttrs(); + + //We can split Nodes into several groups essentially for the deletion behavior: + //1- Text + //2- Block in which the cursor can't enter in when deleting e.g. TABLE, IMG + //3- Block in which the cursor can't escape from e.g. TD, BODY + //4- Block in which the cursor can enter in/escape from when deleting e.g. H1, DIV + //5- Inline + //6- "Invisible" Nodes e.g. HTML, HEAD + + //Hmmm... I guess we can do better here... + if(domNode.nodeType() == DOM::Node::TEXT_NODE) + { + props->setCHCursorFocus(kNodeAttrs::textNode); + props->setCCEnter(true); + props->setCBModified(true); + props->setCBDeleted(true); + } + else if(name == "abbr" || name == "acronym" || name == "address" || name == "b" || name == "bdo" || + name == "big" || name == "cite" || name == "code" || + name == "del" || name == "dfn" || name == "dir" || + name == "em" || name == "fieldset" || name == "font" || name == "i" || name == "iframe" || + name == "ins" || name == "kbd" || name == "label" || name == "legend" || name == "menu" || + name == "noframes" || name == "pre" || name == "s" || name == "samp" || + name == "small" || name == "span" || name == "strike" || name == "strong" || name == "sub" || + name == "sup" || name == "tt" || name == "u" || name == "var" || name == "a" || + name == "blockquote" || + name == "em" || name == "form" || name == "ins" || + name == "q" || name == "tt" ) + { + props->setCHCursorFocus(kNodeAttrs::inlineNode); + props->setCCEnter(true); + props->setCBModified(true); + props->setCBDeleted(true); + } + else if(name == "center" || name == "li" || name == "h1" || name == "h2" || name == "h3" || + name == "h4" || name == "h5" || name == "h6" || name == "div" || name == "dd" || + name == "dt" || name == "p") + { + props->setCHCursorFocus(kNodeAttrs::blockNode); + props->setCCEnter(true); + props->setCBModified(true); + props->setCBDeleted(true); + } + else if(name == "applet" || name == "button" || name == "img" || name == "map" || name == "object" || + name == "hr" || name == "input" || name == "select" || name == "table" || name == "textarea" || + name == "br" || name == "dl" || name == "ul" || name == "ol") + { + props->setCHCursorFocus(kNodeAttrs::singleNodeAndItself); + props->setCCEnter(false); + props->setCBModified(true); + props->setCBDeleted(true); + } + else if(name == "basefont" || name == "location" || name == "fieldset" || name == "noscript" || + name == "script") + { + props->setCHCursorFocus(kNodeAttrs::no); + props->setCCEnter(false); + props->setCBModified(true); + props->setCBDeleted(true); + } + else if(name == "caption" || name == "frame" || name == "frameset" || name == "isindex" || + name == "optgroup" || name == "param" || name == "title" || name == "area" || name == "base" || + name == "body" || name == "col" || name == "colgroup" || name == "head" || name == "html" || + name == "link" || name == "meta" || name == "option" || name == "style" || name == "tbody" || + name == "td" || name == "tfoot" || name == "th" || name == "thead" || name == "tr") + { + props->setCHCursorFocus(kNodeAttrs::no); + props->setCCEnter(false); + props->setCBModified(false); + props->setCBDeleted(false); + } + else + { + kdDebug(25001)<< "KafkaDocument::connectDomNodeToQuantaNode () - " << + "No QTag found! Setting default parameters..." << endl; + props->setCHCursorFocus(kNodeAttrs::no); + props->setCCEnter(false); + props->setCBModified(false); + props->setCBDeleted(false); + } + +#ifdef HEAVY_DEBUG + kdDebug(25001)<< "KafkaDocument::connectDomNodeToQuantaNode() - tag name :" << + name << " canBeDeleted:" << props->cbDel() << " canBeModified:" << + props->cbMod() << " canHaveCursorFocus:" << props->chCurFoc() << + " cursorCanEnter:" << props->ccanEnter() << endl; +#endif + props->setNode(node); + props->setDomNode(domNode); + domNodeProps.insert(domNode.handle(), props); + + return props; +} + +void KafkaDocument::disconnectDomNodeFromQuantaNode(DOM::Node _domNode) +{ + if(_domNode.isNull()) + return; + domNodeProps.remove(_domNode.handle()); +} + +void KafkaDocument::disconnectAllDomNodes() +{ + domNodeProps.clear(); +} + +void KafkaDocument::setCursorAndSelection(NodeSelectionInd *nodeSelection) +{ + bool cursorAtSelectionStart; + DOM::Node startDomNode, endDomNode, foo; + Node *startNode, *endNode; + long startOffset, endOffset; + + cursorAtSelectionStart = nodeSelection->cursorAtSelectionStart(); + startNode = kafkaCommon::getNodeFromLocation(nodeSelection->cursorNode()); + endNode = kafkaCommon::getNodeFromLocation(nodeSelection->cursorNodeEndSel()); + + if(startNode && startNode->rootNode()) + startDomNode = *(startNode->rootNode()); + if(endNode && endNode->rootNode()) + endDomNode = *(endNode->rootNode()); + translateNodeIntoKafkaCursorPosition(startNode, nodeSelection->cursorOffset(), foo, startOffset); + translateNodeIntoKafkaCursorPosition(endNode, nodeSelection->cursorOffsetEndSel(), foo, endOffset); + + + if(cursorAtSelectionStart && !startDomNode.isNull()) + { + m_kafkaPart->setCurrentNode(startDomNode, startOffset); + } + else if(!cursorAtSelectionStart && !endDomNode.isNull()) + { + m_kafkaPart->setCurrentNode(endDomNode, endOffset); + } + + if(!startDomNode.isNull() && !endDomNode.isNull()) + m_kafkaPart->setSelection(DOM::Range(startDomNode, (long)startOffset, endDomNode, (long)endOffset)); +} + +void KafkaDocument::setCursor(Node* cursorNode, int cursorOffset) +{ + DOM::Node domNode; + long longDomNodeOffset; + translateNodeIntoKafkaCursorPosition(cursorNode, cursorOffset, domNode, longDomNodeOffset); + if (!domNode.isNull() && domNode.nodeType() != DOM::Node::TEXT_NODE && + !domNode.firstChild().isNull() && domNode.firstChild().nodeType() == DOM::Node::TEXT_NODE) + domNode = domNode.firstChild(); + if (!domNode.isNull()) + m_kafkaPart->setCurrentNode(domNode, (int)longDomNodeOffset); +} + +bool KafkaDocument::buildKafkaNodeFromNode(Node *node, bool insertNode) +{ +#ifdef LIGHT_DEBUG + if(node) + kdDebug(25001)<< "KafkaDocument::buildKafkaNodeFromNode() " << endl; +#endif + DOM::Node newNode, newNode2, attr, nextNode, parentNode, *ptDomNode; + bool removeLeftWhitespaces, removeRightWhitespaces; + QString str, nodeValue; + Node *n, *parent; + int i; + +// Don't create DOM::Nodes from Quanta empty nodes outside the body or inside other not allowed element, or KHTML +// will give us problems. + bool canInsertEmptyNode = false; + if(node->tag->type == Tag::Empty) + { + if(!m_currentDoc->defaultDTD()->name.contains("HTML", false)) + canInsertEmptyNode = true; + else + canInsertEmptyNode = kafkaCommon::hasParent(node, "body"); + + Node* parent_node = node->parent; + QTag* parent_node_description_tag = QuantaCommon::tagFromDTD(parent_node); + if(parent_node_description_tag && !parent_node_description_tag->isChild(node, false, true)) + canInsertEmptyNode = false; + } + + if(node->tag->type == Tag::XmlTag || + ((node->tag->type == Tag::Text || (node->tag->type == Tag::Empty && canInsertEmptyNode)) && !node->insideSpecial)) + { + str = node->tag->name.lower(); + + //The basics DOM::Nodes HTML, HEAD and BODY are loaded anyway, but we must now + // link the real HTML,... to their Nodes. + //A basic Common tree is !doctype<-html<-(head, body) + if(!node->parent) + {//FIXME:html, head and body are HTML-specific tag, for others DTDs it might result to some pbs. + if(str == "html") + { + if(!html.isNull())//delete the empty Node + disconnectDomNodeFromQuantaNode(html); + newNode = html; + insertNode = false; + } + else if(str == "body") + { + if(!body.isNull()) + disconnectDomNodeFromQuantaNode(body); + newNode = body; + insertNode = false; + } + else if(str == "head") + { + if(!head.isNull()) + disconnectDomNodeFromQuantaNode(head); + newNode = head; + insertNode = false; + } + else + { + if(node->tag->type == Tag::Text || node->tag->type == Tag::Empty) + { + newNode = kafkaCommon::createTextDomNode("", m_kafkaPart->document()); + } + else + { + newNode = kafkaCommon::createDomNode(node, m_kafkaPart->document()); + } + } + } + else if(str == "html" && (!node->parent || (node->parent && !node->parent->parent))) + { + if(!html.isNull())//delete the empty Node + disconnectDomNodeFromQuantaNode(html); + newNode = html; + insertNode = false; + } + else if(str == "body" && ((node->parent && !node->parent->parent) || (node->parent && + node->parent->parent && !node->parent->parent->parent))) + { + if(!body.isNull()) + disconnectDomNodeFromQuantaNode(body); + newNode = body; + insertNode = false; + } + else if(str == "head" && ((node->parent && !node->parent->parent) || (node->parent && + node->parent->parent && !node->parent->parent->parent))) + { + if(!head.isNull()) + disconnectDomNodeFromQuantaNode(head); + newNode = head; + insertNode = false; + } + /** else if(node->parent->tag->str == "html")*/ + else + { + if(node->tag->type == Tag::Text || node->tag->type == Tag::Empty) + { + newNode = kafkaCommon::createTextDomNode("", m_kafkaPart->document()); + } + else + { + newNode = kafkaCommon::createDomNode(node->tag->name, m_currentDoc->defaultDTD(), + m_kafkaPart->document()); + } + } + + if(newNode.isNull()) + { +#ifdef LIGHT_DEBUG + kdDebug(25001)<< "KafkaDocument::buildKafkaNodeFromNode() - ERROR null newNode" << endl; +#endif + return false; + } + + connectDomNodeToQuantaNode(newNode, node); + if(node->tag->type == Tag::Text || node->tag->type == Tag::Empty) + { + //Now we get if the whitespaces at the left and right are kept or not. + //Left whitespaces are removed if: + //- It is the first child of a BLOCK + //- Previous Node(skipping inlines) is a text with spaces at the end + //Right whitespaces are removed if: + //- It is the last child of a BLOCK + removeLeftWhitespaces = false; + n = node; + parent = node->parent; + while(parent && parent->SFirstChild() == n) + { + if(!kafkaCommon::isInline(parent->tag->name)) + { + removeLeftWhitespaces = true; + break; + } + n = parent; + parent = parent->parent; + } + if(!removeLeftWhitespaces) + { + n = node; + n = kafkaCommon::getPrevNodeNE(n); + while(n && (n->tag->type == Tag::XmlTagEnd || + (n->tag->type == Tag::XmlTag && kafkaCommon::isInline(n->tag->name) && + !n->tag->single))) + n = kafkaCommon::getPrevNodeNE(n); + if(n && n->tag->type == Tag::Text) + { + nodeValue = n->tag->tagStr(); + if(nodeValue.length() > 0 && nodeValue[nodeValue.length() - 1].isSpace()) + removeLeftWhitespaces = true; + } + + } + + removeRightWhitespaces = false; + n = node; + parent = node->parent; + while(parent && parent->SLastChild() == n) + { + if(!kafkaCommon::isInline(parent->tag->name)) + { + removeRightWhitespaces = true; + break; + } + n = parent; + parent = parent->parent; + } + + nodeValue = node->tag->tagStr(); + nodeValue = getDecodedText(nodeValue, !kafkaCommon::hasParent(node, "pre"), + removeLeftWhitespaces, removeRightWhitespaces); + newNode.setNodeValue(nodeValue); + } + + for(i = 0; i < node->tag->attrCount(); i++) + { + attr = kafkaCommon::createDomNodeAttribute(node, node->tag->attribute(i), + m_kafkaPart->document()); + if(!attr.isNull()) + { + //TODO: create a createAttr function and add this (setNodeValue sometimes + //don't like null QString) + if(!node->tag->attributeValue(i).isNull()) + attr.setNodeValue(node->tag->attributeValue(i)); + kafkaCommon::insertDomNodeAttribute(newNode, attr); + } + } + + if(node->next && node->next->tag && node->next->tag->name == + ("/" + node->tag->name)) + { + //DEPRECATED, NO USE TO CHANGE THE above check + node->_closingNode = node->next; + } + + if(insertNode) + { + ptDomNode = new DOM::Node(newNode); + node->setRootNode(ptDomNode); + n = node; + while(n->next) + { + n = n->next; + if(n->rootNode()) + { + nextNode = *n->rootNode(); + break; + } + } + if(node->parent && node->parent->leafNode()) + parentNode = *node->parent->leafNode(); + else if(node->parent && !node->parent->leafNode()) + { + //the parent tag was invalid and khtml refuse to insert it + //so impossible to inser the current node + disconnectDomNodeFromQuantaNode(newNode); + if(node->rootNode()) + delete node->rootNode(); + node->setRootNode(0L); + return false; + } + else + parentNode = body; + + //Set the visual enhancements. + ptDomNode = new DOM::Node(newNode); + node->setLeafNode(ptDomNode); + mainEnhancer->enhanceNode(node, parentNode, nextNode); + + QTag* qTag = QuantaCommon::tagFromDTD(getCurrentDoc()->defaultDTD(), + parentNode.nodeName().string()); + + if(qTag && qTag->isChild(node, false)) + { + + if(nextNode.isNull()) + { + if(!kafkaCommon::insertDomNode(newNode, parentNode)) + { + disconnectDomNodeFromQuantaNode(newNode); + if(node->rootNode()) + delete node->rootNode(); + node->setRootNode(0L); + return false; + } + } + else + { + if(!kafkaCommon::insertDomNode(newNode, parentNode, nextNode)) + { + disconnectDomNodeFromQuantaNode(newNode); + if(node->rootNode()) + delete node->rootNode(); + node->setRootNode(0L); + return false; + } + } + } + } + else + { + ptDomNode = new DOM::Node(newNode); + node->setRootNode(ptDomNode); + ptDomNode = new DOM::Node(newNode); + node->setLeafNode(ptDomNode); + } + } + else + { + if(node->parent && node->parent->leafNode()) + parentNode = *node->parent->leafNode(); + else + parentNode = body; + n = node; + while(n->next) + { + n = n->next; + if(n->rootNode()) + { + nextNode = *n->rootNode(); + break; + } + } + mainEnhancer->enhanceNode(node, parentNode, nextNode); + } + return true; +} + +void KafkaDocument::buildNodeFromKafkaNode(Node *node, DOM::Node domNode) +{ +#ifdef LIGHT_DEBUG + kdDebug(25001)<< "void KafkaDocument::buildNodeFromKafkaNode() - Node* DOM::Node" << endl; +#endif + long i; + + if(!node) return; + + node->tag->setCleanStrBuilt(false); + node->tag->setIndentationDone(false); + if(domNode.nodeType() == DOM::Node::TEXT_NODE) + { + QString text = domNode.nodeValue().string(); + text.replace("<", "<"); + text.replace(">", ">"); + node->tag->setStr(text); + } + else + { + while(node->tag->attrCount()) + node->tag->deleteAttribute(0); + for(i = 0; (unsigned)i < domNode.attributes().length(); i++) + { + TagAttr attr; + attr.name = domNode.attributes().item(i).nodeName().string(); + attr.value = domNode.attributes().item(i).nodeValue().string(); + attr.quoted = true; + node->tag->addAttribute(attr); + } + } +} + +Node * KafkaDocument::buildNodeFromKafkaNode(DOM::Node domNode, Node *nodeParent, + Node *beginNode, int beginOffset, Node */*endNode*/, int endOffset, NodeModifsSet *modifs) +{ +#ifdef LIGHT_DEBUG + kdDebug(25001)<< "Node* KafkaDocument::buildNodeFromKafkaNode() - DOM::Node 2xNode* int: " << + beginOffset << " Node* int: " << endOffset << " NodeModifsSet " << endl; +#else + Q_UNUSED(beginOffset); + Q_UNUSED(endOffset); +#endif + DOM::Node *ptDomNode; + Node *node; + + if(domNode.isNull()) + { + kdDebug(25001)<< "Node* KafkaDocument::buildNodeFromKafkaNode(DOM::Node, 2xNode*)" << + " *ERROR* - empty _domNode"<< endl; + } + + //nodeParent can be the false body node which is not in the tree. + if(nodeParent->tag->notInTree()) + nodeParent = 0L; + + /**_node = new Node(_nodeParent);*/ + if(domNode.nodeType() == DOM::Node::TEXT_NODE) + { + node = kafkaCommon::createNode("#text", "",Tag::Text, m_currentDoc); + } + else + { + node = kafkaCommon::createNode(domNode.nodeName().string(), "", + Tag::XmlTag, m_currentDoc); + } + buildNodeFromKafkaNode(node, domNode); + + connectDomNodeToQuantaNode(domNode, node); + + ptDomNode = new DOM::Node(domNode); + node->setRootNode(ptDomNode); + ptDomNode = new DOM::Node(domNode); + node->setLeafNode(ptDomNode); + + kafkaCommon::insertNode(node, nodeParent, beginNode, beginNode, modifs, false); + + return node; +} + +QString KafkaDocument::getDecodedChar(const QString &encodedChar) +{ + QMap<QString, QString>::Iterator it = encodedChars.find(encodedChar); + if(it == encodedChars.end()) + { + //try this + return KGlobal::charsets()->resolveEntities(encodedChar);; + } + return it.data(); +} + +QString KafkaDocument::getDecodedText(const QString &a_encodedText, bool translateWhiteSpacesAndLineBreaks, + bool removeLeftWhitespaces, bool removeRightWhitespaces) +{ + QString encodedText = a_encodedText; + if (encodedText.isEmpty()) + encodedText = " "; + QString decodedChar; + int i, j; +#ifdef LIGHT_DEBUG + QString oldEncodedText = encodedText; +#endif + + i = -1; + while((unsigned)++i < encodedText.length() && translateWhiteSpacesAndLineBreaks) + { + if(encodedText[i].isSpace()) + { + encodedText.remove(i, 1); + encodedText.insert(i, " "); + while((unsigned)++i < encodedText.length() && encodedText[i].isSpace()) + { + encodedText.remove(i, 1); + i--; + } + } + } + i = -1; + while((unsigned)++i < encodedText.length()) + { + if(QString(encodedText[i]) == "&") + { + j = i; + while((unsigned)++i < encodedText.length() && + QString(encodedText[i]) != ";") + {} + decodedChar = getDecodedChar(encodedText.mid(j, i - j + 1)); + encodedText.remove(j, i - j + 1); + //TODO:set a special behavior if the encoded symbol doesn't exist + encodedText.insert(j, decodedChar); + i = j + decodedChar.length() - 1; + } + } + + if(translateWhiteSpacesAndLineBreaks && removeLeftWhitespaces && encodedText.length() > 0 && + encodedText[0].isSpace()) + encodedText.remove(0, 1); + + if(translateWhiteSpacesAndLineBreaks && removeRightWhitespaces && encodedText.length() > 0 && + encodedText[encodedText.length() - 1].isSpace()) + encodedText.remove(encodedText.length() - 1, 1); + +#ifdef LIGHT_DEBUG + kdDebug(25001)<< "KafkaDocument::getDecodedText() - \"" << oldEncodedText << "\" -> \"" << + encodedText << "\"" << endl; +#endif + return encodedText; + //return KGlobal::charsets()->resolveEntities(encodedText); =>nice but not sufficient +} + +QString KafkaDocument::getEncodedChar(const QString &decodedChar, const QString &previousDecodedChar) +{ + if(decodedChar[0].isSpace() && !previousDecodedChar[0].isSpace()) + return " "; + else if(decodedChar[0].isSpace()) + //FIXME:for an unknown reason, by default it return ¿ instead of + return " "; + QMap<QString, QString>::Iterator it = decodedChars.find(decodedChar); + if(it == decodedChars.end()) + return decodedChar; + + return it.data(); + } + +QString KafkaDocument::getEncodedText(const QString &a_decodedText, int bLine, int bCol, int &eLine, int &eCol, + bool translateWhiteSpaces) +{ + QString decodedText = a_decodedText; + QString Encodedchar; + QString decodedChar, previousDecodedChar; +#ifdef LIGHT_DEBUG + QString oldDecodedText = decodedText; +#endif + int i; +#ifdef LIGHT_DEBUG + int _bLine = bLine, _bCol = bCol; +#endif + + i = -1; + while((unsigned)++i < decodedText.length()) + { + previousDecodedChar = decodedChar; + decodedChar = QString(decodedText[i]); + + if(translateWhiteSpaces || !decodedText[i].isSpace()) + Encodedchar = getEncodedChar(QString(decodedText[i]), + (i>=1)?previousDecodedChar:QString("")); + else + Encodedchar = decodedChar; + bCol += Encodedchar.length(); + + decodedText.remove(i,1); + decodedText.insert(i, Encodedchar); + i += Encodedchar.length() - 1; + } + eLine = bLine; + eCol = bCol - 1; +#ifdef LIGHT_DEBUG + kdDebug(25001)<< "KafkaDocument::getEncodedText() - " << oldDecodedText << " -> " << decodedText << + " - " << _bLine << ":" << _bCol << " - " << eLine << ":" << eCol << endl; +#endif + return decodedText; +} + +QString KafkaDocument::getEncodedText(const QString& decodedText) +{ + int a = 0, b = 0, c, d; + return getEncodedText(decodedText, a, b, c, d); +} + +QString KafkaDocument::generateCodeFromNode(Node *node, int bLine, int bCol, int &eLine, int &eCol, bool encodeText) +{ + QString text, _char; + Node *openingNode; + int bLineAttr, bColAttr; + int j = 0; + bool hasPreParent; + + if(!node) return ""; + + if(node->tag->type == Tag::XmlTag) + { + text = "<" + QuantaCommon::tagCase(node->tag->name); + bCol += node->tag->name.length() + 1; + for(j = 0; j < node->tag->attrCount(); j++) + { + text += " "; + bCol++; + bLineAttr = bLine; + bColAttr = bCol; + text += node->tag->attribute(j); + bCol += node->tag->attribute(j).length(); + + //doctype have only attrNames. + if(node->tag->name.lower() != "!doctype" && !node->tag->getAttribute(j).special) + { + text += "="; + bCol++; + if(node->tag->isQuotedAttribute(j)) + { + text += qConfig.attrValueQuotation; + bCol++; + } + node->tag->setAttributePosition(j, bLineAttr, bColAttr, bLine, bCol); + text += node->tag->attributeValue(j); + bCol += node->tag->attributeValue(j).length(); + if(node->tag->isQuotedAttribute(j)) + { + text += qConfig.attrValueQuotation; + bCol++; + } + } + else + node->tag->setAttributePosition(j, bLineAttr, bColAttr, -2, -2); + } + + //only single Nodes except !doctype and ?xml nodes in XML tag style get the "/" + if ( node->tag->dtd()->singleTagStyle == "xml" && + (node->tag->single || (!qConfig.closeOptionalTags && + QuantaCommon::isOptionalTag(node->tag->dtd()->name, node->tag->name))) + && node->tag->name.lower() != "?xml" && node->tag->name.lower() != "!doctype") + { + text += " /"; + bCol += 2; + } + //?xml nodes get a "?" + if(node->tag->name.lower() == "?xml") + { + text += "?"; + bCol++; + } + + text += ">"; + eCol = bCol; + eLine = bLine; + } + else if(node->tag->type == Tag::XmlTagEnd) + { + openingNode = node->getOpeningNode(); + if(openingNode && openingNode->tag->type == Tag::ScriptTag) + { + if(openingNode->tag->name.contains("XML PI", false) || + openingNode->tag->name.contains("PHP", false)) + text = "?>"; + else if(openingNode->tag->name.contains("DTD", false)) + text = ">"; + else + text = ">"; + } + else + { + if (node->tag->tagStr() == "-->") + text = "-->"; + else + text = "<" + QuantaCommon::tagCase(node->tag->name) + ">"; + } + bCol += text.length(); + eCol = bCol - 1; + eLine = bLine; + } + else if(node->tag->type == Tag::Text) + { + hasPreParent = kafkaCommon::hasParent(node, "pre"); + if(encodeText) + text = getEncodedText(node->tag->tagStr(), bLine, bCol, eLine, eCol, + !hasPreParent); + /** Can't use KGlobal::charsets()->toEntity() : + * It translate all chars into entities! */ + else if(!hasPreParent) + text = node->tag->tagStr().replace(QRegExp("\\s+"), " "); + else + text = node->tag->tagStr(); + } + else if(node->tag->type == Tag::ScriptTag) + { + //WARNING : HTML SPECIFIC + if(node->tag->name.contains("style", false)) + { + text = "<" + QuantaCommon::tagCase("style") + ">"; + } + else if(node->tag->name.contains("DTD", false)) + { + text = "<!"; + } + else if(node->tag->name.contains("XML PI", false)) + { + text = "<?xml"; + } + else if(node->tag->name.contains("PHP", false)) + { + text = "<?php"; + } + bCol += text.length(); + eCol = bCol - 1; + eLine = bLine; + } + else + { + //default behavior : return node->tag->tagStr() + text = node->tag->tagStr(); + kafkaCommon::getEndPosition(text, bLine, bCol, eLine, eCol); + } + return text; +} + +void KafkaDocument::translateQuantaIntoKafkaCursorPosition(uint curLine, uint curCol, DOM::Node &domNode, long &offset) +{ + Node *node; + int bCol, bLine, eCol, eLine, col, line; + QString curChar, decodedChar, currentLine; + bool lookForEntity, lookForSpaces, found; + + node = baseNode; + while(node) + { + node->tag->beginPos(bLine, bCol); + node->tag->endPos(eLine, eCol); + if((bLine < (signed)curLine || (bLine == (signed)curLine && bCol <= (signed)curCol)) && + (eLine > (signed)curLine || (eLine == (signed)curLine && eCol >= (signed)curCol))) + { + if(bLine == (signed)curLine && bCol == (signed)curCol && node->tag->type != Tag::Text && + node->previousSibling() && node->previousSibling()->tag->type == Tag::Text) + { + //if we are at the end of a text which is recognized as the beginning of the next tag + node = node->previousSibling(); + //we can go directly to the last offset + if(!node->rootNode()) + { + offset = 0; + kdDebug(25001)<< "KafkaDocument::getKafkaCursorPosition() - ERROR DOM::Node not found" + << endl; + return; + } + domNode = *node->rootNode(); + offset = domNode.nodeValue().string().length(); +#ifdef LIGHT_DEBUG + kdDebug(25001)<< "KafkaDocument::getKafkaCursorPosition() - " << + domNode.nodeName().string() << ":" << offset << endl; +#endif + return; + } + col = bCol; + line = bLine; + break; + } + node = node->nextSibling(); + + } + if(!node) + { + kdDebug(25001)<< "KafkaDocument::getKafkaCursorPosition() - ERROR node not found" << endl; + return; + } +#ifdef LIGHT_DEBUG + kdDebug(25001)<< "KafkaDocument::getKafkaCursorPosition() - node :" << node->tag->name << ": type:" << + node->tag->type << ": pos " << bLine << ":" << bCol << " - " << eLine << ":" << eCol << endl; +#endif + + offset = 0; + if(node->rootNode() && node->rootNode()->nodeType() == DOM::Node::TEXT_NODE) + { + domNode = *node->rootNode(); + currentLine = m_currentDoc->editIf->textLine(line); + if(line < eLine) + currentLine += " ";//remplace the \n + while(line < (signed)curLine || (col < (signed)curCol && line == (signed)curLine)) + { + lookForEntity = false; + lookForSpaces = false; + curChar = currentLine.mid(col, 1); + if(curChar == "&") + lookForEntity = true; + else if(curChar[0].isSpace()) + lookForSpaces = true; + found = false; + while(!found) + { + if((lookForEntity && curChar == ";") || + !(lookForSpaces || lookForEntity)) + found = true; + else if(lookForSpaces && !curChar[0].isSpace())//curChar != " ") + break; + if((col + 1) >= (signed)currentLine.length()) + { + line++; + col = 0; + currentLine = m_currentDoc->editIf->textLine(line); + if(line < eLine) + currentLine += " ";//remplace the \n + } + else col++; + curChar = currentLine.mid(col, 1); + } +#ifdef HEAVY_DEBUG + kdDebug(25001)<< "endpos at " << line << ":" << col << endl; +#endif + offset++; + } + } + else if(node->rootNode()) + offset = 0;//shoud we select? + else + m_kafkaPart->putCursorAtFirstAvailableLocation();//set the cursor in the first text +#ifdef LIGHT_DEBUG + if(!domNode.isNull()) + kdDebug(25001)<< "KafkaDocument::getKafkaCursorPosition() - " << domNode.nodeName().string() << + ":" << offset << endl; + else + kdDebug(25001)<< "KafkaDocument::getKafkaCursorPosition() - NULL domNode" << endl; +#endif +} + +void KafkaDocument::translateQuantaIntoNodeCursorPosition(uint line, uint col, Node **node, long &offset) +{ + int curCol, curLine, beginCol, beginLine; + QString currentLine; + + *node = parser->nodeAt(line, col, false); + + offset = 0; + if(!*node) + return; + + if((*node)->tag->cleanStrBuilt() && (*node)->tag->indentationDone()) + { + (*node)->tag->beginPos(beginLine, beginCol); + curLine = beginLine; + curCol = beginCol; + while(curLine < (signed)line) + { + currentLine = ViewManager::ref()->activeDocument()->editIf->textLine(curLine); + if(curLine == beginLine) + offset += (signed)currentLine.length() - beginCol; + else + offset += (signed)currentLine.length(); + offset++; + curLine++; + } + if(beginLine != (signed)line) + offset += col; + else + offset += col - beginCol; + } + else + { + //TODO + } +#ifdef LIGHT_DEBUG + kdDebug(25001)<< "KafkaDocument::translateQuantaIntoNodeCursorPosition() - " << *node << + ":" << offset << endl; +#endif +} + +void KafkaDocument::translateKafkaIntoNodeCursorPosition(DOM::Node domNode, long domNodeOffset, Node **node, long &offset) +{ + QString decodedText, encodedChar, encodedText, currentChar; + QChar curChar, oldChar; + long currentOffset; + bool waitForSpace = false, found = false; + int curNodeOffset, bLine = 0, bCol = 0, eLine, eCol; + + offset = 0; + (*node) = 0L; + + if(domNode.isNull()) + { +#ifdef HEAVY_DEBUG + kdDebug(25001)<< "KafkaDocument::translateKafkaIntoNodeCursorPosition()" << + " - DOM::Node not found!" << endl; +#endif + return ; + } + + //get the corresponding Node* + (*node) = getNode(domNode); + if(!(*node)) + { +#ifdef HEAVY_DEBUG + kdDebug(25001)<< "KafkaDocument::translateKafkaIntoNodeCursorPosition()" << + " - Node not found!" << endl; +#endif + return ; + } + if(!(*node)->tag) + { +#ifdef HEAVY_DEBUG + kdDebug(25001)<< "KafkaDocument::translateKafkaIntoNodeCursorPosition()" << + " - Node tag not found!" << endl; +#endif + (*node) = 0L; + return ; + } + + //If this node is selected (-1 means selected) then return "node selected" ;-) + if(domNodeOffset == -1) + { + return ; + } + + if(!(*node)->tag->cleanStrBuilt()) + { + //We NEED to have the up-to-date string in node. + (*node)->tag->setStr(generateCodeFromNode((*node), bLine, bCol, eLine, eCol)); + //FIXME we shouldn't set it but if we don't the text will be re-encoded! + (*node)->tag->setCleanStrBuilt(true); + } + + decodedText = domNode.nodeValue().string(); + encodedText = (*node)->tag->tagStr(); + currentOffset = domNodeOffset; + curNodeOffset = 0; + +#ifdef HEAVY_DEBUG + kdDebug(25001)<< "KafkaDocument::translateKafkaIntoNodeCursorPosition() - decodedText:"<< + decodedText << ": encoded text:" << encodedText << ":" << endl; +#endif + + if((*node)->tag->type == Tag::Text) + { + while(currentOffset > 0) + { + curChar = decodedText.at(domNodeOffset - currentOffset); +#ifdef HEAVY_DEBUG + //kdDebug(24000)<< "CurChar:" << QString(curChar) << ": oldChar:" << + // QString(oldChar) << endl; +#endif + encodedChar = getEncodedChar(QString(curChar), QString(oldChar)); + if(encodedChar == " ") + encodedChar = " "; + found = false; + waitForSpace = false; + while(!found) + { +#ifdef HEAVY_DEBUG + //kdDebug(25001)<< "look 4 :" << encodedChar << ": found :" << + // encodedText.mid(curNodeOffset, encodedChar.length()) << endl; +#endif + if(encodedChar != " " && encodedChar == encodedText.mid(curNodeOffset, + encodedChar.length())) + found = true; + else if(encodedChar == " " && encodedText.mid(curNodeOffset, 1).at(0).isSpace()) + waitForSpace = true; + else if(waitForSpace) + break;//no more spaces + else if(encodedChar == " " && encodedText.mid(curNodeOffset, 6) == " ") + { + encodedChar = " "; + found = true; + } + + if(curNodeOffset > (signed)encodedText.length() || encodedChar.isEmpty()) + { + //The good nodeOffset was not found. Returns a default value. + //encodedChar.isEmpty() -->prevent an infinite loop + curNodeOffset = 0; + break; + } + else + curNodeOffset += encodedChar.length(); + } + oldChar = curChar; + currentOffset--; + } + } + +#ifdef LIGHT_DEBUG + kdDebug(25001)<<"KafkaDocument::translateKafkaIntoNodeCursorPosition() - " << + curNodeOffset << endl; +#endif + offset = curNodeOffset; +} + +void KafkaDocument::translateKafkaIntoQuantaCursorPosition(DOM::Node _currentDomNode, int offset, int &line, int &col) +{ + Node *_currentNode, *closingNode, *node; + kNodeAttrs *attrs; + QString decodedText, encodedChar, currentLine, currentChar; + QChar curChar, oldChar; + int currentOffset; + int curLine, curCol, endLine, endCol; + int i; + bool waitForSpace = false, found = false; + bool tagLeft = false, tagRight = false, tagMiddle = false; + bool specialBehavior = false; + + //m_kafkaPart->getCurrentNode(_currentDomNode, offset); + currentOffset = offset; + + if(_currentDomNode.isNull()) + { + kdDebug(25001)<< "KafkaDocument::getQuantaCursorPosition() - DOM::Node not found!" << endl; + line = 0; + col = 0; + return; + } + + attrs = getAttrs(_currentDomNode); + if(!attrs) + { + kdDebug(25001)<< "KafkaDocument::getQuantaCursorPosition() - Attrs not found!" << endl; + line = 0; + col = 0; + return; + } + + //If the current DOM::Node has a special behavior (cf nodeproperties.h), get the nearest + //node which can get the focus! + if(attrs->specialBehavior() != kNodeAttrs::none) + { + specialBehavior = true; + if(attrs->specialBehavior() == kNodeAttrs::emptyTextSurroundingBlockElementAtTheLeft) + { + _currentDomNode = _currentDomNode.nextSibling(); + //tagRight means to put the cursor at the left of the tag so that the cursor + //looks at its right place (otherwise as there is no Node for this DOM::Node, + //the cursor won't go anywhere!) + tagLeft = true; + } + else if(attrs->specialBehavior() == kNodeAttrs::emptyTextSurroundingBlockElementAtTheRight) + { + _currentDomNode = _currentDomNode.previousSibling(); + tagRight = true; + } + else if(attrs->specialBehavior() == kNodeAttrs::emptyTextAsChildOfAChildlessElement) + { + _currentDomNode = _currentDomNode.parentNode(); + tagMiddle = true; + } + attrs = getAttrs(_currentDomNode); + if(!attrs) + { + kdDebug(25001)<< "KafkaDocument::getQuantaCursorPosition() - Attrs not found!" << endl; + line = 0; + col = 0; + return; + } + } + + _currentNode = attrs->getNode(); + if(!_currentNode /* && attrs->specialBehavior() == kNodeAttrs::none -- Andras: what happens if _currentNode is null, but this part of the if is not true?*/) + { + kdDebug(25001)<< "KafkaDocument::getQuantaCursorPosition() - Node not found!" << endl; + line = 0; + col = 0; + return; + } + + decodedText = _currentDomNode.nodeValue().string(); + _currentNode->tag->beginPos(curLine, curCol); + _currentNode->tag->endPos(endLine, endCol); + currentLine = m_currentDoc->editIf->textLine(curLine); + if(curLine < endLine) + currentLine += " ";//remplace the \n + + if(offset == -1) + { + if (m_currentDoc->selectionIf) + m_currentDoc->selectionIf->setSelection(curLine, curCol, endLine, endCol + 1); + line = endLine; + col = endCol + 1; + return; + } +#ifdef HEAVY_DEBUG +// kdDebug(25001)<< "KafkaDocument::getQuantaCursorPosition() - decodedText:"<< decodedText << ":" << endl; +#endif + if(_currentNode->tag->type == Tag::Text) + { + while(currentOffset > 0) + { + curChar = decodedText[offset - currentOffset]; +#ifdef HEAVY_DEBUG +// kdDebug(24000)<< "CurChar:" << QString(curChar) << ": oldChar:" << QString(oldChar) << endl; +#endif + encodedChar = getEncodedChar(QString(curChar), QString(oldChar)); + if(encodedChar == " ") + encodedChar = " "; + found = false; + waitForSpace = false; + while(!found) + { +#ifdef HEAVY_DEBUG +// kdDebug(25001)<< "look 4 :" << encodedChar << ": found :" << +// currentLine.mid(curCol, encodedChar.length()) << endl; +// kdDebug(25001)<< "pos " << curLine << ":" << curCol << endl; +// kdDebug(25001)<< "endPos " << endLine << ":" << endCol << endl; +#endif + if(encodedChar != " " && encodedChar == currentLine.mid(curCol, encodedChar.length())) + found = true; + else if(encodedChar == " " && currentLine.mid(curCol, 1).at(0).isSpace()) + waitForSpace = true; + else if(waitForSpace) + break;//no more spaces + else if(encodedChar == " " && currentLine.mid(curCol, 6) == " ") + { + encodedChar = " "; + found = true; + } + if((curCol > endCol && curLine == endLine) || curLine > endLine || + encodedChar.length() == 0) + { + //encodedChar.length() == 0 =>prevent infinite loop + curLine = endLine; + curCol = endCol + 1; + currentOffset = 0; + break; + } + else if((curCol + encodedChar.length()) >= currentLine.length()) + { + ++curLine; + curCol = 0; + currentLine = m_currentDoc->editIf->textLine(curLine); + if(curLine < endLine) + currentLine += " ";//remplace the \n + } + else + { + curCol += encodedChar.length(); + } + } + oldChar = curChar; + --currentOffset; + } + } + else if(_currentNode->tag->type == Tag::XmlTag) + { + //If we are in the special case + if(specialBehavior) + { + if(tagLeft) + _currentNode->tag->beginPos(curLine, curCol); + else if(tagRight) + { + closingNode = _currentNode->getClosingNode(); + if(closingNode) + closingNode->tag->endPos(curLine, curCol); + else + _currentNode->tag->endPos(curLine, curCol); + ++curCol; + } + else + { + _currentNode->tag->endPos(curLine, curCol); + ++curCol; + } + } + else + { + if(_currentNode->tag->single) + { + //The tag is single, there shouldn't be any (tag, x) location, but the old way + //was (tag,0) for left of the tag and (tag,1) for the right of the tag) + + if(offset == 1) + { + _currentNode->tag->endPos(curLine, curCol); + curCol++; + } + else + _currentNode->tag->beginPos(curLine, curCol); + } + else + { + //The new way to define cursor position. + node = _currentNode->SFirstChild(); + i = 1; + while(i < offset && node && node->SNext()) + { + node = node->SNext(); + ++i; + } + if(!node) + node = _currentNode; + if(offset == 0) + node->tag->beginPos(curLine, curCol); + else + { + closingNode = node->getClosingNode(); + if(closingNode) + closingNode->tag->endPos(curLine, curCol); + else + node->tag->endPos(curLine, curCol); + ++curCol; + } + } + } + } + + line = curLine; + col = curCol; +#ifdef LIGHT_DEBUG + kdDebug(25001)<<"KafkaDocument::getQuantaCursorPosition() - " << line << ":" << col << endl; +#endif + return; +} + +void KafkaDocument::translateNodeIntoKafkaCursorPosition(Node *node, int offset, DOM::Node &domNode, + long &domNodeOffset) +{ + QString textNode, curChar; + int col; + bool lookForEntity, lookForSpaces, found; + + if(node && node->rootNode() && node->rootNode()->nodeType() == DOM::Node::TEXT_NODE) + { + domNodeOffset = 0; + domNode = *node->rootNode(); + textNode = node->tag->tagStr(); + col = 0; + while(col < offset) + { + lookForEntity = false; + lookForSpaces = false; + curChar = textNode.mid(col, 1); + if(curChar == "&") + lookForEntity = true; + else if(curChar[0].isSpace()) + lookForSpaces = true; + found = false; + while(!found && col < offset) + { + if((lookForEntity && curChar == ";") || + !(lookForSpaces || lookForEntity)) + found = true; + else if(lookForSpaces && !curChar[0].isSpace())//curChar != " ") + break; + col++; + curChar = textNode.mid(col, 1); + } +#ifdef HEAVY_DEBUG + //kdDebug(25001)<< "endpos at " << line << ":" << col << endl; +#endif + domNodeOffset++; + } + } + else if(node && node->rootNode()) + { + domNode = *node->rootNode(); + domNodeOffset = 0;//shoud we select? + } + else + { + domNode = DOM::Node(); + domNodeOffset = 0; + } +} + +void KafkaDocument::translateNodeIntoQuantaCursorPosition(Node *node, int offset, uint &line, uint &col) +{ + int curCol, curLine, curOffset; + + node->tag->beginPos(curLine, curCol); + line = curLine; + col = curCol; + curOffset = offset; + while(curOffset > 0) + { + if(node->tag->tagStr()[offset - curOffset] == '\n') + line++; + else + col++; + curOffset--; + } +} + +bool KafkaDocument::insertDomNode(DOM::Node node, DOM::Node parent, + DOM::Node nextSibling, DOM::Node rootNode) +{ + DOM::Node siblingNSpecial; + + //First insert the node + if(!kafkaCommon::insertDomNode(node, parent, nextSibling, rootNode)) + return false; + + //Then unEnhance and reEnhance the nearest non special nodes so that everything's fine. + siblingNSpecial = getPrevSiblingNSpecial(node); + if(!siblingNSpecial.isNull()) + { + mainEnhancer->postUnenhanceNode(siblingNSpecial); + mainEnhancer->postEnhanceNode(siblingNSpecial); + } + siblingNSpecial = getNextSiblingNSpecial(node); + if(!siblingNSpecial.isNull()) + { + mainEnhancer->postUnenhanceNode(siblingNSpecial); + mainEnhancer->postEnhanceNode(siblingNSpecial); + } + mainEnhancer->postUnenhanceNode(node.parentNode()); + mainEnhancer->postEnhanceNode(node.parentNode()); + + mainEnhancer->postEnhanceNode(node); + return true; +} + +bool KafkaDocument::removeDomNode(DOM::Node node) +{ + DOM::Node nextSiblingNSpecial, prevSiblingNSpecial, parent; + + //First remove the node + prevSiblingNSpecial = getPrevSiblingNSpecial(node); + nextSiblingNSpecial = getNextSiblingNSpecial(node); + parent = node.parentNode(); + if(!kafkaCommon::removeDomNode(node)) + return false; + + //Then unEnhance and reEnhance the nearest non special nodes so that everything's fine. + if(!prevSiblingNSpecial.isNull()) + { + mainEnhancer->postUnenhanceNode(prevSiblingNSpecial); + mainEnhancer->postEnhanceNode(prevSiblingNSpecial); + } + if(!nextSiblingNSpecial.isNull()) + { + mainEnhancer->postUnenhanceNode(nextSiblingNSpecial); + mainEnhancer->postEnhanceNode(nextSiblingNSpecial); + } + mainEnhancer->postUnenhanceNode(parent); + mainEnhancer->postEnhanceNode(parent); + + return true; +} + +DOM::Node KafkaDocument::getPrevSiblingNSpecial(DOM::Node domNode) +{ + kNodeAttrs *attrs; + + if(domNode.isNull()) + return DOM::Node(); + + domNode = domNode.previousSibling(); + while(!domNode.isNull()) + { + attrs = getAttrs(domNode); + if(!attrs) + return DOM::Node(); + if(attrs->specialBehavior() == kNodeAttrs::none) + return domNode; + domNode = domNode.previousSibling(); + } + + return DOM::Node(); +} + +DOM::Node KafkaDocument::getNextSiblingNSpecial(DOM::Node domNode) +{ + kNodeAttrs *attrs; + + if(domNode.isNull()) + return DOM::Node(); + + domNode = domNode.nextSibling(); + while(!domNode.isNull()) + { + attrs = getAttrs(domNode); + if(!attrs) + return DOM::Node(); + if(attrs->specialBehavior() == kNodeAttrs::none) + return domNode; + domNode = domNode.nextSibling(); + } + + return DOM::Node(); +} + +void KafkaDocument::readConfig(KConfig *m_config) +{ +#ifdef LIGHT_DEBUG + kdDebug(25001)<< "KafkaDocument::readConfig()" << endl; +#endif + //for each nodeEnhancer + if(mainEnhancer) + mainEnhancer->readConfig(m_config); + //reload the document + reloadDocument(); +} + +const DTDStruct* KafkaDocument::defaultDTD() +{ + return m_currentDoc->defaultDTD(); +} + +#ifdef HEAVY_DEBUG +void KafkaDocument::coutLinkTree(Node *node, int indent) +#else +void KafkaDocument::coutLinkTree(Node *, int) +#endif +{ +#ifdef HEAVY_DEBUG + QString output, dots; + DOM::Node domNode; + Node *n = 0L; + if(!node) + kdDebug(25001)<< "kafkaDocument::coutTree() - bad node!" << endl; + + while (node) + { + dots = ""; + dots.fill('*', indent); + output = dots; + if (node->tag->type != Tag::Text) + output += node->tag->name.replace('\n'," "); + else + output+= node->tag->tagStr().replace('\n'," "); + output += " ("; + output += node->tag->type; + output += ") "; + n = 0L; + if(node->rootNode()) + { + domNode = *node->rootNode(); + n = getNode(domNode); + } + + kdDebug(25001) << output <<" (" << node << ") " << domNode.handle() << " - " << n <<endl; + + if (node->child) + coutLinkTree(node->child, indent + 4); + + node = node->next; + } +#endif +} + +void KafkaDocument::slotDomNodeInserted(DOM::Node domNode, bool insertChilds, NodeModifsSet* modifs) +{ +#ifdef LIGHT_DEBUG + if(!domNode.isNull()) + kdDebug(25001)<< "KafkaDocument::slotDomNodeInserted() - DOM::Node: " << + domNode.nodeName().string() << endl; + else + kdDebug(25001)<< "KafkaDocument::slotDomNodeInserted() - DOM::Node: NULL" << endl; +#endif + Node *_nodeParent = 0L, *nodeNext = 0L, *_node = 0L; + DOM::Node tmpDomNode, nextDomNode; + bool b = false; + +#ifdef LIGHT_DEBUG + QTime t; + t.start(); +#endif + + _nodeParent = getNode(domNode.parentNode()); + + if(!_nodeParent) + {//DOM::Node not found, strange... + kdDebug(25001)<< "KafkaDocument::slotDomNodeInserted() - *ERROR* the" << + " corresponding DOM::Node is not found!" << endl; + return; + } + + nextDomNode = getNextSiblingNSpecial(domNode); + if(!nextDomNode.isNull()) + { + nodeNext = getNode(nextDomNode); + if(!nodeNext) + { + kdDebug(25001)<< "KafkaDocument::slotDomNodeInserted() - *ERROR2*" << + " the corresponding DOM::Node is not found!" << endl; + return; + } + } + + _node = buildNodeFromKafkaNode(domNode, _nodeParent, nodeNext, 0, 0L, 0, modifs); + + if(insertChilds && domNode.hasChildNodes()) + { + //TODO: check if it is working + tmpDomNode = domNode.firstChild(); + while(!tmpDomNode.isNull()) + { + buildNodeFromKafkaNode(tmpDomNode, + getNode(tmpDomNode.parentNode()), 0L, 0, 0L, 0, modifs); + tmpDomNode = kafkaCommon::getNextDomNode(tmpDomNode, b, false, domNode); + } + } + +#ifdef LIGHT_DEBUG + kdDebug(25001)<< "KafkaDocument::slotDomNodeInserted() in " << t.elapsed() << + " ms only!" << endl; +#endif +#ifdef HEAVY_DEBUG + kafkaCommon::coutTree(baseNode, 2); +#endif + +} + +void KafkaDocument::slotDomNodeModified(DOM::Node domNode, NodeModifsSet* modifs) +{ +#ifdef LIGHT_DEBUG + if(!domNode.isNull()) + kdDebug(25001)<< "KafkaDocument::slotDomNodeModified() - DOM::Node: " << + domNode.nodeName().string() << endl; + else + kdDebug(25001)<< "KafkaDocument::slotDomNodeModfied() - DOM::Node: NULL" << endl; +#endif + Node *node = 0L; + NodeModif *modif; + kNodeAttrs *props, *newProps; + DOM::Node newDomNode, parentDomNode, nextSiblingDomNode; + DOM::Node *ptDomNode; + QTag *qTag; + +#ifdef LIGHT_DEBUG + QTime t; + t.start(); +#endif + + //gets the DOM::Node's kNodeAttrs + props = domNodeProps[domNode.handle()]; + if(!props) + { + kdDebug(25001)<< "KafkaDocument::slotDomNodeModified - *ERROR " << + " missing kNodeAttrs for a DOM::Node!!!" << endl; + return; + } + + //First look if domNode has a corresponding Node. + if(props->isLinkedToNode()) + { + + //Look which Node correspond to this DOM::Node + node = props->getNode(); + + if(!node) + {//DOM::Node not found, weird... + kdDebug(25001)<< "KafkaDocument::slotDomNodeModified() - *ERROR* the" << + " corresponding DOM::Node is not found!" << endl; + return; + } + + modif = new NodeModif(); + modif->setType(NodeModif::NodeModified); + modif->setTag(new Tag(*(node->tag))); + modif->setLocation(kafkaCommon::getLocation(node)); + + buildNodeFromKafkaNode(node, domNode); + if (!modifs) + modifs = new NodeModifsSet(); + modifs->addNodeModif(modif); + } + else + { + //no corresponding Node, we are in a special case with a special behavior. + qTag = QuantaCommon::tagFromDTD(getNode(domNode.parentNode())); + if(((!domNode.parentNode().isNull() && domNode.parentNode().nodeName() == "#document") || + qTag) && ( + props->specialBehavior() == kNodeAttrs::emptyTextSurroundingBlockElementAtTheLeft || + props->specialBehavior() == kNodeAttrs::emptyTextSurroundingBlockElementAtTheRight || + props->specialBehavior() == kNodeAttrs::emptyTextAsChildOfAChildlessElement)) + { + //let's create the corresponding Text Node and the P tag only if necessary + modifs = new NodeModifsSet(); + modif = new NodeModif(); + parentDomNode = domNode.parentNode(); + nextSiblingDomNode = domNode.nextSibling(); + + if(!qTag->isChild("#text", false)) + { + newDomNode = kafkaCommon::createDomNode("p", + getNode(domNode.parentNode())->tag->dtd(), m_kafkaPart->document()); + kafkaCommon::removeDomNode(domNode); + kafkaCommon::insertDomNode(newDomNode, parentDomNode, nextSiblingDomNode); + kafkaCommon::insertDomNode(domNode, newDomNode); + + node = kafkaCommon::createNode("p", "", Tag::XmlTag, m_currentDoc); + newProps = connectDomNodeToQuantaNode(newDomNode, node); + ptDomNode = new DOM::Node(newDomNode); + node->setRootNode(ptDomNode); + ptDomNode = new DOM::Node(newDomNode); + node->setLeafNode(ptDomNode); + node = kafkaCommon::insertNode(node, getNode(parentDomNode), + getNode(nextSiblingDomNode), getNode(nextSiblingDomNode), modifs); + newProps->setNode(node); + modifs->addNodeModif(modif); + + parentDomNode = newDomNode; + nextSiblingDomNode = DOM::Node(); + modif = new NodeModif(); + } + + node = kafkaCommon::createNode("", domNode.nodeValue().string(), Tag::Text, + m_currentDoc); + ptDomNode = new DOM::Node(domNode); + node->setRootNode(ptDomNode); + ptDomNode = new DOM::Node(domNode); + node->setLeafNode(ptDomNode); + //avoid the merging of Text Nodes + node = kafkaCommon::insertNode(node, getNode(parentDomNode), + getNode(nextSiblingDomNode), modifs, false); + props->setNode(node); + modifs->addNodeModif(modif); + //If there is a empty Node after of before the text, remove them + if(node->prev && node->prev->tag->type == Tag::Empty) + kafkaCommon::extractNode(node->prev, modifs); + if(node->next && node->next->tag->type == Tag::Empty) + kafkaCommon::extractNode(node->next, modifs); + + //Log the changes + m_currentDoc->docUndoRedo->addNewModifsSet(modifs, undoRedo::KafkaModif); + } + + props->setIsLinkedToNode(true); + props->setSpecialBehavior(kNodeAttrs::none); + } + + +#ifdef LIGHT_DEBUG + kdDebug(25001)<< "KafkaDocument::slotDomNodeModified() in " << t.elapsed() << + " ms only!" << endl; +#endif + +#ifdef HEAVY_DEBUG + kafkaCommon::coutTree(baseNode, 2); +#endif +} + +void KafkaDocument::slotDomNodeAboutToBeRemoved(DOM::Node _domNode, bool deleteChilds, NodeModifsSet* modifs) +{ +#ifdef LIGHT_DEBUG + if(!_domNode.isNull()) + kdDebug(25001)<< "KafkaDocument::slotDomNodeAboutToBeRemoved() - DOM::Node: " << + _domNode.nodeName().string() << "(" << _domNode.handle() << ")"<< " bool :" << + deleteChilds << endl; + else + kdDebug(25001)<< "KafkaDocument::slotDomNodeAboutToBeRemoved() - DOM::Node: NULL bool :" << + deleteChilds << endl; +#endif + Node *_node = 0L, *_nodeNext = 0L, *_tmpNode = 0L, *n = 0L; + int i, bLine, bCol, eLine, eCol, bLine2, bCol2; + bool hasClosingNode = false, b; + NodeModif *modif; + +#ifdef LIGHT_DEBUG + QTime t; + t.start(); +#endif + + _node = getNode(_domNode); + if(!_node) + { + kdDebug(25001)<<"KafkaDocument::slotDomNodeAboutToBeRemoved() - *ERROR* the" << + " corresponding DOM::Node is not found!" << endl; + return; + } + + + //If we are deleting a PHP Node which is embedded into a tag e.g. <a <? echo boo; ?> > + //We must regenerate the <a> tag string. + if(_node->tag->type == Tag::ScriptTag && _node->parent) + { + _node->parent->tag->beginPos(bLine, bCol); + _node->parent->tag->endPos(eLine, eCol); + _node->tag->beginPos(bLine2, bCol2); + if(QuantaCommon::isBetween(bLine2, bCol2, bLine, bCol, eLine,eCol) == 0) + _node->parent->tag->setCleanStrBuilt(false); + } + + if(_node->prev) + { + //delete the previous empty tag if present + _nodeNext = _node; + _node = _node->prev; + if(_node && _node->tag->type == Tag::Empty) + { + modif = new NodeModif(); + modif->setType(NodeModif::NodeRemoved); + modif->setLocation(kafkaCommon::getLocation(_node)); + if(_node->parent && _node->parent->child == _node) + _node->parent->child = _node->next; + if(_node->prev) + _node->prev->next = _node->next; + if(_node->next) + _node->next->prev = _node->prev; + if(_node == baseNode) + baseNode = _node->next; + _node->parent = 0L; + _node->prev = 0L; + _node->next = 0L; + _node->child = 0L; + modif->setNode(_node); + modifs->addNodeModif(modif); + } + _node = _nodeNext; + } + + //delete the Node + modif = new NodeModif(); + if(deleteChilds) + modif->setType(NodeModif::NodeAndChildsRemoved); + else + modif->setType(NodeModif::NodeRemoved); + modif->setLocation(kafkaCommon::getLocation(_node)); + + if(_node->getClosingNode()) + hasClosingNode = true; + else + hasClosingNode = false; + //_node->removeAll = false; + + if(_node->parent && _node->parent->child == _node) + _node->parent->child = _node->next; + if(_node->next) + _node->next->prev = _node->prev; + if(_node->prev) + _node->prev->next = _node->next; + + i = 0; + if(_node->child && deleteChilds) + { + _tmpNode = _node->child; + b = false; + while(_tmpNode) + { + if(_tmpNode->rootNode()) + disconnectDomNodeFromQuantaNode(*_tmpNode->rootNode()); + if(_tmpNode->leafNode()) + disconnectDomNodeFromQuantaNode(*_tmpNode->leafNode()); + _tmpNode = kafkaCommon::getNextNode(_tmpNode, b, _node); + } + //delete _node->child; + } + else if(_node->child)// && !deleteChilds + { + if(_node->parent && !_node->parent->child) + _node->parent->child = _node->child; + if(_node->prev) + _node->prev->next = _node->child; + _tmpNode = _node->child; + while(_tmpNode) + { + i++; + _tmpNode->parent = _node->parent; + n = _tmpNode; + _tmpNode = _tmpNode->next; + if(!_tmpNode) + { + n->next = _node->next; + if(_node->next) + _node->next->prev = n; + } + } + } + if(_node == baseNode) + baseNode = _node->next; + + if(_node->rootNode()) + disconnectDomNodeFromQuantaNode(*_node->rootNode()); + if(_node->leafNode()) + disconnectDomNodeFromQuantaNode(*_node->leafNode()); + _node->parent = 0L; + _nodeNext = _node->next; + _node->next = 0L; + _node->prev = 0L; + _node->child = 0L; + modif->setNode(_node); + //delete _node; + modif->setChildrenMovedUp(i); + modifs->addNodeModif(modif); + _node = _nodeNext; + + if(hasClosingNode) + { + //delete the closing Node if present + if(_node->parent && _node->parent->child == _node) + _node->parent->child = _node->next; + if(_node->prev) + _node->prev->next = _node->next; + if(_node->next) + _node->next->prev = _node->prev; + _node->parent = 0L; + _nodeNext = _node->next; + _node->next = 0L; + _node->prev = 0L; + _node->child = 0L; + modif = new NodeModif(); + modif->setType(NodeModif::NodeRemoved); + modif->setLocation(kafkaCommon::getLocation(_node)); + modif->setNode(_node); + modifs->addNodeModif(modif); + //delete _node; + _node = _nodeNext; + } + + if(_node && _node->tag->type == Tag::Empty) + { + //delete the next empty tag if present + modif = new NodeModif(); + modif->setType(NodeModif::NodeRemoved); + modif->setLocation(kafkaCommon::getLocation(_node)); + if(_node->parent && _node->parent->child == _node) + _node->parent->child = _node->next; + if(_node->prev) + _node->prev->next = _node->next; + if(_node->next) + _node->next->prev = _node->prev; + _node->parent = 0L; + _nodeNext = _node->next; + _node->prev = 0L; + _node->next = 0L; + _node->child = 0L; + modif->setNode(_node); + modifs->addNodeModif(modif); + _node = _nodeNext; + } + + //NO NORMALIZATION!! It is KafkaWidget::normalize()'s job! + +#ifdef LIGHT_DEBUG + kdDebug(25001)<< "KafkaDocument::slotDomNodeDeleted() in " << t.elapsed() << + " ms only!" << endl; +#endif +#ifdef HEAVY_DEBUG + kafkaCommon::coutTree(baseNode, 2); +#endif + +} + +void KafkaDocument::slotDomNodeIsAboutToBeMoved(DOM::Node domNode, DOM::Node newParent, DOM::Node before, NodeModifsSet* modifs) +{ + Node *node, *parent, *nextSibling, *closingNode; + + if(domNode.isNull()) + return; + + node = getNode(domNode); + parent = getNode(newParent); + nextSibling = getNode(before); + + if(!node) + return; + + closingNode = node->getClosingNode(); + + kafkaCommon::moveNode(node, parent, nextSibling, modifs, false); + + if(closingNode) + kafkaCommon::moveNode(closingNode, parent, nextSibling, modifs, false); + +} + +void KafkaDocument::slotdomNodeNewCursorPos(DOM::Node, int) +{ +#ifdef LIGHT_DEBUG + kdDebug(25001)<< "KafkaDocument::slotdomNodeNewCursorPos()" << endl; +#endif + //int line, col; + //dont calculate cursor pos until the next view update + //getQuantaCursorPosition(line, col); + //emit newCursorPosition(line, col); +} + +void KafkaDocument::slotCut() +{ + QString text = m_kafkaPart->selectedText(); + + NodeSelectionInd selection_ind; + selection_ind.fillWithVPLCursorSelection(); + + int startOffset = selection_ind.cursorOffset(); + int endOffset = selection_ind.cursorOffsetEndSel(); + Node* startNode = kafkaCommon::getNodeFromLocation(selection_ind.cursorNode()); + Node* endNode = kafkaCommon::getNodeFromLocation(selection_ind.cursorNodeEndSel()); + + DOM::Node cursorDomNode; + long cursorOffset; + + m_kafkaPart->getCurrentNode(cursorDomNode, cursorOffset); + Node* cursorNode = getNode(cursorDomNode); + + slotCut(startNode, startOffset, endNode, endOffset, &cursorNode, cursorOffset, text); +} + +void KafkaDocument::slotCut(Node* startNode, int startOffset, Node* endNode, int endOffset, + Node** cursorNode, long cursorOffset, QString const& text) +{ + if(!startNode || !endNode) + return; + + NodeModifsSet *modifs = new NodeModifsSet(); + + Node* subtree_root = kafkaCommon::DTDExtractNodeSubtree(startNode, startOffset, endNode, endOffset, + cursorNode, cursorOffset, modifs); + + m_currentDoc->docUndoRedo->addNewModifsSet(modifs, undoRedo::NodeTreeModif); + + //Now update the VPL cursor position + kafkaWidget->setCurrentNode(startNode, startOffset); + + if(subtree_root) + { + KafkaDragObject* node_drag = new KafkaDragObject(subtree_root); + QTextDrag* text_drag = new QTextDrag(text); + KMultipleDrag* drag_object = new KMultipleDrag(); + drag_object->addDragObject(node_drag); + drag_object->addDragObject(text_drag); + + QApplication::clipboard()->setData(drag_object); +#ifdef LIGHT_DEBUG + kafkaCommon::coutTree(subtree_root, 3); +#endif + } +} + +void KafkaDocument::slotCopy() +{ + QString text = m_kafkaPart->selectedText(); + + NodeSelectionInd selection_ind; + selection_ind.fillWithVPLCursorSelection(); + + int startOffset = selection_ind.cursorOffset(); + int endOffset = selection_ind.cursorOffsetEndSel(); + Node* startNode = kafkaCommon::getNodeFromLocation(selection_ind.cursorNode()); + Node* endNode = kafkaCommon::getNodeFromLocation(selection_ind.cursorNodeEndSel()); + + slotCopy(startNode, startOffset, endNode, endOffset, text); +} + +void KafkaDocument::slotCopy(Node* startNode, int startOffset, Node* endNode, int endOffset, QString const& text) +{ + if(!startNode || !endNode) + return; + + Node* subtree_root = kafkaCommon::getNodeSubtree(startNode, startOffset, endNode, endOffset); + + if(subtree_root) + { + KafkaDragObject* node_drag = new KafkaDragObject(subtree_root); + QTextDrag* text_drag = new QTextDrag(text); + KMultipleDrag* drag_object = new KMultipleDrag(); + drag_object->addDragObject(node_drag); + drag_object->addDragObject(text_drag); + + QApplication::clipboard()->setData(drag_object); + // FIXME delete the subtree +#ifdef LIGHT_DEBUG + kafkaCommon::coutTree(subtree_root, 3); +#endif + } +} + +void KafkaDocument::slotPaste() +{ + QClipboard *cb = QApplication::clipboard(); + QMimeSource* e = cb->data(); + Node* node = new Node(0); + + if(KafkaDragObject::decode(e, node)) + { + bool go_up = false; + for(Node* aux = node; aux; aux = kafkaCommon::getNextNode(aux, go_up)) + kafkaCommon::restorePastedNode(aux, getCurrentDoc()); + + NodeSelectionInd selection_ind; + selection_ind.fillWithVPLCursorSelection(); + + Node* cursorNode = kafkaCommon::getNodeFromLocation(selection_ind.cursorNode()); + long cursorOffset = selection_ind.cursorOffset(); + + NodeModifsSet *modifs = new NodeModifsSet(); + + if(selection_ind.hasSelection()) + kafkaCommon::DTDRemoveSelection(selection_ind, &cursorNode, cursorOffset, modifs); + else + cursorNode = 0; // use selection_ind + kafkaCommon::DTDInsertNodeSubtree(node, selection_ind, &cursorNode, cursorOffset, modifs); + + m_currentDoc->docUndoRedo->addNewModifsSet(modifs, undoRedo::NodeTreeModif, 0, false); + + //Now update the VPL cursor position + kafkaWidget->setCurrentNode(cursorNode, cursorOffset); + } +} diff --git a/quanta/parts/kafka/wkafkapart.h b/quanta/parts/kafka/wkafkapart.h new file mode 100644 index 00000000..6b045150 --- /dev/null +++ b/quanta/parts/kafka/wkafkapart.h @@ -0,0 +1,473 @@ +/*************************************************************************** + wkafkapart.h + ------------------- + + copyright : (C) 2003, 2004 - Nicolas Deschildre + email : ndeschildre@kdewebdev.org + ***************************************************************************/ + +/*************************************************************************** + * * + * 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. * + * * + ***************************************************************************/ + +#ifndef WKAFKAPART_H +#define WKAFKAPART_H + +class KConfig; +namespace DOM +{ + class Node; +} + +class Document; +class Node; +class Parser; +class kNodeAttrs; +class NodeModifsSet; +class NodeEnhancer; +struct DTDStruct; +class NodeSelectionInd; +class NodeModifsSet; + +#include <qmap.h> +#include <qobject.h> +#include <qptrdict.h> + +#include "kafkahtmlpart.h" + +/** + * This class assures the synchronisation of the two trees : the quanta and the + * kafka tree. + * By Quanta Node, i mean Node (cf quanta/parser/node.h) + * By Kafka Node, i mean DOM::Node (cf <dom/dom_node.h>) + * It is a singleton class. + */ +class KafkaDocument : public QObject +{ +Q_OBJECT +public: + /** Returns a reference to the KafkaDocument object */ + static KafkaDocument* const ref(QWidget *parent = 0L, QWidget *widgetParent = 0L, const char *name = 0L) + { + static KafkaDocument *m_ref; + if (!m_ref) m_ref = new KafkaDocument(parent, widgetParent, name); + return m_ref; + } + + ~KafkaDocument(); + + + /* ----------------------------- LOADING/UNLOADING -----------------------------------------*/ + + /** + * Builds kafka's own tree from the Quanta tree. + * If the current Document is empty, it will create a minimum tree. + * @param the Document we want to load. + */ + void loadDocument(Document *doc); + + /** + * Unloads the kafka tree. + */ + void unloadDocument(); + + /** + * Reloads the Document. + */ + void reloadDocument(); + + /** + * Called to get KafkaDocument's state. + * @return Returns true if KafkaDocument is loaded. + */ + bool isLoaded() {return _docLoaded;} + + + /* ----------------------------------- KAFKA<->QUANTA NODES LINK ----------------------------------*/ + + /** + * @param _node The DOM::Node we want the attributess. + * @return Return the corresponding kNodeAttrs of the DOM::Node. + */ + kNodeAttrs *getAttrs(DOM::Node _domNode); + + /** + * This function search the corresponding quanta Node to the kafka DOM::Node + * @param _domNode The DOM::Node we seek its corresponding Node. + * @return The Node corresponding to _domNode. + */ + Node *getNode(DOM::Node _domNode); + + /** + * Connects the domNode to the corresponding Quanta Node with an intermediate class : + * a kNodeAttrs which links the node and the DOM::Node. + * This is MANDATORY, even if node is null, to use this function at each + * DOM::Node insertion !! + * @param domNode The DOM::Node to connect to the Node. + * @param node The Node to connect to the DOM::Node. Can be null. + * @return Returns the kNodeAttr which links them. + */ + kNodeAttrs* connectDomNodeToQuantaNode(DOM::Node domNode, Node *node); + + /** + * Disconnects the domNode from its corresponding Quanta Node. + * @param _domNode The DOM::Node to disconnect from its Node. + */ + void disconnectDomNodeFromQuantaNode(DOM::Node _domNode); + + /** + * Disconnects all the domNode from their Quanta Node. + */ + void disconnectAllDomNodes(); + + + /* --------------------------- CURSOR AND SELECTION HANDLING --------------------------*/ + + /** + * Set the cursor and selection coordinates. + * @param nodeSelection The coordinates of the selection and cursor. + */ + void setCursorAndSelection(NodeSelectionInd *nodeSelection); + + void setCursor(Node* node, int cursorOffset); + + /* --------------------------- KAFKA/QUANTA NODES CREATION -----------------------------*/ + + /** + * This function build a kafka DOM:::Node from a Quanta Node. + * @param _node The node from which we build the DOM::Node + * @param insertNode Specifies if we should insert the Node. + * @return Returns if the insertion was successful if asked, else true. + */ + bool buildKafkaNodeFromNode(Node *_node, bool insertNode = true); + + /** + * This function synchronizes the Node from the DOM::Node. If the Node is a text Node, + * we try to keep its indentation while updating it. + * @param node The Node to synchronize. + * @param domNode The Node is synchronized from this DOM::Node. + */ + void buildNodeFromKafkaNode(Node *node, DOM::Node domNode); + + /** + * This function creates and synchronize a Node from the DOM::Node. It adds + * the closing Node if necessary, and the node and its closing Node can surround Nodes + * and thus make them its childs. Usefull when adding a Node on a selected range of Nodes. + * It also create empty Nodes between Nodes. + * @param domNode The Node returned is synchronized from this DOM::Node. + * @param nodeParent The parent Node of the Node returned. + * @param beginNode The new Node will be placed before or within _beginNode. + * @param beginOffset NOT IMLEMENTED If set to 0 or -1, the new Node will be placed before _beginNode, + * else _beginNode will be splitted at offset #beginOffset and the new Node will be placed + * inbetween. + * @param endNode NOT IMPLEMENTED If not null and if the new Node has a closing tag, set the closing node + * after or within endNode. + * @param endOffset NOT IMPLEMENTED If set to -1, the closing node will be placed after _endNode, else _endNode + * will be splitted at offset #endOffset and the closing Node will be placed inbetween. + * @param modifs The NodeModifSet to log the changes made. + * @return Returns the new main Node created from the DOM::Node. + */ + Node * buildNodeFromKafkaNode(DOM::Node domNode, Node *nodeParent, + Node *beginNode, int beginOffset, Node *endNode, int endOffset, + NodeModifsSet *modifs); + + + /* ------------------------------- TEXT ENTITIES ENCODING/DECODING ---------------------*/ + + /** + * This function returns the special XML character (e.g. space, �...) + * from its encoded form (e.g. ) + * @return Returns the special character. + */ + QString getDecodedChar(const QString &encodedChar); + + /** + * This function returns the text decoded from its XML-encoded form. + * @param encodedText The text to decode. + * @param translateWhiteSpacesAndLineBreaks Specifies if whiteSpaces and line breaks should be + * compressed. Set it to false if we are inside PRE tag. + * @param removeLeftWhitespaces Specify if we should remove ALL whitespaces at the left of the string + * e.g. if the current text is the first child of a BLOCK Node (e.g. P) + * @param removeLeftWhitespaces Specify if we should remove ALL whitespaces at the right of the string + * e.g. if the current text is the last child of a BLOCK Node (e.g. P) + * @return Returns the text decoded. + */ + QString getDecodedText(const QString &encodedText, bool translateWhiteSpacesAndLineBreaks = true, + bool removeLeftWhitespaces = false, bool removeRightWhitespaces = false); + + /** + * This function returns the XML-encoded character (e.g. ) + * from the XML special character (e.g. space, �...) + * @param decodedChar The character to encode. + * @param previousDecodedchar The previous decoded character. + * @return Returns the XML-encoded character. + */ + QString getEncodedChar(const QString &decodedChar, const QString &previousDecodedChar); + + /** + * This function returns the text with all the special XML characters encoded. + * @param decodedText The text to encode. + * @param bLine The start line of the returned encoded text. + * @param bCol The start col of the returned encoded text. + * @param eLine Returns the end line of the returned encoded text. + * @param eCol Returns the end col of the return encoded text. + * @param translateWhiteSpaces Specifies if we should translate the whitespaces + * into Set it to false for PRE Tag. + * @return Return the XML-encoded text. + */ + QString getEncodedText(const QString &decodedText, int bLine, int bCol, int &eLine, int &eCol, + bool translateWhiteSpaces = true); + + /** + * This function behaves essentially like the above function except that it doesn't + * return the position of the encoded text. + */ + QString getEncodedText(const QString &decodedText); + + /** + * This function generates the code corresponding to the XML node. + * @param _node The Node we want to generate its code. + * @param bLine The start line of the returned generated code. + * @param bCol The start col of the returned generated code. + * @param eLine Returns the end line of the returned generated code. + * @param eCol Returns the end col of the return generated code. + * @param encodeText Specify, if node is a Text Node, if we should encode the text + * (e.g. whitespace => ) + * @return Returns the code. + */ + QString generateCodeFromNode(Node *node, int bLine, int bCol, int &eLine, int &eCol, + bool encodeText = true); + + + /* ------------------------- KAFKA<->QUANTA POSITION TRANSLATION -------------------------------*/ + + /** + * Returns the kafka cursor position corresponding to the quanta cursor position. + * @param line The quanta line cursor position. + * @param col The quanta column cursor position. + * @param domNode Returns the DOM::Node in which the cursor is located. + * @param offset Returns the offset of the cursor. + */ + void translateQuantaIntoKafkaCursorPosition(uint line, uint col, DOM::Node &domNode, long &offset); + + /** + * Returns the internal Node offset corresponding to the Quanta cursor position. + * @param line The quanta line cursor position. + * @param col The quanta column cursor position. + * @param node Returns the node where is located the node internal offset. + * @param offset Returns the node internal corresponding offset. + */ + void translateQuantaIntoNodeCursorPosition(uint line, uint col, Node **node, long &offset); + + /** + * Returns the internal Node offset corresponding to the kafka cursor position. + * @param domNode The kafka DOM::Node cursor position. + * @param domNodeOffset The kafka offset cursor position. + * @param node Returns the node where is located the node internal offset. + * @param offset Returns the node internal corresponding offset. + */ + void translateKafkaIntoNodeCursorPosition(DOM::Node domNode, long domNodeOffset, Node **node, long &offset); + + /** + * Returns the quanta cursor position corresponding to the kafka cursor position. + * @param domNode The kafka DOM::Node cursor position. + * @param offset The kafka offset cursor position. + * @param line Returns the line cursor position. + * @param col Returns the col cursor position. + */ + void translateKafkaIntoQuantaCursorPosition(DOM::Node domNode, int offset, int &line, int &col); + + /** + * Returns the kafka cursor position corresponding to the internal Node offset. + * @param node The node. + * @param offset The internal offset of Node. + * @param domNode Returns the corresponding DOM::Node of node. Can be null. + * @param domNodeOffset Returns the offset inside the DOM::Node. + */ + void translateNodeIntoKafkaCursorPosition(Node *node, int offset, DOM::Node &domNode, + long &domNodeOffset); + + /** + * Returns the quanta cursor position corresponding to the internal Node offset. + * @param node The node. + * @param offset The internal offset of Node. + * @param line Returns the line position of the corresponding Quanta cursor. + * @param col Returns the col position of the corresponding Quanta cursor. + */ + void translateNodeIntoQuantaCursorPosition(Node *node, int offset, uint &line, uint &col); + + + /** ----------------- DOM::NODE TREE MODIFICATIONS --------------------*/ + + /** + * Insert a DOM::Node in the DOM::Node tree. It takes care to handle the exceptions and + * to postEnhance (cf htmlenhancer.h) + * @param node The node to insert. + * @param parent The new parent of node. If null, insert node at the top level. + * @param nextSibling The new next sibling of node. If null, append node at the end of the child list. + * @param rootNode The root DOM::Node of the DOM::Node tree. Useful when no parent is provided. + * @return Returns true if the operation was successfull. + */ + bool insertDomNode(DOM::Node node, DOM::Node parent = DOM::Node(), + DOM::Node nextSibling = DOM::Node(), DOM::Node rootNode = DOM::Node()); + + + /** + * Removes a DOM::Node from the DOM::Node Tree. It takes care to handle the exceptions + * and to postUnenhance (cf htmlenhancer.h) + * @param node The Node to remove from the tree. + * @retun Returns true if the operation was successfull.. + */ + bool removeDomNode(DOM::Node node); + + + /** ------------------ DOM::NODE TREE NAVIGATION -----------------------------------------*/ + + /** + * @param domNode The DOM::Node the search starts from. + * @return Returns the next previous sibling which has no special behavior (cf htmlenhancer.h) + * Sibling here (contrary to node.h) has the same meaning as in dom/dom_node.h + */ + DOM::Node getPrevSiblingNSpecial(DOM::Node domNode); + + /** + * @param domNode The DOM::Node the search starts from. + * @return Returns the next next sibling which has no special behavior (cf htmlenhancer.h) + * Sibling here (contrary to node.h) has the same meaning as in dom/dom_node.h + */ + DOM::Node getNextSiblingNSpecial(DOM::Node domNode); + + /* --------------------------------- MISCELLANEOUS ------------------------------------------*/ + + /** + * Read the config. + * @param m_config The config to read. + */ + void readConfig(KConfig *m_config); + + /** + * Returns the default DTD of the current Document. + */ + const DTDStruct* defaultDTD(); + + /** + * @return Returns the current KafkaWidget. + */ + KafkaWidget *getKafkaWidget() {return m_kafkaPart;} + + /** + * @return Returns the current Document. + */ + Document *getCurrentDoc() {return m_currentDoc;} + + /** + * Prints in stdout the current Node tree + Node->DOM::Node->Node relationship. + * @node The startNode + * @indent The number of little dots per parent relationship. + */ + void coutLinkTree(Node *node, int indent); + + /** + * In order to have khtml works whatever DTD is loaded, they must always exists + * and be valid. + */ + DOM::Node html, body, head; + +signals: + /** + * Emitted when an error occurs when loading kafka. Can be called multiple times. + * @param node The node which caused this error. + */ + void loadingError(Node *node); + + /** + * Called whenever a DOM::Node get the focus + */ + void newCursorPosition(int col, int row); + + /** + * Called whenever a DOM::Node get the focus + */ + void nodeGetFocus(Node *_node); + + /** + * Called when the wKafkaPart is loaded. + */ + void loaded(); + + /** + * Called when the wKafkaPart is unloaded. + */ + void unloaded(); + +public slots: + /** + * Called whenever a DOM::Node is inserted in the Kafka tree. + * @param domNode is the Node inserted. + * @param insertChilds Specifies if the _domNode's child should be inserted + * @param modifs The changes made are logged into modifs. + */ + void slotDomNodeInserted(DOM::Node domNode, bool insertChilds, NodeModifsSet *modifs); + + /** + * Called whenever DOM::Node's attributes are modified. + * @param domNode is the Node modified. + * @param modifs The changes made are logged into modifs. + */ + void slotDomNodeModified(DOM::Node domNode, NodeModifsSet *modifs); + + /** + * Called whenever a DOM::Node is about to be removed from the Kafka tree. + * @param domNode is the Node to be deleted. + * @param deleteChilds Specifies if we should delete the child nodes of _node + * @param modifs The changes made are logged into modifs. + */ + void slotDomNodeAboutToBeRemoved(DOM::Node domNode, bool deleteChilds, NodeModifsSet *modifs); + + /** + * Called whenever a DOM::Node is moved. + * @param domNode The DOM::Node to move. + * @param newParent The new parent DOM::Node of domNode. + * @param before domNode will be inserted before before. + * @param modifs The changes made are logged into modifs. + */ + void slotDomNodeIsAboutToBeMoved(DOM::Node domNode, DOM::Node newParent, DOM::Node before, NodeModifsSet *modifs); + + /** + * Called whenever a DOM::Node get the focus + */ + void slotdomNodeNewCursorPos(DOM::Node _domNode, int offset); + + void slotCut(); + void slotCopy(); + void slotPaste(); + + void slotCut(Node* startNode, int startOffset, Node* endNode, int endOffset, + Node** cursorNode, long cursorOffset, QString const& plainText); + void slotCopy(Node* startNode, int startOffset, Node* endNode, int endOffset, QString const& plainText); + +public: + //use a QPtrList aferwards + NodeEnhancer *mainEnhancer; + +private: + /** + * Create a KafkaWidget. + */ + KafkaDocument(QWidget *parent, QWidget *widgetParent, const char *name); + + QMap<QString, QString> decodedChars; + QMap<QString, QString> encodedChars; + QPtrDict<kNodeAttrs> domNodeProps; + QGuardedPtr<KafkaWidget> m_kafkaPart; + Document *m_currentDoc; + bool _docLoaded; + +}; + +#endif |