summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--readme.md72
-rw-r--r--starmath/inc/caret.hxx445
-rw-r--r--starmath/inc/cursor.hxx424
-rw-r--r--starmath/inc/document.hxx19
-rw-r--r--starmath/inc/edit.hxx4
-rw-r--r--starmath/inc/node.hxx506
-rw-r--r--starmath/inc/parse.hxx19
-rw-r--r--starmath/inc/starmath.hrc4
-rw-r--r--starmath/inc/view.hxx25
-rw-r--r--starmath/inc/visitors.hxx469
-rw-r--r--starmath/sdi/smath.sdi2
-rw-r--r--starmath/sdi/smslots.sdi14
-rw-r--r--starmath/source/caret.cxx33
-rw-r--r--starmath/source/cursor.cxx1591
-rw-r--r--starmath/source/dialog.cxx4
-rw-r--r--starmath/source/document.cxx40
-rw-r--r--starmath/source/edit.cxx55
-rw-r--r--starmath/source/makefile.mk3
-rw-r--r--starmath/source/node.cxx420
-rw-r--r--starmath/source/parse.cxx70
-rw-r--r--starmath/source/view.cxx271
-rw-r--r--starmath/source/visitors.cxx2502
22 files changed, 6544 insertions, 448 deletions
diff --git a/readme.md b/readme.md
new file mode 100644
index 000000000000..16301fc1b6f0
--- /dev/null
+++ b/readme.md
@@ -0,0 +1,72 @@
+Visual Formula Editor for LibreOffice Math
+==========================================
+
+This repository facilitates development of a visual formula editor
+for LibreOffice/OpenOffice Math. This work was started by me (Jonas
+ Finnemann Jensen) during GSoC 2010 with Go-OO. During development
+this repository will be merged with libreoffice/writer on a random
+basis, but will hopefully at some point be merged back into
+LibreOffice.
+
+Description of the GSoC project that started this, be found
+[here](http://www.freedesktop.org/wiki/Software/ooo-build/SummerOfCode/2010/Jonas).
+Please note that this page will not be updated futher, but it does
+contain relevant information about the project and discusses the
+implementation strategy. I might also post progress updates on
+[my blog](http://jopsen.dk/blog/category/computer/openoffice/).
+But the project status will be documented and updated in this file, see below.
+
+Videos of the patch in action can be found at:
+
+ * [Development Preview 3 (13th of August)](http://www.youtube.com/watch?v=3foNqKYAlYY)
+ * [Development Preview 2 (26th of July)](http://www.youtube.com/watch?v=tELPgJIC1sg)
+ * [Development Preview 1 (21th of July)](http://www.youtube.com/watch?v=W8yXyDiIQPc)
+
+(All videos can also be found [here](http://jopsen.dk/downloads/GSoC2010/Videos/) better quality and various formats).
+
+If you're interested in more technical details the source (that I've
+written) is extensively documented with doxygen comments. You're also
+welcome to [contact me](http://jopsen.dk/blog/about/) with any
+questions, or wish to help hacking. My email is <jopsen@gmail.com>
+and I'm jopsen on #LibreOffice (if I'm online), there's also a finite
+set of [other options](http://jopsen.dk/blog/about/).
+
+Project Status
+==============
+*This is basically my todo list, so don't be surprised if things are not immediately obvious.*
+
+Easy
+----
+1. Draw a non-blinking line under the the visual line that the caret is in, in `SmCaretDrawingVisitor`.
+2. `SmGraphicWindow::KeyInput` relies on comparison of `sal_Char`, a better way must be available for CTRL+c
+3. Code style (missing spaces, linebreaks and a few renames)
+4. More documentation
+5. Replace `j_assert` with `DBG_ASSERT`
+
+Medium
+------
+1. `SmCursor::InsertCol()` method for added columns to matrices should be implemented.
+2. `SmCursor` should support deletion of lines, rows, cols and sub-/superscripts.
+3. `SmCursor::InsertSubSup()` should wrap the body in a `SmBraceNode` if the body is an `SmOperNode`, `SmBinVerNode`, etc.
+4. Make caret in visual editor blink.
+5. Don't draw visual editor caret, when `SmGraphicWindow` doesn't have focus.
+6. When OpenOffice Math runs in standalone mode it centers the current formula, this is not nice for visual editing.
+
+Complex
+-------
+1. `SmAlignNode` and `SmFontNode` are ignored by visual editor, figure out how these should work.
+2. Solve the flickering issue when drawing formulas (See e-mail)
+3. Make " a shortcut for creating an `SmTextNode` with `FNT_TEXT`, also check that `SmNodeToTextVisitor` supports this.
+4. `parse.cxx` merges multiple blanks into one `SmBlankNode`, the visual editor doesn't...
+
+Complex and non-essential
+-------------------------
+1. Global clipboard integration
+2. Support undo/redo with `UndoManager` integration
+3. Consider improving GUI for "Formula Elements"-dialog, most buttons work with visual editor
+4. Consider allowing users to enter commands in visual editor, by prefixing the command...
+5. Optimize things, for instance `SmCursor::AnnotateSelection()` is called way too many places...
+6. Improve handling of `MoveUp` and `MoveDown` in `SmCursor::Move`, `SmCaretPos2LineVisitor` might need improvement.
+
+Items are organized by complexity and importance, and I'm not sure everything needs to be addressed
+before release, but it constitutes a list of things I can work on.
diff --git a/starmath/inc/caret.hxx b/starmath/inc/caret.hxx
new file mode 100644
index 000000000000..ae0f0fc1a0f4
--- /dev/null
+++ b/starmath/inc/caret.hxx
@@ -0,0 +1,445 @@
+#ifndef CARET_H
+#define CARET_H
+
+#include "node.hxx"
+
+/** Representation of caret position with an equantion */
+struct SmCaretPos{
+ SmCaretPos(SmNode* selectedNode = NULL, int iIndex = 0) {
+ pSelectedNode = selectedNode;
+ Index = iIndex;
+ }
+ /** Selected node */
+ SmNode* pSelectedNode;
+ /** Index within the selected node
+ *
+ * 0: Position infront of a node
+ * 1: Position after a node or after first char in SmTextNode
+ * n: Position after n char in SmTextNode
+ *
+ * Notice how there's special cases for SmTextNode.
+ */
+ //TODO: Special cases for SmBlankNode is needed
+ //TODO: Consider forgetting about the todo above... As it's really unpleasent.
+ int Index;
+ /** True, if this is a valid caret position */
+ bool IsValid() { return pSelectedNode != NULL; }
+ bool operator!=(SmCaretPos pos) const {
+ return pos.pSelectedNode != pSelectedNode || Index != pos.Index;
+ }
+ bool operator==(SmCaretPos pos) const {
+ return pos.pSelectedNode == pSelectedNode && Index == pos.Index;
+ }
+ /** Get the caret position after pNode, regardless of pNode
+ *
+ * Gets the caret position following pNode, this is SmCaretPos(pNode, 1).
+ * Unless pNode is an instance of SmTextNode, then the index is the text length.
+ */
+ static SmCaretPos GetPosAfter(SmNode* pNode) {
+ if(pNode && pNode->GetType() == NTEXT)
+ return SmCaretPos(pNode, ((SmTextNode*)pNode)->GetText().Len());
+ return SmCaretPos(pNode, 1);
+ }
+};
+
+/** A line that represents a caret */
+class SmCaretLine{
+public:
+ SmCaretLine(long left = 0, long top = 0, long height = 0) {
+ _top = top;
+ _left = left;
+ _height = height;
+ }
+ long GetTop() const {return _top;}
+ long GetLeft() const {return _left;}
+ long GetHeight() const {return _height;}
+ long SquaredDistanceX(SmCaretLine line) const{
+ return (GetLeft() - line.GetLeft()) * (GetLeft() - line.GetLeft());
+ }
+ long SquaredDistanceX(Point pos) const{
+ return (GetLeft() - pos.X()) * (GetLeft() - pos.X());
+ }
+ long SquaredDistanceY(SmCaretLine line) const{
+ long d = GetTop() - line.GetTop();
+ if(d < 0)
+ d = (d * -1) - GetHeight();
+ else
+ d = d - line.GetHeight();
+ if(d < 0)
+ return 0;
+ return d * d;
+ }
+ long SquaredDistanceY(Point pos) const{
+ long d = GetTop() - pos.Y();
+ if(d < 0)
+ d = (d * -1) - GetHeight();
+ if(d < 0)
+ return 0;
+ return d * d;
+ }
+private:
+ long _top;
+ long _left;
+ long _height;
+};
+
+/////////////////////////////// SmCaretPosGraph////////////////////////////////
+
+/** An entry in SmCaretPosGraph */
+struct SmCaretPosGraphEntry{
+ SmCaretPosGraphEntry(SmCaretPos pos = SmCaretPos(),
+ SmCaretPosGraphEntry* left = NULL,
+ SmCaretPosGraphEntry* right = NULL){
+ CaretPos = pos;
+ Left = left;
+ Right = right;
+ }
+ /** Caret position */
+ SmCaretPos CaretPos;
+ /** Entry to the left visually */
+ SmCaretPosGraphEntry* Left;
+ /** Entry to the right visually */
+ SmCaretPosGraphEntry* Right;
+ void SetRight(SmCaretPosGraphEntry* right){
+ Right = right;
+ }
+ void SetLeft(SmCaretPosGraphEntry* left){
+ Left = left;
+ }
+};
+
+/** Define SmCaretPosGraph to be less than one page 4096 */
+#define SmCaretPosGraphSize 255
+
+class SmCaretPosGraph;
+
+/** Iterator for SmCaretPosGraph */
+class SmCaretPosGraphIterator{
+public:
+ SmCaretPosGraphIterator(SmCaretPosGraph* graph){
+ pGraph = graph;
+ nOffset = 0;
+ pEntry = NULL;
+ }
+ /** Get the next entry, NULL if none */
+ SmCaretPosGraphEntry* Next();
+ /** Get the current entry, NULL if none */
+ SmCaretPosGraphEntry* Current(){
+ return pEntry;
+ }
+ /** Get the current entry, NULL if none */
+ SmCaretPosGraphEntry* operator->(){
+ return pEntry;
+ }
+private:
+ /** Next entry to return */
+ int nOffset;
+ /** Current graph */
+ SmCaretPosGraph* pGraph;
+ /** Current entry */
+ SmCaretPosGraphEntry* pEntry;
+};
+
+
+/** A graph over all caret positions
+ * @remarks Graphs can only grow, entries cannot be removed!
+ */
+class SmCaretPosGraph{
+public:
+ SmCaretPosGraph(){
+ pNext = NULL;
+ nOffset = 0;
+ }
+ ~SmCaretPosGraph();
+ SmCaretPosGraphEntry* Add(SmCaretPosGraphEntry entry);
+ SmCaretPosGraphEntry* Add(SmCaretPos pos,
+ SmCaretPosGraphEntry* left = NULL,
+ SmCaretPosGraphEntry* right = NULL){
+ j_assert(pos.Index >= 0, "Index shouldn't be -1!");
+ return Add(SmCaretPosGraphEntry(pos, left, right));
+ }
+ /** Get an iterator for this graph */
+ SmCaretPosGraphIterator GetIterator(){
+ return SmCaretPosGraphIterator(this);
+ }
+ friend class SmCaretPosGraphIterator;
+private:
+ /** Next graph, to be used when this graph is full */
+ SmCaretPosGraph* pNext;
+ /** Next free entry in graph */
+ int nOffset;
+ /** Entries in this graph segment */
+ SmCaretPosGraphEntry Graph[SmCaretPosGraphSize];
+};
+
+/** \page visual_formula_editing Visual Formula Editing
+ * A visual formula editor allows users to easily edit formulas without having to learn and
+ * use complicated commands. A visual formula editor is a WYSIWYG editor. For OpenOffice Math
+ * this essentially means that you can click on the formula image, to get a caret, which you
+ * can move with arrow keys, and use to modify the formula by entering text, clicking buttons
+ * or using shortcuts.
+ *
+ * \subsection formula_trees Formula Trees
+ * A formula in OpenOffice Math is a tree of nodes, take for instance the formula
+ * "A + {B cdot C} over D", it looks like this
+ * \f$ \mbox{A} + \frac{\mbox{B} \cdot \mbox{C}}{\mbox{D}} \f$. The tree for this formula
+ * looks like this:
+ *
+ * \dot
+ * digraph {
+ * labelloc = "t";
+ * label= "Equation: \"A + {B cdot C} over D\"";
+ * size = "9,9";
+ * n0 [label="SmTableNode (1)"];
+ * n0 -> n1 [label="0"];
+ * n1 [label="SmLineNode (2)"];
+ * n1 -> n2 [label="0"];
+ * n2 [label="SmExpressionNode (3)"];
+ * n2 -> n3 [label="0"];
+ * n3 [label="SmBinHorNode (4)"];
+ * n3 -> n4 [label="0"];
+ * n4 [label="SmTextNode: A (5)"];
+ * n3 -> n5 [label="1"];
+ * n5 [label="SmMathSymbolNode:  (6)"];
+ * n3 -> n6 [label="2"];
+ * n6 [label="SmBinVerNode (7)"];
+ * n6 -> n7 [label="0"];
+ * n7 [label="SmExpressionNode (8)"];
+ * n7 -> n8 [label="0"];
+ * n8 [label="SmBinHorNode (9)"];
+ * n8 -> n9 [label="0"];
+ * n9 [label="SmTextNode: B (10)"];
+ * n8 -> n10 [label="1"];
+ * n10 [label="SmMathSymbolNode: ⋅ (11)"];
+ * n8 -> n11 [label="2"];
+ * n11 [label="SmTextNode: C (12)"];
+ * n6 -> n12 [label="1"];
+ * n12 [label="SmRectangleNode (13)"];
+ * n6 -> n13 [label="2"];
+ * n13 [label="SmTextNode: D (14)"];
+ * }
+ * \enddot
+ *
+ * The vertices are nodes, their label says what kind of node and the number in parentheses is
+ * the identifier of the node (In practices a pointer is used instead of the id). The direction
+ * of the edges tells which node is parent and which is child. The label of the edges are the
+ * child node index number, given to SmNode::GetSubNode() of the parent to get the child node.
+ *
+ *
+ * \subsection visual_lines Visual Lines
+ *
+ * Inorder to do caret movement in visual lines, we need a definition of caret position and
+ * visual line. In a tree such as the above there are three visual lines. There's the outer most
+ * line, with entries such as
+ * \f$\mbox{A}\f$, \f$ + \f$ and \f$ \frac{\mbox{B} \cdot \mbox{C}}{\mbox{D}} \f$. Then there's
+ * the numerator line of the fraction it has entries \f$ \mbox{B} \f$, \f$ \cdot \f$ and \f$ \mbox{C} \f$.
+ * And last by not least there's the denominator line of the fraction it's only entry is \f$ \mbox{D} \f$.
+ *
+ * For visual editing it should be possible to place a caret on both sides of any line entry,
+ * consider a line entry a character or construction that in a line is treated as a character.
+ * Imagine the caret is placed to the right of the plus sign (id: 6), now if user presses
+ * backspace this should delete the plus sign (id: 6), and if the user presses delete this
+ * should delete the entire fraction (id: 7). This is because the caret is in the outer most
+ * line where the fraction is considered a line entry.
+ *
+ * However, inorder to prevent users from accidentally deleting large subtrees, just because
+ * they logically placed there caret a in the wrong line, require that complex constructions
+ * such as a fraction is selected before it is deleted. Thus in this case it wouldn't be
+ * deleted, but only selected and then deleted if the user hit delete again. Anyway, this is
+ * slightly off topic for now.
+ *
+ * Important about visual lines is that they don't always have an SmExpressionNode as root
+ * and the entries in a visual line is all the nodes of a subtree ordered left to right that
+ * isn't either an SmExpressionNode, SmBinHorNode or SmUnHorNode.
+ *
+ *
+ * \subsection caret_positions Caret Positions
+ *
+ * A caret position in OpenOffice Math is representated by an instance of SmCaretPos.
+ * That is a caret position is a node and an index related to this node. For most nodes the
+ * index 0, means caret is infront of this node, the index 1 means caret is after this node.
+ * For SmTextNode the index is the caret position after the specified number of characters,
+ * imagine an SmTextNode with the number 1337. The index 3 in such SmTextNode would mean a
+ * caret placed right before 7, e.g. "133|7".
+ *
+ * For SmExpressionNode, SmBinHorNode and SmUnHorNode the only legal index is 0, which means
+ * infront of the node. Actually the index 0 may only because for the first caret position
+ * in a visual line. From the example above, consider the following subtree that constitutes
+ * a visual line:
+ *
+ * \dot
+ * digraph {
+ * labelloc = "t";
+ * label= "Subtree that constitutes a visual line";
+ * size = "7,5";
+ * n7 [label="SmExpressionNode (8)"];
+ * n7 -> n8 [label="0"];
+ * n8 [label="SmBinHorNode (9)"];
+ * n8 -> n9 [label="0"];
+ * n9 [label="SmTextNode: B (10)"];
+ * n8 -> n10 [label="1"];
+ * n10 [label="SmMathSymbolNode: ⋅ (11)"];
+ * n8 -> n11 [label="2"];
+ * n11 [label="SmTextNode: C (12)"];
+ * }
+ * \enddot
+ * Here the caret positions are:
+ *
+ * <TABLE>
+ * <TR><TD><B>Caret position:</B></TD><TD><B>Example:</B></TD>
+ * </TR><TR>
+ * <TD>{id: 8, index: 0}</TD>
+ * <TD>\f$ \mid \mbox{C} \cdot \mbox{C} \f$</TD>
+ * </TR><TR>
+ * <TD>{id: 10, index: 1}</TD>
+ * <TD>\f$ \mbox{C} \mid \cdot \mbox{C} \f$</TD>
+ * </TR><TR>
+ * <TD>{id: 11, index: 1}</TD>
+ * <TD>\f$ \mbox{C} \cdot \mid \mbox{C} \f$</TD>
+ * </TR><TR>
+ * <TD>{id: 12, index: 1}</TD>
+ * <TD>\f$ \mbox{C} \cdot \mbox{C} \mid \f$</TD>
+ * </TR><TR>
+ * </TABLE>
+ *
+ * Where \f$ \mid \f$ is used to denote caret position.
+ *
+ * With these exceptions included in the definition the id and index: {id: 11, index: 0} does
+ * \b not constitute a caret position in the given context. Note the method
+ * SmCaretPos::IsValid() does not check if this invariant holds true, but code in SmCaret,
+ * SmSetSelectionVisitor and other places depends on this invariant to hold.
+ *
+ *
+ * \subsection caret_movement Caret Movement
+ *
+ * As the placement of caret positions depends very much on the context within which a node
+ * appears it is not trivial to find all caret positions and determine which follows which.
+ * In OpenOffice Math this is done by the SmCaretPosGraphBuildingVisitor. This visitor builds
+ * graph (an instnce of SmCaretPosGraph) over the caret positions. For details on how this
+ * graph is build, and how new methods should be implemented see SmCaretPosGraphBuildingVisitor.
+ *
+ * The result of the SmCaretPosGraphBuildingVisitor is a graph over the caret positions in a
+ * formula, representated by an instance of SmCaretPosGraph. Each entry (instances of SmCaretPosGraphEntry)
+ * has a pointer to the entry to the left and right of itself. This way we can easily find
+ * the caret position to a right or left of a given caret position. Note each caret position
+ * only appears once in this graph.
+ *
+ * When searching for a caret position after a left click on the formula this map is also used.
+ * We simply iterate over all entries, uses the SmCaretPos2LineVisitor to find a line for each
+ * caret position. Then the distance from the click to the line is computed and we choose the
+ * caret position closest to the click.
+ *
+ * For up and down movement, we also iterator over all caret positions and use SmCaretPos2LineVisitor
+ * to find a line for each caret position. Then we compute the distance from the current
+ * caret position to every other caret position and chooses the one closest that is either
+ * above or below the current caret position, depending on wether we're doing up or down movement.
+ *
+ * This result of this approach to caret movement is that we have logically predictable
+ * movement for left and right, whilst leftclick, up and down movement depends on the sizes
+ * and placement of all node and may be less logically predictable. This solution also means
+ * that we only have one complex visitor generating the graph, imagine the nightmare if we
+ * had a visitor for movement in each direction.
+ *
+ * Making up and down movement independent of node sizes and placement wouldn't necessarily
+ * be a good thing either. Consider the formula \f$ \frac{1+2+3+4+5}{6} \f$, if the caret is
+ * placed as displayed here: \f$ \frac{1+2+3+4+5}{6 \mid} \f$, up movement should move to right
+ * after "3": \f$ \frac{1+2+3|+4+5}{6} \f$. However, such a move depends on the sizes and placement
+ * of all nodes in the fraction.
+ *
+ *
+ * \subsubsection caretpos_graph_example Example of Caret Position Graph
+ *
+ * If we consider the formula
+ * \f$ \mbox{A} + \frac{\mbox{B} \cdot \mbox{C}}{\mbox{D}} \f$ from \ref formula_trees.
+ * It has the following caret positions:
+ *
+ * <TABLE>
+ * <TR>
+ * <TD><B>Caret position:</B></TD>
+ * <TD><B>Example:</B></TD>
+ * </TR><TR>
+ * <TD>{id: 3, index: 0}</TD>
+ * <TD>\f$ \mid\mbox{A} + \frac{\mbox{B} \cdot \mbox{C}}{\mbox{D}} \f$</TD>
+ * </TR><TR>
+ * <TD>{id: 5, index: 1}</TD>
+ * <TD>\f$ \mbox{A}\mid + \frac{\mbox{B} \cdot \mbox{C}}{\mbox{D}} \f$</TD>
+ * </TR><TR>
+ * <TD>{id: 6, index: 1}</TD>
+ * <TD>\f$ \mbox{A} + \mid \frac{\mbox{B} \cdot \mbox{C}}{\mbox{D}} \f$</TD>
+ * </TR><TR>
+ * <TD>{id: 8, index: 0}</TD>
+ * <TD>\f$ \mbox{A} + \frac{ \mid \mbox{B} \cdot \mbox{C}}{\mbox{D}} \f$</TD>
+ * </TR><TR>
+ * <TD>{id: 10, index: 1}</TD>
+ * <TD>\f$ \mbox{A} + \frac{\mbox{B} \mid \cdot \mbox{C}}{\mbox{D}} \f$</TD>
+ * </TR><TR>
+ * <TD>{id: 11, index: 1}</TD>
+ * <TD>\f$ \mbox{A} + \frac{\mbox{B} \cdot \mid \mbox{C}}{\mbox{D}} \f$</TD>
+ * </TR><TR>
+ * <TD>{id: 12, index: 1}</TD>
+ * <TD>\f$ \mbox{A} + \frac{\mbox{B} \cdot \mbox{C} \mid}{\mbox{D}} \f$</TD>
+ * </TR><TR>
+ * <TD>{id: 14, index: 0}</TD>
+ * <TD>\f$ \mbox{A} + \frac{\mbox{B} \cdot \mbox{C}}{\mid \mbox{D}} \f$</TD>
+ * </TR><TR>
+ * <TD>{id: 14, index: 1}</TD>
+ * <TD>\f$ \mbox{A} + \frac{\mbox{B} \cdot \mbox{C}}{\mbox{D} \mid} \f$</TD>
+ * </TR><TR>
+ * <TD>{id: 7, index: 1}</TD>
+ * <TD>\f$ \mbox{A} + \frac{\mbox{B} \cdot \mbox{C}}{\mbox{D}} \mid \f$</TD>
+ * </TR>
+ * </TABLE>
+ *
+ * Below is a directed graph over the caret postions and how you can move between them.
+ * \dot
+ * digraph {
+ * labelloc = "t";
+ * label= "Caret Position Graph";
+ * size = "4,6";
+ * p0 [label = "{id: 3, index: 0}"];
+ * p0 -> p1 [fontsize = 10.0, label = "right"];
+ * p1 [label = "{id: 5, index: 1}"];
+ * p1 -> p0 [fontsize = 10.0, label = "left"];
+ * p1 -> p2 [fontsize = 10.0, label = "right"];
+ * p2 [label = "{id: 6, index: 1}"];
+ * p2 -> p1 [fontsize = 10.0, label = "left"];
+ * p2 -> p3 [fontsize = 10.0, label = "right"];
+ * p3 [label = "{id: 8, index: 0}"];
+ * p3 -> p2 [fontsize = 10.0, label = "left"];
+ * p3 -> p4 [fontsize = 10.0, label = "right"];
+ * p4 [label = "{id: 10, index: 1}"];
+ * p4 -> p3 [fontsize = 10.0, label = "left"];
+ * p4 -> p5 [fontsize = 10.0, label = "right"];
+ * p5 [label = "{id: 11, index: 1}"];
+ * p5 -> p4 [fontsize = 10.0, label = "left"];
+ * p5 -> p6 [fontsize = 10.0, label = "right"];
+ * p6 [label = "{id: 12, index: 1}"];
+ * p6 -> p5 [fontsize = 10.0, label = "left"];
+ * p6 -> p9 [fontsize = 10.0, label = "right"];
+ * p7 [label = "{id: 14, index: 0}"];
+ * p7 -> p2 [fontsize = 10.0, label = "left"];
+ * p7 -> p8 [fontsize = 10.0, label = "right"];
+ * p8 [label = "{id: 14, index: 1}"];
+ * p8 -> p7 [fontsize = 10.0, label = "left"];
+ * p8 -> p9 [fontsize = 10.0, label = "right"];
+ * p9 [label = "{id: 7, index: 1}"];
+ * p9 -> p6 [fontsize = 10.0, label = "left"];
+ * }
+ * \enddot
+ */
+
+/* TODO: Write documentation about the following keywords:
+ *
+ * Visual Selections:
+ * - Show images
+ * - Talk about how the visitor does this
+ *
+ * Modifying a Visual Line:
+ * - Find top most non-compo of the line (e.g. The subtree that constitutes a line)
+ * - Make the line into a list
+ * - Edit the list, add/remove/modify nodes
+ * - Parse the list back into a subtree
+ * - Insert the new subtree where the old was taken
+ */
+
+#endif /* CARET_H */
diff --git a/starmath/inc/cursor.hxx b/starmath/inc/cursor.hxx
new file mode 100644
index 000000000000..d78aad764989
--- /dev/null
+++ b/starmath/inc/cursor.hxx
@@ -0,0 +1,424 @@
+#ifndef SMCURSOR_H
+#define SMCURSOR_H
+
+#include "node.hxx"
+#include "caret.hxx"
+
+/** Factor to multiple the squared horizontical distance with
+ * Used for Up and Down movement.
+ */
+#define HORIZONTICAL_DISTANCE_FACTOR 10
+
+/** Enum of direction for movement */
+enum SmMovementDirection{
+ MoveUp,
+ MoveDown,
+ MoveLeft,
+ MoveRight
+};
+
+/** Enum of elements that can inserted into a formula */
+enum SmFormulaElement{
+ BlankElement,
+ FactorialElement,
+ PlusElement,
+ MinusElement,
+ CDotElement,
+ EqualElement,
+ LessThanElement,
+ GreaterThanElement
+};
+
+/** Bracket types that can be inserted */
+enum SmBracketType {
+ /** None brackets, left command "none" */
+ NoneBrackets,
+ /** Round brackets, left command "(" */
+ RoundBrackets,
+ /**Square brackets, left command "[" */
+ SquareBrackets,
+ /** Double square brackets, left command "ldbracket" */
+ DoubleSquareBrackets,
+ /** Line brackets, left command "lline" */
+ LineBrackets,
+ /** Double line brackets, left command "ldline" */
+ DoubleLineBrackets,
+ /** Curly brackets, left command "lbrace" */
+ CurlyBrackets,
+ /** Angle brackets, left command "langle" */
+ AngleBrackets,
+ /** Ceiling brackets, left command "lceil" */
+ CeilBrackets,
+ /** Floor brackets, left command "lfloor" */
+ FloorBrackets
+};
+
+/** A list of nodes */
+typedef std::list<SmNode*> SmNodeList;
+
+class SmDocShell;
+
+/** Formula cursor
+ *
+ * This class is used to represent a cursor in a formula, which can be used to manipulate
+ * an formula programmatically.
+ * @remarks This class is a very intimite friend of SmDocShell.
+ */
+class SmCursor{
+public:
+ SmCursor(SmNode* tree, SmDocShell* pShell){
+ //Initialize members
+ pTree = tree;
+ anchor = NULL;
+ position = NULL;
+ pGraph = NULL;
+ pDocShell = pShell;
+ pClipboard = NULL;
+ nEditSections = 0;
+ //Build graph
+ BuildGraph();
+ }
+
+ ~SmCursor(){
+ SetClipboard();
+ if(pGraph)
+ delete pGraph;
+ pGraph = NULL;
+ }
+
+ /** Gets the anchor */
+ SmCaretPos GetAnchor(){ return anchor->CaretPos; }
+
+ /** Get position */
+ SmCaretPos GetPosition() { return position->CaretPos; }
+
+ /** True, if the cursor has a selection */
+ bool HasSelection() { return anchor != position; }
+
+ /** Move the position of this cursor */
+ void Move(OutputDevice* pDev, SmMovementDirection direction, bool bMoveAnchor = true);
+
+ /** Move to the caret position closet to a given point */
+ void MoveTo(OutputDevice* pDev, Point pos, bool bMoveAnchor = true);
+
+ /** Delete the current selection or do nothing */
+ void Delete();
+
+ /** Insert text at the current position */
+ void InsertText(XubString aString);
+
+ /** Insert an element into the formula */
+ void InsertElement(SmFormulaElement element);
+
+ /** Insert a command specified in commands.src*/
+ void InsertCommand(USHORT nCommand);
+
+ /** Insert command text translated into line entries at position
+ *
+ * Note: This method uses the parser to translate a command text into a
+ * tree, then it copies line entries from this tree into the current tree.
+ * Will not work for commands such as newline or ##, if position is in a matrix.
+ * This will work for stuff like "A intersection B". But stuff spaning multiple lines
+ * or dependent on the context which position is placed in will not work!
+ */
+ void InsertCommandText(String aCommandText);
+
+ /** Insert a special node created from aString
+ *
+ * Used for handling insert request from the "catalog" dialog.
+ * The provided string should be formatet as the desired command: %phi
+ * Note: this method ONLY supports commands defined in Math.xcu
+ *
+ * For more complex expressions use InsertCommandText, this method doesn't
+ * use SmParser, this means that it's faster, but not as strong.
+ */
+ void InsertSpecial(XubString aString);
+
+ /** Create sub-/super script
+ *
+ * If there's a selection, it will be move into the appropriate sub-/super scription
+ * of the node infront of it. If there's no node infront of position (or the selection),
+ * a sub-/super scription of a new SmPlaceNode will be made.
+ *
+ * If there's is an existing subscription of the node, the caret will be moved into it,
+ * and any selection will replace it.
+ */
+ void InsertSubSup(SmSubSup eSubSup);
+
+ /** Create a limit on an SmOperNode
+ *
+ * This this method only work if the caret is inside an SmOperNode, or to the right of one.
+ * Notice also that this method ignores any selection made.
+ *
+ * @param bMoveCaret If true that caret will be moved into the limit.
+ *
+ * @returns True, if the caret was in a context where this operation was possible.
+ */
+ BOOL InsertLimit(SmSubSup eSubSup, BOOL bMoveCaret = TRUE);
+
+ /** Insert a new row or newline
+ *
+ * Inserts a new row if position is in an matrix or stack command.
+ * Otherwise a newline is inserted if we're in a toplevel line.
+ *
+ * @returns True, if a new row/line could be inserted.
+ *
+ * @remarks If the caret is placed in a subline of a command that doesn't support
+ * this operator the method returns FALSE, and doesn't do anything.
+ */
+ BOOL InsertRow();
+
+ /** Insert a fraction, use selection as numerator */
+ void InsertFraction();
+
+ /** Create brackets around current selection, or new SmPlaceNode */
+ void InsertBrackets(SmBracketType eBracketType);
+
+ /** Copy the current selection */
+ void Copy();
+ /** Cut the current selection */
+ void Cut(){
+ Copy();
+ Delete();
+ }
+ /** Paste the clipboard */
+ void Paste();
+
+ /** Returns true if more than one node is selected
+ *
+ * This method is used for implementing backspace and delete.
+ * If one of these causes a complex selection, e.g. a node with
+ * subnodes or similar, this should not be deleted imidiately.
+ */
+ bool HasComplexSelection();
+
+ /** Finds the topmost node in a visual line
+ *
+ * If MoveUpIfSelected is true, this will move up to the parent line
+ * if the parent of the current line is selected.
+ */
+ static SmNode* FindTopMostNodeInLine(SmNode* pSNode, bool MoveUpIfSelected = false);
+
+ /** Draw the caret */
+ void Draw(OutputDevice& pDev, Point Offset);
+
+private:
+ friend class SmDocShell;
+
+ SmCaretPosGraphEntry *anchor,
+ *position;
+ /** Formula tree */
+ SmNode* pTree;
+ /** Owner of the formula tree */
+ SmDocShell* pDocShell;
+ /** Graph over caret position in the current tree */
+ SmCaretPosGraph* pGraph;
+ /** Clipboard holder */
+ SmNodeList* pClipboard;
+
+ /** Returns a node that is selected, if any could be found */
+ SmNode* FindSelectedNode(SmNode* pNode);
+
+ /** Is this one of the nodes used to compose a line
+ *
+ * These are SmExpression, SmBinHorNode, SmUnHorNode etc.
+ */
+ static bool IsLineCompositionNode(SmNode* pNode);
+
+ /** Count number of selected nodes, excluding line composition nodes
+ *
+ * Note this function doesn't count line composition nodes and it
+ * does count all subnodes as well as the owner nodes.
+ *
+ * Used by SmCursor::HasComplexSelection()
+ */
+ int CountSelectedNodes(SmNode* pNode);
+
+ /** Convert a visual line to a list
+ *
+ * Note this method will delete all the nodes that will no longer be needed.
+ * that includes pLine!
+ * This method also deletes SmErrorNode's as they're just meta info in the line.
+ */
+ static SmNodeList* LineToList(SmStructureNode* pLine, SmNodeList* pList = new SmNodeList());
+
+ /** Clone a visual line to a list
+ *
+ * Doesn't clone SmErrorNode's these are ignored, as they are context dependent metadata.
+ */
+ static SmNodeList* CloneLineToList(SmStructureNode* pLine,
+ bool bOnlyIfSelected = false,
+ SmNodeList* pList = new SmNodeList());
+
+ /** Build pGraph over caret positions */
+ void BuildGraph();
+
+ /** Insert new nodes in the tree after position */
+ void InsertNodes(SmNodeList* pNewNodes);
+
+ /** tries to set position to a specific SmCaretPos
+ *
+ * @returns false on failure to find the position in pGraph.
+ */
+ bool SetCaretPosition(SmCaretPos pos, bool moveAnchor = false);
+
+ /** Set selected on nodes of the tree */
+ void AnnotateSelection();
+
+ /** Set the clipboard, and release current clipboard
+ *
+ * Call this method with NULL to reset the clipboard
+ * @remarks: This method takes ownership of pList.
+ */
+ void SetClipboard(SmNodeList* pList = NULL);
+
+ /** Clone list of nodes (creates a deep clone) */
+ static SmNodeList* CloneList(SmNodeList* pList);
+
+ /** Find an iterator pointing to the node in pLineList following aCaretPos
+ *
+ * If aCaretPos::pSelectedNode cannot be found it is assumed that it's infront of pLineList,
+ * thus not an element in pLineList. In this case this method returns an iterator to the
+ * first element in pLineList.
+ *
+ * If the current position is inside an SmTextNode, this node will be split in two, for this
+ * reason you should beaware that iterators to elements in pLineList may be invalidated, and
+ * that you should call PatchLineList() with this iterator if no action is taken.
+ */
+ static SmNodeList::iterator FindPositionInLineList(SmNodeList* pLineList, SmCaretPos aCaretPos);
+
+ /** Patch a line list after modification, merge SmTextNode, remove SmPlaceNode etc.
+ *
+ * @param pLineList The line list to patch
+ * @param aIter Iterator pointing to the element that needs to be patched with it's previous.
+ *
+ * When the list is patched text nodes before and after aIter will be merged.
+ * If there's an, in the context, inappropriate SmPlaceNode before or after aIter it will also be
+ * removed.
+ *
+ * @returns A caret position equivalent to one selecting the node before aIter, the method returns
+ * an invalid SmCaretPos to indicate placement infront of the line.
+ */
+ static SmCaretPos PatchLineList(SmNodeList* pLineList, SmNodeList::iterator aIter);
+
+ /** Take selected nodes from a list
+ *
+ * Puts the selected nodes into pSelectedNodes, or if pSelectedNodes is NULL deletes
+ * the selected nodes.
+ * Note: If there's a selection inside an SmTextNode this node will be split, and it
+ * will not be merged when the selection have been taken. Use PatchLineList on the
+ * iterator returns to fix this.
+ *
+ * @returns An iterator pointing to the element following the selection taken.
+ */
+ static SmNodeList::iterator TakeSelectedNodesFromList(SmNodeList *pLineList,
+ SmNodeList *pSelectedNodes = NULL);
+
+ /** Create an instance of SmMathSymbolNode usable for brackets */
+ static SmNode *CreateBracket(SmBracketType eBracketType, BOOL bIsLeft);
+
+ /** The number of times BeginEdit have been called
+ * Used to allow nesting of BeginEdit() and EndEdit() sections
+ */
+ int nEditSections;
+ /** Holds data for BeginEdit() and EndEdit() */
+ BOOL bIsEnabledSetModifiedSmDocShell;
+ /** Begin edit section where the tree will be modified */
+ void BeginEdit();
+ /** End edit section where the tree will be modified */
+ void EndEdit();
+ /** Finish editing
+ *
+ * Finishes editing by parsing pLineList and inserting back into pParent at nParentIndex.
+ * This method also rebuilts the graph, annotates the selection, sets caret position and
+ * Calls EndEdit.
+ *
+ * @remarks Please note that this method will delete pLineList, as the elements are taken.
+ *
+ * @param pLineList List the constitutes the edited line.
+ * @param pParent Parent to which the line should be inserted.
+ * @param nParentIndex Index in parent where the line should be inserted.
+ * @param PosAfterEdit Caret position to look for after rebuilding graph.
+ * @param pStartLine Line to take first position in, if PosAfterEdit cannot be found,
+ * leave it NULL for pLineList.
+ */
+ void FinishEdit(SmNodeList* pLineList,
+ SmStructureNode* pParent,
+ int nParentIndex,
+ SmCaretPos PosAfterEdit,
+ SmNode* pStartLine = NULL);
+ /** Request the formula is repainted */
+ void RequestRepaint();
+};
+
+/** Minimalistic recursive decent SmNodeList parser
+ *
+ * This parser is used to take a list of nodes that constitues a line
+ * and parse them to a tree of SmBinHorNode, SmUnHorNode and SmExpression.
+ *
+ * Please note, this will not handle all kinds of nodes, only nodes that
+ * constitues and entry in a line.
+ *
+ * Below is an EBNF representation of the grammar used for this parser:
+ * \code
+ * Expression -> Relation*
+ * Relation -> Sum [(=|<|>|...) Sum]*
+ * Sum -> Product [(+|-) Product]*
+ * Product -> Factor [(*|/) Factor]*
+ * Factor -> [+|-|-+|...]* Factor | Postfix
+ * Postfix -> node [!]*
+ * \endcode
+ */
+class SmNodeListParser{
+public:
+ /** Create an instance of SmNodeListParser */
+ SmNodeListParser(){
+ pList = NULL;
+ }
+ /** Parse a list of nodes to an expression
+ *
+ * If bDeleteErrorNodes is true, old error nodes will be deleted.
+ */
+ SmNode* Parse(SmNodeList* list, bool bDeleteErrorNodes = true);
+ /** True, if the token is an operator */
+ static BOOL IsOperator(const SmToken &token);
+ /** True, if the token is a relation operator */
+ static BOOL IsRelationOperator(const SmToken &token);
+ /** True, if the token is a sum operator */
+ static BOOL IsSumOperator(const SmToken &token);
+ /** True, if the token is a product operator */
+ static BOOL IsProductOperator(const SmToken &token);
+ /** True, if the token is a unary operator */
+ static BOOL IsUnaryOperator(const SmToken &token);
+ /** True, if the token is a postfix operator */
+ static BOOL IsPostfixOperator(const SmToken &token);
+private:
+ SmNodeList* pList;
+ /** Get the current terminal */
+ SmNode* Terminal(){
+ if(pList->size() > 0)
+ return pList->front();
+ return NULL;
+ }
+ /** Move to next terminal */
+ SmNode* Next(){
+ pList->pop_front();
+ return Terminal();
+ }
+ /** Take the current terminal */
+ SmNode* Take(){
+ SmNode* pRetVal = Terminal();
+ Next();
+ return pRetVal;
+ }
+ SmNode* Expression();
+ SmNode* Relation();
+ SmNode* Sum();
+ SmNode* Product();
+ SmNode* Factor();
+ SmNode* Postfix();
+ SmNode* Error();
+};
+
+
+#endif /* SMCURSOR_H */
diff --git a/starmath/inc/document.hxx b/starmath/inc/document.hxx
index fc3bc13b66c4..6f73e2244009 100644
--- a/starmath/inc/document.hxx
+++ b/starmath/inc/document.hxx
@@ -48,6 +48,7 @@ class SmNode;
class SfxMenuBarManager;
class SfxPrinter;
class Printer;
+class SmCursor;
#define HINT_DATACHANGED 1004
@@ -108,6 +109,7 @@ class SmDocShell : public SfxObjectShell, public SfxListener
{
friend class SmPrinterAccess;
friend class SmModel;
+ friend class SmCursor;
String aText;
SmFormat aFormat;
@@ -125,6 +127,7 @@ class SmDocShell : public SfxObjectShell, public SfxListener
nBottomBorder;
USHORT nModifyCount;
BOOL bIsFormulaArranged;
+ SmCursor *pCursor;
@@ -164,6 +167,11 @@ class SmDocShell : public SfxObjectShell, public SfxListener
virtual BOOL ConvertFrom(SfxMedium &rMedium);
+ /** Called whenever the formula is changed
+ * Deletes the current cursor
+ */
+ void InvalidateCursor();
+
public:
TYPEINFO();
SFX_DECL_INTERFACE(SFX_INTERFACE_SMA_START+1)
@@ -207,7 +215,7 @@ public:
EditEngine & GetEditEngine();
SfxItemPool & GetEditEngineItemPool();
- void Draw(OutputDevice &rDev, Point &rPosition);
+ void DrawFormula(OutputDevice &rDev, Point &rPosition, BOOL bDrawSelection = FALSE);
Size GetSize();
void Repaint();
@@ -221,6 +229,15 @@ public:
virtual void SetVisArea (const Rectangle & rVisArea);
virtual void SetModified(BOOL bModified);
+
+ /** Get a cursor for modifying this document
+ * @remarks Don't store this reference, a new cursor may be made...
+ */
+ SmCursor& GetCursor();
+ /** True, if cursor have previously been requested and thus
+ * has some sort of position.
+ */
+ BOOL HasCursor() { return pCursor != NULL; }
};
#endif
diff --git a/starmath/inc/edit.hxx b/starmath/inc/edit.hxx
index b94a8a4dfb31..62b74acd2cdf 100644
--- a/starmath/inc/edit.hxx
+++ b/starmath/inc/edit.hxx
@@ -65,15 +65,13 @@ class SmEditWindow : public Window, public DropTargetHelper
ScrollBar *pHScrollBar,
*pVScrollBar;
ScrollBarBox *pScrollBox;
- Timer aModifyTimer,
- aCursorMoveTimer;
+ Timer aModifyTimer;
ESelection aOldSelection;
virtual void KeyInput(const KeyEvent& rKEvt);
virtual void Command(const CommandEvent& rCEvt);
DECL_LINK(MenuSelectHdl, Menu *);
DECL_LINK(ModifyTimerHdl, Timer *);
- DECL_LINK(CursorMoveTimerHdl, Timer *);
virtual void DataChanged( const DataChangedEvent& );
virtual void Resize();
diff --git a/starmath/inc/node.hxx b/starmath/inc/node.hxx
index ab4f633e417f..0ff97fd12a77 100644
--- a/starmath/inc/node.hxx
+++ b/starmath/inc/node.hxx
@@ -30,6 +30,26 @@
#define NODE_HXX
#include <vector>
+#include <fstream>
+#include <iostream>
+#include <stdio.h>
+
+//My special assert macro
+//TODO: replace this with DBG_ASSERT when this patch moves to production, can be done using search/replace
+#define j_assert(cond, msg) do{ \
+ if(!(cond)) \
+ { \
+ std::cerr<<"Failed assertion: "<<msg<<", at line "; \
+ char* f = (char*)__FILE__; \
+ f += strlen(f); \
+ do f--; while(*f != '/'); \
+ do f--; while(*f != '/'); \
+ do f--; while(*f != '/'); \
+ fprintf(stderr, "%d in %s\n", __LINE__, f + 1); \
+ } \
+ } while(false)
+//TODO: Comment out below to disable dumpasdot
+#define DEBUG_ENABLE_DUMPASDOT
#include "parse.hxx"
#include "types.hxx"
@@ -40,6 +60,7 @@
#define ATTR_BOLD 0x0001
#define ATTR_ITALIC 0x0002
+
#define FNTSIZ_ABSOLUT 1
#define FNTSIZ_PLUS 2
#define FNTSIZ_MINUS 3
@@ -58,6 +79,7 @@
extern SmFormat *pActiveFormat;
+class SmVisitor;
class SmDocShell;
class SmNode;
class SmStructureNode;
@@ -94,6 +116,7 @@ class SmNode : public SmRect
RectHorAlign eRectHorAlign;
USHORT nFlags, nAttributes;
BOOL bIsPhantom, bIsDebug;
+
protected:
SmNode(SmNodeType eNodeType, const SmToken &rNodeToken);
@@ -157,7 +180,6 @@ public:
#ifdef SM_RECT_DEBUG
using SmRect::Draw;
#endif
- virtual void Draw(OutputDevice &rDev, const Point &rPosition) const;
virtual void GetAccessibleText( String &rText ) const;
sal_Int32 GetAccessibleIndex() const { return nAccIndex; }
@@ -177,12 +199,128 @@ public:
const SmNode * FindTokenAt(USHORT nRow, USHORT nCol) const;
const SmNode * FindRectClosestTo(const Point &rPoint) const;
-};
+ /** Accept a visitor
+ * Calls the method for this class on the visitor
+ */
+ virtual void Accept(SmVisitor* pVisitor);
+
+ /** True if this node is selected */
+ BOOL IsSelected() const {return bIsSelected;}
+ void SetSelected(BOOL Selected = true) {bIsSelected = Selected;}
+
+#ifdef DEBUG_ENABLE_DUMPASDOT
+ /** The tree as dot graph for graphviz, usable for debugging
+ * Convert the output to a image using $ dot graph.gv -Tpng > graph.png
+ */
+ inline void DumpAsDot(std::ostream &out, String* label = NULL) const{
+ int id = 0;
+ DumpAsDot(out, label, -1, id, -1);
+ }
+#endif /* DEBUG_ENABLE_DUMPASDOT */
+
+ /** Get the parent node of this node */
+ SmStructureNode* GetParent(){ return aParentNode; }
+ /** Set the parent node */
+ void SetParent(SmStructureNode* parent){
+ aParentNode = parent;
+ }
+
+ /** Get the index of a child node
+ *
+ * Returns -1, if pSubNode isn't a subnode of this.
+ */
+ int IndexOfSubNode(SmNode* pSubNode){
+ USHORT nSize = GetNumSubNodes();
+ for(USHORT i = 0; i < nSize; i++)
+ if(pSubNode == GetSubNode(i))
+ return i;
+ return -1;
+ }
+ /** Set the token for this node */
+ void SetToken(SmToken& token){
+ aNodeToken = token;
+ }
+protected:
+ /** Sets parent on children of this node */
+ void ClaimPaternity(){
+ SmNode* pNode;
+ USHORT nSize = GetNumSubNodes();
+ for (USHORT i = 0; i < nSize; i++)
+ if (NULL != (pNode = GetSubNode(i)))
+ pNode->SetParent((SmStructureNode*)this); //Cast is valid if we have children
+ }
+private:
+ SmStructureNode* aParentNode;
+ void DumpAsDot(std::ostream &out, String* label, int number, int& id, int parent) const;
+};
////////////////////////////////////////////////////////////////////////////////
+/** A simple auxiliary iterator class for SmNode
+ *
+ * Example of iteration over children of pMyNode:
+ * \code
+ * //Node to iterate over:
+ * SmNode* pMyNode = 0;// A pointer from somewhere
+ * //The iterator:
+ * SmNodeIterator it(pMyNode);
+ * //The iteration:
+ * while(it.Next()) {
+ * it->SetSelected(true);
+ * }
+ * \endcode
+ */
+class SmNodeIterator{
+public:
+ SmNodeIterator(SmNode* node, bool bReverse = false){
+ pNode = node;
+ nSize = pNode->GetNumSubNodes();
+ nIndex = 0;
+ pChildNode = NULL;
+ bIsReverse = bReverse;
+ }
+ /** Get the subnode or NULL if none */
+ SmNode* Next(){
+ while(!bIsReverse && nIndex < nSize){
+ if(NULL != (pChildNode = pNode->GetSubNode(nIndex++)))
+ return pChildNode;
+ }
+ while(bIsReverse && nSize > 0){
+ if(NULL != (pChildNode = pNode->GetSubNode((nSize--)-1)))
+ return pChildNode;
+ }
+ pChildNode = NULL;
+ return NULL;
+ }
+ /** Get the current child node, NULL if none */
+ SmNode* Current(){
+ return pChildNode;
+ }
+ /** Get the current child node, NULL if none */
+ SmNode* operator->(){
+ return pChildNode;
+ }
+private:
+ /** Current child */
+ SmNode* pChildNode;
+ /** Node whos children we're iterating over */
+ SmNode* pNode;
+ /** Size of the node */
+ USHORT nSize;
+ /** Current index in the node */
+ USHORT nIndex;
+ /** Move reverse */
+ bool bIsReverse;
+};
+
+////////////////////////////////////////////////////////////////////////////////
+/** Abstract baseclass for all composite node
+ *
+ * Subclasses of this class can have subnodes. Nodes that doesn't derivate from
+ * this class does not have subnodes.
+ */
class SmStructureNode : public SmNode
{
SmNodeArray aSubNodes;
@@ -209,12 +347,29 @@ public:
virtual SmStructureNode & operator = ( const SmStructureNode &rNode );
virtual void GetAccessibleText( String &rText ) const;
+
+ void SetSubNode(USHORT nIndex, SmNode* pNode){
+ int size = aSubNodes.size();
+ if(size <= nIndex){
+ //Resize subnodes array
+ aSubNodes.resize(nIndex + 1);
+ //Set new slots to NULL
+ for(int i = size; i < nIndex+1; i++)
+ aSubNodes[i] = NULL;
+ }
+ aSubNodes[nIndex] = pNode;
+ ClaimPaternity();
+ }
};
////////////////////////////////////////////////////////////////////////////////
-
+/** Abstract base class for all visible node
+ *
+ * Nodes that doesn't derivate from this class doesn't draw anything, but their
+ * children.
+ */
class SmVisibleNode : public SmNode
{
protected:
@@ -249,7 +404,10 @@ public:
////////////////////////////////////////////////////////////////////////////////
-
+/** Draws a rectangle
+ *
+ * Used for drawing the line in the OVER and OVERSTRIKE commands.
+ */
class SmRectangleNode : public SmGraphicNode
{
Size aToSize;
@@ -267,15 +425,18 @@ public:
#ifdef SM_RECT_DEBUG
using SmRect::Draw;
#endif
- virtual void Draw(OutputDevice &rDev, const Point &rPosition) const;
void CreateTextFromNode(String &rText);
+ void Accept(SmVisitor* pVisitor);
};
////////////////////////////////////////////////////////////////////////////////
-
+/** Polygon line node
+ *
+ * Used to draw the slash of the WIDESLASH command by SmBinDiagonalNode.
+ */
class SmPolyLineNode : public SmGraphicNode
{
Polygon aPoly;
@@ -286,6 +447,8 @@ public:
SmPolyLineNode(const SmToken &rNodeToken);
long GetWidth() const { return nWidth; }
+ Size GetToSize() const { return aToSize; }
+ Polygon &GetPolygon() { return aPoly; }
virtual void AdaptToX(const OutputDevice &rDev, ULONG nWidth);
virtual void AdaptToY(const OutputDevice &rDev, ULONG nHeight);
@@ -295,17 +458,29 @@ public:
#ifdef SM_RECT_DEBUG
using SmRect::Draw;
#endif
- virtual void Draw(OutputDevice &rDev, const Point &rPosition) const;
+
+ void Accept(SmVisitor* pVisitor);
};
////////////////////////////////////////////////////////////////////////////////
-
+/** Text node
+ *
+ * @remarks This class also serves as baseclass for all nodes that contains text.
+ */
class SmTextNode : public SmVisibleNode
{
XubString aText;
USHORT nFontDesc;
+ /** Index within text where the selection starts
+ * @remarks Only valid if SmNode::IsSelected() is true
+ */
+ xub_StrLen nSelectionStart;
+ /** Index within text where the selection ends
+ * @remarks Only valid if SmNode::IsSelected() is true
+ */
+ xub_StrLen nSelectionEnd;
protected:
SmTextNode(SmNodeType eNodeType, const SmToken &rNodeToken, USHORT nFontDescP );
@@ -316,6 +491,28 @@ public:
USHORT GetFontDesc() const { return nFontDesc; }
void SetText(const XubString &rText) { aText = rText; }
const XubString & GetText() const { return aText; }
+ /** Change the text of this node, including the underlying token */
+ void ChangeText(const XubString &rText) {
+ aText = rText;
+ SmToken token = GetToken();
+ token.aText = rText;
+ SetToken(token); //TODO: Merge this with AdjustFontDesc for better performance
+ AdjustFontDesc();
+ }
+ /** Try to guess the correct FontDesc, used during visual editing */
+ void AdjustFontDesc();
+ /** Index within GetText() where the selection starts
+ * @remarks Only valid of SmNode::IsSelected() is true
+ */
+ xub_StrLen GetSelectionStart() const {return nSelectionStart;}
+ /** Index within GetText() where the selection end
+ * @remarks Only valid of SmNode::IsSelected() is true
+ */
+ xub_StrLen GetSelectionEnd() const {return nSelectionEnd;}
+ /** Set the index within GetText() where the selection starts */
+ void SetSelectionStart(xub_StrLen index) {nSelectionStart = index;}
+ /** Set the index within GetText() where the selection end */
+ void SetSelectionEnd(xub_StrLen index) {nSelectionEnd = index;}
virtual void Prepare(const SmFormat &rFormat, const SmDocShell &rDocShell);
virtual void Arrange(const OutputDevice &rDev, const SmFormat &rFormat);
@@ -324,15 +521,22 @@ public:
#ifdef SM_RECT_DEBUG
using SmRect::Draw;
#endif
- virtual void Draw(OutputDevice &rDev, const Point &rPosition) const;
+
virtual void GetAccessibleText( String &rText ) const;
+ void Accept(SmVisitor* pVisitor);
};
////////////////////////////////////////////////////////////////////////////////
-
+/** Special node for user defined characters
+ *
+ * Node used for pre- and user-defined characters from:
+ * officecfg/registry/data/org/openoffice/Office/Math.xcu
+ *
+ * This is just single characters, I think.
+ */
class SmSpecialNode : public SmTextNode
{
bool bIsFromGreekSymbolSet;
@@ -349,13 +553,22 @@ public:
#ifdef SM_RECT_DEBUG
using SmRect::Draw;
#endif
- virtual void Draw(OutputDevice &rDev, const Point &rPosition) const;
+
+ void Accept(SmVisitor* pVisitor);
};
////////////////////////////////////////////////////////////////////////////////
-
+/** Glyph node for custom operators
+ *
+ * This node is used with commands: oper, uoper and boper.
+ * E.g. in "A boper op B", "op" will be an instance of SmGlyphSpecialNode.
+ * "boper" simply inteprets "op", the following token, as an binary operator.
+ * The command "uoper" interprets the following token as unary operator.
+ * For these commands an instance of SmGlyphSpecialNode is used for the
+ * operator token, following the command.
+ */
class SmGlyphSpecialNode : public SmSpecialNode
{
public:
@@ -364,12 +577,16 @@ public:
{}
virtual void Arrange(const OutputDevice &rDev, const SmFormat &rFormat);
+ void Accept(SmVisitor* pVisitor);
};
////////////////////////////////////////////////////////////////////////////////
-
+/** Math symbol node
+ *
+ * Use for math symbols such as plus, minus and integrale in the INT command.
+ */
class SmMathSymbolNode : public SmSpecialNode
{
protected:
@@ -390,12 +607,18 @@ public:
virtual void Prepare(const SmFormat &rFormat, const SmDocShell &rDocShell);
virtual void Arrange(const OutputDevice &rDev, const SmFormat &rFormat);
void CreateTextFromNode(String &rText);
+ void Accept(SmVisitor* pVisitor);
};
////////////////////////////////////////////////////////////////////////////////
-
+/** Root symbol node
+ *
+ * Root symbol node used by SmRootNode to create the root symbol, infront of
+ * the line with the line above. I don't think this node should be used for
+ * anything else.
+ */
class SmRootSymbolNode : public SmMathSymbolNode
{
ULONG nBodyWidth; // width of body (argument) of root sign
@@ -405,19 +628,26 @@ public:
: SmMathSymbolNode(NROOTSYMBOL, rNodeToken)
{}
+ ULONG GetBodyWidth() const {return nBodyWidth;};
virtual void AdaptToX(const OutputDevice &rDev, ULONG nWidth);
virtual void AdaptToY(const OutputDevice &rDev, ULONG nHeight);
#ifdef SM_RECT_DEBUG
using SmRect::Draw;
#endif
- virtual void Draw(OutputDevice &rDev, const Point &rPosition) const;
+
+ void Accept(SmVisitor* pVisitor);
};
////////////////////////////////////////////////////////////////////////////////
-
+/** Place node
+ *
+ * Used to create the <?> command, that denotes place where something can be
+ * written.
+ * It is drawn as a square with a shadow.
+ */
class SmPlaceNode : public SmMathSymbolNode
{
public:
@@ -425,15 +655,21 @@ public:
: SmMathSymbolNode(NPLACE, rNodeToken)
{
}
+ SmPlaceNode() : SmMathSymbolNode(NPLACE, SmToken(TPLACE, MS_PLACE, "<?>")) {};
virtual void Prepare(const SmFormat &rFormat, const SmDocShell &rDocShell);
virtual void Arrange(const OutputDevice &rDev, const SmFormat &rFormat);
+ void Accept(SmVisitor* pVisitor);
};
////////////////////////////////////////////////////////////////////////////////
-
+/** Error node, for parsing errors
+ *
+ * This node is used for parsing errors and draws an questionmark turned upside
+ * down (inverted question mark).
+ */
class SmErrorNode : public SmMathSymbolNode
{
public:
@@ -445,12 +681,19 @@ public:
virtual void Prepare(const SmFormat &rFormat, const SmDocShell &rDocShell);
virtual void Arrange(const OutputDevice &rDev, const SmFormat &rFormat);
+ void Accept(SmVisitor* pVisitor);
};
////////////////////////////////////////////////////////////////////////////////
-
+/** Table node
+ *
+ * This is the root node for the formula tree. This node is also used for the
+ * STACK and BINOM commands. When used for root node, its
+ * children are instances of SmLineNode, and in some obscure cases the a child
+ * can be an instance of SmExpressionNode, mainly when errors occur.
+ */
class SmTableNode : public SmStructureNode
{
public:
@@ -462,12 +705,17 @@ public:
virtual SmNode * GetLeftMost();
virtual void Arrange(const OutputDevice &rDev, const SmFormat &rFormat);
+ void Accept(SmVisitor* pVisitor);
};
////////////////////////////////////////////////////////////////////////////////
-
+/** A line
+ *
+ * Used as child of SmTableNode when the SmTableNode is the root node of the
+ * formula tree.
+ */
class SmLineNode : public SmStructureNode
{
BOOL bUseExtraSpaces;
@@ -491,12 +739,18 @@ public:
virtual void Prepare(const SmFormat &rFormat, const SmDocShell &rDocShell);
virtual void Arrange(const OutputDevice &rDev, const SmFormat &rFormat);
+ void Accept(SmVisitor* pVisitor);
};
////////////////////////////////////////////////////////////////////////////////
-
+/** Expression node
+ *
+ * Used whenever you have an expression such as "A OVER {B + C}", here there is
+ * an expression node that allows "B + C" to be the denominator of the
+ * SmBinVerNode, that the OVER command creates.
+ */
class SmExpressionNode : public SmLineNode
{
public:
@@ -506,12 +760,16 @@ public:
virtual void Arrange(const OutputDevice &rDev, const SmFormat &rFormat);
void CreateTextFromNode(String &rText);
+ void Accept(SmVisitor* pVisitor);
};
////////////////////////////////////////////////////////////////////////////////
-
+/** Unary horizontical node
+ *
+ * The same as SmBinHorNode except this is for unary operators.
+ */
class SmUnHorNode : public SmStructureNode
{
public:
@@ -522,12 +780,23 @@ public:
}
virtual void Arrange(const OutputDevice &rDev, const SmFormat &rFormat);
+ void Accept(SmVisitor* pVisitor);
};
////////////////////////////////////////////////////////////////////////////////
-
+/** Root node
+ *
+ * Used for create square roots and other roots, example:
+ * \f$ \sqrt[\mbox{[Argument]}]{\mbox{[Body]}} \f$.
+ *
+ * Children:<BR>
+ * 0: Argument (optional)<BR>
+ * 1: Symbol (instance of SmRootSymbolNode)<BR>
+ * 2: Body<BR>
+ * Where argument is optinal and may be NULL.
+ */
class SmRootNode : public SmStructureNode
{
protected:
@@ -544,12 +813,23 @@ public:
virtual void Arrange(const OutputDevice &rDev, const SmFormat &rFormat);
void CreateTextFromNode(String &rText);
+ void Accept(SmVisitor* pVisitor);
};
////////////////////////////////////////////////////////////////////////////////
-
+/** Binary horizontial node
+ *
+ * This node is used for binary operators. In a formula such as "A + B".
+ *
+ * Children:<BR>
+ * 0: Left operand<BR>
+ * 1: Binary operator<BR>
+ * 2: Right operand<BR>
+ *
+ * None of the children may be NULL.
+ */
class SmBinHorNode : public SmStructureNode
{
public:
@@ -560,12 +840,24 @@ public:
}
virtual void Arrange(const OutputDevice &rDev, const SmFormat &rFormat);
+ void Accept(SmVisitor* pVisitor);
};
////////////////////////////////////////////////////////////////////////////////
-
+/** Binary horizontical node
+ *
+ * This node is used for creating the OVER command, consider the formula:
+ * "numerator OVER denominator", which looks like
+ * \f$ \frac{\mbox{numerator}}{\mbox{denominator}} \f$
+ *
+ * Children:<BR>
+ * 0: Numerator<BR>
+ * 1: Line (instance of SmRectangleNode)<BR>
+ * 2: Denominator<BR>
+ * None of the children may be NULL.
+ */
class SmBinVerNode : public SmStructureNode
{
public:
@@ -580,12 +872,22 @@ public:
virtual void Arrange(const OutputDevice &rDev, const SmFormat &rFormat);
void CreateTextFromNode(String &rText);
+ void Accept(SmVisitor* pVisitor);
};
////////////////////////////////////////////////////////////////////////////////
-
+/** Binary diagonal node
+ *
+ * Used for implementing the WIDESLASH command, example: "A WIDESLASH B".
+ *
+ * Children:<BR>
+ * 0: Left operand<BR>
+ * 1: right operand<BR>
+ * 2: Line (instance of SmPolyLineNode).<BR>
+ * None of the children may be NULL.
+ */
class SmBinDiagonalNode : public SmStructureNode
{
BOOL bAscending;
@@ -600,35 +902,53 @@ public:
void SetAscending(BOOL bVal) { bAscending = bVal; }
virtual void Arrange(const OutputDevice &rDev, const SmFormat &rFormat);
+ void Accept(SmVisitor* pVisitor);
};
////////////////////////////////////////////////////////////////////////////////
-// enums used to index sub-/supscripts in the 'aSubNodes' array
-// in 'SmSubSupNode'
-// See graphic for positions at char:
-//
-// CSUP
-//
-// LSUP H H RSUP
-// H H
-// HHHH
-// H H
-// LSUB H H RSUB
-//
-// CSUB
-//
+/** Enum used to index sub-/supscripts in the 'aSubNodes' array
+ * in 'SmSubSupNode'
+ *
+ * See graphic for positions at char:
+ *
+ * \code
+ * CSUP
+ *
+ * LSUP H H RSUP
+ * H H
+ * HHHH
+ * H H
+ * LSUB H H RSUB
+ *
+ * CSUB
+ * \endcode
+ */
enum SmSubSup
{ CSUB, CSUP, RSUB, RSUP, LSUB, LSUP
};
-// numbers of entries in the above enum (that is: the number of possible
-// sub-/supscripts)
+/** numbers of entries in the above enum (that is: the number of possible
+ * sub-/supscripts)
+ */
#define SUBSUP_NUM_ENTRIES 6
-
+/** Super- and subscript node
+ *
+ * Used for creating super- and subscripts for commands such as:
+ * "^", "_", "lsup", "lsub", "csup" and "csub".
+ * Example: "A^2" which looks like: \f$ A^2 \f$
+ *
+ * This node is also used for creating limits on SmOperNode, when
+ * "FROM" and "TO" commands are used with "INT", "SUM" or similar.
+ *
+ * Children of this node can be enumerated using the SmSubSup enum.
+ * Please note that children may be NULL, except for the body.
+ * It is recommended that you access children using GetBody() and
+ * GetSubSup().
+ */
class SmSubSupNode : public SmStructureNode
{
BOOL bUseLimits;
@@ -641,7 +961,9 @@ public:
bUseLimits = FALSE;
}
+ /** Get body (Not NULL) */
SmNode * GetBody() { return GetSubNode(0); }
+ /** Get body (Not NULL) */
const SmNode * GetBody() const
{
return ((SmSubSupNode *) this)->GetBody();
@@ -650,17 +972,39 @@ public:
void SetUseLimits(BOOL bVal) { bUseLimits = bVal; }
BOOL IsUseLimits() const { return bUseLimits; };
+ /** Get super- or subscript
+ * @remarks this method may return NULL.
+ */
SmNode * GetSubSup(SmSubSup eSubSup) { return GetSubNode( sal::static_int_cast< USHORT >(1 + eSubSup) ); };
+ /** Set the body */
+ void SetBody(SmNode* pBody) { SetSubNode(0, pBody); }
+ void SetSubSup(SmSubSup eSubSup, SmNode* pScript) { SetSubNode( 1 + eSubSup, pScript); }
+
virtual void Arrange(const OutputDevice &rDev, const SmFormat &rFormat);
void CreateTextFromNode(String &rText);
+ void Accept(SmVisitor* pVisitor);
};
////////////////////////////////////////////////////////////////////////////////
-
+/** Node for brace construction
+ *
+ * Used for "lbrace [body] rbrace" and similar constructions.
+ * Should look like \f$ \{\mbox{[body]}\} \f$
+ *
+ * Children:<BR>
+ * 0: Opening brace<BR>
+ * 1: Body (usually SmBracebodyNode)<BR>
+ * 2: Closing brace<BR>
+ * None of the children can be NULL.
+ *
+ * Note that child 1 (Body) is usually SmBracebodyNode, I don't know if it can
+ * be an SmExpressionNode, haven't seen the case. But didn't quite read parser.cxx
+ * enought to exclude this possibility.
+ */
class SmBraceNode : public SmStructureNode
{
public:
@@ -672,12 +1016,21 @@ public:
virtual void Arrange(const OutputDevice &rDev, const SmFormat &rFormat);
void CreateTextFromNode(String &rText);
+ void Accept(SmVisitor* pVisitor);
};
////////////////////////////////////////////////////////////////////////////////
-
+/** Body of an SmBraceNode
+ *
+ * This usually only has one child an SmExpressionNode, however, it can also
+ * have other children.
+ * Consider the formula "lbrace [body1] mline [body2] rbrace", looks like:
+ * \f$ \{\mbox{[body1] | [body2]}\} \f$.
+ * In this case SmBracebodyNode will have three children, "[body1]", "|" and
+ * [body2].
+ */
class SmBracebodyNode : public SmStructureNode
{
long nBodyHeight;
@@ -687,6 +1040,7 @@ public:
virtual void Arrange(const OutputDevice &rDev, const SmFormat &rFormat);
long GetBodyHeight() const { return nBodyHeight; }
+ void Accept(SmVisitor* pVisitor);
};
@@ -699,13 +1053,25 @@ inline SmBracebodyNode::SmBracebodyNode(const SmToken &rNodeToken) :
////////////////////////////////////////////////////////////////////////////////
-
+/** Node for vertical brace construction
+ *
+ * Used to implement commands "[body] underbrace [script]" and
+ * "[body] overbrace [script]".
+ * Underbrace should look like this \f$ \underbrace{\mbox{body}}_{\mbox{script}}\f$.
+ *
+ * Children:<BR>
+ * 0: body<BR>
+ * 1: brace<BR>
+ * 2: script<BR>
+ * (None of these children are optional, e.g. they must all be not NULL).
+ */
class SmVerticalBraceNode : public SmStructureNode
{
public:
inline SmVerticalBraceNode(const SmToken &rNodeToken);
virtual void Arrange(const OutputDevice &rDev, const SmFormat &rFormat);
+ void Accept(SmVisitor* pVisitor);
};
@@ -719,6 +1085,18 @@ inline SmVerticalBraceNode::SmVerticalBraceNode(const SmToken &rNodeToken) :
////////////////////////////////////////////////////////////////////////////////
+/** Operation Node
+ *
+ * Used for commands like SUM, INT and similar.
+ *
+ * Children:<BR>
+ * 0: Operation (instance of SmMathSymbolNode)<BR>
+ * 1: Body<BR>
+ * None of the children may be NULL.
+ *
+ * If there are boundaries on the operation the body will an instance of
+ * SmSubSupNode.
+ */
class SmOperNode : public SmStructureNode
{
public:
@@ -737,12 +1115,14 @@ public:
long CalcSymbolHeight(const SmNode &rSymbol, const SmFormat &rFormat) const;
virtual void Arrange(const OutputDevice &rDev, const SmFormat &rFormat);
+ void Accept(SmVisitor* pVisitor);
};
////////////////////////////////////////////////////////////////////////////////
-
+/** Node used for alignment
+ */
class SmAlignNode : public SmStructureNode
{
public:
@@ -751,12 +1131,22 @@ public:
{}
virtual void Arrange(const OutputDevice &rDev, const SmFormat &rFormat);
+ void Accept(SmVisitor* pVisitor);
};
////////////////////////////////////////////////////////////////////////////////
-
+/** Attribute node
+ *
+ * Used to give an attribute to another node. Used for commands such as:
+ * UNDERLINE, OVERLINE, OVERSTRIKE, WIDEVEC, WIDEHAT and WIDETILDE.
+ *
+ * Children:<BR>
+ * 0: Attribute<BR>
+ * 1: Body<BR>
+ * None of these may be NULL.
+ */
class SmAttributNode : public SmStructureNode
{
public:
@@ -766,12 +1156,16 @@ public:
virtual void Arrange(const OutputDevice &rDev, const SmFormat &rFormat);
void CreateTextFromNode(String &rText);
+ void Accept(SmVisitor* pVisitor);
};
////////////////////////////////////////////////////////////////////////////////
-
+/** Font node
+ *
+ * Used to change the font of it's children.
+ */
class SmFontNode : public SmStructureNode
{
USHORT nSizeType;
@@ -792,12 +1186,17 @@ public:
virtual void Prepare(const SmFormat &rFormat, const SmDocShell &rDocShell);
virtual void Arrange(const OutputDevice &rDev, const SmFormat &rFormat);
void CreateTextFromNode(String &rText);
+ void Accept(SmVisitor* pVisitor);
};
////////////////////////////////////////////////////////////////////////////////
-
+/** Matrix node
+ *
+ * Used to implement the MATRIX command, example:
+ * "matrix{ 1 # 2 ## 3 # 4}".
+ */
class SmMatrixNode : public SmStructureNode
{
USHORT nNumRows,
@@ -819,12 +1218,16 @@ public:
virtual void Arrange(const OutputDevice &rDev, const SmFormat &rFormat);
void CreateTextFromNode(String &rText);
+ void Accept(SmVisitor* pVisitor);
};
////////////////////////////////////////////////////////////////////////////////
-
+/** Node for whitespace
+ *
+ * Used to implement the "~" command. This node is just a blank space.
+ */
class SmBlankNode : public SmGraphicNode
{
USHORT nNum;
@@ -838,9 +1241,12 @@ public:
void IncreaseBy(const SmToken &rToken);
void Clear() { nNum = 0; }
+ USHORT GetBlankNum() const { return nNum; }
+ void SetBlankNum(USHORT nNumber) { nNum = nNumber; }
virtual void Prepare(const SmFormat &rFormat, const SmDocShell &rDocShell);
virtual void Arrange(const OutputDevice &rDev, const SmFormat &rFormat);
+ void Accept(SmVisitor* pVisitor);
};
diff --git a/starmath/inc/parse.hxx b/starmath/inc/parse.hxx
index f276d11c77a6..5018340b5a01 100644
--- a/starmath/inc/parse.hxx
+++ b/starmath/inc/parse.hxx
@@ -118,7 +118,7 @@ struct SmToken
String aText;
// token info
SmTokenType eType;
- sal_Unicode cMathChar;
+ sal_Unicode cMathChar;
// parse-help info
ULONG nGroup;
USHORT nLevel;
@@ -127,6 +127,11 @@ struct SmToken
xub_StrLen nCol;
SmToken();
+ SmToken(SmTokenType eTokenType,
+ sal_Unicode cMath,
+ const sal_Char* pText,
+ ULONG nTokenGroup = 0,
+ USHORT nTokenLevel = 0);
};
@@ -168,6 +173,14 @@ enum SmConvert
CONVERT_60_TO_50
};
+struct SmTokenTableEntry
+{
+ const sal_Char* pIdent;
+ SmTokenType eType;
+ sal_Unicode cMathChar;
+ ULONG nGroup;
+ USHORT nLevel;
+};
class SmParser
{
@@ -240,7 +253,10 @@ protected:
public:
SmParser();
+ /** Parse rBuffer to formula tree */
SmNode *Parse(const String &rBuffer);
+ /** Parse rBuffer to formula subtree that constitutes an expression */
+ SmNode *ParseExpression(const String &rBuffer);
const String & GetText() const { return BufferString; };
@@ -257,6 +273,7 @@ public:
const SmErrorDesc * NextError();
const SmErrorDesc * PrevError();
const SmErrorDesc * GetError(USHORT i = 0xFFFF);
+ static const SmTokenTableEntry* GetTokenTableEntry( const String &rName );
};
diff --git a/starmath/inc/starmath.hrc b/starmath/inc/starmath.hrc
index 8d156792e1d3..9be6defc3964 100644
--- a/starmath/inc/starmath.hrc
+++ b/starmath/inc/starmath.hrc
@@ -58,7 +58,9 @@
#define SID_TEXT (SID_SMA_START + 100)
#define SID_GAPHIC_SM (SID_SMA_START + 101)
#define SID_FITINWINDOW (SID_SMA_START + 103)
-#define SID_INSERTTEXT (SID_SMA_START + 104)
+/** Command for inserting a symbol specified by a string (Inserts an SmSpecialNode) */
+#define SID_INSERTSYMBOL (SID_SMA_START + 104)
+/** Command for inserting a math construction specified in commands.src */
#define SID_INSERTCOMMAND (SID_SMA_START + 105)
#define SID_LOADSYMBOLS (SID_SMA_START + 107)
diff --git a/starmath/inc/view.hxx b/starmath/inc/view.hxx
index 0cacffc76387..d1ad4a933750 100644
--- a/starmath/inc/view.hxx
+++ b/starmath/inc/view.hxx
@@ -52,7 +52,6 @@ class SmPrintUIOptions;
class SmGraphicWindow : public ScrollableWindow
{
Point aFormulaDrawPos;
- Rectangle aCursorRect;
::com::sun::star::uno::Reference<
::com::sun::star::accessibility::XAccessible > xAccessible;
@@ -61,14 +60,9 @@ class SmGraphicWindow : public ScrollableWindow
SmViewShell *pViewShell;
USHORT nZoom;
short nModifyCount;
- BOOL bIsCursorVisible;
protected:
void SetFormulaDrawPos(const Point &rPos) { aFormulaDrawPos = rPos; }
- void SetIsCursorVisible(BOOL bVis) { bIsCursorVisible = bVis; }
- using Window::SetCursor;
- void SetCursor(const SmNode *pNode);
- void SetCursor(const Rectangle &rRect);
virtual void DataChanged( const DataChangedEvent& );
virtual void Paint(const Rectangle&);
@@ -99,10 +93,6 @@ public:
using ScrollableWindow::SetTotalSize;
void SetTotalSize();
- BOOL IsCursorVisible() const { return bIsCursorVisible; }
- void ShowCursor(BOOL bShow);
- const SmNode * SetCursorPos(USHORT nRow, USHORT nCol);
-
void ApplyColorConfigValues( const svtools::ColorConfig &rColorCfg );
// for Accessibility
@@ -230,6 +220,11 @@ class SmViewShell: public SfxViewShell
DECL_LINK( DialogClosedHdl, sfx2::FileDialogHelper* );
virtual void Notify( SfxBroadcaster& rBC, const SfxHint& rHint );
+ /** Used to determine whether insertions using SID_INSERTSYMBOL and SID_INSERTCOMMAND
+ * should be inserted into SmEditWindow or directly into the SmDocShell as done if the
+ * visual editor was last to have focus.
+ */
+ BOOL bInsertIntoEditWindow;
protected:
Size GetTextLineSize(OutputDevice& rDevice,
@@ -293,6 +288,16 @@ public:
void Impl_Print( OutputDevice &rOutDev, const SmPrintUIOptions &rPrintUIOptions,
Rectangle aOutRect, Point aZeroPoint );
+
+ /** Set bInsertIntoEditWindow so we know where to insert
+ *
+ * This method is called whenever SmGraphicWindow or SmEditWindow gets focus,
+ * so that when text is inserted from catalog or elsewhere we know whether to
+ * insert for the visual editor, or the text editor.
+ */
+ void SetInsertIntoEditWindow(BOOL bEditWindowHadFocusLast = TRUE){
+ bInsertIntoEditWindow = bEditWindowHadFocusLast;
+ }
};
#endif
diff --git a/starmath/inc/visitors.hxx b/starmath/inc/visitors.hxx
new file mode 100644
index 000000000000..bb4ee155fc3a
--- /dev/null
+++ b/starmath/inc/visitors.hxx
@@ -0,0 +1,469 @@
+#ifndef SMVISITORS_H
+#define SMVISITORS_H
+
+#include "node.hxx"
+#include "caret.hxx"
+
+/** Base class for visitors that visits a tree of SmNodes
+ * @remarks all methods have been left abstract to ensure that implementers
+ * don't forget to implement one.
+ */
+class SmVisitor
+{
+public:
+ virtual void Visit( SmTableNode* pNode ) = 0;
+ virtual void Visit( SmBraceNode* pNode ) = 0;
+ virtual void Visit( SmBracebodyNode* pNode ) = 0;
+ virtual void Visit( SmOperNode* pNode ) = 0;
+ virtual void Visit( SmAlignNode* pNode ) = 0;
+ virtual void Visit( SmAttributNode* pNode ) = 0;
+ virtual void Visit( SmFontNode* pNode ) = 0;
+ virtual void Visit( SmUnHorNode* pNode ) = 0;
+ virtual void Visit( SmBinHorNode* pNode ) = 0;
+ virtual void Visit( SmBinVerNode* pNode ) = 0;
+ virtual void Visit( SmBinDiagonalNode* pNode ) = 0;
+ virtual void Visit( SmSubSupNode* pNode ) = 0;
+ virtual void Visit( SmMatrixNode* pNode ) = 0;
+ virtual void Visit( SmPlaceNode* pNode ) = 0;
+ virtual void Visit( SmTextNode* pNode ) = 0;
+ virtual void Visit( SmSpecialNode* pNode ) = 0;
+ virtual void Visit( SmGlyphSpecialNode* pNode ) = 0;
+ virtual void Visit( SmMathSymbolNode* pNode ) = 0;
+ virtual void Visit( SmBlankNode* pNode ) = 0;
+ virtual void Visit( SmErrorNode* pNode ) = 0;
+ virtual void Visit( SmLineNode* pNode ) = 0;
+ virtual void Visit( SmExpressionNode* pNode ) = 0;
+ virtual void Visit( SmPolyLineNode* pNode ) = 0;
+ virtual void Visit( SmRootNode* pNode ) = 0;
+ virtual void Visit( SmRootSymbolNode* pNode ) = 0;
+ virtual void Visit( SmRectangleNode* pNode ) = 0;
+ virtual void Visit( SmVerticalBraceNode* pNode ) = 0;
+};
+
+/** Simple visitor for testing SmVisitor */
+class SmVisitorTest : public SmVisitor
+{
+public:
+ void Visit( SmTableNode* pNode );
+ void Visit( SmBraceNode* pNode );
+ void Visit( SmBracebodyNode* pNode );
+ void Visit( SmOperNode* pNode );
+ void Visit( SmAlignNode* pNode );
+ void Visit( SmAttributNode* pNode );
+ void Visit( SmFontNode* pNode );
+ void Visit( SmUnHorNode* pNode );
+ void Visit( SmBinHorNode* pNode );
+ void Visit( SmBinVerNode* pNode );
+ void Visit( SmBinDiagonalNode* pNode );
+ void Visit( SmSubSupNode* pNode );
+ void Visit( SmMatrixNode* pNode );
+ void Visit( SmPlaceNode* pNode );
+ void Visit( SmTextNode* pNode );
+ void Visit( SmSpecialNode* pNode );
+ void Visit( SmGlyphSpecialNode* pNode );
+ void Visit( SmMathSymbolNode* pNode );
+ void Visit( SmBlankNode* pNode );
+ void Visit( SmErrorNode* pNode );
+ void Visit( SmLineNode* pNode );
+ void Visit( SmExpressionNode* pNode );
+ void Visit( SmPolyLineNode* pNode );
+ void Visit( SmRootNode* pNode );
+ void Visit( SmRootSymbolNode* pNode );
+ void Visit( SmRectangleNode* pNode );
+ void Visit( SmVerticalBraceNode* pNode );
+private:
+ /** Auxiliary method for visiting the children of a pNode */
+ void VisitChildren( SmNode* pNode );
+};
+
+/////////////////////////////// SmDefaultingVisitor ////////////////////////////////
+
+
+/** Visitor that uses DefaultVisit for handling visits by default
+ *
+ * This abstract baseclass is useful for visitors where many methods share the same
+ * implementation.
+ */
+class SmDefaultingVisitor : public SmVisitor
+{
+public:
+ void Visit( SmTableNode* pNode );
+ void Visit( SmBraceNode* pNode );
+ void Visit( SmBracebodyNode* pNode );
+ void Visit( SmOperNode* pNode );
+ void Visit( SmAlignNode* pNode );
+ void Visit( SmAttributNode* pNode );
+ void Visit( SmFontNode* pNode );
+ void Visit( SmUnHorNode* pNode );
+ void Visit( SmBinHorNode* pNode );
+ void Visit( SmBinVerNode* pNode );
+ void Visit( SmBinDiagonalNode* pNode );
+ void Visit( SmSubSupNode* pNode );
+ void Visit( SmMatrixNode* pNode );
+ void Visit( SmPlaceNode* pNode );
+ void Visit( SmTextNode* pNode );
+ void Visit( SmSpecialNode* pNode );
+ void Visit( SmGlyphSpecialNode* pNode );
+ void Visit( SmMathSymbolNode* pNode );
+ void Visit( SmBlankNode* pNode );
+ void Visit( SmErrorNode* pNode );
+ void Visit( SmLineNode* pNode );
+ void Visit( SmExpressionNode* pNode );
+ void Visit( SmPolyLineNode* pNode );
+ void Visit( SmRootNode* pNode );
+ void Visit( SmRootSymbolNode* pNode );
+ void Visit( SmRectangleNode* pNode );
+ void Visit( SmVerticalBraceNode* pNode );
+protected:
+ /** Method invoked by Visit methods by default */
+ virtual void DefaultVisit( SmNode* pNode ) = 0;
+};
+
+/////////////////////////////// SmCaretDrawingVisitor ////////////////////////////////
+
+/** Visitor for drawing a caret position */
+class SmCaretDrawingVisitor : public SmDefaultingVisitor
+{
+public:
+ /** Given position and device this constructor will draw the caret */
+ SmCaretDrawingVisitor( OutputDevice& rDevice, SmCaretPos position, Point offset );
+ void Visit( SmTextNode* pNode );
+private:
+ OutputDevice &rDev;
+ SmCaretPos pos;
+ /** Offset to draw from */
+ Point Offset;
+protected:
+ /** Default method for drawing pNodes */
+ void DefaultVisit( SmNode* pNode );
+};
+
+/////////////////////////////// SmCaretPos2LineVisitor ////////////////////////////////
+
+/** Visitor getting a line from a caret position */
+class SmCaretPos2LineVisitor : public SmDefaultingVisitor
+{
+public:
+ /** Given position and device this constructor will compute a line for the caret */
+ SmCaretPos2LineVisitor( OutputDevice *pDevice, SmCaretPos position ) {
+ pDev = pDevice;
+ pos = position;
+ j_assert( position.IsValid( ), "Cannot draw invalid position!" );
+
+ pos.pSelectedNode->Accept( this );
+ }
+ void Visit( SmTextNode* pNode );
+ SmCaretLine GetResult( ){
+ return line;
+ }
+private:
+ SmCaretLine line;
+ OutputDevice *pDev;
+ SmCaretPos pos;
+protected:
+ /** Default method for computing lines for pNodes */
+ void DefaultVisit( SmNode* pNode );
+};
+
+/////////////////////////////// SmDrawingVisitor ////////////////////////////////
+
+/** Visitor for drawing SmNodes to OutputDevice */
+class SmDrawingVisitor : public SmVisitor
+{
+public:
+ /** Create an instance of SmDrawingVisitor, and use it to draw a formula
+ * @param rDevice Device to draw on
+ * @param position Offset on device to draw the formula
+ * @param pTree Formula tree to draw
+ * @remarks This constructor will do the drawing, no need to anything more.
+ */
+ SmDrawingVisitor( OutputDevice &rDevice, Point position, SmNode* pTree )
+ : rDev( rDevice ) {
+ this->Position = position;
+ pTree->Accept( this );
+ }
+ void Visit( SmTableNode* pNode );
+ void Visit( SmBraceNode* pNode );
+ void Visit( SmBracebodyNode* pNode );
+ void Visit( SmOperNode* pNode );
+ void Visit( SmAlignNode* pNode );
+ void Visit( SmAttributNode* pNode );
+ void Visit( SmFontNode* pNode );
+ void Visit( SmUnHorNode* pNode );
+ void Visit( SmBinHorNode* pNode );
+ void Visit( SmBinVerNode* pNode );
+ void Visit( SmBinDiagonalNode* pNode );
+ void Visit( SmSubSupNode* pNode );
+ void Visit( SmMatrixNode* pNode );
+ void Visit( SmPlaceNode* pNode );
+ void Visit( SmTextNode* pNode );
+ void Visit( SmSpecialNode* pNode );
+ void Visit( SmGlyphSpecialNode* pNode );
+ void Visit( SmMathSymbolNode* pNode );
+ void Visit( SmBlankNode* pNode );
+ void Visit( SmErrorNode* pNode );
+ void Visit( SmLineNode* pNode );
+ void Visit( SmExpressionNode* pNode );
+ void Visit( SmPolyLineNode* pNode );
+ void Visit( SmRootNode* pNode );
+ void Visit( SmRootSymbolNode* pNode );
+ void Visit( SmRectangleNode* pNode );
+ void Visit( SmVerticalBraceNode* pNode );
+private:
+ /** Draw the children of a pNode
+ * This the default method, use by most pNodes
+ */
+ void DrawChildren( SmNode* pNode );
+
+ /** Draw an SmTextNode or a subclass of this */
+ void DrawTextNode( SmTextNode* pNode );
+ /** Draw an SmSpecialNode or a subclass of this */
+ void DrawSpecialNode( SmSpecialNode* pNode );
+ /** OutputDevice to draw on */
+ OutputDevice& rDev;
+ /** Position to draw on the rDev
+ * @remarks This variable is used to pass parameters in DrawChildren( ), this means
+ that after a call to DrawChildren( ) the contents of this method is undefined
+ so if needed cache it locally on the stack.
+ */
+ Point Position;
+};
+
+/////////////////////////////// SmSetSelectionVisitor ////////////////////////////////
+
+/** Set Selection Visitor
+ * Sets the IsSelected( ) property on all SmNodes of the tree
+ */
+class SmSetSelectionVisitor : public SmDefaultingVisitor
+{
+public:
+ SmSetSelectionVisitor( SmCaretPos startPos,
+ SmCaretPos endPos ){
+ StartPos = startPos;
+ EndPos = endPos;
+ IsSelecting = false;
+ }
+ void Visit( SmBinHorNode* pNode );
+ void Visit( SmUnHorNode* pNode );
+ void Visit( SmFontNode* pNode );
+ void Visit( SmTextNode* pNode );
+ void Visit( SmExpressionNode* pNode );
+ void Visit( SmAlignNode* pNode );
+ /** Set IsSelected on all pNodes of pSubTree */
+ static void SetSelectedOnAll( SmNode* pSubTree, bool IsSelected = true );
+private:
+ /** Visit a selectable pNode
+ * Can be used to handle pNodes that can be selected, that doesn't have more SmCaretPos'
+ * than 0 and 1 inside them. SmTextNode should be handle seperately!
+ * Also note that pNodes such as SmBinVerNode cannot be selected, don't this method for
+ * it.
+ */
+ void DefaultVisit( SmNode* pNode );
+ void VisitCompositionNode( SmNode* pNode );
+ /** Caret position where the selection starts */
+ SmCaretPos StartPos;
+ /** Caret position where the selection ends */
+ SmCaretPos EndPos;
+ /** The current state of this visitor
+ * This property changes when the visitor meets either StartPos
+ * or EndPos. This means that anything visited in between will be
+ * selected.
+ */
+ BOOL IsSelecting;
+};
+
+
+/////////////////////////////// SmCaretPosGraphBuildingVisitor ////////////////////////////////
+
+
+/** A visitor for building a SmCaretPosGraph
+ *
+ * Visit invariant:
+ * Each pNode, except SmExpressionNode, SmBinHorNode and a few others, constitues an entry
+ * in a line. Consider the line entry "H", this entry creates one carat position, here
+ * denoted by | in "H|".
+ *
+ * Parameter variables:
+ * The following variables are used to transfer parameters in to calls and results out
+ * of calls.
+ * pRightMost : SmCaretPosGraphEntry*
+ *
+ * Prior to a Visit call:
+ * pRightMost: A pointer to right most position infront of the current line entry.
+ *
+ * After a Visit call:
+ * pRightMost: A pointer to the right most position in the called line entry, if no there's
+ * no caret positions in called line entry don't change this variable.
+ */
+class SmCaretPosGraphBuildingVisitor : public SmVisitor
+{
+public:
+ /** Builds a caret position graph for pRootNode */
+ SmCaretPosGraphBuildingVisitor( SmNode* pRootNode );
+ void Visit( SmTableNode* pNode );
+ void Visit( SmBraceNode* pNode );
+ void Visit( SmBracebodyNode* pNode );
+ void Visit( SmOperNode* pNode );
+ void Visit( SmAlignNode* pNode );
+ void Visit( SmAttributNode* pNode );
+ void Visit( SmFontNode* pNode );
+ void Visit( SmUnHorNode* pNode );
+ void Visit( SmBinHorNode* pNode );
+ void Visit( SmBinVerNode* pNode );
+ void Visit( SmBinDiagonalNode* pNode );
+ void Visit( SmSubSupNode* pNode );
+ void Visit( SmMatrixNode* pNode );
+ void Visit( SmPlaceNode* pNode );
+ void Visit( SmTextNode* pNode );
+ void Visit( SmSpecialNode* pNode );
+ void Visit( SmGlyphSpecialNode* pNode );
+ void Visit( SmMathSymbolNode* pNode );
+ void Visit( SmBlankNode* pNode );
+ void Visit( SmErrorNode* pNode );
+ void Visit( SmLineNode* pNode );
+ void Visit( SmExpressionNode* pNode );
+ void Visit( SmPolyLineNode* pNode );
+ void Visit( SmRootNode* pNode );
+ void Visit( SmRootSymbolNode* pNode );
+ void Visit( SmRectangleNode* pNode );
+ void Visit( SmVerticalBraceNode* pNode );
+ SmCaretPosGraph* Graph( ){
+ return pGraph;
+ }
+private:
+ SmCaretPosGraphEntry* pRightMost;
+ SmCaretPosGraph* pGraph;
+};
+
+/////////////////////////////// SmCloningVisitor ///////////////////////////////
+
+/** Visitor for cloning a pNode
+ *
+ * This visitor creates deep clones.
+ */
+class SmCloningVisitor : public SmVisitor
+{
+public:
+ SmCloningVisitor( ){ pResult = NULL; }
+ void Visit( SmTableNode* pNode );
+ void Visit( SmBraceNode* pNode );
+ void Visit( SmBracebodyNode* pNode );
+ void Visit( SmOperNode* pNode );
+ void Visit( SmAlignNode* pNode );
+ void Visit( SmAttributNode* pNode );
+ void Visit( SmFontNode* pNode );
+ void Visit( SmUnHorNode* pNode );
+ void Visit( SmBinHorNode* pNode );
+ void Visit( SmBinVerNode* pNode );
+ void Visit( SmBinDiagonalNode* pNode );
+ void Visit( SmSubSupNode* pNode );
+ void Visit( SmMatrixNode* pNode );
+ void Visit( SmPlaceNode* pNode );
+ void Visit( SmTextNode* pNode );
+ void Visit( SmSpecialNode* pNode );
+ void Visit( SmGlyphSpecialNode* pNode );
+ void Visit( SmMathSymbolNode* pNode );
+ void Visit( SmBlankNode* pNode );
+ void Visit( SmErrorNode* pNode );
+ void Visit( SmLineNode* pNode );
+ void Visit( SmExpressionNode* pNode );
+ void Visit( SmPolyLineNode* pNode );
+ void Visit( SmRootNode* pNode );
+ void Visit( SmRootSymbolNode* pNode );
+ void Visit( SmRectangleNode* pNode );
+ void Visit( SmVerticalBraceNode* pNode );
+ /** Clone a pNode */
+ SmNode* Clone( SmNode* pNode );
+private:
+ SmNode* pResult;
+ /** Clone children of pSource and give them to pTarget */
+ void CloneKids( SmStructureNode* pSource, SmStructureNode* pTarget );
+ /** Clone attributes on a pNode */
+ void CloneNodeAttr( SmNode* pSource, SmNode* pTarget );
+};
+
+
+/////////////////////////////// SmSelectionDrawingVisitor ///////////////////////////////
+
+class SmSelectionDrawingVisitor : public SmDefaultingVisitor
+{
+public:
+ /** Draws a selection on rDevice for the selection on pTree */
+ SmSelectionDrawingVisitor( OutputDevice& rDevice, SmNode* pTree, Point Offset );
+ void Visit( SmTextNode* pNode );
+private:
+ /** Reference to drawing device */
+ OutputDevice& rDev;
+ /** True if aSelectionArea have been initialized */
+ BOOL bHasSelectionArea;
+ /** The current area that is selected */
+ Rectangle aSelectionArea;
+ /** Extend the area that must be selected */
+ void ExtendSelectionArea( Rectangle aArea );
+ /** Default visiting method */
+ void DefaultVisit( SmNode* pNode );
+ /** Visit the children of a given pNode */
+ void VisitChildren( SmNode* pNode );
+};
+
+/////////////////////////////// SmNodeToTextVisitor ///////////////////////////////
+
+/** Extract command text from pNodes */
+class SmNodeToTextVisitor : public SmVisitor
+{
+public:
+ SmNodeToTextVisitor( SmNode* pNode, String &rText )
+ : rCmdText( rText ) {
+ pNode->Accept( this );
+ }
+ void Visit( SmTableNode* pNode );
+ void Visit( SmBraceNode* pNode );
+ void Visit( SmBracebodyNode* pNode );
+ void Visit( SmOperNode* pNode );
+ void Visit( SmAlignNode* pNode );
+ void Visit( SmAttributNode* pNode );
+ void Visit( SmFontNode* pNode );
+ void Visit( SmUnHorNode* pNode );
+ void Visit( SmBinHorNode* pNode );
+ void Visit( SmBinVerNode* pNode );
+ void Visit( SmBinDiagonalNode* pNode );
+ void Visit( SmSubSupNode* pNode );
+ void Visit( SmMatrixNode* pNode );
+ void Visit( SmPlaceNode* pNode );
+ void Visit( SmTextNode* pNode );
+ void Visit( SmSpecialNode* pNode );
+ void Visit( SmGlyphSpecialNode* pNode );
+ void Visit( SmMathSymbolNode* pNode );
+ void Visit( SmBlankNode* pNode );
+ void Visit( SmErrorNode* pNode );
+ void Visit( SmLineNode* pNode );
+ void Visit( SmExpressionNode* pNode );
+ void Visit( SmPolyLineNode* pNode );
+ void Visit( SmRootNode* pNode );
+ void Visit( SmRootSymbolNode* pNode );
+ void Visit( SmRectangleNode* pNode );
+ void Visit( SmVerticalBraceNode* pNode );
+private:
+ /** Extract text from a pNode that constitues a line */
+ void LineToText( SmNode* pNode ) {
+ Separate( );
+ if( pNode )
+ pNode->Accept( this );
+ Separate( );
+ }
+ inline void Append( const sal_Char* pCharStr ) {
+ rCmdText.AppendAscii( pCharStr );
+ }
+ inline void Append( const String &rText ) {
+ rCmdText.Append( rText );
+ }
+ /** Append a blank for separation, if needed */
+ inline void Separate( ){
+ if( rCmdText.GetChar( rCmdText.Len( ) - 1 ) != ' ' )
+ rCmdText.AppendAscii( RTL_CONSTASCII_STRINGPARAM( " " ) );
+ }
+ /** Output text generated from the pNodes */
+ String &rCmdText;
+};
+
+#endif /* SMVISITORS_H */
diff --git a/starmath/sdi/smath.sdi b/starmath/sdi/smath.sdi
index f1f812e6c40b..99399c8396e3 100644
--- a/starmath/sdi/smath.sdi
+++ b/starmath/sdi/smath.sdi
@@ -378,7 +378,7 @@ SfxVoidItem InsertCommand SID_INSERTCOMMAND
]
//--------------------------------------------------------------------------
-SfxVoidItem InsertConfigName SID_INSERTTEXT
+SfxVoidItem InsertConfigName SID_INSERTSYMBOL
()
[
/* flags: */
diff --git a/starmath/sdi/smslots.sdi b/starmath/sdi/smslots.sdi
index 9e27c7149502..f96690b5c1f0 100644
--- a/starmath/sdi/smslots.sdi
+++ b/starmath/sdi/smslots.sdi
@@ -270,17 +270,17 @@ interface FormulaView
StateMethod = GetState ;
]
//idlpp kein Menueeintrag , also keine Texte
- SID_INSERTTEXT //idlpp ole : no , status : no
+ SID_INSERTSYMBOL //idlpp ole : no , status : no
[
ExecMethod = Execute ;
StateMethod = GetState ;
]
- SID_INSERT_FORMULA //idlpp ole : no , status : no
- [
- ExecMethod = Execute ;
- StateMethod = GetState ;
- Export = FALSE ;
- ]
+ SID_INSERT_FORMULA //idlpp ole : no , status : no
+ [
+ ExecMethod = Execute ;
+ StateMethod = GetState ;
+ Export = FALSE ;
+ ]
//idlpp kein Menueeintrag , also keine Texte
SID_ATTR_ZOOM //idlpp ole : no , status : no
[
diff --git a/starmath/source/caret.cxx b/starmath/source/caret.cxx
new file mode 100644
index 000000000000..24374beaab39
--- /dev/null
+++ b/starmath/source/caret.cxx
@@ -0,0 +1,33 @@
+#include "caret.hxx"
+
+/////////////////////////////// SmCaretPosGraph ////////////////////////////////
+
+SmCaretPosGraphEntry* SmCaretPosGraphIterator::Next(){
+ if(nOffset >= pGraph->nOffset){
+ if(pGraph->pNext){
+ pGraph = pGraph->pNext;
+ nOffset = 0;
+ pEntry = Next();
+ }else
+ pEntry = NULL;
+ }else
+ pEntry = pGraph->Graph + nOffset++;
+ return pEntry;
+}
+
+SmCaretPosGraphEntry* SmCaretPosGraph::Add(SmCaretPosGraphEntry entry){
+ if(nOffset >= SmCaretPosGraphSize){
+ if(!pNext)
+ pNext = new SmCaretPosGraph();
+ return pNext->Add(entry);
+ }else{
+ Graph[nOffset] = entry;
+ return Graph + nOffset++;
+ }
+}
+
+SmCaretPosGraph::~SmCaretPosGraph(){
+ if(pNext)
+ delete pNext;
+ pNext = NULL;
+}
diff --git a/starmath/source/cursor.cxx b/starmath/source/cursor.cxx
new file mode 100644
index 000000000000..3b5022c9d1a9
--- /dev/null
+++ b/starmath/source/cursor.cxx
@@ -0,0 +1,1591 @@
+#include "cursor.hxx"
+#include "parse.hxx"
+#include "visitors.hxx"
+#include "document.hxx"
+#include "view.hxx"
+
+void SmCursor::Move(OutputDevice* pDev, SmMovementDirection direction, bool bMoveAnchor){
+ SmCaretPosGraphEntry* NewPos = NULL;
+ switch(direction){
+ case MoveLeft:
+ {
+ //If position->Left is NULL, we want NewPos = NULL anyway...
+ NewPos = position->Left;
+ }break;
+ case MoveRight:
+ {
+ //If position->Right is NULL, we want NewPos = NULL anyway...
+ NewPos = position->Right;
+ }break;
+ case MoveUp:
+ //Implementation is practically identical to MoveDown, except for a single if statement
+ //so I've implemented them together and added a direction == MoveDown to the if statements.
+ case MoveDown:
+ {
+ SmCaretLine from_line = SmCaretPos2LineVisitor(pDev, position->CaretPos).GetResult(),
+ best_line, //Best approximated line found so far
+ curr_line; //Current line
+ long dbp_sq = 0; //Distance squared to best line
+ SmCaretPosGraphIterator it = pGraph->GetIterator();
+ while(it.Next()){
+ //Reject it if it's the current position
+ if(it->CaretPos == position->CaretPos) continue;
+ //Compute caret line
+ curr_line = SmCaretPos2LineVisitor(pDev, it->CaretPos).GetResult();
+ //Reject anything above if we're moving down
+ if(curr_line.GetTop() <= from_line.GetTop() && direction == MoveDown) continue;
+ //Reject anything below if we're moving up
+ if(curr_line.GetTop() + curr_line.GetHeight() >= from_line.GetTop() + from_line.GetHeight()
+ && direction == MoveUp) continue;
+ //Compare if it to what we have, if we have anything yet
+ if(NewPos){
+ //Compute distance to current line squared, multiplied with a horizontial factor
+ long dp_sq = curr_line.SquaredDistanceX(from_line) * HORIZONTICAL_DISTANCE_FACTOR +
+ curr_line.SquaredDistanceY(from_line);
+ //Discard current line if best line is closer
+ if(dbp_sq <= dp_sq) continue;
+ }
+ //Take current line as the best
+ best_line = curr_line;
+ NewPos = it.Current();
+ //Update distance to best line
+ dbp_sq = best_line.SquaredDistanceX(from_line) * HORIZONTICAL_DISTANCE_FACTOR +
+ best_line.SquaredDistanceY(from_line);
+ }
+ }break;
+ default:
+ j_assert(false, "Movement direction not supported!");
+ }
+ if(NewPos){
+ position = NewPos;
+ if(bMoveAnchor)
+ anchor = NewPos;
+ RequestRepaint();
+ }
+}
+
+void SmCursor::MoveTo(OutputDevice* pDev, Point pos, bool bMoveAnchor){
+ SmCaretLine best_line, //Best line found so far, when iterating
+ curr_line; //Current line, when iterating
+ SmCaretPosGraphEntry* NewPos = NULL;
+ long dp_sq = 0, //Distance to current line squared
+ dbp_sq = 1; //Distance to best line squared
+ SmCaretPosGraphIterator it = pGraph->GetIterator();
+ while(it.Next()){
+ j_assert(it->CaretPos.IsValid(), "The caret position graph may not have invalid positions!");
+ //Compute current line
+ curr_line = SmCaretPos2LineVisitor(pDev, it->CaretPos).GetResult();
+ //If we have a position compare to it
+ if(NewPos){
+ //Compute squared distance to current line
+ dp_sq = curr_line.SquaredDistanceX(pos) + curr_line.SquaredDistanceY(pos);
+ //If best line is closer, reject current line
+ if(dbp_sq <= dp_sq) continue;
+ }
+ //Accept current position as the best
+ best_line = curr_line;
+ NewPos = it.Current();
+ //Update distance to best line
+ dbp_sq = best_line.SquaredDistanceX(pos) + best_line.SquaredDistanceY(pos);
+ }
+ if(NewPos){
+ position = NewPos;
+ if(bMoveAnchor)
+ anchor = NewPos;
+ RequestRepaint();
+ }
+}
+
+void SmCursor::BuildGraph(){
+ //Save the current anchor and position
+ SmCaretPos _anchor, _position;
+ //Release pGraph if allocated
+ if(pGraph){
+ if(anchor)
+ _anchor = anchor->CaretPos;
+ if(position)
+ _position = position->CaretPos;
+ delete pGraph;
+ //Reset anchor and position as they point into an old graph
+ anchor = NULL;
+ position = NULL;
+ }
+ pGraph = NULL;
+
+ //Build the new graph
+ pGraph = SmCaretPosGraphBuildingVisitor(pTree).Graph();
+
+ //Restore anchor and position pointers
+ if(_anchor.IsValid() || _position.IsValid()){
+ SmCaretPosGraphIterator it = pGraph->GetIterator();
+ while(it.Next()){
+ if(_anchor == it->CaretPos)
+ anchor = it.Current();
+ if(_position == it->CaretPos)
+ position = it.Current();
+ }
+ }
+ //Set position and anchor to first caret position
+ SmCaretPosGraphIterator it = pGraph->GetIterator();
+ if(!position)
+ position = it.Next();
+ if(!anchor)
+ anchor = position;
+
+ j_assert(position->CaretPos.IsValid(), "Position must be valid");
+ j_assert(anchor->CaretPos.IsValid(), "Anchor must be valid");
+}
+
+bool SmCursor::SetCaretPosition(SmCaretPos pos, bool moveAnchor){
+ SmCaretPosGraphIterator it = pGraph->GetIterator();
+ while(it.Next()){
+ if(it->CaretPos == pos){
+ position = it.Current();
+ if(moveAnchor)
+ anchor = it.Current();
+ return true;
+ }
+ }
+ return false;
+}
+
+void SmCursor::AnnotateSelection(){
+ //TODO: Manage a state, reset it upon modification and optimize this call
+ SmSetSelectionVisitor SSV(anchor->CaretPos, position->CaretPos);
+ pTree->Accept(&SSV);
+}
+
+void SmCursor::Draw(OutputDevice& pDev, Point Offset){
+ SmCaretDrawingVisitor(pDev, GetPosition(), Offset);
+}
+
+void SmCursor::Delete(){
+ //Return if we don't have a selection to delete
+ if(!HasSelection())
+ return;
+
+ //Enter edit setion
+ BeginEdit();
+
+ //Set selected on nodes
+ AnnotateSelection();
+
+ //Find an arbitrary selected node
+ SmNode* pSNode = FindSelectedNode(pTree);
+ j_assert(pSNode != NULL, "There must be a selection when HasSelection is true!");
+
+ //Find the topmost node of the line that holds the selection
+ SmNode* pLine = FindTopMostNodeInLine(pSNode, true);
+
+ //Get the parent of the line
+ SmStructureNode* pLineParent = pLine->GetParent();
+ //Find line offset in parent
+ int nLineOffset = pLineParent->IndexOfSubNode(pLine);
+ j_assert(nLineOffset != -1, "pLine must be a child of it's parent!");
+
+ //Position after delete
+ SmCaretPos PosAfterDelete;
+
+ SmNodeList* pLineList;
+ if(IsLineCompositionNode(pLine))
+ pLineList = LineToList((SmStructureNode*)pLine);
+ else {
+ pLineList = new SmNodeList();
+ pLineList->push_back(pLine);
+ }
+
+ //Take the selected nodes and delete them...
+ SmNodeList::iterator patchIt = TakeSelectedNodesFromList(pLineList);
+
+ //Get teh position to set after delete
+ PosAfterDelete = PatchLineList(pLineList, patchIt);
+
+ //Finish editing
+ FinishEdit(pLineList, pLineParent, nLineOffset, PosAfterDelete);
+}
+
+void SmCursor::InsertNodes(SmNodeList* pNewNodes){
+ if(pNewNodes->size() == 0){
+ delete pNewNodes;
+ return;
+ }
+
+ //Begin edit section
+ BeginEdit();
+
+ //Position after insert should be after pNewNode
+ SmCaretPos PosAfterInsert = SmCaretPos(pNewNodes->back(), 1);
+
+ //Get the current position
+ const SmCaretPos pos = position->CaretPos;
+
+ //Find top most of line that holds position
+ SmNode* pLine = FindTopMostNodeInLine(pos.pSelectedNode, false);
+
+ //Find line parent and line index in parent
+ SmStructureNode* pLineParent = pLine->GetParent();
+ int nParentIndex = pLineParent->IndexOfSubNode(pLine);
+ j_assert(nParentIndex != -1, "pLine must be a subnode of pLineParent!");
+
+ //Convert line to list
+ SmNodeList* pLineList;
+ if(IsLineCompositionNode(pLine))
+ pLineList = LineToList((SmStructureNode*)pLine);
+ else {
+ pLineList = new SmNodeList();
+ pLineList->push_front(pLine);
+ }
+
+ //Find iterator for place to insert nodes
+ SmNodeList::iterator it = FindPositionInLineList(pLineList, pos);
+
+ //Insert all new nodes
+ SmNodeList::iterator newIt,
+ patchIt = it, // (pointless default value, fixes compiler warnings)
+ insIt;
+ for(newIt = pNewNodes->begin(); newIt != pNewNodes->end(); newIt++){
+ insIt = pLineList->insert(it, *newIt);
+ if(newIt == pNewNodes->begin())
+ patchIt = insIt;
+ if((*newIt)->GetType() == NTEXT)
+ PosAfterInsert = SmCaretPos(*newIt, ((SmTextNode*)*newIt)->GetText().Len());
+ else
+ PosAfterInsert = SmCaretPos(*newIt, 1);
+ }
+ //Patch the places we've changed stuff
+ PatchLineList(pLineList, patchIt);
+ PosAfterInsert = PatchLineList(pLineList, it);
+ //Release list, we've taken the nodes
+ delete pNewNodes;
+ pNewNodes = NULL;
+
+ //Finish editing
+ FinishEdit(pLineList, pLineParent, nParentIndex, PosAfterInsert);
+}
+
+SmNodeList::iterator SmCursor::FindPositionInLineList(SmNodeList* pLineList, SmCaretPos aCaretPos) {
+ //Find iterator for position
+ SmNodeList::iterator it;
+ for(it = pLineList->begin(); it != pLineList->end(); it++){
+ if(*it == aCaretPos.pSelectedNode){
+ if((*it)->GetType() == NTEXT){
+ //Split textnode if needed
+ if(aCaretPos.Index > 0){
+ SmTextNode* pText = (SmTextNode*)aCaretPos.pSelectedNode;
+ XubString str1 = pText->GetText().Copy(0, aCaretPos.Index);
+ XubString str2 = pText->GetText().Copy(aCaretPos.Index);
+ pText->ChangeText(str1);
+ ++it;
+ //Insert str2 as new text node
+ if(str2.Len() > 0){
+ SmTextNode* pNewText = new SmTextNode(pText->GetToken(), pText->GetFontDesc());
+ pNewText->ChangeText(str2);
+ it = pLineList->insert(it, pNewText);
+ }
+ }
+ }else
+ ++it;
+ //it now pointer to the node following pos, so pLineList->insert(it, ...) will insert correctly
+ return it;
+
+ }
+ }
+ //If we didn't find pSelectedNode, it must be because the caret is infront of the line
+ return pLineList->begin();
+}
+
+SmCaretPos SmCursor::PatchLineList(SmNodeList* pLineList, SmNodeList::iterator aIter) {
+ //The nodes we should consider merging
+ SmNode *prev = NULL,
+ *next = NULL;
+ if(aIter != pLineList->end())
+ next = *aIter;
+ if(aIter != pLineList->begin()) {
+ aIter--;
+ prev = *aIter;
+ aIter++;
+ }
+
+ //Check if there's textnodes to merge
+ if( prev &&
+ next &&
+ prev->GetType() == NTEXT &&
+ next->GetType() == NTEXT &&
+ ( prev->GetToken().eType != TNUMBER ||
+ next->GetToken().eType == TNUMBER) ){
+ SmTextNode *pText = (SmTextNode*)prev,
+ *pOldN = (SmTextNode*)next;
+ SmCaretPos retval(pText, pText->GetText().Len());
+ String newText;
+ newText += pText->GetText();
+ newText += pOldN->GetText();
+ pText->ChangeText(newText);
+ delete pOldN;
+ pLineList->erase(aIter);
+ return retval;
+ }
+
+ //Check if there's a SmPlaceNode to remove:
+ if(prev && next && prev->GetType() == NPLACE && !SmNodeListParser::IsOperator(next->GetToken())){
+ aIter--;
+ aIter = pLineList->erase(aIter);
+ delete prev;
+ //Return caret pos infront of aIter
+ if(aIter != pLineList->begin())
+ aIter--; //Thus find node before aIter
+ if(aIter == pLineList->begin())
+ return SmCaretPos();
+ if((*aIter)->GetType() == NTEXT)
+ return SmCaretPos(*aIter, ((SmTextNode*)*aIter)->GetText().Len());
+ return SmCaretPos(*aIter, 1);
+ }
+ if(prev && next && next->GetType() == NPLACE && !SmNodeListParser::IsOperator(prev->GetToken())){
+ aIter = pLineList->erase(aIter);
+ delete next;
+ if(prev->GetType() == NTEXT)
+ return SmCaretPos(prev, ((SmTextNode*)prev)->GetText().Len());
+ return SmCaretPos(prev, 1);
+ }
+
+ //If we didn't do anything return
+ if(!prev) //return an invalid to indicate we're infront of line
+ return SmCaretPos();
+ if(prev->GetType() == NTEXT)
+ return SmCaretPos(prev, ((SmTextNode*)prev)->GetText().Len());
+ return SmCaretPos(prev, 1);
+}
+
+SmNodeList::iterator SmCursor::TakeSelectedNodesFromList(SmNodeList *pLineList,
+ SmNodeList *pSelectedNodes) {
+ SmNodeList::iterator retval;
+ SmNodeList::iterator it = pLineList->begin();
+ while(it != pLineList->end()){
+ if((*it)->IsSelected()){
+ //Split text nodes
+ if((*it)->GetType() == NTEXT) {
+ SmTextNode* pText = (SmTextNode*)*it;
+ String aText = pText->GetText();
+ //Start and lengths of the segments, 2 is the selected segment
+ int start1 = 0,
+ start2 = pText->GetSelectionStart(),
+ start3 = pText->GetSelectionEnd(),
+ len1 = start2 - 0,
+ len2 = start3 - start2,
+ len3 = aText.Len() - start3;
+ SmToken aToken = pText->GetToken();
+ USHORT eFontDesc = pText->GetFontDesc();
+ //If we need make segment 1
+ if(len1 > 0) {
+ String str = aText.Copy(start1, len1);
+ pText->ChangeText(str);
+ it++;
+ } else {//Remove it if not needed
+ it = pLineList->erase(it);
+ delete pText;
+ }
+ //Set retval to be right after the selection
+ retval = it;
+ //if we need make segment 3
+ if(len3 > 0) {
+ String str = aText.Copy(start3, len3);
+ SmTextNode* pSeg3 = new SmTextNode(aToken, eFontDesc);
+ pSeg3->ChangeText(str);
+ retval = pLineList->insert(it, pSeg3);
+ }
+ //If we need to save the selected text
+ if(pSelectedNodes && len2 > 0) {
+ String str = aText.Copy(start2, len2);
+ SmTextNode* pSeg2 = new SmTextNode(aToken, eFontDesc);
+ pSeg2->ChangeText(str);
+ pSelectedNodes->push_back(pSeg2);
+ }
+ } else { //if it's not textnode
+ SmNode* pNode = *it;
+ retval = it = pLineList->erase(it);
+ if(pSelectedNodes)
+ pSelectedNodes->push_back(pNode);
+ else
+ delete pNode;
+ }
+ } else
+ it++;
+ }
+ return retval;
+}
+
+void SmCursor::InsertSubSup(SmSubSup eSubSup) {
+ AnnotateSelection();
+
+ //Find line
+ SmNode *pLine;
+ if(HasSelection()) {
+ SmNode *pSNode = FindSelectedNode(pTree);
+ j_assert(pSNode != NULL, "There must be a selected node when HasSelection is true!");
+ pLine = FindTopMostNodeInLine(pSNode, TRUE);
+ } else
+ pLine = FindTopMostNodeInLine(position->CaretPos.pSelectedNode, FALSE);
+
+ //Find Parent and offset in parent
+ SmStructureNode *pLineParent = pLine->GetParent();
+ int nParentIndex = pLineParent->IndexOfSubNode(pLine);
+ j_assert(nParentIndex != -1, "pLine must be a subnode of pLineParent!");
+
+ //TODO: Consider handling special cases where parent is an SmOperNode,
+ // Maybe this method should be able to add limits to an SmOperNode...
+
+ //We begin modifying the tree here
+ BeginEdit();
+
+ //Convert line to list
+ SmNodeList* pLineList;
+ if(IsLineCompositionNode(pLine))
+ pLineList = LineToList((SmStructureNode*)pLine);
+ else {
+ pLineList = new SmNodeList();
+ pLineList->push_front(pLine);
+ }
+
+ //Take the selection, and/or find iterator for current position
+ SmNodeList* pSelectedNodesList = new SmNodeList();
+ SmNodeList::iterator it;
+ if(HasSelection())
+ it = TakeSelectedNodesFromList(pLineList, pSelectedNodesList);
+ else
+ it = FindPositionInLineList(pLineList, position->CaretPos);
+
+ //Find node that this should be applied to
+ SmNode* pSubject;
+ BOOL bPatchLine = pSelectedNodesList->size() > 0; //If the line should be patched later
+ if(it != pLineList->begin()) {
+ it--;
+ pSubject = *it;
+ it++;
+ } else {
+ //Create a new place node
+ pSubject = new SmPlaceNode();
+ pSubject->Prepare(pDocShell->GetFormat(), *pDocShell);
+ it = pLineList->insert(it, pSubject);
+ it++;
+ bPatchLine = TRUE; //We've modified the line it should be patched later.
+ }
+
+ //Wrap the subject in a SmSubSupNode
+ SmSubSupNode* pSubSup;
+ if(pSubject->GetType() != NSUBSUP){
+ SmToken token;
+ token.nGroup = TGPOWER;
+ pSubSup = new SmSubSupNode(token);
+ pSubSup->SetBody(pSubject);
+ *(--it) = pSubSup;
+ it++;
+ }else
+ pSubSup = (SmSubSupNode*)pSubject;
+ //pSubject shouldn't be referenced anymore, pSubSup is the SmSubSupNode in pLineList we wish to edit.
+ //and it pointer to the element following pSubSup in pLineList.
+ pSubject = NULL;
+
+ //Patch the line if we noted that was needed previously
+ if(bPatchLine)
+ PatchLineList(pLineList, it);
+
+ //Convert existing, if any, sub-/superscript line to list
+ SmNode *pScriptLine = pSubSup->GetSubSup(eSubSup);
+ SmNodeList* pScriptLineList;
+ if(pScriptLine && IsLineCompositionNode(pScriptLine))
+ pScriptLineList = LineToList((SmStructureNode*)pScriptLine);
+ else{
+ pScriptLineList = new SmNodeList();
+ if(pScriptLine)
+ pScriptLineList->push_front(pScriptLine);
+ }
+
+ //Add selection to pScriptLineList
+ unsigned int nOldSize = pScriptLineList->size();
+ pScriptLineList->insert(pScriptLineList->end(), pSelectedNodesList->begin(), pSelectedNodesList->end());
+ delete pSelectedNodesList;
+ pSelectedNodesList = NULL;
+
+ //Patch pScriptLineList if needed
+ if(0 < nOldSize && nOldSize < pScriptLineList->size()) {
+ SmNodeList::iterator iPatchPoint = pScriptLineList->begin();
+ std::advance(iPatchPoint, nOldSize);
+ PatchLineList(pScriptLineList, iPatchPoint);
+ }
+
+ //Find caret pos, that should be used after sub-/superscription.
+ SmCaretPos PosAfterScript; //Leave invalid for first position
+ if(pScriptLineList->size() > 0)
+ PosAfterScript = SmCaretPos::GetPosAfter(pScriptLineList->back());
+
+ //Parse pScriptLineList
+ pScriptLine = SmNodeListParser().Parse(pScriptLineList);
+ delete pScriptLineList;
+ pScriptLineList = NULL;
+
+ //Insert pScriptLine back into the tree
+ pSubSup->SetSubSup(eSubSup, pScriptLine);
+
+ //Finish editing
+ FinishEdit(pLineList, pLineParent, nParentIndex, PosAfterScript, pScriptLine);
+}
+
+BOOL SmCursor::InsertLimit(SmSubSup eSubSup, BOOL bMoveCaret) {
+ //Find a subject to set limits on
+ SmOperNode *pSubject = NULL;
+ //Check if pSelectedNode might be a subject
+ if(position->CaretPos.pSelectedNode->GetType() == NOPER)
+ pSubject = (SmOperNode*)position->CaretPos.pSelectedNode;
+ else {
+ //If not, check if parent of the current line is a SmOperNode
+ SmNode *pLineNode = FindTopMostNodeInLine(position->CaretPos.pSelectedNode, FALSE);
+ if(pLineNode->GetParent() && pLineNode->GetParent()->GetType() == NOPER)
+ pSubject = (SmOperNode*)pLineNode->GetParent();
+ }
+
+ //Abort operation if we're not in the appropriate context
+ if(!pSubject)
+ return FALSE;
+
+ BeginEdit();
+
+ //Find the sub sup node
+ SmSubSupNode *pSubSup = NULL;
+ //Check if there's already one there...
+ if(pSubject->GetSubNode(0)->GetType() == NSUBSUP)
+ pSubSup = (SmSubSupNode*)pSubject->GetSubNode(0);
+ else { //if not create a new SmSubSupNode
+ SmToken token;
+ token.nGroup = TGLIMIT;
+ pSubSup = new SmSubSupNode(token);
+ //Set it's body
+ pSubSup->SetBody(pSubject->GetSubNode(0));
+ //Replace the operation of the SmOperNode
+ pSubject->SetSubNode(0, pSubSup);
+ }
+
+ //Create the limit, if needed
+ SmCaretPos PosAfterLimit;
+ SmNode *pLine;
+ if(!pSubSup->GetSubSup(eSubSup)){
+ pLine = new SmPlaceNode();
+ pSubSup->SetSubSup(eSubSup, pLine);
+ PosAfterLimit = SmCaretPos(pLine, 1);
+ //If it's already there... let's move the caret
+ } else if(bMoveCaret){
+ pLine = pSubSup->GetSubSup(eSubSup);
+ SmNodeList* pLineList;
+ if(IsLineCompositionNode(pLine))
+ pLineList = LineToList((SmStructureNode*)pLine);
+ else {
+ pLineList = new SmNodeList();
+ pLineList->push_front(pLine);
+ }
+ if(pLineList->size() > 0)
+ PosAfterLimit = SmCaretPos::GetPosAfter(pLineList->back());
+ pLine = SmNodeListParser().Parse(pLineList);
+ delete pLineList;
+ pSubSup->SetSubSup(eSubSup, pLine);
+ }
+
+ //Rebuild graph of caret positions
+ BuildGraph();
+ AnnotateSelection();
+
+ //Set caret position
+ if(bMoveCaret)
+ if(!SetCaretPosition(PosAfterLimit, true))
+ SetCaretPosition(SmCaretPos(pLine, 0), true);
+
+ EndEdit();
+
+ return TRUE;
+}
+
+void SmCursor::InsertBrackets(SmBracketType eBracketType) {
+ BeginEdit();
+
+ AnnotateSelection();
+
+ //Find line
+ SmNode *pLine;
+ if(HasSelection()) {
+ SmNode *pSNode = FindSelectedNode(pTree);
+ j_assert(pSNode != NULL, "There must be a selected node if HasSelection()");
+ pLine = FindTopMostNodeInLine(pSNode, TRUE);
+ } else
+ pLine = FindTopMostNodeInLine(position->CaretPos.pSelectedNode, FALSE);
+
+ //Find parent and offset in parent
+ SmStructureNode *pLineParent = pLine->GetParent();
+ int nParentIndex = pLineParent->IndexOfSubNode(pLine);
+ j_assert( nParentIndex != -1, "pLine must be a subnode of pLineParent!");
+
+ //Convert line to list
+ SmNodeList *pLineList;
+ if(IsLineCompositionNode(pLine))
+ pLineList = LineToList((SmStructureNode*)pLine);
+ else {
+ pLineList = new SmNodeList();
+ pLineList->push_front(pLine);
+ }
+
+ //Take the selection, and/or find iterator for current position
+ SmNodeList *pSelectedNodesList = new SmNodeList();
+ SmNodeList::iterator it;
+ if(HasSelection())
+ it = TakeSelectedNodesFromList(pLineList, pSelectedNodesList);
+ else
+ it = FindPositionInLineList(pLineList, position->CaretPos);
+
+ //If there's no selected nodes, create a place node
+ SmCaretPos PosAfterInsert;
+ if(pSelectedNodesList->size() == 0) {
+ SmNode* pPlace = new SmPlaceNode();
+ PosAfterInsert = SmCaretPos(pPlace, 1);
+ pSelectedNodesList->push_front(pPlace);
+ }
+
+ //Parse body nodes
+ SmNode *pBodyNode = SmNodeListParser().Parse(pSelectedNodesList);
+ delete pSelectedNodesList;
+
+ //Create SmBraceNode
+ SmToken aTok(TLEFT, '\0', "left", 0, 5);
+ SmBraceNode *pBrace = new SmBraceNode(aTok);
+ pBrace->SetScaleMode(SCALE_HEIGHT);
+ SmNode *pLeft = CreateBracket(eBracketType, true),
+ *pRight = CreateBracket(eBracketType, false);
+ SmBracebodyNode *pBody = new SmBracebodyNode(SmToken());
+ pBody->SetSubNodes(pBodyNode, NULL);
+ pBrace->SetSubNodes(pLeft, pBody, pRight);
+ pBrace->Prepare(pDocShell->GetFormat(), *pDocShell);
+
+ //Insert into line
+ pLineList->insert(it, pBrace);
+ //Patch line (I think this is good enough)
+ SmCaretPos pAfter = PatchLineList(pLineList, it);
+ if( !PosAfterInsert.IsValid() )
+ PosAfterInsert = pAfter;
+
+ //Finish editing
+ FinishEdit(pLineList, pLineParent, nParentIndex, PosAfterInsert);
+}
+
+SmNode *SmCursor::CreateBracket(SmBracketType eBracketType, BOOL bIsLeft) {
+ SmToken aTok;
+ if(bIsLeft){
+ switch(eBracketType){
+ case NoneBrackets:
+ aTok = SmToken(TNONE, '\0', "none", TGLBRACES | TGRBRACES, 0);
+ break;
+ case RoundBrackets:
+ aTok = SmToken(TLPARENT, MS_LPARENT, "(", TGLBRACES, 5);
+ break;
+ case SquareBrackets:
+ aTok = SmToken(TLBRACKET, MS_LBRACKET, "[", TGLBRACES, 5);
+ break;
+ case DoubleSquareBrackets:
+ aTok = SmToken(TLDBRACKET, MS_LDBRACKET, "ldbracket", TGLBRACES, 5);
+ break;
+ case LineBrackets:
+ aTok = SmToken(TLLINE, MS_LINE, "lline", TGLBRACES, 5);
+ break;
+ case DoubleLineBrackets:
+ aTok = SmToken(TLDLINE, MS_DLINE, "ldline", TGLBRACES, 5);
+ break;
+ case CurlyBrackets:
+ aTok = SmToken(TLBRACE, MS_LBRACE, "lbrace", TGLBRACES, 5);
+ break;
+ case AngleBrackets:
+ aTok = SmToken(TLANGLE, MS_LANGLE, "langle", TGLBRACES, 5);
+ break;
+ case CeilBrackets:
+ aTok = SmToken(TLCEIL, MS_LCEIL, "lceil", TGLBRACES, 5);
+ break;
+ case FloorBrackets:
+ aTok = SmToken(TLFLOOR, MS_LFLOOR, "lfloor", TGLBRACES, 5);
+ break;
+ }
+ } else {
+ switch(eBracketType) {
+ case NoneBrackets:
+ aTok = SmToken(TNONE, '\0', "none", TGLBRACES | TGRBRACES, 0);
+ break;
+ case RoundBrackets:
+ aTok = SmToken(TRPARENT, MS_RPARENT, ")", TGRBRACES, 5);
+ break;
+ case SquareBrackets:
+ aTok = SmToken(TRBRACKET, MS_RBRACKET, "]", TGRBRACES, 5);
+ break;
+ case DoubleSquareBrackets:
+ aTok = SmToken(TRDBRACKET, MS_RDBRACKET, "rdbracket", TGRBRACES, 5);
+ break;
+ case LineBrackets:
+ aTok = SmToken(TRLINE, MS_LINE, "rline", TGRBRACES, 5);
+ break;
+ case DoubleLineBrackets:
+ aTok = SmToken(TRDLINE, MS_DLINE, "rdline", TGRBRACES, 5);
+ break;
+ case CurlyBrackets:
+ aTok = SmToken(TRBRACE, MS_RBRACE, "rbrace", TGRBRACES, 5);
+ break;
+ case AngleBrackets:
+ aTok = SmToken(TRANGLE, MS_RANGLE, "rangle", TGRBRACES, 5);
+ break;
+ case CeilBrackets:
+ aTok = SmToken(TRCEIL, MS_RCEIL, "rceil", TGRBRACES, 5);
+ break;
+ case FloorBrackets:
+ aTok = SmToken(TRFLOOR, MS_RFLOOR, "rfloor", TGRBRACES, 5);
+ break;
+ }
+ }
+ SmNode* pRetVal = new SmMathSymbolNode(aTok);
+ pRetVal->SetScaleMode(SCALE_HEIGHT);
+ return pRetVal;
+}
+
+BOOL SmCursor::InsertRow() {
+ AnnotateSelection();
+
+ //Find line
+ SmNode *pLine;
+ if(HasSelection()) {
+ SmNode *pSNode = FindSelectedNode(pTree);
+ j_assert(pSNode != NULL, "There must be a selected node if HasSelection()");
+ pLine = FindTopMostNodeInLine(pSNode, TRUE);
+ } else
+ pLine = FindTopMostNodeInLine(position->CaretPos.pSelectedNode, FALSE);
+
+ //Find parent and offset in parent
+ SmStructureNode *pLineParent = pLine->GetParent();
+ int nParentIndex = pLineParent->IndexOfSubNode(pLine);
+ j_assert( nParentIndex != -1, "pLine must be a subnode of pLineParent!");
+
+ //Discover the context of this command
+ SmTableNode *pTable = NULL;
+ SmMatrixNode *pMatrix = NULL;
+ int nTableIndex = nParentIndex;
+ if(pLineParent->GetType() == NTABLE)
+ pTable = (SmTableNode*)pLineParent;
+ //If it's warped in a SmLineNode, we can still insert a newline
+ else if(pLineParent->GetType() == NLINE &&
+ pLineParent->GetParent() &&
+ pLineParent->GetParent()->GetType() == NTABLE) {
+ //NOTE: This hack might give problems if we stop ignoring SmAlignNode
+ pTable = (SmTableNode*)pLineParent->GetParent();
+ nTableIndex = pTable->IndexOfSubNode(pLineParent);
+ j_assert(nTableIndex != -1, "pLineParent must be a child of its parent!");
+ }
+ if(pLineParent->GetType() == NMATRIX)
+ pMatrix = (SmMatrixNode*)pLineParent;
+
+ //If we're not in a context that supports InsertRow, return FALSE
+ if(!pTable && !pMatrix)
+ return FALSE;
+
+ //Now we start editing
+ BeginEdit();
+
+ //Convert line to list
+ SmNodeList *pLineList;
+ if(IsLineCompositionNode(pLine))
+ pLineList = LineToList((SmStructureNode*)pLine);
+ else {
+ pLineList = new SmNodeList();
+ pLineList->push_front(pLine);
+ }
+
+ //Find position in line
+ SmNodeList::iterator it;
+ if(HasSelection()) {
+ //Take the selected nodes and delete them...
+ it = TakeSelectedNodesFromList(pLineList);
+ } else
+ it = FindPositionInLineList(pLineList, position->CaretPos);
+
+ //New caret position after inserting the newline/row in whatever context
+ SmCaretPos PosAfterInsert;
+
+ //If we're in the context of a table
+ if(pTable) {
+ SmNodeList *pNewLineList = new SmNodeList();
+ //Move elements from pLineList to pNewLineList
+ pNewLineList->splice(pNewLineList->begin(), *pLineList, it, pLineList->end());
+ //Make sure it is valid again
+ it = pLineList->end();
+ if(it != pLineList->begin())
+ it--;
+ if(pNewLineList->size() == 0)
+ pNewLineList->push_front(new SmPlaceNode());
+ //Parse new line
+ SmNode *pNewLine = SmNodeListParser().Parse(pNewLineList);
+ delete pNewLineList;
+ //Get position before we wrap in SmLineNode
+ //NOTE: This should be done after, if SmLineNode ever becomes a line composition node
+ PosAfterInsert = SmCaretPos(pNewLine, 0);
+ //Wrap pNewLine in SmLineNode if needed
+ if(pLineParent->GetType() == NLINE) {
+ SmLineNode *pNewLineNode = new SmLineNode(SmToken(TNEWLINE, '\0', "newline"));
+ pNewLineNode->SetSubNodes(pNewLine, NULL);
+ pNewLine = pNewLineNode;
+ }
+ //Move other nodes if needed
+ for( int i = pTable->GetNumSubNodes(); i > nTableIndex + 1; i--)
+ pTable->SetSubNode(i, pTable->GetSubNode(i-1));
+ //Insert new line
+ pTable->SetSubNode(nTableIndex + 1, pNewLine);
+ //Check if we need to change token type:
+ if(pTable->GetNumSubNodes() > 2 && pTable->GetToken().eType == TBINOM) {
+ SmToken tok = pTable->GetToken();
+ tok.eType = TSTACK;
+ pTable->SetToken(tok);
+ }
+ }
+ //If we're in the context of a matrix
+ else if(pMatrix) {
+ //Find position after insert and patch the list
+ PosAfterInsert = PatchLineList(pLineList, it);
+ //Move other children
+ USHORT rows = pMatrix->GetNumRows();
+ USHORT cols = pMatrix->GetNumCols();
+ int nRowStart = (nParentIndex - nParentIndex % cols) + cols;
+ for( int i = pMatrix->GetNumSubNodes() + cols - 1; i >= nRowStart + cols; i--)
+ pMatrix->SetSubNode(i, pMatrix->GetSubNode(i - cols));
+ for( int i = nRowStart; i < nRowStart + cols; i++) {
+ SmPlaceNode *pNewLine = new SmPlaceNode();
+ if(i == nParentIndex + cols)
+ PosAfterInsert = SmCaretPos(pNewLine, 0);
+ pMatrix->SetSubNode(i, pNewLine);
+ }
+ pMatrix->SetRowCol(rows + 1, cols);
+ } else
+ j_assert(FALSE, "We must be either the context of a table or matrix!");
+
+ //Finish editing
+ FinishEdit(pLineList, pLineParent, nParentIndex, PosAfterInsert);
+ //FinishEdit is actually used to handle siturations where parent is an instance of
+ //SmSubSupNode. In this case parent should always be a table or matrix, however, for
+ //code reuse we just use FinishEdit() here too.
+ return TRUE;
+}
+
+void SmCursor::InsertFraction() {
+ AnnotateSelection();
+
+ //Find line
+ SmNode *pLine;
+ if(HasSelection()) {
+ SmNode *pSNode = FindSelectedNode(pTree);
+ j_assert(pSNode != NULL, "There must be a selected node when HasSelection is true!");
+ pLine = FindTopMostNodeInLine(pSNode, TRUE);
+ } else
+ pLine = FindTopMostNodeInLine(position->CaretPos.pSelectedNode, FALSE);
+
+ //Find Parent and offset in parent
+ SmStructureNode *pLineParent = pLine->GetParent();
+ int nParentIndex = pLineParent->IndexOfSubNode(pLine);
+ j_assert(nParentIndex != -1, "pLine must be a subnode of pLineParent!");
+
+ //We begin modifying the tree here
+ BeginEdit();
+
+ //Convert line to list
+ SmNodeList* pLineList;
+ if(IsLineCompositionNode(pLine))
+ pLineList = LineToList((SmStructureNode*)pLine);
+ else {
+ pLineList = new SmNodeList();
+ pLineList->push_front(pLine);
+ }
+
+ //Take the selection, and/or find iterator for current position
+ SmNodeList* pSelectedNodesList = new SmNodeList();
+ SmNodeList::iterator it;
+ if(HasSelection())
+ it = TakeSelectedNodesFromList(pLineList, pSelectedNodesList);
+ else
+ it = FindPositionInLineList(pLineList, position->CaretPos);
+
+ //Create pNum, and pDenom
+ if(pSelectedNodesList->size() == 0)
+ pSelectedNodesList->push_front(new SmPlaceNode());
+ SmNode *pNum = SmNodeListParser().Parse(pSelectedNodesList),
+ *pDenom = new SmPlaceNode();
+ delete pSelectedNodesList;
+ pSelectedNodesList = NULL;
+
+ //Create new fraction
+ SmBinVerNode *pFrac = new SmBinVerNode(SmToken(TOVER, '\0', "over", TGPRODUCT, 0));
+ SmNode *pRect = new SmRectangleNode(SmToken());
+ pFrac->SetSubNodes(pNum, pRect, pDenom);
+
+ //Insert in pLineList
+ SmNodeList::iterator patchIt = pLineList->insert(it, pFrac);
+ PatchLineList(pLineList, patchIt);
+ PatchLineList(pLineList, it);
+
+ //Finish editing
+ FinishEdit(pLineList, pLineParent, nParentIndex, SmCaretPos(pDenom, 1));
+}
+
+
+void SmCursor::InsertText(XubString aString){
+ BeginEdit();
+
+ Delete();
+
+ SmToken token;
+ token.eType = TIDENT;
+ token.cMathChar = '\0';
+ token.nGroup = 0;
+ token.nLevel = 5;
+ token.aText = aString;
+
+ SmTextNode* pText = new SmTextNode(token, FNT_VARIABLE);
+
+ //Prepare the new node
+ pText->Prepare(pDocShell->GetFormat(), *pDocShell);
+ pText->AdjustFontDesc();
+
+ SmNodeList* pList = new SmNodeList();
+ pList->push_front(pText);
+ InsertNodes(pList);
+
+ EndEdit();
+}
+
+void SmCursor::InsertElement(SmFormulaElement element){
+ BeginEdit();
+
+ Delete();
+
+ //Create new node
+ SmNode* pNewNode = NULL;
+ switch(element){
+ case BlankElement:
+ {
+ SmToken token;
+ token.nGroup = TGBLANK;
+ token.aText.AssignAscii("~");
+ pNewNode = new SmBlankNode(token);
+ }break;
+ case FactorialElement:
+ {
+ SmToken token(TFACT, MS_FACT, "fact", TGUNOPER, 5);
+ pNewNode = new SmMathSymbolNode(token);
+ }break;
+ case PlusElement:
+ {
+ SmToken token;
+ token.eType = TPLUS;
+ token.cMathChar = MS_PLUS;
+ token.nGroup = TGUNOPER | TGSUM;
+ token.nLevel = 5;
+ token.aText.AssignAscii("+");
+ pNewNode = new SmMathSymbolNode(token);
+ }break;
+ case MinusElement:
+ {
+ SmToken token;
+ token.eType = TMINUS;
+ token.cMathChar = MS_MINUS;
+ token.nGroup = MS_PLUS;
+ token.nLevel = 5;
+ token.aText.AssignAscii("-");
+ pNewNode = new SmMathSymbolNode(token);
+ }break;
+ case CDotElement:
+ {
+ SmToken token;
+ token.eType = TCDOT;
+ token.cMathChar = MS_CDOT;
+ token.nGroup = TGPRODUCT;
+ token.aText.AssignAscii("cdot");
+ pNewNode = new SmMathSymbolNode(token);
+ }break;
+ case EqualElement:
+ {
+ SmToken token;
+ token.eType = TASSIGN;
+ token.cMathChar = MS_ASSIGN;
+ token.nGroup = TGRELATION;
+ token.aText.AssignAscii("=");
+ pNewNode = new SmMathSymbolNode(token);
+ }break;
+ case LessThanElement:
+ {
+ SmToken token;
+ token.eType = TLT;
+ token.cMathChar = MS_LT;
+ token.nGroup = TGRELATION;
+ token.aText.AssignAscii("<");
+ pNewNode = new SmMathSymbolNode(token);
+ }break;
+ case GreaterThanElement:
+ {
+ SmToken token;
+ token.eType = TGT;
+ token.cMathChar = MS_GT;
+ token.nGroup = TGRELATION;
+ token.aText.AssignAscii(">");
+ pNewNode = new SmMathSymbolNode(token);
+ }break;
+ default:
+ j_assert(false, "Element unknown!");
+ }
+ j_assert(pNewNode != NULL, "No new node was created!");
+ if(!pNewNode)
+ return;
+
+ //Prepare the new node
+ pNewNode->Prepare(pDocShell->GetFormat(), *pDocShell);
+
+ //Insert new node
+ SmNodeList* pList = new SmNodeList();
+ pList->push_front(pNewNode);
+ InsertNodes(pList);
+
+ EndEdit();
+}
+
+void SmCursor::InsertSpecial(XubString aString) {
+ BeginEdit();
+ Delete();
+
+ aString.EraseLeadingAndTrailingChars();
+ aString.EraseLeadingChars('%');
+
+ //Create instance of special node
+ SmToken token;
+ token.eType = TSPECIAL;
+ token.cMathChar = '\0';
+ token.nGroup = 0;
+ token.nLevel = 5;
+ token.aText = aString; //Don't know if leading "%" should be removed
+ SmSpecialNode* pSpecial = new SmSpecialNode(token);
+
+ //Prepare the special node
+ pSpecial->Prepare(pDocShell->GetFormat(), *pDocShell);
+
+ //Insert the node
+ SmNodeList* pList = new SmNodeList();
+ pList->push_front(pSpecial);
+ InsertNodes(pList);
+
+ EndEdit();
+}
+
+void SmCursor::InsertCommand(USHORT nCommand) {
+ switch(nCommand){
+ case RID_NEWLINE:
+ InsertRow();
+ break;
+ case RID_FROMX:
+ InsertLimit(CSUB, TRUE);
+ break;
+ case RID_TOX:
+ InsertLimit(CSUP, TRUE);
+ break;
+ case RID_FROMXTOY:
+ if(InsertLimit(CSUB, FALSE))
+ InsertLimit(CSUP, TRUE);
+ break;
+ default:
+ InsertCommandText(SmResId(nCommand));
+ break;
+ }
+}
+
+void SmCursor::InsertCommandText(XubString aCommandText) {
+ //Parse the the sub expression
+ SmNode* pSubExpr = SmParser().ParseExpression(aCommandText);
+
+ //Prepare the subtree
+ pSubExpr->Prepare(pDocShell->GetFormat(), *pDocShell);
+
+ //Convert subtree to list
+ SmNodeList* pLineList;
+ if(IsLineCompositionNode(pSubExpr))
+ pLineList = LineToList((SmStructureNode*)pSubExpr);
+ else {
+ pLineList = new SmNodeList();
+ pLineList->push_front(pSubExpr);
+ }
+
+ BeginEdit();
+
+ //Delete any selection
+ Delete();
+
+ //Insert it
+ InsertNodes(pLineList);
+
+ EndEdit();
+}
+
+void SmCursor::Copy(){
+ if(!HasSelection())
+ return;
+
+ //Find selected node
+ SmNode* pSNode = FindSelectedNode(pTree);
+ //Find visual line
+ SmNode* pLine = FindTopMostNodeInLine(pSNode, true);
+
+ //Clone selected nodes
+ SmNodeList* pList;
+ if(IsLineCompositionNode(pLine))
+ pList = CloneLineToList((SmStructureNode*)pLine, true);
+ else{
+ pList = new SmNodeList();
+ //Special care to only clone selected text
+ if(pLine->GetType() == NTEXT) {
+ SmTextNode *pText = (SmTextNode*)pLine;
+ SmTextNode *pClone = new SmTextNode( pText->GetToken(), pText->GetFontDesc() );
+ int start = pText->GetSelectionStart(),
+ length = pText->GetSelectionEnd() - pText->GetSelectionStart();
+ pClone->ChangeText(pText->GetText().Copy(start, length));
+ pClone->SetScaleMode(pText->GetScaleMode());
+ pList->push_front(pClone);
+ } else {
+ SmCloningVisitor aCloneFactory;
+ pList->push_front(aCloneFactory.Clone(pLine));
+ }
+ }
+
+ //Set clipboard
+ if(pList->size() > 0)
+ SetClipboard(pList);
+}
+
+void SmCursor::Paste() {
+ BeginEdit();
+ Delete();
+
+ if(pClipboard && pClipboard->size() > 0)
+ InsertNodes(CloneList(pClipboard));
+
+ EndEdit();
+}
+
+SmNodeList* SmCursor::CloneList(SmNodeList* pList){
+ SmCloningVisitor aCloneFactory;
+ SmNodeList* pClones = new SmNodeList();
+
+ SmNodeList::iterator it;
+ for(it = pList->begin(); it != pList->end(); it++){
+ SmNode *pClone = aCloneFactory.Clone(*it);
+ pClones->push_back(pClone);
+ }
+
+ return pClones;
+}
+
+
+void SmCursor::SetClipboard(SmNodeList* pList){
+ if(pClipboard){
+ //Delete all nodes on the clipboard
+ SmNodeList::iterator it;
+ for(it = pClipboard->begin(); it != pClipboard->end(); it++)
+ delete (*it);
+ delete pClipboard;
+ }
+ pClipboard = pList;
+}
+
+SmNode* SmCursor::FindTopMostNodeInLine(SmNode* pSNode, bool MoveUpIfSelected){
+ //If we haven't got a subnode
+ if(!pSNode)
+ return NULL;
+
+ //Move up parent untill we find a node who's
+ //parent isn't selected and not a type of:
+ // SmExpressionNode
+ // SmBinHorNode
+ // SmUnHorNode
+ // SmAlignNode
+ // SmFontNode
+ while((MoveUpIfSelected && pSNode->GetParent()->IsSelected()) ||
+ IsLineCompositionNode(pSNode->GetParent())){
+ pSNode = pSNode->GetParent();
+ j_assert(pSNode, "pSNode shouldn't be NULL, have we hit root node if so, this is bad!");
+ if(!pSNode) //I've got to do something, nothing is probably the best solution :)
+ return NULL;
+ }
+ //Now we have the selection line node
+ return pSNode;
+}
+
+SmNode* SmCursor::FindSelectedNode(SmNode* pNode){
+ SmNodeIterator it(pNode);
+ while(it.Next()){
+ if(it->IsSelected())
+ return it.Current();
+ SmNode* pRetVal = FindSelectedNode(it.Current());
+ if(pRetVal)
+ return pRetVal;
+ }
+ return NULL;
+}
+
+SmNodeList* SmCursor::LineToList(SmStructureNode* pLine, SmNodeList* list){
+ SmNodeIterator it(pLine);
+ while(it.Next()){
+ switch(it->GetType()){
+ case NUNHOR:
+ case NEXPRESSION:
+ case NBINHOR:
+ case NALIGN:
+ case NFONT:
+ LineToList((SmStructureNode*)it.Current(), list);
+ break;
+ case NERROR:
+ delete it.Current();
+ break;
+ default:
+ list->push_back(it.Current());
+ }
+ }
+ SmNodeArray emptyArray(0);
+ pLine->SetSubNodes(emptyArray);
+ delete pLine;
+ return list;
+}
+
+SmNodeList* SmCursor::CloneLineToList(SmStructureNode* pLine, bool bOnlyIfSelected, SmNodeList* pList){
+ SmCloningVisitor aCloneFactory;
+ SmNodeIterator it(pLine);
+ while(it.Next()){
+ if( IsLineCompositionNode( it.Current() ) )
+ CloneLineToList( (SmStructureNode*)it.Current(), bOnlyIfSelected, pList );
+ else if( (!bOnlyIfSelected || it->IsSelected()) && it->GetType() != NERROR ) {
+ //Only clone selected text from SmTextNode
+ if(it->GetType() == NTEXT) {
+ SmTextNode *pText = (SmTextNode*)it.Current();
+ SmTextNode *pClone = new SmTextNode( it->GetToken(), pText->GetFontDesc() );
+ int start = pText->GetSelectionStart(),
+ length = pText->GetSelectionEnd() - pText->GetSelectionStart();
+ pClone->ChangeText(pText->GetText().Copy(start, length));
+ pClone->SetScaleMode(pText->GetScaleMode());
+ pList->push_back(pClone);
+ } else
+ pList->push_back(aCloneFactory.Clone(it.Current()));
+ }
+ }
+ return pList;
+}
+
+bool SmCursor::IsLineCompositionNode(SmNode* pNode){
+ switch(pNode->GetType()){
+ case NUNHOR:
+ case NEXPRESSION:
+ case NBINHOR:
+ case NALIGN:
+ case NFONT:
+ return true;
+ default:
+ return false;
+ }
+ return false;
+}
+
+int SmCursor::CountSelectedNodes(SmNode* pNode){
+ int nCount = 0;
+ SmNodeIterator it(pNode);
+ while(it.Next()){
+ if(it->IsSelected() && !IsLineCompositionNode(it.Current()))
+ nCount++;
+ nCount += CountSelectedNodes(it.Current());
+ }
+ return nCount;
+}
+
+bool SmCursor::HasComplexSelection(){
+ if(!HasSelection())
+ return false;
+ AnnotateSelection();
+
+ return CountSelectedNodes(pTree) > 1;
+}
+
+void SmCursor::FinishEdit(SmNodeList* pLineList,
+ SmStructureNode* pParent,
+ int nParentIndex,
+ SmCaretPos PosAfterEdit,
+ SmNode* pStartLine) {
+ //Store number of nodes in line for later
+ int entries = pLineList->size();
+
+ //Parse list of nodes to a tree
+ SmNodeListParser parser;
+ SmNode* pLine = parser.Parse(pLineList);
+ delete pLineList;
+
+ //Check if we're making the body of a subsup node bigger than one
+ if(pParent->GetType() == NSUBSUP &&
+ nParentIndex == 0 &&
+ entries > 1) {
+ //Wrap pLine in scalable round brackets
+ SmToken aTok(TLEFT, '\0', "left", 0, 5);
+ SmBraceNode *pBrace = new SmBraceNode(aTok);
+ pBrace->SetScaleMode(SCALE_HEIGHT);
+ SmNode *pLeft = CreateBracket(RoundBrackets, true),
+ *pRight = CreateBracket(RoundBrackets, false);
+ SmBracebodyNode *pBody = new SmBracebodyNode(SmToken());
+ pBody->SetSubNodes(pLine, NULL);
+ pBrace->SetSubNodes(pLeft, pBody, pRight);
+ pBrace->Prepare(pDocShell->GetFormat(), *pDocShell);
+ pLine = pBrace;
+ //TODO: Consider the following alternative behavior:
+ //Consider the line: A + {B + C}^D lsub E
+ //Here pLineList is B, + and C and pParent is a subsup node with
+ //both RSUP and LSUB set. Imagine the user just inserted "B +" in
+ //the body of the subsup node...
+ //The most natural thing to do would be to make the line like this:
+ //A + B lsub E + C ^ D
+ //E.g. apply LSUB and LSUP to the first element in pLineList and RSUP
+ //and RSUB to the last eleent in pLineList. But how should this act
+ //for CSUP and CSUB ???
+ //For this reason and because brackets was faster to implement, this solution
+ //have been choosen. It might be worth working on the other solution later...
+ }
+
+ //Set pStartLine if NULL
+ if(!pStartLine)
+ pStartLine = pLine;
+
+ //Insert it back into the parent
+ pParent->SetSubNode(nParentIndex, pLine);
+
+ //Rebuild graph of caret position
+ anchor = NULL;
+ position = NULL;
+ BuildGraph();
+ AnnotateSelection(); //Update selection annotation!
+
+ //Set caret position
+ if(!SetCaretPosition(PosAfterEdit, true))
+ SetCaretPosition(SmCaretPos(pStartLine, 0), true);
+
+ //End edit section
+ EndEdit();
+}
+
+void SmCursor::BeginEdit(){
+ if(nEditSections++ > 0) return;
+
+ bIsEnabledSetModifiedSmDocShell = pDocShell->IsEnableSetModified();
+ if( bIsEnabledSetModifiedSmDocShell )
+ pDocShell->EnableSetModified( FALSE );
+}
+
+void SmCursor::EndEdit(){
+ if(--nEditSections > 0) return;
+
+ pDocShell->SetFormulaArranged(FALSE);
+ //Okay, I don't know what this does... :)
+ //It's used in SmDocShell::SetText and with places where everything is modified.
+ //I think it does some magic, with sfx, but everything is totally undocumented so
+ //it's kinda hard to tell...
+ if ( bIsEnabledSetModifiedSmDocShell )
+ pDocShell->EnableSetModified( bIsEnabledSetModifiedSmDocShell );
+ //I think this notifies people around us that we've modified this document...
+ pDocShell->SetModified(TRUE);
+ //I think SmDocShell uses this value when it sends an update graphics event
+ //Anyway comments elsewhere suggests it need to be updated...
+ pDocShell->nModifyCount++;
+
+ //TODO: Consider copying the update accessability code from SmDocShell::SetText in here...
+ //This somehow updates the size of SmGraphicView if it is running in embedded mode
+ if( pDocShell->GetCreateMode() == SFX_CREATE_MODE_EMBEDDED )
+ pDocShell->OnDocumentPrinterChanged(0);
+
+ //Request a replaint...
+ RequestRepaint();
+
+ //Update the edit engine and text of the document
+ String formula;
+ SmNodeToTextVisitor(pTree, formula);
+ //pTree->CreateTextFromNode(formula);
+ pDocShell->aText = formula;
+ pDocShell->GetEditEngine().SetText(formula);
+}
+
+void SmCursor::RequestRepaint(){
+ SmViewShell *pViewSh = SmGetActiveView();
+ if( pViewSh ) {
+ if ( SFX_CREATE_MODE_EMBEDDED == pDocShell->GetCreateMode() )
+ pDocShell->Repaint();
+ else
+ pViewSh->GetGraphicWindow().Invalidate();
+ }
+}
+
+/////////////////////////////////////// SmNodeListParser ///////////////////////////////////////
+
+SmNode* SmNodeListParser::Parse(SmNodeList* list, bool bDeleteErrorNodes){
+ pList = list;
+ if(bDeleteErrorNodes){
+ //Delete error nodes
+ SmNodeList::iterator it = pList->begin();
+ while(it != pList->end()) {
+ if((*it)->GetType() == NERROR){
+ //Delete and erase
+ delete *it;
+ it = pList->erase(it);
+ }else
+ it++;
+ }
+ }
+ SmNode* retval = Expression();
+ pList = NULL;
+ return retval;
+}
+
+SmNode* SmNodeListParser::Expression(){
+ SmNodeArray NodeArray;
+ //Accept as many relations as there is
+ while(Terminal())
+ NodeArray.push_back(Relation());
+
+ //Create SmExpressionNode, I hope SmToken() will do :)
+ SmStructureNode* pExpr = new SmExpressionNode(SmToken());
+ pExpr->SetSubNodes(NodeArray);
+ return pExpr;
+}
+
+SmNode* SmNodeListParser::Relation(){
+ //Read a sum
+ SmNode* pLeft = Sum();
+ //While we have tokens and the next is a relation
+ while(Terminal() && IsRelationOperator(Terminal()->GetToken())){
+ //Take the operator
+ SmNode* pOper = Take();
+ //Find the right side of the relation
+ SmNode* pRight = Sum();
+ //Create new SmBinHorNode
+ SmStructureNode* pNewNode = new SmBinHorNode(SmToken());
+ pNewNode->SetSubNodes(pLeft, pOper, pRight);
+ pLeft = pNewNode;
+ }
+ return pLeft;
+}
+
+SmNode* SmNodeListParser::Sum(){
+ //Read a product
+ SmNode* pLeft = Product();
+ //While we have tokens and the next is a sum
+ while(Terminal() && IsSumOperator(Terminal()->GetToken())){
+ //Take the operator
+ SmNode* pOper = Take();
+ //Find the right side of the sum
+ SmNode* pRight = Product();
+ //Create new SmBinHorNode
+ SmStructureNode* pNewNode = new SmBinHorNode(SmToken());
+ pNewNode->SetSubNodes(pLeft, pOper, pRight);
+ pLeft = pNewNode;
+ }
+ return pLeft;
+}
+
+SmNode* SmNodeListParser::Product(){
+ //Read a Factor
+ SmNode* pLeft = Factor();
+ //While we have tokens and the next is a product
+ while(Terminal() && IsProductOperator(Terminal()->GetToken())){
+ //Take the operator
+ SmNode* pOper = Take();
+ //Find the right side of the operation
+ SmNode* pRight = Factor();
+ //Create new SmBinHorNode
+ SmStructureNode* pNewNode = new SmBinHorNode(SmToken());
+ pNewNode->SetSubNodes(pLeft, pOper, pRight);
+ pLeft = pNewNode;
+ }
+ return pLeft;
+}
+
+SmNode* SmNodeListParser::Factor(){
+ //Read unary operations
+ if(!Terminal())
+ return Error();
+ //Take care of unary operators
+ else if(IsUnaryOperator(Terminal()->GetToken()))
+ {
+ SmStructureNode *pUnary = new SmUnHorNode(SmToken());
+ SmNode *pOper = Terminal(),
+ *pArg;
+
+ if(Next())
+ pArg = Factor();
+ else
+ pArg = Error();
+
+ pUnary->SetSubNodes(pOper, pArg);
+ return pUnary;
+ }
+ return Postfix();
+}
+
+SmNode* SmNodeListParser::Postfix(){
+ if(!Terminal())
+ return Error();
+ SmNode *pArg = NULL;
+ if(IsPostfixOperator(Terminal()->GetToken()))
+ pArg = Error();
+ else if(IsOperator(Terminal()->GetToken()))
+ return Error();
+ else
+ pArg = Take();
+ while(Terminal() && IsPostfixOperator(Terminal()->GetToken())) {
+ SmStructureNode *pUnary = new SmUnHorNode(SmToken());
+ SmNode *pOper = Take();
+ pUnary->SetSubNodes(pArg, pOper);
+ pArg = pUnary;
+ }
+ return pArg;
+}
+
+SmNode* SmNodeListParser::Error(){
+ return new SmErrorNode(PE_UNEXPECTED_TOKEN, SmToken());
+}
+
+BOOL SmNodeListParser::IsOperator(const SmToken &token) {
+ return IsRelationOperator(token) ||
+ IsSumOperator(token) ||
+ IsProductOperator(token) ||
+ IsUnaryOperator(token) ||
+ IsPostfixOperator(token);
+}
+
+BOOL SmNodeListParser::IsRelationOperator(const SmToken &token) {
+ return token.nGroup & TGRELATION;
+}
+
+BOOL SmNodeListParser::IsSumOperator(const SmToken &token) {
+ return token.nGroup & TGSUM;
+}
+
+BOOL SmNodeListParser::IsProductOperator(const SmToken &token) {
+ return token.nGroup & TGPRODUCT &&
+ token.eType != TWIDESLASH &&
+ token.eType != TWIDEBACKSLASH &&
+ token.eType != TUNDERBRACE &&
+ token.eType != TOVERBRACE &&
+ token.eType != TOVER;
+}
+
+BOOL SmNodeListParser::IsUnaryOperator(const SmToken &token) {
+ return token.nGroup & TGUNOPER &&
+ (token.eType == TPLUS ||
+ token.eType == TMINUS ||
+ token.eType == TPLUSMINUS ||
+ token.eType == TMINUSPLUS ||
+ token.eType == TNEG ||
+ token.eType == TUOPER);
+}
+
+BOOL SmNodeListParser::IsPostfixOperator(const SmToken &token) {
+ return token.eType == TFACT;
+}
diff --git a/starmath/source/dialog.cxx b/starmath/source/dialog.cxx
index 6ecbe37e0a4a..e69c0c31fa09 100644
--- a/starmath/source/dialog.cxx
+++ b/starmath/source/dialog.cxx
@@ -1486,8 +1486,8 @@ IMPL_LINK( SmSymbolDialog, GetClickHdl, Button *, EMPTYARG pButton )
aText += (sal_Unicode)' ';
rViewSh.GetViewFrame()->GetDispatcher()->Execute(
- SID_INSERTTEXT, SFX_CALLMODE_STANDARD,
- new SfxStringItem(SID_INSERTTEXT, aText), 0L);
+ SID_INSERTSYMBOL, SFX_CALLMODE_STANDARD,
+ new SfxStringItem(SID_INSERTSYMBOL, aText), 0L);
}
return 0;
diff --git a/starmath/source/document.cxx b/starmath/source/document.cxx
index cb66b9c196cb..e4d3f0698951 100644
--- a/starmath/source/document.cxx
+++ b/starmath/source/document.cxx
@@ -97,6 +97,7 @@
#include "mathmlexport.hxx"
#include <sfx2/sfxsids.hrc>
#include <svx/svxids.hrc>
+#include "cursor.hxx"
using namespace ::com::sun::star;
using namespace ::com::sun::star::accessibility;
@@ -251,6 +252,7 @@ void SmDocShell::Parse()
pTree = aInterpreter.Parse(aText);
nModifyCount++; //! see comment for SID_GAPHIC_SM in SmDocShell::GetState
SetFormulaArranged( FALSE );
+ InvalidateCursor();
}
@@ -433,9 +435,10 @@ SfxItemPool& SmDocShell::GetEditEngineItemPool()
OSL_ENSURE( pEditEngineItemPool, "EditEngineItemPool missing" );
return *pEditEngineItemPool;
}
+//TODO: Move to the top of the file...
+#include "visitors.hxx"
-
-void SmDocShell::Draw(OutputDevice &rDev, Point &rPosition)
+void SmDocShell::DrawFormula(OutputDevice &rDev, Point &rPosition, BOOL bDrawSelection)
{
RTL_LOGFILE_CONTEXT( aLog, "starmath: SmDocShell::Draw" );
@@ -476,8 +479,16 @@ void SmDocShell::Draw(OutputDevice &rDev, Point &rPosition)
rDev.SetLayoutMode( TEXT_LAYOUT_BIDI_LTR );
INT16 nDigitLang = rDev.GetDigitLanguage();
rDev.SetDigitLanguage( LANGUAGE_ENGLISH );
- //
- pTree->Draw(rDev, rPosition);
+
+ //Set selection if any
+ if(pCursor && bDrawSelection){
+ pCursor->AnnotateSelection();
+ SmSelectionDrawingVisitor(rDev, pTree, rPosition);
+ }
+
+ //Drawing using visitor
+ SmDrawingVisitor(rDev, rPosition, pTree);
+
//
rDev.SetLayoutMode( nLayoutMode );
rDev.SetDigitLanguage( nDigitLang );
@@ -518,6 +529,18 @@ Size SmDocShell::GetSize()
return aRet;
}
+void SmDocShell::InvalidateCursor(){
+ if(pCursor)
+ delete pCursor;
+ pCursor = NULL;
+}
+
+SmCursor& SmDocShell::GetCursor(){
+ if(!pCursor)
+ pCursor = new SmCursor(pTree, this);
+ return *pCursor;
+}
+
////////////////////////////////////////
SmPrinterAccess::SmPrinterAccess( SmDocShell &rDocShell )
@@ -690,6 +713,7 @@ SmDocShell::SmDocShell( const sal_uInt64 i_nSfxCreationFlags ) :
nModifyCount ( 0 ),
bIsFormulaArranged ( FALSE )
{
+ pCursor = NULL;
RTL_LOGFILE_CONTEXT( aLog, "starmath: SmDocShell::SmDocShell" );
SetPool(&SFX_APP()->GetPool());
@@ -714,6 +738,11 @@ SmDocShell::~SmDocShell()
EndListening(aFormat);
EndListening(*pp->GetConfig());
+
+ if(pCursor)
+ delete pCursor;
+ pCursor = NULL;
+
delete pEditEngine;
SfxItemPool::Free(pEditEngineItemPool);
delete pTree;
@@ -745,6 +774,7 @@ BOOL SmDocShell::ConvertFrom(SfxMedium &rMedium)
{
delete pTree;
pTree = 0;
+ InvalidateCursor();
}
Reference<com::sun::star::frame::XModel> xModel(GetModel());
SmXMLImportWrapper aEquation(xModel);
@@ -1301,7 +1331,7 @@ void SmDocShell::Draw(OutputDevice *pDevice,
pDevice->IntersectClipRegion(GetVisArea());
Point atmppoint;
- Draw(*pDevice, atmppoint);
+ DrawFormula(*pDevice, atmppoint);
}
SfxItemPool& SmDocShell::GetPool() const
diff --git a/starmath/source/edit.cxx b/starmath/source/edit.cxx
index a99531515bc7..165c75fb2f43 100644
--- a/starmath/source/edit.cxx
+++ b/starmath/source/edit.cxx
@@ -122,9 +122,6 @@ SmEditWindow::SmEditWindow( SmCmdBoxWindow &rMyCmdBoxWin ) :
aModifyTimer.SetTimeoutHdl(LINK(this, SmEditWindow, ModifyTimerHdl));
aModifyTimer.SetTimeout(500);
- aCursorMoveTimer.SetTimeoutHdl(LINK(this, SmEditWindow, CursorMoveTimerHdl));
- aCursorMoveTimer.SetTimeout(500);
-
// if not called explicitly the this edit window within the
// command window will just show an empty gray panel.
Show();
@@ -133,7 +130,6 @@ SmEditWindow::SmEditWindow( SmCmdBoxWindow &rMyCmdBoxWin ) :
SmEditWindow::~SmEditWindow()
{
- aCursorMoveTimer.Stop();
aModifyTimer.Stop();
@@ -257,36 +253,6 @@ IMPL_LINK( SmEditWindow, ModifyTimerHdl, Timer *, EMPTYARG /*pTimer*/ )
return 0;
}
-
-IMPL_LINK(SmEditWindow, CursorMoveTimerHdl, Timer *, EMPTYARG /*pTimer*/)
- // every once in a while check cursor position (selection) of edit
- // window and if it has changed (try to) set the formula-cursor
- // according to that.
-{
- ESelection aNewSelection (GetSelection());
-
- if (!aNewSelection.IsEqual(aOldSelection))
- { SmViewShell *pView = rCmdBox.GetView();
-
- if (pView)
- {
- // get row and column to look for
- USHORT nRow, nCol;
- SmGetLeftSelectionPart(aNewSelection, nRow, nCol);
- nRow++;
- nCol++;
-
- pView->GetGraphicWindow().SetCursorPos(nRow, nCol);
-
- aOldSelection = aNewSelection;
- }
- }
- aCursorMoveTimer.Stop();
-
- return 0;
-}
-
-
void SmEditWindow::Resize()
{
if (!pEditView)
@@ -320,8 +286,6 @@ void SmEditWindow::MouseButtonUp(const MouseEvent &rEvt)
else
Window::MouseButtonUp (rEvt);
- // ggf FormulaCursor neu positionieren
- CursorMoveTimerHdl(&aCursorMoveTimer);
InvalidateSlots();
}
@@ -426,11 +390,6 @@ void SmEditWindow::KeyInput(const KeyEvent& rKEvt)
}
else
{
- // Timer neu starten, um den Handler (auch bei laengeren Eingaben)
- // moeglichst nur einmal am Ende aufzurufen.
- aCursorMoveTimer.Start();
-
- OSL_ENSURE( pEditView, "EditView missing (NULL pointer)" );
if (!pEditView)
CreateEditView();
if ( !pEditView->PostKeyEvent(rKEvt) )
@@ -632,7 +591,6 @@ void SmEditWindow::SetText(const XubString& rText)
//! Hier die Timer neu zu starten verhindert, dass die Handler fuer andere
//! (im Augenblick nicht mehr aktive) Math Tasks aufgerufen werden.
aModifyTimer.Start();
- aCursorMoveTimer.Start();
pEditView->SetSelection(eSelection);
}
@@ -656,6 +614,10 @@ void SmEditWindow::GetFocus()
EditEngine *pEditEngine = GetEditEngine();
if (pEditEngine)
pEditEngine->SetStatusEventHdl( LINK(this, SmEditWindow, EditStatusHdl) );
+
+ //Let SmViewShell know we got focus
+ if(GetView())
+ GetView()->SetInsertIntoEditWindow(TRUE);
}
@@ -738,7 +700,6 @@ void SmEditWindow::InsertCommand(USHORT nCommand)
}
aModifyTimer.Start();
- aCursorMoveTimer.Start();
GrabFocus();
}
@@ -926,7 +887,6 @@ void SmEditWindow::InsertText(const String& Text)
{
pEditView->InsertText(Text);
aModifyTimer.Start();
- aCursorMoveTimer.Start();
}
}
@@ -944,13 +904,6 @@ void SmEditWindow::Flush()
new SfxStringItem(SID_TEXT, GetText()), 0L);
}
}
-
- if (aCursorMoveTimer.IsActive())
- {
- aCursorMoveTimer.Stop();
- // ggf noch die (neue) FormulaCursor Position setzen
- CursorMoveTimerHdl(&aCursorMoveTimer);
- }
}
diff --git a/starmath/source/makefile.mk b/starmath/source/makefile.mk
index a409e55d2462..ab8f39c5c50c 100644
--- a/starmath/source/makefile.mk
+++ b/starmath/source/makefile.mk
@@ -66,6 +66,9 @@ SLO1FILES = \
$(SLO)$/format.obj \
$(SLO)$/mathtype.obj \
$(SLO)$/node.obj \
+ $(SLO)$/visitors.obj \
+ $(SLO)$/caret.obj \
+ $(SLO)$/cursor.obj \
$(SLO)$/parse.obj \
$(SLO)$/register.obj \
$(SLO)$/smdll.obj \
diff --git a/starmath/source/node.cxx b/starmath/source/node.cxx
index b2e8ca76dbeb..ecfa7d52b08c 100644
--- a/starmath/source/node.cxx
+++ b/starmath/source/node.cxx
@@ -36,6 +36,7 @@
#include "document.hxx"
#include "view.hxx"
#include "mathtype.hxx"
+#include "visitors.hxx"
#include <tools/gen.hxx>
#include <tools/fract.hxx>
@@ -143,6 +144,8 @@ SmNode::SmNode(SmNodeType eNodeType, const SmToken &rNodeToken)
eScaleMode = SCALE_NONE;
aNodeToken = rNodeToken;
nAccIndex = -1;
+ SetSelected(false);
+ aParentNode = NULL;
}
@@ -445,28 +448,6 @@ void SmNode::AdaptToY(const OutputDevice &/*rDev*/, ULONG /*nHeight*/)
}
-void SmNode::Draw(OutputDevice &rDev, const Point &rPosition) const
-{
- if (IsPhantom())
- return;
-
- const SmNode *pNode;
- USHORT nSize = GetNumSubNodes();
- for (USHORT i = 0; i < nSize; i++)
- if (NULL != (pNode = GetSubNode(i)))
- { Point aOffset (pNode->GetTopLeft() - GetTopLeft());
- pNode->Draw(rDev, rPosition + aOffset);
- }
-
-#ifdef SM_RECT_DEBUG
- if (!IsDebug())
- return;
-
- int nRFlags = SM_RECT_CORE | SM_RECT_ITALIC | SM_RECT_LINES | SM_RECT_MID;
- SmRect::Draw(rDev, rPosition, nRFlags);
-#endif
-}
-
const SmNode * SmNode::FindTokenAt(USHORT nRow, USHORT nCol) const
// returns (first) ** visible ** (sub)node with the tokens text at
// position 'nRow', 'nCol'.
@@ -567,6 +548,100 @@ const SmNode * SmNode::FindNodeWithAccessibleIndex(xub_StrLen nAccIdx) const
return pResult;
}
+#ifdef DEBUG_ENABLE_DUMPASDOT
+void SmNode::DumpAsDot(std::ostream &out, String* label, int number, int& id, int parent) const
+{
+ //If this is the root start the file
+ if(number == -1){
+ out<<"digraph {"<<std::endl;
+ if(label){
+ out<<"labelloc = \"t\";"<<std::endl;
+ String eq(*label);
+ //CreateTextFromNode(eq);
+ eq.SearchAndReplaceAll(String::CreateFromAscii("\n"), String::CreateFromAscii(" "));
+ eq.SearchAndReplaceAll(String::CreateFromAscii("\\"), String::CreateFromAscii("\\\\"));
+ eq.SearchAndReplaceAll(String::CreateFromAscii("\""), String::CreateFromAscii("\\\""));
+ out<<"label= \"Equation: \\\"";
+ out<<ByteString( eq, RTL_TEXTENCODING_UTF8).GetBuffer();
+ out<<"\\\"\";"<<std::endl;
+ }
+ }
+
+ //Some how out<<(int)this; doesn't work... So we do this nasty workaround...
+ char strid[100];
+ sprintf(strid, "%i", id);
+
+ char strnr[100];
+ sprintf(strnr, "%i", number);
+
+ //Dump connection to this node
+ if( parent != -1 ){
+ char pid[100];
+ sprintf(pid, "%i", parent);
+ out<<"n"<<pid<<" -> n"<<strid<<" [label=\""<<strnr<<"\"];"<<std::endl;
+ //If doesn't have parent and isn't a rootnode:
+ } else if(number != -1) {
+ out<<"orphaned -> n"<<strid<<" [label=\""<<strnr<<"\"];"<<std::endl;
+ }
+
+ //Dump this node
+ out<<"n"<< strid<<" [label=\"";
+ switch( GetType() ) {
+ case NTABLE: out<<"SmTableNode"; break;
+ case NBRACE: out<<"SmBraceNode"; break;
+ case NBRACEBODY: out<<"SmBracebodyNode"; break;
+ case NOPER: out<<"SmOperNode"; break;
+ case NALIGN: out<<"SmAlignNode"; break;
+ case NATTRIBUT: out<<"SmAttributNode"; break;
+ case NFONT: out<<"SmFontNode"; break;
+ case NUNHOR: out<<"SmUnHorNode"; break;
+ case NBINHOR: out<<"SmBinHorNode"; break;
+ case NBINVER: out<<"SmBinVerNode"; break;
+ case NBINDIAGONAL: out<<"SmBinDiagonalNode"; break;
+ case NSUBSUP: out<<"SmSubSupNode"; break;
+ case NMATRIX: out<<"SmMatrixNode"; break;
+ case NPLACE: out<<"SmPlaceNode"; break;
+ case NTEXT:
+ out<<"SmTextNode: ";
+ out<< ByteString( ((SmTextNode*)this)->GetText(), RTL_TEXTENCODING_UTF8).GetBuffer();
+ break;
+ case NSPECIAL: out<<"SmSpecialNode"; break;
+ case NGLYPH_SPECIAL: out<<"SmGlyphSpecialNode"; break;
+ case NMATH:
+ out<<"SmMathSymbolNode: ";
+ out<< ByteString( ((SmMathSymbolNode*)this)->GetText(), RTL_TEXTENCODING_UTF8).GetBuffer();
+ break;
+ case NBLANK: out<<"SmBlankNode"; break;
+ case NERROR: out<<"SmErrorNode"; break;
+ case NLINE: out<<"SmLineNode"; break;
+ case NEXPRESSION: out<<"SmExpressionNode"; break;
+ case NPOLYLINE: out<<"SmPolyLineNode"; break;
+ case NROOT: out<<"SmRootNode"; break;
+ case NROOTSYMBOL: out<<"SmRootSymbolNode"; break;
+ case NRECTANGLE: out<<"SmRectangleNode"; break;
+ case NVERTICAL_BRACE: out<<"SmVerticalBraceNode"; break;
+ default:
+ out<<"Unknown Node";
+ }
+ out<<"\"";
+ if(IsSelected())
+ out<<", style=dashed";
+ out<<"];"<<std::endl;
+
+ //Dump subnodes
+ int myid = id;
+ const SmNode *pNode;
+ USHORT nSize = GetNumSubNodes();
+ for (USHORT i = 0; i < nSize; i++)
+ if (NULL != (pNode = GetSubNode(i)))
+ pNode->DumpAsDot(out, NULL, i, ++id, myid);
+
+ //If this is the root end the file
+ if( number == -1 )
+ out<<"}"<<std::endl;
+}
+#endif /* DEBUG_ENABLE_DUMPASDOT */
+
///////////////////////////////////////////////////////////////////////////
SmStructureNode::SmStructureNode( const SmStructureNode &rNode ) :
@@ -584,6 +659,7 @@ SmStructureNode::SmStructureNode( const SmStructureNode &rNode ) :
SmNode *pNode = rNode.aSubNodes[i];
aSubNodes[i] = pNode ? new SmNode( *pNode ) : 0;
}
+ ClaimPaternity();
}
@@ -614,6 +690,8 @@ SmStructureNode & SmStructureNode::operator = ( const SmStructureNode &rNode )
aSubNodes[i] = pNode ? new SmNode( *pNode ) : 0;
}
+ ClaimPaternity();
+
return *this;
}
@@ -628,12 +706,15 @@ void SmStructureNode::SetSubNodes(SmNode *pFirst, SmNode *pSecond, SmNode *pThir
aSubNodes[1] = pSecond;
if (pThird)
aSubNodes[2] = pThird;
+
+ ClaimPaternity();
}
void SmStructureNode::SetSubNodes(const SmNodeArray &rNodeArray)
{
aSubNodes = rNodeArray;
+ ClaimPaternity();
}
@@ -2175,36 +2256,6 @@ void SmPolyLineNode::Arrange(const OutputDevice &rDev, const SmFormat &rFormat)
}
-void SmPolyLineNode::Draw(OutputDevice &rDev, const Point &rPosition) const
-{
- if (IsPhantom())
- return;
-
- long nBorderwidth = GetFont().GetBorderWidth();
-
- LineInfo aInfo;
- aInfo.SetWidth(nWidth - 2 * nBorderwidth);
-
- Point aOffset (Point() - aPoly.GetBoundRect().TopLeft()
- + Point(nBorderwidth, nBorderwidth)),
- aPos (rPosition + aOffset);
- ((Polygon &) aPoly).Move(aPos.X(), aPos.Y());
-
- SmTmpDevice aTmpDev ((OutputDevice &) rDev, FALSE);
- aTmpDev.SetLineColor( GetFont().GetColor() );
-
- rDev.DrawPolyLine(aPoly, aInfo);
-
-#ifdef SM_RECT_DEBUG
- if (!IsDebug())
- return;
-
- int nRFlags = SM_RECT_CORE | SM_RECT_ITALIC | SM_RECT_LINES | SM_RECT_MID;
- SmRect::Draw(rDev, rPosition, nRFlags);
-#endif
-}
-
-
/**************************************************************************/
void SmRootSymbolNode::AdaptToX(const OutputDevice &/*rDev*/, ULONG nWidth)
@@ -2221,48 +2272,6 @@ void SmRootSymbolNode::AdaptToY(const OutputDevice &rDev, ULONG nHeight)
}
-void SmRootSymbolNode::Draw(OutputDevice &rDev, const Point &rPosition) const
-{
- if (IsPhantom())
- return;
-
- // draw root-sign itself
- SmMathSymbolNode::Draw(rDev, rPosition);
-
- SmTmpDevice aTmpDev( (OutputDevice &) rDev, TRUE );
- aTmpDev.SetFillColor(GetFont().GetColor());
- rDev.SetLineColor();
- aTmpDev.SetFont( GetFont() );
-
- // since the width is always unscaled it corresponds ot the _original_
- // _unscaled_ font height to be used, we use that to calculate the
- // bar height. Thus it is independent of the arguments height.
- // ( see display of sqrt QQQ versus sqrt stack{Q#Q#Q#Q} )
- long nBarHeight = GetWidth() * 7L / 100L;
- long nBarWidth = nBodyWidth + GetBorderWidth();
- Point aBarOffset( GetWidth(), +GetBorderWidth() );
- Point aBarPos( rPosition + aBarOffset );
-
- Rectangle aBar(aBarPos, Size( nBarWidth, nBarHeight) );
- //! avoid GROWING AND SHRINKING of drawn rectangle when constantly
- //! increasing zoomfactor.
- // This is done by shifting it's output-position to a point that
- // corresponds exactly to a pixel on the output device.
- Point aDrawPos( rDev.PixelToLogic(rDev.LogicToPixel(aBar.TopLeft())) );
- aBar.SetPos( aDrawPos );
-
- rDev.DrawRect( aBar );
-
-#ifdef SM_RECT_DEBUG
- if (!IsDebug())
- return;
-
- int nRFlags = SM_RECT_CORE | SM_RECT_ITALIC | SM_RECT_LINES | SM_RECT_MID;
- SmRect::Draw(rDev, rPosition, nRFlags);
-#endif
-}
-
-
/**************************************************************************/
@@ -2302,47 +2311,6 @@ void SmRectangleNode::Arrange(const OutputDevice &rDev, const SmFormat &/*rForma
}
-void SmRectangleNode::Draw(OutputDevice &rDev, const Point &rPosition) const
-{
- if (IsPhantom())
- return;
-
- SmTmpDevice aTmpDev ((OutputDevice &) rDev, FALSE);
- aTmpDev.SetFillColor(GetFont().GetColor());
- rDev.SetLineColor();
- aTmpDev.SetFont(GetFont());
-
- ULONG nTmpBorderWidth = GetFont().GetBorderWidth();
-
- // get rectangle and remove borderspace
- Rectangle aTmp (AsRectangle() + rPosition - GetTopLeft());
- aTmp.Left() += nTmpBorderWidth;
- aTmp.Right() -= nTmpBorderWidth;
- aTmp.Top() += nTmpBorderWidth;
- aTmp.Bottom() -= nTmpBorderWidth;
-
- OSL_ENSURE(aTmp.GetHeight() > 0 && aTmp.GetWidth() > 0,
- "Sm: empty rectangle");
-
- //! avoid GROWING AND SHRINKING of drawn rectangle when constantly
- //! increasing zoomfactor.
- // This is done by shifting it's output-position to a point that
- // corresponds exactly to a pixel on the output device.
- Point aPos (rDev.PixelToLogic(rDev.LogicToPixel(aTmp.TopLeft())));
- aTmp.SetPos(aPos);
-
- rDev.DrawRect(aTmp);
-
-#ifdef SM_RECT_DEBUG
- if (!IsDebug())
- return;
-
- int nRFlags = SM_RECT_CORE | SM_RECT_ITALIC | SM_RECT_LINES | SM_RECT_MID;
- SmRect::Draw(rDev, rPosition, nRFlags);
-#endif
-}
-
-
/**************************************************************************/
@@ -2451,35 +2419,43 @@ void SmTextNode::CreateTextFromNode(String &rText)
rText.Append(' ');
}
-void SmTextNode::Draw(OutputDevice &rDev, const Point& rPosition) const
-{
- if (IsPhantom() || aText.Len() == 0 || aText.GetChar(0) == xub_Unicode('\0'))
- return;
-
- SmTmpDevice aTmpDev ((OutputDevice &) rDev, FALSE);
- aTmpDev.SetFont(GetFont());
-
- Point aPos (rPosition);
- aPos.Y() += GetBaselineOffset();
- // auf Pixelkoordinaten runden
- aPos = rDev.PixelToLogic( rDev.LogicToPixel(aPos) );
-
- rDev.DrawStretchText(aPos, GetWidth(), aText);
-
-#ifdef SM_RECT_DEBUG
- if (!IsDebug())
- return;
-
- int nRFlags = SM_RECT_CORE | SM_RECT_ITALIC | SM_RECT_LINES | SM_RECT_MID;
- SmRect::Draw(rDev, rPosition, nRFlags);
-#endif
-}
void SmTextNode::GetAccessibleText( String &rText ) const
{
rText += aText;
}
+void SmTextNode::AdjustFontDesc()
+{
+ if (GetToken().eType == TTEXT)
+ nFontDesc = FNT_TEXT;
+ else if(GetToken().eType == TFUNC)
+ nFontDesc = FNT_FUNCTION;
+ else {
+ SmTokenType nTok;
+ const SmTokenTableEntry *pEntry = SmParser::GetTokenTableEntry( aText );
+ if (pEntry && pEntry->nGroup == TGFUNCTION) {
+ nTok = pEntry->eType;
+ nFontDesc = FNT_FUNCTION;
+ } else {
+ sal_Unicode firstChar = aText.GetChar(0);
+ if( ('0' <= firstChar && firstChar <= '9') || firstChar == '.' || firstChar == ',') {
+ nFontDesc = FNT_NUMBER;
+ nTok = TNUMBER;
+ } else if (aText.Len() > 1) {
+ nFontDesc = FNT_VARIABLE;
+ nTok = TIDENT;
+ } else {
+ nFontDesc = FNT_VARIABLE;
+ nTok = TCHARACTER;
+ }
+ }
+ SmToken tok = GetToken();
+ tok.eType = nTok;
+ SetToken(tok);
+ }
+}
+
/**************************************************************************/
void SmMatrixNode::CreateTextFromNode(String &rText)
@@ -2908,17 +2884,6 @@ void SmSpecialNode::Arrange(const OutputDevice &rDev, const SmFormat &rFormat)
SmRect::operator = (SmRect(aTmpDev, &rFormat, GetText(), GetFont().GetBorderWidth()));
}
-
-void SmSpecialNode::Draw(OutputDevice &rDev, const Point& rPosition) const
-{
- //! since this chars might come from any font, that we may not have
- //! set to ALIGN_BASELINE yet, we do it now.
- ((SmSpecialNode *)this)->GetFont().SetAlign(ALIGN_BASELINE);
-
- SmTextNode::Draw(rDev, rPosition);
-}
-
-
/**************************************************************************/
@@ -3029,6 +2994,123 @@ void SmBlankNode::Arrange(const OutputDevice &rDev, const SmFormat &rFormat)
SetWidth(nSpace);
}
+/**************************************************************************/
+//Implementation of all accept methods for SmVisitor
+
+void SmNode::Accept(SmVisitor*){
+ //This method is only implemented to avoid making SmNode abstract because an
+ //obscure copy constructor is used... I can't find it's implementation, and
+ //don't want to figure out how to fix it... If you want to, just delete this
+ //method, making SmNode abstract, and see where you can an problem with that.
+ j_assert(false, "SmNode should not be visitable!");
+}
+
+void SmTableNode::Accept(SmVisitor* pVisitor) {
+ pVisitor->Visit(this);
+}
+
+void SmBraceNode::Accept(SmVisitor* pVisitor) {
+ pVisitor->Visit(this);
+}
+
+void SmBracebodyNode::Accept(SmVisitor* pVisitor) {
+ pVisitor->Visit(this);
+}
+
+void SmOperNode::Accept(SmVisitor* pVisitor) {
+ pVisitor->Visit(this);
+}
+
+void SmAlignNode::Accept(SmVisitor* pVisitor) {
+ pVisitor->Visit(this);
+}
+
+void SmAttributNode::Accept(SmVisitor* pVisitor) {
+ pVisitor->Visit(this);
+}
+
+void SmFontNode::Accept(SmVisitor* pVisitor) {
+ pVisitor->Visit(this);
+}
+
+void SmUnHorNode::Accept(SmVisitor* pVisitor) {
+ pVisitor->Visit(this);
+}
+void SmBinHorNode::Accept(SmVisitor* pVisitor) {
+ pVisitor->Visit(this);
+}
+
+void SmBinVerNode::Accept(SmVisitor* pVisitor) {
+ pVisitor->Visit(this);
+}
+
+void SmBinDiagonalNode::Accept(SmVisitor* pVisitor) {
+ pVisitor->Visit(this);
+}
+
+void SmSubSupNode::Accept(SmVisitor* pVisitor) {
+ pVisitor->Visit(this);
+}
+
+void SmMatrixNode::Accept(SmVisitor* pVisitor) {
+ pVisitor->Visit(this);
+}
+
+void SmPlaceNode::Accept(SmVisitor* pVisitor) {
+ pVisitor->Visit(this);
+}
+
+void SmTextNode::Accept(SmVisitor* pVisitor) {
+ pVisitor->Visit(this);
+}
+
+void SmSpecialNode::Accept(SmVisitor* pVisitor) {
+ pVisitor->Visit(this);
+}
+
+void SmGlyphSpecialNode::Accept(SmVisitor* pVisitor) {
+ pVisitor->Visit(this);
+}
+
+void SmMathSymbolNode::Accept(SmVisitor* pVisitor) {
+ pVisitor->Visit(this);
+}
+
+void SmBlankNode::Accept(SmVisitor* pVisitor) {
+ pVisitor->Visit(this);
+}
+
+void SmErrorNode::Accept(SmVisitor* pVisitor) {
+ pVisitor->Visit(this);
+}
+
+void SmLineNode::Accept(SmVisitor* pVisitor) {
+ pVisitor->Visit(this);
+}
+
+void SmExpressionNode::Accept(SmVisitor* pVisitor) {
+ pVisitor->Visit(this);
+}
+
+void SmPolyLineNode::Accept(SmVisitor* pVisitor) {
+ pVisitor->Visit(this);
+}
+
+void SmRootNode::Accept(SmVisitor* pVisitor) {
+ pVisitor->Visit(this);
+}
+
+void SmRootSymbolNode::Accept(SmVisitor* pVisitor) {
+ pVisitor->Visit(this);
+}
+
+void SmRectangleNode::Accept(SmVisitor* pVisitor) {
+ pVisitor->Visit(this);
+}
+
+void SmVerticalBraceNode::Accept(SmVisitor* pVisitor) {
+ pVisitor->Visit(this);
+}
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/starmath/source/parse.cxx b/starmath/source/parse.cxx
index 9f26ed39f626..7952fa02aeb3 100644
--- a/starmath/source/parse.cxx
+++ b/starmath/source/parse.cxx
@@ -82,16 +82,21 @@ SmToken::SmToken() :
nGroup = nCol = nRow = nLevel = 0;
}
+SmToken::SmToken(SmTokenType eTokenType,
+ sal_Unicode cMath,
+ const sal_Char* pText,
+ ULONG nTokenGroup,
+ USHORT nTokenLevel) {
+ eType = eTokenType;
+ cMathChar = cMath;
+ aText.AssignAscii(pText);
+ nGroup = nTokenGroup;
+ nLevel = nTokenLevel;
+ nCol = nRow = 0;
+}
+
///////////////////////////////////////////////////////////////////////////
-struct SmTokenTableEntry
-{
- const sal_Char* pIdent;
- SmTokenType eType;
- sal_Unicode cMathChar;
- ULONG nGroup;
- USHORT nLevel;
-};
static const SmTokenTableEntry aTokenTable[] =
{
@@ -305,8 +310,7 @@ static const SmTokenTableEntry aTokenTable[] =
{ "", TEND, '\0', 0, 0}
};
-
-static const SmTokenTableEntry * GetTokenTableEntry( const String &rName )
+const SmTokenTableEntry * SmParser::GetTokenTableEntry( const String &rName )
{
const SmTokenTableEntry * pRes = 0;
if (rName.Len())
@@ -1083,6 +1087,13 @@ void SmParser::Line()
ExpressionArray[n - 1] = NodeStack.Pop();
}
+ //If there's no expression, add an empty one.
+ //this is to avoid a formula tree without any caret
+ //positions, in visual formula editor.
+ if(ExpressionArray.size() == 0)
+ ExpressionArray.push_back(new SmExpressionNode(SmToken()));
+
+
SmStructureNode *pSNode = new SmLineNode(CurToken);
pSNode->SetSubNodes(ExpressionArray);
NodeStack.Push(pSNode);
@@ -1185,6 +1196,10 @@ void SmParser::Product()
NextToken();
+ //Let the glyph node know it's a binary operation
+ CurToken.eType = TBOPER;
+ CurToken.nGroup = TGPRODUCT;
+
GlyphSpecial();
pOper = NodeStack.Pop();
break;
@@ -1695,6 +1710,9 @@ void SmParser::UnOper()
case TUOPER :
NextToken();
+ //Let the glyph know what it is...
+ CurToken.eType = TUOPER;
+ CurToken.nGroup = TGUNOPER;
GlyphSpecial();
pOper = NodeStack.Pop();
break;
@@ -2194,7 +2212,11 @@ void SmParser::Stack()
NextToken();
- SmStructureNode *pSNode = new SmTableNode(CurToken);
+ //We need to let the table node know it context
+ //it's used in SmNodeToTextVisitor
+ SmToken aTok = CurToken;
+ aTok.eType = TSTACK;
+ SmStructureNode *pSNode = new SmTableNode(aTok);
pSNode->SetSubNodes(ExpressionArray);
NodeStack.Push(pSNode);
}
@@ -2373,7 +2395,7 @@ SmNode *SmParser::Parse(const String &rBuffer)
{
BufferString = rBuffer;
BufferString.ConvertLineEnd( LINEEND_LF );
- BufferIndex =
+ BufferIndex = 0;
nTokenIndex = 0;
Row = 1;
ColOff = 0;
@@ -2393,6 +2415,30 @@ SmNode *SmParser::Parse(const String &rBuffer)
return NodeStack.Pop();
}
+SmNode *SmParser::ParseExpression(const String &rBuffer)
+{
+ BufferString = rBuffer;
+ BufferString.ConvertLineEnd( LINEEND_LF );
+ BufferIndex = 0;
+ nTokenIndex = 0;
+ Row = 1;
+ ColOff = 0;
+ CurError = -1;
+
+ for (USHORT i = 0; i < ErrDescList.Count(); i++)
+ delete ErrDescList.Remove(i);
+
+ ErrDescList.Clear();
+
+ NodeStack.Clear();
+
+ SetLanguage( Application::GetSettings().GetUILanguage() );
+ NextToken();
+ Expression();
+
+ return NodeStack.Pop();
+}
+
USHORT SmParser::AddError(SmParseError Type, SmNode *pNode)
{
diff --git a/starmath/source/view.cxx b/starmath/source/view.cxx
index 7e52a7a6f176..c0d5caf76dd5 100644
--- a/starmath/source/view.cxx
+++ b/starmath/source/view.cxx
@@ -66,6 +66,7 @@
#include <vcl/menu.hxx>
#include <vcl/msgbox.hxx>
#include <vcl/wrkwin.hxx>
+#include <fstream>
#include "unomodel.hxx"
#include "view.hxx"
@@ -75,7 +76,7 @@
#include "starmath.hrc"
#include "toolbox.hxx"
#include "mathmlimport.hxx"
-
+#include "cursor.hxx"
#define MINWIDTH 200
#define MINHEIGHT 200
@@ -98,8 +99,7 @@ SmGraphicWindow::SmGraphicWindow(SmViewShell* pShell):
ScrollableWindow(&pShell->GetViewFrame()->GetWindow(), 0),
pAccessible(0),
pViewShell(pShell),
- nZoom(100),
- bIsCursorVisible(FALSE)
+ nZoom(100)
{
// docking windows are usually hidden (often already done in the
// resource) and will be shown by the sfx framework.
@@ -156,65 +156,32 @@ void SmGraphicWindow::MouseButtonDown(const MouseEvent& rMEvt)
{
ScrollableWindow::MouseButtonDown(rMEvt);
+ GrabFocus();
+
//
// set formula-cursor and selection of edit window according to the
// position clicked at
//
- OSL_ENSURE(rMEvt.GetClicks() > 0, "Sm : 0 clicks");
- if ( rMEvt.IsLeft() && pViewShell->GetEditWindow() )
+ DBG_ASSERT(rMEvt.GetClicks() > 0, "Sm : 0 clicks");
+ if ( rMEvt.IsLeft() )
{
- const SmNode *pTree = pViewShell->GetDoc()->GetFormulaTree();
- //! kann NULL sein! ZB wenn bereits beim laden des Dokuments (bevor der
- //! Parser angeworfen wurde) ins Fenster geklickt wird.
- if (!pTree)
- return;
-
// get click position relativ to formula
Point aPos (PixelToLogic(rMEvt.GetPosPixel())
- GetFormulaDrawPos());
+ const SmNode* pTree = pViewShell->GetDoc()->GetFormulaTree();
+
// if it was clicked inside the formula then get the appropriate node
- const SmNode *pNode = 0;
if (pTree->OrientedDist(aPos) <= 0)
- pNode = pTree->FindRectClosestTo(aPos);
-
- if (pNode)
- { SmEditWindow *pEdit = pViewShell->GetEditWindow();
- const SmToken aToken (pNode->GetToken());
-
-#ifdef notnow
- // include introducing symbols of special char and text
- // (ie '%' and '"')
- USHORT nExtra = (aToken.eType == TSPECIAL || aToken.eType == TTEXT) ? 1 : 0;
-
- // set selection to the beginning of the token
- ESelection aSel (aToken.nRow - 1, aToken.nCol - 1 - nExtra);
-
- if (rMEvt.GetClicks() != 1)
- { // select whole token
- // for text include terminating symbol (ie '"')
- aSel.nEndPos += aToken.aText.Len() + nExtra
- + (aToken.eType == TTEXT ? 1 : 0);
- }
-#endif
- // set selection to the beginning of the token
- ESelection aSel (aToken.nRow - 1, aToken.nCol - 1);
-
- if (rMEvt.GetClicks() != 1 || aToken.eType == TPLACE)
- aSel.nEndPos = aSel.nEndPos + sal::static_int_cast< USHORT >(aToken.aText.Len());
-
- pEdit->SetSelection(aSel);
- SetCursor(pNode);
-
- // allow for immediate editing and
- //! implicitly synchronize the cursor position mark in this window
- pEdit->GrabFocus();
- }
+ pViewShell->GetDoc()->GetCursor().MoveTo(this, aPos, !rMEvt.IsShift());
}
}
void SmGraphicWindow::GetFocus()
{
+ pViewShell->GetEditWindow()->Flush();
+ //Let view shell know what insertions should be done in visual editor
+ pViewShell->SetInsertIntoEditWindow(FALSE);
}
void SmGraphicWindow::LoseFocus()
@@ -230,69 +197,6 @@ void SmGraphicWindow::LoseFocus()
}
}
-void SmGraphicWindow::ShowCursor(BOOL bShow)
- // shows or hides the formula-cursor depending on 'bShow' is TRUE or not
-{
- BOOL bInvert = bShow != IsCursorVisible();
-
- if (bInvert)
- InvertTracking(aCursorRect, SHOWTRACK_SMALL | SHOWTRACK_WINDOW);
-
- SetIsCursorVisible(bShow);
-}
-
-
-void SmGraphicWindow::SetCursor(const SmNode *pNode)
-{
- const SmNode *pTree = pViewShell->GetDoc()->GetFormulaTree();
-
- // get appropriate rectangle
- Point aOffset (pNode->GetTopLeft() - pTree->GetTopLeft()),
- aTLPos (GetFormulaDrawPos() + aOffset);
- aTLPos.X() -= pNode->GetItalicLeftSpace();
- Size aSize (pNode->GetItalicSize());
- Point aBRPos (aTLPos.X() + aSize.Width(), aTLPos.Y() + aSize.Height());
-
- SetCursor(Rectangle(aTLPos, aSize));
-}
-
-void SmGraphicWindow::SetCursor(const Rectangle &rRect)
- // sets cursor to new position (rectangle) 'rRect'.
- // The old cursor will be removed, and the new one will be shown if
- // that is activated in the ConfigItem
-{
- SmModule *pp = SM_MOD();
-
- if (IsCursorVisible())
- ShowCursor(FALSE); // clean up remainings of old cursor
- aCursorRect = rRect;
- if (pp->GetConfig()->IsShowFormulaCursor())
- ShowCursor(TRUE); // draw new cursor
-}
-
-const SmNode * SmGraphicWindow::SetCursorPos(USHORT nRow, USHORT nCol)
- // looks for a VISIBLE node in the formula tree with it's token at
- // (or around) the position 'nRow', 'nCol' in the edit window
- // (row and column numbering starts with 1 there!).
- // If there is such a node the formula-cursor is set to cover that nodes
- // rectangle. If not the formula-cursor will be hidden.
- // In any case the search result is being returned.
-{
- // find visible node with token at nRow, nCol
- const SmNode *pTree = pViewShell->GetDoc()->GetFormulaTree(),
- *pNode = 0;
- if (pTree)
- pNode = pTree->FindTokenAt(nRow, nCol);
-
- if (pNode)
- SetCursor(pNode);
- else
- ShowCursor(FALSE);
-
- return pNode;
-}
-
-
void SmGraphicWindow::Paint(const Rectangle&)
{
OSL_ENSURE(pViewShell, "Sm : NULL pointer");
@@ -300,25 +204,12 @@ void SmGraphicWindow::Paint(const Rectangle&)
SmDocShell &rDoc = *pViewShell->GetDoc();
Point aPoint;
- rDoc.Draw(*this, aPoint); //! modifies aPoint to be the topleft
+ rDoc.DrawFormula(*this, aPoint, TRUE); //! modifies aPoint to be the topleft
//! corner of the formula
SetFormulaDrawPos(aPoint);
-
- SetIsCursorVisible(FALSE); // (old) cursor must be drawn again
-
- const SmEditWindow *pEdit = pViewShell->GetEditWindow();
- if (pEdit)
- { // get new position for formula-cursor (for possible altered formula)
- USHORT nRow, nCol;
- SmGetLeftSelectionPart(pEdit->GetSelection(), nRow, nCol);
- nRow++;
- nCol++;
- const SmNode *pFound = SetCursorPos(nRow, nCol);
-
- SmModule *pp = SM_MOD();
- if (pFound && pp->GetConfig()->IsShowFormulaCursor())
- ShowCursor(TRUE);
- }
+ //Draw cursor if any...
+ if(pViewShell->GetDoc()->HasCursor())
+ pViewShell->GetDoc()->GetCursor().Draw(*this, aPoint);
}
@@ -330,11 +221,113 @@ void SmGraphicWindow::SetTotalSize ()
ScrollableWindow::SetTotalSize( aTmp );
}
-
void SmGraphicWindow::KeyInput(const KeyEvent& rKEvt)
{
- if (! (GetView() && GetView()->KeyInput(rKEvt)) )
- ScrollableWindow::KeyInput(rKEvt);
+ USHORT nCode = rKEvt.GetKeyCode().GetCode();
+ SmCursor& rCursor = pViewShell->GetDoc()->GetCursor();
+ switch(nCode)
+ {
+ case KEY_LEFT:
+ {
+ rCursor.Move(this, MoveLeft, !rKEvt.GetKeyCode().IsShift());
+ }break;
+ case KEY_RIGHT:
+ {
+ rCursor.Move(this, MoveRight, !rKEvt.GetKeyCode().IsShift());
+ }break;
+ case KEY_UP:
+ {
+ rCursor.Move(this, MoveUp, !rKEvt.GetKeyCode().IsShift());
+ }break;
+ case KEY_DOWN:
+ {
+ rCursor.Move(this, MoveDown, !rKEvt.GetKeyCode().IsShift());
+ }break;
+ case KEY_RETURN:
+ {
+ if(!rKEvt.GetKeyCode().IsShift())
+ rCursor.InsertRow();
+#ifdef DEBUG_ENABLE_DUMPASDOT
+ else {
+ SmNode *pTree = (SmNode*)pViewShell->GetDoc()->GetFormulaTree();
+ std::fstream file("/tmp/smath-dump.gv", std::fstream::out);
+ String label(pViewShell->GetDoc()->GetText());
+ pTree->DumpAsDot(file, &label);
+ file.close();
+ }
+#endif /* DEBUG_ENABLE_DUMPASDOT */
+ }break;
+ case KEY_DELETE:
+ case KEY_BACKSPACE:
+ {
+ if(!rCursor.HasSelection()){
+ rCursor.Move(this, nCode == KEY_DELETE ? MoveRight : MoveLeft, false);
+ if(rCursor.HasComplexSelection()) break;
+ }
+ rCursor.Delete();
+ }break;
+ case KEY_ADD:
+ rCursor.InsertElement(PlusElement);
+ break;
+ case KEY_SUBTRACT:
+ if(rKEvt.GetKeyCode().IsShift())
+ rCursor.InsertSubSup(RSUB);
+ else
+ rCursor.InsertElement(MinusElement);
+ break;
+ case KEY_MULTIPLY:
+ rCursor.InsertElement(CDotElement);
+ break;
+ case KEY_DIVIDE:
+ rCursor.InsertFraction();
+ break;
+ case KEY_LESS:
+ rCursor.InsertElement(LessThanElement);
+ break;
+ case KEY_GREATER:
+ rCursor.InsertElement(GreaterThanElement);
+ break;
+ case KEY_EQUAL:
+ rCursor.InsertElement(EqualElement);
+ break;
+ case KEY_COPY:
+ rCursor.Copy();
+ break;
+ case KEY_CUT:
+ rCursor.Cut();
+ break;
+ case KEY_PASTE:
+ rCursor.Paste();
+ break;
+ default:
+ {
+ sal_Unicode code = rKEvt.GetCharCode();
+ if(code == ' ') {
+ rCursor.InsertElement(BlankElement);
+ }else if(code == 'c' && rKEvt.GetKeyCode().IsMod1()) {
+ rCursor.Copy();
+ }else if(code == 'x' && rKEvt.GetKeyCode().IsMod1()) {
+ rCursor.Cut();
+ }else if(code == 'v' && rKEvt.GetKeyCode().IsMod1()) {
+ rCursor.Paste();
+ }else if(code == '^') {
+ rCursor.InsertSubSup(RSUP);
+ }else if(code == '(') {
+ rCursor.InsertBrackets(RoundBrackets);
+ }else if(code == '[') {
+ rCursor.InsertBrackets(SquareBrackets);
+ }else if(code == '{') {
+ rCursor.InsertBrackets(CurlyBrackets);
+ }else if(code == '!') {
+ rCursor.InsertElement(FactorialElement);
+ }else{
+ if(code != 0){
+ rCursor.InsertText(code);
+ }else if (! (GetView() && GetView()->KeyInput(rKEvt)) )
+ ScrollableWindow::KeyInput(rKEvt);
+ }
+ }
+ }
}
@@ -1133,7 +1126,7 @@ void SmViewShell::Impl_Print(
rOutDev.SetMapMode(OutputMapMode);
rOutDev.SetClipRegion(Region(aOutRect));
- GetDoc()->Draw(rOutDev, aPos);
+ GetDoc()->DrawFormula(rOutDev, aPos, FALSE);
rOutDev.SetClipRegion();
rOutDev.Pop();
@@ -1367,7 +1360,8 @@ void SmViewShell::Execute(SfxRequest& rReq)
bVal = !pp->GetConfig()->IsShowFormulaCursor();
pp->GetConfig()->SetShowFormulaCursor(bVal);
- GetGraphicWindow().ShowCursor(bVal);
+ //GetGraphicWindow().ShowCursor(bVal);
+ //TODO Consider disabling this option!!!
break;
}
case SID_DRAW:
@@ -1512,17 +1506,24 @@ void SmViewShell::Execute(SfxRequest& rReq)
const SfxInt16Item& rItem =
(const SfxInt16Item&)rReq.GetArgs()->Get(SID_INSERTCOMMAND);
- if (pWin)
+ if (pWin && bInsertIntoEditWindow)
pWin->InsertCommand(rItem.GetValue());
+ if (GetDoc() && !bInsertIntoEditWindow) {
+ GetDoc()->GetCursor().InsertCommand(rItem.GetValue());
+ GetGraphicWindow().GrabFocus();
+ }
break;
}
- case SID_INSERTTEXT:
+ case SID_INSERTSYMBOL:
{
const SfxStringItem& rItem =
- (const SfxStringItem&)rReq.GetArgs()->Get(SID_INSERTTEXT);
- if (pWin)
+ (const SfxStringItem&)rReq.GetArgs()->Get(SID_INSERTSYMBOL);
+
+ if (pWin && bInsertIntoEditWindow)
pWin->InsertText(rItem.GetValue());
+ if(GetDoc() && !bInsertIntoEditWindow)
+ GetDoc()->GetCursor().InsertSpecial(rItem.GetValue());
break;
}
diff --git a/starmath/source/visitors.cxx b/starmath/source/visitors.cxx
new file mode 100644
index 000000000000..f7418c08d9de
--- /dev/null
+++ b/starmath/source/visitors.cxx
@@ -0,0 +1,2502 @@
+#include "visitors.hxx"
+#include "cursor.hxx"
+
+///////////////////////////////////// SmVisitorTest /////////////////////////////////////
+
+void SmVisitorTest::Visit( SmTableNode* pNode )
+{
+ j_assert( pNode->GetType( ) == NTABLE, "the visitor-patterns isn't implemented correctly" );
+ VisitChildren( pNode );
+}
+
+void SmVisitorTest::Visit( SmBraceNode* pNode )
+{
+ j_assert( pNode->GetType( ) == NBRACE, "the visitor-patterns isn't implemented correctly" );
+ VisitChildren( pNode );
+}
+
+void SmVisitorTest::Visit( SmBracebodyNode* pNode )
+{
+ j_assert( pNode->GetType( ) == NBRACEBODY, "the visitor-patterns isn't implemented correctly" );
+ VisitChildren( pNode );
+}
+
+void SmVisitorTest::Visit( SmOperNode* pNode )
+{
+ j_assert( pNode->GetType( ) == NOPER, "the visitor-patterns isn't implemented correctly" );
+ VisitChildren( pNode );
+}
+
+void SmVisitorTest::Visit( SmAlignNode* pNode )
+{
+ j_assert( pNode->GetType( ) == NALIGN, "the visitor-patterns isn't implemented correctly" );
+ VisitChildren( pNode );
+}
+
+void SmVisitorTest::Visit( SmAttributNode* pNode )
+{
+ j_assert( pNode->GetType( ) == NATTRIBUT, "the visitor-patterns isn't implemented correctly" );
+ VisitChildren( pNode );
+}
+
+void SmVisitorTest::Visit( SmFontNode* pNode )
+{
+ j_assert( pNode->GetType( ) == NFONT, "the visitor-patterns isn't implemented correctly" );
+ VisitChildren( pNode );
+}
+
+void SmVisitorTest::Visit( SmUnHorNode* pNode )
+{
+ j_assert( pNode->GetType( ) == NUNHOR, "the visitor-patterns isn't implemented correctly" );
+ VisitChildren( pNode );
+}
+
+void SmVisitorTest::Visit( SmBinHorNode* pNode )
+{
+ j_assert( pNode->GetType( ) == NBINHOR, "the visitor-patterns isn't implemented correctly" );
+ VisitChildren( pNode );
+}
+
+void SmVisitorTest::Visit( SmBinVerNode* pNode )
+{
+ j_assert( pNode->GetType( ) == NBINVER, "the visitor-patterns isn't implemented correctly" );
+ VisitChildren( pNode );
+}
+
+void SmVisitorTest::Visit( SmBinDiagonalNode* pNode )
+{
+ j_assert( pNode->GetType( ) == NBINDIAGONAL, "the visitor-patterns isn't implemented correctly" );
+ VisitChildren( pNode );
+}
+
+void SmVisitorTest::Visit( SmSubSupNode* pNode )
+{
+ j_assert( pNode->GetType( ) == NSUBSUP, "the visitor-patterns isn't implemented correctly" );
+ VisitChildren( pNode );
+}
+
+void SmVisitorTest::Visit( SmMatrixNode* pNode )
+{
+ j_assert( pNode->GetType( ) == NMATRIX, "the visitor-patterns isn't implemented correctly" );
+ VisitChildren( pNode );
+}
+
+void SmVisitorTest::Visit( SmPlaceNode* pNode )
+{
+ j_assert( pNode->GetType( ) == NPLACE, "the visitor-patterns isn't implemented correctly" );
+ VisitChildren( pNode );
+}
+
+void SmVisitorTest::Visit( SmTextNode* pNode )
+{
+ j_assert( pNode->GetType( ) == NTEXT, "the visitor-patterns isn't implemented correctly" );
+ VisitChildren( pNode );
+}
+
+void SmVisitorTest::Visit( SmSpecialNode* pNode )
+{
+ j_assert( pNode->GetType( ) == NSPECIAL, "the visitor-patterns isn't implemented correctly" );
+ VisitChildren( pNode );
+}
+
+void SmVisitorTest::Visit( SmGlyphSpecialNode* pNode )
+{
+ j_assert( pNode->GetType( ) == NGLYPH_SPECIAL, "the visitor-patterns isn't implemented correctly" );
+ VisitChildren( pNode );
+}
+
+void SmVisitorTest::Visit( SmMathSymbolNode* pNode )
+{
+ j_assert( pNode->GetType( ) == NMATH, "the visitor-patterns isn't implemented correctly" );
+ VisitChildren( pNode );
+}
+
+void SmVisitorTest::Visit( SmBlankNode* pNode )
+{
+ j_assert( pNode->GetType( ) == NBLANK, "the visitor-patterns isn't implemented correctly" );
+ VisitChildren( pNode );
+}
+
+void SmVisitorTest::Visit( SmErrorNode* pNode )
+{
+ j_assert( pNode->GetType( ) == NERROR, "the visitor-patterns isn't implemented correctly" );
+ VisitChildren( pNode );
+}
+
+void SmVisitorTest::Visit( SmLineNode* pNode )
+{
+ j_assert( pNode->GetType( ) == NLINE, "the visitor-patterns isn't implemented correctly" );
+ VisitChildren( pNode );
+}
+
+void SmVisitorTest::Visit( SmExpressionNode* pNode )
+{
+ j_assert( pNode->GetType( ) == NEXPRESSION, "the visitor-patterns isn't implemented correctly" );
+ VisitChildren( pNode );
+}
+
+void SmVisitorTest::Visit( SmPolyLineNode* pNode )
+{
+ j_assert( pNode->GetType( ) == NPOLYLINE, "the visitor-patterns isn't implemented correctly" );
+ VisitChildren( pNode );
+}
+
+void SmVisitorTest::Visit( SmRootNode* pNode )
+{
+ j_assert( pNode->GetType( ) == NROOT, "the visitor-patterns isn't implemented correctly" );
+ VisitChildren( pNode );
+}
+
+void SmVisitorTest::Visit( SmRootSymbolNode* pNode )
+{
+ j_assert( pNode->GetType( ) == NROOTSYMBOL, "the visitor-patterns isn't implemented correctly" );
+ VisitChildren( pNode );
+}
+
+void SmVisitorTest::Visit( SmRectangleNode* pNode )
+{
+ j_assert( pNode->GetType( ) == NRECTANGLE, "the visitor-patterns isn't implemented correctly" );
+ VisitChildren( pNode );
+}
+
+void SmVisitorTest::Visit( SmVerticalBraceNode* pNode )
+{
+ j_assert( pNode->GetType( ) == NVERTICAL_BRACE, "the visitor-patterns isn't implemented correctly" );
+ VisitChildren( pNode );
+}
+
+void SmVisitorTest::VisitChildren( SmNode* pNode )
+{
+ SmNodeIterator it( pNode );
+ while( it.Next( ) )
+ it->Accept( this );
+}
+
+/////////////////////////////// SmDefaultingVisitor ////////////////////////////////
+
+void SmDefaultingVisitor::Visit( SmTableNode* pNode )
+{
+ DefaultVisit( pNode );
+}
+
+void SmDefaultingVisitor::Visit( SmBraceNode* pNode )
+{
+ DefaultVisit( pNode );
+}
+
+void SmDefaultingVisitor::Visit( SmBracebodyNode* pNode )
+{
+ DefaultVisit( pNode );
+}
+
+void SmDefaultingVisitor::Visit( SmOperNode* pNode )
+{
+ DefaultVisit( pNode );
+}
+
+void SmDefaultingVisitor::Visit( SmAlignNode* pNode )
+{
+ DefaultVisit( pNode );
+}
+
+void SmDefaultingVisitor::Visit( SmAttributNode* pNode )
+{
+ DefaultVisit( pNode );
+}
+
+void SmDefaultingVisitor::Visit( SmFontNode* pNode )
+{
+ DefaultVisit( pNode );
+}
+
+void SmDefaultingVisitor::Visit( SmUnHorNode* pNode )
+{
+ DefaultVisit( pNode );
+}
+
+void SmDefaultingVisitor::Visit( SmBinHorNode* pNode )
+{
+ DefaultVisit( pNode );
+}
+
+void SmDefaultingVisitor::Visit( SmBinVerNode* pNode )
+{
+ DefaultVisit( pNode );
+}
+
+void SmDefaultingVisitor::Visit( SmBinDiagonalNode* pNode )
+{
+ DefaultVisit( pNode );
+}
+
+void SmDefaultingVisitor::Visit( SmSubSupNode* pNode )
+{
+ DefaultVisit( pNode );
+}
+
+void SmDefaultingVisitor::Visit( SmMatrixNode* pNode )
+{
+ DefaultVisit( pNode );
+}
+
+void SmDefaultingVisitor::Visit( SmPlaceNode* pNode )
+{
+ DefaultVisit( pNode );
+}
+
+void SmDefaultingVisitor::Visit( SmTextNode* pNode )
+{
+ DefaultVisit( pNode );
+}
+
+void SmDefaultingVisitor::Visit( SmSpecialNode* pNode )
+{
+ DefaultVisit( pNode );
+}
+
+void SmDefaultingVisitor::Visit( SmGlyphSpecialNode* pNode )
+{
+ DefaultVisit( pNode );
+}
+
+void SmDefaultingVisitor::Visit( SmMathSymbolNode* pNode )
+{
+ DefaultVisit( pNode );
+}
+
+void SmDefaultingVisitor::Visit( SmBlankNode* pNode )
+{
+ DefaultVisit( pNode );
+}
+
+void SmDefaultingVisitor::Visit( SmErrorNode* pNode )
+{
+ DefaultVisit( pNode );
+}
+
+void SmDefaultingVisitor::Visit( SmLineNode* pNode )
+{
+ DefaultVisit( pNode );
+}
+
+void SmDefaultingVisitor::Visit( SmExpressionNode* pNode )
+{
+ DefaultVisit( pNode );
+}
+
+void SmDefaultingVisitor::Visit( SmPolyLineNode* pNode )
+{
+ DefaultVisit( pNode );
+}
+
+void SmDefaultingVisitor::Visit( SmRootNode* pNode )
+{
+ DefaultVisit( pNode );
+}
+
+void SmDefaultingVisitor::Visit( SmRootSymbolNode* pNode )
+{
+ DefaultVisit( pNode );
+}
+
+void SmDefaultingVisitor::Visit( SmRectangleNode* pNode )
+{
+ DefaultVisit( pNode );
+}
+
+void SmDefaultingVisitor::Visit( SmVerticalBraceNode* pNode )
+{
+ DefaultVisit( pNode );
+}
+
+
+/////////////////////////////// SmCaretDrawingVisitor ////////////////////////////////
+
+SmCaretDrawingVisitor::SmCaretDrawingVisitor( OutputDevice& rDevice,
+ SmCaretPos position,
+ Point offset )
+ : rDev( rDevice )
+{
+ pos = position;
+ Offset = offset;
+ j_assert( position.IsValid( ), "Cannot draw invalid position!" );
+ if( !position.IsValid( ) )
+ return;
+
+ //Save device state
+ rDev.Push( PUSH_FONT | PUSH_MAPMODE | PUSH_LINECOLOR | PUSH_FILLCOLOR | PUSH_TEXTCOLOR );
+
+ pos.pSelectedNode->Accept( this );
+ //Restore device state
+ rDev.Pop( );
+}
+
+void SmCaretDrawingVisitor::Visit( SmTextNode* pNode )
+{
+ long i = pos.Index;
+
+ rDev.SetFont( pNode->GetFont( ) );
+
+ //Find the line
+ SmNode* pLine = SmCursor::FindTopMostNodeInLine( pNode );
+
+ //Find coordinates
+ long left = pNode->GetLeft( ) + rDev.GetTextWidth( pNode->GetText( ), 0, i ) + Offset.X( );
+ long top = pLine->GetTop( ) + Offset.Y( );
+ long height = pLine->GetHeight( );
+
+ //Set color
+ rDev.SetLineColor( Color( COL_BLACK ) );
+
+ //Draw vertical line
+ Point p1( left, top );
+ Point p2( left, top + height );
+ rDev.DrawLine( p1, p2 );
+}
+
+void SmCaretDrawingVisitor::DefaultVisit( SmNode* pNode )
+{
+ rDev.SetLineColor( Color( COL_BLACK ) );
+
+ //Find the line
+ SmNode* pLine = SmCursor::FindTopMostNodeInLine( pNode );
+
+ //Find coordinates
+ long left = pNode->GetLeft( ) + Offset.X( ) + ( pos.Index == 1 ? pNode->GetWidth( ) : 0 );
+ long top = pLine->GetTop( ) + Offset.Y( );
+ long height = pLine->GetHeight( );
+
+ //Set color
+ rDev.SetLineColor( Color( COL_BLACK ) );
+
+ //Draw vertical line
+ Point p1( left, top );
+ Point p2( left, top + height );
+ rDev.DrawLine( p1, p2 );
+}
+
+/////////////////////////////// SmCaretPos2LineVisitor ////////////////////////////////
+
+void SmCaretPos2LineVisitor::Visit( SmTextNode* pNode )
+{
+ //Save device state
+ pDev->Push( PUSH_FONT | PUSH_TEXTCOLOR );
+
+ long i = pos.Index;
+
+ pDev->SetFont( pNode->GetFont( ) );
+
+ //Find coordinates
+ long left = pNode->GetLeft( ) + pDev->GetTextWidth( pNode->GetText( ), 0, i );
+ long top = pNode->GetTop( );
+ long height = pNode->GetHeight( );
+
+ line = SmCaretLine( left, top, height );
+
+ //Restore device state
+ pDev->Pop( );
+}
+
+void SmCaretPos2LineVisitor::DefaultVisit( SmNode* pNode )
+{
+ //Vertical line ( code from SmCaretDrawingVisitor )
+ Point p1 = pNode->GetTopLeft( );
+ if( pos.Index == 1 )
+ p1.Move( pNode->GetWidth( ), 0 );
+
+ line = SmCaretLine( p1.X( ), p1.Y( ), pNode->GetHeight( ) );
+}
+
+/////////////////////////////// Nasty temporary device!!! ////////////////////////////////
+
+
+#include <tools/gen.hxx>
+#include <tools/fract.hxx>
+#include <rtl/math.hxx>
+#include <tools/color.hxx>
+#include <vcl/metric.hxx>
+#include <vcl/lineinfo.hxx>
+#include <vcl/outdev.hxx>
+#include <sfx2/module.hxx>
+#include "symbol.hxx"
+#include "smmod.hxx"
+
+class SmTmpDevice2
+{
+ OutputDevice &rOutDev;
+
+ // disallow use of copy-constructor and assignment-operator
+ SmTmpDevice2( const SmTmpDevice2 &rTmpDev );
+ SmTmpDevice2 & operator = ( const SmTmpDevice2 &rTmpDev );
+
+ Color Impl_GetColor( const Color& rColor );
+
+public:
+ SmTmpDevice2( OutputDevice &rTheDev, BOOL bUseMap100th_mm );
+ ~SmTmpDevice2( ) { rOutDev.Pop( ); }
+
+ void SetFont( const Font &rNewFont );
+
+ void SetLineColor( const Color& rColor ) { rOutDev.SetLineColor( Impl_GetColor( rColor ) ); }
+ void SetFillColor( const Color& rColor ) { rOutDev.SetFillColor( Impl_GetColor( rColor ) ); }
+ void SetTextColor( const Color& rColor ) { rOutDev.SetTextColor( Impl_GetColor( rColor ) ); }
+
+ operator OutputDevice & ( ) { return rOutDev; }
+};
+
+
+SmTmpDevice2::SmTmpDevice2( OutputDevice &rTheDev, BOOL bUseMap100th_mm ) :
+ rOutDev( rTheDev )
+{
+ rOutDev.Push( PUSH_FONT | PUSH_MAPMODE |
+ PUSH_LINECOLOR | PUSH_FILLCOLOR | PUSH_TEXTCOLOR );
+ if ( bUseMap100th_mm && MAP_100TH_MM != rOutDev.GetMapMode( ).GetMapUnit( ) )
+ {
+ DBG_ERROR( "incorrect MapMode?" );
+ rOutDev.SetMapMode( MAP_100TH_MM ); //Immer fuer 100% fomatieren
+ }
+}
+
+
+Color SmTmpDevice2::Impl_GetColor( const Color& rColor )
+{
+ ColorData nNewCol = rColor.GetColor( );
+ if ( COL_AUTO == nNewCol )
+ {
+ if ( OUTDEV_PRINTER == rOutDev.GetOutDevType( ) )
+ nNewCol = COL_BLACK;
+ else
+ {
+ Color aBgCol( rOutDev.GetBackground( ).GetColor( ) );
+ if ( OUTDEV_WINDOW == rOutDev.GetOutDevType( ) )
+ aBgCol = ( ( Window & ) rOutDev ).GetDisplayBackground( ).GetColor( );
+
+ nNewCol = SM_MOD( )->GetColorConfig( ).GetColorValue( svtools::FONTCOLOR ).nColor;
+
+ Color aTmpColor( nNewCol );
+ if ( aBgCol.IsDark( ) && aTmpColor.IsDark( ) )
+ nNewCol = COL_WHITE;
+ else if ( aBgCol.IsBright( ) && aTmpColor.IsBright( ) )
+ nNewCol = COL_BLACK;
+ }
+ }
+ return Color( nNewCol );
+}
+
+
+void SmTmpDevice2::SetFont( const Font &rNewFont )
+{
+ rOutDev.SetFont( rNewFont );
+ rOutDev.SetTextColor( Impl_GetColor( rNewFont.GetColor( ) ) );
+}
+
+/////////////////////////////// SmDrawingVisitor ////////////////////////////////
+
+void SmDrawingVisitor::Visit( SmTableNode* pNode )
+{
+ DrawChildren( pNode );
+}
+
+void SmDrawingVisitor::Visit( SmBraceNode* pNode )
+{
+ DrawChildren( pNode );
+}
+
+void SmDrawingVisitor::Visit( SmBracebodyNode* pNode )
+{
+ DrawChildren( pNode );
+}
+
+void SmDrawingVisitor::Visit( SmOperNode* pNode )
+{
+ DrawChildren( pNode );
+}
+
+void SmDrawingVisitor::Visit( SmAlignNode* pNode )
+{
+ DrawChildren( pNode );
+}
+
+void SmDrawingVisitor::Visit( SmAttributNode* pNode )
+{
+ DrawChildren( pNode );
+}
+
+void SmDrawingVisitor::Visit( SmFontNode* pNode )
+{
+ DrawChildren( pNode );
+}
+
+void SmDrawingVisitor::Visit( SmUnHorNode* pNode )
+{
+ DrawChildren( pNode );
+}
+
+void SmDrawingVisitor::Visit( SmBinHorNode* pNode )
+{
+ DrawChildren( pNode );
+}
+
+void SmDrawingVisitor::Visit( SmBinVerNode* pNode )
+{
+ DrawChildren( pNode );
+}
+
+void SmDrawingVisitor::Visit( SmBinDiagonalNode* pNode )
+{
+ DrawChildren( pNode );
+}
+
+void SmDrawingVisitor::Visit( SmSubSupNode* pNode )
+{
+ DrawChildren( pNode );
+}
+
+void SmDrawingVisitor::Visit( SmMatrixNode* pNode )
+{
+ DrawChildren( pNode );
+}
+
+void SmDrawingVisitor::Visit( SmPlaceNode* pNode )
+{
+ DrawSpecialNode( pNode );
+}
+
+void SmDrawingVisitor::Visit( SmTextNode* pNode )
+{
+ DrawTextNode( pNode );
+}
+
+void SmDrawingVisitor::Visit( SmSpecialNode* pNode )
+{
+ DrawSpecialNode( pNode );
+}
+
+void SmDrawingVisitor::Visit( SmGlyphSpecialNode* pNode )
+{
+ DrawSpecialNode( pNode );
+}
+
+void SmDrawingVisitor::Visit( SmMathSymbolNode* pNode )
+{
+ DrawSpecialNode( pNode );
+}
+
+void SmDrawingVisitor::Visit( SmBlankNode* pNode )
+{
+ DrawChildren( pNode );
+}
+
+void SmDrawingVisitor::Visit( SmErrorNode* pNode )
+{
+ DrawSpecialNode( pNode );
+}
+
+void SmDrawingVisitor::Visit( SmLineNode* pNode )
+{
+ DrawChildren( pNode );
+}
+
+void SmDrawingVisitor::Visit( SmExpressionNode* pNode )
+{
+ DrawChildren( pNode );
+}
+
+void SmDrawingVisitor::Visit( SmRootNode* pNode )
+{
+ DrawChildren( pNode );
+}
+
+void SmDrawingVisitor::Visit( SmVerticalBraceNode* pNode )
+{
+ DrawChildren( pNode );
+}
+
+void SmDrawingVisitor::Visit( SmRootSymbolNode* pNode )
+{
+ if ( pNode->IsPhantom( ) )
+ return;
+
+ // draw root-sign itself
+ DrawSpecialNode( pNode );
+
+
+ SmTmpDevice2 aTmpDev( ( OutputDevice & ) rDev, TRUE );
+ aTmpDev.SetFillColor( pNode->GetFont( ).GetColor( ) );
+ rDev.SetLineColor( );
+ aTmpDev.SetFont( pNode->GetFont( ) );
+
+ // since the width is always unscaled it corresponds ot the _original_
+ // _unscaled_ font height to be used, we use that to calculate the
+ // bar height. Thus it is independent of the arguments height.
+ // ( see display of sqrt QQQ versus sqrt stack{Q#Q#Q#Q} )
+ long nBarHeight = pNode->GetWidth( ) * 7L / 100L;
+ long nBarWidth = pNode->GetBodyWidth( ) + pNode->GetBorderWidth( );
+ Point aBarOffset( pNode->GetWidth( ), +pNode->GetBorderWidth( ) );
+ Point aBarPos( Position + aBarOffset );
+
+ Rectangle aBar( aBarPos, Size( nBarWidth, nBarHeight ) );
+ //! avoid GROWING AND SHRINKING of drawn rectangle when constantly
+ //! increasing zoomfactor.
+ // This is done by shifting it's output-position to a point that
+ // corresponds exactly to a pixel on the output device.
+ Point aDrawPos( rDev.PixelToLogic( rDev.LogicToPixel( aBar.TopLeft( ) ) ) );
+ //aDrawPos.X( ) = aBar.Left( ); //! don't change X position
+ aBar.SetPos( aDrawPos );
+
+ rDev.DrawRect( aBar );
+
+#ifdef SM_RECT_DEBUG
+ if ( !pNode->IsDebug( ) )
+ return;
+
+ int nRFlags = SM_RECT_CORE | SM_RECT_ITALIC | SM_RECT_LINES | SM_RECT_MID;
+ pNode->SmRect::Draw( rDev, Position, nRFlags );
+#endif
+}
+
+void SmDrawingVisitor::Visit( SmPolyLineNode* pNode )
+{
+ if ( pNode->IsPhantom( ) )
+ return;
+
+ long nBorderwidth = pNode->GetFont( ).GetBorderWidth( );
+
+ LineInfo aInfo;
+ aInfo.SetWidth( pNode->GetWidth( ) - 2 * nBorderwidth );
+
+ Point aOffset ( Point( ) - pNode->GetPolygon( ).GetBoundRect( ).TopLeft( )
+ + Point( nBorderwidth, nBorderwidth ) ),
+ aPos ( Position + aOffset );
+ pNode->GetPolygon( ).Move( aPos.X( ), aPos.Y( ) ); //Works because Polygon wraps a pointer
+
+ SmTmpDevice2 aTmpDev ( ( OutputDevice & ) rDev, FALSE );
+ aTmpDev.SetLineColor( pNode->GetFont( ).GetColor( ) );
+
+ rDev.DrawPolyLine( pNode->GetPolygon( ), aInfo );
+
+#ifdef SM_RECT_DEBUG
+ if ( !pNode->IsDebug( ) )
+ return;
+
+ int nRFlags = SM_RECT_CORE | SM_RECT_ITALIC | SM_RECT_LINES | SM_RECT_MID;
+ pNode->SmRect::Draw( rDev, Position, nRFlags );
+#endif
+}
+
+void SmDrawingVisitor::Visit( SmRectangleNode* pNode )
+{
+ if ( pNode->IsPhantom( ) )
+ return;
+
+ SmTmpDevice2 aTmpDev ( ( OutputDevice & ) rDev, FALSE );
+ aTmpDev.SetFillColor( pNode->GetFont( ).GetColor( ) );
+ rDev.SetLineColor( );
+ aTmpDev.SetFont( pNode->GetFont( ) );
+
+ ULONG nTmpBorderWidth = pNode->GetFont( ).GetBorderWidth( );
+
+ // get rectangle and remove borderspace
+ Rectangle aTmp ( pNode->AsRectangle( ) + Position - pNode->GetTopLeft( ) );
+ aTmp.Left( ) += nTmpBorderWidth;
+ aTmp.Right( ) -= nTmpBorderWidth;
+ aTmp.Top( ) += nTmpBorderWidth;
+ aTmp.Bottom( ) -= nTmpBorderWidth;
+
+ DBG_ASSERT( aTmp.GetHeight( ) > 0 && aTmp.GetWidth( ) > 0,
+ "Sm: leeres Rechteck" );
+
+ //! avoid GROWING AND SHRINKING of drawn rectangle when constantly
+ //! increasing zoomfactor.
+ // This is done by shifting it's output-position to a point that
+ // corresponds exactly to a pixel on the output device.
+ Point aPos ( rDev.PixelToLogic( rDev.LogicToPixel( aTmp.TopLeft( ) ) ) );
+ aTmp.SetPos( aPos );
+
+ rDev.DrawRect( aTmp );
+
+#ifdef SM_RECT_DEBUG
+ if ( !pNode->IsDebug( ) )
+ return;
+
+ int nRFlags = SM_RECT_CORE | SM_RECT_ITALIC | SM_RECT_LINES | SM_RECT_MID;
+ pNode->SmRect::Draw( rDev, rPosition, nRFlags );
+#endif
+}
+
+void SmDrawingVisitor::DrawTextNode( SmTextNode* pNode )
+{
+ if ( pNode->IsPhantom( ) || pNode->GetText( ).Len( ) == 0 || pNode->GetText( ).GetChar( 0 ) == xub_Unicode( '\0' ) )
+ return;
+
+ SmTmpDevice2 aTmpDev ( ( OutputDevice & ) rDev, FALSE );
+ aTmpDev.SetFont( pNode->GetFont( ) );
+
+ Point aPos ( Position );
+ aPos.Y( ) += pNode->GetBaselineOffset( );
+ // auf Pixelkoordinaten runden
+ aPos = rDev.PixelToLogic( rDev.LogicToPixel( aPos ) );
+
+ rDev.DrawStretchText( aPos, pNode->GetWidth( ), pNode->GetText( ) );
+
+#ifdef SM_RECT_DEBUG
+ if ( !pNode->IsDebug( ) )
+ return;
+
+ int nRFlags = SM_RECT_CORE | SM_RECT_ITALIC | SM_RECT_LINES | SM_RECT_MID;
+ pNode->SmRect::Draw( rDev, Position, nRFlags );
+#endif
+}
+
+void SmDrawingVisitor::DrawSpecialNode( SmSpecialNode* pNode )
+{
+ //! since this chars might come from any font, that we may not have
+ //! set to ALIGN_BASELINE yet, we do it now.
+ pNode->GetFont( ).SetAlign( ALIGN_BASELINE );
+
+ DrawTextNode( pNode );
+}
+
+void SmDrawingVisitor::DrawChildren( SmNode* pNode )
+{
+ if ( pNode->IsPhantom( ) )
+ return;
+
+ Point rPosition = Position;
+
+ SmNodeIterator it( pNode );
+ while( it.Next( ) )
+ {
+ Point aOffset ( it->GetTopLeft( ) - pNode->GetTopLeft( ) );
+ Position = rPosition + aOffset;
+ it->Accept( this );
+ }
+
+#ifdef SM_RECT_DEBUG
+ if ( !pNode->IsDebug( ) )
+ return;
+
+ int nRFlags = SM_RECT_CORE | SM_RECT_ITALIC | SM_RECT_LINES | SM_RECT_MID;
+ pNode->SmRect::Draw( rDev, rPosition, nRFlags );
+#endif
+}
+
+/////////////////////////////// SmSetSelectionVisitor ////////////////////////////////
+
+void SmSetSelectionVisitor::SetSelectedOnAll( SmNode* pSubTree, bool IsSelected )
+{
+ pSubTree->SetSelected( IsSelected );
+
+ //Quick BFS to set all selections
+ SmNodeIterator it( pSubTree );
+ while( it.Next( ) )
+ SetSelectedOnAll( it.Current( ), IsSelected );
+}
+
+void SmSetSelectionVisitor::DefaultVisit( SmNode* pNode )
+{
+ //Change state if StartPos is infront of this node
+ if( StartPos.pSelectedNode == pNode && StartPos.Index == 0 )
+ IsSelecting = !IsSelecting;
+ //Change state if EndPos is infront of this node
+ if( EndPos.pSelectedNode == pNode && EndPos.Index == 0 )
+ IsSelecting = !IsSelecting;
+
+ //Cache current state
+ BOOL WasSelecting = IsSelecting;
+ BOOL ChangedState = FALSE;
+
+ //Set selected
+ pNode->SetSelected( IsSelecting );
+
+ //Visit children
+ SmNodeIterator it( pNode );
+ while( it.Next( ) )
+ {
+ it->Accept( this );
+ ChangedState = ( WasSelecting != IsSelecting ) || ChangedState;
+ }
+
+ //If state changed
+ if( ChangedState )
+ {
+ //Select this node and all of it's children
+ //(Make exception for SmBracebodyNode)
+ if( pNode->GetType() != NBRACEBODY ||
+ !pNode->GetParent() ||
+ pNode->GetParent()->GetType() != NBRACE )
+ SetSelectedOnAll( pNode, true );
+ else
+ SetSelectedOnAll( pNode->GetParent(), true );
+ /* If the equation is: sqrt{2 + 4} + 5
+ * And the selection is: sqrt{2 + [4} +] 5
+ * Where [ denotes StartPos and ] denotes EndPos
+ * Then the sqrt node should be selected, so that the
+ * effective selection is: [sqrt{2 + 4} +] 5
+ * The same is the case if we swap StartPos and EndPos.
+ */
+ }
+
+ //Change state if StartPos is after this node
+ if( StartPos.pSelectedNode == pNode && StartPos.Index == 1 )
+ {
+ IsSelecting = !IsSelecting;
+ }
+ //Change state if EndPos is after of this node
+ if( EndPos.pSelectedNode == pNode && EndPos.Index == 1 )
+ {
+ IsSelecting = !IsSelecting;
+ }
+}
+
+void SmSetSelectionVisitor::VisitCompositionNode( SmNode* pNode )
+{
+ //Change state if StartPos is infront of this node
+ if( StartPos.pSelectedNode == pNode && StartPos.Index == 0 )
+ IsSelecting = !IsSelecting;
+ //Change state if EndPos is infront of this node
+ if( EndPos.pSelectedNode == pNode && EndPos.Index == 0 )
+ IsSelecting = !IsSelecting;
+
+ //Cache current state
+ bool WasSelecting = IsSelecting;
+
+ //Visit children
+ SmNodeIterator it( pNode );
+ while( it.Next( ) )
+ it->Accept( this );
+
+ //Set selected, if everything was selected
+ pNode->SetSelected( WasSelecting && IsSelecting );
+
+ //Change state if StartPos is after this node
+ if( StartPos.pSelectedNode == pNode && StartPos.Index == 1 )
+ IsSelecting = !IsSelecting;
+ //Change state if EndPos is after of this node
+ if( EndPos.pSelectedNode == pNode && EndPos.Index == 1 )
+ IsSelecting = !IsSelecting;
+}
+
+void SmSetSelectionVisitor::Visit( SmTextNode* pNode )
+{
+ long i1 = -1,
+ i2 = -1;
+ if( StartPos.pSelectedNode == pNode )
+ i1 = StartPos.Index;
+ if( EndPos.pSelectedNode == pNode )
+ i2 = EndPos.Index;
+
+ long start, end;
+ pNode->SetSelected( true );
+ if( i1 != -1 && i2 != -1 ) {
+ start = i1 < i2 ? i1 : i2; //MIN
+ end = i1 > i2 ? i1 : i2; //MAX
+ } else if( IsSelecting && i1 != -1 ) {
+ start = 0;
+ end = i1;
+ IsSelecting = false;
+ } else if( IsSelecting && i2 != -1 ) {
+ start = 0;
+ end = i2;
+ IsSelecting = false;
+ } else if( !IsSelecting && i1 != -1 ) {
+ start = i1;
+ end = pNode->GetText( ).Len( );
+ IsSelecting = true;
+ } else if( !IsSelecting && i2 != -1 ) {
+ start = i2;
+ end = pNode->GetText( ).Len( );
+ IsSelecting = true;
+ } else if( IsSelecting ) {
+ start = 0;
+ end = pNode->GetText( ).Len( );
+ } else {
+ pNode->SetSelected( false );
+ start = 0;
+ end = 0;
+ }
+ pNode->SetSelected( start != end );
+ pNode->SetSelectionStart( start );
+ pNode->SetSelectionEnd( end );
+}
+
+void SmSetSelectionVisitor::Visit( SmExpressionNode* pNode )
+{
+ VisitCompositionNode( pNode );
+}
+
+void SmSetSelectionVisitor::Visit( SmAlignNode* pNode )
+{
+ VisitCompositionNode( pNode );
+}
+
+void SmSetSelectionVisitor::Visit( SmBinHorNode* pNode )
+{
+ VisitCompositionNode( pNode );
+}
+
+void SmSetSelectionVisitor::Visit( SmUnHorNode* pNode )
+{
+ VisitCompositionNode( pNode );
+}
+
+void SmSetSelectionVisitor::Visit( SmFontNode* pNode )
+{
+ VisitCompositionNode( pNode );
+}
+
+
+
+/////////////////////////////// SmCaretPosGraphBuildingVisitor ////////////////////////////////
+
+SmCaretPosGraphBuildingVisitor::SmCaretPosGraphBuildingVisitor( SmNode* pRootNode ){
+ pRightMost = NULL;
+ pGraph = new SmCaretPosGraph( );
+ //pRootNode should always be a table
+ j_assert( pRootNode->GetType( ) == NTABLE, "pRootNode must be a table node");
+ //Handle the special case where NTABLE is used a rootnode
+ if( pRootNode->GetType( ) == NTABLE ){
+ //Children are SmLineNodes
+ //Or so I thought... Aparently, the children can be instances of SmExpression
+ //especially if there's a error in the formula... So he we go, a simple work around.
+ SmNodeIterator it( pRootNode );
+ while( it.Next( ) ){
+ //There's a special invariant between this method and the Visit( SmLineNode* )
+ //Usually pRightMost may not be NULL, to avoid this pRightMost should here be
+ //set to a new SmCaretPos infront of it.Current( ), however, if it.Current( ) is
+ //an instance of SmLineNode we let SmLineNode create this position infront of
+ //the visual line.
+ //The argument for doing this is that we now don't have to worry about SmLineNode
+ //being a visual line composition node. Thus, no need for yet another special case
+ //in SmCursor::IsLineCompositionNode and everywhere this method is used.
+ if( it->GetType( ) != NLINE )
+ pRightMost = pGraph->Add( SmCaretPos( it.Current( ), 0 ) );
+ it->Accept( this );
+ }
+ }else
+ pRootNode->Accept(this);
+}
+
+void SmCaretPosGraphBuildingVisitor::Visit( SmLineNode* pNode ){
+ pRightMost = NULL;
+ SmNodeIterator it( pNode );
+ while( it.Next( ) ){
+ if( !pRightMost )
+ pRightMost = pGraph->Add( SmCaretPos( it.Current( ), 0 ) );
+ it->Accept( this );
+ }
+}
+
+/** Build SmCaretPosGraph for SmTableNode
+ * This method covers cases where SmTableNode is used in a binom or stack,
+ * the special case where it is used as root node for the entire formula is
+ * handled in the constructor.
+ */
+void SmCaretPosGraphBuildingVisitor::Visit( SmTableNode* pNode ){
+ SmCaretPosGraphEntry *left = pRightMost,
+ *right = pGraph->Add( SmCaretPos( pNode, 1) );
+ BOOL bIsFirst = TRUE;
+ SmNodeIterator it( pNode );
+ while( it.Next() ){
+ pRightMost = pGraph->Add( SmCaretPos( it.Current(), 0 ), left);
+ if(bIsFirst)
+ left->SetRight(pRightMost);
+ it->Accept( this );
+ pRightMost->SetRight(right);
+ if(bIsFirst)
+ right->SetLeft(pRightMost);
+ bIsFirst = FALSE;
+ }
+ pRightMost = right;
+}
+
+/** Build SmCaretPosGraph for SmSubSupNode
+ *
+ * The child positions in a SubSupNode, where H is the body:
+ * \code
+ * CSUP
+ *
+ * LSUP H H RSUP
+ * H H
+ * HHHH
+ * H H
+ * LSUB H H RSUB
+ *
+ * CSUB
+ * \endcode
+ *
+ * Graph over these, where "left" is before the SmSubSupNode and "right" is after:
+ * \dot
+ * digraph Graph{
+ * left -> H;
+ * H -> right;
+ * LSUP -> H;
+ * LSUB -> H;
+ * CSUP -> right;
+ * CSUB -> right;
+ * RSUP -> right;
+ * RSUB -> right;
+ * };
+ * \enddot
+ *
+ */
+void SmCaretPosGraphBuildingVisitor::Visit( SmSubSupNode* pNode )
+{
+ SmCaretPosGraphEntry *left,
+ *right,
+ *bodyLeft,
+ *bodyRight;
+
+ left = pRightMost;
+ j_assert( pRightMost, "pRightMost shouldn't be NULL here!" );
+
+ //Create bodyLeft
+ j_assert( pNode->GetBody( ), "SmSubSupNode Doesn't have a body!" );
+ bodyLeft = pGraph->Add( SmCaretPos( pNode->GetBody( ), 0 ), left );
+ left->SetRight( bodyLeft ); //TODO: Don't make this if LSUP or LSUB are NULL ( not sure??? )
+
+ //Create right
+ right = pGraph->Add( SmCaretPos( pNode, 1 ) );
+
+ //Visit the body, to get bodyRight
+ pRightMost = bodyLeft;
+ pNode->GetBody( )->Accept( this );
+ bodyRight = pRightMost;
+ bodyRight->SetRight( right );
+ right->SetLeft( bodyRight );
+
+ SmNode* pChild;
+ //If there's an LSUP
+ if( ( pChild = pNode->GetSubSup( LSUP ) ) ){
+ SmCaretPosGraphEntry *cLeft; //Child left
+ cLeft = pGraph->Add( SmCaretPos( pChild, 0 ), left );
+
+ pRightMost = cLeft;
+ pChild->Accept( this );
+
+ pRightMost->SetRight( bodyLeft );
+ }
+ //If there's an LSUB
+ if( ( pChild = pNode->GetSubSup( LSUB ) ) ){
+ SmCaretPosGraphEntry *cLeft; //Child left
+ cLeft = pGraph->Add( SmCaretPos( pChild, 0 ), left );
+
+ pRightMost = cLeft;
+ pChild->Accept( this );
+
+ pRightMost->SetRight( bodyLeft );
+ }
+ //If there's an CSUP
+ if( ( pChild = pNode->GetSubSup( CSUP ) ) ){
+ SmCaretPosGraphEntry *cLeft; //Child left
+ cLeft = pGraph->Add( SmCaretPos( pChild, 0 ), left );
+
+ pRightMost = cLeft;
+ pChild->Accept( this );
+
+ pRightMost->SetRight( right );
+ }
+ //If there's an CSUB
+ if( ( pChild = pNode->GetSubSup( CSUB ) ) ){
+ SmCaretPosGraphEntry *cLeft; //Child left
+ cLeft = pGraph->Add( SmCaretPos( pChild, 0 ), left );
+
+ pRightMost = cLeft;
+ pChild->Accept( this );
+
+ pRightMost->SetRight( right );
+ }
+ //If there's an RSUP
+ if( ( pChild = pNode->GetSubSup( RSUP ) ) ){
+ SmCaretPosGraphEntry *cLeft; //Child left
+ cLeft = pGraph->Add( SmCaretPos( pChild, 0 ), bodyRight );
+
+ pRightMost = cLeft;
+ pChild->Accept( this );
+
+ pRightMost->SetRight( right );
+ }
+ //If there's an RSUB
+ if( ( pChild = pNode->GetSubSup( RSUB ) ) ){
+ SmCaretPosGraphEntry *cLeft; //Child left
+ cLeft = pGraph->Add( SmCaretPos( pChild, 0 ), bodyRight );
+
+ pRightMost = cLeft;
+ pChild->Accept( this );
+
+ pRightMost->SetRight( right );
+ }
+
+ //Set return parameters
+ pRightMost = right;
+}
+
+/** Build caret position for SmOperNode
+ *
+ * If first child is an SmSubSupNode we will ignore it's
+ * body, as this body is a SmMathSymbol, for SUM, INT or similar
+ * that shouldn't be subject to modification.
+ * If first child is not a SmSubSupNode, ignore it completely
+ * as it is a SmMathSymbol.
+ *
+ * The child positions in a SmOperNode, where H is symbol, e.g. int, sum or similar:
+ * \code
+ * TO
+ *
+ * LSUP H H RSUP BBB BB BBB B B
+ * H H B B B B B B B B
+ * HHHH BBB B B B B B
+ * H H B B B B B B B
+ * LSUB H H RSUB BBB BB BBB B
+ *
+ * FROM
+ * \endcode
+ * Notice, CSUP, etc. are actually granchildren, but inorder to ignore H, these are visited
+ * from here. If they are present, that is if pOper is an instance of SmSubSupNode.
+ *
+ * Graph over these, where "left" is before the SmOperNode and "right" is after:
+ * \dot
+ * digraph Graph{
+ * left -> BODY;
+ * BODY -> right;
+ * LSUP -> BODY;
+ * LSUB -> BODY;
+ * TO -> BODY;
+ * FROM -> BODY;
+ * RSUP -> BODY;
+ * RSUB -> BODY;
+ * };
+ * \enddot
+ */
+void SmCaretPosGraphBuildingVisitor::Visit( SmOperNode* pNode )
+{
+ SmNode *pOper = pNode->GetSubNode( 0 ),
+ *pBody = pNode->GetSubNode( 1 );
+
+ SmCaretPosGraphEntry *left = pRightMost,
+ *bodyLeft,
+ *bodyRight,
+ *right;
+ //Create body left
+ bodyLeft = pGraph->Add( SmCaretPos( pBody, 0 ), left );
+ left->SetRight( bodyLeft );
+
+ //Visit body, get bodyRight
+ pRightMost = bodyLeft;
+ pBody->Accept( this );
+ bodyRight = pRightMost;
+
+ //Create right
+ right = pGraph->Add( SmCaretPos( pNode, 1 ), bodyRight );
+ bodyRight->SetRight( right );
+
+ //Get subsup pNode if any
+ SmSubSupNode* pSubSup = pOper->GetType( ) == NSUBSUP ? ( SmSubSupNode* )pOper : NULL;
+
+ SmNode* pChild;
+ SmCaretPosGraphEntry *childLeft;
+ if( pSubSup && ( pChild = pSubSup->GetSubSup( LSUP ) ) ) {
+ //Create position infront of pChild
+ childLeft = pGraph->Add( SmCaretPos( pChild, 0 ), left );
+ //Visit pChild
+ pRightMost = childLeft;
+ pChild->Accept( this );
+ //Set right on pRightMost from pChild
+ pRightMost->SetRight( bodyLeft );
+ }
+ if( pSubSup && ( pChild = pSubSup->GetSubSup( LSUB ) ) ) {
+ //Create position infront of pChild
+ childLeft = pGraph->Add( SmCaretPos( pChild, 0 ), left );
+ //Visit pChild
+ pRightMost = childLeft;
+ pChild->Accept( this );
+ //Set right on pRightMost from pChild
+ pRightMost->SetRight( bodyLeft );
+ }
+ if( pSubSup && ( pChild = pSubSup->GetSubSup( CSUP ) ) ) {//TO
+ //Create position infront of pChild
+ childLeft = pGraph->Add( SmCaretPos( pChild, 0 ), left );
+ //Visit pChild
+ pRightMost = childLeft;
+ pChild->Accept( this );
+ //Set right on pRightMost from pChild
+ pRightMost->SetRight( bodyLeft );
+ }
+ if( pSubSup && ( pChild = pSubSup->GetSubSup( CSUB ) ) ) { //FROM
+ //Create position infront of pChild
+ childLeft = pGraph->Add( SmCaretPos( pChild, 0 ), left );
+ //Visit pChild
+ pRightMost = childLeft;
+ pChild->Accept( this );
+ //Set right on pRightMost from pChild
+ pRightMost->SetRight( bodyLeft );
+ }
+ if( pSubSup && ( pChild = pSubSup->GetSubSup( RSUP ) ) ) {
+ //Create position infront of pChild
+ childLeft = pGraph->Add( SmCaretPos( pChild, 0 ), left );
+ //Visit pChild
+ pRightMost = childLeft;
+ pChild->Accept( this );
+ //Set right on pRightMost from pChild
+ pRightMost->SetRight( bodyLeft );
+ }
+ if( pSubSup && ( pChild = pSubSup->GetSubSup( RSUB ) ) ) {
+ //Create position infront of pChild
+ childLeft = pGraph->Add( SmCaretPos( pChild, 0 ), left );
+ //Visit pChild
+ pRightMost = childLeft;
+ pChild->Accept( this );
+ //Set right on pRightMost from pChild
+ pRightMost->SetRight( bodyLeft );
+ }
+
+ //Return right
+ pRightMost = right;
+}
+
+void SmCaretPosGraphBuildingVisitor::Visit( SmMatrixNode* pNode )
+{
+ SmCaretPosGraphEntry *left = pRightMost,
+ *right = pGraph->Add( SmCaretPos( pNode, 1 ) );
+
+ for ( USHORT i = 0; i < pNode->GetNumRows( ); i++ ) {
+ SmCaretPosGraphEntry* r = left;
+ for ( USHORT j = 0; j < pNode->GetNumCols( ); j++ ){
+ SmNode* pSubNode = pNode->GetSubNode( i * pNode->GetNumCols( ) + j );
+
+ pRightMost = pGraph->Add( SmCaretPos( pSubNode, 0 ), r );
+ if( j != 0 || ( pNode->GetNumRows( ) - 1 ) / 2 == i )
+ r->SetRight( pRightMost );
+
+ pSubNode->Accept( this );
+
+ r = pRightMost;
+ }
+ pRightMost->SetRight( right );
+ if( ( pNode->GetNumRows( ) - 1 ) / 2 == i )
+ right->SetLeft( pRightMost );
+ }
+
+ pRightMost = right;
+}
+
+/** Build SmCaretPosGraph for SmTextNode
+ *
+ * Lines in an SmTextNode:
+ * \code
+ * A B C
+ * \endcode
+ * Where A B and C are characters in the text.
+ *
+ * Graph over these, where "left" is before the SmTextNode and "right" is after:
+ * \dot
+ * digraph Graph{
+ * left -> A;
+ * A -> B
+ * B -> right;
+ * };
+ * \enddot
+ * Notice that C and right is the same position here.
+ */
+void SmCaretPosGraphBuildingVisitor::Visit( SmTextNode* pNode )
+{
+ j_assert( pNode->GetText( ).Len( ) > 0, "Empty SmTextNode is bad" );
+
+ int size = pNode->GetText( ).Len( );
+ for( int i = 1; i <= size; i++ ){
+ SmCaretPosGraphEntry* pRight = pRightMost;
+ pRightMost = pGraph->Add( SmCaretPos( pNode, i ), pRight );
+ pRight->SetRight( pRightMost );
+ }
+}
+
+/** Build SmCaretPosGraph for SmBinVerNode
+ *
+ * Lines in an SmBinVerNode:
+ * \code
+ * A
+ * -----
+ * B
+ * \endcode
+ *
+ * Graph over these, where "left" is before the SmBinVerNode and "right" is after:
+ * \dot
+ * digraph Graph{
+ * left -> A;
+ * A -> right;
+ * B -> right;
+ * };
+ * \enddot
+ */
+void SmCaretPosGraphBuildingVisitor::Visit( SmBinVerNode* pNode )
+{
+ //None if these children can be NULL, see SmBinVerNode::Arrange
+ SmNode *pNum = pNode->GetSubNode( 0 ),
+ *pDenom = pNode->GetSubNode( 2 );
+
+ SmCaretPosGraphEntry *left,
+ *right,
+ *numLeft,
+ *denomLeft;
+
+ //Set left
+ left = pRightMost;
+ j_assert( pRightMost, "There must be a position infront of this" );
+
+ //Create right
+ right = pGraph->Add( SmCaretPos( pNode, 1 ) );
+
+ //Create numLeft
+ numLeft = pGraph->Add( SmCaretPos( pNum, 0 ), left );
+ left->SetRight( numLeft );
+
+ //Visit pNum
+ pRightMost = numLeft;
+ pNum->Accept( this );
+ pRightMost->SetRight( right );
+ right->SetLeft( pRightMost );
+
+ //Create denomLeft
+ denomLeft = pGraph->Add( SmCaretPos( pDenom, 0 ), left );
+
+ //Visit pDenom
+ pRightMost = denomLeft;
+ pDenom->Accept( this );
+ pRightMost->SetRight( right );
+
+ //Set return parameter
+ pRightMost = right;
+}
+
+/** Build SmCaretPosGraph for SmVerticalBraceNode
+ *
+ * Lines in an SmVerticalBraceNode:
+ * \code
+ * pScript
+ * ________
+ * / \
+ * pBody
+ * \endcode
+ *
+ */
+void SmCaretPosGraphBuildingVisitor::Visit( SmVerticalBraceNode* pNode )
+{
+ SmNode *pBody = pNode->GetSubNode( 0 ),
+ *pScript = pNode->GetSubNode( 2 );
+ //None of these children can be NULL
+
+ SmCaretPosGraphEntry *left,
+ *bodyLeft,
+ *scriptLeft,
+ *right;
+
+ left = pRightMost;
+
+ //Create right
+ right = pGraph->Add( SmCaretPos( pNode, 1 ) );
+
+ //Create bodyLeft
+ bodyLeft = pGraph->Add( SmCaretPos( pBody, 0 ), left );
+ left->SetRight( bodyLeft );
+ pRightMost = bodyLeft;
+ pBody->Accept( this );
+ pRightMost->SetRight( right );
+ right->SetLeft( pRightMost );
+
+ //Create script
+ scriptLeft = pGraph->Add( SmCaretPos( pScript, 0 ), left );
+ pRightMost = scriptLeft;
+ pScript->Accept( this );
+ pRightMost->SetRight( right );
+
+ //Set return value
+ pRightMost = right;
+}
+
+/** Build SmCaretPosGraph for SmBinDiagonalNode
+ *
+ * Lines in an SmBinDiagonalNode:
+ * \code
+ * A /
+ * /
+ * / B
+ * \endcode
+ * Where A and B are lines.
+ *
+ * Used in formulas such as "A wideslash B"
+ */
+void SmCaretPosGraphBuildingVisitor::Visit( SmBinDiagonalNode* pNode )
+{
+ SmNode *A = pNode->GetSubNode( 0 ),
+ *B = pNode->GetSubNode( 1 );
+
+ SmCaretPosGraphEntry *left,
+ *leftA,
+ *rightA,
+ *leftB,
+ *right;
+ left = pRightMost;
+
+ //Create right
+ right = pGraph->Add( SmCaretPos( pNode, 1 ) );
+
+ //Create left A
+ leftA = pGraph->Add( SmCaretPos( A, 0 ), left );
+ left->SetRight( leftA );
+
+ //Visit A
+ pRightMost = leftA;
+ A->Accept( this );
+ rightA = pRightMost;
+
+ //Create left B
+ leftB = pGraph->Add( SmCaretPos( B, 0 ), rightA );
+ rightA->SetRight( leftB );
+
+ //Visit B
+ pRightMost = leftB;
+ B->Accept( this );
+ pRightMost->SetRight( right );
+ right->SetLeft( pRightMost );
+
+ //Set return value
+ pRightMost = right;
+}
+
+
+//Straigt forward ( I think )
+void SmCaretPosGraphBuildingVisitor::Visit( SmBinHorNode* pNode )
+{
+ SmNodeIterator it( pNode );
+ while( it.Next( ) )
+ it->Accept( this );
+}
+void SmCaretPosGraphBuildingVisitor::Visit( SmUnHorNode* pNode )
+{
+ // Unary operator node
+ SmNodeIterator it( pNode );
+ while( it.Next( ) )
+ it->Accept( this );
+
+}
+
+void SmCaretPosGraphBuildingVisitor::Visit( SmExpressionNode* pNode )
+{
+ SmNodeIterator it( pNode );
+ while( it.Next( ) )
+ it->Accept( this );
+}
+
+void SmCaretPosGraphBuildingVisitor::Visit( SmFontNode* pNode )
+{
+ //Has only got one child, should act as an expression if possible
+ SmNodeIterator it( pNode );
+ while( it.Next( ) )
+ it->Accept( this );
+}
+
+/** Build SmCaretPosGraph for SmBracebodyNode
+ * Acts as an SmExpressionNode
+ *
+ * Below is an example of a formula tree that has multiple children for SmBracebodyNode
+ * \dot
+ * digraph {
+ * labelloc = "t";
+ * label= "Equation: \"lbrace i mline i in setZ rbrace\"";
+ * n0 [label="SmTableNode"];
+ * n0 -> n1 [label="0"];
+ * n1 [label="SmLineNode"];
+ * n1 -> n2 [label="0"];
+ * n2 [label="SmExpressionNode"];
+ * n2 -> n3 [label="0"];
+ * n3 [label="SmBraceNode"];
+ * n3 -> n4 [label="0"];
+ * n4 [label="SmMathSymbolNode: {"];
+ * n3 -> n5 [label="1"];
+ * n5 [label="SmBracebodyNode"];
+ * n5 -> n6 [label="0"];
+ * n6 [label="SmExpressionNode"];
+ * n6 -> n7 [label="0"];
+ * n7 [label="SmTextNode: i"];
+ * n5 -> n8 [label="1"];
+ * n8 [label="SmMathSymbolNode: ∣"];
+ * n5 -> n9 [label="2"];
+ * n9 [label="SmExpressionNode"];
+ * n9 -> n10 [label="0"];
+ * n10 [label="SmBinHorNode"];
+ * n10 -> n11 [label="0"];
+ * n11 [label="SmTextNode: i"];
+ * n10 -> n12 [label="1"];
+ * n12 [label="SmMathSymbolNode: ∈"];
+ * n10 -> n13 [label="2"];
+ * n13 [label="SmMathSymbolNode: ℤ"];
+ * n3 -> n14 [label="2"];
+ * n14 [label="SmMathSymbolNode: }"];
+ * }
+ * \enddot
+ */
+void SmCaretPosGraphBuildingVisitor::Visit( SmBracebodyNode* pNode )
+{
+ SmNodeIterator it( pNode );
+ while( it.Next( ) ) {
+ SmCaretPosGraphEntry* pStart = pGraph->Add( SmCaretPos( it.Current(), 0), pRightMost );
+ pRightMost->SetRight( pStart );
+ pRightMost = pStart;
+ it->Accept( this );
+ }
+}
+
+/** Build SmCaretPosGraph for SmAlignNode
+ * Acts as an SmExpressionNode, as it only has one child this okay
+ */
+void SmCaretPosGraphBuildingVisitor::Visit( SmAlignNode* pNode )
+{
+ SmNodeIterator it( pNode );
+ while( it.Next( ) )
+ it->Accept( this );
+}
+
+/** Build SmCaretPosGraph for SmRootNode
+ *
+ * Lines in an SmRootNode:
+ * \code
+ * _________
+ * A/
+ * \/ B
+ *
+ * \endcode
+ * A: pExtra ( optional, can be NULL ),
+ * B: pBody
+ *
+ * Graph over these, where "left" is before the SmRootNode and "right" is after:
+ * \dot
+ * digraph Graph{
+ * left -> B;
+ * B -> right;
+ * A -> B;
+ * }
+ * \enddot
+ */
+void SmCaretPosGraphBuildingVisitor::Visit( SmRootNode* pNode )
+{
+ SmNode *pExtra = pNode->GetSubNode( 0 ), //Argument, NULL for sqrt, and SmTextNode if cubicroot
+ *pBody = pNode->GetSubNode( 2 ); //Body of the root
+ j_assert( pBody, "pBody cannot be NULL" );
+
+ SmCaretPosGraphEntry *left,
+ *right,
+ *bodyLeft,
+ *bodyRight;
+
+ //Get left and save it
+ j_assert( pRightMost, "There must be a position infront of this" );
+ left = pRightMost;
+
+ //Create body left
+ bodyLeft = pGraph->Add( SmCaretPos( pBody, 0 ), left );
+ left->SetRight( bodyLeft );
+
+ //Create right
+ right = pGraph->Add( SmCaretPos( pNode, 1 ) );
+
+ //Visit body
+ pRightMost = bodyLeft;
+ pBody->Accept( this );
+ bodyRight = pRightMost;
+ bodyRight->SetRight( right );
+ right->SetLeft( bodyRight );
+
+ //Visit pExtra
+ if( pExtra ){
+ pRightMost = pGraph->Add( SmCaretPos( pExtra, 0 ), left );
+ pExtra->Accept( this );
+ pRightMost->SetRight( bodyLeft );
+ }
+
+ pRightMost = right;
+}
+
+/** Build SmCaretPosGraph for SmPlaceNode
+ * Consider this a single character.
+ */
+void SmCaretPosGraphBuildingVisitor::Visit( SmPlaceNode* pNode )
+{
+ SmCaretPosGraphEntry* right = pGraph->Add( SmCaretPos( pNode, 1 ), pRightMost );
+ pRightMost->SetRight( right );
+ pRightMost = right;
+}
+
+/** SmErrorNode is context dependent metadata, it can't be selected
+ *
+ * @remarks There's no point in deleting, copying and/or moving an instance
+ * of SmErrorNode as it may not exist in an other context! Thus there are no
+ * positions to select an SmErrorNode.
+ */
+void SmCaretPosGraphBuildingVisitor::Visit( SmErrorNode* )
+{
+}
+
+/** Build SmCaretPosGraph for SmBlankNode
+ * Consider this a single character, as it is only a blank space
+ */
+void SmCaretPosGraphBuildingVisitor::Visit( SmBlankNode* pNode )
+{
+ SmCaretPosGraphEntry* right = pGraph->Add( SmCaretPos( pNode, 1 ), pRightMost );
+ pRightMost->SetRight( right );
+ pRightMost = right;
+}
+
+/** Build SmCaretPosGraph for SmBraceNode
+ *
+ * Lines in an SmBraceNode:
+ * \code
+ * | |
+ * | B |
+ * | |
+ * \endcode
+ * B: Body
+ *
+ * Graph over these, where "left" is before the SmBraceNode and "right" is after:
+ * \dot
+ * digraph Graph{
+ * left -> B;
+ * B -> right;
+ * }
+ * \enddot
+ */
+void SmCaretPosGraphBuildingVisitor::Visit( SmBraceNode* pNode )
+{
+ SmNode* pBody = pNode->GetSubNode( 1 );
+
+ SmCaretPosGraphEntry *left = pRightMost,
+ *right = pGraph->Add( SmCaretPos( pNode, 1 ) );
+
+ if( pBody->GetType() != NBRACEBODY ) {
+ pRightMost = pGraph->Add( SmCaretPos( pBody, 0 ), left );
+ left->SetRight( pRightMost );
+ }else
+ pRightMost = left;
+
+ pBody->Accept( this );
+ pRightMost->SetRight( right );
+ right->SetLeft( pRightMost );
+
+ pRightMost = right;
+}
+
+/** Build SmCaretPosGraph for SmAttributNode
+ *
+ * Lines in an SmAttributNode:
+ * \code
+ * Attr
+ * Body
+ * \endcode
+ *
+ * There's a body and an attribute, the construction is used for "widehat A", where "A" is the body
+ * and "^" is the attribute ( note GetScaleMode( ) on SmAttributNode tells how the attribute should be
+ * scaled ).
+ */
+void SmCaretPosGraphBuildingVisitor::Visit( SmAttributNode* pNode )
+{
+ SmNode *pAttr = pNode->GetSubNode( 0 ),
+ *pBody = pNode->GetSubNode( 1 );
+ //None of the children can be NULL
+
+ SmCaretPosGraphEntry *left = pRightMost,
+ *attrLeft,
+ *bodyLeft,
+ *bodyRight,
+ *right;
+
+ //Creating bodyleft
+ bodyLeft = pGraph->Add( SmCaretPos( pBody, 0 ), left );
+ left->SetRight( bodyLeft );
+
+ //Creating right
+ right = pGraph->Add( SmCaretPos( pNode, 1 ) );
+
+ //Visit the body
+ pRightMost = bodyLeft;
+ pBody->Accept( this );
+ bodyRight = pRightMost;
+ bodyRight->SetRight( right );
+ right->SetLeft( bodyRight );
+
+ //Create attrLeft
+ attrLeft = pGraph->Add( SmCaretPos( pAttr, 0 ), left );
+
+ //Visit attribute
+ pRightMost = attrLeft;
+ pAttr->Accept( this );
+ pRightMost->SetRight( right );
+
+ //Set return value
+ pRightMost = right;
+}
+
+//Consider these single symboles
+void SmCaretPosGraphBuildingVisitor::Visit( SmSpecialNode* pNode )
+{
+ SmCaretPosGraphEntry* right = pGraph->Add( SmCaretPos( pNode, 1 ), pRightMost );
+ pRightMost->SetRight( right );
+ pRightMost = right;
+}
+void SmCaretPosGraphBuildingVisitor::Visit( SmGlyphSpecialNode* pNode )
+{
+ SmCaretPosGraphEntry* right = pGraph->Add( SmCaretPos( pNode, 1 ), pRightMost );
+ pRightMost->SetRight( right );
+ pRightMost = right;
+}
+void SmCaretPosGraphBuildingVisitor::Visit( SmMathSymbolNode* pNode )
+{
+ SmCaretPosGraphEntry* right = pGraph->Add( SmCaretPos( pNode, 1 ), pRightMost );
+ pRightMost->SetRight( right );
+ pRightMost = right;
+}
+
+void SmCaretPosGraphBuildingVisitor::Visit( SmRootSymbolNode* )
+{
+ //Do nothing
+}
+void SmCaretPosGraphBuildingVisitor::Visit( SmRectangleNode* )
+{
+ //Do nothing
+}
+void SmCaretPosGraphBuildingVisitor::Visit( SmPolyLineNode* )
+{
+ //Do nothing
+}
+
+/////////////////////////////// SmCloningVisitor ///////////////////////////////
+
+SmNode* SmCloningVisitor::Clone( SmNode* pNode )
+{
+ SmNode* pCurrResult = pResult;
+ pNode->Accept( this );
+ SmNode* pClone = pResult;
+ pResult = pCurrResult;
+ return pClone;
+}
+
+void SmCloningVisitor::CloneNodeAttr( SmNode* pSource, SmNode* pTarget )
+{
+ pTarget->SetScaleMode( pSource->GetScaleMode( ) );
+ //Other attributes are set when prepare or arrange is executed
+ //and may depend on stuff not being cloned here.
+}
+
+void SmCloningVisitor::CloneKids( SmStructureNode* pSource, SmStructureNode* pTarget )
+{
+ //Cache current result
+ SmNode* pCurrResult = pResult;
+
+ //Create array for holding clones
+ USHORT nSize = pSource->GetNumSubNodes( );
+ SmNodeArray aNodes( nSize );
+
+ //Clone children
+ SmNode* pKid;
+ for( USHORT i = 0; i < nSize; i++ ){
+ if( NULL != ( pKid = pSource->GetSubNode( i ) ) )
+ pKid->Accept( this );
+ else
+ pResult = NULL;
+ aNodes[i] = pResult;
+ }
+
+ //Set subnodes of pTarget
+ pTarget->SetSubNodes( aNodes );
+
+ //Restore result as where prior to call
+ pResult = pCurrResult;
+}
+
+void SmCloningVisitor::Visit( SmTableNode* pNode )
+{
+ SmTableNode* pClone = new SmTableNode( pNode->GetToken( ) );
+ CloneNodeAttr( pNode, pClone );
+ CloneKids( pNode, pClone );
+ pResult = pClone;
+}
+
+void SmCloningVisitor::Visit( SmBraceNode* pNode )
+{
+ SmBraceNode* pClone = new SmBraceNode( pNode->GetToken( ) );
+ CloneNodeAttr( pNode, pClone );
+ CloneKids( pNode, pClone );
+ pResult = pClone;
+}
+
+
+void SmCloningVisitor::Visit( SmBracebodyNode* pNode )
+{
+ SmBracebodyNode* pClone = new SmBracebodyNode( pNode->GetToken( ) );
+ CloneNodeAttr( pNode, pClone );
+ CloneKids( pNode, pClone );
+ pResult = pClone;
+}
+
+void SmCloningVisitor::Visit( SmOperNode* pNode )
+{
+ SmOperNode* pClone = new SmOperNode( pNode->GetToken( ) );
+ CloneNodeAttr( pNode, pClone );
+ CloneKids( pNode, pClone );
+ pResult = pClone;
+}
+
+void SmCloningVisitor::Visit( SmAlignNode* pNode )
+{
+ SmAlignNode* pClone = new SmAlignNode( pNode->GetToken( ) );
+ CloneNodeAttr( pNode, pClone );
+ CloneKids( pNode, pClone );
+ pResult = pClone;
+}
+
+void SmCloningVisitor::Visit( SmAttributNode* pNode )
+{
+ SmAttributNode* pClone = new SmAttributNode( pNode->GetToken( ) );
+ CloneNodeAttr( pNode, pClone );
+ CloneKids( pNode, pClone );
+ pResult = pClone;
+}
+
+void SmCloningVisitor::Visit( SmFontNode* pNode )
+{
+ SmFontNode* pClone = new SmFontNode( pNode->GetToken( ) );
+ pClone->SetSizeParameter( pNode->GetSizeParameter( ), pNode->GetSizeType( ) );
+ CloneNodeAttr( pNode, pClone );
+ CloneKids( pNode, pClone );
+ pResult = pClone;
+}
+
+void SmCloningVisitor::Visit( SmUnHorNode* pNode )
+{
+ SmUnHorNode* pClone = new SmUnHorNode( pNode->GetToken( ) );
+ CloneNodeAttr( pNode, pClone );
+ CloneKids( pNode, pClone );
+ pResult = pClone;
+}
+
+void SmCloningVisitor::Visit( SmBinHorNode* pNode )
+{
+ SmBinHorNode* pClone = new SmBinHorNode( pNode->GetToken( ) );
+ CloneNodeAttr( pNode, pClone );
+ CloneKids( pNode, pClone );
+ pResult = pClone;
+}
+
+void SmCloningVisitor::Visit( SmBinVerNode* pNode )
+{
+ SmBinVerNode* pClone = new SmBinVerNode( pNode->GetToken( ) );
+ CloneNodeAttr( pNode, pClone );
+ CloneKids( pNode, pClone );
+ pResult = pClone;
+}
+
+void SmCloningVisitor::Visit( SmBinDiagonalNode* pNode )
+{
+ SmBinDiagonalNode *pClone = new SmBinDiagonalNode( pNode->GetToken( ) );
+ pClone->SetAscending( pNode->IsAscending( ) );
+ CloneNodeAttr( pNode, pClone );
+ CloneKids( pNode, pClone );
+ pResult = pClone;
+}
+
+void SmCloningVisitor::Visit( SmSubSupNode* pNode )
+{
+ SmSubSupNode *pClone = new SmSubSupNode( pNode->GetToken( ) );
+ pClone->SetUseLimits( pNode->IsUseLimits( ) );
+ CloneNodeAttr( pNode, pClone );
+ CloneKids( pNode, pClone );
+ pResult = pClone;
+}
+
+void SmCloningVisitor::Visit( SmMatrixNode* pNode )
+{
+ SmMatrixNode *pClone = new SmMatrixNode( pNode->GetToken( ) );
+ pClone->SetRowCol( pNode->GetNumRows( ), pNode->GetNumCols( ) );
+ CloneNodeAttr( pNode, pClone );
+ CloneKids( pNode, pClone );
+ pResult = pClone;
+}
+
+void SmCloningVisitor::Visit( SmPlaceNode* pNode )
+{
+ pResult = new SmPlaceNode( pNode->GetToken( ) );
+ CloneNodeAttr( pNode, pResult );
+}
+
+void SmCloningVisitor::Visit( SmTextNode* pNode )
+{
+ SmTextNode* pClone = new SmTextNode( pNode->GetToken( ), pNode->GetFontDesc( ) );
+ pClone->ChangeText( pNode->GetText( ) );
+ CloneNodeAttr( pNode, pClone );
+ pResult = pClone;
+}
+
+void SmCloningVisitor::Visit( SmSpecialNode* pNode )
+{
+ pResult = new SmSpecialNode( pNode->GetToken( ) );
+ CloneNodeAttr( pNode, pResult );
+}
+
+void SmCloningVisitor::Visit( SmGlyphSpecialNode* pNode )
+{
+ pResult = new SmGlyphSpecialNode( pNode->GetToken( ) );
+ CloneNodeAttr( pNode, pResult );
+}
+
+void SmCloningVisitor::Visit( SmMathSymbolNode* pNode )
+{
+ pResult = new SmMathSymbolNode( pNode->GetToken( ) );
+ CloneNodeAttr( pNode, pResult );
+}
+
+void SmCloningVisitor::Visit( SmBlankNode* pNode )
+{
+ SmBlankNode* pClone = new SmBlankNode( pNode->GetToken( ) );
+ pClone->SetBlankNum( pNode->GetBlankNum( ) );
+ pResult = pClone;
+ CloneNodeAttr( pNode, pResult );
+}
+
+void SmCloningVisitor::Visit( SmErrorNode* pNode )
+{
+ //PE_NONE is used the information have been discarded and isn't used
+ pResult = new SmErrorNode( PE_NONE, pNode->GetToken( ) );
+ CloneNodeAttr( pNode, pResult );
+}
+
+void SmCloningVisitor::Visit( SmLineNode* pNode )
+{
+ SmLineNode* pClone = new SmLineNode( pNode->GetToken( ) );
+ CloneNodeAttr( pNode, pClone );
+ CloneKids( pNode, pClone );
+ pResult = pClone;
+}
+
+void SmCloningVisitor::Visit( SmExpressionNode* pNode )
+{
+ SmExpressionNode* pClone = new SmExpressionNode( pNode->GetToken( ) );
+ CloneNodeAttr( pNode, pClone );
+ CloneKids( pNode, pClone );
+ pResult = pClone;
+}
+
+void SmCloningVisitor::Visit( SmPolyLineNode* pNode )
+{
+ pResult = new SmPolyLineNode( pNode->GetToken( ) );
+ CloneNodeAttr( pNode, pResult );
+}
+
+void SmCloningVisitor::Visit( SmRootNode* pNode )
+{
+ SmRootNode* pClone = new SmRootNode( pNode->GetToken( ) );
+ CloneNodeAttr( pNode, pClone );
+ CloneKids( pNode, pClone );
+ pResult = pClone;
+}
+
+void SmCloningVisitor::Visit( SmRootSymbolNode* pNode )
+{
+ pResult = new SmRootSymbolNode( pNode->GetToken( ) );
+ CloneNodeAttr( pNode, pResult );
+}
+
+void SmCloningVisitor::Visit( SmRectangleNode* pNode )
+{
+ pResult = new SmRectangleNode( pNode->GetToken( ) );
+ CloneNodeAttr( pNode, pResult );
+}
+
+void SmCloningVisitor::Visit( SmVerticalBraceNode* pNode )
+{
+ SmVerticalBraceNode* pClone = new SmVerticalBraceNode( pNode->GetToken( ) );
+ CloneNodeAttr( pNode, pClone );
+ CloneKids( pNode, pClone );
+ pResult = pClone;
+}
+
+/////////////////////////////// SmSelectionDrawingVisitor ///////////////////////////////
+
+SmSelectionDrawingVisitor::SmSelectionDrawingVisitor( OutputDevice& rDevice, SmNode* pTree, Point Offset )
+ : rDev( rDevice ) {
+ bHasSelectionArea = FALSE;
+
+ //Visit everything
+ j_assert( pTree, "pTree can't be null!" );
+ if( pTree )
+ pTree->Accept( this );
+
+ //Draw selection if there's any
+ if( bHasSelectionArea ){
+ aSelectionArea.Move( Offset.X( ), Offset.Y( ) );
+
+ //Save device state
+ rDev.Push( PUSH_LINECOLOR | PUSH_FILLCOLOR );
+ //Change colors
+ rDev.SetLineColor( );
+ rDev.SetFillColor( Color( COL_LIGHTGRAY ) );
+
+ //Draw rectangle
+ rDev.DrawRect( aSelectionArea );
+
+ //Restore device state
+ rDev.Pop( );
+ }
+}
+
+void SmSelectionDrawingVisitor::ExtendSelectionArea( Rectangle aArea )
+{
+ if ( ! bHasSelectionArea ) {
+ aSelectionArea = aArea;
+ bHasSelectionArea = true;
+ } else
+ aSelectionArea.Union( aArea );
+}
+
+void SmSelectionDrawingVisitor::DefaultVisit( SmNode* pNode )
+{
+ if( pNode->IsSelected( ) )
+ ExtendSelectionArea( pNode->AsRectangle( ) );
+ VisitChildren( pNode );
+}
+
+void SmSelectionDrawingVisitor::VisitChildren( SmNode* pNode )
+{
+ SmNodeIterator it( pNode );
+ while( it.Next( ) )
+ it->Accept( this );
+}
+
+void SmSelectionDrawingVisitor::Visit( SmTextNode* pNode )
+{
+ if( pNode->IsSelected( ) ){
+ rDev.Push( PUSH_TEXTCOLOR | PUSH_FONT );
+
+ rDev.SetFont( pNode->GetFont( ) );
+ Point Position = pNode->GetTopLeft( );
+ long left = Position.getX( ) + rDev.GetTextWidth( pNode->GetText( ), 0, pNode->GetSelectionStart( ) );
+ long right = Position.getX( ) + rDev.GetTextWidth( pNode->GetText( ), 0, pNode->GetSelectionEnd( ) );
+ long top = Position.getY( );
+ long bottom = top + pNode->GetHeight( );
+ Rectangle rect( left, top, right, bottom );
+
+ ExtendSelectionArea( rect );
+
+ rDev.Pop( );
+ }
+}
+
+
+/////////////////////////////// SmNodeToTextVisitor ///////////////////////////////
+
+void SmNodeToTextVisitor::Visit( SmTableNode* pNode )
+{
+ if( pNode->GetToken( ).eType == TBINOM ) {
+ Append( "binom" );
+ LineToText( pNode->GetSubNode( 0 ) );
+ LineToText( pNode->GetSubNode( 1 ) );
+ } else if( pNode->GetToken( ).eType == TSTACK ) {
+ Append( "stack{ " );
+ SmNodeIterator it( pNode );
+ it.Next( );
+ while( true ) {
+ LineToText( it.Current( ) );
+ if( it.Next( ) ) {
+ Separate( );
+ Append( "## " );
+ }else
+ break;
+ }
+ Separate( );
+ Append( "}" );
+ } else { //Assume it's a toplevel table, containing lines
+ SmNodeIterator it( pNode );
+ it.Next( );
+ while( true ) {
+ Separate( );
+ it->Accept( this );
+ if( it.Next( ) ) {
+ Separate( );
+ Append( "newline" );
+ }else
+ break;
+ }
+ }
+}
+
+void SmNodeToTextVisitor::Visit( SmBraceNode* pNode )
+{
+ SmNode *pLeftBrace = pNode->GetSubNode( 0 ),
+ *pBody = pNode->GetSubNode( 1 ),
+ *pRightBrace = pNode->GetSubNode( 2 );
+ //Handle special case where it's absolute function
+ if( pNode->GetToken( ).eType == TABS ) {
+ Append( "abs" );
+ LineToText( pBody );
+ } else {
+ if( pNode->GetScaleMode( ) == SCALE_HEIGHT )
+ Append( "left " );
+ pLeftBrace->Accept( this );
+ Separate( );
+ pBody->Accept( this );
+ Separate( );
+ if( pNode->GetScaleMode( ) == SCALE_HEIGHT )
+ Append( "right " );
+ pRightBrace->Accept( this );
+ }
+}
+
+void SmNodeToTextVisitor::Visit( SmBracebodyNode* pNode )
+{
+ SmNodeIterator it( pNode );
+ while( it.Next( ) ){
+ Separate( );
+ it->Accept( this );
+ }
+}
+
+void SmNodeToTextVisitor::Visit( SmOperNode* pNode )
+{
+ Append( pNode->GetToken( ).aText );
+ Separate( );
+ if( pNode->GetToken( ).eType == TOPER ){
+ //There's an SmGlyphSpecialNode if eType == TOPER
+ if( pNode->GetSubNode( 0 )->GetType( ) == NSUBSUP )
+ Append( pNode->GetSubNode( 0 )->GetSubNode( 0 )->GetToken( ).aText );
+ else
+ Append( pNode->GetSubNode( 0 )->GetToken( ).aText );
+ }
+ if( pNode->GetSubNode( 0 )->GetType( ) == NSUBSUP ) {
+ SmSubSupNode *pSubSup = ( SmSubSupNode* )pNode->GetSubNode( 0 );
+ SmNode* pChild;
+ if( ( pChild = pSubSup->GetSubSup( LSUP ) ) ) {
+ Separate( );
+ Append( "lsup " );
+ LineToText( pChild );
+ }
+ if( ( pChild = pSubSup->GetSubSup( LSUB ) ) ) {
+ Separate( );
+ Append( "lsub " );
+ LineToText( pChild );
+ }
+ if( ( pChild = pSubSup->GetSubSup( RSUP ) ) ) {
+ Separate( );
+ Append( "rsup " );
+ LineToText( pChild );
+ }
+ if( ( pChild = pSubSup->GetSubSup( RSUB ) ) ) {
+ Separate( );
+ Append( "rsub " );
+ LineToText( pChild );
+ }
+ if( ( pChild = pSubSup->GetSubSup( CSUP ) ) ) {
+ Separate( );
+ Append( "csup " );
+ LineToText( pChild );
+ }
+ if( ( pChild = pSubSup->GetSubSup( CSUB ) ) ) {
+ Separate( );
+ Append( "csub " );
+ LineToText( pChild );
+ }
+ }
+ LineToText( pNode->GetSubNode( 1 ) );
+}
+
+void SmNodeToTextVisitor::Visit( SmAlignNode* pNode )
+{
+ Append( pNode->GetToken( ).aText );
+ LineToText( pNode->GetSubNode( 0 ) );
+}
+
+void SmNodeToTextVisitor::Visit( SmAttributNode* pNode )
+{
+ Append( pNode->GetToken( ).aText );
+ LineToText( pNode->GetSubNode( 1 ) );
+}
+
+void SmNodeToTextVisitor::Visit( SmFontNode* pNode )
+{
+ switch ( pNode->GetToken( ).eType )
+ {
+ case TBOLD:
+ Append( "bold " );
+ break;
+ case TNBOLD:
+ Append( "nbold " );
+ break;
+ case TITALIC:
+ Append( "italic " );
+ break;
+ case TNITALIC:
+ Append( "nitalic " );
+ break;
+ case TPHANTOM:
+ Append( "phantom " );
+ break;
+ case TSIZE:
+ {
+ Append( "size " );
+ switch ( pNode->GetSizeType( ) )
+ {
+ case FNTSIZ_PLUS:
+ Append( "+" );
+ break;
+ case FNTSIZ_MINUS:
+ Append( "-" );
+ break;
+ case FNTSIZ_MULTIPLY:
+ Append( "*" );
+ break;
+ case FNTSIZ_DIVIDE:
+ Append( "/" );
+ break;
+ case FNTSIZ_ABSOLUT:
+ default:
+ break;
+ }
+ Append( String( ::rtl::math::doubleToUString(
+ static_cast<double>( pNode->GetSizeParameter( ) ),
+ rtl_math_StringFormat_Automatic,
+ rtl_math_DecimalPlaces_Max, '.', sal_True ) ) );
+ Append( " " );
+ }
+ break;
+ case TBLACK:
+ Append( "color black " );
+ break;
+ case TWHITE:
+ Append( "color white " );
+ break;
+ case TRED:
+ Append( "color red " );
+ break;
+ case TGREEN:
+ Append( "color green " );
+ break;
+ case TBLUE:
+ Append( "color blue " );
+ break;
+ case TCYAN:
+ Append( "color cyan " );
+ break;
+ case TMAGENTA:
+ Append( "color magenta " );
+ break;
+ case TYELLOW:
+ Append( "color yellow " );
+ break;
+ case TSANS:
+ Append( "font sans " );
+ break;
+ case TSERIF:
+ Append( "font serif " );
+ break;
+ case TFIXED:
+ Append( "font fixed " );
+ break;
+ default:
+ break;
+ }
+ LineToText( pNode->GetSubNode( 1 ) );
+}
+
+void SmNodeToTextVisitor::Visit( SmUnHorNode* pNode )
+{
+ Append( "{" );
+ SmNodeIterator it( pNode, pNode->GetSubNode( 1 )->GetToken( ).eType == TFACT );
+ while( it.Next( ) ) {
+ Separate( );
+ it->Accept( this );
+ }
+ Append( " }" );
+}
+
+void SmNodeToTextVisitor::Visit( SmBinHorNode* pNode )
+{
+ Append( "{" );
+ SmNode *pLeft = pNode->GetSubNode( 0 ),
+ *pOper = pNode->GetSubNode( 1 ),
+ *pRight = pNode->GetSubNode( 2 );
+ Separate( );
+ pLeft->Accept( this );
+ Separate( );
+ pOper->Accept( this );
+ Separate( );
+ pRight->Accept( this );
+ Separate( );
+ Append( "}" );
+}
+
+void SmNodeToTextVisitor::Visit( SmBinVerNode* pNode )
+{
+ SmNode *pNum = pNode->GetSubNode( 0 ),
+ *pDenom = pNode->GetSubNode( 2 );
+ LineToText( pNum );
+ Append( "over" );
+ LineToText( pDenom );
+}
+
+void SmNodeToTextVisitor::Visit( SmBinDiagonalNode* pNode )
+{
+ SmNode *pLeftOperand = pNode->GetSubNode( 0 ),
+ *pRightOperand = pNode->GetSubNode( 1 );
+ LineToText( pLeftOperand );
+ Separate( );
+ Append( "wideslash " );
+ LineToText( pRightOperand );
+}
+
+void SmNodeToTextVisitor::Visit( SmSubSupNode* pNode )
+{
+ LineToText( pNode->GetBody( ) );
+ SmNode *pChild;
+ if( ( pChild = pNode->GetSubSup( LSUP ) ) ) {
+ Separate( );
+ Append( "lsup " );
+ LineToText( pChild );
+ }
+ if( ( pChild = pNode->GetSubSup( LSUB ) ) ) {
+ Separate( );
+ Append( "lsub " );
+ LineToText( pChild );
+ }
+ if( ( pChild = pNode->GetSubSup( RSUP ) ) ) {
+ Separate( );
+ Append( "rsup " );
+ LineToText( pChild );
+ }
+ if( ( pChild = pNode->GetSubSup( RSUB ) ) ) {
+ Separate( );
+ Append( "rsub " );
+ LineToText( pChild );
+ }
+ if( ( pChild = pNode->GetSubSup( CSUP ) ) ) {
+ Separate( );
+ Append( "csup " );
+ LineToText( pChild );
+ }
+ if( ( pChild = pNode->GetSubSup( CSUB ) ) ) {
+ Separate( );
+ Append( "csub " );
+ LineToText( pChild );
+ }
+}
+
+void SmNodeToTextVisitor::Visit( SmMatrixNode* pNode )
+{
+ Append( "matrix{" );
+ for ( USHORT i = 0; i < pNode->GetNumRows( ); i++ ) {
+ for ( USHORT j = 0; j < pNode->GetNumCols( ); j++ ) {
+ SmNode* pSubNode = pNode->GetSubNode( i * pNode->GetNumCols( ) + j );
+ Separate( );
+ pSubNode->Accept( this );
+ Separate( );
+ if( j != pNode->GetNumCols( ) - 1 )
+ Append( "#" );
+ }
+ Separate( );
+ if( i != pNode->GetNumRows( ) - 1 )
+ Append( "##" );
+ }
+ Append( "}" );
+}
+
+void SmNodeToTextVisitor::Visit( SmPlaceNode* )
+{
+ Append( "<?>" );
+}
+
+void SmNodeToTextVisitor::Visit( SmTextNode* pNode )
+{
+ //TODO: This method might need improvements, see SmTextNode::CreateTextFromNode
+ if( pNode->GetToken( ).eType == TTEXT )
+ Append( "\"" );
+ Append( pNode->GetText( ) );
+ if( pNode->GetToken( ).eType == TTEXT )
+ Append( "\"" );
+}
+
+void SmNodeToTextVisitor::Visit( SmSpecialNode* pNode )
+{
+ Append( "%" );
+ Append( pNode->GetToken( ).aText );
+}
+
+void SmNodeToTextVisitor::Visit( SmGlyphSpecialNode* pNode )
+{
+ if( pNode->GetToken( ).eType == TBOPER )
+ Append( "boper " );
+ else
+ Append( "uoper " );
+ Append( pNode->GetToken( ).aText );
+}
+
+void SmNodeToTextVisitor::Visit( SmMathSymbolNode* pNode )
+{
+ Append( pNode->GetToken( ).aText );
+}
+
+void SmNodeToTextVisitor::Visit( SmBlankNode* pNode )
+{
+ Append( pNode->GetToken( ).aText );
+}
+
+void SmNodeToTextVisitor::Visit( SmErrorNode* )
+{
+}
+
+void SmNodeToTextVisitor::Visit( SmLineNode* pNode )
+{
+ SmNodeIterator it( pNode );
+ while( it.Next( ) ){
+ Separate( );
+ it->Accept( this );
+ }
+}
+
+void SmNodeToTextVisitor::Visit( SmExpressionNode* pNode )
+{
+ Append( "{ " );
+ SmNodeIterator it( pNode );
+ while( it.Next( ) ) {
+ it->Accept( this );
+ Separate( );
+ }
+ Append( "}" );
+}
+
+void SmNodeToTextVisitor::Visit( SmPolyLineNode* )
+{
+}
+
+void SmNodeToTextVisitor::Visit( SmRootNode* pNode )
+{
+ SmNode *pExtra = pNode->GetSubNode( 0 ),
+ *pBody = pNode->GetSubNode( 2 );
+ if( pExtra ) {
+ Append( "nroot" );
+ LineToText( pExtra );
+ } else
+ Append( "sqrt" );
+ LineToText( pBody );
+}
+
+void SmNodeToTextVisitor::Visit( SmRootSymbolNode* )
+{
+}
+
+void SmNodeToTextVisitor::Visit( SmRectangleNode* )
+{
+}
+
+void SmNodeToTextVisitor::Visit( SmVerticalBraceNode* pNode )
+{
+ SmNode *pBody = pNode->GetSubNode( 0 ),
+ *pScript = pNode->GetSubNode( 2 );
+ LineToText( pBody );
+ Append( pNode->GetToken( ).aText );
+ LineToText( pScript );
+}