diff options
author | Jonas Finnemann Jensen <jopsen@gmail.com> | 2010-10-01 22:04:50 +0200 |
---|---|---|
committer | Jonas Finnemann Jensen <jopsen@gmail.com> | 2010-10-01 23:08:54 +0200 |
commit | 11ddbed837eab61d2ff1dcbbf4c978c037a58ac9 (patch) | |
tree | ae7813c9c36ca4dbe2f7515c06b81d06b41d24d6 /starmath | |
parent | e7afab12ef8e9975709fb5a10e75aabaa65b9f01 (diff) |
Integrated the visual formula editor patch
Ported the most recent version of the visual formula editor patch,
to LibreOffice. This patch is not finished yet, see README for more
information.
Diffstat (limited to 'starmath')
-rw-r--r-- | starmath/inc/caret.hxx | 445 | ||||
-rw-r--r-- | starmath/inc/cursor.hxx | 424 | ||||
-rw-r--r-- | starmath/inc/document.hxx | 21 | ||||
-rw-r--r-- | starmath/inc/edit.hxx | 4 | ||||
-rw-r--r-- | starmath/inc/node.hxx | 514 | ||||
-rw-r--r-- | starmath/inc/parse.hxx | 23 | ||||
-rw-r--r-- | starmath/inc/starmath.hrc | 4 | ||||
-rw-r--r-- | starmath/inc/view.hxx | 31 | ||||
-rw-r--r-- | starmath/inc/visitors.hxx | 470 | ||||
-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 | 1593 | ||||
-rw-r--r-- | starmath/source/dialog.cxx | 4 | ||||
-rw-r--r-- | starmath/source/document.cxx | 46 | ||||
-rw-r--r-- | starmath/source/edit.cxx | 54 | ||||
-rw-r--r-- | starmath/source/makefile.mk | 3 | ||||
-rwxr-xr-x | starmath/source/node.cxx | 426 | ||||
-rwxr-xr-x | starmath/source/parse.cxx | 86 | ||||
-rwxr-xr-x | starmath/source/view.cxx | 273 | ||||
-rw-r--r-- | starmath/source/visitors.cxx | 2473 |
21 files changed, 6472 insertions, 471 deletions
diff --git a/starmath/inc/caret.hxx b/starmath/inc/caret.hxx new file mode 100644 index 0000000000..ae0f0fc1a0 --- /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 0000000000..d78aad7649 --- /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 fb2aa9d994..2bcb97a39f 100644 --- a/starmath/inc/document.hxx +++ b/starmath/inc/document.hxx @@ -46,6 +46,7 @@ class SmNode; class SfxMenuBarManager; class SfxPrinter; class Printer; +class SmCursor; #define HINT_DATACHANGED 1004 @@ -106,6 +107,7 @@ class SmDocShell : public SfxObjectShell, public SfxListener { friend class SmPrinterAccess; friend class SmModel; + friend class SmCursor; String aText; SmFormat aFormat; @@ -123,6 +125,7 @@ class SmDocShell : public SfxObjectShell, public SfxListener nBottomBorder; USHORT nModifyCount; BOOL bIsFormulaArranged; + SmCursor *pCursor; @@ -158,10 +161,15 @@ class SmDocShell : public SfxObjectShell, public SfxListener OutputDevice* GetRefDev(); BOOL IsFormulaArranged() const { return bIsFormulaArranged; } - void SetFormulaArranged(BOOL bVal) { bIsFormulaArranged = bVal; } + void SetFormulaArranged(BOOL bVal) { bIsFormulaArranged = bVal; } 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) @@ -205,7 +213,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(); @@ -219,6 +227,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; } }; diff --git a/starmath/inc/edit.hxx b/starmath/inc/edit.hxx index 084501e153..b8a7a90561 100644 --- a/starmath/inc/edit.hxx +++ b/starmath/inc/edit.hxx @@ -68,15 +68,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 156a3c1f24..d5af727eca 100644 --- a/starmath/inc/node.hxx +++ b/starmath/inc/node.hxx @@ -1,7 +1,7 @@ /************************************************************************* * * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. - * + * * Copyright 2000, 2010 Oracle and/or its affiliates. * * OpenOffice.org - a multi-platform office productivity suite @@ -31,6 +31,26 @@ #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" @@ -41,6 +61,7 @@ #define ATTR_BOLD 0x0001 #define ATTR_ITALIC 0x0002 + #define FNTSIZ_ABSOLUT 1 #define FNTSIZ_PLUS 2 #define FNTSIZ_MINUS 3 @@ -59,6 +80,7 @@ extern SmFormat *pActiveFormat; +class SmVisitor; class SmDocShell; class SmNode; class SmStructureNode; @@ -77,7 +99,7 @@ enum SmNodeType NATTRIBUT, NFONT, NUNHOR, NBINHOR, NBINVER, NBINDIAGONAL, NSUBSUP, NMATRIX, NPLACE, NTEXT, NSPECIAL, NGLYPH_SPECIAL, NMATH, NBLANK, NERROR, - NLINE, NEXPRESSION, NPOLYLINE, NROOT, NROOTSYMBOL, + NLINE, NEXPRESSION, NPOLYLINE, NROOT, NROOTSYMBOL, NRECTANGLE, NVERTICAL_BRACE }; @@ -97,6 +119,8 @@ class SmNode : public SmRect nAttributes; BOOL bIsPhantom, bIsDebug; + + BOOL bIsSelected; protected: SmNode(SmNodeType eNodeType, const SmToken &rNodeToken); @@ -160,7 +184,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; } @@ -180,12 +203,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; @@ -212,12 +351,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: @@ -252,7 +408,10 @@ public: //////////////////////////////////////////////////////////////////////////////// - +/** Draws a rectangle + * + * Used for drawing the line in the OVER and OVERSTRIKE commands. + */ class SmRectangleNode : public SmGraphicNode { Size aToSize; @@ -270,15 +429,19 @@ 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; @@ -289,6 +452,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); @@ -298,17 +463,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 ); @@ -319,6 +496,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); @@ -327,15 +526,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; @@ -352,13 +558,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: @@ -367,12 +582,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: @@ -393,12 +612,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 @@ -408,19 +633,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: @@ -428,15 +660,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: @@ -448,12 +686,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: @@ -465,12 +710,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; @@ -494,12 +744,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: @@ -509,12 +765,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: @@ -525,12 +785,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: @@ -547,12 +818,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: @@ -563,12 +845,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: @@ -583,12 +877,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; @@ -603,35 +907,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; @@ -644,7 +966,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(); @@ -653,17 +977,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: @@ -675,12 +1021,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; @@ -690,6 +1045,7 @@ public: virtual void Arrange(const OutputDevice &rDev, const SmFormat &rFormat); long GetBodyHeight() const { return nBodyHeight; } + void Accept(SmVisitor* pVisitor); }; @@ -702,13 +1058,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); }; @@ -722,6 +1090,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: @@ -740,12 +1120,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: @@ -754,12 +1136,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: @@ -769,12 +1161,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; @@ -795,12 +1191,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, @@ -822,12 +1223,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; @@ -841,9 +1246,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 314f2353d3..8716c37229 100644 --- a/starmath/inc/parse.hxx +++ b/starmath/inc/parse.hxx @@ -1,7 +1,7 @@ /************************************************************************* * * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. - * + * * Copyright 2000, 2010 Oracle and/or its affiliates. * * OpenOffice.org - a multi-platform office productivity suite @@ -117,7 +117,7 @@ struct SmToken String aText; // token info SmTokenType eType; - sal_Unicode cMathChar; + sal_Unicode cMathChar; // parse-help info ULONG nGroup; USHORT nLevel; @@ -126,6 +126,11 @@ struct SmToken xub_StrLen nCol; SmToken(); + SmToken(SmTokenType eTokenType, + sal_Unicode cMath, + const sal_Char* pText, + ULONG nTokenGroup = 0, + USHORT nTokenLevel = 0); }; @@ -167,6 +172,14 @@ enum SmConvert CONVERT_60_TO_50 }; +struct SmTokenTableEntry +{ + const sal_Char* pIdent; + SmTokenType eType; + sal_Unicode cMathChar; + ULONG nGroup; + USHORT nLevel; +}; class SmParser { @@ -230,7 +243,7 @@ protected: void Special(); void GlyphSpecial(); // end of grammar - + LanguageType GetLanguage() const { return nLang; } void SetLanguage( LanguageType nNewLang ) { nLang = nNewLang; } @@ -239,7 +252,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; }; @@ -256,6 +272,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 7d11b64aa0..19fa5ce9c9 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 76f64c60c8..bcadd34101 100644 --- a/starmath/inc/view.hxx +++ b/starmath/inc/view.hxx @@ -1,7 +1,7 @@ /************************************************************************* * * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. - * + * * Copyright 2000, 2010 Oracle and/or its affiliates. * * OpenOffice.org - a multi-platform office productivity suite @@ -51,7 +51,6 @@ class SmPrintUIOptions; class SmGraphicWindow : public ScrollableWindow { Point aFormulaDrawPos; - Rectangle aCursorRect; ::com::sun::star::uno::Reference< ::com::sun::star::accessibility::XAccessible > xAccessible; @@ -60,14 +59,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&); @@ -98,10 +92,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 @@ -149,9 +139,9 @@ class SmCmdBoxWindow : public SfxDockingWindow SmEditWindow aEdit; SmEditController aController; BOOL bExiting; - + Timer aInitialFocusTimer; - + DECL_LINK(InitialFocusTimerHdl, Timer *); protected : @@ -229,6 +219,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, @@ -292,6 +287,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 0000000000..e2be49dedf --- /dev/null +++ b/starmath/inc/visitors.hxx @@ -0,0 +1,470 @@ +#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 */ +class SmCaretPosGraphBuildingVisitor : public SmVisitor +{ +public: + SmCaretPosGraphBuildingVisitor( ){ + pRightMost = NULL; + pGraph = new 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. + */ + 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 f62dd0b5c4..d90403616c 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 92eb8ded63..bf93f70255 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 0000000000..24374beaab --- /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 0000000000..9637889781 --- /dev/null +++ b/starmath/source/cursor.cxx @@ -0,0 +1,1593 @@ +#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 + SmCaretPosGraphBuildingVisitor builder; + pTree->Accept(&builder); + pGraph = builder.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 a929045f05..f292409a98 100644 --- a/starmath/source/dialog.cxx +++ b/starmath/source/dialog.cxx @@ -1487,8 +1487,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 8c9127d6dd..f3b352172a 100644 --- a/starmath/source/document.cxx +++ b/starmath/source/document.cxx @@ -1,7 +1,7 @@ /************************************************************************* * * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. - * + * * Copyright 2000, 2010 Oracle and/or its affiliates. * * OpenOffice.org - a multi-platform office productivity suite @@ -96,6 +96,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; @@ -131,7 +132,7 @@ void SmDocShell::SFX_NOTIFY(SfxBroadcaster&, const TypeId&, { case HINT_FORMATCHANGED: SetFormulaArranged(FALSE); - + nModifyCount++; //! see comment for SID_GAPHIC_SM in SmDocShell::GetState Repaint(); @@ -250,6 +251,7 @@ void SmDocShell::Parse() pTree = aInterpreter.Parse(aText); nModifyCount++; //! see comment for SID_GAPHIC_SM in SmDocShell::GetState SetFormulaArranged( FALSE ); + InvalidateCursor(); } @@ -432,9 +434,10 @@ SfxItemPool& SmDocShell::GetEditEngineItemPool() DBG_ASSERT( 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" ); @@ -475,8 +478,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 ); @@ -517,6 +528,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 ) @@ -689,6 +712,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()); @@ -713,6 +737,11 @@ SmDocShell::~SmDocShell() EndListening(aFormat); EndListening(*pp->GetConfig()); + + if(pCursor) + delete pCursor; + pCursor = NULL; + delete pEditEngine; SfxItemPool::Free(pEditEngineItemPool); delete pTree; @@ -744,6 +773,7 @@ BOOL SmDocShell::ConvertFrom(SfxMedium &rMedium) { delete pTree; pTree = 0; + InvalidateCursor(); } Reference<com::sun::star::frame::XModel> xModel(GetModel()); SmXMLImportWrapper aEquation(xModel); @@ -1216,7 +1246,7 @@ void SmDocShell::GetState(SfxItemSet &rSet) case SID_GAPHIC_SM: //! very old (pre UNO) and ugly hack to invalidate the SmGraphicWindow. - //! If nModifyCount gets changed then the call below will implicitly notify + //! If nModifyCount gets changed then the call below will implicitly notify //! SmGraphicController::StateChanged and there the window gets invalidated. //! Thus all the 'nModifyCount++' before invalidating this slot. rSet.Put(SfxInt16Item(SID_GAPHIC_SM, nModifyCount)); @@ -1300,7 +1330,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 935c2133c1..b6cf80b70a 100644 --- a/starmath/source/edit.cxx +++ b/starmath/source/edit.cxx @@ -121,9 +121,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(); @@ -132,7 +129,6 @@ SmEditWindow::SmEditWindow( SmCmdBoxWindow &rMyCmdBoxWin ) : SmEditWindow::~SmEditWindow() { - aCursorMoveTimer.Stop(); aModifyTimer.Stop(); @@ -256,36 +252,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) @@ -319,8 +285,6 @@ void SmEditWindow::MouseButtonUp(const MouseEvent &rEvt) else Window::MouseButtonUp (rEvt); - // ggf FormulaCursor neu positionieren - CursorMoveTimerHdl(&aCursorMoveTimer); InvalidateSlots(); } @@ -425,10 +389,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(); - DBG_ASSERT( pEditView, "EditView missing (NULL pointer)" ); if (!pEditView) CreateEditView(); @@ -631,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); } @@ -655,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); } @@ -737,7 +700,6 @@ void SmEditWindow::InsertCommand(USHORT nCommand) } aModifyTimer.Start(); - aCursorMoveTimer.Start(); GrabFocus(); } @@ -925,7 +887,6 @@ void SmEditWindow::InsertText(const String& Text) { pEditView->InsertText(Text); aModifyTimer.Start(); - aCursorMoveTimer.Start(); } } @@ -943,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 a409e55d24..ab8f39c5c5 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 f46bfe0f77..fddbde1b04 100755 --- a/starmath/source/node.cxx +++ b/starmath/source/node.cxx @@ -1,7 +1,7 @@ /************************************************************************* * * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. - * + * * Copyright 2000, 2010 Oracle and/or its affiliates. * * OpenOffice.org - a multi-platform office productivity suite @@ -35,6 +35,7 @@ #include "document.hxx" #include "view.hxx" #include "mathtype.hxx" +#include "visitors.hxx" #include <tools/gen.hxx> #include <tools/fract.hxx> @@ -142,6 +143,8 @@ SmNode::SmNode(SmNodeType eNodeType, const SmToken &rNodeToken) eScaleMode = SCALE_NONE; aNodeToken = rNodeToken; nAccIndex = -1; + SetSelected(false); + aParentNode = NULL; } @@ -444,28 +447,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'. @@ -566,6 +547,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 ) : @@ -583,6 +658,7 @@ SmStructureNode::SmStructureNode( const SmStructureNode &rNode ) : SmNode *pNode = rNode.aSubNodes[i]; aSubNodes[i] = pNode ? new SmNode( *pNode ) : 0; } + ClaimPaternity(); } @@ -613,6 +689,8 @@ SmStructureNode & SmStructureNode::operator = ( const SmStructureNode &rNode ) aSubNodes[i] = pNode ? new SmNode( *pNode ) : 0; } + ClaimPaternity(); + return *this; } @@ -627,12 +705,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(); } @@ -2174,36 +2255,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) @@ -2220,48 +2271,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 -} - - /**************************************************************************/ @@ -2301,51 +2310,10 @@ 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; - - 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 (!IsDebug()) - return; - - int nRFlags = SM_RECT_CORE | SM_RECT_ITALIC | SM_RECT_LINES | SM_RECT_MID; - SmRect::Draw(rDev, rPosition, nRFlags); -#endif -} - - /**************************************************************************/ -SmTextNode::SmTextNode( SmNodeType eNodeType, const SmToken &rNodeToken, USHORT nFontDescP ) : +SmTextNode::SmTextNode( SmNodeType eNodeType, const SmToken &rNodeToken, USHORT nFontDescP ) : SmVisibleNode(eNodeType, rNodeToken) { nFontDesc = nFontDescP; @@ -2450,35 +2418,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) @@ -2520,7 +2496,7 @@ void SmMatrixNode::Arrange(const OutputDevice &rDev, const SmFormat &rFormat) { USHORT nIdx = nNodes - 1 - i; if (NULL != (pNode = GetSubNode(nIdx))) - { + { pNode->Arrange(rDev, rFormat); int nCol = nIdx % nNumCols; pColWidth[nCol] = Max(pColWidth[nCol], pNode->GetItalicWidth()); @@ -2907,17 +2883,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); -} - - /**************************************************************************/ @@ -3028,5 +2993,122 @@ 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); +} diff --git a/starmath/source/parse.cxx b/starmath/source/parse.cxx index 4409d4f4a4..0ef0898030 100755 --- a/starmath/source/parse.cxx +++ b/starmath/source/parse.cxx @@ -1,7 +1,7 @@ /************************************************************************* * * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. - * + * * Copyright 2000, 2010 Oracle and/or its affiliates. * * OpenOffice.org - a multi-platform office productivity suite @@ -84,16 +84,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[] = { @@ -307,8 +312,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()) @@ -434,7 +438,7 @@ void SmParser::NextToken() ParseResult aTmpRes; lang::Locale aOldLoc( aCC.getLocale() ); aCC.setLocale( aDotLoc ); - aTmpRes = aCC.parsePredefinedToken( + aTmpRes = aCC.parsePredefinedToken( KParseType::ASC_NUMBER, BufferString, BufferIndex, KParseTokens::ASC_DIGIT, aEmptyStr, @@ -560,7 +564,7 @@ void SmParser::NextToken() else if (aRes.TokenType & KParseType::BOOLEAN) { sal_Int32 &rnEndPos = aRes.EndPos; - String aName( BufferString.Copy( nRealStart, + String aName( BufferString.Copy( nRealStart, sal::static_int_cast< xub_StrLen >(rnEndPos - nRealStart) )); if (2 >= aName.Len()) { @@ -665,7 +669,7 @@ void SmParser::NextToken() else if (aRes.TokenType & KParseType::ONE_SINGLE_CHAR) { sal_Int32 &rnEndPos = aRes.EndPos; - String aName( BufferString.Copy( nRealStart, + String aName( BufferString.Copy( nRealStart, sal::static_int_cast< xub_StrLen >(rnEndPos - nRealStart) ) ); if (1 == aName.Len()) @@ -927,7 +931,7 @@ void SmParser::NextToken() } while ( cChar == '.' || IsDigit( cChar ) ); - CurToken.aText = BufferString.Copy( sal::static_int_cast< xub_StrLen >(nTxtStart), + CurToken.aText = BufferString.Copy( sal::static_int_cast< xub_StrLen >(nTxtStart), sal::static_int_cast< xub_StrLen >(BufferIndex - nTxtStart) ); aRes.EndPos = BufferIndex; } @@ -1085,6 +1089,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); @@ -1187,6 +1198,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; @@ -1346,7 +1361,7 @@ void SmParser::Blank() void SmParser::Term() { switch (CurToken.eType) - { + { case TESCAPE : Escape(); break; @@ -1371,7 +1386,7 @@ void SmParser::Term() // allow for empty group if (CurToken.eType == TRGROUP) - { + { if (bNoSpace) // get rid of the 'no space' node pushed above NodeStack.Pop(); SmStructureNode *pSNode = new SmExpressionNode(CurToken); @@ -1381,7 +1396,7 @@ void SmParser::Term() NextToken(); } else // go as usual - { + { Align(); if (CurToken.eType != TRGROUP) Error(PE_RGROUP_EXPECTED); @@ -1697,6 +1712,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; @@ -2196,7 +2214,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); } @@ -2375,7 +2397,7 @@ SmNode *SmParser::Parse(const String &rBuffer) { BufferString = rBuffer; BufferString.ConvertLineEnd( LINEEND_LF ); - BufferIndex = + BufferIndex = 0; nTokenIndex = 0; Row = 1; ColOff = 0; @@ -2395,6 +2417,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 913e5ff4c7..31622bb1d9 100755 --- a/starmath/source/view.cxx +++ b/starmath/source/view.cxx @@ -1,7 +1,7 @@ /************************************************************************* * * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. - * + * * Copyright 2000, 2010 Oracle and/or its affiliates. * * OpenOffice.org - a multi-platform office productivity suite @@ -65,6 +65,7 @@ #include <vcl/menu.hxx> #include <vcl/msgbox.hxx> #include <vcl/wrkwin.hxx> +#include <fstream> #include "unomodel.hxx" #include "view.hxx" @@ -74,7 +75,7 @@ #include "starmath.hrc" #include "toolbox.hxx" #include "mathmlimport.hxx" - +#include "cursor.hxx" #define MINWIDTH 200 #define MINHEIGHT 200 @@ -97,8 +98,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. @@ -155,65 +155,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 // DBG_ASSERT(rMEvt.GetClicks() > 0, "Sm : 0 clicks"); - if ( rMEvt.IsLeft() && pViewShell->GetEditWindow() ) + 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() @@ -229,69 +196,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&) { DBG_ASSERT(pViewShell, "Sm : NULL pointer"); @@ -299,25 +203,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); } @@ -329,11 +220,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); + } + } + } } @@ -987,7 +980,7 @@ void SmViewShell::DrawText(OutputDevice& rDevice, const Point& rPosition, const } void SmViewShell::Impl_Print( - OutputDevice &rOutDev, + OutputDevice &rOutDev, const SmPrintUIOptions &rPrintUIOptions, Rectangle aOutRect, Point aZeroPoint ) { @@ -1132,7 +1125,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(); @@ -1366,7 +1359,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: @@ -1511,17 +1505,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 0000000000..f39825fb36 --- /dev/null +++ b/starmath/source/visitors.cxx @@ -0,0 +1,2473 @@ +#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 //////////////////////////////// + + +//Needs special care: +void SmCaretPosGraphBuildingVisitor::Visit( SmTableNode* pNode ) +{ + //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( pNode ); + 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 ); + } +} +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 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 ); +} |