diff --git .gitignore .gitignore
index 0ab7295..20cb7a4 100644
--- .gitignore
+++ .gitignore
@@ -19,3 +19,5 @@ config.status
configure
set_soenv
warn
+.*.swp
+/starmath/starmath.vis
diff --git starmath/inc/caret.hxx starmath/inc/caret.hxx
new file mode 100644
index 0000000..b1ff954
--- /dev/null
+++ 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:
+ *
+ *
+ * | Caret position: | Example: |
+ *
+ * | {id: 8, index: 0} |
+ * \f$ \mid \mbox{C} \cdot \mbox{C} \f$ |
+ *
+ * | {id: 10, index: 1} |
+ * \f$ \mbox{C} \mid \cdot \mbox{C} \f$ |
+ *
+ * | {id: 11, index: 1} |
+ * \f$ \mbox{C} \cdot \mid \mbox{C} \f$ |
+ *
+ * | {id: 12, index: 1} |
+ * \f$ \mbox{C} \cdot \mbox{C} \mid \f$ |
+ *
+ *
+ *
+ * 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:
+ *
+ *
+ *
+ * | Caret position: |
+ * Example: |
+ *
+ * | {id: 3, index: 0} |
+ * \f$ \mid\mbox{A} + \frac{\mbox{B} \cdot \mbox{C}}{\mbox{D}} \f$ |
+ *
+ * | {id: 5, index: 1} |
+ * \f$ \mbox{A}\mid + \frac{\mbox{B} \cdot \mbox{C}}{\mbox{D}} \f$ |
+ *
+ * | {id: 6, index: 1} |
+ * \f$ \mbox{A} + \mid \frac{\mbox{B} \cdot \mbox{C}}{\mbox{D}} \f$ |
+ *
+ * | {id: 8, index: 0} |
+ * \f$ \mbox{A} + \frac{ \mid \mbox{B} \cdot \mbox{C}}{\mbox{D}} \f$ |
+ *
+ * | {id: 10, index: 1} |
+ * \f$ \mbox{A} + \frac{\mbox{B} \mid \cdot \mbox{C}}{\mbox{D}} \f$ |
+ *
+ * | {id: 11, index: 1} |
+ * \f$ \mbox{A} + \frac{\mbox{B} \cdot \mid \mbox{C}}{\mbox{D}} \f$ |
+ *
+ * | {id: 12, index: 1} |
+ * \f$ \mbox{A} + \frac{\mbox{B} \cdot \mbox{C} \mid}{\mbox{D}} \f$ |
+ *
+ * | {id: 14, index: 0} |
+ * \f$ \mbox{A} + \frac{\mbox{B} \cdot \mbox{C}}{\mid \mbox{D}} \f$ |
+ *
+ * | {id: 14, index: 1} |
+ * \f$ \mbox{A} + \frac{\mbox{B} \cdot \mbox{C}}{\mbox{D} \mid} \f$ |
+ *
+ * | {id: 7, index: 1} |
+ * \f$ \mbox{A} + \frac{\mbox{B} \cdot \mbox{C}}{\mbox{D}} \mid \f$ |
+ *
+ *
+ *
+ * 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 starmath/inc/cursor.hxx starmath/inc/cursor.hxx
new file mode 100644
index 0000000..792d18e
--- /dev/null
+++ starmath/inc/cursor.hxx
@@ -0,0 +1,374 @@
+#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,
+ OverElement,
+ 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 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 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 section where the tree will be modified */
+ void BeginEdit();
+ /** End section where the tree will be modified */
+ void EndEdit();
+ /** 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 |
+ * \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);
+private:
+ SmNodeList* pList;
+ /** Get the current token */
+ SmNode* Token(){
+ if(pList->size() > 0)
+ return pList->front();
+ return NULL;
+ }
+ /** Move to next token */
+ SmNode* Next(){
+ pList->pop_front();
+ return Token();
+ }
+ /** Take the current token */
+ SmNode* Take(){
+ SmNode* pRetVal = Token();
+ Next();
+ return pRetVal;
+ }
+ SmNode* Expression();
+ SmNode* Relation();
+ SmNode* Sum();
+ SmNode* Product();
+ SmNode* Factor();
+ SmNode* Error();
+};
+
+
+#endif /* SMCURSOR_H */
diff --git starmath/inc/document.hxx starmath/inc/document.hxx
index 364eff2..1e7cd62 100644
--- starmath/inc/document.hxx
+++ starmath/inc/document.hxx
@@ -47,6 +47,7 @@ class SmNode;
class SfxMenuBarManager;
class SfxPrinter;
class Printer;
+class SmCursor;
#define HINT_DATACHANGED 1004
@@ -105,6 +106,7 @@ class SmDocShell : public SfxObjectShell, public SfxListener
{
friend class SmPrinterAccess;
friend class SmModel;
+ friend class SmCursor;
String aText;
SmFormat aFormat;
@@ -122,6 +124,7 @@ class SmDocShell : public SfxObjectShell, public SfxListener
nBottomBorder;
USHORT nModifyCount;
BOOL bIsFormulaArranged;
+ SmCursor *pCursor;
@@ -157,10 +160,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)
@@ -204,7 +212,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();
@@ -218,6 +226,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 starmath/inc/edit.hxx starmath/inc/edit.hxx
index 595564f..56bafab 100644
--- starmath/inc/edit.hxx
+++ 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 starmath/inc/node.hxx starmath/inc/node.hxx
index dabb503..142e7a3 100644
--- starmath/inc/node.hxx
+++ starmath/inc/node.hxx
@@ -31,6 +31,24 @@
#include
+#include
+#include
+#include
+
+//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: "< graph.png
+ */
+ inline void DumpAsDot(std::ostream &out, String* label = NULL) const{
+ int id = 0;
+ DumpAsDot(out, label, -1, id, -1);
+ }
+ /** 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;
+ }
+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
+ }
+ /** Set the token for this node */
+ void SetToken(SmToken& token){
+ aNodeToken = token;
+ }
+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 +347,29 @@ public:
virtual SmStructureNode & operator = ( const SmStructureNode &rNode );
virtual void GetAccessibleText( String &rText ) const;
+
+ void SetSubNode(USHORT nIndex, SmNode* pNode){
+ int size = aSubNodes.size();
+ if(size <= nIndex){
+ //Resize subnodes array
+ aSubNodes.resize(nIndex + 1);
+ //Set new slots to NULL
+ for(int i = size; i < nIndex+1; i++)
+ aSubNodes[i] = NULL;
+ }
+ aSubNodes[nIndex] = pNode;
+ ClaimPaternity();
+ }
};
////////////////////////////////////////////////////////////////////////////////
-
+/** Abstract base class for all visible node
+ *
+ * Nodes that doesn't derivate from this class doesn't draw anything, but their
+ * children.
+ */
class SmVisibleNode : public SmNode
{
protected:
@@ -252,7 +404,10 @@ public:
////////////////////////////////////////////////////////////////////////////////
-
+/** Draws a rectangle
+ *
+ * Used for drawing the line in the OVER and OVERSTRIKE commands.
+ */
class SmRectangleNode : public SmGraphicNode
{
Size aToSize;
@@ -270,15 +425,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 +448,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 +459,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)
@@ -328,6 +501,25 @@ 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);
+ }
+ /** 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);
@@ -336,15 +528,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
{
protected:
@@ -363,13 +562,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:
@@ -378,12 +586,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:
@@ -404,12 +616,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
@@ -419,19 +637,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:
@@ -439,15 +664,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:
@@ -459,12 +690,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:
@@ -476,12 +714,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
{
protected:
@@ -496,12 +739,18 @@ public:
virtual void Prepare(const SmFormat &rFormat, const SmDocShell &rDocShell);
virtual void Arrange(const OutputDevice &rDev, const SmFormat &rFormat);
+ void Accept(SmVisitor* pVisitor);
};
////////////////////////////////////////////////////////////////////////////////
-
+/** Expression node
+ *
+ * Used whenever you have an expression such as "A OVER {B + C}", here there is
+ * an expression node that allows "B + C" to be the denominator of the
+ * SmBinVerNode, that the OVER command creates.
+ */
class SmExpressionNode : public SmLineNode
{
public:
@@ -511,12 +760,16 @@ public:
virtual void Arrange(const OutputDevice &rDev, const SmFormat &rFormat);
void CreateTextFromNode(String &rText);
+ void Accept(SmVisitor* pVisitor);
};
////////////////////////////////////////////////////////////////////////////////
-
+/** Unary horizontical node
+ *
+ * The same as SmBinHorNode except this is for unary operators.
+ */
class SmUnHorNode : public SmStructureNode
{
public:
@@ -527,12 +780,23 @@ public:
}
virtual void Arrange(const OutputDevice &rDev, const SmFormat &rFormat);
+ void Accept(SmVisitor* pVisitor);
};
////////////////////////////////////////////////////////////////////////////////
-
+/** Root node
+ *
+ * Used for create square roots and other roots, example:
+ * \f$ \sqrt[\mbox{[Argument]}]{\mbox{[Body]}} \f$.
+ *
+ * Children:
+ * 0: Argument (optional)
+ * 1: Symbol (instance of SmRootSymbolNode)
+ * 2: Body
+ * Where argument is optinal and may be NULL.
+ */
class SmRootNode : public SmStructureNode
{
protected:
@@ -549,12 +813,23 @@ public:
virtual void Arrange(const OutputDevice &rDev, const SmFormat &rFormat);
void CreateTextFromNode(String &rText);
+ void Accept(SmVisitor* pVisitor);
};
////////////////////////////////////////////////////////////////////////////////
-
+/** Binary horizontial node
+ *
+ * This node is used for binary operators. In a formula such as "A + B".
+ *
+ * Children:
+ * 0: Left operand
+ * 1: Binary operator
+ * 2: Right operand
+ *
+ * None of the children may be NULL.
+ */
class SmBinHorNode : public SmStructureNode
{
public:
@@ -565,12 +840,24 @@ public:
}
virtual void Arrange(const OutputDevice &rDev, const SmFormat &rFormat);
+ void Accept(SmVisitor* pVisitor);
};
////////////////////////////////////////////////////////////////////////////////
-
+/** Binary horizontical node
+ *
+ * This node is used for creating the OVER command, consider the formula:
+ * "numerator OVER denominator", which looks like
+ * \f$ \frac{\mbox{numerator}}{\mbox{denominator}} \f$
+ *
+ * Children:
+ * 0: Numerator
+ * 1: Line (instance of SmRectangleNode)
+ * 2: Denominator
+ * None of the children may be NULL.
+ */
class SmBinVerNode : public SmStructureNode
{
public:
@@ -585,12 +872,22 @@ public:
virtual void Arrange(const OutputDevice &rDev, const SmFormat &rFormat);
void CreateTextFromNode(String &rText);
+ void Accept(SmVisitor* pVisitor);
};
////////////////////////////////////////////////////////////////////////////////
-
+/** Binary diagonal node
+ *
+ * Used for implementing the WIDESLASH command, example: "A WIDESLASH B".
+ *
+ * Children:
+ * 0: Left operand
+ * 1: right operand
+ * 2: Line (instance of SmPolyLineNode).
+ * None of the children may be NULL.
+ */
class SmBinDiagonalNode : public SmStructureNode
{
BOOL bAscending;
@@ -605,35 +902,53 @@ public:
void SetAscending(BOOL bVal) { bAscending = bVal; }
virtual void Arrange(const OutputDevice &rDev, const SmFormat &rFormat);
+ void Accept(SmVisitor* pVisitor);
};
////////////////////////////////////////////////////////////////////////////////
-// enums used to index sub-/supscripts in the 'aSubNodes' array
-// in 'SmSubSupNode'
-// See graphic for positions at char:
-//
-// CSUP
-//
-// LSUP H H RSUP
-// H H
-// HHHH
-// H H
-// LSUB H H RSUB
-//
-// CSUB
-//
+/** Enum used to index sub-/supscripts in the 'aSubNodes' array
+ * in 'SmSubSupNode'
+ *
+ * See graphic for positions at char:
+ *
+ * \code
+ * CSUP
+ *
+ * LSUP H H RSUP
+ * H H
+ * HHHH
+ * H H
+ * LSUB H H RSUB
+ *
+ * CSUB
+ * \endcode
+ */
enum SmSubSup
{ CSUB, CSUP, RSUB, RSUP, LSUB, LSUP
};
-// numbers of entries in the above enum (that is: the number of possible
-// sub-/supscripts)
+/** numbers of entries in the above enum (that is: the number of possible
+ * sub-/supscripts)
+ */
#define SUBSUP_NUM_ENTRIES 6
-
+/** Super- and subscript node
+ *
+ * Used for creating super- and subscripts for commands such as:
+ * "^", "_", "lsup", "lsub", "csup" and "csub".
+ * Example: "A^2" which looks like: \f$ A^2 \f$
+ *
+ * This node is also used for creating limits on SmOperNode, when
+ * "FROM" and "TO" commands are used with "INT", "SUM" or similar.
+ *
+ * Children of this node can be enumerated using the SmSubSup enum.
+ * Please note that children may be NULL, except for the body.
+ * It is recommended that you access children using GetBody() and
+ * GetSubSup().
+ */
class SmSubSupNode : public SmStructureNode
{
BOOL bUseLimits;
@@ -646,7 +961,9 @@ public:
bUseLimits = FALSE;
}
+ /** Get body (Not NULL) */
SmNode * GetBody() { return GetSubNode(0); }
+ /** Get body (Not NULL) */
const SmNode * GetBody() const
{
return ((SmSubSupNode *) this)->GetBody();
@@ -655,17 +972,39 @@ public:
void SetUseLimits(BOOL bVal) { bUseLimits = bVal; }
BOOL IsUseLimits() const { return bUseLimits; };
+ /** Get super- or subscript
+ * @remarks this method may return NULL.
+ */
SmNode * GetSubSup(SmSubSup eSubSup) { return GetSubNode( sal::static_int_cast< USHORT >(1 + eSubSup) ); };
+ /** Set the body */
+ void SetBody(SmNode* pBody) { SetSubNode(0, pBody); }
+ void SetSubSup(SmSubSup eSubSup, SmNode* pScript) { SetSubNode( 1 + eSubSup, pScript); }
+
virtual void Arrange(const OutputDevice &rDev, const SmFormat &rFormat);
void CreateTextFromNode(String &rText);
+ void Accept(SmVisitor* pVisitor);
};
////////////////////////////////////////////////////////////////////////////////
-
+/** Node for brace construction
+ *
+ * Used for "lbrace [body] rbrace" and similar constructions.
+ * Should look like \f$ \{\mbox{[body]}\} \f$
+ *
+ * Children:
+ * 0: Opening brace
+ * 1: Body (usually SmBracebodyNode)
+ * 2: Closing brace
+ * 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:
@@ -677,12 +1016,21 @@ public:
virtual void Arrange(const OutputDevice &rDev, const SmFormat &rFormat);
void CreateTextFromNode(String &rText);
+ void Accept(SmVisitor* pVisitor);
};
////////////////////////////////////////////////////////////////////////////////
-
+/** Body of an SmBraceNode
+ *
+ * This usually only has one child an SmExpressionNode, however, it can also
+ * have other children.
+ * Consider the formula "lbrace [body1] mline [body2] rbrace", looks like:
+ * \f$ \{\mbox{[body1] | [body2]}\} \f$.
+ * In this case SmBracebodyNode will have three children, "[body1]", "|" and
+ * [body2].
+ */
class SmBracebodyNode : public SmStructureNode
{
long nBodyHeight;
@@ -692,6 +1040,7 @@ public:
virtual void Arrange(const OutputDevice &rDev, const SmFormat &rFormat);
long GetBodyHeight() const { return nBodyHeight; }
+ void Accept(SmVisitor* pVisitor);
};
@@ -704,13 +1053,25 @@ inline SmBracebodyNode::SmBracebodyNode(const SmToken &rNodeToken) :
////////////////////////////////////////////////////////////////////////////////
-
+/** Node for vertical brace construction
+ *
+ * Used to implement commands "[body] underbrace [script]" and
+ * "[body] overbrace [script]".
+ * Underbrace should look like this \f$ \underbrace{\mbox{body}}_{\mbox{script}}\f$.
+ *
+ * Children:
+ * 0: body
+ * 1: brace
+ * 2: script
+ * (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);
};
@@ -724,6 +1085,18 @@ inline SmVerticalBraceNode::SmVerticalBraceNode(const SmToken &rNodeToken) :
////////////////////////////////////////////////////////////////////////////////
+/** Operation Node
+ *
+ * Used for commands like SUM, INT and similar.
+ *
+ * Children:
+ * 0: Operation (instance of SmMathSymbolNode)
+ * 1: Body
+ * 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:
@@ -742,12 +1115,14 @@ public:
long CalcSymbolHeight(const SmNode &rSymbol, const SmFormat &rFormat) const;
virtual void Arrange(const OutputDevice &rDev, const SmFormat &rFormat);
+ void Accept(SmVisitor* pVisitor);
};
////////////////////////////////////////////////////////////////////////////////
-
+/** Node used for alignment
+ */
class SmAlignNode : public SmStructureNode
{
public:
@@ -756,12 +1131,22 @@ public:
{}
virtual void Arrange(const OutputDevice &rDev, const SmFormat &rFormat);
+ void Accept(SmVisitor* pVisitor);
};
////////////////////////////////////////////////////////////////////////////////
-
+/** Attribute node
+ *
+ * Used to give an attribute to another node. Used for commands such as:
+ * UNDERLINE, OVERLINE, OVERSTRIKE, WIDEVEC, WIDEHAT and WIDETILDE.
+ *
+ * Children:
+ * 0: Attribute
+ * 1: Body
+ * None of these may be NULL.
+ */
class SmAttributNode : public SmStructureNode
{
public:
@@ -771,12 +1156,16 @@ public:
virtual void Arrange(const OutputDevice &rDev, const SmFormat &rFormat);
void CreateTextFromNode(String &rText);
+ void Accept(SmVisitor* pVisitor);
};
////////////////////////////////////////////////////////////////////////////////
-
+/** Font node
+ *
+ * Used to change the font of it's children.
+ */
class SmFontNode : public SmStructureNode
{
USHORT nSizeType;
@@ -797,12 +1186,17 @@ public:
virtual void Prepare(const SmFormat &rFormat, const SmDocShell &rDocShell);
virtual void Arrange(const OutputDevice &rDev, const SmFormat &rFormat);
void CreateTextFromNode(String &rText);
+ void Accept(SmVisitor* pVisitor);
};
////////////////////////////////////////////////////////////////////////////////
-
+/** Matrix node
+ *
+ * Used to implement the MATRIX command, example:
+ * "matrix{ 1 # 2 ## 3 # 4}".
+ */
class SmMatrixNode : public SmStructureNode
{
USHORT nNumRows,
@@ -824,12 +1218,16 @@ public:
virtual void Arrange(const OutputDevice &rDev, const SmFormat &rFormat);
void CreateTextFromNode(String &rText);
+ void Accept(SmVisitor* pVisitor);
};
////////////////////////////////////////////////////////////////////////////////
-
+/** Node for whitespace
+ *
+ * Used to implement the "~" command. This node is just a blank space.
+ */
class SmBlankNode : public SmGraphicNode
{
USHORT nNum;
@@ -843,9 +1241,12 @@ public:
void IncreaseBy(const SmToken &rToken);
void Clear() { nNum = 0; }
+ USHORT GetBlankNum() const { return nNum; }
+ void SetBlankNum(USHORT nNumber) { nNum = nNumber; }
virtual void Prepare(const SmFormat &rFormat, const SmDocShell &rDocShell);
virtual void Arrange(const OutputDevice &rDev, const SmFormat &rFormat);
+ void Accept(SmVisitor* pVisitor);
};
diff --git starmath/inc/parse.hxx starmath/inc/parse.hxx
index 0211d97..97a7413 100644
--- starmath/inc/parse.hxx
+++ starmath/inc/parse.hxx
@@ -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);
};
@@ -239,7 +244,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; };
diff --git starmath/inc/starmath.hrc starmath/inc/starmath.hrc
index 0f9aec4..cb3cbe5 100644
--- starmath/inc/starmath.hrc
+++ starmath/inc/starmath.hrc
@@ -62,7 +62,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 starmath/inc/view.hxx starmath/inc/view.hxx
index eab2444..45d3990 100644
--- starmath/inc/view.hxx
+++ starmath/inc/view.hxx
@@ -50,7 +50,6 @@ class SmViewShell;
class SmGraphicWindow : public ScrollableWindow
{
Point aFormulaDrawPos;
- Rectangle aCursorRect;
::com::sun::star::uno::Reference<
::com::sun::star::accessibility::XAccessible > xAccessible;
@@ -59,14 +58,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&);
@@ -97,10 +91,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
@@ -228,6 +218,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,
@@ -291,6 +286,16 @@ public:
void Impl_Print( OutputDevice &rOutDev, const SmPrintSize ePrintSize,
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 starmath/inc/visitors.hxx starmath/inc/visitors.hxx
new file mode 100644
index 0000000..932578f
--- /dev/null
+++ starmath/inc/visitors.hxx
@@ -0,0 +1,505 @@
+#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* node) = 0;
+ virtual void Visit(SmBraceNode* node) = 0;
+ virtual void Visit(SmBracebodyNode* node) = 0;
+ virtual void Visit(SmOperNode* node) = 0;
+ virtual void Visit(SmAlignNode* node) = 0;
+ virtual void Visit(SmAttributNode* node) = 0;
+ virtual void Visit(SmFontNode* node) = 0;
+ virtual void Visit(SmUnHorNode* node) = 0;
+ virtual void Visit(SmBinHorNode* node) = 0;
+ virtual void Visit(SmBinVerNode* node) = 0;
+ virtual void Visit(SmBinDiagonalNode* node) = 0;
+ virtual void Visit(SmSubSupNode* node) = 0;
+ virtual void Visit(SmMatrixNode* node) = 0;
+ virtual void Visit(SmPlaceNode* node) = 0;
+ virtual void Visit(SmTextNode* node) = 0;
+ virtual void Visit(SmSpecialNode* node) = 0;
+ virtual void Visit(SmGlyphSpecialNode* node) = 0;
+ virtual void Visit(SmMathSymbolNode* node) = 0;
+ virtual void Visit(SmBlankNode* node) = 0;
+ virtual void Visit(SmErrorNode* node) = 0;
+ virtual void Visit(SmLineNode* node) = 0;
+ virtual void Visit(SmExpressionNode* node) = 0;
+ virtual void Visit(SmPolyLineNode* node) = 0;
+ virtual void Visit(SmRootNode* node) = 0;
+ virtual void Visit(SmRootSymbolNode* node) = 0;
+ virtual void Visit(SmRectangleNode* node) = 0;
+ virtual void Visit(SmVerticalBraceNode* node) = 0;
+};
+
+/** Simple visitor for testing SmVisitor */
+class SmVisitorTest : public SmVisitor{
+public:
+ void Visit(SmTableNode* node);
+ void Visit(SmBraceNode* node);
+ void Visit(SmBracebodyNode* node);
+ void Visit(SmOperNode* node);
+ void Visit(SmAlignNode* node);
+ void Visit(SmAttributNode* node);
+ void Visit(SmFontNode* node);
+ void Visit(SmUnHorNode* node);
+ void Visit(SmBinHorNode* node);
+ void Visit(SmBinVerNode* node);
+ void Visit(SmBinDiagonalNode* node);
+ void Visit(SmSubSupNode* node);
+ void Visit(SmMatrixNode* node);
+ void Visit(SmPlaceNode* node);
+ void Visit(SmTextNode* node);
+ void Visit(SmSpecialNode* node);
+ void Visit(SmGlyphSpecialNode* node);
+ void Visit(SmMathSymbolNode* node);
+ void Visit(SmBlankNode* node);
+ void Visit(SmErrorNode* node);
+ void Visit(SmLineNode* node);
+ void Visit(SmExpressionNode* node);
+ void Visit(SmPolyLineNode* node);
+ void Visit(SmRootNode* node);
+ void Visit(SmRootSymbolNode* node);
+ void Visit(SmRectangleNode* node);
+ void Visit(SmVerticalBraceNode* node);
+private:
+ /** Auxiliary method for visiting the children of a node */
+ void VisitChildren(SmNode* node){
+ SmNode *pNode;
+ USHORT nSize = node->GetNumSubNodes();
+ for (USHORT i = 0; i < nSize; i++)
+ if (NULL != (pNode = node->GetSubNode(i)))
+ pNode->Accept(this);
+ }
+};
+
+/////////////////////////////// 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* node);
+ void Visit(SmBraceNode* node);
+ void Visit(SmBracebodyNode* node);
+ void Visit(SmOperNode* node);
+ void Visit(SmAlignNode* node);
+ void Visit(SmAttributNode* node);
+ void Visit(SmFontNode* node);
+ void Visit(SmUnHorNode* node);
+ void Visit(SmBinHorNode* node);
+ void Visit(SmBinVerNode* node);
+ void Visit(SmBinDiagonalNode* node);
+ void Visit(SmSubSupNode* node);
+ void Visit(SmMatrixNode* node);
+ void Visit(SmPlaceNode* node);
+ void Visit(SmTextNode* node);
+ void Visit(SmSpecialNode* node);
+ void Visit(SmGlyphSpecialNode* node);
+ void Visit(SmMathSymbolNode* node);
+ void Visit(SmBlankNode* node);
+ void Visit(SmErrorNode* node);
+ void Visit(SmLineNode* node);
+ void Visit(SmExpressionNode* node);
+ void Visit(SmPolyLineNode* node);
+ void Visit(SmRootNode* node);
+ void Visit(SmRootSymbolNode* node);
+ void Visit(SmRectangleNode* node);
+ void Visit(SmVerticalBraceNode* node);
+protected:
+ /** Method invoked by Visit methods by default */
+ virtual void DefaultVisit(SmNode* node) = 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)
+ : 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 Visit(SmTextNode* node);
+private:
+ OutputDevice &rDev;
+ SmCaretPos pos;
+ /** Offset to draw from */
+ Point Offset;
+protected:
+ /** Default method for drawing nodes */
+ void DefaultVisit(SmNode* node);
+};
+
+/////////////////////////////// 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* node);
+ SmCaretLine GetResult(){
+ return line;
+ }
+private:
+ SmCaretLine line;
+ OutputDevice *pDev;
+ SmCaretPos pos;
+protected:
+ /** Default method for computing lines for nodes */
+ void DefaultVisit(SmNode* node);
+};
+
+/////////////////////////////// DrawingVisitor ////////////////////////////////
+
+/** Visitor for drawing SmNodes to OutputDevice */
+class DrawingVisitor : public SmVisitor {
+public:
+ /** Create an instance of DrawingVisitor, 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.
+ */
+ DrawingVisitor(OutputDevice &rDevice, Point position, SmNode* pTree) : rDev(rDevice) {
+ this->Position = position;
+ pTree->Accept(this);
+ }
+ void Visit(SmTableNode* node);
+ void Visit(SmBraceNode* node);
+ void Visit(SmBracebodyNode* node);
+ void Visit(SmOperNode* node);
+ void Visit(SmAlignNode* node);
+ void Visit(SmAttributNode* node);
+ void Visit(SmFontNode* node);
+ void Visit(SmUnHorNode* node);
+ void Visit(SmBinHorNode* node);
+ void Visit(SmBinVerNode* node);
+ void Visit(SmBinDiagonalNode* node);
+ void Visit(SmSubSupNode* node);
+ void Visit(SmMatrixNode* node);
+ void Visit(SmPlaceNode* node);
+ void Visit(SmTextNode* node);
+ void Visit(SmSpecialNode* node);
+ void Visit(SmGlyphSpecialNode* node);
+ void Visit(SmMathSymbolNode* node);
+ void Visit(SmBlankNode* node);
+ void Visit(SmErrorNode* node);
+ void Visit(SmLineNode* node);
+ void Visit(SmExpressionNode* node);
+ void Visit(SmPolyLineNode* node);
+ void Visit(SmRootNode* node);
+ void Visit(SmRootSymbolNode* node);
+ void Visit(SmRectangleNode* node);
+ void Visit(SmVerticalBraceNode* node);
+private:
+ /** Draw the children of a node
+ * This the default method, use by most nodes
+ */
+ void DrawChildren(SmNode* node){
+ if (node->IsPhantom())
+ return;
+
+ Point rPosition = Position;
+
+ SmNode *pNode;
+ USHORT nSize = node->GetNumSubNodes();
+ for (USHORT i = 0; i < nSize; i++)
+ if (NULL != (pNode = node->GetSubNode(i)))
+ {
+ Point aOffset (pNode->GetTopLeft() - node->GetTopLeft());
+ Position = rPosition + aOffset;
+ pNode->Accept(this);
+ }
+
+ #ifdef SM_RECT_DEBUG
+ if (!node->IsDebug())
+ return;
+
+ int nRFlags = SM_RECT_CORE | SM_RECT_ITALIC | SM_RECT_LINES | SM_RECT_MID;
+ node->SmRect::Draw(rDev, rPosition, nRFlags);
+ #endif
+ }
+ /** Draw an SmTextNode or a subclass of this */
+ void DrawTextNode(SmTextNode* node);
+ /** Draw an SmSpecialNode or a subclass of this */
+ void DrawSpecialNode(SmSpecialNode* node);
+ /** 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;
+};
+
+/////////////////////////////// SetSelectionVisitor ////////////////////////////////
+
+/** Set Selection Visitor
+ * Sets the IsSelected() property on all SmNodes of the tree
+ */
+class SetSelectionVisitor : public SmDefaultingVisitor{
+public:
+ SetSelectionVisitor(SmCaretPos startPos,
+ SmCaretPos endPos){
+ StartPos = startPos;
+ EndPos = endPos;
+ IsSelecting = false;
+ }
+ void Visit(SmBinHorNode* node);
+ void Visit(SmUnHorNode* node);
+ void Visit(SmFontNode* node);
+ void Visit(SmTextNode* node);
+ void Visit(SmExpressionNode* node);
+ void Visit(SmBracebodyNode* node);
+ void Visit(SmAlignNode* node);
+ /** Set IsSelected on all nodes of pSubTree */
+ static void SetSelectedOnAll(SmNode* pSubTree, bool IsSelected = true);
+private:
+ /** Visit a selectable node
+ * Can be used to handle nodes that can be selected, that doesn't have more SmCaretPos'
+ * than 0 and 1 inside them. SmTextNode should be handle seperately!
+ * Also note that nodes such as SmBinVerNode cannot be selected, don't this method for
+ * it.
+ */
+ void DefaultVisit(SmNode* node);
+ void VisitCompositionNode(SmNode* node);
+ /** 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 node, 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* node);
+ void Visit(SmBraceNode* node);
+ void Visit(SmBracebodyNode* node);
+ void Visit(SmOperNode* node);
+ void Visit(SmAlignNode* node);
+ void Visit(SmAttributNode* node);
+ void Visit(SmFontNode* node);
+ void Visit(SmUnHorNode* node);
+ void Visit(SmBinHorNode* node);
+ void Visit(SmBinVerNode* node);
+ void Visit(SmBinDiagonalNode* node);
+ void Visit(SmSubSupNode* node);
+ void Visit(SmMatrixNode* node);
+ void Visit(SmPlaceNode* node);
+ void Visit(SmTextNode* node);
+ void Visit(SmSpecialNode* node);
+ void Visit(SmGlyphSpecialNode* node);
+ void Visit(SmMathSymbolNode* node);
+ void Visit(SmBlankNode* node);
+ void Visit(SmErrorNode* node);
+ void Visit(SmLineNode* node);
+ void Visit(SmExpressionNode* node);
+ void Visit(SmPolyLineNode* node);
+ void Visit(SmRootNode* node);
+ void Visit(SmRootSymbolNode* node);
+ void Visit(SmRectangleNode* node);
+ void Visit(SmVerticalBraceNode* node);
+ SmCaretPosGraph* Graph(){
+ return pGraph;
+ }
+private:
+ SmCaretPosGraphEntry* pRightMost;
+ SmCaretPosGraph* pGraph;
+};
+
+/////////////////////////////// SmCloningVisitor ///////////////////////////////
+
+/** Visitor for cloning a node
+ *
+ * This visitor creates deep clones.
+ */
+class SmCloningVisitor : public SmVisitor{
+public:
+ SmCloningVisitor(){ pResult = NULL; }
+ void Visit(SmTableNode* node);
+ void Visit(SmBraceNode* node);
+ void Visit(SmBracebodyNode* node);
+ void Visit(SmOperNode* node);
+ void Visit(SmAlignNode* node);
+ void Visit(SmAttributNode* node);
+ void Visit(SmFontNode* node);
+ void Visit(SmUnHorNode* node);
+ void Visit(SmBinHorNode* node);
+ void Visit(SmBinVerNode* node);
+ void Visit(SmBinDiagonalNode* node);
+ void Visit(SmSubSupNode* node);
+ void Visit(SmMatrixNode* node);
+ void Visit(SmPlaceNode* node);
+ void Visit(SmTextNode* node);
+ void Visit(SmSpecialNode* node);
+ void Visit(SmGlyphSpecialNode* node);
+ void Visit(SmMathSymbolNode* node);
+ void Visit(SmBlankNode* node);
+ void Visit(SmErrorNode* node);
+ void Visit(SmLineNode* node);
+ void Visit(SmExpressionNode* node);
+ void Visit(SmPolyLineNode* node);
+ void Visit(SmRootNode* node);
+ void Visit(SmRootSymbolNode* node);
+ void Visit(SmRectangleNode* node);
+ void Visit(SmVerticalBraceNode* node);
+ /** Clone a node */
+ 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 node */
+ 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* node);
+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* node);
+ /** Visit the children of a given node */
+ void VisitChildren(SmNode* node);
+};
+
+/////////////////////////////// SmNodeToTextVisitor ///////////////////////////////
+
+/** Extract command text from nodes */
+class SmNodeToTextVisitor : public SmVisitor {
+public:
+ SmNodeToTextVisitor(SmNode* pNode, String &rText)
+ : rCmdText(rText) {
+ pNode->Accept(this);
+ }
+ void Visit(SmTableNode* node);
+ void Visit(SmBraceNode* node);
+ void Visit(SmBracebodyNode* node);
+ void Visit(SmOperNode* node);
+ void Visit(SmAlignNode* node);
+ void Visit(SmAttributNode* node);
+ void Visit(SmFontNode* node);
+ void Visit(SmUnHorNode* node);
+ void Visit(SmBinHorNode* node);
+ void Visit(SmBinVerNode* node);
+ void Visit(SmBinDiagonalNode* node);
+ void Visit(SmSubSupNode* node);
+ void Visit(SmMatrixNode* node);
+ void Visit(SmPlaceNode* node);
+ void Visit(SmTextNode* node);
+ void Visit(SmSpecialNode* node);
+ void Visit(SmGlyphSpecialNode* node);
+ void Visit(SmMathSymbolNode* node);
+ void Visit(SmBlankNode* node);
+ void Visit(SmErrorNode* node);
+ void Visit(SmLineNode* node);
+ void Visit(SmExpressionNode* node);
+ void Visit(SmPolyLineNode* node);
+ void Visit(SmRootNode* node);
+ void Visit(SmRootSymbolNode* node);
+ void Visit(SmRectangleNode* node);
+ void Visit(SmVerticalBraceNode* node);
+private:
+ /** Extract text from a node that constitues a line */
+ void LineToText(SmNode* pNode) {
+ Append("{");
+ Separate();
+ if(pNode)
+ pNode->Accept(this);
+ Separate();
+ Append("}");
+ }
+ 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 nodes */
+ String &rCmdText;
+};
+
+#endif /* SMVISITORS_H */
diff --git starmath/sdi/smath.sdi starmath/sdi/smath.sdi
index a8fb305..8682f08 100644
--- starmath/sdi/smath.sdi
+++ starmath/sdi/smath.sdi
@@ -378,7 +378,7 @@ SfxVoidItem InsertCommand SID_INSERTCOMMAND
]
//--------------------------------------------------------------------------
-SfxVoidItem InsertConfigName SID_INSERTTEXT
+SfxVoidItem InsertConfigName SID_INSERTSYMBOL
()
[
/* flags: */
diff --git starmath/sdi/smslots.sdi starmath/sdi/smslots.sdi
index b8494c6..518b8b1 100644
--- starmath/sdi/smslots.sdi
+++ starmath/sdi/smslots.sdi
@@ -267,17 +267,17 @@ interface FormulaView : View
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 starmath/source/caret.cxx starmath/source/caret.cxx
new file mode 100644
index 0000000..bb312ef
--- /dev/null
+++ starmath/source/caret.cxx
@@ -0,0 +1,35 @@
+#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 starmath/source/cursor.cxx starmath/source/cursor.cxx
new file mode 100644
index 0000000..7da1fe6
--- /dev/null
+++ starmath/source/cursor.cxx
@@ -0,0 +1,1305 @@
+#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; //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, //Distance to current line squared
+ dbp_sq; //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
+ SetSelectionVisitor 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);
+
+ //Parse list of nodes to a tree
+ SmNodeListParser parser;
+ pLine = parser.Parse(pLineList);
+ delete pLineList;
+
+ //Insert it back into the parent
+ pLineParent->SetSubNode(nLineOffset, pLine);
+
+ //Rebuild graph of caret position
+ anchor = NULL;
+ position = NULL;
+ BuildGraph();
+ AnnotateSelection(); //Update selection annotation!
+
+ //Set caret position
+ if(!SetCaretPosition(PosAfterDelete, true))
+ SetCaretPosition(SmCaretPos(pLine, 0), true);
+
+ //End edit section
+ EndEdit();
+}
+
+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, 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;
+
+ //Parse line
+ SmNodeListParser parser;
+ pLine = parser.Parse(pLineList);
+ delete pLineList;
+
+ //Insert it back into the parent
+ pLineParent->SetSubNode(nParentIndex, pLine);
+
+ //Rebuild graph of caret position
+ anchor = NULL;
+ position = NULL;
+ BuildGraph();
+ AnnotateSelection(); //Update selection annotation!
+
+ //Set caret position
+ if(!SetCaretPosition(PosAfterInsert, true))
+ SetCaretPosition(SmCaretPos(pLine, 0), true);
+
+ //End edit section
+ EndEdit();
+}
+
+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){
+ 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
+ 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) {
+ BeginEdit();
+
+ 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!");
+
+ //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);
+
+ //Parse pLineList
+ pLine = SmNodeListParser().Parse(pLineList);
+ delete pLineList;
+ pLineList = NULL;
+
+ //Insert pLineList back into the tree
+ pLineParent->SetSubNode(nParentIndex, pLine);
+
+ //Rebuild graph of caret positions
+ anchor = NULL;
+ position = NULL;
+ BuildGraph();
+ AnnotateSelection();
+
+ //Set caret position
+ if(!SetCaretPosition(PosAfterScript, true))
+ SetCaretPosition(SmCaretPos(pScriptLine, 0), true);
+
+ EndEdit();
+}
+
+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
+ if(pSelectedNodesList->size() == 0)
+ pSelectedNodesList->push_front(new SmPlaceNode());
+
+ //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 PosAfterInsert = PatchLineList(pLineList, it);
+
+ //Parse line
+ pLine = SmNodeListParser().Parse(pLineList);
+ delete pLineList;
+
+ //Insert line back into tree
+ pLineParent->SetSubNode(nParentIndex, pLine);
+
+ //Rebuild graph of caret positions
+ anchor = NULL;
+ position = NULL;
+ BuildGraph();
+ AnnotateSelection();
+
+ //Set caret position
+ if(!SetCaretPosition(PosAfterInsert, true))
+ SetCaretPosition(SmCaretPos(pLine, 0), true);
+
+ EndEdit();
+}
+
+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;
+}
+
+void SmCursor::InsertText(XubString aString){
+ BeginEdit();
+
+ Delete();
+
+ //TODO: Use other values than FNT_VARIABLE for numbers and functions
+
+ 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);
+
+ 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 OverElement:
+ {
+ SmToken token;
+ token.eType = TOVER;
+ token.nGroup = TGPRODUCT;
+ token.aText.AssignAscii("over");
+ pNewNode = new SmBinVerNode(token);
+ SmNode *pRect = new SmRectangleNode(token),
+ *pExpr1 = new SmExpressionNode(SmToken()),
+ *pExpr2 = new SmExpressionNode(SmToken());
+ ((SmStructureNode*)pNewNode)->SetSubNodes(pExpr1, pRect, pExpr2);
+ }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) {
+ InsertCommandText(SmResId(nCommand));
+}
+
+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:
+ //TFACT is a postfix operator, so if there's a TFACT reverse subnode ordering
+ if(it->GetSubNode(1)->GetToken().eType == TFACT){
+ SmNodeArray aNodes(2);
+ aNodes[0] = it->GetSubNode(1);
+ aNodes[1] = it->GetSubNode(0);
+ ((SmStructureNode*)it.Current())->SetSubNodes(aNodes);
+ }
+ 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;
+ //Iterator reverse if pLine is unary node with a TFACT
+ SmNodeIterator it(pLine, pLine->GetType() == NUNHOR && pLine->GetSubNode(1)->GetToken().eType == TFACT);
+ 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::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(Token())
+ 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(Token() && IsRelationOperator(Token()->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(Token() && IsSumOperator(Token()->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(Token() && IsProductOperator(Token()->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(!Token())
+ return Error();
+ //Take care of unary operators
+ else if(IsUnaryOperator(Token()->GetToken()))
+ {
+ SmStructureNode *pUnary = new SmUnHorNode(SmToken());
+ SmNode *pOper = Token(),
+ *pArg;
+
+ if(Next())
+ pArg = Factor();
+ else
+ pArg = Error();
+
+ //TFACT is a postfix operator handle this case
+ if(pOper->GetToken().eType == TFACT)
+ pUnary->SetSubNodes(pArg, pOper);
+ else
+ pUnary->SetSubNodes(pOper, pArg);
+ return pUnary;
+ }else if(IsOperator(Token()->GetToken()))
+ return Error();
+ return Take();
+}
+
+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);
+}
+
+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 == TFACT ||
+ token.eType == TUOPER);
+}
diff --git starmath/source/dialog.cxx starmath/source/dialog.cxx
index f8b8f7e..82e386a 100644
--- starmath/source/dialog.cxx
+++ starmath/source/dialog.cxx
@@ -1477,8 +1477,8 @@ IMPL_LINK( SmSymbolDialog, GetClickHdl, Button *, EMPTYARG pButton )
aText += pSym->GetName();
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 starmath/source/document.cxx starmath/source/document.cxx
index 259c26d..5985aa2 100644
--- starmath/source/document.cxx
+++ starmath/source/document.cxx
@@ -94,7 +94,7 @@
#include "mathtype.hxx"
#include "mathmlimport.hxx"
#include "mathmlexport.hxx"
-
+#include "cursor.hxx"
using namespace ::com::sun::star;
@@ -248,6 +248,7 @@ void SmDocShell::Parse()
pTree = aInterpreter.Parse(aText);
nModifyCount++;
SetFormulaArranged( FALSE );
+ InvalidateCursor();
}
@@ -434,9 +435,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" );
@@ -477,8 +479,16 @@ void SmDocShell::Draw(OutputDevice &rDev, Point &rPosition)
rDev.SetLayoutMode( TEXT_LAYOUT_BIDI_LTR );
INT16 nDigitLang = rDev.GetDigitLanguage();
rDev.SetDigitLanguage( LANGUAGE_ENGLISH );
- //
- pTree->Draw(rDev, rPosition);
+
+ //Set selection if any
+ if(pCursor && bDrawSelection){
+ pCursor->AnnotateSelection();
+ SmSelectionDrawingVisitor(rDev, pTree, rPosition);
+ }
+
+ //Drawing using visitor
+ DrawingVisitor(rDev, rPosition, pTree);
+
//
rDev.SetLayoutMode( nLayoutMode );
rDev.SetDigitLanguage( nDigitLang );
@@ -519,6 +529,18 @@ Size SmDocShell::GetSize()
return aRet;
}
+void SmDocShell::InvalidateCursor(){
+ if(pCursor)
+ delete pCursor;
+ pCursor = NULL;
+}
+
+SmCursor& SmDocShell::GetCursor(){
+ if(!pCursor)
+ pCursor = new SmCursor(pTree, this);
+ return *pCursor;
+}
+
////////////////////////////////////////
SmPrinterAccess::SmPrinterAccess( SmDocShell &rDocShell )
@@ -691,6 +713,7 @@ SmDocShell::SmDocShell(SfxObjectCreateMode eMode,const sal_Bool _bScriptSupport)
nModifyCount ( 0 ),
bIsFormulaArranged ( FALSE )
{
+ pCursor = NULL;
RTL_LOGFILE_CONTEXT( aLog, "starmath: SmDocShell::SmDocShell" );
SetPool(&SFX_APP()->GetPool());
@@ -720,6 +743,11 @@ SmDocShell::~SmDocShell()
EndListening(aFormat);
EndListening(*pp->GetConfig());
+
+ if(pCursor)
+ delete pCursor;
+ pCursor = NULL;
+
delete pEditEngine;
SfxItemPool::Free(pEditEngineItemPool);
delete pTree;
@@ -751,6 +779,7 @@ BOOL SmDocShell::ConvertFrom(SfxMedium &rMedium)
{
delete pTree;
pTree = 0;
+ InvalidateCursor();
}
Reference xModel(GetModel());
SmXMLImportWrapper aEquation(xModel);
@@ -1313,7 +1342,7 @@ void SmDocShell::Draw(OutputDevice *pDevice,
pDevice->IntersectClipRegion(GetVisArea());
Point atmppoint;
- Draw(*pDevice, atmppoint);
+ DrawFormula(*pDevice, atmppoint);
}
SfxItemPool& SmDocShell::GetPool() const
diff --git starmath/source/edit.cxx starmath/source/edit.cxx
index 1994271..62905b4 100644
--- starmath/source/edit.cxx
+++ starmath/source/edit.cxx
@@ -122,9 +122,6 @@ SmEditWindow::SmEditWindow( SmCmdBoxWindow &rMyCmdBoxWin ) :
aModifyTimer.SetTimeout(2000);
aModifyTimer.Start();
- aCursorMoveTimer.SetTimeoutHdl(LINK(this, SmEditWindow, CursorMoveTimerHdl));
- aCursorMoveTimer.SetTimeout(500);
-
// if not called explicitly the this edit window within the
// command window will just show an empty gray panel.
Show();
@@ -133,7 +130,6 @@ SmEditWindow::SmEditWindow( SmCmdBoxWindow &rMyCmdBoxWin ) :
SmEditWindow::~SmEditWindow()
{
- aCursorMoveTimer.Stop();
aModifyTimer.Stop();
@@ -257,35 +253,6 @@ IMPL_LINK( SmEditWindow, ModifyTimerHdl, Timer *, EMPTYARG /*pTimer*/ )
return 0;
}
-
-IMPL_LINK(SmEditWindow, CursorMoveTimerHdl, Timer *, EMPTYARG /*pTimer*/)
- // every once in a while check cursor position (selection) of edit
- // window and if it has changed (try to) set the formula-cursor
- // according to that.
-{
- ESelection aNewSelection (GetSelection());
-
- if (!aNewSelection.IsEqual(aOldSelection))
- { SmViewShell *pView = rCmdBox.GetView();
-
- if (pView)
- {
- // get row and column to look for
- USHORT nRow, nCol;
- SmGetLeftSelectionPart(aNewSelection, nRow, nCol);
- nRow++;
- nCol++;
-
- pView->GetGraphicWindow().SetCursorPos(nRow, nCol);
-
- aOldSelection = aNewSelection;
- }
- }
-
- return 0;
-}
-
-
void SmEditWindow::Resize()
{
if (!pEditView)
@@ -319,8 +286,6 @@ void SmEditWindow::MouseButtonUp(const MouseEvent &rEvt)
else
Window::MouseButtonUp (rEvt);
- // ggf FormulaCursor neu positionieren
- CursorMoveTimerHdl(&aCursorMoveTimer);
InvalidateSlots();
}
@@ -425,10 +390,6 @@ void SmEditWindow::KeyInput(const KeyEvent& rKEvt)
}
else
{
- // Timer neu starten, um den Handler (auch bei laengeren Eingaben)
- // moeglichst nur einmal am Ende aufzurufen.
- aCursorMoveTimer.Start();
-
DBG_ASSERT( pEditView, "EditView missing (NULL pointer)" );
if (!pEditView)
CreateEditView();
@@ -631,7 +592,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 +615,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);
}
@@ -925,13 +889,6 @@ void SmEditWindow::Flush()
SID_TEXT, SFX_CALLMODE_STANDARD,
new SfxStringItem(SID_TEXT, GetText()), 0L);
}
-
- if (aCursorMoveTimer.IsActive())
- {
- aCursorMoveTimer.Stop();
- // ggf noch die (neue) FormulaCursor Position setzen
- CursorMoveTimerHdl(&aCursorMoveTimer);
- }
}
diff --git starmath/source/makefile.mk starmath/source/makefile.mk
index 912e6e6..da8046e 100644
--- starmath/source/makefile.mk
+++ starmath/source/makefile.mk
@@ -63,6 +63,9 @@ SLO1FILES = \
$(SLO)$/mathmlexport.obj \
$(SLO)$/format.obj \
$(SLO)$/node.obj \
+ $(SLO)$/visitors.obj \
+ $(SLO)$/caret.obj \
+ $(SLO)$/cursor.obj \
$(SLO)$/parse.obj \
$(SLO)$/utility.obj \
$(SLO)$/smdll.obj \
diff --git starmath/source/node.cxx starmath/source/node.cxx
index b243ced..cd68767 100644
--- starmath/source/node.cxx
+++ starmath/source/node.cxx
@@ -45,6 +45,7 @@
#include "smmod.hxx"
#include
#include
+#include "visitors.hxx"
#ifndef _MATHTYPE_HXX
#include "mathtype.hxx"
#endif
@@ -144,6 +145,8 @@ SmNode::SmNode(SmNodeType eNodeType, const SmToken &rNodeToken)
eScaleMode = SCALE_NONE;
aNodeToken = rNodeToken;
nAccIndex = -1;
+ SetSelected(false);
+ aParentNode = NULL;
}
@@ -448,28 +451,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'.
@@ -570,6 +551,98 @@ const SmNode * SmNode::FindNodeWithAccessibleIndex(xub_StrLen nAccIdx) const
return pResult;
}
+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 {"< n"< n"<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<<"];"<DumpAsDot(out, NULL, i, ++id, myid);
+
+ //If this is the root end the file
+ if( number == -1 )
+ out<<"}"< 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
-}
-
-
/**************************************************************************/
@@ -2445,29 +2411,6 @@ 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
{
@@ -2804,7 +2747,7 @@ void SmSpecialNode::Prepare(const SmFormat &rFormat, const SmDocShell &rDocShell
const SmSym *pSym;
SmModule *pp = SM_MOD1();
-
+
if (NULL != (pSym = pp->GetSymSetManager().GetSymbolByName(GetToken().aText)))
{
SetText( pSym->GetCharacter() );
@@ -2844,17 +2787,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);
-}
-
-
/**************************************************************************/
@@ -2965,5 +2897,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 starmath/source/parse.cxx starmath/source/parse.cxx
index aab3502..0bda46c 100644
--- starmath/source/parse.cxx
+++ starmath/source/parse.cxx
@@ -84,6 +84,19 @@ 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
@@ -1124,6 +1137,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);
@@ -1215,6 +1235,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;
@@ -1704,6 +1728,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;
@@ -2203,7 +2230,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);
}
@@ -2382,7 +2413,7 @@ SmNode *SmParser::Parse(const String &rBuffer)
{
BufferString = rBuffer;
BufferString.ConvertLineEnd( LINEEND_LF );
- BufferIndex =
+ BufferIndex = 0;
nTokenIndex = 0;
Row = 1;
ColOff = 0;
@@ -2402,6 +2433,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 starmath/source/view.cxx starmath/source/view.cxx
index f00036f..89f8ed7 100644
--- starmath/source/view.cxx
+++ starmath/source/view.cxx
@@ -65,6 +65,7 @@
#include
#include
#include
+#include
#include "view.hxx"
#include "config.hxx"
@@ -73,7 +74,7 @@
#include "starmath.hrc"
#include "toolbox.hxx"
#include "mathmlimport.hxx"
-
+#include "cursor.hxx"
#define MINWIDTH 200
#define MINHEIGHT 200
@@ -98,8 +99,7 @@ SmGraphicWindow::SmGraphicWindow(SmViewShell* pShell):
ScrollableWindow(&pShell->GetViewFrame()->GetWindow(), 0),
pAccessible(0),
pViewShell(pShell),
- nZoom(100),
- bIsCursorVisible(FALSE)
+ nZoom(100)
{
// docking windows are usually hidden (often already done in the
// resource) and will be shown by the sfx framework.
@@ -156,65 +156,32 @@ void SmGraphicWindow::MouseButtonDown(const MouseEvent& rMEvt)
{
ScrollableWindow::MouseButtonDown(rMEvt);
+ GrabFocus();
+
//
// set formula-cursor and selection of edit window according to the
// position clicked at
//
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);
/*
if (xAccessible.is())
{
@@ -240,69 +207,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_MOD1();
-
- 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");
@@ -310,25 +214,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_MOD1();
- if (pFound && pp->GetConfig()->IsShowFormulaCursor())
- ShowCursor(TRUE);
- }
+ //Draw cursor if any...
+ if(pViewShell->GetDoc()->HasCursor())
+ pViewShell->GetDoc()->GetCursor().Draw(*this, aPoint);
}
@@ -340,11 +231,105 @@ 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:
+ {
+ 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();
+ }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.InsertElement(OverElement);
+ 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 != 0){
+ rCursor.InsertText(code);
+ }else if (! (GetView() && GetView()->KeyInput(rKEvt)) )
+ ScrollableWindow::KeyInput(rKEvt);
+ }
+ }
+ }
}
@@ -1146,7 +1131,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();
@@ -1418,7 +1403,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:
@@ -1563,18 +1549,22 @@ 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());
break;
}
- case SID_INSERTTEXT:
+ case SID_INSERTSYMBOL:
{
const SfxStringItem& rItem =
- (const SfxStringItem&)rReq.GetArgs()->Get(SID_INSERTTEXT);
+ (const SfxStringItem&)rReq.GetArgs()->Get(SID_INSERTSYMBOL);
- if (pWin)
+ if (pWin && bInsertIntoEditWindow)
pWin->InsertText(rItem.GetValue());
+ if(GetDoc() && !bInsertIntoEditWindow)
+ GetDoc()->GetCursor().InsertSpecial(rItem.GetValue());
break;
}
diff --git starmath/source/visitors.cxx starmath/source/visitors.cxx
new file mode 100644
index 0000000..bd5d541
--- /dev/null
+++ starmath/source/visitors.cxx
@@ -0,0 +1,2229 @@
+#include "visitors.hxx"
+#include "cursor.hxx"
+
+///////////////////////////////////// SmVisitorTest /////////////////////////////////////
+
+void SmVisitorTest::Visit(SmTableNode* node) {
+ j_assert(node->GetType() == NTABLE, "the visitor-patterns isn't implemented correctly");
+ VisitChildren(node);
+}
+
+void SmVisitorTest::Visit(SmBraceNode* node) {
+ j_assert(node->GetType() == NBRACE, "the visitor-patterns isn't implemented correctly");
+ VisitChildren(node);
+}
+
+void SmVisitorTest::Visit(SmBracebodyNode* node) {
+ j_assert(node->GetType() == NBRACEBODY, "the visitor-patterns isn't implemented correctly");
+ VisitChildren(node);
+}
+
+void SmVisitorTest::Visit(SmOperNode* node) {
+ j_assert(node->GetType() == NOPER, "the visitor-patterns isn't implemented correctly");
+ VisitChildren(node);
+}
+
+void SmVisitorTest::Visit(SmAlignNode* node) {
+ j_assert(node->GetType() == NALIGN, "the visitor-patterns isn't implemented correctly");
+ VisitChildren(node);
+}
+
+void SmVisitorTest::Visit(SmAttributNode* node) {
+ j_assert(node->GetType() == NATTRIBUT, "the visitor-patterns isn't implemented correctly");
+ VisitChildren(node);
+}
+
+void SmVisitorTest::Visit(SmFontNode* node) {
+ j_assert(node->GetType() == NFONT, "the visitor-patterns isn't implemented correctly");
+ VisitChildren(node);
+}
+
+void SmVisitorTest::Visit(SmUnHorNode* node) {
+ j_assert(node->GetType() == NUNHOR, "the visitor-patterns isn't implemented correctly");
+ VisitChildren(node);
+}
+
+void SmVisitorTest::Visit(SmBinHorNode* node) {
+ j_assert(node->GetType() == NBINHOR, "the visitor-patterns isn't implemented correctly");
+ VisitChildren(node);
+}
+
+void SmVisitorTest::Visit(SmBinVerNode* node) {
+ j_assert(node->GetType() == NBINVER, "the visitor-patterns isn't implemented correctly");
+ VisitChildren(node);
+}
+
+void SmVisitorTest::Visit(SmBinDiagonalNode* node) {
+ j_assert(node->GetType() == NBINDIAGONAL, "the visitor-patterns isn't implemented correctly");
+ VisitChildren(node);
+}
+
+void SmVisitorTest::Visit(SmSubSupNode* node) {
+ j_assert(node->GetType() == NSUBSUP, "the visitor-patterns isn't implemented correctly");
+ VisitChildren(node);
+}
+
+void SmVisitorTest::Visit(SmMatrixNode* node) {
+ j_assert(node->GetType() == NMATRIX, "the visitor-patterns isn't implemented correctly");
+ VisitChildren(node);
+}
+
+void SmVisitorTest::Visit(SmPlaceNode* node) {
+ j_assert(node->GetType() == NPLACE, "the visitor-patterns isn't implemented correctly");
+ VisitChildren(node);
+}
+
+void SmVisitorTest::Visit(SmTextNode* node) {
+ j_assert(node->GetType() == NTEXT, "the visitor-patterns isn't implemented correctly");
+ VisitChildren(node);
+}
+
+void SmVisitorTest::Visit(SmSpecialNode* node) {
+ j_assert(node->GetType() == NSPECIAL, "the visitor-patterns isn't implemented correctly");
+ VisitChildren(node);
+}
+
+void SmVisitorTest::Visit(SmGlyphSpecialNode* node) {
+ j_assert(node->GetType() == NGLYPH_SPECIAL, "the visitor-patterns isn't implemented correctly");
+ VisitChildren(node);
+}
+
+void SmVisitorTest::Visit(SmMathSymbolNode* node) {
+ j_assert(node->GetType() == NMATH, "the visitor-patterns isn't implemented correctly");
+ VisitChildren(node);
+}
+
+void SmVisitorTest::Visit(SmBlankNode* node) {
+ j_assert(node->GetType() == NBLANK, "the visitor-patterns isn't implemented correctly");
+ VisitChildren(node);
+}
+
+void SmVisitorTest::Visit(SmErrorNode* node) {
+ j_assert(node->GetType() == NERROR, "the visitor-patterns isn't implemented correctly");
+ VisitChildren(node);
+}
+
+void SmVisitorTest::Visit(SmLineNode* node) {
+ j_assert(node->GetType() == NLINE, "the visitor-patterns isn't implemented correctly");
+ VisitChildren(node);
+}
+
+void SmVisitorTest::Visit(SmExpressionNode* node) {
+ j_assert(node->GetType() == NEXPRESSION, "the visitor-patterns isn't implemented correctly");
+ VisitChildren(node);
+}
+
+void SmVisitorTest::Visit(SmPolyLineNode* node) {
+ j_assert(node->GetType() == NPOLYLINE, "the visitor-patterns isn't implemented correctly");
+ VisitChildren(node);
+}
+
+void SmVisitorTest::Visit(SmRootNode* node) {
+ j_assert(node->GetType() == NROOT, "the visitor-patterns isn't implemented correctly");
+ VisitChildren(node);
+}
+
+void SmVisitorTest::Visit(SmRootSymbolNode* node) {
+ j_assert(node->GetType() == NROOTSYMBOL, "the visitor-patterns isn't implemented correctly");
+ VisitChildren(node);
+}
+
+void SmVisitorTest::Visit(SmRectangleNode* node) {
+ j_assert(node->GetType() == NRECTANGLE, "the visitor-patterns isn't implemented correctly");
+ VisitChildren(node);
+}
+
+void SmVisitorTest::Visit(SmVerticalBraceNode* node) {
+ j_assert(node->GetType() == NVERTICAL_BRACE, "the visitor-patterns isn't implemented correctly");
+ VisitChildren(node);
+}
+
+/////////////////////////////// SmDefaultingVisitor ////////////////////////////////
+
+void SmDefaultingVisitor::Visit(SmTableNode* node) {
+ DefaultVisit(node);
+}
+
+void SmDefaultingVisitor::Visit(SmBraceNode* node) {
+ DefaultVisit(node);
+}
+
+void SmDefaultingVisitor::Visit(SmBracebodyNode* node) {
+ DefaultVisit(node);
+}
+
+void SmDefaultingVisitor::Visit(SmOperNode* node) {
+ DefaultVisit(node);
+}
+
+void SmDefaultingVisitor::Visit(SmAlignNode* node) {
+ DefaultVisit(node);
+}
+
+void SmDefaultingVisitor::Visit(SmAttributNode* node) {
+ DefaultVisit(node);
+}
+
+void SmDefaultingVisitor::Visit(SmFontNode* node) {
+ DefaultVisit(node);
+}
+
+void SmDefaultingVisitor::Visit(SmUnHorNode* node) {
+ DefaultVisit(node);
+}
+
+void SmDefaultingVisitor::Visit(SmBinHorNode* node) {
+ DefaultVisit(node);
+}
+
+void SmDefaultingVisitor::Visit(SmBinVerNode* node) {
+ DefaultVisit(node);
+}
+
+void SmDefaultingVisitor::Visit(SmBinDiagonalNode* node) {
+ DefaultVisit(node);
+}
+
+void SmDefaultingVisitor::Visit(SmSubSupNode* node) {
+ DefaultVisit(node);
+}
+
+void SmDefaultingVisitor::Visit(SmMatrixNode* node) {
+ DefaultVisit(node);
+}
+
+void SmDefaultingVisitor::Visit(SmPlaceNode* node) {
+ DefaultVisit(node);
+}
+
+void SmDefaultingVisitor::Visit(SmTextNode* node) {
+ DefaultVisit(node);
+}
+
+void SmDefaultingVisitor::Visit(SmSpecialNode* node) {
+ DefaultVisit(node);
+}
+
+void SmDefaultingVisitor::Visit(SmGlyphSpecialNode* node) {
+ DefaultVisit(node);
+}
+
+void SmDefaultingVisitor::Visit(SmMathSymbolNode* node) {
+ DefaultVisit(node);
+}
+
+void SmDefaultingVisitor::Visit(SmBlankNode* node) {
+ DefaultVisit(node);
+}
+
+void SmDefaultingVisitor::Visit(SmErrorNode* node) {
+ DefaultVisit(node);
+}
+
+void SmDefaultingVisitor::Visit(SmLineNode* node) {
+ DefaultVisit(node);
+}
+
+void SmDefaultingVisitor::Visit(SmExpressionNode* node) {
+ DefaultVisit(node);
+}
+
+void SmDefaultingVisitor::Visit(SmPolyLineNode* node) {
+ DefaultVisit(node);
+}
+
+void SmDefaultingVisitor::Visit(SmRootNode* node) {
+ DefaultVisit(node);
+}
+
+void SmDefaultingVisitor::Visit(SmRootSymbolNode* node) {
+ DefaultVisit(node);
+}
+
+void SmDefaultingVisitor::Visit(SmRectangleNode* node) {
+ DefaultVisit(node);
+}
+
+void SmDefaultingVisitor::Visit(SmVerticalBraceNode* node) {
+ DefaultVisit(node);
+}
+
+
+/////////////////////////////// SmCaretDrawingVisitor ////////////////////////////////
+
+void SmCaretDrawingVisitor::Visit(SmTextNode* node) {
+ long i = pos.Index;
+
+ rDev.SetFont(node->GetFont());
+
+ //Find the line
+ SmNode* pLine = SmCursor::FindTopMostNodeInLine(node);
+
+ //Find coordinates
+ long left = node->GetLeft() + rDev.GetTextWidth(node->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* node){
+ rDev.SetLineColor(Color( COL_BLACK ));
+
+ //Find the line
+ SmNode* pLine = SmCursor::FindTopMostNodeInLine(node);
+
+ //Find coordinates
+ long left = node->GetLeft() + Offset.X() + (pos.Index == 1 ? node->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* node) {
+ //Save device state
+ pDev->Push( PUSH_FONT | PUSH_TEXTCOLOR );
+
+ long i = pos.Index;
+
+ pDev->SetFont(node->GetFont());
+
+ //Find coordinates
+ long left = node->GetLeft() + pDev->GetTextWidth(node->GetText(), 0, i);
+ long top = node->GetTop();
+ long height = node->GetHeight();
+
+ line = SmCaretLine(left, top, height);
+
+ //Restore device state
+ pDev->Pop();
+}
+
+void SmCaretPos2LineVisitor::DefaultVisit(SmNode* node){
+ //Vertical line (code from SmCaretDrawingVisitor)
+ Point p1 = node->GetTopLeft();
+ if(pos.Index == 1)
+ p1.Move(node->GetWidth(), 0);
+
+ line = SmCaretLine(p1.X(), p1.Y(), node->GetHeight());
+}
+
+/////////////////////////////// Nasty temporary device!!! ////////////////////////////////
+
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#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_MOD1()->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() ) );
+}
+
+/////////////////////////////// DrawingVisitor ////////////////////////////////
+
+void DrawingVisitor::Visit(SmTableNode* node) {
+ DrawChildren(node);
+}
+
+void DrawingVisitor::Visit(SmBraceNode* node) {
+ DrawChildren(node);
+}
+
+void DrawingVisitor::Visit(SmBracebodyNode* node) {
+ DrawChildren(node);
+}
+
+void DrawingVisitor::Visit(SmOperNode* node) {
+ DrawChildren(node);
+}
+
+void DrawingVisitor::Visit(SmAlignNode* node) {
+ DrawChildren(node);
+}
+
+void DrawingVisitor::Visit(SmAttributNode* node) {
+ DrawChildren(node);
+}
+
+void DrawingVisitor::Visit(SmFontNode* node) {
+ DrawChildren(node);
+}
+
+void DrawingVisitor::Visit(SmUnHorNode* node) {
+ DrawChildren(node);
+}
+
+void DrawingVisitor::Visit(SmBinHorNode* node) {
+ DrawChildren(node);
+}
+
+void DrawingVisitor::Visit(SmBinVerNode* node) {
+ DrawChildren(node);
+}
+
+void DrawingVisitor::Visit(SmBinDiagonalNode* node) {
+ DrawChildren(node);
+}
+
+void DrawingVisitor::Visit(SmSubSupNode* node) {
+ DrawChildren(node);
+}
+
+void DrawingVisitor::Visit(SmMatrixNode* node) {
+ DrawChildren(node);
+}
+
+void DrawingVisitor::Visit(SmPlaceNode* node) {
+ DrawSpecialNode(node);
+}
+
+void DrawingVisitor::Visit(SmTextNode* node) {
+ DrawTextNode(node);
+}
+
+void DrawingVisitor::Visit(SmSpecialNode* node) {
+ DrawSpecialNode(node);
+}
+
+void DrawingVisitor::Visit(SmGlyphSpecialNode* node) {
+ DrawSpecialNode(node);
+}
+
+void DrawingVisitor::Visit(SmMathSymbolNode* node) {
+ DrawSpecialNode(node);
+}
+
+void DrawingVisitor::Visit(SmBlankNode* node) {
+ DrawChildren(node);
+}
+
+void DrawingVisitor::Visit(SmErrorNode* node) {
+ DrawSpecialNode(node);
+}
+
+void DrawingVisitor::Visit(SmLineNode* node) {
+ DrawChildren(node);
+}
+
+void DrawingVisitor::Visit(SmExpressionNode* node) {
+ DrawChildren(node);
+}
+
+void DrawingVisitor::Visit(SmRootNode* node) {
+ DrawChildren(node);
+}
+
+void DrawingVisitor::Visit(SmVerticalBraceNode* node) {
+ DrawChildren(node);
+}
+
+void DrawingVisitor::Visit(SmRootSymbolNode* node) {
+ if (node->IsPhantom())
+ return;
+
+ // draw root-sign itself
+ DrawSpecialNode(node);
+
+
+ SmTmpDevice2 aTmpDev( (OutputDevice &) rDev, TRUE );
+ aTmpDev.SetFillColor(node->GetFont().GetColor());
+ rDev.SetLineColor();
+ aTmpDev.SetFont( node->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 = node->GetWidth() * 7L / 100L;
+ long nBarWidth = node->GetBodyWidth() + node->GetBorderWidth();
+ Point aBarOffset( node->GetWidth(), +node->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 (!node->IsDebug())
+ return;
+
+ int nRFlags = SM_RECT_CORE | SM_RECT_ITALIC | SM_RECT_LINES | SM_RECT_MID;
+ node->SmRect::Draw(rDev, Position, nRFlags);
+#endif
+}
+
+void DrawingVisitor::Visit(SmPolyLineNode* node) {
+ if (node->IsPhantom())
+ return;
+
+ long nBorderwidth = node->GetFont().GetBorderWidth();
+
+ LineInfo aInfo;
+ aInfo.SetWidth(node->GetWidth() - 2 * nBorderwidth);
+
+ Point aOffset (Point() - node->GetPolygon().GetBoundRect().TopLeft()
+ + Point(nBorderwidth, nBorderwidth)),
+ aPos (Position + aOffset);
+ node->GetPolygon().Move(aPos.X(), aPos.Y()); //Works because Polygon wraps a pointer
+
+ SmTmpDevice2 aTmpDev ((OutputDevice &) rDev, FALSE);
+ aTmpDev.SetLineColor( node->GetFont().GetColor() );
+
+ rDev.DrawPolyLine(node->GetPolygon(), aInfo);
+
+#ifdef SM_RECT_DEBUG
+ if (!node->IsDebug())
+ return;
+
+ int nRFlags = SM_RECT_CORE | SM_RECT_ITALIC | SM_RECT_LINES | SM_RECT_MID;
+ node->SmRect::Draw(rDev, Position, nRFlags);
+#endif
+}
+
+void DrawingVisitor::Visit(SmRectangleNode* node) {
+ if (node->IsPhantom())
+ return;
+
+ SmTmpDevice2 aTmpDev ((OutputDevice &) rDev, FALSE);
+ aTmpDev.SetFillColor(node->GetFont().GetColor());
+ rDev.SetLineColor();
+ aTmpDev.SetFont(node->GetFont());
+
+ ULONG nTmpBorderWidth = node->GetFont().GetBorderWidth();
+
+ // get rectangle and remove borderspace
+ Rectangle aTmp (node->AsRectangle() + Position - node->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 (!node->IsDebug())
+ return;
+
+ int nRFlags = SM_RECT_CORE | SM_RECT_ITALIC | SM_RECT_LINES | SM_RECT_MID;
+ node->SmRect::Draw(rDev, rPosition, nRFlags);
+#endif
+}
+
+void DrawingVisitor::DrawTextNode(SmTextNode* node){
+ if (node->IsPhantom() || node->GetText().Len() == 0 || node->GetText().GetChar(0) == xub_Unicode('\0'))
+ return;
+
+ SmTmpDevice2 aTmpDev ((OutputDevice &) rDev, FALSE);
+ aTmpDev.SetFont(node->GetFont());
+
+ Point aPos (Position);
+ aPos.Y() += node->GetBaselineOffset();
+ // auf Pixelkoordinaten runden
+ aPos = rDev.PixelToLogic( rDev.LogicToPixel(aPos) );
+
+ rDev.DrawStretchText(aPos, node->GetWidth(), node->GetText());
+
+#ifdef SM_RECT_DEBUG
+ if (!node->IsDebug())
+ return;
+
+ int nRFlags = SM_RECT_CORE | SM_RECT_ITALIC | SM_RECT_LINES | SM_RECT_MID;
+ node->SmRect::Draw(rDev, Position, nRFlags);
+#endif
+}
+
+void DrawingVisitor::DrawSpecialNode(SmSpecialNode* node) {
+ //! since this chars might come from any font, that we may not have
+ //! set to ALIGN_BASELINE yet, we do it now.
+ node->GetFont().SetAlign(ALIGN_BASELINE);
+
+ DrawTextNode(node);
+}
+
+/////////////////////////////// SetSelectionVisitor ////////////////////////////////
+
+void SetSelectionVisitor::SetSelectedOnAll(SmNode* pSubTree, bool IsSelected){
+ pSubTree->SetSelected(IsSelected);
+
+ //Quick BFS to set all selections
+ SmNode *pNode;
+ USHORT nSize = pSubTree->GetNumSubNodes();
+ for (USHORT i = 0; i < nSize; i++)
+ if (NULL != (pNode = pSubTree->GetSubNode(i)))
+ SetSelectedOnAll(pNode, IsSelected);
+}
+
+void SetSelectionVisitor::DefaultVisit(SmNode* node){
+ //Change state if StartPos is infront of this node
+ if(StartPos.pSelectedNode == node && StartPos.Index == 0)
+ IsSelecting = !IsSelecting;
+ //Change state if EndPos is infront of this node
+ if(EndPos.pSelectedNode == node && EndPos.Index == 0)
+ IsSelecting = !IsSelecting;
+
+ //Cache current state
+ BOOL WasSelecting = IsSelecting;
+ BOOL ChangedState = FALSE;
+
+ //Set selected
+ node->SetSelected(IsSelecting);
+
+ //Visit children
+ SmNodeIterator it(node);
+ while(it.Next()) {
+ it->Accept(this);
+ ChangedState = (WasSelecting != IsSelecting) || ChangedState;
+ }
+
+ //If state changed
+ if(ChangedState) {
+ //Select this node and all of it's children
+ SetSelectedOnAll(node, 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 == node && StartPos.Index == 1){
+ IsSelecting = !IsSelecting;
+ }
+ //Change state if EndPos is after of this node
+ if(EndPos.pSelectedNode == node && EndPos.Index == 1){
+ IsSelecting = !IsSelecting;
+ }
+}
+
+void SetSelectionVisitor::VisitCompositionNode(SmNode* node){
+ //Change state if StartPos is infront of this node
+ if(StartPos.pSelectedNode == node && StartPos.Index == 0)
+ IsSelecting = !IsSelecting;
+ //Change state if EndPos is infront of this node
+ if(EndPos.pSelectedNode == node && EndPos.Index == 0)
+ IsSelecting = !IsSelecting;
+
+ //Cache current state
+ bool WasSelecting = IsSelecting;
+
+ //Visit children
+ SmNode *pNode;
+ USHORT nSize = node->GetNumSubNodes();
+ for (USHORT i = 0; i < nSize; i++)
+ if (NULL != (pNode = node->GetSubNode(i)))
+ pNode->Accept(this);
+
+ //Set selected, if everything was selected
+ node->SetSelected(WasSelecting && IsSelecting);
+
+ //Change state if StartPos is after this node
+ if(StartPos.pSelectedNode == node && StartPos.Index == 1){
+ IsSelecting = !IsSelecting;
+ }
+ //Change state if EndPos is after of this node
+ if(EndPos.pSelectedNode == node && EndPos.Index == 1){
+ IsSelecting = !IsSelecting;
+ }
+}
+
+void SetSelectionVisitor::Visit(SmTextNode* node) {
+ long i1 = -1,
+ i2 = -1;
+ if(StartPos.pSelectedNode == node)
+ i1 = StartPos.Index;
+ if(EndPos.pSelectedNode == node)
+ i2 = EndPos.Index;
+
+ long start, end;
+ node->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 = node->GetText().Len();
+ IsSelecting = true;
+ }else if(!IsSelecting && i2 != -1){
+ start = i2;
+ end = node->GetText().Len();
+ IsSelecting = true;
+ }else if(IsSelecting){
+ start = 0;
+ end = node->GetText().Len();
+ }else{
+ node->SetSelected(false);
+ start = 0;
+ end = 0;
+ }
+ node->SetSelected(start != end);
+ node->SetSelectionStart(start);
+ node->SetSelectionEnd(end);
+}
+
+void SetSelectionVisitor::Visit(SmExpressionNode* node) {
+ VisitCompositionNode(node);
+}
+
+void SetSelectionVisitor::Visit(SmBracebodyNode* node) {
+ VisitCompositionNode(node);
+}
+
+void SetSelectionVisitor::Visit(SmAlignNode* node) {
+ VisitCompositionNode(node);
+}
+
+void SetSelectionVisitor::Visit(SmBinHorNode* node) {
+ VisitCompositionNode(node);
+}
+
+void SetSelectionVisitor::Visit(SmUnHorNode* node) {
+ VisitCompositionNode(node);
+}
+
+void SetSelectionVisitor::Visit(SmFontNode* node) {
+ VisitCompositionNode(node);
+}
+
+
+
+/////////////////////////////// SmCaretPosGraphBuildingVisitor ////////////////////////////////
+
+
+//Needs special care:
+void SmCaretPosGraphBuildingVisitor::Visit(SmTableNode* node){
+ //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(node);
+ 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* node){
+ pRightMost = NULL;
+ SmNodeIterator it(node);
+ 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* node){
+ SmCaretPosGraphEntry *left,
+ *right,
+ *bodyLeft,
+ *bodyRight;
+
+ left = pRightMost;
+ j_assert(pRightMost, "pRightMost shouldn't be NULL here!");
+
+ //Create bodyLeft
+ j_assert(node->GetBody(), "SmSubSupNode Doesn't have a body!");
+ bodyLeft = pGraph->Add(SmCaretPos(node->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(node, 1));
+
+ //Visit the body, to get bodyRight
+ pRightMost = bodyLeft;
+ node->GetBody()->Accept(this);
+ bodyRight = pRightMost;
+ bodyRight->SetRight(right);
+ right->SetLeft(bodyRight);
+
+ SmNode* pChild;
+ //If there's an LSUP
+ if((pChild = node->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 = node->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 = node->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 = node->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 = node->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 = node->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* node){
+ SmNode *pOper = node->GetSubNode(0),
+ *pBody = node->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(node, 1), bodyRight);
+ bodyRight->SetRight(right);
+
+ //Get subsup node 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* node){
+ SmCaretPosGraphEntry *left = pRightMost,
+ *right = pGraph->Add(SmCaretPos(node, 1));
+
+ for (USHORT i = 0; i < node->GetNumRows(); i++) {
+ SmCaretPosGraphEntry* r = left;
+ for (USHORT j = 0; j < node->GetNumCols(); j++){
+ SmNode* pNode = node->GetSubNode(i * node->GetNumCols() + j);
+
+ pRightMost = pGraph->Add(SmCaretPos(pNode, 0), r);
+ if(j != 0 || (node->GetNumRows() - 1) / 2 == i)
+ r->SetRight(pRightMost);
+
+ pNode->Accept(this);
+
+ r = pRightMost;
+ }
+ pRightMost->SetRight(right);
+ if((node->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* node){
+ j_assert(node->GetText().Len() > 0, "Empty SmTextNode is bad");
+
+ int size = node->GetText().Len();
+ for(int i = 1; i <= size; i++){
+ SmCaretPosGraphEntry* pRight = pRightMost;
+ pRightMost = pGraph->Add(SmCaretPos(node, 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* node){
+ //None if these children can be NULL, see SmBinVerNode::Arrange
+ SmNode *pNum = node->GetSubNode(0),
+ *pDenom = node->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(node, 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* node){
+ SmNode *pBody = node->GetSubNode(0),
+ *pScript = node->GetSubNode(2);
+ //None of these children can be NULL
+
+ SmCaretPosGraphEntry *left,
+ *bodyLeft,
+ *scriptLeft,
+ *right;
+
+ left = pRightMost;
+
+ //Create right
+ right = pGraph->Add(SmCaretPos(node, 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* node){
+ SmNode *A = node->GetSubNode(0),
+ *B = node->GetSubNode(1);
+
+ SmCaretPosGraphEntry *left,
+ *leftA,
+ *rightA,
+ *leftB,
+ *right;
+ left = pRightMost;
+
+ //Create right
+ right = pGraph->Add(SmCaretPos(node, 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* node){
+ SmNodeIterator it(node);
+ while(it.Next())
+ it->Accept(this);
+}
+void SmCaretPosGraphBuildingVisitor::Visit(SmUnHorNode* node){
+ // Unary operator node
+ SmNodeIterator it(node);
+ while(it.Next())
+ it->Accept(this);
+
+}
+
+void SmCaretPosGraphBuildingVisitor::Visit(SmExpressionNode* node){
+ SmNodeIterator it(node);
+ while(it.Next())
+ it->Accept(this);
+}
+
+void SmCaretPosGraphBuildingVisitor::Visit(SmFontNode* node){
+ //Has only got one child, should act as an expression if possible
+ SmNodeIterator it(node);
+ 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* node){
+ SmNodeIterator it(node);
+ while(it.Next())
+ it->Accept(this);
+}
+
+/** Build SmCaretPosGraph for SmAlignNode
+ * Acts as an SmExpressionNode, as it only has one child this okay
+ */
+void SmCaretPosGraphBuildingVisitor::Visit(SmAlignNode* node){
+ SmNodeIterator it(node);
+ 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* node){
+ SmNode *pExtra = node->GetSubNode(0), //Argument, NULL for sqrt, and SmTextNode if cubicroot
+ *pBody = node->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(node, 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* node){
+ SmCaretPosGraphEntry* right = pGraph->Add(SmCaretPos(node, 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* node){
+ SmCaretPosGraphEntry* right = pGraph->Add(SmCaretPos(node, 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* node){
+ SmNode* pBody = node->GetSubNode(1);
+
+ SmCaretPosGraphEntry *left = pRightMost,
+ *right = pGraph->Add(SmCaretPos(node, 1));
+
+ pRightMost = pGraph->Add(SmCaretPos(pBody, 0), left);
+ left->SetRight(pRightMost);
+
+ 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* node){
+ SmNode *pAttr = node->GetSubNode(0),
+ *pBody = node->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(node, 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* node) {
+ SmCaretPosGraphEntry* right = pGraph->Add(SmCaretPos(node, 1), pRightMost);
+ pRightMost->SetRight(right);
+ pRightMost = right;
+}
+void SmCaretPosGraphBuildingVisitor::Visit(SmGlyphSpecialNode* node) {
+ SmCaretPosGraphEntry* right = pGraph->Add(SmCaretPos(node, 1), pRightMost);
+ pRightMost->SetRight(right);
+ pRightMost = right;
+}
+void SmCaretPosGraphBuildingVisitor::Visit(SmMathSymbolNode* node) {
+ SmCaretPosGraphEntry* right = pGraph->Add(SmCaretPos(node, 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* node){
+ SmTableNode* pClone = new SmTableNode(node->GetToken());
+ CloneNodeAttr(node, pClone);
+ CloneKids(node, pClone);
+ pResult = pClone;
+}
+
+void SmCloningVisitor::Visit(SmBraceNode* node){
+ SmBraceNode* pClone = new SmBraceNode(node->GetToken());
+ CloneNodeAttr(node, pClone);
+ CloneKids(node, pClone);
+ pResult = pClone;
+}
+
+
+void SmCloningVisitor::Visit(SmBracebodyNode* node){
+ SmBracebodyNode* pClone = new SmBracebodyNode(node->GetToken());
+ CloneNodeAttr(node, pClone);
+ CloneKids(node, pClone);
+ pResult = pClone;
+}
+
+void SmCloningVisitor::Visit(SmOperNode* node){
+ SmOperNode* pClone = new SmOperNode(node->GetToken());
+ CloneNodeAttr(node, pClone);
+ CloneKids(node, pClone);
+ pResult = pClone;
+}
+
+void SmCloningVisitor::Visit(SmAlignNode* node){
+ SmAlignNode* pClone = new SmAlignNode(node->GetToken());
+ CloneNodeAttr(node, pClone);
+ CloneKids(node, pClone);
+ pResult = pClone;
+}
+
+void SmCloningVisitor::Visit(SmAttributNode* node){
+ SmAttributNode* pClone = new SmAttributNode(node->GetToken());
+ CloneNodeAttr(node, pClone);
+ CloneKids(node, pClone);
+ pResult = pClone;
+}
+
+void SmCloningVisitor::Visit(SmFontNode* node){
+ SmFontNode* pClone = new SmFontNode(node->GetToken());
+ pClone->SetSizeParameter(node->GetSizeParameter(), node->GetSizeType());
+ CloneNodeAttr(node, pClone);
+ CloneKids(node, pClone);
+ pResult = pClone;
+}
+
+void SmCloningVisitor::Visit(SmUnHorNode* node){
+ SmUnHorNode* pClone = new SmUnHorNode(node->GetToken());
+ CloneNodeAttr(node, pClone);
+ CloneKids(node, pClone);
+ pResult = pClone;
+}
+
+void SmCloningVisitor::Visit(SmBinHorNode* node){
+ SmBinHorNode* pClone = new SmBinHorNode(node->GetToken());
+ CloneNodeAttr(node, pClone);
+ CloneKids(node, pClone);
+ pResult = pClone;
+}
+
+void SmCloningVisitor::Visit(SmBinVerNode* node){
+ SmBinVerNode* pClone = new SmBinVerNode(node->GetToken());
+ CloneNodeAttr(node, pClone);
+ CloneKids(node, pClone);
+ pResult = pClone;
+}
+
+void SmCloningVisitor::Visit(SmBinDiagonalNode* node){
+ SmBinDiagonalNode *pClone = new SmBinDiagonalNode(node->GetToken());
+ pClone->SetAscending(node->IsAscending());
+ CloneNodeAttr(node, pClone);
+ CloneKids(node, pClone);
+ pResult = pClone;
+}
+
+void SmCloningVisitor::Visit(SmSubSupNode* node){
+ SmSubSupNode *pClone = new SmSubSupNode(node->GetToken());
+ pClone->SetUseLimits(node->IsUseLimits());
+ CloneNodeAttr(node, pClone);
+ CloneKids(node, pClone);
+ pResult = pClone;
+}
+
+void SmCloningVisitor::Visit(SmMatrixNode* node){
+ SmMatrixNode *pClone = new SmMatrixNode(node->GetToken());
+ pClone->SetRowCol(node->GetNumRows(), node->GetNumCols());
+ CloneNodeAttr(node, pClone);
+ CloneKids(node, pClone);
+ pResult = pClone;
+}
+
+void SmCloningVisitor::Visit(SmPlaceNode* node){
+ pResult = new SmPlaceNode(node->GetToken());
+ CloneNodeAttr(node, pResult);
+}
+
+void SmCloningVisitor::Visit(SmTextNode* node){
+ SmTextNode* pClone = new SmTextNode(node->GetToken(), node->GetFontDesc());
+ pClone->ChangeText(node->GetText());
+ CloneNodeAttr(node, pClone);
+ pResult = pClone;
+}
+
+void SmCloningVisitor::Visit(SmSpecialNode* node){
+ pResult = new SmSpecialNode(node->GetToken());
+ CloneNodeAttr(node, pResult);
+}
+
+void SmCloningVisitor::Visit(SmGlyphSpecialNode* node){
+ pResult = new SmGlyphSpecialNode(node->GetToken());
+ CloneNodeAttr(node, pResult);
+}
+
+void SmCloningVisitor::Visit(SmMathSymbolNode* node){
+ pResult = new SmMathSymbolNode(node->GetToken());
+ CloneNodeAttr(node, pResult);
+}
+
+void SmCloningVisitor::Visit(SmBlankNode* node){
+ SmBlankNode* pClone = new SmBlankNode(node->GetToken());
+ pClone->SetBlankNum(node->GetBlankNum());
+ pResult = pClone;
+ CloneNodeAttr(node, pResult);
+}
+
+void SmCloningVisitor::Visit(SmErrorNode* node){
+ //PE_NONE is used the information have been discarded and isn't used
+ pResult = new SmErrorNode(PE_NONE, node->GetToken());
+ CloneNodeAttr(node, pResult);
+}
+
+void SmCloningVisitor::Visit(SmLineNode* node){
+ SmLineNode* pClone = new SmLineNode(node->GetToken());
+ CloneNodeAttr(node, pClone);
+ CloneKids(node, pClone);
+ pResult = pClone;
+}
+
+void SmCloningVisitor::Visit(SmExpressionNode* node){
+ SmExpressionNode* pClone = new SmExpressionNode(node->GetToken());
+ CloneNodeAttr(node, pClone);
+ CloneKids(node, pClone);
+ pResult = pClone;
+}
+
+void SmCloningVisitor::Visit(SmPolyLineNode* node){
+ pResult = new SmPolyLineNode(node->GetToken());
+ CloneNodeAttr(node, pResult);
+}
+
+void SmCloningVisitor::Visit(SmRootNode* node){
+ SmRootNode* pClone = new SmRootNode(node->GetToken());
+ CloneNodeAttr(node, pClone);
+ CloneKids(node, pClone);
+ pResult = pClone;
+}
+
+void SmCloningVisitor::Visit(SmRootSymbolNode* node){
+ pResult = new SmRootSymbolNode(node->GetToken());
+ CloneNodeAttr(node, pResult);
+}
+
+void SmCloningVisitor::Visit(SmRectangleNode* node){
+ pResult = new SmRectangleNode(node->GetToken());
+ CloneNodeAttr(node, pResult);
+}
+
+void SmCloningVisitor::Visit(SmVerticalBraceNode* node){
+ SmVerticalBraceNode* pClone = new SmVerticalBraceNode(node->GetToken());
+ CloneNodeAttr(node, pClone);
+ CloneKids(node, 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* node){
+ if(node->IsSelected())
+ ExtendSelectionArea(node->AsRectangle());
+ VisitChildren(node);
+}
+
+void SmSelectionDrawingVisitor::VisitChildren(SmNode* node){
+ SmNodeIterator it( node );
+ while( it.Next() )
+ it->Accept( this );
+}
+
+void SmSelectionDrawingVisitor::Visit(SmTextNode* node){
+ if(node->IsSelected()){
+ rDev.Push(PUSH_TEXTCOLOR | PUSH_FONT);
+
+ rDev.SetFont(node->GetFont());
+ Point Position = node->GetTopLeft();
+ long left = Position.getX() + rDev.GetTextWidth(node->GetText(), 0, node->GetSelectionStart());
+ long right = Position.getX() + rDev.GetTextWidth(node->GetText(), 0, node->GetSelectionEnd());
+ long top = Position.getY();
+ long bottom = top + node->GetHeight();
+ Rectangle rect(left, top, right, bottom);
+
+ ExtendSelectionArea(rect);
+
+ rDev.Pop();
+ }
+}
+
+
+/////////////////////////////// SmNodeToTextVisitor ///////////////////////////////
+
+void SmNodeToTextVisitor::Visit(SmTableNode* node) {
+ if(node->GetToken().eType == TBINOM) {
+ Append("binom");
+ LineToText(node->GetSubNode(0));
+ LineToText(node->GetSubNode(1));
+ } else if(node->GetToken().eType == TSTACK) {
+ Append("stack{ ");
+ SmNodeIterator it(node);
+ 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(node);
+ it.Next();
+ while(true) {
+ Separate();
+ it->Accept(this);
+ if(it.Next()) {
+ Separate();
+ Append("newline");
+ }else
+ break;
+ }
+ }
+}
+
+void SmNodeToTextVisitor::Visit(SmBraceNode* node) {
+ SmNode *pLeftBrace = node->GetSubNode(0),
+ *pBody = node->GetSubNode(1),
+ *pRightBrace = node->GetSubNode(2);
+ //Handle special case where it's absolute function
+ if(node->GetToken().eType == TABS) {
+ Append("abs");
+ LineToText(pBody);
+ } else {
+ if(node->GetScaleMode() == SCALE_HEIGHT)
+ Append("left ");
+ pLeftBrace->Accept(this);
+ Separate();
+ pBody->Accept(this);
+ Separate();
+ if(node->GetScaleMode() == SCALE_HEIGHT)
+ Append("right ");
+ pRightBrace->Accept(this);
+ }
+}
+
+void SmNodeToTextVisitor::Visit(SmBracebodyNode* node) {
+ SmNodeIterator it(node);
+ while(it.Next()){
+ Separate();
+ it->Accept(this);
+ }
+}
+
+void SmNodeToTextVisitor::Visit(SmOperNode* node) {
+ Append(node->GetToken().aText);
+ Separate();
+ if(node->GetToken().eType == TOPER){
+ //There's an SmGlyphSpecialNode if eType == TOPER
+ if(node->GetSubNode(0)->GetType() == NSUBSUP)
+ Append(node->GetSubNode(0)->GetSubNode(0)->GetToken().aText);
+ else
+ Append(node->GetSubNode(0)->GetToken().aText);
+ }
+ if(node->GetSubNode(0)->GetType() == NSUBSUP) {
+ SmSubSupNode *pSubSup = (SmSubSupNode*)node->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(node->GetSubNode(1));
+}
+
+void SmNodeToTextVisitor::Visit(SmAlignNode* node) {
+ Append(node->GetToken().aText);
+ LineToText(node->GetSubNode(0));
+}
+
+void SmNodeToTextVisitor::Visit(SmAttributNode* node) {
+ Append(node->GetToken().aText);
+ LineToText(node->GetSubNode(1));
+}
+
+void SmNodeToTextVisitor::Visit(SmFontNode* node) {
+ switch (node->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 (node->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(node->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(node->GetSubNode(1));
+}
+
+void SmNodeToTextVisitor::Visit(SmUnHorNode* node) {
+ Append("{");
+ SmNode *pOper = node->GetSubNode(0),
+ *pRight = node->GetSubNode(1);
+ Separate();
+ pOper->Accept(this);
+ Separate();
+ pRight->Accept(this);
+ Append(" }");
+}
+
+void SmNodeToTextVisitor::Visit(SmBinHorNode* node) {
+ Append("{");
+ SmNode *pLeft = node->GetSubNode(0),
+ *pOper = node->GetSubNode(1),
+ *pRight = node->GetSubNode(2);
+ Separate();
+ pLeft->Accept(this);
+ Separate();
+ pOper->Accept(this);
+ Separate();
+ pRight->Accept(this);
+ Separate();
+ Append("}");
+}
+
+void SmNodeToTextVisitor::Visit(SmBinVerNode* node) {
+ SmNode *pNum = node->GetSubNode(0),
+ *pDenom = node->GetSubNode(2);
+ LineToText(pNum);
+ Append("over");
+ LineToText(pDenom);
+}
+
+void SmNodeToTextVisitor::Visit(SmBinDiagonalNode* node) {
+ SmNode *pLeftOperand = node->GetSubNode(0),
+ *pRightOperand = node->GetSubNode(1);
+ LineToText(pLeftOperand);
+ Separate();
+ Append("wideslash ");
+ LineToText(pRightOperand);
+}
+
+void SmNodeToTextVisitor::Visit(SmSubSupNode* node) {
+ LineToText(node->GetBody());
+ SmNode *pChild;
+ if((pChild = node->GetSubSup(LSUP))) {
+ Separate();
+ Append("lsup ");
+ LineToText(pChild);
+ }
+ if((pChild = node->GetSubSup(LSUB))) {
+ Separate();
+ Append("lsub ");
+ LineToText(pChild);
+ }
+ if((pChild = node->GetSubSup(RSUP))) {
+ Separate();
+ Append("rsup ");
+ LineToText(pChild);
+ }
+ if((pChild = node->GetSubSup(RSUB))) {
+ Separate();
+ Append("rsub ");
+ LineToText(pChild);
+ }
+ if((pChild = node->GetSubSup(CSUP))) {
+ Separate();
+ Append("csup ");
+ LineToText(pChild);
+ }
+ if((pChild = node->GetSubSup(CSUB))) {
+ Separate();
+ Append("csub ");
+ LineToText(pChild);
+ }
+}
+
+void SmNodeToTextVisitor::Visit(SmMatrixNode* node) {
+ Append("matrix{");
+ for (USHORT i = 0; i < node->GetNumRows(); i++) {
+ for (USHORT j = 0; j < node->GetNumCols(); j++) {
+ SmNode* pNode = node->GetSubNode(i * node->GetNumCols() + j);
+ Separate();
+ pNode->Accept(this);
+ Separate();
+ if(j != node->GetNumCols() - 1)
+ Append("#");
+ }
+ Separate();
+ if(i != node->GetNumRows() - 1)
+ Append("##");
+ }
+ Append("}");
+}
+
+void SmNodeToTextVisitor::Visit(SmPlaceNode*) {
+ Append(">");
+}
+
+void SmNodeToTextVisitor::Visit(SmTextNode* node) {
+ //TODO: This method might need improvements, see SmTextNode::CreateTextFromNode
+ if(node->GetToken().eType == TTEXT)
+ Append("\"");
+ Append(node->GetText());
+ if(node->GetToken().eType == TTEXT)
+ Append("\"");
+}
+
+void SmNodeToTextVisitor::Visit(SmSpecialNode* node) {
+ Append("%");
+ Append(node->GetToken().aText);
+}
+
+void SmNodeToTextVisitor::Visit(SmGlyphSpecialNode* node) {
+ if(node->GetToken().eType == TBOPER)
+ Append("boper ");
+ else
+ Append("uoper ");
+ Append(node->GetToken().aText);
+}
+
+void SmNodeToTextVisitor::Visit(SmMathSymbolNode* node) {
+ Append(node->GetToken().aText);
+}
+
+void SmNodeToTextVisitor::Visit(SmBlankNode* node) {
+ Append(node->GetToken().aText);
+}
+
+void SmNodeToTextVisitor::Visit(SmErrorNode*) {}
+
+void SmNodeToTextVisitor::Visit(SmLineNode* node) {
+ SmNodeIterator it(node);
+ while(it.Next()){
+ Separate();
+ it->Accept(this);
+ }
+}
+
+void SmNodeToTextVisitor::Visit(SmExpressionNode* node) {
+ Append("{ ");
+ SmNodeIterator it(node);
+ while(it.Next()) {
+ it->Accept(this);
+ Separate();
+ }
+ Append("}");
+}
+
+void SmNodeToTextVisitor::Visit(SmPolyLineNode*) {}
+
+void SmNodeToTextVisitor::Visit(SmRootNode* node) {
+ SmNode *pExtra = node->GetSubNode(0),
+ *pBody = node->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* node) {
+ SmNode *pBody = node->GetSubNode(0),
+ *pScript = node->GetSubNode(2);
+ LineToText(pBody);
+ Append(node->GetToken().aText);
+ LineToText(pScript);
+}
+