diff options
author | Michael Meeks <michael.meeks@novell.com> | 2010-10-23 16:20:23 +0100 |
---|---|---|
committer | Michael Meeks <michael.meeks@novell.com> | 2010-10-23 16:20:23 +0100 |
commit | 46a1843255067dfcda58d1a00913a5d26627cd04 (patch) | |
tree | 28ca6e4d88b790cd9c7fa0afad2a453a507cf6f9 | |
parent | 88b663b7317ef1c6239c9e5ca52b1a9ec14f1993 (diff) | |
parent | befebfbf3690989e0eb08dab34b0e8505850760d (diff) |
Merge branch 'formula' into intformulae
Conflicts:
starmath/inc/node.hxx
starmath/source/edit.cxx
starmath/source/node.cxx
starmath/source/view.cxx
-rw-r--r-- | readme.md | 72 | ||||
-rw-r--r-- | starmath/inc/caret.hxx | 445 | ||||
-rw-r--r-- | starmath/inc/cursor.hxx | 424 | ||||
-rw-r--r-- | starmath/inc/document.hxx | 19 | ||||
-rw-r--r-- | starmath/inc/edit.hxx | 4 | ||||
-rw-r--r-- | starmath/inc/node.hxx | 506 | ||||
-rw-r--r-- | starmath/inc/parse.hxx | 19 | ||||
-rw-r--r-- | starmath/inc/starmath.hrc | 4 | ||||
-rw-r--r-- | starmath/inc/view.hxx | 25 | ||||
-rw-r--r-- | starmath/inc/visitors.hxx | 469 | ||||
-rw-r--r-- | starmath/sdi/smath.sdi | 2 | ||||
-rw-r--r-- | starmath/sdi/smslots.sdi | 14 | ||||
-rw-r--r-- | starmath/source/caret.cxx | 33 | ||||
-rw-r--r-- | starmath/source/cursor.cxx | 1591 | ||||
-rw-r--r-- | starmath/source/dialog.cxx | 4 | ||||
-rw-r--r-- | starmath/source/document.cxx | 40 | ||||
-rw-r--r-- | starmath/source/edit.cxx | 55 | ||||
-rw-r--r-- | starmath/source/makefile.mk | 3 | ||||
-rw-r--r-- | starmath/source/node.cxx | 420 | ||||
-rw-r--r-- | starmath/source/parse.cxx | 70 | ||||
-rw-r--r-- | starmath/source/view.cxx | 271 | ||||
-rw-r--r-- | starmath/source/visitors.cxx | 2502 |
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 ); +} |